Почему в клиентском коде нет менеджера Перечисления и чем его заменить

· 9 мин

Содержание

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

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

Почему клиент не видит менеджер перечислений

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

Свойство глобального контекста Перечисления — это менеджер, который знает метаданные конфигурации. Метаданные на клиенте тонкого и веб-клиента недоступны, поэтому и менеджера там нет. Это проектное решение платформы, а не ошибка конфигурации.

Что доступно на каждой стороне:

КонструкцияТонкий и веб-клиентСерверТолстый клиент, обычное приложение
Перечисления.Имя.Значениенетдада
ПредопределенноеЗначение("Перечисление.Имя.Значение")дадада
Метаданные.Перечислениянетдада
Тип("ПеречислениеСсылка.Имя")дадада
Ссылка на значение в реквизите формыдадада
ЗНАЧЕНИЕ(Перечисление.Имя.Значение)только в тексте запросав запросев запросе

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

Где именно действует запрет, видно по директиве над процедурой:

  • 🔴 &НаКлиенте и клиентские общие модули — менеджера нет;
  • 🔴 &НаКлиентеНаСервереБезКонтекста — тоже нет, потому что такой код должен компилироваться и на клиенте;
  • 🟢 &НаСервере, &НаСервереБезКонтекста, модули объектов и менеджеров — менеджер Перечисления доступен.

Толстый клиент в режиме обычного приложения видит менеджер Перечисления напрямую. Поэтому старый код из конфигураций на 8.1–8.2 при переводе на управляемые формы ломается именно на этих строках.

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

ПредопределенноеЗначение — основной путь

Функция глобального контекста принимает строку с полным именем значения. В ней используется единственное число вида объекта: Перечисление, а не Перечисления.

&НаКлиенте
Процедура ПроверитьСтатус(Команда)

	СтатусЗакрыт = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Закрыт");

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

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

Имена СтатусыЗаказов и Закрыт — условные, подставьте свои из дерева метаданных. Важно, чтобы использовалось имя, а не синоним: «Закрыт» и «Заказ закрыт» — разные вещи.

Пустое значение получают так же:

ПустойСтатус = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.ПустаяСсылка");

Для проверки «заполнено или нет» удобнее ЗначениеЗаполнено(Объект.Статус) — функция работает и на клиенте.

Значение уже лежит в данных формы

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

Значение пришло с сервера

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

Проверка типа

Когда в реквизите составной тип, сначала проверьте, что там вообще перечисление:

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

Обратите внимание на тип: ПеречислениеСсылка.Имя, без точки-значения в конце.

Перечисление в условиях видимости и доступности полей

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

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	УправлениеФормой(ЭтотОбъект);
КонецПроцедуры

&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
	УправлениеФормой(ЭтотОбъект);
КонецПроцедуры

&НаКлиентеНаСервереБезКонтекста
Процедура УправлениеФормой(Форма)

	Объект = Форма.Объект;
	Элементы = Форма.Элементы;

	ЭтоВозврат = (Объект.ВидОперации =
		ПредопределенноеЗначение("Перечисление.ВидыОпераций.ВозвратОтПокупателя"));

	Элементы.ДокументОснование.Видимость = ЭтоВозврат;
	Элементы.Договор.Доступность = ЗначениеЗаполнено(Объект.ВидОперации);

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

Почему именно &НаКлиентеНаСервереБезКонтекста:

  • ✅ процедура вызывается с клиента без обращения к серверу — форма откликается мгновенно;
  • ✅ та же процедура работает при открытии формы на сервере, логика не дублируется;
  • ✅ ПредопределенноеЗначение доступно в обоих контекстах, а Перечисления. здесь не сработал бы как раз из-за клиентской части.

Для существующих объектов есть нюанс. Если форма читает данные в обработчике ПриЧтенииНаСервере, вызов УправлениеФормой ставят туда или в ПриСозданииНаСервере после заполнения объекта. Иначе поле при открытии старого документа покажет неверное состояние до первого изменения реквизита.

Когда лучше условное оформление

В табличной части скрывать колонку целиком по значению в строке бессмысленно — у разных строк разные значения. Здесь работает условное оформление формы: условие «Объект.Товары.ВидСтроки равно значению перечисления» и оформление «Только просмотр» или цвет текста. Значение перечисления в условии выбирается мышкой в настройке, без кода.

Условное оформление выполняется платформой само и не требует обработчиков на клиенте. Для обычных полей вне таблиц код из примера выше обычно нагляднее и проще в отладке.

Отбор динамического списка по значению перечисления

Ещё один клиентский сценарий: флажок «Скрыть закрытые» над списком заказов. Отбор настраивается прямо в обработчике на клиенте, значение берётся через ПредопределенноеЗначение:

&НаКлиенте
Процедура СкрытьЗакрытыеПриИзменении(Элемент)

	Список.Отбор.Элементы.Очистить();

	Если СкрытьЗакрытые Тогда
		ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Статус");
		ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.НеРавно;
		ЭлементОтбора.ПравоеЗначение = ПредопределенноеЗначение("Перечисление.СтатусыЗаказов.Закрыт");
		ЭлементОтбора.Использование = Истина;
	КонецЕсли;

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

СкрытьЗакрытые — реквизит формы типа «Булево», Список — динамический список. Очистка в начале нужна, чтобы при каждом щелчке не копились одинаковые элементы отбора. Если у списка есть и другие программные отборы, удаляйте только свой элемент, а не очищайте всё.

Список всех значений перечисления: получаем через сервер

Перебрать значения перечисления на клиенте нельзя: для этого нужны метаданные. Решение — серверная функция без контекста формы.

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

	Результат = Новый СписокЗначений;
	МетаПеречисление = Метаданные.Перечисления[ИмяПеречисления];

	Для Каждого МетаЗначение Из МетаПеречисление.ЗначенияПеречисления Цикл
		Результат.Добавить(
			Перечисления[ИмяПеречисления][МетаЗначение.Имя],
			МетаЗначение.Синоним);
	КонецЦикла;

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

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

Вызов с клиента:

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

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

Ограничить выбор в поле ввода

Часто нужен не весь список, а его часть: например, в поле «Статус» разрешить только три значения из пяти. Проще всего заполнить СписокВыбора поля на сервере при создании формы:

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

Здесь уже серверный контекст, поэтому менеджер Перечисления доступен напрямую. Поле начнёт показывать только эти значения.

Кеширование через общий модуль

Если список нужен во многих формах, вынесите функцию в общий серверный модуль с флагом «Вызов сервера» и свойством «Повторное использование возвращаемых значений» = «На время сеанса». Тогда клиент получит ответ сервера один раз за сеанс, дальше — из кеша.

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

Имя, синоним и порядковый номер значения

Иногда нужно не само значение, а его текстовое имя: для обмена, логов, ключей настроек.

Что нужноГде доступноКак получить
Синоним (представление)клиент и серверСтрока(Значение)
Имя значениясерверчерез метаданные, пример ниже
Порядковый номерсерверПеречисления.Имя.Индекс(Значение)
Значение по имениклиент и серверПредопределенноеЗначение("Перечисление." + ИмяПеречисления + "." + ИмяЗначения)

Имя значения по ссылке:

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

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

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

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

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

Синоним видит пользователь, и его легко поменяют при доработке. Для сравнения в коде и для ключей в данных используйте имя или саму ссылку, но не Строка(Значение).

Ошибки при обращении к перечислению на клиенте и как их найти

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

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

Как писать код с перечислениями без сюрпризов

Несколько привычек, которые снимают большую часть проблем.

  1. Получайте значение один раз. Перед циклом сохраните ПредопределенноеЗначение в переменную и сравнивайте с ней. Код короче, а опечатка, если она есть, находится в одном месте.
  2. Держите логику видимости в одной процедуре. Вариант с &НаКлиентеНаСервереБезКонтекста из примера выше избавляет от рассинхрона между открытием формы и изменением реквизита.
  3. Не тяните сервер ради того, что есть на клиенте. Ссылка в реквизите формы и ПредопределенноеЗначение уже на клиенте. Серверный вызов нужен для списка значений, имени и индекса.
  4. Сравнивайте ссылки, а не представления. Ни Строка(Значение) = "Закрыт", ни сравнение синонимов не переживут переименования.
  5. В запросах используйте ЗНАЧЕНИЕ() или параметр. Внутри текста запроса пишется ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Закрыт) — так же в единственном числе. Альтернатива — передать ссылку через Запрос.УстановитьПараметр.
  6. Не правьте типовые перечисления без нужды. Добавление значения в типовое перечисление — изменение конфигурации поставщика, которое потом придётся сопровождать при каждом обновлении. Если нужна своя логика, расширение конфигурации обычно обходится дешевле.

Пример «до» и «после»:

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

// Хорошо: значение получено один раз
&НаКлиенте
Процедура ПроверитьСтроки()
	ВидУслуга = ПредопределенноеЗначение("Перечисление.ВидыСтрок.Услуга");
	Для Каждого СтрокаТЧ Из Объект.Товары Цикл
		Если СтрокаТЧ.ВидСтроки = ВидУслуга Тогда
			СтрокаТЧ.Количество = 1;
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

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

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

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