Ссылка есть, реквизита нет: чтение данных справочника из клиентского кода 1С 8.3
Содержание
На клиенте 1С 8.3 значение справочника получают тремя способами. Предопределённый элемент берут функцией ПредопределенноеЗначение("Справочник.Валюты.Рубль") прямо в клиентском коде. Реквизиты обычного элемента читают через серверную функцию с директивой &НаСервереБезКонтекста или через общий модуль с флагом «Вызов сервера». Если данные уже выведены на форму, их берут из реквизитов формы или из Элементы.Список.ТекущиеДанные вообще без обращения к серверу.
Типичная ситуация: в обработчике ПриИзменении поля «Контрагент» нужно проверить, юрлицо это или физлицо, и показать или скрыть поле КПП. Разработчик пишет Объект.Контрагент.ЮридическоеФизическоеЛицо и получает ошибку в тонком клиенте. Ссылка на клиенте есть, а доступа к её полям нет. Разберём, как получить значение справочника на клиенте правильно и без лишних серверных вызовов.
Что доступно на клиенте: ссылка, представление и больше ничего
В управляемом приложении клиент и сервер — разные процессы, часто на разных компьютерах. Клиент не обращается к базе данных напрямую. Любые данные справочника попадают на клиент только как результат серверного вызова или вместе с данными формы.
Ссылка на элемент справочника (СправочникСсылка.Контрагенты) передаётся на клиент свободно. Но это лишь идентификатор плюс тип. Что можно сделать со ссылкой в клиентском коде:
- ✅ сравнить с другой ссылкой:
Если Объект.Валюта = ВалютаРубль Тогда; - ✅ проверить на пустоту:
Объект.Контрагент.Пустая()или сравнением с пустой ссылкой; - ✅ проверить тип:
ТипЗнч(Значение) = Тип("СправочникСсылка.Контрагенты"); - ✅ получить представление для вывода:
Строка(Ссылка)— если ссылка пришла с формы, представление уже есть; - ❌ обратиться к реквизиту через точку:
Ссылка.Наименование,Ссылка.ИНН,Ссылка.Родитель; - ❌ вызвать
ПолучитьОбъект(), методы менеджераСправочники.Контрагенты.НайтиПоКоду()или выполнить запрос.
В толстом клиенте обычного приложения обращение через точку работало, потому что клиент сам читал базу. Отсюда старая привычка, которая ломается при переходе на тонкий и веб-клиент.
| Что нужно получить | Где взять на клиенте | Серверный вызов |
|---|---|---|
| Предопределённый элемент | ПредопределенноеЗначение() | нет (кешируется) |
| Пустая ссылка | ПредопределенноеЗначение("Справочник.X.ПустаяСсылка") | нет |
| Реквизит элемента на форме объекта | реквизит формы, заполненный на сервере | нет, данные пришли с формой |
| Поле текущей строки списка | Элементы.Список.ТекущиеДанные | нет |
| Реквизит произвольной ссылки | функция &НаСервереБезКонтекста | да, один |
| Константа-настройка, которая не меняется | модуль с повторным использованием | один раз за сеанс |
Предопределённые элементы и пустые ссылки без обращения к серверу
Предопределённые элементы (валюта «Рубль», вид контактной информации, статья по умолчанию) на клиенте получают глобальной функцией ПредопределенноеЗначение. Ей передают полное имя строкой:
&НаКлиенте
Процедура ВалютаПриИзменении(Элемент)
ВалютаРегл = ПредопределенноеЗначение("Справочник.Валюты.Рубль");
Элементы.Курс.Доступность = (Объект.Валюта <> ВалютаРегл);
КонецПроцедуры
Платформа кеширует результат: первый вызов идёт на сервер, следующие возвращают значение из клиентского кеша. Поэтому функцию можно смело вызывать в обработчиках, которые срабатывают часто.
Тем же способом получают пустую ссылку нужного типа:
ПустойКонтрагент = ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка");
Если предопределённого элемента с таким именем нет в конфигурации, ПредопределенноеЗначение вызывает исключение. Код упадёт у пользователя, а не при проверке модуля: имя записано строкой, и синтаксический контроль его не проверяет.
В типовых конфигурациях на БСП для этого есть безопасная обёртка ОбщегоНазначенияКлиентСервер.ПредопределенныйЭлемент("Справочник.Валюты.Рубль"). Она возвращает Неопределено, если элемента нет. Это полезно в расширениях и общем коде, который работает в нескольких конфигурациях.
Не путайте имя предопределённого элемента с наименованием. Имя видно в конфигураторе в свойствах элемента и пишется без пробелов. Наименование («Российский рубль») пользователь может переименовать, имя — нет.
Получение значения справочника через серверный вызов
Для обычного элемента справочника реквизит читают на сервере и возвращают на клиент примитивное значение или структуру. Главное правило — директива &НаСервереБезКонтекста, а не &НаСервере.
Почему без контекста
При вызове процедуры &НаСервере платформа отправляет на сервер данные формы и возвращает их обратно. На форме документа с табличной частью в 500 строк это сотни килобайт трафика в обе стороны. Процедура &НаСервереБезКонтекста получает только параметры. Внутри неё нет доступа к Объект, Элементы и реквизитам формы, зато вызов в разы легче.
Пример: один реквизит
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
ЭтоЮрЛицо = КонтрагентЮрЛицо(Объект.Контрагент);
Элементы.КПП.Видимость = ЭтоЮрЛицо;
КонецПроцедуры
&НаСервереБезКонтекста
Функция КонтрагентЮрЛицо(Контрагент)
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат Ложь;
КонецЕсли;
Возврат Контрагент.ЮридическоеФизическоеЛицо
= Перечисления.ЮридическоеФизическоеЛицо.ЮридическоеЛицо;
КонецФункции
Обращение Контрагент.ЮридическоеФизическоеЛицо на сервере допустимо, но платформа при этом читает весь элемент справочника целиком, со всеми табличными частями. Для одного-двух полей лучше запрос или функция БСП.
Функции БСП для чтения реквизитов
В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11 и другие) в серверном модуле ОбщегоНазначения есть готовые функции:
- 📌
ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ИНН")— одно значение; - 📌
ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "Код, Наименование, ИНН")— структура с ключами по именам реквизитов; - 📌
ОбщегоНазначения.ЗначениеРеквизитаОбъектов(МассивСсылок, "ИНН")— соответствие «ссылка → значение» для нескольких элементов.
Внутри они строят запрос только по нужным полям. Модуль ОбщегоНазначения серверный, напрямую с клиента его не вызвать. Оборачиваем в свою функцию без контекста:
&НаСервереБезКонтекста
Функция ДанныеКонтрагента(Контрагент)
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Контрагент,
"Наименование, ИНН, КПП, ЮридическоеФизическоеЛицо");
КонецФункции
На клиенте результат — обычная Структура: Данные.ИНН, Данные.КПП.
Общий модуль «Вызов сервера»
Если одно и то же значение справочника нужно в десятке форм, вынесите функцию в общий модуль. В конфигураторе создайте общий модуль, установите флаги «Сервер» и «Вызов сервера», снимите «Клиент (управляемое приложение)». Экспортные функции такого модуля вызываются из клиентского кода как МойМодульВызовСервера.ДанныеКонтрагента(Ссылка).
Каждая экспортная функция модуля с флагом «Вызов сервера» доступна с клиента любому пользователю. Проверяйте в ней параметры и не возвращайте лишнего: такой модуль — открытая дверь к данным.
Реквизиты и дополнительные поля без лишних вызовов
Самый быстрый серверный вызов — тот, которого нет. Если значение справочника нужно постоянно, пока открыта форма, получите его один раз вместе с формой.
Реквизит формы, заполненный при создании
Добавьте на форму реквизит, например ВалютаКонтрагента, и заполните его на сервере в ПриСозданииНаСервере и при смене контрагента. Клиентский код читает реквизит формы как обычную переменную:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ОбновитьДанныеКонтрагента();
КонецПроцедуры
&НаСервере
Процедура ОбновитьДанныеКонтрагента()
Если ЗначениеЗаполнено(Объект.Контрагент) Тогда
ИННКонтрагента = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Объект.Контрагент, "ИНН");
Иначе
ИННКонтрагента = "";
КонецЕсли;
КонецПроцедуры
Здесь &НаСервере оправдан: процедуру вызывают из серверного обработчика, где контекст формы уже есть.
Текущие данные динамического списка
В форме списка поля текущей строки доступны на клиенте через Элементы.Список.ТекущиеДанные. Туда попадают колонки, выбранные в запросе динамического списка, даже если они не выведены на форму:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элементы.Список.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
Элементы.КомандаОтправить.Доступность = ЗначениеЗаполнено(ТекущиеДанные.ИНН);
КонецПроцедуры
Чтобы поле появилось в ТекущиеДанные, его нужно добавить в запрос динамического списка (произвольный запрос) или выбрать в списке полей основной таблицы. Поле без колонки на форме иногда не передаётся на клиент: в этом случае добавьте его в свойство динамического списка «Использовать всегда».
ТекущиеДанные может вернуть Неопределено — например, когда список пуст или строка не выбрана. Проверка в начале обработчика обязательна.
Скорость: кеширование и пакетное чтение
Медленная форма почти всегда означает серверные вызовы в цикле или в часто срабатывающих событиях. Включите замер производительности и посмотрите количество вызовов сервера на одно действие пользователя.
| Приём | Когда применять | Риск |
|---|---|---|
&НаСервереБезКонтекста вместо &НаСервере | всегда, если не нужна форма | нет |
| Один вызов вместо нескольких | нужно 3-5 полей разом | нет |
| Пакетное чтение по массиву ссылок | обработка строк табличной части | крупный массив = долгий вызов |
| Модуль с повторным использованием | настройки, которые не меняются в сеансе | устаревшие данные |
| Реквизит формы | значение нужно всё время работы формы | надо обновлять при изменении |
Не вызывайте сервер в цикле
Типичная ошибка — проверка каждой строки табличной части отдельным вызовом:
// Плохо: 300 строк = 300 обращений к серверу
Для Каждого Строка Из Объект.Товары Цикл
Если ЭтоУслуга(Строка.Номенклатура) Тогда
Строка.Количество = 1;
КонецЕсли;
КонецЦикла;
Соберите ссылки в массив и получите всё за один вызов:
&НаКлиенте
Процедура ПроставитьКоличествоУслуг()
Ссылки = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Ссылки.Добавить(Строка.Номенклатура);
КонецЦикла;
Услуги = ПризнакиУслуг(Ссылки);
Для Каждого Строка Из Объект.Товары Цикл
Если Услуги.Получить(Строка.Номенклатура) = Истина Тогда
Строка.Количество = 1;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПризнакиУслуг(Ссылки)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Услуга КАК Услуга
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&Ссылки)";
Запрос.УстановитьПараметр("Ссылки", Ссылки);
Результат = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Результат.Вставить(Выборка.Ссылка, Выборка.Услуга);
КонецЦикла;
Возврат Результат;
КонецФункции
Реквизит Услуга здесь для примера: в вашей конфигурации признак может называться иначе или храниться в виде номенклатуры. Повторяющиеся ссылки в массиве запросу не мешают.
Кеширование на время сеанса
Для значений, которые за сеанс не меняются (основная организация, валюта учёта, настройки пользователя), создайте серверный общий модуль с флагом «Вызов сервера» и свойством «Повторное использование возвращаемых значений» = «На время сеанса». Первый вызов функции идёт на сервер, повторные с теми же параметрами отдаются из кеша.
Возвращайте из такого модуля неизменяемые значения: ФиксированнаяСтруктура, ФиксированныйМассив, примитивы, ссылки. Обычную структуру вызывающий код может случайно изменить, и испорченное значение получат все следующие вызовы.
Не кешируйте то, что пользователь может изменить в течение дня: цены, остатки, статусы. Кеш сбрасывается только с окончанием сеанса или через ОбновитьПовторноИспользуемыеЗначения(), а эта функция очищает весь кеш, а не одно значение.
Ошибки при чтении справочника с клиента и как их обработать
&НаСервереБезКонтекста или возьмите значение из реквизита формы.ОбщегоНазначенияКлиентСервер.ПредопределенныйЭлемент.СправочникОбъект, Запрос, РезультатЗапроса, ТаблицаЗначений. Верните структуру, массив, соответствие или примитив.Как обрабатывать исключения
Исключение, вызванное на сервере, долетает до клиента. Его ловят обычной конструкцией Попытка ... Исключение:
&НаКлиенте
Процедура ЗаполнитьПоКонтрагенту()
Попытка
Данные = ДанныеКонтрагента(Объект.Контрагент);
Исключение
ИнфоОшибки = ИнформацияОбОшибке();
ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнфоОшибки));
Возврат;
КонецПопытки;
Объект.ИНН = Данные.ИНН;
КонецПроцедуры
Подробности для разработчика (ПодробноеПредставлениеОшибки) пишите в журнал регистрации на сервере, а пользователю показывайте краткий текст. Стек вызовов в окне предупреждения бухгалтеру ничего не скажет.
Проверку прав делайте на сервере. В запросах используйте ВЫБРАТЬ РАЗРЕШЕННЫЕ: тогда при ограничении доступа на уровне записей запрос вернёт только доступные строки вместо ошибки. Функция ПравоДоступа("Чтение", Метаданные.Справочники.Контрагенты) подскажет, есть ли у пользователя право на справочник в целом.
Частые вопросы
Можно ли на клиенте получить ссылку на элемент по коду или наименованию?
&НаСервереБезКонтекста, внутри вызовите Справочники.Валюты.НайтиПоКоду("643") и верните ссылку на клиент.Работает ли это в веб-клиенте и мобильном клиенте?
Чем `&НаСервереБезКонтекста` отличается от общего модуля «Вызов сервера»?
Почему в обычном приложении обращение через точку работало, а в управляемом нет?
Как получить значение справочника в расширении конфигурации?
&НаСервереБезКонтекста или общий модуль расширения с флагом «Вызов сервера». Предопределённые элементы основной конфигурации доступны через ПредопределенноеЗначение.