Почему в клиентском коде нет менеджера Перечисления и чем его заменить
Содержание
На клиенте управляемой формы к значению перечисления обращаются через ПредопределенноеЗначение("Перечисление.ИмяПеречисления.ИмяЗначения"). Запись Перечисления.ИмяПеречисления.ИмяЗначения в процедуре с директивой &НаКлиенте тонкого или веб-клиента не работает
: модуль падает с ошибкой Переменная не определена (Перечисления).
Ограничение касается только менеджера. Сама ссылка на значение перечисления свободно ходит между клиентом и сервером, поэтому сравнения, видимость полей и отборы решаются на клиенте без лишних серверных вызовов. Сервер нужен лишь там, где требуются метаданные: полный список значений, имя значения, его индекс.
Почему клиент не видит менеджер перечислений
В управляемом приложении код формы делится на две части. Клиентская выполняется на компьютере пользователя в тонком клиенте или в браузере. Серверная работает в рабочем процессе кластера или в файловом режиме внутри того же процесса, но всё равно в отдельном серверном контексте.
Свойство глобального контекста Перечисления — это менеджер, который знает метаданные конфигурации. Метаданные на клиенте тонкого и веб-клиента недоступны, поэтому и менеджера там нет. Это проектное решение платформы, а не ошибка конфигурации.
Что доступно на каждой стороне:
| Конструкция | Тонкий и веб-клиент | Сервер | Толстый клиент, обычное приложение |
|---|---|---|---|
Перечисления.Имя.Значение | нет | да | да |
ПредопределенноеЗначение("Перечисление.Имя.Значение") | да | да | да |
Метаданные.Перечисления | нет | да | да |
Тип("ПеречислениеСсылка.Имя") | да | да | да |
| Ссылка на значение в реквизите формы | да | да | да |
ЗНАЧЕНИЕ(Перечисление.Имя.Значение) | только в тексте запроса | в запросе | в запросе |
Отсюда главное правило. Сама ссылка на значение перечисления спокойно живёт на клиенте: её можно хранить в переменной, сравнивать, передавать на сервер и обратно. Нельзя только достать значение по имени через менеджер.
Где именно действует запрет, видно по директиве над процедурой:
- 🔴
&НаКлиентеи клиентские общие модули — менеджера нет; - 🔴
&НаКлиентеНаСервереБезКонтекста— тоже нет, потому что такой код должен компилироваться и на клиенте; - 🟢
&НаСервере,&НаСервереБезКонтекста, модули объектов и менеджеров — менеджерПеречислениядоступен.
Толстый клиент в режиме обычного приложения видит менеджер Перечисления напрямую. Поэтому старый код из конфигураций на 8.1–8.2 при переводе на управляемые формы ломается именно на этих строках.
Способы получить значение перечисления в клиентском коде
ПредопределенноеЗначение — основной путь
Функция глобального контекста принимает строку с полным именем значения. В ней используется единственное число вида объекта: Перечисление, а не Перечисления.
&НаКлиенте
Процедура ПроверитьСтатус(Команда)
СтатусЗакрыт = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Закрыт");
Если Объект.Статус = СтатусЗакрыт Тогда
ТолькоПросмотр = Истина;
КонецЕсли;
КонецПроцедуры
Имена СтатусыЗаказов и Закрыт — условные, подставьте свои из дерева метаданных. Важно, чтобы использовалось имя, а не синоним: «Закрыт» и «Заказ закрыт» — разные вещи.
Пустое значение получают так же:
ПустойСтатус = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.ПустаяСсылка");
Для проверки «заполнено или нет» удобнее ЗначениеЗаполнено(Объект.Статус) — функция работает и на клиенте.
Значение уже лежит в данных формы
Если реквизит объекта имеет тип перечисления, то Объект.ВидОперации на клиенте — это уже готовая ссылка. Её можно передать в другую процедуру, записать в реквизит формы, положить в структуру. Повторно получать его через ПредопределенноеЗначение не нужно.
Значение пришло с сервера
Серверная функция может вернуть ссылку на значение перечисления, массив ссылок или список значений. Ссылки перечислений передаются между клиентом и сервером без ограничений, в отличие, например, от объектов СправочникОбъект.
Проверка типа
Когда в реквизите составной тип, сначала проверьте, что там вообще перечисление:
Если ТипЗнч(Значение) = Тип("ПеречислениеСсылка.СтатусыЗаказов") Тогда
// работаем как с перечислением
КонецЕсли;
Обратите внимание на тип: ПеречислениеСсылка.Имя, без точки-значения в конце.
Перечисление в условиях видимости и доступности полей
Самая частая задача: показать или скрыть поля формы в зависимости от вида операции. Удобный приём — одна процедура управления формой, которую вызывают и при открытии на сервере, и при изменении реквизита на клиенте.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
УправлениеФормой(ЭтотОбъект);
КонецПроцедуры
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
УправлениеФормой(ЭтотОбъект);
КонецПроцедуры
&НаКлиентеНаСервереБезКонтекста
Процедура УправлениеФормой(Форма)
Объект = Форма.Объект;
Элементы = Форма.Элементы;
ЭтоВозврат = (Объект.ВидОперации =
ПредопределенноеЗначение("Перечисление.ВидыОпераций.ВозвратОтПокупателя"));
Элементы.ДокументОснование.Видимость = ЭтоВозврат;
Элементы.Договор.Доступность = ЗначениеЗаполнено(Объект.ВидОперации);
КонецПроцедуры
Почему именно &НаКлиентеНаСервереБезКонтекста:
- ✅ процедура вызывается с клиента без обращения к серверу — форма откликается мгновенно;
- ✅ та же процедура работает при открытии формы на сервере, логика не дублируется;
- ✅
ПредопределенноеЗначениедоступно в обоих контекстах, аПеречисления.здесь не сработал бы как раз из-за клиентской части.
Для существующих объектов есть нюанс. Если форма читает данные в обработчике ПриЧтенииНаСервере, вызов УправлениеФормой ставят туда или в ПриСозданииНаСервере после заполнения объекта. Иначе поле при открытии старого документа покажет неверное состояние до первого изменения реквизита.
Когда лучше условное оформление
В табличной части скрывать колонку целиком по значению в строке бессмысленно — у разных строк разные значения. Здесь работает условное оформление формы: условие «Объект.Товары.ВидСтроки равно значению перечисления» и оформление «Только просмотр» или цвет текста. Значение перечисления в условии выбирается мышкой в настройке, без кода.
Условное оформление выполняется платформой само и не требует обработчиков на клиенте. Для обычных полей вне таблиц код из примера выше обычно нагляднее и проще в отладке.
Отбор динамического списка по значению перечисления
Ещё один клиентский сценарий: флажок «Скрыть закрытые» над списком заказов. Отбор настраивается прямо в обработчике на клиенте, значение берётся через ПредопределенноеЗначение:
&НаКлиенте
Процедура СкрытьЗакрытыеПриИзменении(Элемент)
Список.Отбор.Элементы.Очистить();
Если СкрытьЗакрытые Тогда
ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Статус");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.НеРавно;
ЭлементОтбора.ПравоеЗначение = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Закрыт");
ЭлементОтбора.Использование = Истина;
КонецЕсли;
КонецПроцедуры
СкрытьЗакрытые — реквизит формы типа «Булево», Список — динамический список. Очистка в начале нужна, чтобы при каждом щелчке не копились одинаковые элементы отбора. Если у списка есть и другие программные отборы, удаляйте только свой элемент, а не очищайте всё.
Список всех значений перечисления: получаем через сервер
Перебрать значения перечисления на клиенте нельзя: для этого нужны метаданные. Решение — серверная функция без контекста формы.
&НаСервереБезКонтекста
Функция ЗначенияПеречисления(ИмяПеречисления)
Результат = Новый СписокЗначений;
МетаПеречисление = Метаданные.Перечисления[ИмяПеречисления];
Для Каждого МетаЗначение Из МетаПеречисление.ЗначенияПеречисления Цикл
Результат.Добавить(
Перечисления[ИмяПеречисления][МетаЗначение.Имя],
МетаЗначение.Синоним);
КонецЦикла;
Возврат Результат;
КонецФункции
Вызов с клиента:
&НаКлиенте
Процедура ЗаполнитьСписокСтатусов()
Статусы = ЗначенияПеречисления("СтатусыЗаказов");
Элементы.ОтборСтатус.СписокВыбора.ЗагрузитьЗначения(Статусы.ВыгрузитьЗначения());
КонецПроцедуры
Порядок значений совпадает с порядком в дереве метаданных. Если нужен другой, сортируйте список уже на клиенте или сразу на сервере.
Ограничить выбор в поле ввода
Часто нужен не весь список, а его часть: например, в поле «Статус» разрешить только три значения из пяти. Проще всего заполнить СписокВыбора поля на сервере при создании формы:
&НаСервере
Процедура ЗаполнитьДопустимыеСтатусы()
Список = Элементы.Статус.СписокВыбора;
Список.Очистить();
Список.Добавить(Перечисления.СтатусыЗаказов.Новый);
Список.Добавить(Перечисления.СтатусыЗаказов.ВРаботе);
Список.Добавить(Перечисления.СтатусыЗаказов.Закрыт);
КонецПроцедуры
Здесь уже серверный контекст, поэтому менеджер Перечисления доступен напрямую. Поле начнёт показывать только эти значения.
Кеширование через общий модуль
Если список нужен во многих формах, вынесите функцию в общий серверный модуль с флагом «Вызов сервера» и свойством «Повторное использование возвращаемых значений» = «На время сеанса». Тогда клиент получит ответ сервера один раз за сеанс, дальше — из кеша.
Кешированный результат лучше возвращать как ФиксированныйМассив или копировать перед изменением. Если поменять возвращённый список на месте, следующая форма может получить уже изменённые данные.
Имя, синоним и порядковый номер значения
Иногда нужно не само значение, а его текстовое имя: для обмена, логов, ключей настроек.
| Что нужно | Где доступно | Как получить |
|---|---|---|
| Синоним (представление) | клиент и сервер | Строка(Значение) |
| Имя значения | сервер | через метаданные, пример ниже |
| Порядковый номер | сервер | Перечисления.Имя.Индекс(Значение) |
| Значение по имени | клиент и сервер | ПредопределенноеЗначение("Перечисление." + ИмяПеречисления + "." + ИмяЗначения) |
Имя значения по ссылке:
&НаСервереБезКонтекста
Функция ИмяЗначенияПеречисления(Значение)
Если Не ЗначениеЗаполнено(Значение) Тогда
Возврат "";
КонецЕсли;
МетаПеречисление = Значение.Метаданные();
Индекс = Перечисления[МетаПеречисление.Имя].Индекс(Значение);
Возврат МетаПеречисление.ЗначенияПеречисления[Индекс].Имя;
КонецФункции
Проверка на пустое значение здесь нужна: у пустой ссылки нет строки в коллекции ЗначенияПеречисления.
Синоним видит пользователь, и его легко поменяют при доработке. Для сравнения в коде и для ключей в данных используйте имя или саму ссылку, но не Строка(Значение).
Ошибки при обращении к перечислению на клиенте и как их найти
Отдельно о строковых именах. Ошибка в строке ПредопределенноеЗначение не ловится синтаксическим контролем модуля: строка — просто текст. Код открывается без замечаний, а падает только когда пользователь дойдёт до этой ветки. Поэтому после переименования значений перечисления ищите старое имя глобальным поиском по конфигурации, включая строки.
Как писать код с перечислениями без сюрпризов
Несколько привычек, которые снимают большую часть проблем.
- Получайте значение один раз. Перед циклом сохраните
ПредопределенноеЗначениев переменную и сравнивайте с ней. Код короче, а опечатка, если она есть, находится в одном месте. - Держите логику видимости в одной процедуре. Вариант с
&НаКлиентеНаСервереБезКонтекстаиз примера выше избавляет от рассинхрона между открытием формы и изменением реквизита. - Не тяните сервер ради того, что есть на клиенте. Ссылка в реквизите формы и
ПредопределенноеЗначениеуже на клиенте. Серверный вызов нужен для списка значений, имени и индекса. - Сравнивайте ссылки, а не представления. Ни
Строка(Значение) = "Закрыт", ни сравнение синонимов не переживут переименования. - В запросах используйте
ЗНАЧЕНИЕ()или параметр. Внутри текста запроса пишетсяЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Закрыт)— так же в единственном числе. Альтернатива — передать ссылку черезЗапрос.УстановитьПараметр. - Не правьте типовые перечисления без нужды. Добавление значения в типовое перечисление — изменение конфигурации поставщика, которое потом придётся сопровождать при каждом обновлении. Если нужна своя логика, расширение конфигурации обычно обходится дешевле.
Пример «до» и «после»:
// Плохо: менеджер на клиенте, сравнение строкой
&НаКлиенте
Процедура ПроверитьСтроки()
Для Каждого Строка Из Объект.Товары Цикл
Если Строка.ВидСтроки = Перечисления.ВидыСтрок.Услуга Тогда // ошибка
Строка.Количество = 1;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Хорошо: значение получено один раз
&НаКлиенте
Процедура ПроверитьСтроки()
ВидУслуга = ПредопределенноеЗначение("Перечисление.ВидыСтрок.Услуга");
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Если СтрокаТЧ.ВидСтроки = ВидУслуга Тогда
СтрокаТЧ.Количество = 1;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Во втором варианте заодно переименована переменная цикла: Строка совпадает с именем функции глобального контекста, и такие совпадения путают при чтении кода.