Как передать таблицу значений из управляемой формы в модуль объекта 1С без создания реквизитов

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки отчетов на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с архитектурной дилеммой. Представьте ситуацию: на управляемой форме отчета расположена таблица значений, которую пользователь заполняет интерактивно или которая рассчитывается сложными алгоритмами непосредственно в модуле формы. Затем эти данные необходимо передать в модуль объекта для формирования отчета с использованием схемы компоновки данных (СКД), например, в качестве внешнего набора данных в обработчике ПриКомпоновкеРезультата.

Первое побуждение начинающего разработчика — попытаться напрямую "достучаться" из модуля объекта к элементам формы. Однако платформа спроектирована по принципу разделения интерфейса и бизнес-логики (паттерн MVC). Модуль объекта выполняется на сервере и ничего не знает о существовании формы, кнопок, переключателей или табличных частей на клиенте. Прямое обращение к форме из модуля объекта технически невозможно и методологически неверно.

В этой статье мы подробно разберем, как решить эту задачу изящно и профессионально, не забивая метаданные отчета временными реквизитами, и выясним, какие подводные камни могут подстерегать вас при работе в веб-клиенте.

Почему не стоит делать таблицу значений реквизитом объекта

Самый простой совет, который часто дают на форумах: «Сделай таблицу реквизитом объекта». На первый взгляд это решает проблему — реквизиты объекта доступны как в модуле формы, так и в модуле объекта. Но давайте проанализируем последствия такого подхода:

  1. Засорение метаданных. Создание реквизитов объекта ради временного хранения данных захламляет структуру конфигурации.
  2. Избыточный трафик. Реквизиты объекта постоянно гоняются между клиентом и сервером при контекстных вызовах, что снижает производительность системы, особенно в веб-клиенте и на слабых каналах связи.
  3. Проблемы масштабируемости. Если помимо таблицы значений вам понадобится передать структуру настроек, двоичные данные или соответствие, вам придется создавать все новые и новые реквизиты. Это путь к "говнокоду".

Рассмотрим по шагам правильные, профессиональные альтернативы.

Выясняем причину: почему временное хранилище «не работает» в веб-клиенте и фоновых заданиях

Использование временного хранилища — отличный способ передачи данных. Мы помещаем таблицу во временное хранилище на форме, получаем адрес (строку) и передаем этот адрес в модуль объекта. Однако на практике разработчики часто сталкиваются с тем, что в модуле объекта функция ПолучитьИзВременногоХранилища возвращает Неопределено.

Выясним причину этого поведения. В современных конфигурациях 1С (особенно построенных на базе БСП — Библиотеки стандартных подсистем) формирование отчетов СКД по умолчанию происходит асинхронно в фоновом задании на сервере. Фоновое задание запускается в отдельном сеансе. Временное хранилище, созданное в текущем сеансе пользователя (на форме), по умолчанию изолировано и недоступно из сеанса фонового задания. Именно поэтому у многих разработчиков этот механизм «вдруг перестает работать» при переходе на веб-клиент или при формировании отчета на сервере.

Чтобы обойти это ограничение, нам нужно связать время жизни и доступность временного хранилища с уникальным идентификатором формы или использовать специальные каналы передачи параметров.

Способ №1. Использование дополнительных свойств Компоновщика настроек (Рекомендуемый метод)

У объекта КомпоновщикНастроек есть коллекции настроек, содержащие свойство ДополнительныеСвойства (имеет тип Структура). Это свойство идеально подходит для передачи любых произвольных данных из формы в модуль объекта СКД без изменения метаданных.

Разберем по шагам реализацию этого метода. Сначала напишем код в модуле управляемой формы отчета перед началом компоновки:


&НаСервере
Процедура ПередЗапускомКомпоновкиНаСервере()
    
    // Получаем таблицу значений из реквизита формы
    ТаблицаДанных = РеквизитФормыВЗначение("ТаблицаНаФорме");
    
    // Помещаем таблицу во временное хранилище, привязывая её к УИД формы.
    // Это гарантирует, что данные не удалятся преждевременно.
    АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДанных, УникальныйИдентификатор);
    
    // Записываем адрес в дополнительные свойства пользовательских настроек компоновщика.
    // Рекомендуется использовать именно ПользовательскиеНастройки, так как основные настройки 
    // могут сбрасываться платформой при интерактивных действиях пользователя.
    Отчет.КомпоновщикНастроек.ПользовательскиеНастройки.ДополнительныеСвойства.Вставить("АдресТаблицыДляСКД", АдресХранилища);
    
КонецПроцедуры

Теперь перейдем в модуль объекта нашего отчета и примем эти данные в стандартном обработчике события ПриКомпоновкеРезультата:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    // Извлекаем коллекцию дополнительных свойств
    ДопСвойства = КомпоновщикНастроек.ПользовательскиеНастройки.ДополнительныеСвойства;
    
    Если ДопСвойства.Свойство("АдресТаблицыДляСКД") Тогда
        
        Адрес = ДопСвойства.АдресТаблицыДляСКД;
        
        // Получаем таблицу значений из хранилища
        ТаблицаДляСКД = ПолучитьИзВременногоХранилища(Адрес);
        
        Если ТипЗнч(ТаблицаДляСКД) = Тип("ТаблицаЗначений") Тогда
            
            // Отключаем стандартный вывод платформы, так как будем выводить отчет программно
            СтандартнаяОбработка = Ложь;
            
            // Настраиваем внешний набор данных. 
            // Имя в структуре должно строго совпадать с именем набора данных в схеме СКД.
            ВнешниеНаборыДанных = Новый Структура("ВнешнийНаборДанныхТЗ", ТаблицаДляСКД);
            
            // Инициализируем компоновку
            КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
            МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
            
            ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
            ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
            
            ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
            ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
            ПроцессорВывода.Вывести(ПроцессорКомпоновки);
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Этот способ элегантен тем, что мы не меняем структуру метаданных, сохраняем чистоту кода и можем передавать абсолютно любые типы данных: таблицы значений, структуры настроек, соответствия и даже двоичные данные (например, файлы картинок или макеты).

Способ №2. Полный программный вывод отчета на форме

Если логика работы вашего отчета тесно связана с интерактивными действиями на форме (пользователь сопоставляет номенклатуру, переключает флажки, правит показатели руками), то передавать управление в модуль объекта вообще не обязательно. Мы можем перехватить команду формирования отчета и выполнить построение СКД целиком в модуле формы.

Рассмотрим пример реализации такого подхода в модуле формы:


&НаСервере
Процедура СформироватьОтчетНаСервере()
    
    // Очищаем табличный документ на форме, куда выводится результат
    Результат.Очистить();
    
    // Получаем таблицу значений из реквизита формы
    ТаблицаДанных = РеквизитФормыВЗначение("ТаблицаНаФорме");
    
    // Получаем объект отчета для обращения к его свойствам
    ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
    СхемаСКД = ОтчетОбъект.СхемаКомпоновкиДанных;
    
    // Выполняем программную компоновку макета
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(
        СхемаСКД, 
        Отчет.КомпоновщикНастроек.ПолучитьНастройки()
    );
    
    // Связываем нашу ТЗ с именем внешнего источника данных в СКД
    ВнешниеНаборыДанных = Новый Структура("ВнешнийНаборДанныхТЗ", ТаблицаДанных);
    
    // Инициализируем процессор компоновки
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, , Истина);
    
    // Выводим результат в табличный документ на форме
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(Результат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);
    
КонецПроцедуры

При таком подходе модуль объекта остается абсолютно чистым, а вся логика связывания интерфейса с выводом данных сосредоточена в одном месте — модуле формы. Это значительно упрощает отладку и поддержку кода.

Способ №3. Использование "Дополнительных свойств" самого объекта

Этот способ применим в случаях, когда отчет или обработка вызываются программно из внешнего кода (например, из другой обработки или общего модуля). Каждый прикладной объект в 1С имеет стандартное свойство ДополнительныеСвойства, представляющее собой структуру.

Проанализируем пример передачи данных через свойства объекта. Код вызова в модуле управляемой формы:


&НаСервере
Процедура ПрограммныйЗапускОтчета()
    
    // Преобразуем реквизит формы в объект
    ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
    
    // Выгружаем таблицу значений формы
    ТЗ = РеквизитФормыВЗначение("ТаблицаНаФорме");
    
    // Передаем таблицу напрямую в структуру дополнительных свойств объекта
    ОтчетОбъект.ДополнительныеСвойства.Вставить("ПереданнаяТаблицаЗначений", ТЗ);
    
    // Запускаем стандартный метод компоновки объекта
    ОтчетОбъект.СкомпоноватьРезультат(Результат);
    
КонецПроцедуры

А в модуле объекта мы просто ловим это свойство в процедуре ПриКомпоновкеРезультата:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    Если ДополнительныеСвойства.Свойство("ПереданнаяТаблицаЗначений") Тогда
        
        ТаблицаДанных = ДополнительныеСвойства.ПереданнаяТаблицаЗначений;
        
        // Здесь пишем код программной инициализации процессора компоновки данных
        // с использованием нашей таблицы в качестве внешнего набора данных.
        
    КонецЕсли;
    
КонецПроцедуры

Подводим итоги

Мы рассмотрели несколько надежных способов передачи динамических данных из формы в модуль объекта без изменения метаданных конфигурации. Давайте обобщим правила выбора лучшего решения:

Использование данных методик позволит вам писать чистый, масштабируемый и отказоустойчивый код, соответствующий стандартам разработки фирмы "1С".

← На главную