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