При разработке на платформе 1С 8.3 часто возникает задача расширения функционала существующих документов без изменения структуры метаданных в конфигураторе. Программисты используют динамическое добавление реквизитов и элементов формы. Однако многие сталкиваются с неприятным эффектом: данные в этих колонках заполняются, отображаются, но бесследно исчезают сразу после нажатия кнопки Записать. Давайте разберем природу этого явления и выясним, как обеспечить сохранность данных в программных колонках.
Проанализируем жизненный цикл управляемой формы в момент записи. Когда пользователь нажимает кнопку Записать, платформа выполняет целый каскад действий. Основная причина потери данных кроется в том, что программно добавленные реквизиты существуют только в контексте формы и не имеют соответствующих полей в базе данных (в структуре метаданных объекта).
Рассмотрим пошагово процесс записи на сервере:
ДанныеФормыСтруктура (реквизит Объект на форме) конвертируются в реальный объект ссылочного типа (например, ДокументОбъект).Выясним причину: данные не «стираются» каким-то специальным кодом, они просто замещаются актуальным состоянием объекта из базы данных, в котором этих колонок не существует.
Самый простой и эффективный способ борьбы с очисткой — повторное заполнение колонок сразу после того, как платформа обновила данные формы. Как было отмечено в обсуждении, нам необходимо задействовать два ключевых серверных обработчика формы.
Разберем, какие процедуры нам понадобятся:
ПриЧтенииНаСервере — вызывается при открытии формы существующего документа. Здесь мы заполняем колонки первый раз.ПослеЗаписиНаСервере — вызывается после завершения транзакции записи. Именно здесь мы восстанавливаем данные, которые затерлись при обновлении формы из объекта.Посмотрим на пример реализации кода. Для начала создадим общую процедуру заполнения, чтобы не дублировать логику:
&НаСервере
Процедура ЗаполнитьПрограммныеКолонкиНаСервере()
// Допустим, мы добавили колонку "ОстатокНаСкладе" в ТЧ "Товары"
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// Здесь может быть запрос к базе или расчет
СтрокаТЧ.ОстатокНаСкладе = ПолучитьОстатокПоНоменклатуре(СтрокаТЧ.Номенклатура);
КонецЦикла;
КонецПроцедуры
Теперь подключим эту процедуру к стандартным обработчикам событий формы:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Заполняем при открытии формы
ЗаполнитьПрограммныеКолонкиНаСервере();
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Восстанавливаем данные после того, как платформа их очистила при записи
ЗаполнитьПрограммныеКолонкиНаСервере();
КонецПроцедуры
Ситуация усложняется, если программные колонки предназначены не для вывода расчетных данных (как остатки), а для ручного ввода пользователем. В этом случае в ПослеЗаписиНаСервере нам нечего «вычислять», так как введенные пользователем данные уже пропали. Нам нужно их «спрятать» перед записью и «вернуть» после.
Рассмотрим алгоритм сохранения пользовательских данных:
ПередЗаписьюНаСервере выгружаем программные колонки во временную таблицу.ВременноеХранилище.ПослеЗаписиНаСервере достаем таблицу из хранилища и сопоставляем данные со строками табличной части.Проанализируем пример реализации этого метода:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Создаем таблицу для временного хранения данных программных колонок
ТаблицаДанных = Объект.Товары.Выгрузить( , "НомерСтроки, МояПрограммнаяКолонка");
// Сохраняем адрес в параметрах записи, чтобы передать его в ПослеЗаписиНаСервере
ПараметрыЗаписи.Вставить("АдресХранилищаДанных", ПоместитьВоВременноеХранилище(ТаблицаДанных, ЭтаФорма.УникальныйИдентификатор));
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Проверяем наличие нашего адреса в параметрах
Если ПараметрыЗаписи.Свойство("АдресХранилищаДанных") Тогда
ТаблицаДанных = ПолучитьИзВременногоХранилища(ПараметрыЗаписи.АдресХранилищаДанных);
// Возвращаем данные в таблицу формы
Для Каждого СтрокаИсточник Из ТаблицаДанных Цикл
// Поиск строки по номеру (или по Идентификатору строки, если ТЧ менялась)
СтрокаПриемник = Объект.Товары.НайтиСтрокуПоИдентификатору(СтрокаИсточник.НомерСтроки);
// Примечание: лучше использовать УникальныйИдентификатор строки для надежности
Если СтрокаПриемник <> Неопределено Тогда
СтрокаПриемник.МояПрограммнаяКолонка = СтрокаИсточник.МояПрограммнаяКолонка;
КонецЕсли;
КонецЦикла;
УдалитьИзВременногоХранилища(ПараметрыЗаписи.АдресХранилищаДанных);
КонецЕсли;
КонецПроцедуры
Стоит упомянуть важный нюанс, связанный с использованием метода РеквизитФормыВЗначение("Объект"). Если вы вызываете этот метод в своих серверных процедурах, вы создаете копию объекта на основе данных формы. Эта копия также ничего не знает о ваших программных колонках.
Если после модификации этого объекта вы вызываете ЗначениеВРеквизитФормы(Объект), то программные колонки в реквизите Объект на форме будут перезаписаны пустыми значениями из ОбъектЗначение. Поэтому, если вы используете эти методы, всегда вызывайте процедуру заполнения программных колонок сразу после ЗначениеВРеквизитФормы.
Подведем итог. Программные колонки — это мощный инструмент интерфейса, но они требуют «ручного управления» их состоянием. Выясним основные правила работы с ними:
ПриЧтенииНаСервере и ПослеЗаписиНаСервере.ВременноеХранилище для их «переброски» через момент физической записи документа.ИзменитьРеквизиты(), убедитесь, что путь к данным указан корректно (например, Объект.Товары.ИмяКолонки), иначе платформа не сможет связать элемент формы с данными в ТЧ.Строка, где данные хранятся в формате JSON.Используя эти методы, вы сможете создавать гибкие и динамичные интерфейсы, не теряя при этом данные в процессе работы пользователя с документом.