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