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