В процессе доработки типовых конфигураций 1С с помощью расширений часто возникает задача: добавить свою логику к уже существующему событию элемента формы, например, к ПриИзменении. При этом критически важно, чтобы стандартный обработчик события продолжал выполняться, а наш код срабатывал сразу после него. Особенно актуальной эта задача становится, когда таких доработок нужно сделать много, для десятков разных документов, и хочется найти универсальное программное решение.
Проанализируем ситуацию: можно ли программно, в одной процедуре, "подписаться" на события множества элементов, чтобы наш код выполнялся после типового? Разберем, с какими ограничениями платформы мы столкнемся и какие существуют правильные подходы к решению этой задачи.
Первое, что приходит на ум — использовать метод элемента формы УстановитьДействие(). Посмотрим, почему попытки его применения не приводят к желаемому результату.
Автор исходной проблемы попробовал следующий код:
&НаСервере
Процедура BAI_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
Для Каждого Элемент Из Элементы Цикл
// ... прочие проверки
Если Элемент.ПолучитьДействие("ПриИзменении") <> "" Тогда
Сообщить("ЕстьДействие");
Элемент.УстановитьДействие("ПриИзмененииПосле", "BAI_ЛогироватьИзменение");
КонецЕсли;
// ...
КонецЦикла;
КонецПроцедуры
Эта попытка завершается ошибкой "Первый параметр не верный". Давайте выясним причину. Дело в том, что ПриИзмененииПосле — это не имя события. Это специальная аннотация компилятора (&После), которая используется в коде модуля формы для указания, что процедура должна выполняться после другой процедуры. Само же событие элемента формы называется просто ПриИзменении. Метод УстановитьДействие ожидает именно имя события, а не аннотацию.
Хорошо, попробуем указать правильное имя события:
Элемент.УстановитьДействие("ПриИзменении", "МоеДействие");
Этот код выполнится без ошибок. Но результат нас не устроит: он полностью заменяет существующий типовой обработчик на наш. Стандартная логика, заложенная разработчиками конфигурации, перестанет работать. Это происходит потому, что у элемента формы на одно событие может быть назначен только один обработчик. Метод УстановитьДействие не добавляет обработчик, а перезаписывает его.
Итак, мы выяснили, что прямого программного способа "добавить" обработчик "После" не существует. Рассмотрим правильные и обходные пути решения.
&После (рекомендуемый способ)Наиболее корректный, надежный и поддерживаемый платформой способ расширения логики событий — это использование аннотаций в модуле формы, заимствованной в расширение. Аннотация &После была создана именно для таких задач.
Разберем по шагам, как это сделать:
ПриИзменении. Допустим, она называется КонтрагентПриИзменении.&После с именем типового обработчика в кавычках.Посмотрим на пример:
&После("КонтрагентПриИзменении")
&НаКлиенте
Процедура расш1_КонтрагентПриИзменении(Элемент)
// Ваш дополнительный код, который выполнится
// СТРОГО ПОСЛЕ типовой процедуры КонтрагентПриИзменении
Сообщить("Сработал дополнительный обработчик для элемента: " + Элемент.Имя);
КонецПроцедуры
Плюсы этого подхода:
Минус для исходной задачи:
&ПослеДаже если приходится создавать много обработчиков вручную, сам исполняемый код можно (и нужно) вынести в одно место. Это решает часть проблемы с дублированием и поддержкой.
Для этого создайте общую экспортную процедуру в клиентском общем модуле, а из всех обработчиков &После вызывайте только ее.
Пример реализации:
В общем модуле МойМодульКлиент:
&НаКлиенте
Процедура ЕдиныйОбработчикИзменений(Элемент, Форма) Экспорт
// Здесь ваша единая логика для всех элементов
Сообщить("Изменён элемент '" + Элемент.Имя + "' на форме '" + Форма.ИмяФормы + "'");
КонецПроцедуры
В модуле формы документа 1:
&После("КонтрагентПриИзменении")
&НаКлиенте
Процедура расш1_КонтрагентПриИзменении(Элемент)
МойМодульКлиент.ЕдиныйОбработчикИзменений(Элемент, ЭтаФорма);
КонецПроцедуры
В модуле формы документа 2:
&После("ПоставщикПриИзменении")
&НаКлиенте
Процедура расш1_ПоставщикПриИзменении(Элемент)
МойМодульКлиент.ЕдиныйОбработчикИзменений(Элемент, ЭтаФорма);
КонецПроцедуры
Таким образом, мы получаем компромисс: "обвязку" для каждого события создаем вручную, но вся бизнес-логика находится в одном месте, что упрощает ее изменение и отладку.
Существует теоретическая возможность реализовать программную подмену обработчика. Идея заключается в том, чтобы сохранить имя старого обработчика, назначить новый, а уже внутри нового сначала вызвать старый, а потом выполнить свой код.
Алгоритм мог бы выглядеть так:
ИмяСтарогоОбработчика = Элемент.ПолучитьДействие("ПриИзменении");Элемент.УстановитьДействие("ПриИзменении", "МойНовыйОбработчик");МойНовыйОбработчик нужно как-то исполнить код процедуры, имя которой хранится в переменной ИмяСтарогоОбработчика.Однако на третьем шаге мы сталкиваемся с серьезной проблемой. В 1С нет простого и надежного способа вызвать процедуру модуля формы по ее имени, хранящемуся в строке. Процедуры модуля формы не являются частью глобального контекста, и функция Выполнить() здесь не поможет. Кроме того, эти процедуры обычно не являются экспортными.
Этот путь является крайне сложным, ненадежным, и его реализация будет похожа на "костыль", который может сломаться при любом обновлении конфигурации. Поэтому данный подход не рекомендуется к использованию.
Проанализировав возможности платформы 1С, приходим к заключению:
ПриИзмененииПосле не являются именами событий.&После("ИмяТиповогоОбработчика") в модуле формы, заимствованной в расширение.&После делать только вызов этой общей процедуры.Несмотря на то, что это требует больше рутинной работы по настройке каждого обработчика, такой подход гарантирует стабильность, предсказуемость поведения системы и простоту дальнейшей поддержки вашего расширения.