Передача данных с сервера в управляемую форму 1С 8.3
Содержание
Данные в управляемую форму грузятся на сервере — у клиента нет прямого доступа ни к базе, ни к запросам. Поэтому любое заполнение формы с сервера сводится к одному: вызвать серверную процедуру, прочитать данные запросом или через менеджер объекта и положить результат в реквизит формы. Разберём, как это сделать для реквизитов, табличных частей и динамических списков, чем отличается &НаСервере от &НаСервереБезКонтекста и как не превратить форму в вереницу лишних обращений к серверу.
Выбор способа зависит от того, когда нужны данные. При открытии формы — заполняем в ПриСозданииНаСервере. По нажатию кнопки или смене поля — вызываем серверную процедуру из клиентского обработчика. Для списков чаще всего вообще ничего писать не надо: подойдёт динамический список с собственным запросом.
Как устроено разделение на клиент и сервер
Управляемая форма исполняется в двух контекстах одновременно. Часть кода работает на клиенте (у пользователя), часть — на сервере (где доступна база данных). Контекст задаётся директивой компиляции перед процедурой:
- 🖥️
&НаКлиенте— выполняется у пользователя. Нет доступа к запросам и менеджерам объектов, зато доступны интерактивные методы и элементы формы. - 🗄️
&НаСервере— выполняется на сервере, имеет доступ к базе. На сервер передаётся весь контекст формы (её реквизиты), а после выполнения возвращается обратно. - ⚡
&НаСервереБезКонтекста— тоже на сервере, но без передачи данных формы. Работает быстрее, потому что гонять контекст туда-обратно не нужно. - 🔁
&НаКлиентеНаСервереБезКонтекста— компилируется в обе среды, для общих вспомогательных функций без обращения к базе.
Главное правило: каждый вызов сервера из клиента — это сетевое обращение. Чем их меньше, тем отзывчивее форма. Поэтому заполнение данными планируют так, чтобы за один серверный вызов прочитать всё нужное, а не дёргать сервер в цикле.
Клиентский обработчик (&НаКлиенте) не может вызвать &НаСервереБезКонтекста-процедуру и при этом рассчитывать, что та увидит реквизиты формы. Если процедуре нужны данные формы — либо берите &НаСервере, либо передавайте нужные значения параметрами.
Заполнение формы при открытии
Самый частый сценарий — форма должна открыться уже с данными. Для этого в модуле формы используют событие ПриСозданииНаСервере: оно отрабатывает на сервере один раз при формировании формы, до её показа пользователю.
Открыть палитру событий можно так: в конфигураторе откройте форму, перейдите на закладку, где перечислены события формы, и для события ПриСозданииНаСервере создайте обработчик.
Пример — заполнить реквизит формы КурсВалюты текущим курсом из регистра сведений:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Отбор = Новый Структура("Валюта", Объект.Валюта);
Ресурсы = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(
ТекущаяДата(), Отбор);
Если Ресурсы <> Неопределено Тогда
КурсВалюты = Ресурсы.Курс;
КонецЕсли;
КонецПроцедуры
Здесь КурсВалюты — реквизит самой формы (не объекта). Поскольку процедура &НаСервере, она видит реквизиты формы и после выполнения вернёт заполненное значение на клиент автоматически.
Если данные тяжёлые, а в ПриСозданииНаСервере они нужны не всегда, вынесите чтение в отдельную процедуру &НаСервереБезКонтекста и верните результат через возвращаемое значение или параметр. Так контекст формы не придётся гонять на сервер ради одного числа.
Длинные расчёты в ПриСозданииНаСервере замедляют открытие формы для всех пользователей. Если заполнение можно отложить до действия пользователя — отложите.
Чтение данных: запрос или менеджер объекта
На сервере данные берут двумя путями:
- Запросом — когда нужна выборка, агрегация, соединение таблиц. Запрос компилируется и выполняется только на сервере.
- Через менеджер —
Справочники.Номенклатура.НайтиПоКоду(...),Документы.РеализацияТоваровУслуг.СоздатьДокумент()и подобное — когда нужен один объект или его реквизиты.
Запрос для заполнения формы выглядит обычно так:
&НаСервере
Процедура ЗаполнитьОстатки()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиНоменклатуры.Номенклатура КАК Номенклатура,
| ОстаткиНоменклатуры.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, )
| КАК ОстаткиНоменклатуры";
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Остатки.Добавить();
НоваяСтрока.Номенклатура = Выборка.Номенклатура;
НоваяСтрока.Остаток = Выборка.Остаток;
КонецЦикла;
КонецПроцедуры
Параметры в запрос всегда передавайте через УстановитьПараметр, а не склейкой строк — это защищает от ошибок с типами и делает текст запроса читаемым.
Когда удобнее работать с объектом целиком
Иногда заполнять реквизиты формы по одному неудобно: проще получить объект, отработать его штатными методами (например, вызвать общий модуль заполнения) и вернуть обратно в форму. Для этого есть пара методов преобразования.
РеквизитФормыВЗначение("Объект")— собирает из данных формы прикладной объект (документ, элемент справочника), с которым можно работать как обычно.ЗначениеВРеквизитФормы(Объект, "Объект")— кладёт изменённый объект обратно в реквизит формы.
&НаСервере
Процедура ЗаполнитьЧерезОбъект()
ОбъектДокумента = РеквизитФормыВЗначение("Объект");
ОбъектДокумента.Заполнить(Неопределено); // штатное заполнение
ЗначениеВРеквизитФормы(ОбъектДокумента, "Объект");
КонецПроцедуры
Такой приём экономит код, когда логика заполнения уже реализована в модуле объекта и дублировать её в форме не хочется.
Заполнение по действию пользователя
Если данные нужны не при открытии, а по нажатию кнопки или по смене значения в поле, схема двухступенчатая: клиентский обработчик ловит действие и вызывает серверную процедуру.
&НаКлиенте
Процедура ЗаполнитьПоКнопке(Команда)
ЗаполнитьНаСервере();
// здесь форма уже обновлена данными с сервера
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьНаСервере()
// чтение данных и запись в реквизиты формы
КонецПроцедуры
Когда серверной процедуре не нужны все данные формы, а достаточно пары значений, передавайте их параметрами и объявляйте процедуру &НаСервереБезКонтекста:
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
ЗаполнитьРеквизиты(Объект.Контрагент);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗаполнитьРеквизиты(Контрагент)
// быстро: контекст формы на сервер не передаётся
КонецПроцедуры
Но тут есть ловушка. Процедура без контекста не видит реквизиты формы и не может писать в них напрямую. Чтобы вернуть данные, используйте возвращаемое значение функции или параметр, передаваемый по ссылке, и присвойте результат в клиентском коде.
Что можно передавать между клиентом и сервером
Через границу клиент-сервер проходят только сериализуемые значения: примитивы (число, строка, дата, булево), ссылки на объекты базы, структуры, соответствия, массивы, таблицы и деревья значений из таких типов. Нельзя передавать объект документа или справочника, менеджер, запрос, выборку — они живут только на сервере.
Если на клиенте нужно поработать с объектом, передайте его ссылку, а чтение реквизитов сделайте на сервере и верните уже готовые значения.
Табличные части и динамические списки
Два разных механизма, и путают их часто.
Табличная часть объекта (Объект.Товары) — это данные документа или справочника. Заполняют её на сервере в цикле по выборке запроса, как в примере с остатками выше, обращаясь к Объект.Товары.Добавить() или Объект.Товары.Очистить() перед заполнением.
&НаСервере
Процедура ЗаполнитьТоварыИзЗаказа(СсылкаНаЗаказ)
Объект.Товары.Очистить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Товары.Номенклатура, Товары.Количество, Товары.Цена
|ИЗ Документ.ЗаказПокупателя.Товары КАК Товары
|ГДЕ Товары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаЗаказ);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;
КонецПроцедуры
ЗаполнитьЗначенияСвойств(Приёмник, Источник) копирует одноимённые реквизиты — удобно, когда имена колонок запроса совпадают с именами колонок табличной части.
Динамический список — это реквизит формы особого типа, который сам обращается к базе и сам подгружает строки порциями при прокрутке. Его не нужно заполнять циклом: достаточно задать источник данных. Для нестандартной выборки включите у списка произвольный запрос и впишите текст запроса в свойствах списка.
Параметры динамического списка задают из кода формы:
&НаСервере
Процедура УстановитьОтборСписка(Организация)
СписокДокументов.Параметры.УстановитьЗначениеПараметра(
"Организация", Организация);
КонецПроцедуры
Динамический список сам управляет чтением данных и пагинацией. Для больших таблиц это всегда лучше, чем читать всё запросом в обычную таблицу значений на форме: список не тянет в память тысячи строк разом.
Асинхронное обновление и интерактивные вызовы
В 1С 8.3 интерактивные методы — вопрос пользователю, выбор файла, открытие формы с ожиданием результата — работают асинхронно. Серверный вызов сам по себе синхронный: клиент ждёт ответа сервера. А вот диалоги с пользователем прерывать выполнение не должны.
Есть два подхода, в зависимости от версии платформы:
- Через оповещение (
ОписаниеОповещения) — работает во всех актуальных релизах. Вы передаёте имя процедуры-обработчика, которую платформа вызовет, когда пользователь ответит:
&НаКлиенте
Процедура СпроситьИЗаполнить(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеОтвета", ЭтотОбъект);
ПоказатьВопрос(Оповещение,
"Перезаполнить форму данными с сервера?",
РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеОтвета(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
ЗаполнитьНаСервере();
КонецЕсли;
КонецПроцедуры
- Через
Ждать(Await) — в платформе 8.3.18 и новее появились асинхронные методы, которые пишутся линейно, без разрыва на обработчик. Процедуру помечают какАсинх, а вызов — ключевым словомЖдать. Код читается сверху вниз, как синхронный, но не блокирует интерфейс.
Какой выбрать — зависит от требований к совместимости. Если база может работать на платформе до 8.3.18, берите оповещения. Если минимальная версия платформы не ниже 8.3.18 — асинхронные методы читаются проще.
Отдельно про обновление данных, когда их изменила другая форма: механизм оповещений ОповеститьОбИзменении и обработчик ОбработкаОповещения. Одна форма сообщает об изменении объекта, другая ловит это и перечитывает данные с сервера.
Проверка заполнения перед записью
Данные с сервера загрузили — теперь их надо проверить перед записью. Для этого есть штатный механизм, не нужно писать проверки вручную в каждом обработчике.
У реквизитов объекта и формы включается свойство проверки заполнения. Метод ПроверитьЗаполнение() пробегает по ним и собирает незаполненные. Для своей логики используют событие ОбработкаПроверкиЗаполнения:
&НаСервере
Процедура ОбработкаПроверкиЗаполненияНаСервере(Отказ, ПроверяемыеРеквизиты)
Если Объект.Сумма <= 0 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Сумма должна быть больше нуля";
Сообщение.Поле = "Объект.Сумма";
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Привязка сообщения к полю через свойство Поле подсвечивает проблемный реквизит прямо в форме — пользователь сразу видит, где ошибка. Установка Отказ = Истина отменяет запись.
Валидацию ввода «на лету» (пользователь меняет значение — проверяем сразу) вешают на событие поля ПриИзменении. Но тяжёлые проверки с обращением к базе выносите на сервер и не делайте их на каждое нажатие клавиши — только когда пользователь закончил ввод в поле.
Типичные ошибки и как их устранить
Экспорт или с несовместимой директивой компиляции. Клиентский код не видит &НаСервере-процедуру, если та не экспортная и лежит в общем модуле; в модуле формы экспорт не нужен, но директива должна допускать вызов.ПриСозданииНаСервере. Отложите заполнение до действия пользователя или оптимизируйте запрос (индексы, временные таблицы, отказ от соединений в цикле).Очистить(). Всегда очищайте табличную часть перед перезаполнением.УстановитьЗначениеПараметра не совпадает с именем в запросе. Проверьте точное совпадение имён.Ещё одна частая беда — лишние серверные вызовы. Если форма при каждом изменении поля дёргает сервер, интерфейс начинает «тормозить» на медленном канале. Соберите данные за один вызов там, где это возможно, и выносите на сервер только то, что действительно требует базы.
Частые вопросы
Чем `&НаСервере` отличается от `&НаСервереБезКонтекста`?
&НаСервере передаёт на сервер весь контекст формы (её реквизиты) и возвращает его обратно — процедура видит и может менять данные формы. &НаСервереБезКонтекста контекст не передаёт, поэтому работает быстрее, но реквизиты формы ей недоступны: нужные значения передавайте параметрами.Можно ли выполнить запрос прямо в клиентском обработчике?
Как заполнить табличную часть по данным другого документа?
Очистить() и заполните в цикле. Для копирования одноимённых колонок удобен ЗаполнитьЗначенияСвойств.Почему данные не обновляются после закрытия другой формы?
ОповеститьОбИзменении, а принимающая обрабатывает это в ОбработкаОповещения и перечитывает данные с сервера.Как уменьшить число обращений к серверу при заполнении?
&НаСервереБезКонтекста. Для таблиц с большим числом строк используйте динамический список — он подгружает данные порциями сам.