Часто перед разработчиками 1С встает задача: как только динамический список на форме получил данные из базы, нужно взять часть этих данных (например, из первой строки или итоговые значения) и заполнить ими другие поля или реквизиты формы. Основная сложность заключается в том, что динамический список работает асинхронно, и в платформе нет очевидного клиентского события, которое бы срабатывало ровно в момент обновления данных. Давайте разберемся, как правильно отловить этот момент и обработать полученные данные.
Наиболее корректный и производительный способ решения этой задачи появился в платформе 1С начиная с версии 8.3.10. Он основан на использовании серверного события динамического списка ПриПолученииДанныхНаСервере в связке с клиентским механизмом оповещений.
Проанализируем ситуацию. Событие ПриПолученииДанныхНаСервере идеально подходит по своему назначению — оно срабатывает на сервере каждый раз, когда для списка получена очередная порция данных. Однако у него есть ключевая особенность: оно выполняется в бесконтекстном вызове. Это значит, что из обработчика этого события нет прямого доступа к реквизитам и элементам формы. Такое ограничение введено для оптимизации, чтобы не передавать весь "контекст" формы на сервер при каждом обращении списка за данными.
Как же тогда сообщить форме на клиенте, что данные получены и их можно использовать? Для этого и существует механизм оповещений.
Рассмотрим по шагам, как это реализовать:
ПриСозданииНаСервере, мы "подписываем" одну из клиентских процедур на получение уведомления с уникальным именем.ПриПолученииДанныхНаСервере, после того как мы получили доступ к строкам данных, мы генерируем оповещение с тем же именем.Посмотрим на пример кода:
Предположим, у нас есть динамический список СписокНоменклатуры, и нам нужно получить артикул из первой строки списка и поместить его в поле на форме.
1. Модуль формы - клиентская часть и создание подписки
// Клиентская экспортная процедура, которая будет вызвана по оповещению.
// Именно она получит данные с сервера и обновит элементы формы.
&НаКлиенте
Процедура ОбработатьПолученныеДанныеСписка(Параметры) Экспорт
// В параметрах пришли данные, которые мы передали из серверного события.
Если Параметры.Свойство("ПервыйАртикул") Тогда
// Здесь мы уже имеем доступ к элементам формы
Элементы.ПолеДляАртикула.Заголовок = "Артикул первой номенклатуры: " + Параметры.ПервыйАртикул;
КонецЕсли;
КонецПроцедуры
// В событии ПриСозданииНаСервере мы подписываем нашу процедуру на оповещение.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Подключаем обработчик. Первым параметром идет имя оповещения,
// вторым - имя клиентской процедуры-обработчика.
ПодключитьОбработчикОповещения("СписокНоменклатуры_ДанныеПолучены", "ОбработатьПолученныеДанныеСписка");
КонецПроцедуры
2. Модуль формы - серверная часть и обработка события списка
// Событие динамического списка "СписокНоменклатуры"
&НаСервере
Процедура СписокНоменклатурыПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Проверяем, что строки вообще были получены
Если Строки.Количество() > 0 Тогда
// Получаем первую строку из полученной порции данных
ПерваяСтрока = Строки[0];
// Создаем структуру, чтобы передать данные на клиента
ПараметрыДляКлиента = Новый Структура;
ПараметрыДляКлиента.Вставить("ПервыйАртикул", ПерваяСтрока.Данные.Артикул);
// Генерируем оповещение с тем же именем, на которое мы подписались
Оповестить("СписокНоменклатуры_ДанныеПолучены", ПараметрыДляКлиента);
КонецЕсли;
КонецПроцедуры
Этот подход является наиболее правильным и рекомендованным. Он не создает лишней нагрузки и позволяет элегантно связать серверное событие с клиентской логикой.
Как выяснилось в обсуждении на форуме, событие ПриПолученииДанныхНаСервере недоступно в старых версиях платформы, например, в 8.3.9. В этом случае приходится использовать обходные пути, которые менее эффективны, но позволяют решить задачу.
1. Использование обработчика ожидания
Это самый распространенный "костыльный" метод. Суть в том, что мы запускаем на клиенте периодически выполняемую процедуру, которая проверяет, появились ли строки в динамическом списке. Как только строки появляются, обработчик выполняет нужные действия и отключается.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Запускаем проверку раз в секунду. Интервал нужно подбирать осторожно.
ПодключитьОбработчикОжидания("ПроверитьЗаполнениеСписка", 1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьЗаполнениеСписка()
// Проверяем, есть ли элементы в списке
Если Элементы.СписокНоменклатуры.ВсегоСтрок > 0 Тогда
// Данные появились! Отключаем обработчик, чтобы он не выполнялся постоянно.
ОтключитьОбработчикОжидания("ПроверитьЗаполнениеСписка");
// Получаем данные текущей (первой) строки
ТекущиеДанныеСписка = Элементы.СписокНоменклатуры.ТекущиеДанные;
Если ТекущиеДанныеСписка <> Неопределено Тогда
// Выполняем нужные действия
Элементы.ПолеДляАртикула.Заголовок = "Артикул: " + ТекущиеДанныеСписка.Артикул;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Минусы: создает постоянную нагрузку на клиентское приложение, может срабатывать с задержкой, требует аккуратного управления (обязательно отключать его после выполнения).
2. Получение данных по кнопке или другому действию пользователя
Если автоматическое срабатывание не является критичным, можно просто добавить кнопку "Заполнить по списку", которая принудительно получит нужные данные с сервера.
&НаКлиенте
Процедура ЗаполнитьПоСписку(Команда)
ЗаполнитьПоСпискуНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьПоСпискуНаСервере()
// Этот способ получает не данные из списка, а выполняет его запрос заново.
// Это может быть неэффективно, но гарантирует получение актуальных данных.
КомпоновщикНастроек = ЭтотОбъект.СписокНоменклатуры.КомпоновщикНастроек;
СхемаКомпоновкиДанных = ПолучитьИсполняемуюСхемуКомпоновкиДанных(КомпоновщикНастроек);
НастройкиКомпоновкиДанных = ПолучитьИсполняемыеНастройкиКомпоновкиДанных(КомпоновщикНастроек);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиКомпоновкиДанных);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТаблицуЗначений;
ТаблицаРезультата = ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Если ТаблицаРезультата.Количество() > 0 Тогда
ПерваяСтрока = ТаблицаРезультата[0];
// Здесь мы напрямую меняем реквизит формы, т.к. находимся в серверном вызове с контекстом
Объект.РеквизитДляАртикула = ПерваяСтрока.Артикул;
КонецЕсли;
КонецПроцедуры
В ходе поиска решения часто упоминаются другие события, но они предназначены для иных целей:
ПриАктивизацииСтроки: Срабатывает только тогда, когда пользователь кликает мышкой или перемещается по строкам с помощью клавиатуры. Оно не вызывается при первоначальной загрузке или обновлении данных (F5).ОбновлениеОтображения: Это клиентское событие, которое срабатывает очень часто при любых изменениях внешнего вида формы. Пытаться "поймать" в нем появление данных — очень ненадежный способ, который может привести к ошибкам и лишним срабатываниям.Элементы.Список.ТекущиеДанные: Это не событие, а свойство. Оно позволяет получить данные уже выделенной строки списка. Его удобно использовать в связке с событием ПриАктивизацииСтроки, но оно не решает задачу получения данных в момент их загрузки.Таким образом, выбор метода зависит от версии платформы 1С, на которой вы работаете. Для версий 8.3.10 и новее однозначно стоит использовать связку события ПриПолученииДанныхНаСервере и механизма оповещений, как наиболее эффективное и идеологически верное решение.