1С ERP: паттерн доработки форм типовых объектов конфигурации

Содержание
- Общие сведения
- Общие модули паттерна
- Пример 1: Добавление элементов на форму
- Пример 2: Добавление обработчиков событий элемента формы
- Пример 3: Добавление команд формы
- Приложение: шаблоны модулей
- Связанные публикации
Общие сведения
В данной статье речь идёт о доработке типовой конфигурации 1С: Управление предприятием 2.5 (ERP) способом №1, в основной конфигурации, снятой с поддержки. Доработка основной конфигурации является наиболее распространённым способом на больших проектах. Данный паттерн придуман не мной, я его здесь лишь слегка усовершенствовал и задокументировал. Его схема по верхам выглядит так:

Основная идея заключается в том, что доработка типового объекта реализуется в трёх общих модулях разного типа: серверном, клиентском и модуле вызова сервера. Имена этих модулей формируются по единому шаблону, установленному архитектором.
В типовой конфигурации 1С ERP почти во всех формах объектов обработчики событий уже содержат вызовы методов из общих модулей СобытияФорм и СобытияФормКлиент. Нам остаётся лишь добавить в них вызовы обработчиков из собственного аналогичного модуля. В этом модуле реализуется алгоритм определения имени модуля с доработками (спойлер: на основе полного имени формы) — и вызываются соответствующие методы, если они были реализованы.
Стоить заметить, что эта методика подходит и для других типовых конфигураций где нет вышеуказанных общих модулей, вместо них придётся вставлять напрямую вызовы своих модулей по все типовые формы, которые требуется доработать. Точно также и в ERP можно делать вызовы из своих модулей напрямую, если вы посчитаете что СобытияФорм и СобытияФормКлиент - это лишняя прослойка.
Достоинства:
- Минимальное вмешательство в исходный код типовой конфигурации, что упрощает её поддержку и обновление
- Все изменения в коде типовой конфигурации шаблонизированы и стандартизированы, что повышает их предсказуемость и упрощает сопровождение
- Единая архитектурная модель — любой разработчик из команды быстро разберётся в реализации доработок, выполненных коллегами
- Сниженная трудоёмкость обновления типовой конфигурации поставщика, благодаря изолированности доработок
- В системе коллективной разработки (GIT) отсутствует поток изменений по типовым объектам, что минимизирует риски нарушения стабильности типового функционала
Недостатки:
- Повышенная трудоёмкость реализации отдельных доработок из-за сложности и громоздкости используемого паттерна
- При значительном объёме доработок наблюдается резкий рост количества метаданных, что может существенно замедлить работу сред разработки, сборку конфигурации, отладку и выгрузку в базу. Для стабильной работы EDT может потребоваться выделение до 64 ГБ оперативной памяти и более
- При обновлении типовой конфигурации поставщика через диалог сравнения-объединения будет практически невозможно предсказать что сломается в доработках - ошибки придётся отлавливать при опытной эксплуатации или настраивать автотесты.
Общие модули паттерна
Исходный код общих модулей АШ_СобытияФорм и АШ_СобытияФормКлиент приведён ниже. Пока он не полный, для начала реализованы только по паре обработчиков событий формы. Полный код с примерами будет в конце статьи.
Общий модуль 'АШ_СобытияФорм' (сервер)
#Область ПрограммныйИнтерфейс
// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур общего модуля 'СобытияФорм' или 'СобытияФормЛокализация'
#Область СобытияФормы
Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриСозданииНаСервере") Тогда
ОМ.ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
Процедура ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриЧтенииНаСервере") Тогда
ОМ.ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
// добавить здесь другие обработчики серверных событий формы при необходимости
#КонецОбласти
#Область СобытияЭлементовФормыНаСервере
// здесь будут обработчики событий элементов формы, вызванные из клиентских обработчиков
#КонецОбласти
#Область КомандыНаСервере
// здесь будут обработчики команд формы, вызванные из клиентских обработчиков
#КонецОбласти
#КонецОбласти
#Область Служебная
// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
// ИмяФормы - Строка - Полное имя формы объекта конфигурации
//
// Возвращаемое значение:
// ОбщийМодуль - Объект общего модуля (на сервере) для модификации объекта, полученный
// по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
ОМ = Неопределено;
// Здесь можно настроить собственный шаблон для имен общих модулей:
Шаблон = "АШ_%1_%2";
ЧастиИмени = СтрРазделить(ИмяФормы, ".");
Если ЧастиИмени.Количество() > 1 Тогда
ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
Попытка
ОМ = ОбщегоНазначения.ОбщийМодуль(ИмяОбщегоМодуля);
Исключение
КонецПопытки
КонецЕсли;
Возврат ОМ;
КонецФункции
#КонецОбласти
Общий модуль 'АШ_СобытияФормКлиент' (клиент)
#Область ПрограммныйИнтерфейс
// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур ОМ 'СобытияФормКлиент'
#Область СобытияФормы
Процедура ПриОткрытии(Форма, Отказ) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриОткрытии") Тогда
ОМ.ПриОткрытии(Форма, Отказ);
КонецЕсли;
КонецПроцедуры
Процедура ПередЗаписью(Форма, Отказ, ПараметрыЗаписи) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПередЗаписью") Тогда
ОМ.ПередЗаписью(Форма, Отказ, ПараметрыЗаписи);
КонецЕсли;
КонецПроцедуры
// добавить другие обработчики клиентских событий формы при необходимости
#КонецОбласти
#Область СобытияЭлементовФормы
// здесь будут обработчики событий элементов формы
#КонецОбласти
#Область Оповещения
// здесь будет обработчик оповещений
#КонецОбласти
#Область Команды
// здесь будет обработчик команд
#КонецОбласти
#КонецОбласти
#Область Служебная
// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
// ИмяФормы - Строка - Полное имя формы объекта конфигурации
//
// Возвращаемое значение:
// ОбщийМодуль - Объект общего модуля (на клиенте) для модификации объекта, полученный
// по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
ОМ = Неопределено;
// Здесь можно настроить собственный шаблон для имен общих модулей:
Шаблон = "АШ_%1_%2Клиент";
ЧастиИмени = СтрРазделить(ИмяФормы, ".");
Если ЧастиИмени.Количество() > 1 Тогда
ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
Попытка
ОМ = ОбщегоНазначенияКлиент.ОбщийМодуль(ИмяОбщегоМодуля);
Исключение
КонецПопытки
КонецЕсли;
Возврат ОМ;
КонецФункции
#КонецОбласти
Кроме того, понадобятся ещё 2 общих модуля, они в ходе разработки изменяться не будут и окончательные варианты здесь: АШ_ОбщегоНазначения и АШ_ОбщегоНазначенияКлиент.
Пример 1: Добавление элементов на форму
Допустим, требуется добавить пару реквизитов и полей на форму документа 'Расходный кассовый ордер'. Для этого создадим общий серверный модуль с наименованием 'АШ_Документ_РасходныйКассовыйОрдер', согласно требованиям шаблона паттерна:

Реквизиты и элементы формы создаются программно в обработчике события формы 'ПриСозданииНаСервере':
#Область ПрограммныйИнтерфейс
Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры) Экспорт
Если Отказ Тогда
Возврат;
КонецЕсли;
Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
ФормаДокументаПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
#КонецОбласти
#Область Служебная
Процедура ФормаДокументаПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры)
// Пример: программное создание реквизитов формы
ДобавляемыеРеквизиты = Новый Массив;
ДобавляемыеРеквизиты.Добавить(
Новый РеквизитФормы(
"АШ_ЧисловойРеквизит",
Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(12, 2, ДопустимыйЗнак.Любой)),,
"Числовой реквизит"
)
);
ДобавляемыеРеквизиты.Добавить(
Новый РеквизитФормы(
"АШ_БулевыйРеквизит",
Новый ОписаниеТипов("Булево"),,
"Вкл. числовой реквизит"
)
);
Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
// Пример: программное создание элементов формы
АШ_ПолеЧисло1 = Форма.Элементы.Добавить(
"АШ_ПолеЧисло1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
);
АШ_ПолеЧисло1.Вид = ВидПоляФормы.ПолеВвода;
АШ_ПолеЧисло1.ПутьКДанным = "АШ_ЧисловойРеквизит";
АШ_ПолеБулево1 = Форма.Элементы.Добавить(
"АШ_ПолеБулево1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
);
АШ_ПолеБулево1.Вид = ВидПоляФормы.ПолеФлажка;
АШ_ПолеБулево1.ВидФлажка = ВидФлажка.Выключатель;
АШ_ПолеБулево1.ПутьКДанным = "АШ_БулевыйРеквизит";
КонецПроцедуры
#КонецОбласти
Заглянем в модуль формы документа и убедимся, что вызов одноименного метода стандартного модуля присутствует:

Таким образом, для нашей простой задачи типовой документ трогать вообще не требуется. Протестируем:

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

Точно таким же путём добавляются обработчики других событий элементов формы.
Пример 3: Добавление команд формы
Добавление команд формы делается по аналогии с добавлением элементов и их обработчиков событий. В качестве примера добавим команду формы в модуле 'АШ_Документ_РасходныйКассовыйОрдер' в процедуре 'ПриСозданииНаСервере':
АШ_Загрузить = Форма.Команды.Добавить("АШ_Загрузить");
АШ_Загрузить.Заголовок = "Загрузить из Excel";
АШ_Загрузить.Действие = "Подключаемый_ВыполнитьПереопределяемуюКоманду";
АШ_КнопкаЗагрузить = Форма.Элементы.Добавить("АШ_КнопкаЗагрузить", Тип("КнопкаФормы"), Форма.КоманднаяПанель);
АШ_КнопкаЗагрузить.ИмяКоманды = "АШ_Загрузить";
В модуле формы типового объекта уже есть эта процедура и ничего там делать не надо. Идём дальше, в общий модуль 'СобытияФормКлиент' и прописываем вызов одноименного метода из 'АШ_СобытияФормКлиент':
#Область Команды
Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ВыполнитьПереопределяемуюКоманду") Тогда
ОМ.ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
#КонецОбласти
В модуль '' добавляем:
Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
Иначе
// другие формы документа
КонецЕсли;
КонецПроцедуры
Процедура ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры)
ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);
Если Команда.Имя = "АШ_Загрузить" Тогда
ОбщегоНазначенияКлиент.СообщитьПользователю("Нажата кнопка 'Загрузить из файла'");
//ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
Иначе
// другие команды формы
КонецЕсли;
КонецПроцедуры
Что делать, если требуется выполнять и серверный код? Ответ: делать по аналогии с событиями элементов формы. Обратите внимание, что в типовом коде при вызове СобытияФорм.ВыполнитьПереопределяемуюКоманду не передается третий аргумент 'ДополнительныеПараметры'.

В этом случае лучше всего добавить в модуль формы собственный шаблон обработчика с префиксом, например 'Подключаемый_АШ_ВыполнитьПереопределяемуюКоманду' и прописывать в команде именно его.
Приложение: шаблоны модулей
АШ_ОбщегоНазначения
#Область ПрограммныйИнтерфейс
// Функция - Проверяет, существует ли экспортный метод у общего модуля
//
// Параметры:
// Объект - ОбщийМодуль - Объект общего модуля
// ИмяМетода - Строка - Имя процедуры или функции
//
// Возвращаемое значение:
// Булево - В общем модуле реализован указанный метод
//
Функция МетодСуществует(Объект, ИмяМетода) Экспорт
Результат = Ложь;
Если Объект <> Неопределено Тогда
Выражение = СтрШаблон("Объект.%1(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)", ИмяМетода);
Попытка
//@skip-check server-execution-safe-mode
Выполнить(Выражение);
Исключение
Описание = ИнформацияОбОшибке().Описание;
Результат = Найти(НРег(Описание), СтрШаблон("(%1)", НРег(ИмяМетода))) = 0;
КонецПопытки;
КонецЕсли;
Возврат Результат;
КонецФункции
#КонецОбласти
АШ_ОбщегоНазначенияКлиент
#Область ПрограммныйИнтерфейс
// Функция - Проверяет, существует ли экспортный метод у общего модуля
//
// Параметры:
// Объект - ОбщийМодуль - Объект общего модуля
// ИмяМетода - Строка - Имя процедуры или функции
//
// Возвращаемое значение:
// Булево - В общем модуле реализован указанный метод
//
Функция МетодСуществует(Объект, ИмяМетода) Экспорт
Результат = Ложь;
Если Объект <> Неопределено Тогда
Выражение = СтрШаблон("Объект.%1(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)", ИмяМетода);
Попытка
Выполнить(Выражение);
Исключение
Описание = ИнформацияОбОшибке().Описание;
Результат = Найти(НРег(Описание), СтрШаблон("(%1)", НРег(ИмяМетода))) = 0;
КонецПопытки;
КонецЕсли;
Возврат Результат;
КонецФункции
#КонецОбласти
АШ_СобытияФорм
#Область ПрограммныйИнтерфейс
// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур ОМ 'СобытияФорм' или 'СобытияФормЛокализация'
#Область СобытияФормы
Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриСозданииНаСервере") Тогда
ОМ.ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
Процедура ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриЧтенииНаСервере") Тогда
ОМ.ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
// добавить другие обработчики серверных событий формы при необходимости
#КонецОбласти
#Область СобытияЭлементовФормыНаСервере
Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриИзмененииЭлемента") Тогда
ОМ.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
// здесь будут обработчики событий других элементов формы, вызванные из клиентских обработчиков
#КонецОбласти
#Область КомандыНаСервере
// здесь будут обработчики команд формы, вызванные из клиентских обработчиков
#КонецОбласти
#КонецОбласти
#Область Служебная
// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
// ИмяФормы - Строка - Полное имя формы объекта конфигурации
//
// Возвращаемое значение:
// ОбщийМодуль - Объект общего модуля (на сервере) для модификации объекта, полученный
// по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
ОМ = Неопределено;
// Здесь можно настроить собственный шаблон для имен общих модулей:
Шаблон = "АШ_%1_%2";
ЧастиИмени = СтрРазделить(ИмяФормы, ".");
Если ЧастиИмени.Количество() > 1 Тогда
ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
Попытка
ОМ = ОбщегоНазначения.ОбщийМодуль(ИмяОбщегоМодуля);
Исключение
КонецПопытки
КонецЕсли;
Возврат ОМ;
КонецФункции
#КонецОбласти
АШ_СобытияФормКлиент
#Область ПрограммныйИнтерфейс
// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур ОМ 'СобытияФормКлиент'
#Область СобытияФормы
Процедура ПриОткрытии(Форма, Отказ) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриОткрытии") Тогда
ОМ.ПриОткрытии(Форма, Отказ);
КонецЕсли;
КонецПроцедуры
Процедура ПередЗаписью(Форма, Отказ, ПараметрыЗаписи) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПередЗаписью") Тогда
ОМ.ПередЗаписью(Форма, Отказ, ПараметрыЗаписи);
КонецЕсли;
КонецПроцедуры
// добавить другие обработчики клиентских событий формы при необходимости
#КонецОбласти
#Область СобытияЭлементовФормы
Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриИзмененииЭлемента") Тогда
ОМ.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
// здесь будут другие обработчики событий элементов формы
#КонецОбласти
#Область Оповещения
// здесь будет обработчик оповещений
#КонецОбласти
#Область Команды
Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);
Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ВыполнитьПереопределяемуюКоманду") Тогда
ОМ.ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
#КонецОбласти
#КонецОбласти
#Область Служебная
// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
// ИмяФормы - Строка - Полное имя формы объекта конфигурации
//
// Возвращаемое значение:
// ОбщийМодуль - Объект общего модуля (на клиенте) для модификации объекта, полученный
// по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
ОМ = Неопределено;
// Здесь можно настроить собственный шаблон для имен общих модулей:
Шаблон = "АШ_%1_%2Клиент";
ЧастиИмени = СтрРазделить(ИмяФормы, ".");
Если ЧастиИмени.Количество() > 1 Тогда
ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
Попытка
ОМ = ОбщегоНазначенияКлиент.ОбщийМодуль(ИмяОбщегоМодуля);
Исключение
КонецПопытки
КонецЕсли;
Возврат ОМ;
КонецФункции
#КонецОбласти
АШ_Документ_РасходныйКассовыйОрдер
#Область ПрограммныйИнтерфейс
Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры) Экспорт
Если Отказ Тогда
Возврат;
КонецЕсли;
Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
ФормаДокументаПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
Иначе
// другие формы документа
КонецЕсли;
КонецПроцедуры
#КонецОбласти
#Область Служебная
Процедура ФормаДокументаПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры)
// Пример: программное создание реквизитов формы
ДобавляемыеРеквизиты = Новый Массив;
ДобавляемыеРеквизиты.Добавить(
Новый РеквизитФормы(
"АШ_ЧисловойРеквизит",
Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(12, 2, ДопустимыйЗнак.Любой)),,
"Числовой реквизит"
)
);
ДобавляемыеРеквизиты.Добавить(
Новый РеквизитФормы(
"АШ_БулевыйРеквизит",
Новый ОписаниеТипов("Булево"),,
"Вкл. числовой реквизит"
)
);
Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
// Пример: программное создание элементов формы
АШ_ПолеЧисло1 = Форма.Элементы.Добавить(
"АШ_ПолеЧисло1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
);
АШ_ПолеЧисло1.Вид = ВидПоляФормы.ПолеВвода;
АШ_ПолеЧисло1.ПутьКДанным = "АШ_ЧисловойРеквизит";
АШ_ПолеБулево1 = Форма.Элементы.Добавить(
"АШ_ПолеБулево1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
);
АШ_ПолеБулево1.Вид = ВидПоляФормы.ПолеФлажка;
АШ_ПолеБулево1.ВидФлажка = ВидФлажка.Выключатель;
АШ_ПолеБулево1.ПутьКДанным = "АШ_БулевыйРеквизит";
АШ_ПолеБулево1.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииЭлемента");
// Пример: программное добавление отсутствующего обработчика события для типового поля:
Форма.Элементы.Выдать.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииЭлемента");
// Команды
АШ_Загрузить = Форма.Команды.Добавить("АШ_Загрузить");
АШ_Загрузить.Заголовок = "Загрузить из Excel";
АШ_Загрузить.Действие = "Подключаемый_ВыполнитьПереопределяемуюКоманду";
// Кнопка команды
АШ_КнопкаЗагрузить = Форма.Элементы.Добавить("АШ_КнопкаЗагрузить", Тип("КнопкаФормы"), Форма.КоманднаяПанель);
АШ_КнопкаЗагрузить.ИмяКоманды = "АШ_Загрузить";
КонецПроцедуры
Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)
Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
Если Форма.Объект.Ссылка.Пустая() Тогда
ОбщегоНазначения.СообщитьПользователю("Документ не сохранен");
Иначе
ОбщегоНазначения.СообщитьПользователю("Документ сохранен");
КонецЕсли;
ДополнительныеПараметры.Вставить("ТребуетсяЗавершитьНаКлиенте");
Иначе
// другие элементы формы
КонецЕсли;
КонецПроцедуры
#КонецОбласти
АШ_Документ_РасходныйКассовыйОрдерКлиент
#Область ПрограммныйИнтерфейс
Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
Иначе
// другие формы документа
КонецЕсли;
КонецПроцедуры
Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
Иначе
// другие формы документа
КонецЕсли;
КонецПроцедуры
#КонецОбласти
#Область Служебная
Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)
ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);
Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
Если Не ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда
Форма.Элементы.АШ_ПолеЧисло1.Доступность = Форма.АШ_БулевыйРеквизит;
ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
Иначе
ОбщегоНазначенияКлиент.СообщитьПользователю("Завершение на клиенте");
КонецЕсли;
ИначеЕсли Элемент.Имя = "Выдать" Тогда
ОбщегоНазначенияКлиент.СообщитьПользователю("Значение поменялось: " + Форма.Объект.Выдать);
ИначеЕсли Элемент.Имя = "Касса" Тогда
ОбщегоНазначенияКлиент.СообщитьПользователю("Касса поменялась: " + Форма.Объект.Касса);
Иначе
// другие элементы формы
КонецЕсли;
КонецПроцедуры
Процедура ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры)
ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);
Если Команда.Имя = "АШ_Загрузить" Тогда
ОбщегоНазначенияКлиент.СообщитьПользователю("Нажата кнопка 'Загрузить из файла'");
//ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
Иначе
// другие команды формы
КонецЕсли;
КонецПроцедуры
#КонецОбласти
Шаблон для вставки в модуль типовой формы
#Область АШ_Доработки
&НаКлиенте
Процедура Подключаемый_АШ_ВыполнитьПереопределяемуюКоманду(Команда)
ДополнительныеПараметры = Неопределено;
СобытияФормКлиент.ВыполнитьПереопределяемуюКоманду(ЭтотОбъект, Команда, ДополнительныеПараметры);
Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяВыполнитьНаСервере") Тогда
Подключаемый_АШ_ВыполнитьПереопределяемуюКомандуНаСервере(Команда.Имя, ДополнительныеПараметры);
КонецЕсли;
Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда
СобытияФормКлиент.ВыполнитьПереопределяемуюКоманду(ЭтотОбъект, Команда, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура Подключаемый_АШ_ВыполнитьПереопределяемуюКомандуНаСервере(ИмяКоманды, ДополнительныеПараметры)
ДополнительныеПараметры.Вставить("ИмяКоманды", ИмяКоманды);
СобытияФорм.ВыполнитьПереопределяемуюКоманду(ЭтотОбъект, ДополнительныеПараметры);
онецПроцедуры
&НаКлиенте
Процедура Подключаемый_ПриИзмененииЭлемента(Элемент)
ДополнительныеПараметры = Неопределено;
СобытияФормКлиент.ПриИзмененииЭлемента(ЭтотОбъект, Элемент, ДополнительныеПараметры);
Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяВыполнитьНаСервере") Тогда
Подключаемый_ПриИзмененииЭлементаНаСервере(Элемент.Имя, ДополнительныеПараметры);
КонецЕсли;
Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда
СобытияФормКлиент.ПриИзмененииЭлемента(ЭтотОбъект, Элемент, ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура Подключаемый_ПриИзмененииЭлементаНаСервере(ИмяЭлемента, ДополнительныеПараметры)
СобытияФорм.ПриИзмененииЭлемента(ЭтотОбъект, Элементы[ИмяЭлемента], ДополнительныеПараметры);
КонецПроцедуры
#КонецОбласти
Связанные публикации
Программное создание реквизитов управляемой формы 1С