Как открыть форму выбора с произвольным запросом динамического списка и настроить автоподбор в 1С

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

В практике разработки на платформе 1С:Предприятие 8.3 (Управляемое приложение) часто возникает задача предоставить пользователю возможность выбора элементов справочника (например, Номенклатура) по сложным правилам. Обычные отборы здесь не помогают, так как условия требуют соединения с таблицами регистров накопления, сведений или другими справочниками. При этом важно сохранить типовую конфигурацию на поддержке и не вносить изменения непосредственно в стандартные объекты.

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

---

Почему не работает настройка динамического списка на клиенте?

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


ФормаВыбора = ПолучитьФорму("Справочник.Номенклатура.ФормаВыбора", , Элемент);
СвойстваСписка = ОбщегоНазначения.СтруктураСвойствДинамическогоСписка;
СвойстваСписка.ТекстЗапроса = МойТекстЗапроса;
ФормаВыбора.Элементы.Список.ПроизвольныйЗапрос = Истина;
// Попытка применить свойства...

Выясним причину, почему этот код не меняет поведение формы, и она открывается в стандартном виде:

  1. Разделение контекста выполнения (Клиент / Сервер): Объект ДинамическийСписок является серверным прикладным объектом. На клиенте разработчик видит лишь его визуальное отображение. Изменение структуры запроса, признака произвольного запроса и состава полей должно происходить исключительно в контексте сервера.
  2. Жизненный цикл формы: Функция ПолучитьФорму() инициализирует создание формы на сервере, в процессе чего вызывается и полностью отрабатывает обработчик события ПриСозданииНаСервере. На этом этапе динамический список уже скомпилирован сервером с использованием его исходного (типового) запроса. Любые попытки изменить свойства списка на клиенте перед вызовом Открыть() попросту игнорируются платформой.
  3. Передача неверных типов данных: Использование клиентских элементов формы (таких как ФормаВыбора.Элементы.Список) в качестве параметров для серверных методов настройки свойств списков является грубой ошибкой. На сервере необходимо работать напрямую с реквизитом формы (ФормаВыбора.Список), а не с элементом интерфейса.

---

Способ 1. Передача параметров через «ОткрытьФорму» и обработка в расширении (Рекомендуемый метод)

Проанализируем наиболее современный, безопасный и чистый подход. Вместо устаревшей связки ПолучитьФорму() и попыток ручной настройки на клиенте, мы передадим управляющую структуру параметров в функцию ОткрытьФорму(), а саму обработку и модификацию запроса выполним на сервере внутри расширения.

Рассмотрим реализацию этого метода по шагам.

Шаг 1. Вызов формы из документа (на стороне клиента):

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


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

Шаг 2. Перехват события создания формы в Расширении (на стороне сервера):

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


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

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

---

Способ 2. Отбор по предварительно подготовленному массиву ссылок

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

Идея заключается в следующем: мы выполняем сложный запрос на сервере в контексте документа, получаем массив ссылок на подходящую номенклатуру, а затем открываем типовую форму выбора, передавая этот массив в стандартный отбор по полю Ссылка с видом сравнения В Списке (в платформе для этого достаточно передать массив).

Разберем пример реализации данного подхода:


&НаКлиенте
Процедура НоменклатураНачалоВыбораОтбор(Элемент, ДанныеВыбора, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    
    // Получаем массив разрешенных ссылок номенклатуры на сервере
    МассивРазрешенныхСсылок = ПолучитьМассивРазрешеннойНоменклатуры(Объект.Склад);
    
    // Формируем стандартную структуру отбора
    СтруктураОтбора = Новый Структура;
    СтруктураОтбора.Вставить("Ссылка", МассивРазрешенныхСсылок);
    
    ПараметрыВыбора = Новый Структура;
    ПараметрыВыбора.Вставить("Отбор", СтруктураОтбора);
    
    // Открываем стандартную форму выбора с наложенным фильтром
    ОткрытьФорму("Справочник.Номенклатура.ФормаВыбора", ПараметрыВыбора, Элемент);
    
КонецПроцедуры

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

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

Ограничения метода: Если массив подходящих элементов будет содержать десятки тысяч позиций, передача такого объема данных между сервером и клиентом может снизить производительность системы.

---

Способ 3. Модификация динамического списка через переопределяемые модули БСП

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

Например, в обработчике создания форм можно проанализировать имя открываемой формы и, если это форма выбора номенклатуры, программно перестроить её динамический список. Посмотрим, как это выглядит концептуально:


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

---

Реализация аналогичного отбора при «вводе по строке» (автоподборе)

Второй важный вопрос темы: как сделать так, чтобы при быстром вводе номенклатуры с клавиатуры в поле ввода документа (ввод по строке/автоподбор) система предлагала только те товары, которые соответствуют нашему сложному запросу?

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

Вариант А. Перехват события «АвтоПодбор» в форме документа (Без изменений справочника)

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

Разберем по шагам логику этого процесса:

  1. Для элемента формы поля ввода номенклатуры создаем обработчик события АвтоПодбор.
  2. Устанавливаем параметр СтандартнаяОбработка = Ложь, тем самым отменяя стандартный платформенный поиск.
  3. Вызываем неконтекстный серверный метод, который выполнит сложный поиск с учетом введенного текста и вернет отфильтрованный список значений.

Реализуем данный алгоритм в коде:


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

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

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

Вариант Б. Использование события «ОбработкаПолученияДанныхВыбора» в модуле менеджера

Если вам требуется, чтобы этот сложный автоподбор работал глобально во всей системе, или вы хотите связать параметры выбора через свойства реквизита метаданных, следует задействовать событие ОбработкаПолученияДанныхВыбора в модуле менеджера справочника Номенклатура.

Для реализации этого метода без изменения конфигурации:

  1. В расширении конфигурации заимствуем модуль менеджера справочника Номенклатура.
  2. Создаем перехват метода ОбработкаПолученияДанныхВыбора с типом вызова Вместо.
  3. Для передачи контекста (например, Склада) в модуль менеджера, настраиваем у реквизита документа свойство «Связи параметров выбора». Платформа автоматически передаст эти параметры в структуру Параметры на сервер.

Пример кода в расширении модуля менеджера:


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

---

Резюме

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

← На главную