Как прочитать реквизит ссылки в клиентском коде управляемой формы
Содержание
Получить реквизит объекта на клиенте в 1С 8.3 через точку не получится: Контрагент.ИНН в процедуре с директивой &НаКлиенте выдаст ошибку. Ссылка на клиенте есть только идентификатор. Значение реквизита берут одним из трёх способов: из данных формы, если оно там уже есть; вызовом серверной функции с директивой &НаСервереБезКонтекста; из кэша, который заполнили заранее.
Самое частое заблуждение — что раз в толстом клиенте обычного приложения Ссылка.Реквизит работал, то и в управляемой форме он должен работать. Не должен. Тонкий клиент и веб-клиент не имеют доступа к базе, поэтому любые данные объекта приходят только с сервера. Задача разработчика — сделать так, чтобы таких обращений было как можно меньше.
Почему ссылка на клиенте «пустая»: как устроен обмен с сервером
В управляемом приложении код формы делится по директивам компиляции. Каждая директива определяет, где выполняется процедура и что ей доступно.
| Директива | Где выполняется | Что доступно |
|---|---|---|
&НаКлиенте | на компьютере пользователя | данные формы, элементы, клиентские модули |
&НаСервере | на сервере, с передачей формы | данные формы + вся прикладная модель |
&НаСервереБезКонтекста | на сервере, без формы | только параметры функции + прикладная модель |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | только общие для клиента и сервера методы |
На клиенте ссылочный тип (СправочникСсылка.Контрагенты, ДокументСсылка.РеализацияТоваровУслуг) можно хранить, сравнивать, передавать в параметры и открывать по нему форму. Но обратиться к его реквизитам нельзя. Платформа обычно сообщает: Поле объекта не обнаружено (ИНН).
Недоступны на клиенте и объекты целиком: Ссылка.ПолучитьОбъект(), менеджеры справочников, запросы, регистры, константы. Всё это есть только на сервере.
Каждый вызов сервера из клиентского кода — это отдельный сетевой обмен. В локальной сети он почти незаметен, а через интернет, веб-клиент или медленный VPN десять вызовов подряд дают заметную задержку при открытии формы или выборе значения.
Что можно прочитать на клиенте без обращения к серверу
Часть данных уже лежит на клиенте, и за ними не нужно ходить на сервер.
- 📄 Реквизиты основного объекта формы. В форме документа
Объект.Дата,Объект.Номер,Объект.Контрагентчитаются прямо на клиенте. Это данные формы (ДанныеФормыСтруктура), а не обращение к базе. - 📋 Табличные части.
Объект.Товарына клиенте — коллекцияДанныеФормыКоллекция. По ней можно пройти циклом и прочитать колонки строк. - 🧩 Дополнительные реквизиты формы, которые вы создали в редакторе формы и заполнили на сервере.
- 🎯 Текущая строка таблицы:
Элементы.Товары.ТекущиеДанныевозвращает данные выделенной строки.
&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
Сумма = ТекущиеДанные.Количество * ТекущиеДанные.Цена;
КонецПроцедуры
Обратите внимание на границу. Объект.Контрагент на клиенте читается — это ссылка. А Объект.Контрагент.ИНН — уже нет: ИНН хранится в справочнике, а не в форме документа.
Элементы формы (Элементы.Контрагент) — это оформление: заголовок, видимость, доступность, подсказка. Значение элемента берите из реквизита, к которому он привязан (свойство ПутьКДанным), а не из самого элемента.
Вызов сервера: как получить реквизиты объекта за одно обращение
Когда значения в форме нет, нужна серверная функция. Правильный шаблон — функция с директивой &НаСервереБезКонтекста, которая принимает ссылку и возвращает структуру с нужными полями.
Почему без контекста
Директива &НаСервере передаёт на сервер всю форму: реквизиты, табличные части, состояние. Если в документе 500 строк товаров, они уедут на сервер и вернутся обратно ради одного ИНН.
&НаСервереБезКонтекста передаёт только параметры функции. Для чтения реквизитов по ссылке этого достаточно, а трафик меньше в разы.
Вариант 1. Запрос в своей функции
Работает в любой конфигурации, даже без Библиотеки стандартных подсистем.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
Если Реквизиты <> Неопределено Тогда
ИННКонтрагента = Реквизиты.ИНН;
КППКонтрагента = Реквизиты.КПП;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.ИНН КАК ИНН,
| Контрагенты.КПП КАК КПП
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Контрагент);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат Неопределено;
КонецЕсли;
Результат = Новый Структура("ИНН, КПП");
ЗаполнитьЗначенияСвойств(Результат, Выборка);
Возврат Результат;
КонецФункции
Запрос читает только два поля. Обращение Контрагент.ИНН на сервере тоже сработает, но платформа при этом прочитает весь объект справочника целиком, включая табличные части.
Вариант 2. Функции БСП
В типовых конфигурациях (Бухгалтерия 3.0, ЗУП 3.1, УТ 11, ERP) есть готовые функции общего модуля ОбщегоНазначения:
ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ИНН")— одно значение;ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "ИНН, КПП, Наименование")— структура с ключами по именам полей.
Модуль ОбщегоНазначения серверный, напрямую с клиента его не вызвать. Оберните функцию в свою серверную:
&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Контрагент, "ИНН, КПП, Наименование");
КонецФункции
Не вызывайте ЗначениеРеквизитаОбъекта три раза подряд ради трёх полей. Это три запроса к базе. Одна ЗначенияРеквизитовОбъекта со списком полей делает то же за один.
Сравнение подходов
| Способ | Когда подходит | Минусы |
|---|---|---|
| Данные формы | значение уже есть в Объект или реквизите формы | нет |
&НаСервереБезКонтекста + запрос | любая конфигурация, нужен контроль полей | больше кода |
| Обёртка над БСП | типовая конфигурация | зависит от версии БСП |
&НаСервере | нужно заодно изменить данные формы | гоняет всю форму по сети |
| Кэш | значение редко меняется и нужно часто | риск устаревших данных |
Загрузить заранее: реквизиты в форме при её создании
Лучший серверный вызов — тот, которого нет. Если форме точно понадобятся реквизиты связанных объектов, прочитайте их в ПриСозданииНаСервере. Этот обработчик и так выполняется на сервере, лишнего обмена не будет.
- Откройте форму в конфигураторе или в расширении.
- На вкладке «Реквизиты» добавьте реквизиты формы, например
ВалютаДоговораиВедениеВзаиморасчетов. - В
ПриСозданииНаСерверезаполните их одним запросом. - В клиентском коде читайте их как обычные данные формы.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ЗаполнитьРеквизитыДоговора();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьРеквизитыДоговора()
Если Не ЗначениеЗаполнено(Объект.Договор) Тогда
Возврат;
КонецЕсли;
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Объект.Договор, "ВалютаВзаиморасчетов");
ВалютаДоговора = Реквизиты.ВалютаВзаиморасчетов;
КонецПроцедуры
Имена реквизитов справочника здесь условные: проверьте их в дереве метаданных своей конфигурации.
Когда пользователь меняет договор, обновите эти реквизиты в обработчике ДоговорПриИзменении. Если там всё равно нужно пересчитать форму, вызовите процедуру &НаСервере один раз и сделайте в ней всё сразу: прочитайте реквизиты и пересчитайте табличную часть.
Для типовых форм не правьте модуль напрямую. Добавьте реквизит формы и обработчик через расширение конфигурации: доработка переживёт обновление релиза.
Кэш и временное хранилище: когда значение нужно много раз
Если одно и то же значение нужно во многих формах — например, валюта регламентированного учёта или настройки текущего пользователя, — кэшируйте его.
Модуль с повторным использованием возвращаемых значений
- Создайте общий модуль, например
РаботаСКонтрагентамиПовтИсп. - Установите флажки «Сервер» и «Вызов сервера».
- В свойстве «Повторное использование возвращаемых значений» выберите «На время сеанса».
- Напишите экспортную функцию, которая возвращает фиксированную структуру.
Функция РеквизитыОрганизации(Организация) Экспорт
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Организация, "ИНН, КПП");
Возврат Новый ФиксированнаяСтруктура(Реквизиты);
КонецФункции
Повторный вызов с тем же параметром не выполняет тело функции, а отдаёт сохранённое значение. Возвращайте именно фиксированные коллекции: обычную структуру вызывающий код может случайно изменить, и испорченное значение уйдёт в кэш для всех следующих вызовов.
Платформа сама сбрасывает такой кэш через некоторое время. Принудительно его очищает метод ОбновитьПовторноИспользуемыеЗначения().
Клиентская переменная ПараметрыПриложения
ПараметрыПриложения — свойство глобального контекста на клиенте, соответствие, которое живёт весь сеанс. Его удобно использовать как клиентский кэш: прочитали значение с сервера один раз, положили по ключу, дальше берёте без обмена.
&НаКлиенте
Функция ВалютаУчетаКлиент()
Ключ = "МояПодсистема.ВалютаУчета";
Значение = ПараметрыПриложения.Получить(Ключ);
Если Значение = Неопределено Тогда
Значение = ВалютаУчетаСервер();
ПараметрыПриложения.Вставить(Ключ, Значение);
КонецЕсли;
Возврат Значение;
КонецФункции
Ключ начинайте с имени своей подсистемы, чтобы не столкнуться с ключами типовой конфигурации.
Временное хранилище
Через ПоместитьВоВременноеХранилище и ПолучитьИзВременногоХранилища передают большие данные между сервером и клиентом или между формами: таблицу подбора, двоичные данные, результат фонового задания. Адрес хранилища — строка, её хранят в реквизите формы.
Для одного-двух реквизитов хранилище избыточно. Его место — когда данных много или они готовятся в фоне.
Кэш отдаёт старое значение, пока его не сбросили. Не кэшируйте то, что пользователи часто меняют: цены, остатки, статусы документов.
Реквизиты текущей строки динамического списка
В форме списка данные строк приходят с сервера порциями. На клиенте Элементы.Список.ТекущиеДанные возвращает структуру с полями текущей строки.
Подвох в том, что в структуре есть только поля, которые нужны для отображения: видимые колонки и служебные поля. Если реквизита нет на экране, ТекущиеДанные.ИНН выдаст ошибку.
Решения по порядку предпочтения:
- Добавьте поле в колонки списка и скройте колонку, если она не нужна пользователю.
- Либо в настройке динамического списка отметьте у поля «Использовать всегда». Тогда оно будет в данных строки, даже если колонки нет.
- Если значение вычисляемое, используйте событие таблицы
ПриПолученииДанныхНаСервере: оно позволяет дописать данные в строки на сервере, пока они передаются клиенту. - Для разовой операции над выделенными строками передайте массив
Элементы.Список.ВыделенныеСтрокив одну серверную функцию.
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ОтключитьОбработчикОжидания("ОбновитьПодсказку");
ПодключитьОбработчикОжидания("ОбновитьПодсказку", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ОбновитьПодсказку()
ТекущиеДанные = Элементы.Список.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
Подсказка = ТекущиеДанные.Наименование;
КонецПроцедуры
Отложенный обработчик нужен на случай, если вы всё-таки вызываете сервер при смене строки. Пользователь листает список стрелками, и без задержки вызов уйдёт на каждую промелькнувшую строку.
Элементы.Список.ТекущаяСтрока в списке справочника или документа возвращает ссылку текущей записи. Её можно передать на сервер как параметр.
Ошибки при получении реквизитов и как их обработать
Большинство сбоев сводится к нескольким ситуациям.
Пустая ссылка и битая ссылка
Пустая ссылка проверяется функцией ЗначениеЗаполнено. Битая — ссылка на объект, которого больше нет в базе. Запрос по ней ничего не вернёт, поэтому в своей функции проверяйте результат Выборка.Следующий() и возвращайте Неопределено, как в примере выше.
Права доступа
Если в конфигурации настроено ограничение доступа на уровне записей, запрос без слова РАЗРЕШЕННЫЕ упадёт с ошибкой прав на «чужом» объекте. С РАЗРЕШЕННЫЕ запрос вернёт пустой результат. У ЗначенияРеквизитовОбъекта в БСП для этого есть параметр выбора только разрешённых значений.
Не включайте привилегированный режим только ради того, чтобы «ошибка ушла». Пользователь увидит данные, которые ему видеть нельзя.
Перехват исключений
Серверная ошибка приходит на клиент как исключение. Оберните вызов в Попытка … Исключение, если сбой не должен прерывать работу пользователя:
&НаКлиенте
Процедура ЗаполнитьИНН()
Попытка
Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
Исключение
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не удалось получить ИНН: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
Сообщение.Сообщить();
Возврат;
КонецПопытки;
Если Реквизиты <> Неопределено Тогда
ИННКонтрагента = Реквизиты.ИНН;
КонецЕсли;
КонецПроцедуры
Подробности ошибки пишите в журнал регистрации на сервере. Пользователю достаточно короткого понятного текста.