Как реализовать периодическое выполнение кода из формы 1С 8.3 для автоматической записи данных?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разберем ситуацию, когда вам необходимо регулярно выполнять определенный код в открытой форме 1С 8.3, например, для автоматической записи данных через каждые N секунд. Основная сложность заключается в том, что форма открывается из серверного контекста, а для периодического выполнения кода в клиентском приложении традиционно используется клиентский механизм ПодключитьОбработчикОжидания. Проблема заключается в том, как запустить этот клиентский обработчик ожидания, если основная логика открытия формы происходит в серверном методе ПриСозданииНаСервере.

Почему ПодключитьОбработчикОжидания не может быть использован напрямую в ПриСозданииНаСервере?

Давайте проанализируем ситуацию. Когда мы открываем форму с помощью функции ОткрытьФорму из другой формы или обработки, на сервере срабатывает событие ПриСозданииНаСервере. В этом методе происходит инициализация формы, заполнение ее данными и выполнение другой подготовительной логики на стороне сервера. Однако, ПодключитьОбработчикОжидания является клиентской функцией. Это означает, что она может быть вызвана только в контексте, который исполняется на клиентской стороне (в браузере, тонком или толстом клиенте). Серверный код, такой как ПриСозданииНаСервере, исполняется на сервере 1С:Предприятия и не имеет прямого доступа к функциям клиентского интерфейса. Именно поэтому при попытке использования ПодключитьОбработчикОжидания в ПриСозданииНаСервере вы получите ошибку.

Нам необходимо каким-то образом "передать эстафету" с сервера на клиент и уже на клиенте инициировать периодическое выполнение. И здесь нам на помощь приходит событие ПриОткрытии.

Роль события ПриОткрытии в жизненном цикле формы

Для того чтобы понять, где именно нам следует запускать наш обработчик ожидания, рассмотрим подробнее последовательность выполнения событий при открытии управляемой формы в 1С 8.3:

  1. ОбработкаПолученияФормы() (в модуле менеджера объекта, если используется): Это самое первое событие, которое позволяет переопределить открываемую форму.
  2. Активация модуля формы на сервере: Код, расположенный в модуле формы с директивой &НаСервере, начинает выполняться.
  3. Активация модуля объекта: Если форма связана с объектом (например, документ или справочник), то срабатывает код в его модуле объекта.
  4. ПриЧтенииНаСервере(): Вызывается для существующих объектов, когда данные объекта читаются с сервера.
  5. ПриСозданииНаСервере(): Это событие вызывается при создании формы на сервере. Здесь происходит основная серверная подготовка формы, заполнение реквизитов, инициализация списка и так далее. Это тот самый метод, о котором вы упоминали, и который исполняется до того, как форма фактически отобразится на клиенте.
  6. Активация модуля формы на клиенте: После того как серверная часть работы завершена и форма готова к передаче на клиент, активируется клиентская часть модуля формы.
  7. ПриОткрытии(): Это клиентское событие, которое срабатывает непосредственно перед тем, как форма становится видимой пользователю. Оно идеально подходит для выполнения действий, которые должны произойти на клиенте только после полной загрузки и готовности формы к отображению. Именно здесь мы и будем подключать наш обработчик ожидания.

Таким образом, событие ПриОткрытии является тем самым "мостом" между серверной инициализацией и клиентским взаимодействием, где мы можем безопасно инициировать клиентские процедуры.

Как добавить обработчик события ПриОткрытии в форму (и в расширение)

Часто возникает вопрос, как добавить обработчик события, если его нет в модуле формы по умолчанию. В 1С 8.3 для управляемых форм это делается через свойства формы:

  1. Откройте форму в конфигураторе: Вам необходимо открыть саму форму (не через дерево конфигурации, а двойным щелчком по названию формы в дереве объектов метаданных). Это действие откроет редактор формы.
  2. Перейдите к свойствам формы: В окне редактора формы (обычно слева внизу, или вызывается через контекстное меню/панель свойств) найдите панель "Свойства".
  3. Найдите раздел "События": В свойствах формы прокрутите вниз до раздела "События".
  4. Выберите событие ПриОткрытии: В списке событий найдите "ПриОткрытии". Рядом с ним обычно есть кнопка с лупой или многоточием. Нажмите на нее.
  5. Создайте обработчик: Откроется диалоговое окно, предлагающее создать новый обработчик события. Выберите опцию "Создать на клиенте" (если доступно и необходимо, но для ПриОткрытии это всегда клиентское событие) и нажмите "ОК". Конфигуратор автоматически добавит в модуль формы процедуру ПриОткрытии с директивой &НаКлиенте.

Особенности для расширений:

Если вы работаете в расширении и модифицируете форму из основной конфигурации, модуль заимствованной формы изначально может быть пустым. Однако вы точно так же можете добавить в него обработчики событий, следуя вышеописанным шагам. При этом вы сможете выбрать, будет ли ваш обработчик события выполняться:

Для нашей задачи, где мы добавляем новую функциональность, обычно подойдет выполнение "После" или просто создание обработчика, если его в основной конфигурации не было.

Реализация периодического выполнения с помощью ПодключитьОбработчикОжидания

Теперь, когда у нас есть место для запуска клиентского кода, мы можем использовать ПодключитьОбработчикОжидания. Разберем шаги:

  1. Определим процедуру-обработчик: Нам понадобится клиентская процедура, которая будет вызываться регулярно. Важно, чтобы эта процедура была объявлена с директивой &НаКлиенте и не имела формальных параметров. Если она будет вызываться из ПодключитьОбработчикОжидания, она также должна быть экспортируемой, если находится не непосредственно в модуле формы, а, например, в общем модуле, чтобы ПодключитьОбработчикОжидания мог её найти. Для процедуры в модуле формы Экспорт не является строго обязательным.
  2. Подключим обработчик в ПриОткрытии: В созданном нами обработчике ПриОткрытии мы вызовем функцию ПодключитьОбработчикОжидания.
  3. Отключим обработчик при закрытии: Чтобы периодическое выполнение прекратилось при закрытии формы, необходимо отключить обработчик. Для этого используем событие формы ПриЗакрытии и функцию ОтключитьОбработчикОжидания.

Синтаксис ПодключитьОбработчикОжидания:

ПодключитьОбработчикОжидания(<ИмяПроцедуры>, <Интервал>, [<Однократно>]);

Важные моменты:

Взаимодействие клиентского и серверного кода из обработчика ожидания

Наш обработчик ожидания (например, МояПроцедураРегулярногоВыполнения) работает на клиенте. Однако, для записи данных в базу данных, нам потребуется выполнить серверный код. Прямой вызов клиентской процедуры с сервера невозможен, но из клиентской процедуры мы можем вызывать серверные.

Мы используем клиентскую процедуру, которая периодически срабатывает, а уже из нее вызываем серверную процедуру для выполнения операций с данными (например, запись).

Существует два основных способа вызова серверных процедур из клиентских:

  1. Контекстный вызов (&НаСервере): При таком вызове на сервер передается весь контекст формы (все данные формы). Это удобно, когда серверной процедуре нужен доступ к реквизитам формы. Однако это может быть неэффективно, если контекст формы большой, а серверной процедуре нужны только отдельные параметры.
  2. Внеконтекстный вызов (&НаСервереБезКонтекста): При таком вызове на сервер передаются только явно указанные параметры. Контекст формы не передается. Это более производительный вариант, если серверной процедуре не нужен доступ ко всему контексту формы, а требуются лишь конкретные значения.

Для задач типа автозаписи данных, где, возможно, нужно передать лишь несколько параметров (например, ID объекта, данные для записи), внеконтекстный вызов может быть предпочтительнее для оптимизации производительности.

Пример: в вашей клиентской процедуре, которая запускается по ПодключитьОбработчикОжидания, вы можете вызывать серверную процедуру:


&НаКлиенте
Процедура МояПроцедураРегулярногоВыполнения() Экспорт
    // Получаем данные с клиента, которые нужно записать
    ПеременнаяСДанными = Объект.МойРеквизит; 
    
    // Вызываем серверную процедуру для записи
    ВыполнитьДействиеНаСервере(ПеременнаяСДанными); 
КонецПроцедуры

&НаСервереБезКонтекста // Или &НаСервере, если нужен весь контекст формы
Процедура ВыполнитьДействиеНаСервере(ДанныеДляЗаписи)
    // Здесь выполняется серверный код, например, запись данных в базу
    // Можно получить объект по ссылке и записать его
    Попытка
        ОбъектДляЗаписи = Справочники.МойСправочник.ПолучитьСсылку(Новый УникальныйИдентификатор("...")); // Пример получения ссылки
        // или
        ОбъектДляЗаписи = РеквизитФормыВЗначение("Объект"); // Если это контекстный вызов и объект доступен
        
        // Модифицируем объект
        // ОбъектДляЗаписи.МойРеквизит = ДанныеДляЗаписи; 
        
        // Записываем его
        // ОбъектДляЗаписи.Записать(); 
        
        // Или делаем прямые записи в регистры и т.п.
        
        Сообщить("Данные успешно записаны на сервере: " + Строка(ДанныеДляЗаписи));
    Исключение
        Сообщить("Ошибка записи данных на сервере: " + ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

Примеры кода

Рассмотрим полный пример реализации регулярного выполнения кода для автозаписи данных в модуле управляемой формы.


// *** Начало клиентских процедур ***

&НаКлиенте
Перем м_ИдентификаторОбработчикаОжидания; // Переменная для хранения идентификатора обработчика, если необходимо

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // Определяем интервал выполнения в секундах (например, каждые 5 секунд)
    Конст_ИнтервалВСекундах = 5; 
    
    // Подключаем обработчик ожидания. 
    // "МояПроцедураРегулярногоВыполнения" будет вызываться каждые Конст_ИнтервалВСекундах.
    ПодключитьОбработчикОжидания("МояПроцедураРегулярногоВыполнения", Конст_ИнтервалВСекундах);
    
    Сообщить("Автозапись данных включена. Интервал: " + Строка(Конст_ИнтервалВСекундах) + " секунд.");
КонецПроцедуры

&НаКлиенте
Процедура ПриЗакрытии()
    // Отключаем обработчик ожидания при закрытии формы
    ОтключитьОбработчикОжидания("МояПроцедураРегулярногоВыполнения");
    Сообщить("Автозапись данных отключена.");
КонецПроцедуры

&НаКлиенте
Процедура МояПроцедураРегулярногоВыполнения() Экспорт
    // Здесь мы выполняем клиентский код, который предшествует серверной записи.
    // Например, можно получить текущие значения полей формы.
    Попытка
        ТекущееЗначениеПоля = Объект.Наименование; // Предположим, у формы есть реквизит "Объект"
        ТекущийПользователь = Пользователи.ТекущийПользователь().Имя;
        
        // Вызываем серверную процедуру для выполнения записи.
        // Передаем необходимые данные.
        ЗаписатьДанныеНаСервере(ТекущееЗначениеПоля, ТекущийПользователь);
        
    Исключение
        Сообщить("Ошибка в клиентской процедуре МояПроцедураРегулярногоВыполнения: " + ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

// *** Конец клиентских процедур ***


// *** Начало серверных процедур ***

&НаСервереБезКонтекста // Или &НаСервере, если нужен весь контекст формы
Процедура ЗаписатьДанныеНаСервере(ЗначениеПоля, ИмяПользователя)
    // Эта процедура выполняется на сервере.
    // Здесь мы реализуем логику записи данных в базу.
    Попытка
        // Пример записи данных:
        // Создаем или получаем объект, который нужно обновить
        // Например, если форма редактирует справочник "МойСправочник"
        // ИмяРеквизитаОбъекта = "Объект"; // Если форма основана на объекте
        // СсылкаНаОбъект = РеквизитФормыВЗначение(ИмяРеквизитаОбъекта).Ссылка;
        // Если СсылкаНаОбъект.Пустая() Тогда
        //     ОбъектДляИзменения = Справочники.МойСправочник.СоздатьЭлемент();
        // Иначе
        //     ОбъектДляИзменения = СсылкаНаОбъект.ПолучитьОбъект();
        // КонецЕсли;
        
        // Устанавливаем или обновляем данные
        // ОбъектДляИзменения.Наименование = ЗначениеПоля;
        // ОбъектДляИзменения.Комментарий = "Автоматическая запись по пользователю: " + ИмяПользователя;
        // ОбъектДляИзменения.ДатаПоследнегоИзменения = ТекущаяДата();
        
        // ОбъектДляИзменения.Записать();
        
        // Или, например, запись в регистр сведений:
        НаборЗаписей = РегистрыСведений.МоиДанныеАвтозаписи.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Пользователь.Установить(Пользователи.НайтиПоИмени(ИмяПользователя));
        НаборЗаписей.Прочитать();
        
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Пользователь = Пользователи.НайтиПоИмени(ИмяПользователя);
        НоваяЗапись.Значение = ЗначениеПоля;
        НоваяЗапись.ДатаВремяЗаписи = ТекущаяДата();
        
        НаборЗаписей.Записать();
        
        // Отладка:
        // Сообщить("Сервер: Данные '" + Строка(ЗначениеПоля) + "' записаны для пользователя '" + ИмяПользователя + "' в " + Строка(ТекущаяДата()));
        
    Исключение
        // Важно обрабатывать ошибки на сервере, чтобы они не приводили к падению клиентского сеанса
        ЗаписьЖурналаРегистрации(
            "Автозапись",
            УровеньЖурналаРегистрации.Ошибка,
            ,
            ,
            "Ошибка при автоматической записи данных на сервере: " + ОписаниеОшибки()
        );
        // Можно также вывести сообщение на клиент, если это критично:
        // СообщитьНаКлиенте("Произошла ошибка при автозаписи данных.");
    КонецПопытки;
КонецПроцедуры

// &НаКлиенте
// Процедура СообщитьНаКлиенте(ТекстСообщения)
//     Сообщить(ТекстСообщения);
// КонецПроцедуры

// *** Конец серверных процедур ***

Таким образом, мы успешно обходим ограничение на использование клиентских функций на сервере, используя правильную последовательность событий формы 1С и механизм клиент-серверного взаимодействия.

← На главную