При разработке универсальных обработок, панелей администрирования или инструментов аудита данных нередко возникает задача: программно обойти все реквизиты выбранного документа произвольного типа, прочитать их текущие значения и отобразить поля для редактирования или просмотра непосредственно на управляемой форме. Разберем по шагам, как правильно реализовать эту задачу, какие методы платформы использовать и как избежать типичных ошибок при динамическом создании реквизитов и элементов управления.
Для того чтобы обойти структуру любого документа, переданного в качестве ссылки, воспользуемся объектом метаданных. Метод Метаданные() позволяет получить описание конфигурации для конкретного объекта данных.
Рассмотрим серверную процедуру обхода реквизитов документа. Обратите внимание, что обращение к значению реквизита по его имени выполняется через оператор индексации Док[Реквизит.Имя]:
&НаСервере
Процедура ОбойтиРеквизитыДокумента(ДокСсылка)
Если Не ЗначениеЗаполнено(ДокСсылка) Тогда
Возврат;
КонецЕсли;
МетаданныеДокумента = ДокСсылка.Метаданные();
// Обход пользовательских реквизитов
Для Каждого ОписаниеРеквизита Из МетаданныеДокумента.Реквизиты Цикл
ИмяРеквизита = ОписаниеРеквизита.Имя;
ЗначениеРеквизита = ДокСсылка[ИмяРеквизита];
Сообщить(СтрШаблон("Реквизит: %1, Значение: %2", ИмяРеквизита, ЗначениеРеквизита));
КонецЦикла;
КонецПроцедуры
Важное замечание: коллекция Метаданные().Реквизиты содержит только дополнительные реквизиты, созданные разработчиком. Общесистемные поля, такие как Номер, Дата, Проведен или ПометкаУдаления, хранятся в отдельной коллекции МетаданныеДокумента.СтандартныеРеквизиты. Если требуется полный список реквизитов, необходимо дополнительно обойти и эту коллекцию.
Также помните о производительности: прямое обращение вида ДокСсылка[ИмяРеквизита] в цикле порождает серию отдельных обращений к базе данных. Если реквизитов много, эффективнее прочитать данные одним запросом к таблице документа либо получить объект в оперативную память через ДокСсылка.ПолучитьОбъект().
Когда коллекция реквизитов получена, требуется вывести их на управляемую форму. В управляемом интерфейсе 1С данные формы отделены от интерфейсных элементов. Процесс создания полей состоит из двух связанных действий:
Новый РеквизитФормы() и применение изменений методом ИзменитьРеквизиты().ЭтаФорма.Элементы.Добавить() и привязка их свойства ПутьКДанным к созданным реквизитам.При добавлении элементов формы критически важно гарантировать уникальность их имен, иначе платформа сгенерирует исключение. Посмотрим на реализацию процедуры генерации полей:
&НаСервере
Процедура СоздатьДинамическиеПоля(МассивНовыхРеквизитов, СтруктураЗначений)
// Применяем добавление реквизитов в структуру формы
ЭтаФорма.ИзменитьРеквизиты(МассивНовыхРеквизитов);
Счетчик = 0;
Для Каждого РеквизитФормы Из МассивНовыхРеквизитов Цикл
Счетчик = Счетчик + 1;
ИмяЭлемента = "ПолеДинамическое_" + РеквизитФормы.Имя + "_" + Счетчик;
// Создаем поле ввода в корне формы или в предназначенной группе
Элемент = ЭтаФорма.Элементы.Добавить(ИмяЭлемента, Тип("ПолеФормы"), ЭтаФорма);
Элемент.Вид = ВидПоляФормы.ПолеВвода;
Элемент.ПутьКДанным = РеквизитФормы.Имя;
// Заполняем значение реквизита на форме
ЭтаФорма[РеквизитФормы.Имя] = СтруктураЗначений[РеквизитФормы.Имя];
КонецЦикла;
КонецПроцедуры
Выясним причину частой ошибки: при повторном выборе документа и попытке удалить старые реквизиты разработчики сталкиваются с ошибкой «Не может найти путь к данным». Это происходит из-за нарушения порядка очистки объектов:
ПутьКДанным.ИзменитьРеквизиты(): первый параметр принимает массив объектов РеквизитФормы (добавляемые данные), а второй параметр должен содержать исключительно массив строк с именами реквизитов (удаляемые данные). Попытка передать во второй параметр объекты или ссылки вызовет аварийное завершение.ИзменитьРеквизиты() разрешено исключительно те реквизиты, которые были созданы программно в текущем сеансе работы формы.Проанализируем правильный алгоритм пересоздания полей на форме:
&НаСервере
Процедура ОчиститьДинамическиеЭлементыИРеквизиты()
// 1. Сначала удаляем элементы интерфейса
МассивДляУдаленияРеквизитов = Новый Массив;
// Имена созданных реквизитов рекомендуется сохранять, например, в реквизите формы "СписокСозданныхРеквизитов"
Для Каждого ИмяРеквизита Из СписокСозданныхРеквизитов Цикл
// Находим и удаляем элемент формы
ИмяЭлемента = "ПолеДинамическое_" + ИмяРеквизита;
ЭлементФормы = ЭтаФорма.Элементы.Найти(ИмяЭлемента);
Если ЭлементФормы <> Неопределено Тогда
ЭтаФорма.Элементы.Удалить(ЭлементФормы);
КонецЕсли;
// Добавляем строковое имя для удаления из структуры данных
МассивДляУдаленияРеквизитов.Добавить(ИмяРеквизита);
КонецЦикла;
// 2. Удаляем сами реквизиты формы (передаем пустой массив добавляемых и массив имен удаляемых)
Если МассивДляУдаленияРеквизитов.Количество() > 0 Тогда
ЭтаФорма.ИзменитьРеквизиты(Новый Массив, МассивДляУдаленияРеквизитов);
СписокСозданныхРеквизитов.Очистить();
КонецЕсли;
КонецПроцедуры
Метод ИзменитьРеквизиты() является ресурсоемким: он полностью перестраивает контекст формы на сервере и передает измененную схему на клиент. При частом перевыборе документов пользователем интерфейс может работать с заметной задержкой.
В большинстве прикладных задач намного проще и надежнее не перестраивать элементы управления, а вывести на форму стандартный элемент Таблица, связанный с реквизитом формы типа ТаблицаЗначений. В таблице создаются фиксированные колонки: «Имя реквизита», «Синоним» и «Значение». При изменении документа таблица просто очищается и повторно заполняется за один проход метаданных, исключая любые ошибки динамических путей к данным.