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