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