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

· 9 мин

Содержание

Получить реквизит объекта на клиенте в 1С 8.3 через точку не получится: Контрагент.ИНН в процедуре с директивой &НаКлиенте выдаст ошибку. Ссылка на клиенте есть только идентификатор. Значение реквизита берут одним из трёх способов: из данных формы, если оно там уже есть; вызовом серверной функции с директивой &НаСервереБезКонтекста; из кэша, который заполнили заранее.

Самое частое заблуждение — что раз в толстом клиенте обычного приложения Ссылка.Реквизит работал, то и в управляемой форме он должен работать. Не должен. Тонкий клиент и веб-клиент не имеют доступа к базе, поэтому любые данные объекта приходят только с сервера. Задача разработчика — сделать так, чтобы таких обращений было как можно меньше.

Почему ссылка на клиенте «пустая»: как устроен обмен с сервером

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

ДирективаГде выполняетсяЧто доступно
&НаКлиентена компьютере пользователяданные формы, элементы, клиентские модули
&НаСерверена сервере, с передачей формыданные формы + вся прикладная модель
&НаСервереБезКонтекстана сервере, без формытолько параметры функции + прикладная модель
&НаКлиентеНаСервереБезКонтекстатам, откуда вызвалитолько общие для клиента и сервера методы

На клиенте ссылочный тип (СправочникСсылка.Контрагенты, ДокументСсылка.РеализацияТоваровУслуг) можно хранить, сравнивать, передавать в параметры и открывать по нему форму. Но обратиться к его реквизитам нельзя. Платформа обычно сообщает: Поле объекта не обнаружено (ИНН).

Недоступны на клиенте и объекты целиком: Ссылка.ПолучитьОбъект(), менеджеры справочников, запросы, регистры, константы. Всё это есть только на сервере.

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

Что можно прочитать на клиенте без обращения к серверу

Часть данных уже лежит на клиенте, и за ними не нужно ходить на сервер.

  • 📄 Реквизиты основного объекта формы. В форме документа Объект.Дата, Объект.Номер, Объект.Контрагент читаются прямо на клиенте. Это данные формы (ДанныеФормыСтруктура), а не обращение к базе.
  • 📋 Табличные части. Объект.Товары на клиенте — коллекция ДанныеФормыКоллекция. По ней можно пройти циклом и прочитать колонки строк.
  • 🧩 Дополнительные реквизиты формы, которые вы создали в редакторе формы и заполнили на сервере.
  • 🎯 Текущая строка таблицы: Элементы.Товары.ТекущиеДанные возвращает данные выделенной строки.
&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
	ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;
	Сумма = ТекущиеДанные.Количество * ТекущиеДанные.Цена;
КонецПроцедуры

Обратите внимание на границу. Объект.Контрагент на клиенте читается — это ссылка. А Объект.Контрагент.ИНН — уже нет: ИНН хранится в справочнике, а не в форме документа.

Элементы формы (Элементы.Контрагент) — это оформление: заголовок, видимость, доступность, подсказка. Значение элемента берите из реквизита, к которому он привязан (свойство ПутьКДанным), а не из самого элемента.

Вызов сервера: как получить реквизиты объекта за одно обращение

Когда значения в форме нет, нужна серверная функция. Правильный шаблон — функция с директивой &НаСервереБезКонтекста, которая принимает ссылку и возвращает структуру с нужными полями.

Почему без контекста

Директива &НаСервере передаёт на сервер всю форму: реквизиты, табличные части, состояние. Если в документе 500 строк товаров, они уедут на сервер и вернутся обратно ради одного ИНН.

&НаСервереБезКонтекста передаёт только параметры функции. Для чтения реквизитов по ссылке этого достаточно, а трафик меньше в разы.

Вариант 1. Запрос в своей функции

Работает в любой конфигурации, даже без Библиотеки стандартных подсистем.

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
	Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
	Если Реквизиты <> Неопределено Тогда
		ИННКонтрагента = Реквизиты.ИНН;
		КППКонтрагента = Реквизиты.КПП;
	КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)
	Если Не ЗначениеЗаполнено(Контрагент) Тогда
		Возврат Неопределено;
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Контрагенты.ИНН КАК ИНН,
	|	Контрагенты.КПП КАК КПП
	|ИЗ
	|	Справочник.Контрагенты КАК Контрагенты
	|ГДЕ
	|	Контрагенты.Ссылка = &Ссылка";
	Запрос.УстановитьПараметр("Ссылка", Контрагент);

	Выборка = Запрос.Выполнить().Выбрать();
	Если Не Выборка.Следующий() Тогда
		Возврат Неопределено;
	КонецЕсли;

	Результат = Новый Структура("ИНН, КПП");
	ЗаполнитьЗначенияСвойств(Результат, Выборка);
	Возврат Результат;
КонецФункции

Запрос читает только два поля. Обращение Контрагент.ИНН на сервере тоже сработает, но платформа при этом прочитает весь объект справочника целиком, включая табличные части.

Вариант 2. Функции БСП

В типовых конфигурациях (Бухгалтерия 3.0, ЗУП 3.1, УТ 11, ERP) есть готовые функции общего модуля ОбщегоНазначения:

  • ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ИНН") — одно значение;
  • ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "ИНН, КПП, Наименование") — структура с ключами по именам полей.

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

&НаСервереБезКонтекста
Функция РеквизитыКонтрагента(Контрагент)
	Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Контрагент, "ИНН, КПП, Наименование");
КонецФункции

Не вызывайте ЗначениеРеквизитаОбъекта три раза подряд ради трёх полей. Это три запроса к базе. Одна ЗначенияРеквизитовОбъекта со списком полей делает то же за один.

Сравнение подходов

СпособКогда подходитМинусы
Данные формызначение уже есть в Объект или реквизите формынет
&НаСервереБезКонтекста + запрослюбая конфигурация, нужен контроль полейбольше кода
Обёртка над БСПтиповая конфигурациязависит от версии БСП
&НаСерверенужно заодно изменить данные формыгоняет всю форму по сети
Кэшзначение редко меняется и нужно часториск устаревших данных

Загрузить заранее: реквизиты в форме при её создании

Лучший серверный вызов — тот, которого нет. Если форме точно понадобятся реквизиты связанных объектов, прочитайте их в ПриСозданииНаСервере. Этот обработчик и так выполняется на сервере, лишнего обмена не будет.

  1. Откройте форму в конфигураторе или в расширении.
  2. На вкладке «Реквизиты» добавьте реквизиты формы, например ВалютаДоговора и ВедениеВзаиморасчетов.
  3. В ПриСозданииНаСервере заполните их одним запросом.
  4. В клиентском коде читайте их как обычные данные формы.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	ЗаполнитьРеквизитыДоговора();
КонецПроцедуры

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

Имена реквизитов справочника здесь условные: проверьте их в дереве метаданных своей конфигурации.

Когда пользователь меняет договор, обновите эти реквизиты в обработчике ДоговорПриИзменении. Если там всё равно нужно пересчитать форму, вызовите процедуру &НаСервере один раз и сделайте в ней всё сразу: прочитайте реквизиты и пересчитайте табличную часть.

Для типовых форм не правьте модуль напрямую. Добавьте реквизит формы и обработчик через расширение конфигурации: доработка переживёт обновление релиза.

Кэш и временное хранилище: когда значение нужно много раз

Если одно и то же значение нужно во многих формах — например, валюта регламентированного учёта или настройки текущего пользователя, — кэшируйте его.

Модуль с повторным использованием возвращаемых значений

  1. Создайте общий модуль, например РаботаСКонтрагентамиПовтИсп.
  2. Установите флажки «Сервер» и «Вызов сервера».
  3. В свойстве «Повторное использование возвращаемых значений» выберите «На время сеанса».
  4. Напишите экспортную функцию, которая возвращает фиксированную структуру.
Функция РеквизитыОрганизации(Организация) Экспорт
	Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Организация, "ИНН, КПП");
	Возврат Новый ФиксированнаяСтруктура(Реквизиты);
КонецФункции

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

Платформа сама сбрасывает такой кэш через некоторое время. Принудительно его очищает метод ОбновитьПовторноИспользуемыеЗначения().

Клиентская переменная ПараметрыПриложения

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

&НаКлиенте
Функция ВалютаУчетаКлиент()
	Ключ = "МояПодсистема.ВалютаУчета";
	Значение = ПараметрыПриложения.Получить(Ключ);
	Если Значение = Неопределено Тогда
		Значение = ВалютаУчетаСервер();
		ПараметрыПриложения.Вставить(Ключ, Значение);
	КонецЕсли;
	Возврат Значение;
КонецФункции

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

Временное хранилище

Через ПоместитьВоВременноеХранилище и ПолучитьИзВременногоХранилища передают большие данные между сервером и клиентом или между формами: таблицу подбора, двоичные данные, результат фонового задания. Адрес хранилища — строка, её хранят в реквизите формы.

Для одного-двух реквизитов хранилище избыточно. Его место — когда данных много или они готовятся в фоне.

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

Реквизиты текущей строки динамического списка

В форме списка данные строк приходят с сервера порциями. На клиенте Элементы.Список.ТекущиеДанные возвращает структуру с полями текущей строки.

Подвох в том, что в структуре есть только поля, которые нужны для отображения: видимые колонки и служебные поля. Если реквизита нет на экране, ТекущиеДанные.ИНН выдаст ошибку.

Решения по порядку предпочтения:

  1. Добавьте поле в колонки списка и скройте колонку, если она не нужна пользователю.
  2. Либо в настройке динамического списка отметьте у поля «Использовать всегда». Тогда оно будет в данных строки, даже если колонки нет.
  3. Если значение вычисляемое, используйте событие таблицы ПриПолученииДанныхНаСервере: оно позволяет дописать данные в строки на сервере, пока они передаются клиенту.
  4. Для разовой операции над выделенными строками передайте массив Элементы.Список.ВыделенныеСтроки в одну серверную функцию.
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
	ОтключитьОбработчикОжидания("ОбновитьПодсказку");
	ПодключитьОбработчикОжидания("ОбновитьПодсказку", 0.2, Истина);
КонецПроцедуры

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

Отложенный обработчик нужен на случай, если вы всё-таки вызываете сервер при смене строки. Пользователь листает список стрелками, и без задержки вызов уйдёт на каждую промелькнувшую строку.

Элементы.Список.ТекущаяСтрока в списке справочника или документа возвращает ссылку текущей записи. Её можно передать на сервер как параметр.

Ошибки при получении реквизитов и как их обработать

Большинство сбоев сводится к нескольким ситуациям.

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

Пустая ссылка и битая ссылка

Пустая ссылка проверяется функцией ЗначениеЗаполнено. Битая — ссылка на объект, которого больше нет в базе. Запрос по ней ничего не вернёт, поэтому в своей функции проверяйте результат Выборка.Следующий() и возвращайте Неопределено, как в примере выше.

Права доступа

Если в конфигурации настроено ограничение доступа на уровне записей, запрос без слова РАЗРЕШЕННЫЕ упадёт с ошибкой прав на «чужом» объекте. С РАЗРЕШЕННЫЕ запрос вернёт пустой результат. У ЗначенияРеквизитовОбъекта в БСП для этого есть параметр выбора только разрешённых значений.

Не включайте привилегированный режим только ради того, чтобы «ошибка ушла». Пользователь увидит данные, которые ему видеть нельзя.

Перехват исключений

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

&НаКлиенте
Процедура ЗаполнитьИНН()
	Попытка
		Реквизиты = РеквизитыКонтрагента(Объект.Контрагент);
	Исключение
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = "Не удалось получить ИНН: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
		Сообщение.Сообщить();
		Возврат;
	КонецПопытки;
	Если Реквизиты <> Неопределено Тогда
		ИННКонтрагента = Реквизиты.ИНН;
	КонецЕсли;
КонецПроцедуры

Подробности ошибки пишите в журнал регистрации на сервере. Пользователю достаточно короткого понятного текста.

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

Можно ли передать на клиент объект справочника целиком?
Нет. СправочникОбъект и ДокументОбъект на клиент не передаются. Передают ссылку, структуру с нужными полями или данные формы.
Работает ли Ссылка.Реквизит в обычном приложении?
В толстом клиенте обычного приложения такое обращение выполняется, потому что клиент сам читает базу. В тонком клиенте, веб-клиенте и управляемых формах — нет.
Как узнать, сколько вызовов сервера делает форма?
Включите замер производительности в отладчике и выполните действие в форме. В результатах будут видны серверные вызовы и время каждого.
Можно ли получить реквизит реквизита, например Договор.Организация.ИНН?
Да, на сервере: в запросе через точку или отдельным чтением по полученной ссылке. Главное — сделать это внутри одной серверной функции.
Можно ли запомнить значение в переменной модуля формы?
Да. Переменная, объявленная в модуле формы с директивой НаКлиенте, живёт, пока открыта форма. Это самый простой кэш на время работы с одним документом.