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