Как программно добавить элемент отбора на управляемую форму списка и настроить фильтрацию по табличной части в расширении 1С

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

При разработке в среде «1С:Предприятие 8.3» перед программистами часто встает задача кастомизации типовых форм конфигурации без изменения основного кода. Использование механизмов расширений — стандарт де-факто для решения таких задач. Однако при попытке добавить элемент отбора на форму списка справочника или документа в расширении разработчики регулярно сталкиваются с неочевидными трудностями: от ошибок связывания реквизитов формы до падения производительности динамических списков при попытке отфильтровать данные по табличной части.

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

Часть 1. Программное добавление реквизита и элемента управления на управляемую форму

Довольно частой ошибкой при программном добавлении элементов на форму является неверное указание пути к данным или нарушение последовательности создания программных объектов. Давайте проанализируем типичный сценарий, в котором разработчик пытается связать программно созданное поле с новым реквизитом и получает ошибку «Недопустимое значение».

Выясним причину этой ошибки. Она кроется в двух ключевых моментах:

  1. Неверное указание префикса в пути к данным. При указании пути к данным нового реквизита формы верхнего уровня нельзя использовать префиксы типа Форма. или ЭтаФорма.. Платформа ожидает увидеть исключительно имя самого реквизита. Правильный путь к данным должен выглядеть так: "МойРеквизит". Если бы мы создавали колонку таблицы формы, путь был бы вида "ИмяТаблицы.ИмяКолонки".
  2. Нарушение фаз создания элементов. Процесс добавления новых реквизитов и элементов должен строго следовать схеме: сначала объявление структуры новых реквизитов, затем вызов метода ИзменитьРеквизиты(), и только после успешного изменения состава реквизитов формы — создание визуальных элементов (полей ввода, переключателей и т.д.) и привязка их к новым реквизитам через свойство ПутьКДанным.

Давайте посмотрим на практический пример реализации этого алгоритма в модуле управляемой формы. Поскольку мы работаем в расширении, мы будем использовать перехватчик события ПриСозданииНаСервере с аннотацией &После.


&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Шаг 1. Создаем массив добавляемых реквизитов
    ДобавляемыеРеквизиты = Новый Массив;
    
    // Описываем тип данных для нашего нового реквизита отбора (например, СправочникСсылка.Организации)
    МассивТипов = Новый Массив;
    МассивТипов.Добавить(Тип("СправочникСсылка.Организации"));
    ОписаниеТиповОрганизация = Новый ОписаниеТипов(МассивТипов);
    
    // Создаем сам реквизит формы. Третий параметр - Путь, оставляем пустым для верхнего уровня
    НовыйРеквизит = Новый РеквизитФормы("ОтборОрганизация", ОписаниеТиповОрганизация, , "Организация для фильтра", Истина);
    ДобавляемыеРеквизиты.Добавить(НовыйРеквизит);
    
    // Применяем изменения на форме
    ЭтаФорма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
    
    // Шаг 2. Создаем элемент формы (поле ввода) для нашего реквизита
    // Элемент разместим в стандартной группе "ГруппаБыстрыеОтборы" или непосредственно на форме
    РодительЭлемента = ЭтаФорма.Элементы.Найти("ГруппаБыстрыеОтборы");
    Если РодительЭлемента = Неопределено Тогда
        РодительЭлемента = ЭтаФорма;
    КонецЕсли;
    
    // Создаем элемент формы типа "ПолеФормы"
    НовоеПоле = ЭтаФорма.Элементы.Добавить("ОтборОрганизацияПоле", Тип("ПолеФормы"), РодительЭлемента);
    НовоеПоле.Вид = ВидПоляФормы.ПолеВвода;
    
    // КРИТИЧЕСКИ ВАЖНО: указываем ПутьКДанным БЕЗ префикса "Форма"
    НовоеПоле.ПутьКДанным = "ОтборОрганизация";
    НовоеПоле.КнопкаОчистки = Истина;
    
    // Подключаем обработчик события "ПриИзменении" для мгновенной фильтрации списка
    НовоеПоле.УстановитьДействие("ПриИзменении", "Подключаемый_ОтборОрганизацияПриИзменении");
    
КонецПроцедуры

Теперь на клиенте нам необходимо описать процедуру-обработчик, которая будет реагировать на изменение значения в созданном поле ввода и применять отбор к динамическому списку формы:


&НаКлиенте
Процедура Подключаемый_ОтборОрганизацияПриИзменении(Элемент)
    
    // Вызываем стандартный метод БСП для установки отбора динамического списка
    // Если БСП не используется, ниже мы разберем универсальный серверный метод
    ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
        Список, 
        "Организация", 
        ОтборОрганизация, 
        , 
        "Отбор по организации", 
        ЗначениеЗаполнено(ОтборОрганизация)
    );
    
КонецПроцедуры

Часть 2. Фильтрация динамического списка по табличной части объекта

Стандартный динамический список по умолчанию строит свой запрос по основной таблице метаданных (шапке справочника или документа). Если перед нами стоит задача отфильтровать элементы списка по значению, которое находится внутри подчиненной табличной части (например, найти все договоры, в которых в табличной части товаров присутствует определенная номенклатура), стандартные механизмы отбора компоновщика данных «в лоб» не сработают. Нам потребуется модифицировать запрос динамического списка.

Давайте подробно рассмотрим два основных архитектурных подхода к изменению текста запроса для фильтрации по табличным частям, проанализируем их плюсы и минусы с точки зрения производительности СУБД.

Вариант А. Использование левого соединения (LEFT JOIN)

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

Пример изменения текста запроса:


ВЫБРАТЬ
    СправочникДоговоры.Ссылка КАК Ссылка,
    СправочникДоговоры.Код КАК Код,
    СправочникДоговоры.Наименование КАК Наименование
ИЗ
    Справочник.ДоговорыКонтрагентов КАК СправочникДоговоры
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов.Товары КАК ДоговорыТовары
        ПО ДоговорыТовары.Ссылка = СправочникДоговоры.Ссылка

Существенный минус данного подхода: дублирование строк. Если в табличной части одного договора будет содержаться пять строк с товарами, левое соединение вернет пять записей для одного и того же договора в СУБД. На форме списка этот договор отобразится пять раз подряд! Для борьбы с этим эффектом приходится использовать инструкцию СГРУППИРОВАТЬ ПО или ключевое слово РАЗЛИЧНЫЕ. Это значительно снижает скорость выполнения запроса базой данных, ломает стандартную постраничную навигацию (динамическое считывание данных) и приводит к серьезным задержкам на больших объемах данных.

Вариант Б. Использование конструкции "ГДЕ Ссылка В (ВЫБРАТЬ...)" (Рекомендуемый способ)

Гораздо более производительным и изящным решением является использование вложенного запроса в секции условий ГДЕ. Этот способ исключает дублирование строк на уровне СУБД и сохраняет высокую скорость работы динамического считывания списка.

Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    СправочникДоговоры.Ссылка КАК Ссылка,
    СправочникДоговоры.Код КАК Код,
    СправочникДоговоры.Наименование КАК Наименование
ИЗ
    Справочник.ДоговорыКонтрагентов КАК СправочникДоговоры
ГДЕ
    (&ФильтрПоНоменклатуре = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
        ИЛИ СправочникДоговоры.Ссылка В (
            ВЫБРАТЬ
                ТчТовары.Ссылка
            ИЗ
                Справочник.ДоговорыКонтрагентов.Товары КАК ТчТовары
            ГДЕ
                ТчТовары.Номенклатура = &ФильтрПоНоменклатуре
        )
    )

В этом случае СУБД строит оптимальный план выполнения запроса, эффективно используя индексы табличной части, а строки в динамическом списке гарантированно не будут дублироваться.

Часть 3. Безопасная модификация запроса динамического списка в расширении

Когда мы работаем в расширении конфигурации, интерактивное (визуальное) изменение динамического списка через конструктор крайне нежелательно. Чтобы внести изменения в запрос визуально, платформа потребует заимствовать в расширение все объекты и реквизиты, задействованные в исходном запросе. Это приводит к значительному увеличению веса файла расширения, усложняет последующие обновления типовой конфигурации и может вызвать конфликты метаданных.

Рассмотрим три лучших способа программного изменения запроса списка непосредственно из кода расширения, гарантирующих стабильность системы при обновлениях.

Способ 1. Простая программная подмена текста запроса

Мы можем полностью заменить текст запроса динамического списка при создании формы на сервере. При этом нам не нужно заимствовать объекты метаданных — платформа скомпилирует программно переданный запрос в рантайме.


&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Задаем новый оптимизированный текст запроса динамического списка напрямую
    НовыйЗапрос = 
    "ВЫБРАТЬ
    |	ТаблицаИсточника.Ссылка КАК Ссылка,
    |	ТаблицаИсточника.Код КАК Код,
    |	ТаблицаИсточника.Наименование КАК Наименование
    |ИЗ
    |	Справочник.ДоговорыКонтрагентов КАК ТаблицаИсточника
    |ГДЕ
    |	(&НеИспользоватьОтбор ИЛИ ТаблицаИсточника.Ссылка В (
    |		ВЫБРАТЬ Тч.Ссылка 
    |		ИЗ Справочник.ДоговорыКонтрагентов.Товары КАК Тч 
    |		ГДЕ Тч.Номенклатура = &ОтборНоменклатура
    |	))";
    
    Список.ТекстЗапроса = НовыйЗапрос;
    
    // Устанавливаем начальные значения параметров
    Список.Параметры.УстановитьЗначениеПараметра("НеИспользоватьОтбор", Истина);
    Список.Параметры.УстановитьЗначениеПараметра("ОтборНоменклатура", Справочники.Номенклатура.ПустаяСсылка());
    
КонецПроцедуры

Способ 2. Динамическая замена фрагментов запроса на лету

Если оригинальный запрос динамического списка в типовой конфигурации очень сложный, содержит много условий и часто меняется разработчиками фирмы «1С» при обновлениях, полная замена текста запроса кодом может привести к критической ошибке при очередном обновлении. В таких случаях безопаснее внедрять наши условия динамически с помощью функций строковой замены:


&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    ИсходныйТекст = Список.ТекстЗапроса;
    
    // Нам нужно встроить условие фильтрации в секцию ГДЕ.
    // Если секция ГДЕ уже есть, мы можем привязаться к конструкции "ГДЕ" или внедрить условие в конец.
    // Пример безопасного внедрения нового условия в конец текста запроса:
    ДополнительноеУсловие = 
    " И (НЕ &ИспользоватьОтборПоТЧ ИЛИ Ссылка В (
    |   ВЫБРАТЬ Тч.Ссылка ИЗ Справочник.ДоговорыКонтрагентов.Товары КАК Тч ГДЕ Тч.Номенклатура = &ОтборНоменклатура
    |))";
    
    // Если в запросе уже есть условия, добавляем через И. 
    // Для надежности можно использовать СтрЗаменить или СхемаЗапроса (см. ниже)
    Если СтрНайти(ИсходныйТекст, "ГДЕ") > 0 Тогда
        Список.ТекстЗапроса = ИсходныйТекст + ДополнительноеУсловие;
    Иначе
        Список.ТекстЗапроса = ИсходныйТекст + " ГДЕ " + СтрЗаменить(ДополнительноеУсловие, " И (", " (");
    КонецЕсли;
    
    Список.Параметры.УстановитьЗначениеПараметра("ИспользоватьОтборПоТЧ", Ложь);
    Список.Параметры.УстановитьЗначениеПараметра("ОтборНоменклатура", Справочники.Номенклатура.ПустаяСсылка());
    
КонецПроцедуры

Способ 3. Оптимизация сброса настроек с помощью БСП

При прямом изменении свойства Список.ТекстЗапроса платформа полностью сбрасывает настройки компоновщика данных динамического списка (пользовательские настройки отборов, группировок и сортировок). Это приводит к дополнительным накладным расходам и замедлению работы интерфейса при открытии формы.

Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), настоятельно рекомендуется производить изменение параметров динамического списка с помощью специализированных процедур общего назначения. Это сохранит пользовательские настройки и минимизирует количество серверных вызовов:


&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Описываем новый запрос
    НовыйТекстЗапроса = "ВЫБРАТЬ ..."; 
    
    // Используем механизмы БСП для бесшовной установки свойств списка
    СвойстваСписка = ОбщегоНазначения.СтруктураСвойствДинамическогоСписка();
    СвойстваСписка.ТекстЗапроса = НовыйТекстЗапроса;
    СвойстваСписка.ОсновнаяТаблица = "Справочник.ДоговорыКонтрагентов";
    СвойстваСписка.ДинамическоеСчитываниеДанных = Истина;
    
    ОбщегоНазначения.УстановитьСвойстваДинамическогоСписка(Элементы.Список, СвойстваСписка);
    
КонецПроцедуры

Используя предложенные методики, мы можем гибко расширять функционал управляемых форм списков, обеспечивая пользователям удобный интерфейс фильтрации данных без потери производительности системы и сохраняя высокую степень обновляемости нашей конфигурации.

← На главную