&НаКлиенте и &НаСервере в 1С: директивы компиляции в управляемых формах
В управляемой форме 1С код выполняется на двух сторонах, и граница между ними задаётся директивами компиляции. Неправильный выбор приводит либо к ошибке («метод недоступен на клиенте»), либо к медленной форме с десятками лишних серверных вызовов. В статье — все директивы, правила передачи данных, использование РеквизитФормыВЗначение, общих модулей с флагом «Вызов сервера», инструкций препроцессора и разбор типичных ошибок.
Задача: понять, где выполняется код формы
В управляемой форме 1С код живёт в двух местах одновременно: часть процедур выполняется на клиенте (на компьютере пользователя или в браузере), часть — на сервере приложений. Граница задаётся директивами компиляции — &НаКлиенте, &НаСервере и их вариантами. От правильного выбора зависят и работоспособность (на клиенте нет запросов и объектов базы), и скорость: каждый переход между сторонами — это сетевой вызов. Ниже разберём все директивы, что доступно в каждом контексте, как передавать данные и какие ошибки возникают чаще всего.
Директивы компиляции и контекст формы
Директива ставится строкой перед процедурой или функцией в модуле формы и определяет, где этот метод будет выполняться. Если директива не указана, метод в модуле формы считается серверным.
| Директива | Где выполняется | Контекст формы |
|---|---|---|
&НаКлиенте | Клиент | Есть: элементы, реквизиты, Объект в виде данных формы |
&НаСервере | Сервер | Есть: доступны реквизиты формы и ЭтаФорма; при вызове форма передаётся на сервер и возвращается обратно |
&НаСервереБезКонтекста | Сервер | Нет: доступны только параметры метода, форма не передаётся |
&НаКлиентеНаСервереБезКонтекста | Клиент и сервер (метод компилируется в обоих контекстах) | Нет: работа только с параметрами |
&НаКлиентеНаСервере | Клиент и сервер (выполняется там, откуда вызван) | Есть: доступ к форме и её реквизитам |
Типичный сценарий: пользователь нажимает кнопку на клиенте, клиентская процедура вызывает серверную, та выполняет работу с базой, после чего форма обновляется.
&НаКлиенте
Процедура ЗаполнитьТовары(Команда)
ЗаполнитьТовары_НаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьТовары_НаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ЗаполнитьТовары();
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
Метод ЗаполнитьТовары здесь — экспортная процедура модуля объекта документа. Важно: реквизит Объект на клиенте — это не ДокументОбъект, а особая структура данных формы. Настоящий серверный объект получают функцией РеквизитФормыВЗначение("Объект"), а после изменений возвращают обратно через ЗначениеВРеквизитФормы(...). Без второго вызова изменения останутся только в локальной переменной и до формы не дойдут.
Что доступно на клиенте и на сервере
На клиенте есть элементы формы, диалоги и сообщения пользователю, работа с буфером обмена и файлами пользовательского компьютера (с учётом ограничений веб-клиента), базовые типы и коллекции — массивы, структуры, соответствия, таблицы значений. Чего на клиенте нет: объектов Запрос, менеджеров Справочники и Документы, объектов базы данных (СправочникОбъект, ДокументОбъект), записи в регистры, транзакций и привилегированного режима. Всё, что требует обращения к базе, выполняется на сервере — в том числе запросы.
На сервере доступны запросы, объекты и менеджеры, регистры и транзакции, но нет взаимодействия с пользователем: серверный код не может показать диалог или открыть форму. Серверная процедура лишь изменяет данные формы и возвращает результат, а показывает его клиент.
Серверный вызов и его стоимость
Вызов серверного метода с клиента — это сетевое обращение: параметры сериализуются, уходят на сервер, метод выполняется, результат возвращается. Для метода &НаСервере дополнительно пересылается состояние формы — все её данные, включая табличные части. Чем больше документ, тем дороже вызов. Для методов &НаСервереБезКонтекста передаются лишь параметры, поэтому они дешевле и должны быть вариантом по умолчанию, если форма не нужна.
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
СтрокаТовара = Элементы.Товары.ТекущиеДанные;
СтрокаТовара.Цена = ЦенаНоменклатуры(СтрокаТовара.Номенклатура, Объект.Дата);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ЦенаНоменклатуры(Знач Номенклатура, Знач Дата)
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Номенклатура) КАК Цены");
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Цена;
КонецЕсли;
Возврат 0;
КонецФункции
Модификатор Знач у параметров серверных методов рекомендуется ставить, если изменённые значения на клиент возвращать не нужно: тогда платформа не пересылает их обратно. Главное правило — минимум обращений. Один вызов на сто строк лучше, чем сто вызовов по одной строке:
// Плохо: серверный вызов в цикле
Для Каждого Строка Из Объект.Товары Цикл
Строка.Цена = ЦенаНоменклатуры(Строка.Номенклатура, Объект.Дата);
КонецЦикла;
// Хорошо: один вызов, результат — соответствие «номенклатура → цена»
Номенклатура = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Номенклатура.Добавить(Строка.Номенклатура);
КонецЦикла;
Цены = ЦеныНоменклатуры(Номенклатура, Объект.Дата);
Для Каждого Строка Из Объект.Товары Цикл
Цена = Цены.Получить(Строка.Номенклатура);
Строка.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;
&НаСервереБезКонтекста
Функция ЦеныНоменклатуры(Знач Номенклатура, Знач Дата)
Результат = Новый Соответствие;
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура В (&Номенклатура)) КАК Цены");
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Результат.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;
Возврат Результат;
КонецФункции
Какие данные можно передавать между клиентом и сервером
Через границу свободно ходят простые типы (число, строка, дата, булево), ссылки на объекты базы, перечисления, а также коллекции из них: массив, структура, соответствие, список значений, таблица значений. Нельзя передать объекты, которых на клиенте просто нет: СправочникОбъект, ДокументОбъект, Запрос, результат запроса, менеджеры. Поэтому на клиент возвращают данные (структуру, таблицу значений), а не серверный объект; обратно же передают ссылку и получают объект на сервере через Ссылка.ПолучитьОбъект().
Чтобы передать с клиента табличную часть, её копируют в таблицу значений или массив структур на клиенте либо обращаются к ней на сервере через РеквизитФормыВЗначение("Объект") — во втором случае вызов должен быть методом с контекстом формы.
Общие модули: флаги Клиент, Сервер, Вызов сервера
Код, нужный нескольким формам, выносят в общие модули. Среди свойств модуля есть флаги исполнения. «Клиент (управляемое приложение)» и «Сервер» определяют, где модуль компилируется. Флаг «Вызов сервера» разрешает клиентскому коду вызывать экспортные методы серверного модуля напрямую: каждое такое обращение — это один серверный вызов. Без флага серверный модуль с клиента вызвать нельзя.
В конфигурациях принято соглашение об именах: суффикс ВызовСервера у модулей с флагом вызова с клиента (например, РаботаСЦенамиВызовСервера), суффикс Клиент у клиентских, КлиентСервер у модулей, доступных с обеих сторон.
// Общий модуль РаботаСЦенамиВызовСервера: флаги «Сервер» и «Вызов сервера»
Функция ЦенаНоменклатуры(Знач Номенклатура, Знач Дата) Экспорт
// тело функции как выше
КонецФункции
// Модуль формы, клиент:
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
СтрокаТовара = Элементы.Товары.ТекущиеДанные;
СтрокаТовара.Цена = РаботаСЦенамиВызовСервера.ЦенаНоменклатуры(
СтрокаТовара.Номенклатура, Объект.Дата);
КонецПроцедуры
Методы без контекста для обеих сторон
Если функция чисто вычислительная и не требует ни формы, ни базы, её удобно объявить как &НаКлиентеНаСервереБезКонтекста. Она одинаково вызывается и из клиентских, и из серверных процедур и не порождает серверных вызовов.
&НаКлиентеНаСервереБезКонтекста
Функция СуммаСтроки(Знач Количество, Знач Цена, Знач Скидка)
Возврат Окр(Количество * Цена * (1 - Скидка / 100), 2);
КонецФункции
Инструкции препроцессора
В общем модуле, доступном и на клиенте, и на сервере, часть кода может быть применима только к одной стороне. Для этого есть инструкции препроцессора #Если ... Тогда, которые решают, компилировать ли фрагмент в данном контексте.
Функция КоличествоЗаказов(Контрагент) Экспорт
#Если Сервер Тогда
Запрос = Новый Запрос(
"ВЫБРАТЬ
| КОЛИЧЕСТВО(*) КАК Количество
|ИЗ
| Документ.ЗаказКлиента КАК Заказы
|ГДЕ
| Заказы.Контрагент = &Контрагент");
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
Возврат Выборка.Количество;
#Иначе
ВызватьИсключение НСтр("ru = 'Функция доступна только на сервере.'");
#КонецЕсли
КонецФункции
Чаще всего встречаются условия Сервер, Клиент, ТонкийКлиент, ВебКлиент, ТолстыйКлиентУправляемоеПриложение и ВнешнееСоединение; их можно комбинировать через Или и Не. Блок завершается инструкцией #КонецЕсли. Препроцессор не заменяет директивы: он нужен, когда один и тот же метод компилируется в нескольких контекстах и отдельные его строки в каких-то из них недопустимы.
Типичные ошибки
- Вызов клиентского метода из серверного. Сервер не может обратиться к методу с директивой
&НаКлиенте: платформа не найдёт такой метод в серверном контексте. Результат нужно вернуть клиенту, а показать или обработать его — в клиентской процедуре. - Обращение к
Объектили реквизитам в&НаСервереБезКонтекста. В таком методе формы нет, переменная не определена. Передавайте нужные значения параметрами. - Использование серверных объектов на клиенте. Попытка вызвать
Новый Запрос, обратиться кСправочники.Номенклатураили записатьОбъект.Записать()в клиентской процедуре завершится сообщением о недоступности метода или типа в этом контексте. Точный текст зависит от версии платформы. Для записи документа с клиента используйте метод формыЗаписать()или серверную процедуру. - Передача объекта базы на сервер или обратно. Платформа сообщает о недопустимом типе параметра. Передавайте ссылку или набор нужных данных.
- Потерянные изменения. Получили
РеквизитФормыВЗначение, поменяли, аЗначениеВРеквизитФормыне вызвали — форма осталась прежней. - Серверные вызовы в цикле. Работает, но на каждой итерации тратится время на сеть. Собирайте данные пачкой и вызывайте сервер один раз.
- Лишний контекст. Метод помечен
&НаСервере, хотя форма ему не нужна, — на сервер уходят все данные формы. Переведите его на&НаСервереБезКонтекста.
Директивы: краткая шпаргалка
| Нужно | Выбор |
|---|---|
| Реакция на действие пользователя, работа с элементами | &НаКлиенте |
| Запись объекта, заполнение реквизитов формы данными из базы | &НаСервере |
| Получить данные из базы по параметрам | &НаСервереБезКонтекста |
| Чистая функция, нужная на обеих сторонах | &НаКлиентеНаСервереБезКонтекста |
| Общая логика, использующая форму на обеих сторонах | &НаКлиентеНаСервере |
| Вызвать сервер из общего клиентского кода | Модуль с флагом «Вызов сервера» |
Актуальность для новых версий
Набор директив &НаКлиенте, &НаСервере, &НаСервереБезКонтекста, &НаКлиентеНаСервереБезКонтекста, &НаКлиентеНаСервере и принципы клиент-серверного взаимодействия одинаковы во всех версиях платформы 8.3, включая 8.3.2x. В режиме веб-клиента и тонкого клиента ограничения на доступные объекты совпадают, различия касаются главным образом работы с файлами и расширениями браузера. Тексты сообщений об ошибках могут отличаться в зависимости от релиза.
Частые вопросы
Чем &НаСервере отличается от &НаСервереБезКонтекста?
&НаСервере имеет доступ к форме и её реквизитам, но при вызове на сервер уходят данные всей формы. &НаСервереБезКонтекста получает только параметры, поэтому вызов быстрее, а обращаться к Объекту и реквизитам формы внутри нельзя.
Почему нельзя передать объект справочника или документа на клиент?
Объекты базы данных (СправочникОбъект, ДокументОбъект) существуют только на сервере. На клиент передают ссылку, структуру или таблицу значений, а объект получают на сервере через Ссылка.ПолучитьОбъект() или РеквизитФормыВЗначение("Объект").
Как вызвать серверную функцию общего модуля из клиентского кода?
У общего модуля включите флаги «Сервер» и «Вызов сервера» и объявите функцию с ключевым словом Экспорт. После этого её можно вызывать из клиентской процедуры формы, и каждый такой вызов будет одним обращением к серверу.
Что значит директива &НаКлиентеНаСервереБезКонтекста?
Метод компилируется и на клиенте, и на сервере, не использует форму и работает только с параметрами. Подходит для чистых вычислений, которые нужны с обеих сторон и не должны порождать серверный вызов.