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

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

Разработка интерфейсов на управляемых формах в 1С:Предприятии 8 нередко ставит перед разработчиком неочевидные задачи. Одной из таких проблем является программная сортировка дерева значений по колонке, на которой в данный момент сфокусирован пользователь. Пытаясь решить эту задачу стандартными методами, мы быстро сталкиваемся с ограничениями платформы: свойство ПутьКДанным у элементов формы недоступно на клиенте, а объект ДанныеФормыДерево лишен привычного метода Сортировать() в клиентском контексте.

В этой статье мы подробно проанализируем причины этих ограничений и разберем несколько практических способов реализации сортировки — от классического серверного метода до полностью автономного клиентского алгоритма.

Выясняем причину: почему стандартный подход не работает?

Рассмотрим типичный сценарий. На управляемой форме у нас есть реквизит с типом ДеревоЗначений, отображаемый в виде таблицы. Мы хотим, чтобы при нажатии на кнопку дерево сортировалось по текущей активной колонке. Напишем самый простой код:


ТекущееПоле = Элементы.ДеревоДанных.ТекущийЭлемент;
// Пытаемся получить имя колонки в источнике данных
ИмяКолонки = ТекущееПоле.ПутьКДанным; 

При выполнении этого кода на клиенте система незамедлительно выдаст ошибку. Дело в том, что свойство ПутьКДанным у элементов формы (объектов типа ПолеФормы) доступно только на сервере. На клиенте получить реальное имя реквизита напрямую из элемента управления невозможно.

Второй неприятный сюрприз: у объекта ДанныеФормыДерево на клиенте отсутствует метод Сортировать(), который есть у плоских таблиц (ДанныеФормыКоллекция). Попытка вызвать его на клиенте вызовет ошибку выполнения.

Давайте разберем три надежных способа обойти эти ограничения.

Способ 1. Классическая сортировка на сервере с преобразованием реквизита

Самый надежный и универсальный метод заключается в переносе всей логики на сервер. На сервере нам доступен и контекст элементов формы (со свойством ПутьКДанным), и возможность преобразовать реквизит формы в полноценное прикладное ДеревоЗначений.

Рассмотрим этот процесс по шагам:

  1. На клиенте мы определяем текущий элемент таблицы. Передать сам элемент на сервер нельзя (это недопустимый тип для передачи), поэтому мы передаем только его строковое имя (ТекущийЭлемент.Имя).
  2. На сервере находим элемент формы по переданному имени, извлекаем его ПутьКДанным и отсекаем префикс пути, чтобы получить чистое имя колонки.
  3. Конвертируем данные формы в объект дерева значений с помощью функции РеквизитФормыВЗначение().
  4. Сортируем строки прикладного объекта.
  5. Возвращаем измененный объект обратно на форму с помощью ЗначениеВРеквизитФормы().

Посмотрим на пример реализации данного метода:


&НаКлиенте
Процедура СортироватьДерево(Команда)
    ТекущийЭлемент = Элементы.ДеревоДанных.ТекущийЭлемент;
    Если ТекущийЭлемент = Неопределено Тогда
        ПоказатьПредупреждение(, "Не выбрана колонка для сортировки!");
        Возврат;
    КонецЕсли;
    
    // Передаем только строковое имя элемента управления
    СортироватьДеревоНаСервере(ТекущийЭлемент.Имя);
КонецПроцедуры

&НаСервере
Процедура СортироватьДеревоНаСервере(ИмяЭлементаКолонки)
    ЭлементКолонки = Элементы[ИмяЭлементаКолонки];
    
    // Преобразуем путь вида "ДеревоДанных.Номенклатура" в имя колонки "Номенклатура"
    МассивПути = СтрРазделить(ЭлементКолонки.ПутьКДанным, ".");
    ИмяКолонки = МассивПути[МассивПути.Количество() - 1];
    
    // Преобразуем данные формы в полноценное ДеревоЗначений
    ДеревоОбъект = РеквизитФормыВЗначение("ДеревоДанных");
    
    // Сортируем строки. Второй параметр "Истина" отвечает за сортировку подчиненных строк
    ДеревоОбъект.Строки.Сортировать(ИмяКолонки + " Возр", Истина);
    
    // Возвращаем результат обратно в реквизит формы
    ЗначениеВРеквизитФормы(ДеревоОбъект, "ДеревоДанных");
КонецПроцедуры

Преимущества метода: Работает со встроенной высокооптимизированной сортировкой платформы на уровне базы данных или оперативной памяти сервера. Сортирует неограниченные объемы данных мгновенно.

Недостатки: Требует полноценного серверного вызова с передачей всего контекста формы туда и обратно (сериализация/десериализация дерева значений), что может негативно сказаться на отзывчивости интерфейса при медленном интернет-соединении.

Способ 2. Кэширование путей к данным на клиенте через JSON

Если мы хотим избежать постоянных серверных вызовов только для того, чтобы узнать имя колонки, мы можем закэшировать соответствия «Имя элемента – Путь к данным» при создании формы.

Для этого создадим на сервере структуру соответствий, сериализуем ее в строку JSON и запишем в скрытый строковый реквизит формы КэшПутейКолонок. На клиенте мы сможем мгновенно прочитать эту информацию.

Разберем реализацию этого приема:


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

Теперь на клиенте мы можем легко и быстро определять имя колонки без единого обращения к серверу:


&НаКлиенте
Функция ПолучитьИмяКолонкиИзКэша(ИмяЭлемента)
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(КэшПутейКолонок);
    СоответствиеПутей = ПрочитатьJSON(ЧтениеJSON);
    Возврат СоответствиеПутей[ИмяЭлемента];
КонецФункции

Этот способ решает проблему определения имени колонки на клиенте раз и навсегда. Код остается динамическим: при добавлении новых колонок на форму вам не придется переписывать алгоритмы сопоставления.

Способ 3. Полностью клиентская сортировка дерева (без вызовов сервера)

Если дерево значений имеет небольшой или средний размер (до 500-1000 строк), мы можем отсортировать его непосредственно на клиенте. Для этого мы применим алгоритм сортировки методом сравнения и сдвига строк. Элементы коллекции строк дерева на клиенте поддерживают метод Сдвинуть(), что позволяет нам менять строки местами.

Проанализируем универсальную процедуру рекурсивной клиентской сортировки:


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

Теперь объединим кэширование путей к колонкам из Способа 2 и клиентскую сортировку из Способа 3 в единый обработчик нажатия кнопки на клиенте:


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

Подведем итоги: какой метод выбрать?

Выбор оптимального алгоритма напрямую зависит от объема данных в вашем дереве:

Использование кэширования метаданных колонок в JSON (Способ 2) рекомендуется в обоих случаях, так как это избавляет код от жестких конструкций Если...ИначеЕсли и делает решение полностью универсальным.

← На главную