При разработке в среде управляемых форм 1С программисты часто сталкиваются с задачей динамического добавления полей ввода на форму с последующим сохранением введенных пользователем данных в базу, например, в регистр сведений. Попытка передать эти данные через обычные переменные модуля (объявленные через Перем) между клиентскими и серверными процедурами приводит к ошибкам. Разберем по шагам, почему так происходит, как правильно создавать динамические реквизиты и как корректно сохранять их значения в событиях записи объекта.
Проанализируем архитектуру управляемого приложения. Процедуры с директивой &НаКлиенте и процедуры с директивой &НаСервере выполняются в совершенно разных контекстах — зачастую на физически разных машинах (на компьютере пользователя и на сервере 1С:Предприятия). Переменные модуля формы, объявленные через оператор Перем, не сериализуются и не синхронизируются автоматически между клиентом и сервером. На сервере они инициализируются заново и принимают значение Неопределено.
Для гарантированной передачи данных между контекстами формы необходимо использовать:
ИзменитьРеквизиты) — их значения автоматически передаются между клиентом и сервером в составе контекста формы.ПередЗаписью в серверные обработчики записи.Рассмотрим типовой сценарий, в котором реквизит и связанное с ним поле ввода создаются на сервере. Обратим внимание на частую синтаксическую ошибку: в метод Элементы.Добавить первым параметром нужно передавать строковое имя создаваемого элемента, а не сам объект реквизита.
Посмотрим на корректный пример добавления реквизита и элемента формы:
&НаСервере
Процедура ДобавитьДинамическийРеквизит(ИмяРеквизита, ОписаниеТипаРеквизита, ЗаголовокПоля)
// 1. Создаем массив новых реквизитов формы
НовыеРеквизиты = Новый Массив;
НовыйРеквизит = Новый РеквизитФормы(ИмяРеквизита, ОписаниеТипаРеквизита, , ЗаголовокПоля, Истина);
НовыеРеквизиты.Добавить(НовыйРеквизит);
// 2. Добавляем реквизит в состав формы
ЭтаФорма.ИзменитьРеквизиты(НовыеРеквизиты);
// 3. Создаем визуальный элемент на форме (передаем строковое имя элемента)
ИмяЭлемента = "Поле_" + ИмяРеквизита;
Элемент = ЭтаФорма.Элементы.Добавить(ИмяЭлемента, Тип("ПолеФормы"));
Элемент.Вид = ВидПоляФормы.ПолеВвода;
Элемент.ПутьКДанным = ИмяРеквизита;
Элемент.РастягиватьПоГоризонтали = Ложь;
КонецПроцедуры
После вызова метода ИзменитьРеквизиты созданный реквизит становится частью контекста формы. Обращаться к нему можно по его имени через обращение к свойствам формы по индексу — ЭтаФорма[ИмяРеквизита]. Это работает как на клиенте, так и на сервере.
Разберем, как прочитать введенное значение в обработчике ПередЗаписью на клиенте и передать его в цепочку серверных событий записи:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
ИмяРеквизита = "МойДинамическийРеквизит";
// Получаем значение динамического реквизита формы
ЗначениеРеквизита = ЭтаФорма[ИмяРеквизита];
// Помещаем значение в структуру ПараметрыЗаписи для передачи на сервер
ПараметрыЗаписи.Вставить("ЗначениеДляРегистра", ЗначениеРеквизита);
ПараметрыЗаписи.Вставить("ИмяХарактеристики", ИмяРеквизита);
КонецПроцедуры
Выясним причину частой ошибки при записи в регистры сведений: если объект создается впервые, до момента фактической записи в базу данных его ссылка (Объект.Ссылка) еще не существует или является пустой. Поэтому запись в регистр сведений нельзя выполнять до записи самого документа или справочника.
Оптимальным местом для сохранения связанных данных является серверный обработчик ПриЗаписиНаСервере. В этот момент основной объект уже имеет сформированную ссылку в базе данных, а запись выполняется в рамках единой транзакции.
Посмотрим на пример безопасного сохранения данных через НаборЗаписей:
&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Проверяем наличие переданных параметров
Если Не ПараметрыЗаписи.Свойство("ЗначениеДляРегистра") Тогда
Возврат;
КонецЕсли;
ЗначениеРеквизита = ПараметрыЗаписи.ЗначениеДляРегистра;
// Записываем данные с отбором по ссылке текущего объекта
НаборЗаписей = РегистрыСведений.Характеристики.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Кошелек.Установить(ТекущийОбъект.Ссылка);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Кошелек = ТекущийОбъект.Ссылка;
НоваяЗапись.ВидыСвойств = ПараметрыЗаписи.ИмяХарактеристики;
НоваяЗапись.Значение = ЗначениеРеквизита;
НаборЗаписей.Записать();
КонецПроцедуры
Если задача заключается в хранении дополнительных свойств объектов конфигурации, рассмотрим альтернативные стандартные варианты, не требующие ручного динамического добавления элементов управления:
ДополнительныеРеквизитыИСведения.Свойство и Значение), что исключает необходимость программного управления транзакциями записи.