Перечисления в клиентском коде 1С 8.3: что работает в тонком клиенте и почему

· 9 мин

Содержание

Чтобы получить значение перечисления на клиенте, используйте функцию ПредопределенноеЗначение("Перечисление.ИмяПеречисления.ИмяЗначения"). Она доступна в процедурах с директивой &НаКлиенте и возвращает ту же ссылку, что Перечисления.ИмяПеречисления.ИмяЗначения на сервере.

Это главный способ, но не единственный. Если значение лежит в реквизите другого объекта, нужен серверный вызов. Если нужно имя значения, а не ссылка, тоже придётся идти на сервер. Если константы перечисления нужны в десятке мест формы, выгоднее один раз получить их через модуль с повторным использованием. Ниже каждый случай с кодом. Всё сказанное относится к платформе 8.3 и управляемым формам, от конфигурации не зависит.

Почему обращение к перечислению на клиенте даёт ошибку

Код вида Перечисления.ВидыОперацийПоступления.ПокупкаКомиссия в модуле формы с директивой &НаКлиенте не компилируется в тонком и веб-клиенте. Синтаксический контроль выдаёт:

Переменная не определена (Перечисления)

Причина в контекстах выполнения. Свойство глобального контекста Перечисления — это коллекция менеджеров. Менеджеры объектов живут на сервере (а ещё в толстом клиенте и во внешнем соединении). Тонкий клиент работает с базой только через сервер, поэтому менеджеров у него нет. По той же причине на клиенте недоступны Справочники, Документы, Метаданные, ПараметрыСеанса.

При этом сама ссылка на значение перечисления на клиенте прекрасно существует. Она передаётся в форму, хранится в реквизитах формы, сравнивается и выводится в поле. Недоступен только способ получить её «по имени» через менеджер.

КонтекстПеречисления.X.YПредопределенноеЗначение(...)
&НаСервере, &НаСервереБезКонтекста✅✅
&НаКлиенте в тонком и веб-клиенте❌ ошибка компиляции✅
Толстый клиент, обычное приложение✅✅
Общий модуль «Клиент»❌✅
Общий модуль «Клиент» + «Сервер»❌ при компиляции на клиенте✅

В толстом клиенте обычного приложения ошибка не появится. Она всплывает, когда ту же обработку открывают в управляемом приложении или переносят код в модуль управляемой формы. Если код должен работать везде, сразу пишите через ПредопределенноеЗначение.

ПредопределенноеЗначение: основной способ получить значение перечисления на клиенте

Функция глобального контекста ПредопределенноеЗначение() принимает строку с полным именем и возвращает ссылку. Для перечислений формат такой:

&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)

    Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступления.ПокупкаКомиссия") Тогда
        Элементы.ГруппаКомиссионер.Видимость = Истина;
    Иначе
        Элементы.ГруппаКомиссионер.Видимость = Ложь;
    КонецЕсли;

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

Правила записи строки:

  • 🔹 первое слово — тип в единственном числе: Перечисление, а не Перечисления;
  • 🔹 второе — имя перечисления из дерева метаданных, не синоним;
  • 🔹 третье — имя значения, тоже не синоним;
  • 🔹 пустое значение получают так: ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступления.ПустаяСсылка").

Если в строке опечатка, ошибка возникнет не при сохранении модуля, а во время выполнения, когда дойдёт до этой строки. Синтаксический контроль содержимое строки не проверяет. Поэтому после переименования значения в конфигураторе ищите старое имя глобальным поиском по тексту (Ctrl+Shift+F), а не надейтесь на проверку модулей.

Если имя значения вычисляется динамически или значение могли удалить из метаданных, оберните вызов в Попытка ... Исключение. В конфигурациях на БСП для этого есть ОбщегоНазначенияКлиентСервер.ПредопределенныйЭлемент(): при отсутствии элемента он возвращает Неопределено вместо исключения.

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

Значение перечисления из реквизита ссылки: серверный вызов

Частая ситуация: на клиенте есть ссылка на документ или справочник, а нужно значение перечисления из его реквизита. Например, ВыбранныйДоговор.ВидДоговора. На клиенте обращение через точку к реквизитам ссылки не работает : ссылка там не «знает» своих данных, за ними надо идти в базу.

Решение — функция без контекста формы:

&НаСервереБезКонтекста
Функция ВидДоговора(Договор)
    Возврат Договор.ВидДоговора;
КонецФункции

&НаКлиенте
Процедура ДоговорПриИзменении(Элемент)

    Вид = ВидДоговора(Объект.Договор);
    Если Вид = ПредопределенноеЗначение("Перечисление.ВидыДоговоров.СПоставщиком") Тогда
        // логика для поставщика
    КонецЕсли;

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

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

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

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

Как передать значения перечисления на клиент через форму, общие модули и параметры сеанса

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

Реквизиты и параметры формы

В ПриСозданииНаСервере положите нужное значение в реквизит формы типа ПеречислениеСсылка.ИмяПеречисления. Реквизиты формы доступны на клиенте без вызовов сервера.

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    ОсновнойВидОплаты = Константы.ОсновнойВидОплаты.Получить();
КонецПроцедуры

Значение перечисления можно передать и при открытии формы — в структуре параметров ОткрытьФорму(). Ссылка на перечисление сериализуется без проблем.

Общий модуль с повторным использованием

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

// Общий модуль ПеречисленияПовтИсп (Сервер, Вызов сервера, на время сеанса)
Функция НастройкиУчета() Экспорт

    Результат = Новый Структура;
    Результат.Вставить("СистемаНалогообложения", Константы.СистемаНалогообложения.Получить());
    Результат.Вставить("ВидУчета", Константы.ВидУчетаЗапасов.Получить());
    Возврат Новый ФиксированнаяСтруктура(Результат);

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

На клиенте: Настройки = ПеречисленияПовтИсп.НастройкиУчета(); — дальше Настройки.СистемаНалогообложения сравнивайте с ПредопределенноеЗначение. Имена констант здесь условные: подставьте свои.

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

Параметры сеанса и ПараметрыПриложения

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

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

ПриёмГде хранитсяКогда выбирать
Реквизит формыВ данных формыЗначение нужно одной форме
Параметр ОткрытьФормуВ параметрах формыЗначение известно вызывающей форме
Модуль с повторным использованиемКэш платформыЗначение нужно многим формам, меняется редко
ПараметрыПриложенияКлиент, на сеансКлиентский кэш своих данных

Имя, синоним и порядок значения перечисления на клиенте

Ссылка — это не строка. На клиенте у неё доступно немногое:

  • 📌 Строка(Значение) — представление, то есть синоним значения («Покупка, комиссия»). Работает на клиенте без серверного вызова;
  • 📌 ЗначениеЗаполнено(Значение) — проверка на пустую ссылку;
  • 📌 ТипЗнч(Значение) = Тип("ПеречислениеСсылка.ВидыОперацийПоступления") — проверка типа.

Имя значения из метаданных (ПокупкаКомиссия) и его порядковый номер на клиенте не получить: для этого нужны Метаданные и менеджер перечисления. Пишем серверную функцию:

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

    Если Не ЗначениеЗаполнено(Значение) Тогда
        Возврат "";
    КонецЕсли;

    МетаданныеПеречисления = Значение.Метаданные();
    Индекс = Перечисления[МетаданныеПеречисления.Имя].Индекс(Значение);
    Возврат МетаданныеПеречисления.ЗначенияПеречисления[Индекс].Имя;

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

Метод менеджера Индекс() возвращает номер значения в порядке, заданном в конфигураторе, начиная с нуля. По нему берём объект метаданных значения и его имя. На сервере короче будет XMLСтрока(Значение): для ссылки на перечисление она возвращает имя значения.

Не сравнивайте значения через синоним: Если Строка(Объект.ВидОперации) = "Покупка, комиссия". Синоним правят без пересборки кода, а в многоязычной конфигурации он зависит от языка пользователя. Условие молча перестанет срабатывать.

Сравнение значений перечислений в клиентском коде

Ссылки на перечисления сравнивают операторами = и <>. Это быстрая операция без обращения к серверу.

Одно значение

Если Объект.СтатусЗаказа = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Закрыт") Тогда
    ТолькоПросмотр = Истина;
КонецЕсли;

Несколько значений

Длинные цепочки ИЛИ читать тяжело. Соберите допустимые значения в массив и ищите в нём:

&НаКлиенте
Функция СтатусРазрешаетИзменение(Статус)

    Разрешенные = Новый Массив;
    Разрешенные.Добавить(ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Новый"));
    Разрешенные.Добавить(ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.НаСогласовании"));

    Возврат Разрешенные.Найти(Статус) <> Неопределено;

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

Метод массива Найти() возвращает индекс найденного элемента или Неопределено.

Список выбора и отбор

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

Элементы.ВидОплаты.СписокВыбора.Очистить();
Элементы.ВидОплаты.СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.ВидыОплат.Наличные"));
Элементы.ВидОплаты.СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.ВидыОплат.Безналичные"));

Для отбора динамического списка по значению перечисления в БСП есть ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(). Правое значение отбора передайте через ПредопределенноеЗначение.

В тексте запроса для того же самого используют ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Закрыт). Это синтаксис языка запросов, в коде модуля он не работает. И наоборот, ПредопределенноеЗначение в тексте запроса не пишут.

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

Лучшие практики работы с перечислениями на клиенте

Несколько правил, которые экономят отладку и вызовы сервера.

  1. Вычисляйте один раз. Если значение нужно внутри цикла по табличной части, получите его в переменную до цикла. Строка с ПредопределенноеЗначение внутри цикла на 500 строк — лишняя работа на каждой итерации.
  2. Не тащите контекст формы без нужды. Для получения одного реквизита — &НаСервереБезКонтекста. &НаСервере оставьте для случаев, когда сервер действительно меняет данные формы.
  3. Разделяйте серверную и клиентскую логику по модулям. Клиентский общий модуль не должен знать про менеджеры. Клиент-серверный модуль пишите только через ПредопределенноеЗначение.
  4. Не храните имя значения строкой в реквизитах. Для настроек используйте реквизит или константу типа перечисления. Строка не проверяется ни компилятором, ни ссылочной целостностью.
  5. Проверяйте на пустую ссылку явно. ЗначениеЗаполнено() перед сравнением избавит от ветки, в которой «ни одно условие не выполнилось», а программа делает вид, что так и надо.
  6. Для доработок типовых конфигураций держите свою логику в расширении и не меняйте имена значений типовых перечислений. Строки в ПредопределенноеЗначение по всей конфигурации ссылаются именно на эти имена.
ЗадачаГде решатьИнструмент
Сравнить реквизит формы с константой перечисленияКлиентПредопределенноеЗначение
Прочитать перечисление из реквизита ссылкиСервер&НаСервереБезКонтекста
Получить имя значенияСерверИндекс() + Метаданные() или XMLСтрока
Показать пользователю названиеКлиентСтрока(Значение)
Использовать значение в запросеСерверЗНАЧЕНИЕ(Перечисление.X.Y)

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

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