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