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