Перечисления в клиентском коде 1С 8.3: что работает в тонком клиенте и почему
Содержание
Чтобы получить значение перечисления на клиенте, используйте функцию ПредопределенноеЗначение("Перечисление.ИмяПеречисления.ИмяЗначения"). Она доступна в процедурах с директивой &НаКлиенте и возвращает ту же ссылку, что Перечисления.ИмяПеречисления.ИмяЗначения на сервере.
Это главный способ, но не единственный. Если значение лежит в реквизите другого объекта, нужен серверный вызов. Если нужно имя значения, а не ссылка, тоже придётся идти на сервер. Если константы перечисления нужны в десятке мест формы, выгоднее один раз получить их через модуль с повторным использованием. Ниже каждый случай с кодом. Всё сказанное относится к платформе 8.3 и управляемым формам, от конфигурации не зависит.
Почему обращение к перечислению на клиенте даёт ошибку
Код вида Перечисления.ВидыОперацийПоступления.ПокупкаКомиссия в модуле формы с директивой &НаКлиенте не компилируется в тонком и веб-клиенте. Синтаксический контроль выдаёт:
Переменная не определена (Перечисления)
Причина в контекстах выполнения. Свойство глобального контекста Перечисления — это коллекция менеджеров. Менеджеры объектов живут на сервере (а ещё в толстом клиенте и во внешнем соединении). Тонкий клиент работает
с базой только через сервер, поэтому менеджеров у него нет. По той же причине на клиенте недоступны Справочники, Документы, Метаданные, ПараметрыСеанса.
При этом сама ссылка на значение перечисления на клиенте прекрасно существует. Она передаётся в форму, хранится в реквизитах формы, сравнивается и выводится в поле. Недоступен только способ получить её «по имени» через менеджер.
| Контекст | Перечисления.X.Y | ПредопределенноеЗначение(...) |
|---|---|---|
&НаСервере, &НаСервереБезКонтекста | ✅ | ✅ |
&НаКлиенте в тонком и веб-клиенте | ❌ ошибка компиляции | ✅ |
| Толстый клиент, обычное приложение | ✅ | ✅ |
| Общий модуль «Клиент» | ❌ | ✅ |
| Общий модуль «Клиент» + «Сервер» | ❌ при компиляции на клиенте | ✅ |
В толстом клиенте обычного приложения ошибка не появится. Она всплывает, когда ту же обработку открывают в управляемом приложении или переносят код в модуль управляемой формы. Если код должен работать везде, сразу пишите через ПредопределенноеЗначение.
ПредопределенноеЗначение: основной способ получить значение перечисления на клиенте
Функция глобального контекста ПредопределенноеЗначение() принимает строку с полным именем и возвращает ссылку. Для перечислений формат такой:
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступления.ПокупкаКомиссия") Тогда
Элементы.ГруппаКомиссионер.Видимость = Истина;
Иначе
Элементы.ГруппаКомиссионер.Видимость = Ложь;
КонецЕсли;
КонецПроцедуры
Правила записи строки:
- 🔹 первое слово — тип в единственном числе:
Перечисление, а неПеречисления; - 🔹 второе — имя перечисления из дерева метаданных, не синоним;
- 🔹 третье — имя значения, тоже не синоним;
- 🔹 пустое значение получают так:
ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступления.ПустаяСсылка").
Если в строке опечатка, ошибка возникнет не при сохранении модуля, а во время выполнения, когда дойдёт до этой строки. Синтаксический контроль содержимое строки не проверяет. Поэтому после переименования значения в конфигураторе ищите старое имя глобальным поиском по тексту (Ctrl+Shift+F), а не надейтесь на проверку модулей.
Если имя значения вычисляется динамически или значение могли удалить из метаданных, оберните вызов в Попытка ... Исключение. В конфигурациях на БСП для этого есть ОбщегоНазначенияКлиентСервер.ПредопределенныйЭлемент(): при отсутствии элемента он возвращает Неопределено вместо исключения.
Та же функция работает и на сервере, поэтому в клиент-серверных общих модулях пишите только через неё. Тогда код одинаково компилируется в обоих контекстах.
Значение перечисления из реквизита ссылки: серверный вызов
Частая ситуация: на клиенте есть ссылка на документ или справочник, а нужно значение перечисления из его реквизита. Например, ВыбранныйДоговор.ВидДоговора. На клиенте обращение через точку к реквизитам ссылки не работает
: ссылка там не «знает» своих данных, за ними надо идти в базу.
Решение — функция без контекста формы:
&НаСервереБезКонтекста
Функция ВидДоговора(Договор)
Возврат Договор.ВидДоговора;
КонецФункции
&НаКлиенте
Процедура ДоговорПриИзменении(Элемент)
Вид = ВидДоговора(Объект.Договор);
Если Вид = ПредопределенноеЗначение("Перечисление.ВидыДоговоров.СПоставщиком") Тогда
// логика для поставщика
КонецЕсли;
КонецПроцедуры
Почему &НаСервереБезКонтекста, а не &НаСервере: при вызове без контекста на сервер не передаются данные формы. Для формы документа с табличной частью на сотни строк разница в трафике заметна.
В конфигурациях на БСП вместо своей функции удобно вызвать серверную ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Договор, "ВидДоговора"). Сама она серверная, поэтому с клиента её вызывают из своей функции &НаСервереБезКонтекста или из общего модуля с флажком «Вызов сервера».
Если нужно сразу несколько реквизитов, возвращайте их структурой одним вызовом. Каждый серверный вызов — это отдельный обмен с сервером, три вызова подряд в обработчике ПриИзменении пользователь почувствует.
Как передать значения перечисления на клиент через форму, общие модули и параметры сеанса
Иногда значения нужны многократно: в условных обработчиках, при заполнении списков выбора, в проверках перед записью. Вызывать сервер каждый раз не стоит. Есть три рабочих приёма.
Реквизиты и параметры формы
В ПриСозданииНаСервере положите нужное значение в реквизит формы типа ПеречислениеСсылка.ИмяПеречисления. Реквизиты формы доступны на клиенте без вызовов сервера.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ОсновнойВидОплаты = Константы.ОсновнойВидОплаты.Получить();
КонецПроцедуры
Значение перечисления можно передать и при открытии формы — в структуре параметров ОткрытьФорму(). Ссылка на перечисление сериализуется без проблем.
Общий модуль с повторным использованием
Создайте общий модуль с флажками «Сервер» и «Вызов сервера», в свойстве «Повторное использование возвращаемых значений» выберите «На время сеанса». Результат вызова кэшируется, повторный вызов с теми же параметрами не идёт на сервер.
// Общий модуль ПеречисленияПовтИсп (Сервер, Вызов сервера, на время сеанса)
Функция НастройкиУчета() Экспорт
Результат = Новый Структура;
Результат.Вставить("СистемаНалогообложения", Константы.СистемаНалогообложения.Получить());
Результат.Вставить("ВидУчета", Константы.ВидУчетаЗапасов.Получить());
Возврат Новый ФиксированнаяСтруктура(Результат);
КонецФункции
На клиенте: Настройки = ПеречисленияПовтИсп.НастройкиУчета(); — дальше Настройки.СистемаНалогообложения сравнивайте с ПредопределенноеЗначение. Имена констант здесь условные: подставьте свои.
Возвращайте фиксированные коллекции. Кэш отдаёт один и тот же экземпляр, и если вызывающий код изменит обычную структуру, остальные получат испорченные данные.
Параметры сеанса и ПараметрыПриложения
Параметр сеанса может иметь тип ссылки на перечисление, например текущая роль пользователя в процессе. Но ПараметрыСеанса на клиенте недоступны так же, как Перечисления. Читайте их через серверную функцию, а результат кэшируйте одним из способов выше.
Для клиентского кэша на весь сеанс есть свойство глобального контекста ПараметрыПриложения — это соответствие, живущее на клиенте. Записали значение один раз, дальше берёте без обращения к серверу. Ключи делайте с префиксом подсистемы, чтобы не пересечься с чужими.
| Приём | Где хранится | Когда выбирать |
|---|---|---|
| Реквизит формы | В данных формы | Значение нужно одной форме |
Параметр ОткрытьФорму | В параметрах формы | Значение известно вызывающей форме |
| Модуль с повторным использованием | Кэш платформы | Значение нужно многим формам, меняется редко |
ПараметрыПриложения | Клиент, на сеанс | Клиентский кэш своих данных |
Имя, синоним и порядок значения перечисления на клиенте
Ссылка — это не строка. На клиенте у неё доступно немногое:
- 📌
Строка(Значение)— представление, то есть синоним значения («Покупка, комиссия»). Работает на клиенте без серверного вызова; - 📌
ЗначениеЗаполнено(Значение)— проверка на пустую ссылку; - 📌
ТипЗнч(Значение) = Тип("ПеречислениеСсылка.ВидыОперацийПоступления")— проверка типа.
Имя значения из метаданных (ПокупкаКомиссия) и его порядковый номер на клиенте не получить: для этого нужны Метаданные и менеджер перечисления. Пишем серверную функцию:
&НаСервереБезКонтекста
Функция ИмяЗначенияПеречисления(Значение)
Если Не ЗначениеЗаполнено(Значение) Тогда
Возврат "";
КонецЕсли;
МетаданныеПеречисления = Значение.Метаданные();
Индекс = Перечисления[МетаданныеПеречисления.Имя].Индекс(Значение);
Возврат МетаданныеПеречисления.ЗначенияПеречисления[Индекс].Имя;
КонецФункции
Метод менеджера Индекс() возвращает номер значения в порядке, заданном в конфигураторе, начиная с нуля. По нему берём объект метаданных значения и его имя. На сервере короче будет XMLСтрока(Значение): для ссылки на перечисление она возвращает имя значения.
Не сравнивайте значения через синоним: Если Строка(Объект.ВидОперации) = "Покупка, комиссия". Синоним правят без пересборки кода, а в многоязычной конфигурации он зависит от языка пользователя. Условие молча перестанет срабатывать.
Сравнение значений перечислений в клиентском коде
Ссылки на перечисления сравнивают операторами = и <>. Это быстрая операция без обращения к серверу.
Одно значение
Если Объект.СтатусЗаказа = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Закрыт") Тогда
ТолькоПросмотр = Истина;
КонецЕсли;
Несколько значений
Длинные цепочки ИЛИ читать тяжело. Соберите допустимые значения в массив и ищите в нём:
&НаКлиенте
Функция СтатусРазрешаетИзменение(Статус)
Разрешенные = Новый Массив;
Разрешенные.Добавить(ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Новый"));
Разрешенные.Добавить(ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.НаСогласовании"));
Возврат Разрешенные.Найти(Статус) <> Неопределено;
КонецФункции
Метод массива Найти() возвращает индекс найденного элемента или Неопределено.
Список выбора и отбор
Значения перечисления можно добавить в список выбора поля прямо на клиенте:
Элементы.ВидОплаты.СписокВыбора.Очистить();
Элементы.ВидОплаты.СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.ВидыОплат.Наличные"));
Элементы.ВидОплаты.СписокВыбора.Добавить(ПредопределенноеЗначение("Перечисление.ВидыОплат.Безналичные"));
Для отбора динамического списка по значению перечисления в БСП есть ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(). Правое значение отбора передайте через ПредопределенноеЗначение.
В тексте запроса для того же самого используют ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Закрыт). Это синтаксис языка запросов, в коде модуля он не работает. И наоборот, ПредопределенноеЗначение в тексте запроса не пишут.
Лучшие практики работы с перечислениями на клиенте
Несколько правил, которые экономят отладку и вызовы сервера.
- Вычисляйте один раз. Если значение нужно внутри цикла по табличной части, получите его в переменную до цикла. Строка с
ПредопределенноеЗначениевнутри цикла на 500 строк — лишняя работа на каждой итерации. - Не тащите контекст формы без нужды. Для получения одного реквизита —
&НаСервереБезКонтекста.&НаСервереоставьте для случаев, когда сервер действительно меняет данные формы. - Разделяйте серверную и клиентскую логику по модулям. Клиентский общий модуль не должен знать про менеджеры. Клиент-серверный модуль пишите только через
ПредопределенноеЗначение. - Не храните имя значения строкой в реквизитах. Для настроек используйте реквизит или константу типа перечисления. Строка не проверяется ни компилятором, ни ссылочной целостностью.
- Проверяйте на пустую ссылку явно.
ЗначениеЗаполнено()перед сравнением избавит от ветки, в которой «ни одно условие не выполнилось», а программа делает вид, что так и надо. - Для доработок типовых конфигураций держите свою логику в расширении и не меняйте имена значений типовых перечислений. Строки в
ПредопределенноеЗначениепо всей конфигурации ссылаются именно на эти имена.
| Задача | Где решать | Инструмент |
|---|---|---|
| Сравнить реквизит формы с константой перечисления | Клиент | ПредопределенноеЗначение |
| Прочитать перечисление из реквизита ссылки | Сервер | &НаСервереБезКонтекста |
| Получить имя значения | Сервер | Индекс() + Метаданные() или XMLСтрока |
| Показать пользователю название | Клиент | Строка(Значение) |
| Использовать значение в запросе | Сервер | ЗНАЧЕНИЕ(Перечисление.X.Y) |
Частые вопросы
Можно ли получить на клиенте все значения перечисления списком?
Метаданные.Перечисления.Имя.ЗначенияПеречисления с получением ссылок через менеджер, и верните его на клиент.Работает ли ПредопределенноеЗначение для справочников и планов счетов?
ПредопределенноеЗначение("Справочник.Валюты.Рубль") или ПредопределенноеЗначение("ПланСчетов.Хозрасчетный.Касса"), если такие элементы есть в конфигурации.