Переход с обычных форм на управляемые (например, при обновлении конфигураций «Бухгалтерия предприятия» с версии 2.0 на 3.0) часто преподносит разработчикам неприятные сюрпризы. Один из самых распространенных вопросов новичков: «Как из процедуры, выполняемой на сервере, вызвать клиентскую процедуру той же управляемой формы?».
Типичный сценарий: в серверном обработчике ПриЗаписиНаСервере выполняется проверка данных, и при определенных условиях необходимо выдать пользователю предупреждение (старый добрый метод Предупреждение()) или спросить его согласия на продолжение операции. Однако попытка напрямую запустить клиентский код из серверного приводит к ошибке компиляции. Выясним причину этого ограничения и подробно разберем, как правильно реализовать подобную логику в управляемом приложении.
Давайте проанализируем архитектуру управляемой платформы «1С:Предприятие 8». Она построена по жесткой схеме «клиент-сервер», где инициатором любого взаимодействия всегда выступает клиентский сеанс.
Клиент отправляет запрос на сервер (например, команду записать документ), сервер выполняет код в своем контексте и возвращает результат обратно на клиент. Обратный вызов — когда сервер по своей инициативе пытается прервать поток выполнения и «дернуть» процедуру на конкретном компьютере пользователя — физически невозможен. Сервер «не знает» о состоянии интерфейса пользователя и не может напрямую управлять потоком выполнения на клиенте во время транзакции.
Тем не менее, решать подобные бизнес-задачи необходимо. Ниже мы детально рассмотрим четыре эффективных способа обхода этого ограничения.
Если ваша главная цель в серверном коде — проинформировать пользователя о какой-то некритичной ошибке или событии, забудьте про метод Предупреждение(). На сервере он недоступен. Вместо этого мы будем использовать специализированный объект СообщениеПользователю.
Этот объект уникален тем, что умеет «накапливать» сообщения во время выполнения серверного кода формы. Как только сервер завершит работу и управление вернется на клиентскую сторону, платформа автоматически отобразит все накопленные сообщения в интерфейсе пользователя.
Рассмотрим пример реализации на сервере:
&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Какая-то проверка бизнес-логики
Если ТекущийОбъект.СуммаДокумента > 100000 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Внимание! Сумма документа превышает лимит в 100 000 руб.";
// Привязываем сообщение к конкретному реквизиту формы для удобства пользователя
Сообщение.Поле = "Объект.СуммаДокумента";
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
Этот способ идеально подходит для уведомлений и не блокирует процесс записи, если это не требуется. Он полностью заменяет старый метод Сообщить().
Если вам обязательно нужно выводить интерактивные окна (например, предупреждения, требующие нажатия кнопки «ОК») перед тем, как начнется транзакция записи, перенесите логику проверок на клиентский уровень.
Вместо использования серверного обработчика ПриЗаписиНаСервере, задействуйте клиентское событие формы ПередЗаписью. На клиенте методы интерактивного общения с пользователем полностью доступны.
Проанализируем пример структуры кода:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Выполняем быструю проверку на клиенте
Если Не ЗначениеЗаполнено(Объект.Контрагент) Тогда
// Показываем предупреждение (в асинхронном стиле)
ПоказатьПредупреждение(, "Необходимо заполнить контрагента!");
Отказ = Истина; // Отменяем запись
Возврат;
КонецЕсли;
// Если нужны тяжелые проверки на сервере перед записью:
Если Не ВыполнитьПроверкуНаСервере() Тогда
ПоказатьПредупреждение(, "Проверка на сервере не пройдена!");
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ВыполнитьПроверкуНаСервере()
// Здесь выполняем сложные запросы к базе данных
Возврат Истина;
КонецФункции
Это классический паттерн разработки в управляемом приложении 1С, когда в процессе записи на сервере обнаруживается ситуация, требующая явного согласия пользователя (например: «Этот объект уже выгружался в другую базу. Перезаписать?»).
Поскольку приостановить серверную транзакцию, спросить пользователя и продолжить выполнение в той же транзакции невозможно, мы разбиваем процесс на этапы под управлением клиента:
ПередЗаписью мы делаем быстрый «пристрелочный» вызов сервера для проверки условий.Отказ = Истина).ПоказатьВопрос().Рассмотрим, как это выглядит в коде управляемой формы:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Проверяем, не идет ли повторная запись после подтверждения пользователем
Если ПараметрыЗаписи.Свойство("ПодтверждениеПолучено") Тогда
Возврат; // Пропускаем проверку, пишем документ напрямую
КонецЕсли;
// Шаг 1: Вызываем серверную функцию для проверки ситуации
Если ОбъектВыгружалсяРанее(Объект.Ссылка) Тогда
// Шаг 2: Отменяем текущую запись
Отказ = Истина;
// Шаг 3: Настраиваем оповещение и задаем вопрос пользователю
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВопросаОПерезаписи", ЭтотОбъект, ПараметрыЗаписи);
ПоказатьВопрос(ОписаниеОповещения, "Этот документ уже выгружался. Перезаписать?", РежимДиалогаВопрос.ДаНет);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеВопросаОПерезаписи(РезультатВопроса, ДополнительныеПараметры) Экспорт
Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
// Шаг 4: Пользователь согласился. Формируем новые параметры записи с флагом-разрешением
НовыеПараметры = Новый Структура;
Для Каждого КлючЗначение Из ДополнительныеПараметры Цикл
НовыеПараметры.Вставить(КлючЗначение.Ключ, КлючЗначение.Значение);
КонецЦикла;
НовыеПараметры.Вставить("ПодтверждениеПолучено", Истина);
// Шаг 5: Повторно запускаем запись программно
Записать(НовыеПараметры);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОбъектВыгружалсяРанее(СсылкаНаДокумент)
// Серверная логика проверки по регистрам или внешним базам
Возврат Истина;
КонецФункции
Если перед вами стоит задача, аналогичная описанной в теме (оповещение пользователя о фоновых событиях, например, о появлении новых задач в 1С:Документообороте), стандартные методы вывода сообщений не всегда эффективны, так как они требуют активности пользователя в конкретной форме. Для таких сценариев подойдёт обработка для отображения уведомлений 1С в центре уведомлений Windows.
Для таких сценариев в современной платформе 1С:Предприятие 8 реализованы мощные механизмы:
УведомленияКлиента.ПодключитьОбработчик(), вы сможете мгновенно реагировать на вызовы сервера УведомленияКлиента.ОтправитьУведомление().В процессе обсуждения на форуме был поднят еще один важный вопрос: «Как на клиенте вызвать экспортную процедуру модуля формы другого документа, если у нас есть только ссылка на него?».
Напрямую у ссылки (тип ДокументСсылка) на клиенте нет доступных методов формы. Чтобы решить эту задачу, сначала необходимо получить форму этого документа на клиенте, а затем обратиться к ее экспортным методам:
&НаКлиенте
Процедура ВызватьМетодДругогоДокумента(СсылкаНаДокумент)
// Получаем форму нужного документа
ПараметрыФормы = Новый Структура("Ключ", СсылкаНаДокумент);
ФормаДокумента = ПолучитьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента", ПараметрыФормы);
// Теперь нам доступны экспортные клиентские процедуры этой формы
ФормаДокумента.ИмяВашейЭкспортнойПроцедуры();
КонецПроцедуры
Подведем итог. Прямой вызов клиентского кода из серверного контекста противоречит архитектуре платформы 1С. Однако, используя правильные паттерны проектирования, такие как асинхронные цепочки вызовов, объект СообщениеПользователю, или разделение транзакционной логики на этапы, вы сможете реализовать любые интерфейсные решения, сохранив производительность и стабильность вашей системы.