При переходе к разработке на управляемых формах платформы «1С:Предприятие 8» многие разработчики сталкиваются с неочевидной на первый взгляд логикой работы с реквизитами. Возникает закономерный вопрос: почему для получения данных из табличной части мы обращаемся к коллекции Элементы (через Элементы.ИмяТаблицы.ТекущиеДанные.ИмяРеквизита), а для получения значения обычного реквизита шапки попытка написать Элементы.ИмяРеквизита приводит к ошибке? Почему в этом случае мы обязаны использовать структуру Объект.ИмяРеквизита?
В этой статье мы подробно разберем внутреннюю архитектуру управляемых форм, выясним причины такого разделения и рассмотрим практические приемы работы с данными на клиенте и сервере.
Для того чтобы понять логику платформы, проанализируем базовый паттерн проектирования, лежащий в основе управляемого приложения — Model-View-Controller (MVC). Разработчики управляемой платформы «1С» четко разделили форму на две независимые сущности:
Объект типа ДанныеФормыСтруктура, либо напрямую реквизиты формы типа ДанныеФормыКоллекция, строки ДанныеФормыЭлементКоллекции и т.д.).Элементы.Основное правило управляемого интерфейса: элементы формы не хранят внутри себя редактируемые данные. Они являются лишь «окнами» для отображения и редактирования данных, хранящихся в модели.
В старых «обычных» формах (платформа 1С 8.1 и ранее) у элементов управления было свойство Значение (например, ЭлементыФормы.ИмяРеквизита.Значение). В управляемых формах от этого архитектурного решения полностью отказались в целях оптимизации сетевого трафика между клиентом и сервером. Элементы формы на тонком и веб-клиенте знают только свои визуальные свойства: видимость, доступность, цвет, шрифт, положение на экране. Сами данные передаются отдельно в виде легких структур данных.
Рассмотрим детально реквизит шапки, например, Организация. На форме ему соответствуют:
Объект.Организация.Элементы.Организация (тип ПолеФормы).Связь между ними устанавливается через свойство элемента ПутьКДанным (значение которого равно строке "Объект.Организация").
Так как реквизит шапки одиночный, связь здесь жесткая: «один реквизит — одно значение». Платформа всегда точно знает, где лежит значение этого реквизита — в структуре данных Объект. Обращаться к нему через интерфейсный элемент бессмысленно и технически невозможно, так как объект типа ПолеФормы не имеет свойств для непосредственного хранения или получения значения бизнес-данных.
// Правильно: получаем данные напрямую из модели
ТекущаяОрганизация = Объект.Организация;
// Неправильно: вызовет ошибку, у элемента формы нет свойства для получения значения
// ТекущаяОрганизация = Элементы.Организация.Значение;
Ситуация с табличными частями принципиально иная. Табличная часть в модели данных — это коллекция строк (объект типа ДанныеФормыКоллекция), содержащая множество строк и колонок (например, Объект.Товары).
При этом на экране пользователя отображается всего один элемент управления — табличное поле типа ТаблицаФормы (в коллекции элементов это Элементы.Товары).
Выясним причину диссонанса: модель данных хранит все строки таблицы одновременно. Но когда пользователь совершает какое-либо действие (например, нажимает кнопку на панели или меняет значение в текущей строке), программе необходимо знать, с какой конкретно строкой сейчас взаимодействует пользователь.
Информация об активной строке (фокусе ввода, выделении) — это исключительно свойство интерфейса (отображения). Данные Объект.Товары ничего не знают о том, куда кликнул пользователь мышкой. Об этом знает только визуальный элемент управления — таблица формы.
Именно для этого у объекта ТаблицаФормы реализовано специальное свойство-мост — ТекущиеДанные. Оно обращается к интерфейсу, выясняет индекс активной строки и возвращает структуру данных (ДанныеФормыЭлементКоллекции) именно для этой конкретной строки.
// Мы запрашиваем у элемента интерфейса (Элементы.Товары) активную строку,
// и получаем доступ к данным этой строки из модели
СтрокаТабличнойЧасти = Элементы.Товары.ТекущиеДанные;
Если СтрокаТабличнойЧасти <> Неопределено Тогда
// Теперь мы можем читать и изменять реквизиты конкретной строки
ТекущийТовар = СтрокаТабличнойЧасти.Номенклатура;
СтрокаТабличнойЧасти.Количество = 10;
КонецЕсли;
Таким образом, использование коллекции Элементы для табличных частей — это вынужденная необходимость связывания многострочных данных с текущим фокусом внимания пользователя в интерфейсе.
Иногда при разработке универсальных механизмов возникает задача: в общем клиентском обработчике (например, при изменении поля, куда передается параметр Элемент) динамически получить значение реквизита, связанного с этим элементом.
Рассмотрим несколько способов решения этой задачи и проанализируем их ограничения.
Если в форме имена элементов управления совпадают с именами реквизитов (что является стандартным поведением платформы при перетаскивании реквизитов на форму), мы можем использовать обращение через квадратные скобки к структуре данных:
&НаКлиенте
Процедура ИмяЭлементаПриИзменении(Элемент)
// Получаем имя элемента (например, "Организация")
ИмяРеквизита = Элемент.Имя;
// Получаем значение из модели данных "Объект" по имени
ЗначениеРеквизита = Объект[ИмяРеквизита];
ПоказатьПредупреждение(, "Изменено значение реквизита: " + ЗначениеРеквизита);
КонецПроцедуры
Ограничение: Этот способ не сработает, если имя элемента на форме было изменено вручную и больше не совпадает с именем реквизита в объекте (или если путь к данным имеет сложную структуру, например, Объект.Контрагент.ОсновнойДоговор).
Свойство элемента формы ПутьКДанным хранит точный путь к переменной в модели данных. Однако на тонком и веб-клиенте это свойство недоступно (вызовет ошибку выполнения). Чтобы им воспользоваться, нам необходимо обратиться к серверному контексту:
&НаКлиенте
Процедура УниверсальныйОбработчикПриИзменении(Элемент)
ЗначениеРеквизита = ПолучитьЗначениеЭлементаНаСервере(Элемент.Имя);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьЗначениеЭлементаНаСервере(Знач ИмяЭлемента)
// На сервере мы имеем полный доступ к свойствам элементов формы
ТекущийЭлемент = Элементы[ИмяЭлемента];
ПутьКДанным = ТекущийЭлемент.ПутьКДанным; // Вернет, например, "Объект.Организация"
// Внимание! Использовать Вычислить() на сервере небезопасно,
// поэтому разберем путь программно или воспользуемся функциями БСП
Возврат Вычислить(ПутьКДанным);
КонецФункции
Важное примечание: Использование встроенной функции Вычислить() (Eval) крайне не рекомендуется в современных конфигурациях из соображений безопасности (особенно в веб-клиенте), а также из-за снижения производительности. Кроме того, на клиенте функция Вычислить() полностью отсутствует.
Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), для решения этой задачи предусмотрены готовые безопасные функции клиент-серверного общего модуля. Они позволяют корректно разбирать сложные пути к данным, включая точки:
&НаКлиенте
Процедура БезопасныйОбработчикПриИзменении(Элемент)
// Получаем значение реквизита формы по его имени через БСП
// Функция сама определит путь и безопасно извлечет данные
ЗначениеРеквизита = ОбщегоНазначенияКлиентСервер.ПолучитьРеквизитФормыПоПути(ЭтотОбъект, "Объект." + Элемент.Имя);
КонецПроцедуры
Подведем итоги нашего анализа архитектурных особенностей:
Элементы, мы далее работаем непосредственно с ее данными.Значение.