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