При разработке управляемых форм в платформе «1С:Предприятие 8.3» одной из наиболее частых задач является фильтрация данных в динамических списках. Обычно это делается легко через простые отборы. Однако ситуация существенно усложняется, когда нам требуется реализовать комбинированное условие. Например: отфильтровать список по переданным параметрам (организация, программа, подразделение), но при этом всегда показывать в списке один конкретный предопределенный элемент (например, элемент с кодом «000» или пустой элемент для выбора), независимо от того, подходит он под основные критерии фильтра или нет.
В этой статье мы подробно разберем, как правильно реализовать такую логику программно, исправим распространенные логические ошибки, которые часто допускают разработчики при работе с группами отборов, а также рассмотрим лучшие практики написания чистого и производительного кода.
Рассмотрим практическую ситуацию. У нас есть форма выбора или форма списка справочника. В форму передаются два параметра отбора: Программа и АБП. Нам нужно применить эти фильтры к динамическому списку. Но в справочнике также существует важный предопределенный элемент (например, с кодом «000»), который пользователи всегда должны иметь возможность выбрать. Если мы просто наложим стандартный отбор по параметрам, этот элемент с кодом «000» скроется, так как его реквизиты не соответствуют переданным параметрам программы и АБП.
Нам необходимо построить логическое условие следующего вида:
(Элемент.Программа = Параметр.Программа И Элемент.АБП = Параметр.АБП) ИЛИ (Элемент.Код = "000")
Выясним, как построить такую структуру программно с помощью объектной модели компоновки данных, используемой в динамических списках.
Часто разработчики пытаются решить задачу «в лоб», просто добавляя все элементы отбора в одну плоскую группу с типом «ИЛИ». Давайте посмотрим на пример такого кода, который ошибочно считают рабочим:
// ПРИМЕР С ОШИБКОЙ! НЕ ИСПОЛЬЗОВАТЬ В РЕАЛЬНЫХ ЗАДАЧАХ
СтандартнаяОбработка = Ложь;
Список.Отбор.Элементы.Очистить();
ГруппаОтбора = Список.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИЛИ;
Если Параметры.Свойство("Программа") Тогда
ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Программа");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Параметры.Программа;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
Если Параметры.Свойство("АБП") Тогда
ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("АБП");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Параметры.АБП;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Код");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = "000";
ЭлементОтбора.Использование = Истина;
Проанализируем ситуацию и поймем, почему этот код работает некорректно. Добавление всех элементов в одну группу ГруппаИЛИ приводит к тому, что платформа объединяет их по логическому закону «ИЛИ». В итоге система сформирует следующий запрос к базе данных:
Где (Программа = &Программа) ИЛИ (АБП = &АБП) ИЛИ (Код = "000")
Это критическая логическая ошибка! Если в форму переданы оба параметра (и программа, и АБП), то они должны фильтровать список одновременно (через логическое «И»), сужая выборку. В текущем же ошибочном варианте система покажет вообще все элементы с указанной программой (даже если у них другой АБП), а также вообще все элементы с указанным АБП (даже если у них другая программа). Фильтрация по пересечению параметров полностью ломается.
Чтобы реализовать математически верную логику, нам необходимо использовать вложенные группы отборов. Разберем по шагам правильный алгоритм построения дерева условий:
ГруппаИЛИ. Она будет отвечать за объединение основного фильтра и нашего исключения.ГруппаИ. Эта группа будет отвечать за строгое пересечение переданных параметров.Рассмотрим подробнее, как выглядит реализация этой схемы на встроенном языке 1С:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Очищаем существующие отборы динамического списка
Список.Отбор.Элементы.Очистить();
// Шаг 1. Создаем корневую группу ИЛИ
КорневаяГруппаИЛИ = Список.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
КорневаяГруппаИЛИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИЛИ;
КорневаяГруппаИЛИ.Использование = Истина;
// Шаг 2. Добавляем в группу ИЛИ условие-исключение (наш предопределенный элемент)
ЭлементИсключение = КорневаяГруппаИЛИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементИсключение.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
ЭлементИсключение.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
// Используем обращение к предопределенному элементу вместо жесткого кодирования строки
ЭлементИсключение.ПравоеЗначение = ПредопределенноеЗначение("Справочник.ИмяВашегоСправочника.ПустойЭлемент");
ЭлементИсключение.Использование = Истина;
// Шаг 3. Создаем вложенную группу И для основных параметров фильтрации
ВложеннаяГруппаИ = КорневаяГруппаИЛИ.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ВложеннаяГруппаИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;
ВложеннаяГруппаИ.Использование = Истина;
// Шаг 4. Наполняем вложенную группу И параметрами, если они переданы в форму
Если Параметры.Свойство("Программа") И ЗначениеЗаполнено(Параметры.Программа) Тогда
ЭтотОбъект.вхПрограмма = Параметры.Программа; // Сохраняем в реквизит формы при необходимости
ЭлементОтбора = ВложеннаяГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Программа");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Параметры.Программа;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
Если Параметры.Свойство("АБП") И ЗначениеЗаполнено(Параметры.АБП) Тогда
ЭтотОбъект.вхАБП = Параметры.АБП;
ЭлементОтбора = ВложеннаяГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("АБП");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Параметры.АБП;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
КонецПроцедуры
Данный подход гарантирует формирование абсолютно верного математического условия:
Где (Ссылка = &ПредопределенныйЭлемент) ИЛИ (Программа = &ПараметрПрограмма И АБП = &ПараметрАБП)
Список отобразит только те элементы, у которых совпадает и программа, и АБП, но при этом элемент-исключение всегда останется видимым в списке.
Давайте разберем несколько важных моментов оптимизации, которые сделают ваш код надежным и устойчивым к изменениям в будущем:
ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Код") и ПравоеЗначение = "000". Это крайне опасная практика. Если в пользовательском режиме кто-то случайно или намеренно изменит код этого элемента (например, переименует в «0001» или «00-000»), ваш программный отбор молча перестанет работать. Всегда используйте поле Ссылка и системную функцию ПредопределенноеЗначение(), обращающуюся к имени предопределенного элемента, заданному в конфигураторе. Поиск по ссылке также работает значительно быстрее, чем по строковым реквизитам.ЗначениеЗаполнено(). Если параметр передан пустым, добавлять под под него неактивный или пустой элемент отбора бессмысленно и вредно для производительности СУБД.Если вы ведете разработку в современной конфигурации на базе Библиотеки Стандартных Подсистем (БСП), вам не обязательно вручную конструировать объекты компоновки данных и писать громоздкий код. В БСП предусмотрены готовые инструменты в общем модуле ОбщегоНазначенияКлиентСервер.
Для создания групп отборов вы можете использовать функцию СоздатьГруппуЭлементовОтбора(). Рассмотрим пример того, как лаконично можно переписать наш алгоритм с использованием БСП:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Список.Отбор.Элементы.Очистить();
// Создаем корневую группу ИЛИ через БСП
КорневаяГруппаИЛИ = ОбщегоНазначенияКлиентСервер.СоздатьГруппуЭлементовОтбора(
Список.Отбор.Элементы,
"ГруппаИЛИ",
ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИЛИ
);
// Добавляем исключение
ОбщегоНазначенияКлиентСервер.ДобавитьЭлементОтбора(
КорневаяГруппаИЛИ,
"Ссылка",
ПредопределенноеЗначение("Справочник.ИмяВашегоСправочника.ПустойЭлемент"),
ВидСравненияКомпоновкиДанных.Равно
);
// Создаем вложенную группу И
ВложеннаяГруппаИ = ОбщегоНазначенияКлиентСервер.СоздатьГруппуЭлементовОтбора(
КорневаяГруппаИЛИ.Элементы,
"ГруппаИ",
ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ
);
// Добавляем параметры во вложенную группу
Если Параметры.Свойство("Программа") И ЗначениеЗаполнено(Параметры.Программа) Тогда
ОбщегоНазначенияКлиентСервер.ДобавитьЭлементОтбора(
ВложеннаяГруппаИ,
"Программа",
Параметры.Программа,
ВидСравненияКомпоновкиДанных.Равно
);
КонецЕсли;
Если Параметры.Свойство("АБП") И ЗначениеЗаполнено(Параметры.АБП) Тогда
ОбщегоНазначенияКлиентСервер.ДобавитьЭлементОтбора(
ВложеннаяГруппаИ,
"АБП",
Параметры.АБП,
ВидСравненияКомпоновкиДанных.Равно
);
КонецЕсли;
КонецПроцедуры
Использование БСП значительно повышает читаемость кода, стандартизирует разработку и защищает от случайных опечаток при написании типов и свойств объектов компоновщика настроек динамического списка.