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