Передача данных с сервера в управляемую форму 1С 8.3

· 10 мин

Содержание

Данные в управляемую форму грузятся на сервере — у клиента нет прямого доступа ни к базе, ни к запросам. Поэтому любое заполнение формы с сервера сводится к одному: вызвать серверную процедуру, прочитать данные запросом или через менеджер объекта и положить результат в реквизит формы. Разберём, как это сделать для реквизитов, табличных частей и динамических списков, чем отличается &НаСервере от &НаСервереБезКонтекста и как не превратить форму в вереницу лишних обращений к серверу.

Выбор способа зависит от того, когда нужны данные. При открытии формы — заполняем в ПриСозданииНаСервере. По нажатию кнопки или смене поля — вызываем серверную процедуру из клиентского обработчика. Для списков чаще всего вообще ничего писать не надо: подойдёт динамический список с собственным запросом.

Как устроено разделение на клиент и сервер

Управляемая форма исполняется в двух контекстах одновременно. Часть кода работает на клиенте (у пользователя), часть — на сервере (где доступна база данных). Контекст задаётся директивой компиляции перед процедурой:

  • 🖥️ &НаКлиенте — выполняется у пользователя. Нет доступа к запросам и менеджерам объектов, зато доступны интерактивные методы и элементы формы.
  • 🗄️ &НаСервере — выполняется на сервере, имеет доступ к базе. На сервер передаётся весь контекст формы (её реквизиты), а после выполнения возвращается обратно.
  • ⚡ &НаСервереБезКонтекста — тоже на сервере, но без передачи данных формы. Работает быстрее, потому что гонять контекст туда-обратно не нужно.
  • 🔁 &НаКлиентеНаСервереБезКонтекста — компилируется в обе среды, для общих вспомогательных функций без обращения к базе.

Главное правило: каждый вызов сервера из клиента — это сетевое обращение. Чем их меньше, тем отзывчивее форма. Поэтому заполнение данными планируют так, чтобы за один серверный вызов прочитать всё нужное, а не дёргать сервер в цикле.

Клиентский обработчик (&НаКлиенте) не может вызвать &НаСервереБезКонтекста-процедуру и при этом рассчитывать, что та увидит реквизиты формы. Если процедуре нужны данные формы — либо берите &НаСервере, либо передавайте нужные значения параметрами.

Заполнение формы при открытии

Самый частый сценарий — форма должна открыться уже с данными. Для этого в модуле формы используют событие ПриСозданииНаСервере: оно отрабатывает на сервере один раз при формировании формы, до её показа пользователю.

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

Пример — заполнить реквизит формы КурсВалюты текущим курсом из регистра сведений:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    Отбор = Новый Структура("Валюта", Объект.Валюта);
    Ресурсы = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(
        ТекущаяДата(), Отбор);

    Если Ресурсы <> Неопределено Тогда
        КурсВалюты = Ресурсы.Курс;
    КонецЕсли;

КонецПроцедуры

Здесь КурсВалюты — реквизит самой формы (не объекта). Поскольку процедура &НаСервере, она видит реквизиты формы и после выполнения вернёт заполненное значение на клиент автоматически.

Если данные тяжёлые, а в ПриСозданииНаСервере они нужны не всегда, вынесите чтение в отдельную процедуру &НаСервереБезКонтекста и верните результат через возвращаемое значение или параметр. Так контекст формы не придётся гонять на сервер ради одного числа.

Длинные расчёты в ПриСозданииНаСервере замедляют открытие формы для всех пользователей. Если заполнение можно отложить до действия пользователя — отложите.

Чтение данных: запрос или менеджер объекта

На сервере данные берут двумя путями:

  1. Запросом — когда нужна выборка, агрегация, соединение таблиц. Запрос компилируется и выполняется только на сервере.
  2. Через менеджер — Справочники.Номенклатура.НайтиПоКоду(...), Документы.РеализацияТоваровУслуг.СоздатьДокумент() и подобное — когда нужен один объект или его реквизиты.

Запрос для заполнения формы выглядит обычно так:

&НаСервере
Процедура ЗаполнитьОстатки()

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |   ОстаткиНоменклатуры.Номенклатура КАК Номенклатура,
    |   ОстаткиНоменклатуры.КоличествоОстаток КАК Остаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, )
    |       КАК ОстаткиНоменклатуры";

    Запрос.УстановитьПараметр("Период", ТекущаяДата());

    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = Остатки.Добавить();
        НоваяСтрока.Номенклатура = Выборка.Номенклатура;
        НоваяСтрока.Остаток = Выборка.Остаток;
    КонецЦикла;

КонецПроцедуры

Параметры в запрос всегда передавайте через УстановитьПараметр, а не склейкой строк — это защищает от ошибок с типами и делает текст запроса читаемым.

Когда удобнее работать с объектом целиком

Иногда заполнять реквизиты формы по одному неудобно: проще получить объект, отработать его штатными методами (например, вызвать общий модуль заполнения) и вернуть обратно в форму. Для этого есть пара методов преобразования.

  • РеквизитФормыВЗначение("Объект") — собирает из данных формы прикладной объект (документ, элемент справочника), с которым можно работать как обычно.
  • ЗначениеВРеквизитФормы(Объект, "Объект") — кладёт изменённый объект обратно в реквизит формы.
&НаСервере
Процедура ЗаполнитьЧерезОбъект()
    ОбъектДокумента = РеквизитФормыВЗначение("Объект");
    ОбъектДокумента.Заполнить(Неопределено);   // штатное заполнение
    ЗначениеВРеквизитФормы(ОбъектДокумента, "Объект");
КонецПроцедуры

Такой приём экономит код, когда логика заполнения уже реализована в модуле объекта и дублировать её в форме не хочется.

Заполнение по действию пользователя

Если данные нужны не при открытии, а по нажатию кнопки или по смене значения в поле, схема двухступенчатая: клиентский обработчик ловит действие и вызывает серверную процедуру.

&НаКлиенте
Процедура ЗаполнитьПоКнопке(Команда)
    ЗаполнитьНаСервере();
    // здесь форма уже обновлена данными с сервера
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьНаСервере()
    // чтение данных и запись в реквизиты формы
КонецПроцедуры

Когда серверной процедуре не нужны все данные формы, а достаточно пары значений, передавайте их параметрами и объявляйте процедуру &НаСервереБезКонтекста:

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
    ЗаполнитьРеквизиты(Объект.Контрагент);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаполнитьРеквизиты(Контрагент)
    // быстро: контекст формы на сервер не передаётся
КонецПроцедуры

Но тут есть ловушка. Процедура без контекста не видит реквизиты формы и не может писать в них напрямую. Чтобы вернуть данные, используйте возвращаемое значение функции или параметр, передаваемый по ссылке, и присвойте результат в клиентском коде.

Что можно передавать между клиентом и сервером

Через границу клиент-сервер проходят только сериализуемые значения: примитивы (число, строка, дата, булево), ссылки на объекты базы, структуры, соответствия, массивы, таблицы и деревья значений из таких типов. Нельзя передавать объект документа или справочника, менеджер, запрос, выборку — они живут только на сервере.

Если на клиенте нужно поработать с объектом, передайте его ссылку, а чтение реквизитов сделайте на сервере и верните уже готовые значения.

Табличные части и динамические списки

Два разных механизма, и путают их часто.

Табличная часть объекта (Объект.Товары) — это данные документа или справочника. Заполняют её на сервере в цикле по выборке запроса, как в примере с остатками выше, обращаясь к Объект.Товары.Добавить() или Объект.Товары.Очистить() перед заполнением.

&НаСервере
Процедура ЗаполнитьТоварыИзЗаказа(СсылкаНаЗаказ)

    Объект.Товары.Очистить();

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ Товары.Номенклатура, Товары.Количество, Товары.Цена
    |ИЗ Документ.ЗаказПокупателя.Товары КАК Товары
    |ГДЕ Товары.Ссылка = &Ссылка";
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаЗаказ);

    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = Объект.Товары.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
    КонецЦикла;

КонецПроцедуры

ЗаполнитьЗначенияСвойств(Приёмник, Источник) копирует одноимённые реквизиты — удобно, когда имена колонок запроса совпадают с именами колонок табличной части.

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

Параметры динамического списка задают из кода формы:

&НаСервере
Процедура УстановитьОтборСписка(Организация)
    СписокДокументов.Параметры.УстановитьЗначениеПараметра(
        "Организация", Организация);
КонецПроцедуры

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

Асинхронное обновление и интерактивные вызовы

В 1С 8.3 интерактивные методы — вопрос пользователю, выбор файла, открытие формы с ожиданием результата — работают асинхронно. Серверный вызов сам по себе синхронный: клиент ждёт ответа сервера. А вот диалоги с пользователем прерывать выполнение не должны.

Есть два подхода, в зависимости от версии платформы:

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

&НаКлиенте
Процедура ПослеОтвета(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = КодВозвратаДиалога.Да Тогда
        ЗаполнитьНаСервере();
    КонецЕсли;
КонецПроцедуры
  1. Через Ждать (Await) — в платформе 8.3.18 и новее появились асинхронные методы, которые пишутся линейно, без разрыва на обработчик. Процедуру помечают как Асинх, а вызов — ключевым словом Ждать. Код читается сверху вниз, как синхронный, но не блокирует интерфейс.

Какой выбрать — зависит от требований к совместимости. Если база может работать на платформе до 8.3.18, берите оповещения. Если минимальная версия платформы не ниже 8.3.18 — асинхронные методы читаются проще.

Отдельно про обновление данных, когда их изменила другая форма: механизм оповещений ОповеститьОбИзменении и обработчик ОбработкаОповещения. Одна форма сообщает об изменении объекта, другая ловит это и перечитывает данные с сервера.

Проверка заполнения перед записью

Данные с сервера загрузили — теперь их надо проверить перед записью. Для этого есть штатный механизм, не нужно писать проверки вручную в каждом обработчике.

У реквизитов объекта и формы включается свойство проверки заполнения. Метод ПроверитьЗаполнение() пробегает по ним и собирает незаполненные. Для своей логики используют событие ОбработкаПроверкиЗаполнения:

&НаСервере
Процедура ОбработкаПроверкиЗаполненияНаСервере(Отказ, ПроверяемыеРеквизиты)

    Если Объект.Сумма <= 0 Тогда
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Сумма должна быть больше нуля";
        Сообщение.Поле = "Объект.Сумма";
        Сообщение.Сообщить();
        Отказ = Истина;
    КонецЕсли;

КонецПроцедуры

Привязка сообщения к полю через свойство Поле подсвечивает проблемный реквизит прямо в форме — пользователь сразу видит, где ошибка. Установка Отказ = Истина отменяет запись.

Валидацию ввода «на лету» (пользователь меняет значение — проверяем сразу) вешают на событие поля ПриИзменении. Но тяжёлые проверки с обращением к базе выносите на сервер и не делайте их на каждое нажатие клавиши — только когда пользователь закончил ввод в поле.

Типичные ошибки и как их устранить

Диагностика: выберите симптом

Ещё одна частая беда — лишние серверные вызовы. Если форма при каждом изменении поля дёргает сервер, интерфейс начинает «тормозить» на медленном канале. Соберите данные за один вызов там, где это возможно, и выносите на сервер только то, что действительно требует базы.

Проверка перед публикацией формы
Выполнено: 0 / 6

Частые вопросы

Чем `&НаСервере` отличается от `&НаСервереБезКонтекста`?
&НаСервере передаёт на сервер весь контекст формы (её реквизиты) и возвращает его обратно — процедура видит и может менять данные формы. &НаСервереБезКонтекста контекст не передаёт, поэтому работает быстрее, но реквизиты формы ей недоступны: нужные значения передавайте параметрами.
Можно ли выполнить запрос прямо в клиентском обработчике?
Нет. Запрос компилируется и исполняется только на сервере. Из клиента вызовите серверную процедуру, а в ней уже выполните запрос и верните результат.
Как заполнить табличную часть по данным другого документа?
Передайте ссылку на документ-источник в серверную процедуру, прочитайте его табличную часть запросом, очистите приёмник через Очистить() и заполните в цикле. Для копирования одноимённых колонок удобен ЗаполнитьЗначенияСвойств.
Почему данные не обновляются после закрытия другой формы?
Форма не узнаёт об изменениях автоматически. Используйте механизм оповещений: изменившая данные форма вызывает ОповеститьОбИзменении, а принимающая обрабатывает это в ОбработкаОповещения и перечитывает данные с сервера.
Как уменьшить число обращений к серверу при заполнении?
Читайте все нужные данные за один серверный вызов, а не в цикле по строкам. Для тяжёлых чтений применяйте &НаСервереБезКонтекста. Для таблиц с большим числом строк используйте динамический список — он подгружает данные порциями сам.