Приветствуем вас, уважаемые разработчики! Сегодня мы подробно разберем одну очень распространенную юзабилити-задачу, с которой регулярно сталкиваются специалисты при проектировании интерфейсов в системе «1С:Предприятие 8.3». Представьте ситуацию: у нас есть бизнес-процесс или документ, содержащий табличную часть. В этой табличной части присутствует колонка с датой (например, ДатаВыполненияЗадачи). Для тех задач, которые еще не завершены, эта дата остается пустой, то есть принимает стандартное минимальное значение 0001-01-01.
Когда мы пытаемся отсортировать табличную часть по возрастанию даты, платформа вполне ожидаемо помещает незаполненные значения на самый верх списка. С точки зрения математики и логики СУБД это абсолютно верно, ведь пустая дата — самая маленькая. Однако для конечного пользователя это крайне неудобно: невыполненные задачи без даты завершения «мозолят глаза» вверху таблицы, мешая анализировать уже выполненные этапы в хронологическом порядке. Пользователю хочется видеть сначала завершенные задачи по порядку их выполнения, а все незавершенные (пустые) — аккуратно сложенными в самом низу списка. Как этого добиться? Давайте разберем по шагам несколько эффективных способов решения этой проблемы: от простых обходных путей до профессиональных серверных практик.
Прежде чем перейти к написанию кода, давайте проанализируем ситуацию. Стандартный метод табличной части Сортировать() работает по строгому математическому принципу сравнения значений. Если мы выполняем команду:
Объект.РезультатыВыполнения.Сортировать("ДатаВыполненияЗадачи Возр");
то система сравнивает значения в колонке напрямую. Пустая дата в 1С эквивалентна началу нашей эры. Любая реальная дата выполнения задачи (например, текущая дата) будет строго больше пустой. Соответственно, при сортировке по возрастанию (Возр) пустые ячейки неизбежно окажутся первыми.
На форуме разработчики предложили несколько интересных обходных путей. Мы детально рассмотрим каждый из них, а также добавим к ним профессиональные серверные практики и рекомендации стандартов разработки 1С.
Этот способ основан на подмене пустой даты максимальным возможным значением в скрытой колонке. Разберем этот алгоритм по шагам:
РезультатыВыполнения реквизит ДатаВыполненияЗадачиСорт с типом Дата.ДатаВыполненияЗадачи, а служебную колонку ДатаВыполненияЗадачиСорт сделаем невидимой, сняв флаг видимости в свойствах элемента формы.31.12.9999. Если дата заполнена, мы просто скопируем ее значение.ДатаВыполненияЗадачиСорт по возрастанию.Давайте посмотрим на пример реализации заполнения вспомогательного поля. Код обхода табличной части на сервере можно написать следующим образом:
&НаСервере
Процедура ЗаполнитьКолонкуДляСортировкиНаСервере()
ДатаКонцаСвета = Дата("99991231");
Для Каждого ТекСтрока Из Объект.РезультатыВыполнения Цикл
Если ТекСтрока.ДатаВыполненияЗадачи = Дата("00010101") Тогда
ТекСтрока.ДатаВыполненияЗадачиСорт = ДатаКонцаСвета;
Иначе
ТекСтрока.ДатаВыполненияЗадачиСорт = ТекСтрока.ДатаВыполненияЗадачи;
КонецЕсли;
КонецЦикла;
// Выполняем саму сортировку по служебному полю
Объект.РезультатыВыполнения.Сортировать("ДатаВыполненияЗадачиСорт Возр");
КонецПроцедуры
Этот метод абсолютно рабочий, но у него есть один минус: нам приходится перебирать всю коллекцию строк. Если строк в табличной части действительно много, можно прибегнуть к оптимизации через поиск строк с пустой датой с помощью метода НайтиСтроки():
&НаСервере
Процедура ОптимизированноеЗаполнениеСортировкиНаСервере()
// Заполняем сначала все строки реальными датами
Для Каждого ТекСтрока Из Объект.РезультатыВыполнения Цикл
ТекСтрока.ДатаВыполненияЗадачиСорт = ТекСтрока.ДатаВыполненияЗадачи;
КонецЦикла;
// Находим только те строки, где дата не заполнена, и заменяем их
ПараметрыОтбора = Новый Структура("ДатаВыполненияЗадачи", Дата("00010101"));
МассивПустыхСтрок = Объект.РезультатыВыполнения.НайтиСтроки(ПараметрыОтбора);
ДатаКонцаСвета = Дата("99991231");
Для Каждого ТекСтрока Из МассивПустыхСтрок Цикл
ТекСтрока.ДатаВыполненияЗадачиСорт = ДатаКонцаСвета;
КонецЦикла;
Объект.РезультатыВыполнения.Сортировать("ДатаВыполненияЗадачиСорт Возр");
КонецПроцедуры
Этот способ участники форума назвали более элегантным, и мы с ними полностью согласны. Вместо манипуляций с «магическими датами» конца света мы можем использовать логический признак завершенности задачи. Если задача выполнена, у нее есть дата, а флаг равен Истина. Если задача не выполнена, даты нет, флаг равен Ложь.
Если в вашей табличной части уже существует колонка ЗадачаВыполнена (тип Булево), вы можете использовать её для сортировки напрямую. Если такой колонки нет, её можно добавить в метаданные или рассчитывать динамически.
Давайте выясним причину, почему этот способ так красив. При сортировке булевы значения ведут себя предсказуемо: Ложь (0) меньше, чем Истина (1). Нам же нужно, чтобы пустые даты (то есть невыполненные задачи, у которых флаг выполнения равен Ложь) оказались в самом низу. Следовательно, по колонке ЗадачаВыполнена нам нужно применить сортировку по убыванию (Убыв). В таком случае сначала пойдут все строки со значением Истина (выполненные), а затем — Ложь (невыполненные).
Внутри группы выполненных задач нам нужно отсортировать строки по возрастанию даты выполнения. Код сортировки будет выглядеть предельно лаконично:
Объект.РезультатыВыполнения.Сортировать("ЗадачаВыполнена Убыв, ДатаВыполненияЗадачи Возр");
Вам даже не потребуется писать сложные циклы по перезаполнению дат! Система сначала сгруппирует все заполненные задачи и выстроит их по хронологии, а незаполненные аккуратно перенесет в самый конец списка.
А теперь давайте выйдем за рамки обсуждения на форуме и проанализируем ситуацию с точки зрения архитектуры крупных информационных систем. Добавление новых реквизитов в метаданные (в структуру табличной части документа или бизнес-процесса в конфигураторе) ради одной лишь визуальной сортировки на форме — это не самый лучший путь. Это усложняет структуру таблиц СУБД, увеличивает размер базы данных и требует монопольной реструктуризации при обновлении конфигурации.
Мы можем легко выполнить сортировку «на лету» прямо на сервере с помощью временных таблиц и языка запросов. Метод Выгрузить() позволяет получить данные табличной части в виде обычной таблицы значений, передать её в запрос в качестве параметра, динамически вычислить приоритеты строк с помощью конструкции ВЫБОР КОГДА и загрузить обратно уже отсортированный результат.
Разберем по шагам эту серверную процедуру:
&НаСервере
Процедура ОтсортироватьТабличнуюЧастьЗапросомНаСервере()
// Шаг 1. Выгружаем данные табличной части в Таблицу Значений
ТаблицаДанных = Объект.РезультатыВыполнения.Выгрузить();
// Шаг 2. Создаем запрос для сортировки в оперативной памяти сервера
ЗапросСортировки = Новый Запрос;
ЗапросСортировки.Текст =
"ВЫБРАТЬ * ИЗ &ТаблицаДанных КАК ТаблицаДанных
|УПОРЯДОЧИТЬ ПО
| ВЫБОР
| КОГДА ТаблицаДанных.ДатаВыполненияЗадачи = ДАТАВРЕМЯ(1, 1, 1) ТОГДА 1
| ИНАЧЕ 0
| КОНЕЦ ВОЗР,
| ТаблицаДанных.ДатаВыполненияЗадачи ВОЗР";
ЗапросСортировки.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
// Шаг 3. Загружаем отсортированный результат обратно в объект
Объект.РезультатыВыполнения.Загрузить(ЗапросСортировки.Выполнить().Выгрузить());
КонецПроцедуры
Проанализируем работу конструкции ВЫБОР в запросе. Если дата пустая (равна ДАТАВРЕМЯ(1,1,1)), выражение возвращает единицу (1). Если дата заполнена — возвращает ноль (0). При сортировке по возрастанию (ВОЗР) сначала пойдут строки с нулями (то есть заполненные даты), а строки с единицами (пустые даты) уйдут в конец списка. Вторым уровнем сортировки мы упорядочиваем реальные даты по возрастанию. Этот метод идеален: база данных чиста, метаданные не изменены, а сортировка выполняется за миллисекунды прямо в оперативной памяти сервера приложений 1С.
Часто разработчики вызывают метод Сортировать() непосредственно в клиентских процедурах управляемой формы, например, при открытии формы на клиенте или при нажатии кнопки пользователем. Важно понимать внутреннюю «кухню» платформы.
Коллекция ДанныеФормыКоллекция (которая представляет табличную часть на клиенте) действительно имеет метод Сортировать(), и он доступен в тонком клиенте. Но этот метод на клиенте работает через неявный (скрытый) вызов сервера. Платформа вынуждена сериализовать данные формы, передать их на сервер, там выполнить сортировку и вернуть измененное состояние обратно на клиент. Это приводит к лишней нагрузке на сеть и сервер. Стремитесь всегда переносить сортировку в серверный контекст формы (например, в процедуру ПриСозданииНаСервере или вызывать серверный метод принудительно, совмещая это с другими серверными операциями).
Если в вашей табличной части помимо даты есть сложные ссылочные поля (например, Исполнитель с типом СправочникСсылка.Пользователи) и вы планируете сортировать таблицу в том числе и по ним, помните о производительности. При стандартной сортировке по ссылочному полю система выполняет неявные запросы к базе данных для получения текстового представления каждой ссылки, чтобы сравнить их в алфавитном порядке. На больших таблицах это приводит к серьезным задержкам.
Чтобы избежать снижения производительности, используйте специальный системный объект СравнениеЗначений. Он позволяет платформе быстро сравнивать ссылки по их внутренним уникальным идентификаторам (GUID) без обращений к СУБД:
&НаСервере
Процедура БыстраяСортировкаСУчетомСсылок()
ОбъектСравнения = Новый СравнениеЗначений;
// Сортируем сначала по исполнителю (быстро по GUID), затем по дате
Объект.РезультатыВыполнения.Сортировать("Исполнитель Возр, ДатаВыполненияЗадачи Возр", ОбъектСравнения);
КонецПроцедуры
Если ваша табличная часть выводится на форму исключительно для просмотра, ознакомления и анализа (пользователь не редактирует строки вручную прямо в этой форме), то лучшим решением по стандартам юзабилити и производительности будет замена элемента табличной части на Динамический список с произвольным запросом.
В этом случае вы можете прямо в конфигураторе написать запрос к табличной части бизнес-процесса и добавить виртуальное поле сортировки:
ВЫБРАТЬ
БПРезультаты.Ссылка КАК Ссылка,
БПРезультаты.НомерСтроки КАК НомерСтроки,
БПРезультаты.ДатаВыполненияЗадачи КАК ДатаВыполненияЗадачи,
ВЫБОР
КОГДА БПРезультаты.ДатаВыполненияЗадачи = ДАТАВРЕМЯ(1, 1, 1)
ТОГДА ДАТАВРЕМЯ(3999, 12, 31)
ИНАЧЕ БПРезультаты.ДатаВыполненияЗадачи
КОНЕЦ КАК ДатаДляСортировки
ИЗ
БизнесПроцесс.ВыполнениеЗадачи.РезультатыВыполнения КАК БПРезультаты
ГДЕ
БПРезультаты.Ссылка = &Ссылка
На форме вы просто скрываете поле ДатаДляСортировки, но в настройках динамического списка задаете сортировку по умолчанию именно по этой колонке по возрастанию. Результат получится великолепным: данные упорядочиваются на уровне СУБД, форма открывается мгновенно, а код модуля формы остается абсолютно чистым!
Если вы всё же решили пойти по первому пути с заполнением пустых дат значением 31.12.9999 напрямую в объекте или таблице значений, вам обязательно нужно скрыть этот "технический мусор" от глаз пользователя. Никто не должен видеть пугающую дату конца света в интерфейсе программы.
Для этого настроим Условное оформление управляемой формы. Выполним следующие шаги:
Формат и задайте форматную строку, которая скрывает некорректные даты (например, использование формата ДФ=dd.MM.yyyy; ДП='Не выполнена', где параметр ДП задает текст для пустых дат).Объект.РезультатыВыполнения.ДатаВыполненияЗадачи Равно 31.12.9999 00:00:00.ДатаВыполненияЗадачи.Благодаря этому условному оформлению платформа на уровне базы данных будет использовать максимальную дату для идеальной сортировки строк, а на экране пользователя отобразится аккуратный и понятный текст "Не выполнена" (или пустая ячейка, если вы так настроите).
Мы подробно рассмотрели все возможные варианты решения этой задачи: от классического добавления реквизитов до передовых серверных методов сортировки запросом и условного оформления интерфейса. Выбирайте подход, наиболее подходящий под архитектуру вашей конфигурации, и делайте ваши интерфейсы в 1С удобными и быстрыми для пользователей!