Клиентские процедуры в 1С: объявление, вызов, отладка
Содержание
Чтобы процедура выполнилась на клиенте, поставьте перед ней директиву &НаКлиенте в модуле формы или в общем модуле с флагом «Клиент (управляемое приложение)». Вызывайте её из обработчика события формы, из другой клиентской процедуры или по оповещению, и она отработает на компьютере пользователя, а не на сервере 1С.
Обратное не работает . Сервер не может напрямую вызвать клиентскую процедуру. Вызов всегда идёт от клиента к серверу, а результат возвращается обратно. Как обойти это ограничение, разберём ниже, вместе с ошибками и отладкой.
Статья про управляемые формы платформы 1С 8.3. Для обычных форм директив нет, там весь код выполняется в одном контексте.
Что делает процедуру клиентской и когда это оправдано
Приложение 1С в режиме управляемых форм работает в двух местах. Клиент отвечает за окно, поля, кнопки и диалоги. Сервер хранит данные, строит отчёты и проводит документы. Директива компиляции указывает, где исполняется конкретная процедура.
На клиенте стоит оставлять то, что связано с интерфейсом:
- 🖱 реакция на нажатие кнопки, выбор значения, смену поля;
- 🪟 открытие форм, показ сообщений, вопросов пользователю;
- 🔢 простые расчёты по реквизитам формы (сумма = цена × количество);
- 👁 включение и отключение видимости и доступности элементов;
- 📂 работа с файлами на компьютере пользователя.
Всё, что читает или пишет базу, запускает запросы, проводит документы, уходит на сервер. Клиент к базе напрямую не обращается.
Правило практика: каждый вызов сервера стоит времени на передачу данных по сети. Чем дальше сервер от пользователя (терминал, веб-клиент, тонкий клиент по интернету), тем заметнее задержка. Один вызов сервера на действие пользователя обычно предел разумного.
Способы объявить клиентскую процедуру
В управляемой форме доступны четыре директивы. Таблица показывает, чем они отличаются.
| Директива | Где выполняется | Доступ к данным формы | Когда применять |
|---|---|---|---|
&НаКлиенте | Клиент | Да, к реквизитам и элементам | Обработчики событий, интерфейсная логика |
&НаСервере | Сервер | Да, к реквизитам формы | Нужны и база, и данные формы |
&НаСервереБезКонтекста | Сервер | Нет, только параметры | Тяжёлые выборки, меньше передаваемых данных |
&НаКлиентеНаСервереБезКонтекста | Клиент и сервер | Нет | Утилиты без формы, нужные в обоих местах |
Процедура в модуле формы
Самый частый случай. Вы создаёте обработчик события кнопки, платформа сама ставит директиву:
&НаКлиенте
Процедура ПересчитатьСумму(Команда)
Объект.Сумма = Объект.Цена * Объект.Количество;
КонецПроцедуры
Процедура выполняется на клиенте, читает и пишет реквизиты формы без обращений к серверу. Для такого расчёта вызов сервера лишний.
Процедура в общем модуле
Если логика нужна нескольким формам, вынесите её в общий модуль. В свойствах модуля включите флаг Клиент (управляемое приложение). Тогда из любой клиентской процедуры можно вызвать МойМодуль.МояПроцедура(), и она выполнится на клиенте.
Для общего модуля, который должен работать и на клиенте, и на сервере, отметьте оба флага. Но тогда внутри нельзя использовать то, что недоступно в одном из контекстов, иначе платформа не даст сохранить конфигурацию .
Модуль с флагом «Сервер» без флага «Клиент» с клиента не вызвать. Если нужная процедура лежит в таком модуле, придётся обернуть её серверной процедурой формы.
Какую директиву не спутать
Типичный промах новичка: поставил &НаСервере на обработчик, который должен просто показать сообщение. Работать будет, но каждый клик превращается в круг до сервера и обратно. Если процедура не трогает базу, ей место на клиенте.
Как вызвать клиентскую процедуру из серверного кода
Прямого вызова нет. Но задачу «сервер что-то сделал, теперь клиент должен отреагировать» решают тремя приёмами.
Приём 1. Серверная процедура возвращает результат, клиент реагирует
Это основной способ. Клиентский обработчик вызывает сервер, получает значение и сам запускает нужную клиентскую процедуру:
&НаКлиенте
Процедура ПровестиИОповестить(Команда)
Успешно = ПровестиДокументНаСервере();
Если Успешно Тогда
ПоказатьОповещениеПользователя("Документ проведён");
Иначе
ПоказатьПредупреждение(, "Не удалось провести документ");
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПровестиДокументНаСервере()
Возврат Объект.Проведен ИЛИ ЗаписатьСПроведением();
КонецФункции
Здесь ЗаписатьСПроведением() условная, подставьте свою логику записи. Важна схема: сервер отвечает значением, клиент решает, что делать дальше.
Приём 2. Оповещение формы
Если нужно сообщить другой открытой форме, используйте Оповестить("ИмяСобытия", Параметр, Источник) на клиенте и обработчик ОбработкаОповещения в принимающей форме. Событие рассылается клиентом, а сервер лишь подготавливает данные.
Приём 3. Периодический опрос
Когда клиент должен узнать, что на сервере что-то изменилось, подключите обработчик ожидания:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПодключитьОбработчикОжидания("ПроверитьСтатус", 10);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьСтатус()
Если ПолучитьСтатусНаСервере() = "Готово" Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатус");
ПоказатьОповещениеПользователя("Обработка завершена");
КонецЕсли;
КонецПроцедуры
Интервал 10 секунд задан в секундах. Слишком частый опрос нагружает сервер, редко опрашивайте только то, что нельзя узнать иначе.
Приём для долгих операций
Если серверная операция идёт минуты, клиентское окно «зависнет» на время вызова. Для таких задач используют фоновые задания и опрос их состояния, чтобы пользователь видел прогресс и мог работать дальше.
Асинхронные вызовы: диалоги и ожидание ответа
Диалоговые методы на клиенте (Вопрос, ПоказатьВопрос, открытие формы с ожиданием закрытия) не останавливают выполнение кода. Поэтому код после вызова диалога сработает сразу, не дожидаясь ответа. Результат приходит в процедуру-обработчик через ОписаниеОповещения:
&НаКлиенте
Процедура УдалитьСтроки(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеВопроса", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Удалить выбранные строки?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВопроса(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
// удаление строк
КонецЕсли;
КонецПроцедуры
Процедура-обработчик должна находиться в модуле, который передан вторым параметром (ЭтотОбъект), и быть доступной для вызова. Для формы этого достаточно, для общего модуля потребуется пометить её Экспорт.
В актуальных релизах платформы 8.3 (начиная с 8.3.18) появились асинхронные методы с ключевым словом Ждать, где код читается последовательно. Они удобнее цепочек оповещений, но на более ранних платформах не заработают, поэтому смотрите на номер релиза у пользователей.
Если код после диалога «исполняется раньше времени», причина почти всегда в этом: вы ждали синхронного ответа, а его нет.
Ошибки при работе с клиентскими процедурами
Ниже самые частые ситуации. Точные тексты сообщений зависят от релиза, поэтому ориентируйтесь на симптом.
ОписаниеОповещения.Что нельзя делать на клиенте
Список короткий, но запомнить его стоит:
- 🚫 выполнять запросы к базе;
- 🚫 создавать и записывать объекты базы напрямую (документы, элементы справочников);
- 🚫 вызывать процедуры серверных модулей без клиентского флага;
- 🚫 рассчитывать на то, что серверное состояние сохранится между вызовами. Каждый вызов сервера получает данные заново.
Как ускорить клиентскую процедуру
Процедура на клиенте сама быстра. Тормозит обычно то, что вокруг неё.
Меньше обращений к серверу
Один вызов в цикле на 500 строк — это 500 поездок на сервер. Соберите нужные данные одним вызовом, верните массив или таблицу и обрабатывайте на клиенте:
// Плохо
Для Каждого Строка Из Объект.Товары Цикл
Строка.Остаток = ПолучитьОстатокНаСервере(Строка.Номенклатура);
КонецЦикла;
// Лучше
Остатки = ПолучитьОстаткиНаСервере(Объект.Товары.Выгрузить(, "Номенклатура"));
Оценка простая: если сетевая задержка 50 мс, 500 вызовов дадут около 25 секунд ожидания, один вызов даст 50 мс плюс время самой выборки.
Лёгкий серверный контекст
Для серверной части, которой не нужны данные формы, используйте &НаСервереБезКонтекста. На сервер тогда не уходит весь контекст формы, а только параметры. Выигрыш заметен на формах с большими табличными частями.
Не гоняйте данные туда-обратно
Передавайте на сервер только нужные поля, а не всю таблицу целиком. Возвращайте только результат. Чем меньше данных пересекает границу, тем быстрее отклик.
Кэш на клиенте
Значения, которые не меняются за сеанс (например, настройки пользователя), считайте один раз и храните в реквизите формы или в модуле с повторным использованием возвращаемых значений. Но помните: кэш в рамках вызова и в рамках сеанса ведёт себя по-разному, сверяйтесь с настройкой модуля.
Практические примеры: от простого к сложному
Пример 1. Показать и скрыть поле
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
Элементы.ГруппаДоставка.Видимость = (Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОпераций.Доставка"));
КонецПроцедуры
Чисто клиентская логика: сервер не нужен, реакция мгновенная.
Пример 2. Клиент спрашивает сервер
Пользователь выбрал контрагента, нужно подставить договор по умолчанию:
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
Объект.Договор = ПолучитьДоговорПоУмолчанию(Объект.Контрагент);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДоговорПоУмолчанию(Контрагент)
Запрос = Новый Запрос("ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ Справочник.Договоры ГДЕ Владелец = &К");
Запрос.УстановитьПараметр("К", Контрагент);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Ссылка, Справочники.Договоры.ПустаяСсылка());
КонецФункции
Запрос живёт на сервере, клиент получает ссылку. Одна поездка на действие.
Пример 3. Длинная операция с отчётом о ходе
- Клиентская процедура запускает фоновое задание на сервере и подключает обработчик ожидания.
- Каждые несколько секунд клиент проверяет состояние задания.
- Когда задание завершилось, клиент забирает результат и показывает оповещение.
Пользователь всё это время может работать с формой, а не смотреть на «песочные часы».
&НаКлиенте. Сервер не нужен.&НаСервереБезКонтекста, если контекст формы не требуется, иначе &НаСервере. Результат верните клиенту.ОписаниеОповещения.Отладка клиентских процедур
Отладка работает и для клиентского, и для серверного кода, но настраивается одинаково для обоих.
- Откройте конфигуратор и модуль формы с нужной процедурой.
- Поставьте точку останова на строке клавишей
F9. - Запустите отладку через меню
Отладка→Начать отладку(клавишаF5). - Выполните действие в открывшемся приложении. Выполнение остановится на точке.
- Идите по коду:
F11заходит внутрь вызова,F10проходит строку целиком. - Значения переменных смотрите в окне локальных переменных или через вычисление выражения.
Отладка выполняется в вашей информационной базе. Не отлаживайте код, который пишет данные, на рабочей базе: используйте копию.
Если точка останова «не срабатывает», проверьте два момента. Первый: отлаживаемый клиент запущен из конфигуратора, а не отдельно. Второй: модуль в запущенной конфигурации совпадает с тем, что вы открыли, то есть изменения применены.
Для клиент-серверного вызова точка останова может сработать сначала на клиенте, затем на сервере. Это нормально: вы видите весь путь вызова.
Простые сообщения тоже работают. Вызов Сообщить("значение = " + Строка(Переменная)) на клиенте выведет текст в окно сообщений без отладчика, что удобно для быстрой проверки.
Частые вопросы
Можно ли вызвать клиентскую процедуру из серверной?
Чем `&НаСервере` отличается от `&НаСервереБезКонтекста`?
Почему код после `ПоказатьВопрос` выполняется до ответа?
ОписаниеОповещения.