Решение задач 1С:Предприятие 8 на примерах

&НаКлиенте и &НаСервере в 1С: директивы компиляции в управляемых формах

Управляемые формыОпубликовано

В управляемой форме 1С код выполняется на двух сторонах, и граница между ними задаётся директивами компиляции. Неправильный выбор приводит либо к ошибке («метод недоступен на клиенте»), либо к медленной форме с десятками лишних серверных вызовов. В статье — все директивы, правила передачи данных, использование РеквизитФормыВЗначение, общих модулей с флагом «Вызов сервера», инструкций препроцессора и разбор типичных ошибок.

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

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

Директивы компиляции и контекст формы

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

ДирективаГде выполняетсяКонтекст формы
&НаКлиентеКлиентЕсть: элементы, реквизиты, Объект в виде данных формы
&НаСервереСерверЕсть: доступны реквизиты формы и ЭтаФорма; при вызове форма передаётся на сервер и возвращается обратно
&НаСервереБезКонтекстаСерверНет: доступны только параметры метода, форма не передаётся
&НаКлиентеНаСервереБезКонтекстаКлиент и сервер (метод компилируется в обоих контекстах)Нет: работа только с параметрами
&НаКлиентеНаСервереКлиент и сервер (выполняется там, откуда вызван)Есть: доступ к форме и её реквизитам

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

&НаКлиенте
Процедура ЗаполнитьТовары(Команда)
    ЗаполнитьТовары_НаСервере();
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьТовары_НаСервере()
    ДокументОбъект = РеквизитФормыВЗначение("Объект");
    ДокументОбъект.ЗаполнитьТовары();
    ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры

Метод ЗаполнитьТовары здесь — экспортная процедура модуля объекта документа. Важно: реквизит Объект на клиенте — это не ДокументОбъект, а особая структура данных формы. Настоящий серверный объект получают функцией РеквизитФормыВЗначение("Объект"), а после изменений возвращают обратно через ЗначениеВРеквизитФормы(...). Без второго вызова изменения останутся только в локальной переменной и до формы не дойдут.

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

На клиенте есть элементы формы, диалоги и сообщения пользователю, работа с буфером обмена и файлами пользовательского компьютера (с учётом ограничений веб-клиента), базовые типы и коллекции — массивы, структуры, соответствия, таблицы значений. Чего на клиенте нет: объектов Запрос, менеджеров Справочники и Документы, объектов базы данных (СправочникОбъект, ДокументОбъект), записи в регистры, транзакций и привилегированного режима. Всё, что требует обращения к базе, выполняется на сервере — в том числе запросы.

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

Серверный вызов и его стоимость

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

&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    СтрокаТовара = Элементы.Товары.ТекущиеДанные;
    СтрокаТовара.Цена = ЦенаНоменклатуры(СтрокаТовара.Номенклатура, Объект.Дата);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ЦенаНоменклатуры(Знач Номенклатура, Знач Дата)

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

    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Цена;
    КонецЕсли;

    Возврат 0;

КонецФункции

Модификатор Знач у параметров серверных методов рекомендуется ставить, если изменённые значения на клиент возвращать не нужно: тогда платформа не пересылает их обратно. Главное правило — минимум обращений. Один вызов на сто строк лучше, чем сто вызовов по одной строке:

// Плохо: серверный вызов в цикле
Для Каждого Строка Из Объект.Товары Цикл
    Строка.Цена = ЦенаНоменклатуры(Строка.Номенклатура, Объект.Дата);
КонецЦикла;

// Хорошо: один вызов, результат — соответствие «номенклатура → цена»
Номенклатура = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
    Номенклатура.Добавить(Строка.Номенклатура);
КонецЦикла;

Цены = ЦеныНоменклатуры(Номенклатура, Объект.Дата);
Для Каждого Строка Из Объект.Товары Цикл
    Цена = Цены.Получить(Строка.Номенклатура);
    Строка.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;
&НаСервереБезКонтекста
Функция ЦеныНоменклатуры(Знач Номенклатура, Знач Дата)

    Результат = Новый Соответствие;

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

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

    Возврат Результат;

КонецФункции

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

Через границу свободно ходят простые типы (число, строка, дата, булево), ссылки на объекты базы, перечисления, а также коллекции из них: массив, структура, соответствие, список значений, таблица значений. Нельзя передать объекты, которых на клиенте просто нет: СправочникОбъект, ДокументОбъект, Запрос, результат запроса, менеджеры. Поэтому на клиент возвращают данные (структуру, таблицу значений), а не серверный объект; обратно же передают ссылку и получают объект на сервере через Ссылка.ПолучитьОбъект().

Чтобы передать с клиента табличную часть, её копируют в таблицу значений или массив структур на клиенте либо обращаются к ней на сервере через РеквизитФормыВЗначение("Объект") — во втором случае вызов должен быть методом с контекстом формы.

Общие модули: флаги Клиент, Сервер, Вызов сервера

Код, нужный нескольким формам, выносят в общие модули. Среди свойств модуля есть флаги исполнения. «Клиент (управляемое приложение)» и «Сервер» определяют, где модуль компилируется. Флаг «Вызов сервера» разрешает клиентскому коду вызывать экспортные методы серверного модуля напрямую: каждое такое обращение — это один серверный вызов. Без флага серверный модуль с клиента вызвать нельзя.

В конфигурациях принято соглашение об именах: суффикс ВызовСервера у модулей с флагом вызова с клиента (например, РаботаСЦенамиВызовСервера), суффикс Клиент у клиентских, КлиентСервер у модулей, доступных с обеих сторон.

// Общий модуль РаботаСЦенамиВызовСервера: флаги «Сервер» и «Вызов сервера»
Функция ЦенаНоменклатуры(Знач Номенклатура, Знач Дата) Экспорт
    // тело функции как выше
КонецФункции

// Модуль формы, клиент:
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    СтрокаТовара = Элементы.Товары.ТекущиеДанные;
    СтрокаТовара.Цена = РаботаСЦенамиВызовСервера.ЦенаНоменклатуры(
        СтрокаТовара.Номенклатура, Объект.Дата);
КонецПроцедуры

Методы без контекста для обеих сторон

Если функция чисто вычислительная и не требует ни формы, ни базы, её удобно объявить как &НаКлиентеНаСервереБезКонтекста. Она одинаково вызывается и из клиентских, и из серверных процедур и не порождает серверных вызовов.

&НаКлиентеНаСервереБезКонтекста
Функция СуммаСтроки(Знач Количество, Знач Цена, Знач Скидка)
    Возврат Окр(Количество * Цена * (1 - Скидка / 100), 2);
КонецФункции

Инструкции препроцессора

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

Функция КоличествоЗаказов(Контрагент) Экспорт
#Если Сервер Тогда
    Запрос = Новый Запрос(
    "ВЫБРАТЬ
    |    КОЛИЧЕСТВО(*) КАК Количество
    |ИЗ
    |    Документ.ЗаказКлиента КАК Заказы
    |ГДЕ
    |    Заказы.Контрагент = &Контрагент");
    Запрос.УстановитьПараметр("Контрагент", Контрагент);
    Выборка = Запрос.Выполнить().Выбрать();
    Выборка.Следующий();
    Возврат Выборка.Количество;
#Иначе
    ВызватьИсключение НСтр("ru = 'Функция доступна только на сервере.'");
#КонецЕсли
КонецФункции

Чаще всего встречаются условия Сервер, Клиент, ТонкийКлиент, ВебКлиент, ТолстыйКлиентУправляемоеПриложение и ВнешнееСоединение; их можно комбинировать через Или и Не. Блок завершается инструкцией #КонецЕсли. Препроцессор не заменяет директивы: он нужен, когда один и тот же метод компилируется в нескольких контекстах и отдельные его строки в каких-то из них недопустимы.

Типичные ошибки

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

Директивы: краткая шпаргалка

НужноВыбор
Реакция на действие пользователя, работа с элементами&НаКлиенте
Запись объекта, заполнение реквизитов формы данными из базы&НаСервере
Получить данные из базы по параметрам&НаСервереБезКонтекста
Чистая функция, нужная на обеих сторонах&НаКлиентеНаСервереБезКонтекста
Общая логика, использующая форму на обеих сторонах&НаКлиентеНаСервере
Вызвать сервер из общего клиентского кодаМодуль с флагом «Вызов сервера»

Актуальность для новых версий

Набор директив &НаКлиенте, &НаСервере, &НаСервереБезКонтекста, &НаКлиентеНаСервереБезКонтекста, &НаКлиентеНаСервере и принципы клиент-серверного взаимодействия одинаковы во всех версиях платформы 8.3, включая 8.3.2x. В режиме веб-клиента и тонкого клиента ограничения на доступные объекты совпадают, различия касаются главным образом работы с файлами и расширениями браузера. Тексты сообщений об ошибках могут отличаться в зависимости от релиза.

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

Чем &НаСервере отличается от &НаСервереБезКонтекста?

&НаСервере имеет доступ к форме и её реквизитам, но при вызове на сервер уходят данные всей формы. &НаСервереБезКонтекста получает только параметры, поэтому вызов быстрее, а обращаться к Объекту и реквизитам формы внутри нельзя.

Почему нельзя передать объект справочника или документа на клиент?

Объекты базы данных (СправочникОбъект, ДокументОбъект) существуют только на сервере. На клиент передают ссылку, структуру или таблицу значений, а объект получают на сервере через Ссылка.ПолучитьОбъект() или РеквизитФормыВЗначение("Объект").

Как вызвать серверную функцию общего модуля из клиентского кода?

У общего модуля включите флаги «Сервер» и «Вызов сервера» и объявите функцию с ключевым словом Экспорт. После этого её можно вызывать из клиентской процедуры формы, и каждый такой вызов будет одним обращением к серверу.

Что значит директива &НаКлиентеНаСервереБезКонтекста?

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

Элементы формы 1С: программное добавление на управляемую форму

Управляемые формы

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

Как открыть форму в управляемом приложении 1С и обработать результат после закрытия

Управляемые формы

Шесть типовых сценариев открытия управляемой формы в 1С: новый объект, создание на основании, существующий документ, форма выбора и получение результата после закрытия через ОписаниеОповещения.

Условное оформление элементов формы программным способом

Управляемые формы

Шесть примеров программного условного оформления в управляемой форме 1С: цвет поля по значению, сравнение двух реквизитов, оформление строк динамического списка и условие по текущей дате.

Как программно установить параметры выбора для формы выбора объекта

Управляемые формы

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