В практике разработки на платформе 1С:Предприятие 8.3 (Управляемое приложение) часто возникает задача предоставить пользователю возможность выбора элементов справочника (например, Номенклатура) по сложным правилам. Обычные отборы здесь не помогают, так как условия требуют соединения с таблицами регистров накопления, сведений или другими справочниками. При этом важно сохранить типовую конфигурацию на поддержке и не вносить изменения непосредственно в стандартные объекты.
В этой статье мы подробно разберем, почему не работает прямая модификация динамического списка на клиенте, рассмотрим три надежных способа открытия формы выбора со сложным произвольным запросом и решим проблему фильтрации при «вводе по строке» (автоподборе).
---
Рассмотрим типичную ошибку, с которой сталкиваются многие разработчики при попытке настроить форму выбора программно перед ее открытием:
ФормаВыбора = ПолучитьФорму("Справочник.Номенклатура.ФормаВыбора", , Элемент);
СвойстваСписка = ОбщегоНазначения.СтруктураСвойствДинамическогоСписка;
СвойстваСписка.ТекстЗапроса = МойТекстЗапроса;
ФормаВыбора.Элементы.Список.ПроизвольныйЗапрос = Истина;
// Попытка применить свойства...
Выясним причину, почему этот код не меняет поведение формы, и она открывается в стандартном виде:
ДинамическийСписок является серверным прикладным объектом. На клиенте разработчик видит лишь его визуальное отображение. Изменение структуры запроса, признака произвольного запроса и состава полей должно происходить исключительно в контексте сервера.ПолучитьФорму() инициализирует создание формы на сервере, в процессе чего вызывается и полностью отрабатывает обработчик события ПриСозданииНаСервере. На этом этапе динамический список уже скомпилирован сервером с использованием его исходного (типового) запроса. Любые попытки изменить свойства списка на клиенте перед вызовом Открыть() попросту игнорируются платформой.ФормаВыбора.Элементы.Список) в качестве параметров для серверных методов настройки свойств списков является грубой ошибкой. На сервере необходимо работать напрямую с реквизитом формы (ФормаВыбора.Список), а не с элементом интерфейса.---
Проанализируем наиболее современный, безопасный и чистый подход. Вместо устаревшей связки ПолучитьФорму() и попыток ручной настройки на клиенте, мы передадим управляющую структуру параметров в функцию ОткрытьФорму(), а саму обработку и модификацию запроса выполним на сервере внутри расширения.
Рассмотрим реализацию этого метода по шагам.
Шаг 1. Вызов формы из документа (на стороне клиента):
В форме документа, где пользователь выбирает номенклатуру, переопределим событие начала выбора реквизита:
&НаКлиенте
Процедура НоменклатураНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь; // Отменяем стандартное открытие формы выбора
// Формируем структуру параметров для передачи в форму выбора
ПараметрыВыбора = Новый Структура;
ПараметрыВыбора.Вставить("РежимСложногоФильтра", Истина);
// Передаем дополнительные параметры, необходимые для нашего запроса (например, склад)
ПараметрыВыбора.Вставить("ТекущийСклад", Объект.Склад);
ОткрытьФорму("Справочник.Номенклатура.ФормаВыбора", ПараметрыВыбора, Элемент);
КонецПроцедуры
Шаг 2. Перехват события создания формы в Расширении (на стороне сервера):
Создадим расширение конфигурации, добавим в него форму выбора справочника Номенклатура и создадим для нее обработчик После события ПриСозданииНаСервере. Это позволит нам безопасно изменить свойства динамического списка до того, как форма отобразится у пользователя.
&НаСервере
&После("ПриСозданииНаСервере")
Процедура Расш1_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, пришел ли специфический параметр из нашего документа
Если Параметры.Свойство("РежимСложногоФильтра") Тогда
// Включаем произвольный запрос для динамического списка "Список"
Список.ПроизвольныйЗапрос = Истина;
// Задаем новый текст запроса с соединением с регистром остатков
Список.ТекстЗапроса =
"ВЫБРАТЬ
| СправочникНоменклатура.Ссылка КАК Ссылка,
| СправочникНоменклатура.Код КАК Код,
| СправочникНоменклатура.Наименование КАК Наименование,
| ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
| ПО ТоварыНаСкладахОстатки.Номенклатура = СправочникНоменклатура.Ссылка";
// Устанавливаем параметры динамического списка
Список.Параметры.УстановитьЗначениеПараметра("Склад", Параметры.ТекущийСклад);
КонецЕсли;
КонецПроцедуры
Этот способ идеален тем, что типовая форма выбора не изменяется в основной конфигурации, а все доработки изолированы в расширении и активируются только по требованию.
---
Посмотрим на альтернативное решение, предложенное в ходе обсуждения. Если вам вообще не хочется использовать расширения для формы выбора справочника Номенклатура, мы можем перенести всю тяжелую логику выполнения сложного запроса на сторону вызывающего документа.
Идея заключается в следующем: мы выполняем сложный запрос на сервере в контексте документа, получаем массив ссылок на подходящую номенклатуру, а затем открываем типовую форму выбора, передавая этот массив в стандартный отбор по полю Ссылка с видом сравнения В Списке (в платформе для этого достаточно передать массив).
Разберем пример реализации данного подхода:
&НаКлиенте
Процедура НоменклатураНачалоВыбораОтбор(Элемент, ДанныеВыбора, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Получаем массив разрешенных ссылок номенклатуры на сервере
МассивРазрешенныхСсылок = ПолучитьМассивРазрешеннойНоменклатуры(Объект.Склад);
// Формируем стандартную структуру отбора
СтруктураОтбора = Новый Структура;
СтруктураОтбора.Вставить("Ссылка", МассивРазрешенныхСсылок);
ПараметрыВыбора = Новый Структура;
ПараметрыВыбора.Вставить("Отбор", СтруктураОтбора);
// Открываем стандартную форму выбора с наложенным фильтром
ОткрытьФорму("Справочник.Номенклатура.ФормаВыбора", ПараметрыВыбора, Элемент);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьМассивРазрешеннойНоменклатуры(Знач ТекущийСклад)
МассивРезультата = Новый Массив;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ТоварыНаСкладахОстатки.Номенклатура КАК Ссылка
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("Склад", ТекущийСклад);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
МассивРезультата.Добавить(Выборка.Ссылка);
КонецЦикла;
Возврат МассивРезультата;
КонецФункции
Преимущества метода: Полная безопасность при обновлениях конфигурации. Форма выбора номенклатуры открывается в абсолютно штатном режиме, не требуя никаких доработок внутри себя.
Ограничения метода: Если массив подходящих элементов будет содержать десятки тысяч позиций, передача такого объема данных между сервером и клиентом может снизить производительность системы.
---
Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), то во всех ключевых формах, как правило, встроены механизмы перехвата событий. Мы можем использовать общий переопределяемый модуль, который вызывается при создании любой формы в системе.
Например, в обработчике создания форм можно проанализировать имя открываемой формы и, если это форма выбора номенклатуры, программно перестроить её динамический список. Посмотрим, как это выглядит концептуально:
// В общем модуле (например, МодификацияИнтерфейсаПереопределяемый)
Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка) Экспорт
Если Форма.ИмяФормы = "Справочник.Номенклатура.ФормаВыбора" Тогда
// Проверяем наличие наших параметров
Если Форма.Параметры.Свойство("СложныйРежимВыбора") Тогда
// Изменяем текст запроса динамического списка непосредственно на сервере
Форма.Список.ПроизвольныйЗапрос = Истина;
Форма.Список.ТекстЗапроса = "ВЫБРАТЬ ...";
КонецЕсли;
КонецЕсли;
КонецПроцедуры
---
Второй важный вопрос темы: как сделать так, чтобы при быстром вводе номенклатуры с клавиатуры в поле ввода документа (ввод по строке/автоподбор) система предлагала только те товары, которые соответствуют нашему сложному запросу?
Если мы не ограничим автоподбор, пользователь сможет обойти ограничения формы выбора, просто начав писать название номенклатуры прямо в ячейке документа.
Самый простой и локальный метод — обработать событие автоподбора прямо у элемента формы поля ввода номенклатуры в документе. Нам не потребуется затрагивать справочник или использовать глобальные механизмы.
Разберем по шагам логику этого процесса:
АвтоПодбор.СтандартнаяОбработка = Ложь, тем самым отменяя стандартный платформенный поиск.Реализуем данный алгоритм в коде:
&НаКлиенте
Процедура НоменклатураАвтоПодбор(Элемент, Текст, ДанныеВыбора, Ожидание, СтандартнаяОбработка)
// Отменяем стандартный поиск платформы
СтандартнаяОбработка = Ложь;
// Если пользователь ничего не ввел, не нагружаем сервер
Если ПустаяСтрока(Текст) Тогда
Возврат;
КонецЕсли;
// Получаем подходящие данные с сервера
ДанныеВыбора = ПолучитьНоменклатуруДляАвтоподбора(Текст, Объект.Склад);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьНоменклатуруДляАвтоподбора(Знач ТекстПоиска, Знач ТекущийСклад)
СписокВыбора = Новый СписокЗначений;
Запрос = Новый Запрос;
// Пишем сложный запрос с поиском по наименованию и проверкой остатков
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ ТОП 30
| СправочникНоменклатура.Ссылка КАК Ссылка,
| СправочникНоменклатура.Наименование КАК Представление
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
| ПО ТоварыНаСкладахОстатки.Номенклатура = СправочникНоменклатура.Ссылка
|ГДЕ
| СправочникНоменклатура.Наименование ПОДОБНО &ШаблонПоиска";
// Формируем шаблон для поиска вхождения подстроки
Запрос.УстановитьПараметр("ШаблонПоиска", "%" + ТекстПоиска + "%");
Запрос.УстановитьПараметр("Склад", ТекущийСклад);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Добавляем в список выбора пару: Ссылка и текстовое Представление для отображения в выпадающем меню
СписокВыбора.Добавить(Выборка.Ссылка, Выборка.Представление);
КонецЦикла;
Возврат СписокВыбора;
КонецФункции
Этот метод полностью решает проблему ввода по строке в конкретном документе и гарантирует, что пользователь увидит в выпадающем списке автоподбора только те товары, которые действительно присутствуют на выбранном складе.
Если вам требуется, чтобы этот сложный автоподбор работал глобально во всей системе, или вы хотите связать параметры выбора через свойства реквизита метаданных, следует задействовать событие ОбработкаПолученияДанныхВыбора в модуле менеджера справочника Номенклатура.
Для реализации этого метода без изменения конфигурации:
Номенклатура.ОбработкаПолученияДанныхВыбора с типом вызова Вместо.Параметры на сервер.Пример кода в расширении модуля менеджера:
&Вместо("ОбработкаПолученияДанныхВыбора")
Процедура Расш1_ОбработкаПолученияДанныхВыбора(ДанныеВыбора, Параметры, СтандартнаяОбработка)
// Проверяем, передан ли наш специфический связующий параметр
Если Параметры.Свойство("ОтборПоСкладуОстатков") Тогда
СтандартнаяОбработка = Ложь; // Отменяем системный алгоритм подбора
СписокВыбора = Новый СписокЗначений;
ТекстПоиска = Параметры.СтрокаПоиска;
СкладОтбора = Параметры.ОтборПоСкладуОстатков;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ ТОП 20
| СправочникНоменклатура.Ссылка КАК Ссылка,
| СправочникНоменклатура.Наименование КАК Представление
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
| ПО ТоварыНаСкладахОстатки.Номенклатура = СправочникНоменклатура.Ссылка
|ГДЕ
| СправочникНоменклатура.Наименование ПОДОБНО &ШаблонПоиска";
Запрос.УстановитьПараметр("ШаблонПоиска", "%" + ТекстПоиска + "%");
Запрос.УстановитьПараметр("Склад", СкладОтбора);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СписокВыбора.Добавить(Выборка.Ссылка, Выборка.Представление);
КонецЦикла;
ДанныеВыбора = СписокВыбора;
КонецЕсли;
КонецПроцедуры
---
Мы подробно разобрали особенности работы с динамическими списками в управляемом интерфейсе 1С. Подведем итог проделанной работы:
ДинамическогоСписка на клиенте не работают из-за особенностей жизненного цикла управляемой формы.ОткрытьФорму() с последующей обработкой в расширении формы в событии ПриСозданииНаСервере.АвтоПодбор либо глобально через перехват метода ОбработкаПолученияДанныхВыбора в модуле менеджера справочника.