В разработке на платформе «1С:Предприятие 8.3» одной из наиболее частых преград для начинающих разработчиков становится непонимание клиент-серверной архитектуры. Типичный сценарий: требуется периодически проверять базу данных на наличие определенных событий (например, появление просроченных задач или заметок) и при их обнаружении выводить пользователю форму уведомления или списка документов. Попытка решить эту задачу «в лоб» из серверного общего модуля неизбежно приводит к ошибкам компиляции.
В данной статье мы подробно разберем, почему возникает популярная ошибка Переменная не определена (Элементы), выясним правила разделения контекста и рассмотрим пошаговые инструкции по реализации надежного механизма оповещения.
Разберем ситуацию, представленную в обсуждении. Автор пытается реализовать периодическую проверку актуальности заметок с помощью обработчика ожидания и сталкивается со следующими проблемами:
ОткрытьФорму является методом глобального контекста, доступным только на клиенте (в тонком, толстом или веб-клиенте).ЭтаФорма.ИзменитьРеквизиты и Элементы.Добавить. Системные переменные ЭтаФорма и Элементы доступны только внутри модуля самой управляемой формы. В общем модуле (даже если он компилируется на клиенте) этих переменных не существует, так как они принадлежат конкретному экземпляру формы. Попытка обратиться к ним напрямую вызывает ошибку компиляции: Переменная не определена (Элементы).Для исправления этой ситуации нам необходимо строго разделить бизнес-логику (получение данных из БД на сервере) и интерфейсную логику (обработку результата и открытие формы на клиенте).
Рассмотрим правильный паттерн проектирования данной задачи. Мы разделим процесс на три этапа:
Выполним реализацию по шагам.
Шаг 1. Создание серверной функции в общем модуле
Создадим или используем существующий общий модуль (например, ОбщегоНазначения). Убедимся, что в его свойствах установлены флажки Сервер и Вызов сервера. Напишем функцию, которая будет собирать данные о просроченных заметках и возвращать их на клиент в виде массива структур. Это исключит передачу тяжелых серверных типов (таких как набор записей или таблица значений) через границу «клиент-сервер»:
Функция ПолучитьПросроченныеЗаметки(ТекущийПользователь) Экспорт
МассивРезультатов = Новый Массив;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Заметки.Документ КАК Документ,
| Заметки.ДатаОповещения КАК ДатаОповещения,
| Заметки.Описание КАК Описание
|ИЗ
| РегистрСведений.Заметки КАК Заметки
|ГДЕ
| Заметки.Пользователь = &Пользователь
| И Заметки.Завершено = ЛОЖЬ
| И Заметки.ДатаОповещения <= &ТекущаяДата";
Запрос.УстановитьПараметр("Пользователь", ТекущийПользователь);
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СтруктураЗаписи = Новый Структура;
СтруктураЗаписи.Вставить("Документ", Выборка.Документ);
СтруктураЗаписи.Вставить("ДатаОповещения", Выборка.ДатаОповещения);
СтруктураЗаписи.Вставить("Описание", Выборка.Описание);
МассивРезультатов.Добавить(СтруктураЗаписи);
КонецЦикла;
Возврат МассивРезультатов;
КонецФункции
Шаг 2. Настройка обработчика ожидания на клиенте
Перейдем в модуль управляемого приложения (или модуль конкретной формы, если оповещения должны работать только в ней). Инициализируем обработчик ожидания, который будет срабатывать, например, каждые 60 секунд:
Процедура ПриНачалеРаботыСистемы()
// Подключаем обработчик ожидания.
// "ПроверитьАктуальныеЗаметки" - имя процедуры, которая должна быть объявлена как Экспорт
// 60 - интервал в секундах
// Ложь - обработчик будет выполняться многократно
ПодключитьОбработчикОжидания("ПроверитьАктуальныеЗаметки", 60, Ложь);
КонецПроцедуры
Шаг 3. Реализация клиентского обработчика ожидания
В этом же клиентском модуле опишем экспортную процедуру, которая будет получать данные с сервера и открывать форму в случае обнаружения записей:
Процедура ПроверитьАктуальныеЗаметки() Экспорт
ТекущийПользователь = ПараметрыСеанса.ТекущийПользователь; // Доступно на клиенте
// Вызываем серверную функцию общего модуля
СписокЗаметок = ОбщегоНазначения.ПолучитьПросроченныеЗаметки(ТекущийПользователь);
Если СписокЗаметок.Количество() > 0 Тогда
// Формируем параметры для открытия формы списка или специализированной формы уведомлений
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Отбор", Новый Структура("Завершено", Ложь));
ПараметрыФормы.Вставить("ПросроченныеЗаметки", СписокЗаметок);
// Открываем форму. Данный метод доступен ТОЛЬКО на клиенте
ОткрытьФорму("РегистрСведений.Заметки.ФормаСписка", ПараметрыФормы);
КонецЕсли;
КонецПроцедуры
Этот подход полностью безопасен, соответствует стандартам разработки фирмы «1С» и исключает любые ошибки контекста.
Если ваша задача действительно требует динамического формирования интерфейса (как пытался сделать автор в Сообщении 11), запомните главное правило: любое изменение структуры формы должно выполняться в контексте самой этой формы, а не в общем модуле.
Выясним, как правильно реализовать динамическое создание таблицы заметок на управляемой форме при её создании:
ПриСозданииНаСервере.Посмотрим на пример правильной реализации динамического создания таблицы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем данные для таблицы (можно вызвать функцию общего модуля)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Заметки.Пользователь КАК Пользователь,
| Заметки.Документ КАК Документ,
| Заметки.ДатаОповещения КАК ДатаОповещения
|ИЗ
| РегистрСведений.Заметки КАК Заметки";
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Шаг 1. Создаем новые реквизиты формы для хранения таблицы данных
МассивДобавляемыхРеквизитов = Новый Массив;
// Описываем структуру таблицы значений на форме
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("ТаблицаЗначений"));
ОписаниеТипаТЗ = Новый ОписаниеТипов(МассивТипов);
ИмяРеквизитаТЗ = "ДинамическаяТаблицаЗаметок";
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяРеквизитаТЗ, ОписаниеТипаТЗ, "", "Заметки пользователя"));
// Добавляем колонки в описание реквизита
Для Каждого Колонка Из ТаблицаДанных.Колонки Цикл
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(Колонка.Имя, Колонка.ТипЗначения, ИмяРеквизитаТЗ));
КонецЦикла;
// Применяем изменения структуры формы
ЭтаФорма.ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
// Шаг 2. Создаем визуальные элементы формы
// Добавляем саму таблицу на форму
ЭлементТаблица = Элементы.Добавить("ТаблицаИнтерфейсаЗаметок", Тип("ТаблицаФормы"));
ЭлементТаблица.ПутьКДанным = ИмяРеквизитаТЗ;
ЭлементТаблица.Отображение = ОтображениеТаблицы.Список;
// Добавляем колонки (поля) внутрь таблицы формы
Для Каждого Колонка Из ТаблицаДанных.Колонки Цикл
НоваяКолонка = Элементы.Добавить("Колонка_" + Колонка.Имя, Тип("ПолеФормы"), ЭлементТаблица);
НоваяКолонка.Вид = ВидПоляФормы.ПолеВвода;
НоваяКолонка.ПутьКДанным = ИмяРеквизитаТЗ + "." + Колонка.Имя;
КонецЦикла;
// Шаг 3. Заполняем реквизит формы данными
ЗначениеВРеквизитФормы(ТаблицаДанных, ИмяРеквизитаТЗ);
КонецПроцедуры
В этом примере контекст ЭтаФорма и Элементы используется легитимно, так как код выполняется строго в рамках обработчика события формы на сервере. Если вам необходимо вынести эту логику в общий модуль для повторного использования, вы должны обязательно передавать саму форму в качестве параметра:
// В общем модуле (Сервер)
Процедура ИнициализироватьТаблицуФормы(ПолучательФорма, ТаблицаДанных) Экспорт
// Обращаемся к коллекции элементов переданной формы
ЭлементТаблица = ПолучательФорма.Элементы.Добавить("Таблица", Тип("ТаблицаФормы"));
// Дальнейшая логика аналогична...
КонецПроцедуры
Разработка собственной системы периодических проверок и всплывающих окон оправдана только в самописных конфигурациях «с нуля». Если вы работаете в любой типовой конфигурации фирмы «1С» (например, 1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей, 1С:УНФ), настоятельно рекомендуется использовать стандартную подсистему «Напоминания пользователя» из состава Библиотеки стандартных подсистем (БСП).
Преимущества использования БСП вместо собственных обработчиков ожидания:
Рассмотрим пример программного создания напоминания средствами БСП:
// Пример создания напоминания для пользователя по конкретному договору
ПараметрыНапоминания = Новый Структура;
ПараметрыНапоминания.Вставить("Пользователь", Пользователи.ТекущийПользователь());
ПараметрыНапоминания.Вставить("ВремяСобытия", ТекущаяДата() + 3600); // Напомнить через 1 час
ПараметрыНапоминания.Вставить("СпособУстановкиВремени", Перечисления.СпособыУстановкиВремениНапоминания.В ОдноВремя);
ПараметрыНапоминания.Вставить("Описание", "Проверить условия договора!");
ПараметрыНапоминания.Вставить("Источник", СсылкаНаДоговор); // Ссылка на объект базы данных
НапоминанияПользователяСлужебный.СоздатьНапоминание(ПараметрыНапоминания);
Используя данный метод, вы избавите себя от необходимости писать громоздкие обработчики ожидания и динамически перестраивать формы вручную.