Как получить разворачиваемую строку дерева значений на управляемой форме 1С без ее предварительного выделения?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с деревьями значений на управляемых формах 1С разработчики часто сталкиваются с задачей динамического (или так называемого «ленивого») заполнения подчиненных веток. Типичный сценарий: пользователь нажимает на значок «+» (развернуть) около строки, система перехватывает это действие, считывает данные из базы данных для этой конкретной строки, добавляет подчиненные элементы и показывает их пользователю.

Однако на этом пути подстерегает неприятная проблема. Если пользователь кликает по значку «+» без предварительного выделения самой строки, текущая строка на форме не меняется. Попытка получить строку через стандартное свойство элемента формы приводит к ошибкам, а передаваемый в событие параметр строки возвращает странные, постоянно растущие числа (например, вместо индекса 0 мы получаем 14, затем 28 и так далее). В этой статье мы подробно разберем, почему так происходит, и как реализовать задачу правильно и красиво с точки зрения стандартов платформы 1С:Предприятие 8.3.

Выясняем причину: почему растут идентификаторы и ломается дерево?

Давайте проанализируем ситуацию, с которой столкнулся автор темы. В обработчик события ПередРазворачиванием передаются три параметра: Элемент, Строка и Отказ.

Главное заблуждение начинающих разработчиков заключается в том, что в параметр Строка передается индекс строки (номер по порядку в таблице). Это не так. В этот параметр передается числовой внутренний идентификатор элемента коллекции ДанныеФормыДерево.

Когда мы пытаемся обновить дерево значений на сервере, используя привычный шаблон:


ДеревоФормы = РеквизитФормыВЗначение("Состав");
// ...какие-то манипуляции с деревом значений...
ЗначениеВРеквизитФормы(ДеревоФормы, "Состав");

Платформа выполняет полное пересоздание коллекции на стороне клиента. Происходит следующее:

  1. Предыдущие строки физически уничтожаются на форме.
  2. Новые строки создаются заново, даже если их состав не изменился.
  3. Каждому новому элементу платформа присваивает новые уникальные идентификаторы. Именно поэтому при каждом обновлении числа увеличивались на 14: старые идентификаторы «сгорали», а новые начинали свой отсчет дальше.
  4. Поскольку старых идентификаторов больше нет, визуальное состояние дерева сбрасывается — все ветки сворачиваются, а фокус активности теряется.

Для решения этой проблемы категорически нельзя перезаполнять всё дерево через конвертацию в прикладной объект ДеревоЗначений и обратно. Вместо этого мы должны использовать прямую модификацию коллекции прямо на сервере.

Правильный подход: Работа с ДанныеФормыДерево на сервере

Реквизиты управляемой формы, имеющие тип ДанныеФормыДерево, прекрасно поддаются изменению в серверном контексте без всяких конвертаций. Платформа 1С оптимизирована таким образом, что при точечном изменении элементов этой коллекции на клиенте обновляется только измененная «дельта». Идентификаторы строк при этом остаются неизменными, а состояние развернутости веток сохраняется.

Рассмотрим по шагам, как построить правильный алгоритм работы с динамическим деревом значений.

Шаг 1. Обработка события на клиенте

В клиентском событии ПередРазворачиванием мы должны получить идентификатор разворачиваемой строки и передать его на сервер. Если данные для этой ветки еще не были загружены, мы временно отменяем стандартное разворачивание, отправляем запрос на сервер, заполняем строку данными и затем программно разворачиваем ее.


&НаКлиенте
Процедура СоставПередРазворачиванием(Элемент, Строка, Отказ)
    
    // Проверим, загружены ли подчиненные элементы для этой строки.
    // Для этого напишем вспомогательную клиентскую функцию.
    Если ТребуетсяЗагрузкаДанныхСтроки(Строка) Тогда
        
        // Отменяем стандартное интерактивное разворачивание,
        // чтобы успеть подгрузить данные с сервера.
        Отказ = Истина;
        
        // Передаем идентификатор строки на сервер для заполнения данными
        ПодгрузитьПодчиненныеСтрокиНаСервере(Строка);
        
        // После успешной загрузки разворачиваем строку программно
        Элементы.Состав.Развернуть(Строка, Ложь);
        
    КонецЕсли;
    
КонецПроцедуры

Шаг 2. Определение необходимости загрузки на клиенте

Чтобы понять, нужно ли отправлять запрос на сервер, мы можем анализировать подчиненные элементы строки. Обычно при первоначальном заполнении дерева для узлов, у которых потенциально есть дети, добавляют одну пустую строку-маркер (так называемую «пустышку»). Наличие этой «пустышки» заставляет платформу отображать символ «+» рядом со строкой.


&НаКлиенте
Функция ТребуетсяЗагрузкаДанныхСтроки(ИдентификаторСтроки)
    
    // Получаем элемент дерева по его идентификатору на клиенте
    ТекущийЭлементДерева = Состав.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    Если ТекущийЭлементДерева = Неопределено Тогда
        Возврат Ложь;
    КонецЕсли;
    
    ПодчиненныеЭлементы = ТекущийЭлементДерева.ПолучитьЭлементы();
    
    // Если в подчиненных элементах ровно одна строка и у нее нет ключевых данных
    // (например, пустое субконто или специальный флаг-пустышка), значит требуется загрузка.
    Если ПодчиненныеЭлементы.Количество() = 1 И ПодчиненныеЭлементы[0].ЭтоПустышка = Истина Тогда
        Возврат Истина;
    КонецЕсли;
    
    Возврат Ложь;
    
КонецФункции

Шаг 3. Заполнение данных на сервере

На сервере мы находим строку в реквизите формы напрямую через метод НайтиПоИдентификатору. Обратите внимание, что мы работаем непосредственно с реквизитом формы Состав без вызова ресурсоемких методов сериализации.


&НаСервере
Процедура ПодгрузитьПодчиненныеСтрокиНаСервере(ИдентификаторСтроки)
    
    // Находим нужный элемент ДанныеФормыЭлементДерева в реквизите формы
    РодительскийЭлемент = Состав.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    Если РодительскийЭлемент = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    ПодчиненныеСтроки = РодительскийЭлемент.ПолучитьЭлементы();
    
    // Удаляем строку-пустышку, которая использовалась для отображения знака "+"
    ПодчиненныеСтроки.Очистить();
    
    // Получаем реальные данные из базы данных.
    // В качестве параметра передаем уникальный идентификатор или ссылку родителя.
    ТаблицаДанных = ПолучитьИзБДПодчиненныеЭлементы(РодительскийЭлемент.Ссылка);
    
    Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
        
        НоваяСтрока = ПодчиненныеСтроки.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
        
        // Если у добавленной строки в БД тоже могут быть подчиненные элементы,
        // сразу добавляем ей маркер-пустышку, чтобы у пользователя появился "+"
        Если СтрокаДанных.ИмеетПодчиненные Тогда
            СтрокаПустышка = НоваяСтрока.ПолучитьЭлементы().Добавить();
            СтрокаПустышка.ЭтоПустышка = Истина;
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Резюме преимуществ данного подхода

Использование прямой модификации объектов коллекции ДанныеФормыДерево по их идентификаторам имеет ключевые преимущества перед классической выгрузкой в полноценное дерево значений:

← На главную