Сервер хочет что-то сделать на клиенте: как это устроено в 1С
Содержание
Напрямую вызвать клиентскую процедуру с сервера 1С нельзя. Платформа устроена так, что инициатор всегда клиент: он обращается к серверу, получает ответ и дальше сам решает, что показать. Серверный код не видит форму, окна и сообщения пользователя. Поэтому «вызов клиента с сервера» в 1С всегда реализуется обходным путём.
Таких путей несколько, и выбор зависит от задачи. Нужно показать результат расчёта, сообщить о завершении долгой операции или обновить открытую у другого пользователя форму. Ниже по порядку: как разделены контексты, какая схема подходит под какую задачу, что можно передавать между клиентом и сервером и где обычно ошибаются.
Как 1С делит код на клиентский и серверный
Каждая процедура формы помечается директивой компиляции. От неё зависит, где код выполняется и что ему доступно.
| Директива | Где выполняется | Что доступно |
|---|---|---|
&НаКлиенте | В клиентском приложении | Элементы формы, диалоги, окна, Сообщить |
&НаСервере | На сервере, с контекстом формы | Реквизиты формы, объекты базы, запросы |
&НаСервереБезКонтекста | На сервере, без контекста формы | Только переданные параметры и база |
&НаКлиентеНаСервереБезКонтекста | И там и там | Только то, что есть в обоих контекстах |
Правило вызовов простое:
- 🟢 клиент вызывает серверную процедуру;
- 🟢 сервер вызывает серверные процедуры и общие модули;
- 🔴 сервер не вызывает клиентские процедуры, у него нет к ним доступа.
Серверная процедура может выполняться в другом процессе или на другой машине, чем клиентское приложение. Ей просто некому «показать» окно. Компилятор не даст сделать такой вызов: при проверке модуля или запуске получите ошибку, что процедура не найдена в текущем контексте.
Это касается и общих модулей. Модуль с признаком «Клиент» не виден серверному коду, а модуль с признаком «Сервер» не виден клиентскому. Процедуры для обоих контекстов выносят в модуль с обоими признаками или помечают &НаКлиентеНаСервереБезКонтекста в модуле формы.
Пять рабочих схем вместо прямого вызова
Ниже способы от простого к сложному. Начинайте с первого: в большинстве задач его хватает.
Способ 1. Вернуть результат и вызвать клиентскую процедуру самому
Это самый частый и самый надёжный вариант. Клиентская процедура вызывает сервер, получает данные и сразу после этого выполняет нужное действие на клиенте.
&НаКлиенте
Процедура РассчитатьСумму(Команда)
Результат = РассчитатьСуммуНаСервере(Объект.Количество, Объект.Цена);
ПоказатьПредупреждение(, "Итого: " + Формат(Результат, "ЧДЦ=2; ЧГ=0"));
КонецПроцедуры
&НаСервереБезКонтекста
Функция РассчитатьСуммуНаСервере(Количество, Цена)
Возврат Количество * Цена;
КонецФункции
Серверная функция ничего не знает о клиенте. Она возвращает значение, а решение «что показать» принимает клиентская процедура. Если результатов несколько, возвращайте структуру:
&НаСервере
Функция ВыполнитьПроверку()
Результат = Новый Структура;
Результат.Вставить("Успешно", Ложь);
Результат.Вставить("Сообщение", "Не заполнен контрагент");
Возврат Результат;
КонецФункции
На клиенте разбираете поля структуры и вызываете любую клиентскую процедуру в зависимости от флага Успешно.
Подходит, когда сервер отработал быстро (до нескольких секунд) и клиент ждёт ответа.
Способ 2. Передать на сторону клиента «что делать» через данные
Если после серверной операции нужно выбрать одно из нескольких действий на клиенте, не пытайтесь «вызвать» их с сервера. Верните код действия и выполните выбор на клиенте.
&НаКлиенте
Процедура ПровестиИЗакрыть(Команда)
Итог = ПровестиДокументНаСервере();
Если Итог.Действие = "Закрыть" Тогда
Закрыть();
ИначеЕсли Итог.Действие = "Предупредить" Тогда
ПоказатьПредупреждение(, Итог.Текст);
КонецЕсли;
КонецПроцедуры
Сервер заполняет Действие и Текст, клиент реагирует. Список допустимых значений держите в одном месте, например в перечислении или константах модуля, чтобы не разойтись в написании строк.
Для сообщений пользователю есть и готовый механизм. Сервер вызывает Сообщить(...) или создаёт Новый СообщениеПользователю и вызывает у него Сообщить(). Сообщение доставляется в окно сообщений клиента, который инициировал вызов. Привязка к полю формы делается через свойства сообщения ПутьКДанным и Поле.
Способ 3. Оповещение формы после серверной работы
Когда результат нужно передать не вызвавшей процедуре, а другой открытой форме, используют глобальное оповещение. Оно клиентское: его вызывает клиентский код, как только получил результат с сервера.
&НаКлиенте
Процедура ЗаписатьИОповестить(Команда)
ЗаписатьНаСервере();
Оповестить("ИзмененКонтрагент", Объект.Ссылка, ЭтотОбъект);
КонецПроцедуры
В форме, которая должна отреагировать, пишут обработчик:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ИзмененКонтрагент" Тогда
Элементы.Список.Обновить();
КонецЕсли;
КонецПроцедуры
Оповещение работает только внутри одного клиентского сеанса. Форма другого пользователя его не получит.
Второй встроенный механизм — описание оповещения. Он нужен для асинхронных диалогов и закрытия форм: вы создаёте Новый ОписаниеОповещения("ИмяПроцедуры", ЭтотОбъект, ДополнительныеПараметры) и передаёте в метод, который вызовет вашу процедуру после завершения (ПоказатьВопрос, ОткрытьФорму с оповещением о закрытии). Для своей логики вызвать его вручную можно через ВыполнитьОбработкуОповещения(Оповещение, Результат).
Способ 4. Долгая операция: фоновое задание и опрос
Если сервер работает десятки секунд или минуты, клиент не должен висеть в ожидании вызова. Запускаем серверную работу в фоне, а клиент периодически спрашивает, готово ли.
В конфигурациях с библиотекой стандартных подсистем (БСП) для этого есть общий модуль ДлительныеОперации. Схема такая:
&НаКлиенте
Процедура НачатьВыгрузку(Команда)
Операция = ЗапуститьВыгрузкуНаСервере();
ПараметрыОжидания = ДлительныеОперации.ПараметрыОжидания(ЭтотОбъект);
Оповещение = Новый ОписаниеОповещения("ПослеВыгрузки", ЭтотОбъект);
ДлительныеОперации.ОжидатьЗавершение(Операция, Оповещение, ПараметрыОжидания);
КонецПроцедуры
&НаСервере
Функция ЗапуститьВыгрузкуНаСервере()
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
Возврат ДлительныеОперации.ВыполнитьВФоне("ОбщийМодуль.Выгрузка.Выполнить",
Новый Структура("Период", Период), ПараметрыВыполнения);
КонецФункции
&НаКлиенте
Процедура ПослеВыгрузки(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат; // задание отменено
КонецЕсли;
Если Результат.Статус = "Выполнено" Тогда
ПоказатьПредупреждение(, "Выгрузка завершена");
Иначе
ПоказатьПредупреждение(, "Ошибка: " + Результат.КраткоеПредставлениеОшибки);
КонецЕсли;
КонецПроцедуры
Здесь «вызов клиентской процедуры с сервера» — это ПослеВыгрузки. Её запускает не сервер, а клиентский код БСП, когда фоновое задание закончилось.
Точные имена параметров и сигнатуры зависят от версии БСП в вашей конфигурации. Откройте модуль ДлительныеОперации в конфигураторе и сверьтесь с описанием процедур, прежде чем копировать пример.
Без БСП тот же приём собирают вручную: запускают фоновое задание, на клиенте подключают ПодключитьОбработчикОжидания("ПроверитьФон", 3), а в обработчике проверяют состояние и при готовности вызывают ОтключитьОбработчикОжидания("ПроверитьФон").
Способ 5. Серверное событие для другого пользователя
Сервер не может «толкнуть» сообщение в открытую форму произвольного клиента на уровне базового кода формы. Если нужно уведомить других пользователей о событии, есть два подхода:
- 🔄 Опрос. Клиент раз в несколько секунд обращается к серверу через обработчик ожидания и забирает накопленные события (например, из регистра сведений или очереди в данных). Простой и предсказуемый вариант. Интервал меньше 3-5 секунд для интерфейса избыточен: нагрузка растёт, а выгоды нет.
- 💬 Система взаимодействия. Подсистема обсуждений и уведомлений платформы. Подходит для сообщений людям, но не заменяет вызов процедуры в форме.
Сам по себе обработчик ожидания — клиентский механизм. Он вызывает ваш клиентский код по таймеру, а данные берёт с сервера. Так и достигается эффект «сервер сказал клиенту».
Ещё один частый случай — серверный обработчик события объекта, например ПриЗаписи в модуле документа. Он выполняется на сервере и в модуль формы не заглядывает. Если после записи нужно что-то показать, пользуйтесь обработчиком формы ПослеЗаписи или ПослеЗаписиНаСервере: он клиентский, и в нём вы уже знаете результат записи. Для проверок заполнения серверный модуль объекта вызывает Отказ = Истина и Сообщить(...), а форма сама покажет сообщения, не требуя вызова клиентской процедуры.
Если логика нужна и в форме, и в фоновом задании, держите её в серверном общем модуле без обращений к интерфейсу. Тогда один и тот же код работает при вызове с клиента, из регламентного задания и из внешнего обмена, а различия в показе результата остаются на клиенте.
Что можно передавать между клиентом и сервером
Параметры серверных процедур и возвращаемые значения передаются копированием. Сервер получает независимую копию, а не ссылку на объект клиента. Поэтому изменения параметра внутри серверной процедуры клиент увидит только если вы вернули значение.
Передаются без проблем:
- ✅ примитивные типы: число, строка, дата, булево;
- ✅ ссылки на объекты базы;
- ✅
Структура,Массив,Соответствиес допустимыми значениями внутри; - ✅
ТаблицаЗначений,ХранилищеЗначения,ДвоичныеДанные.
Не передаются объекты, которые существуют только в одном из контекстов: элементы формы, ОписаниеОповещения, COM-объекты, открытые файловые потоки. Попытка передать такое значение приводит к ошибке при вызове.
Отдельное правило для вызовов с клиента на сервер: параметр, переданный в процедуру &НаСервере, по умолчанию передаётся туда и обратно. Если обратная передача не нужна, ставьте у параметра ключевое слово Знач — так вы сэкономите трафик на больших коллекциях.
&НаСервере
Процедура ОбработатьСписок(Знач СписокСсылок)
// клиент получит назад только изменения реквизитов формы, список обратно не возвращается
КонецПроцедуры
Возвращаемые значения: что и как отдавать
Несколько приёмов, которые упрощают жизнь:
- Для одного значения используйте функцию с
Возврат. - Для нескольких — возвращайте
Структурус понятными именами ключей, а не массив с «магическими» индексами. - Для больших таблиц не гоняйте данные туда-обратно. Положите результат во временное хранилище (
ПоместитьВоВременноеХранилище) и верните клиенту адрес, а на второй стороне получите данные черезПолучитьИзВременногоХранилища. - Ошибки не прячьте: серверный код вызывает
ВызватьИсключение, клиентский ловитПопытка ... Исключениеи показываетКраткоеПредставлениеОшибки(ИнформацияОбОшибке()).
&НаКлиенте
Процедура ВыполнитьДействие(Команда)
Попытка
ВыполнитьДействиеНаСервере();
Исключение
ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Типичные ошибки и что с ними делать
&НаКлиенте из &НаСервере. Верните результат клиентскому коду и вызовите её там.ОбработкаОповещения. Проверьте совпадение имени события и порядок открытия форм.Практические рекомендации
- 📏 Делайте меньше серверных вызовов, но не дробите их мелко. Каждый вызов — сетевой обмен, и на медленном канале десять мелких вызовов заметно хуже одного.
- 🧱 Выносите бизнес-логику в серверные общие модули, а в форме оставляйте только оркестрацию: собрать параметры, вызвать, показать результат.
- 🚫 Не используйте
&НаСерверетам, где контекст формы не нужен:&НаСервереБезКонтекстабыстрее, потому что не передаёт на сервер реквизиты формы. - ⏱ Для всего, что дольше нескольких секунд, сразу закладывайте фоновое выполнение. Переделывать потом сложнее.
- 🧪 Проверяйте код в режиме тонкого клиента, а не только в толстом. Различия контекстов лучше всего видны именно там.
Частые вопросы
Можно ли вызвать клиентскую процедуру из серверной напрямую?
Как показать сообщение пользователю из серверной процедуры?
Сообщить или Новый СообщениеПользователю с вызовом Сообщить(). Сообщение появится у клиента, который вызвал сервер.