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