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