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