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