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