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