При разработке документов в среде «1С:Предприятие» часто возникает задача автоматического заполнения связанных полей в строке табличной части. Например, при выборе конкретного оборудования (принтера) в строке должны автоматически заполниться поля его владельца (контрагента, подразделения) и сопутствующие технические характеристики (например, срок эксплуатации).
В этой статье мы подробно разберем, как правильно реализовать такой функционал на управляемых формах. Мы проанализируем различные подходы — от самого простого и быстрого решения «в лоб» до профессиональной реализации с использованием универсальных запросов и механизмов Библиотеки стандартных подсистем (БСП). Также мы выясним, почему обращение к реквизитам «через точку» на клиенте крайне нежелательно и как обойти ограничение платформы на использование точек в ключах структуры.
Рассмотрим ситуацию, когда в самописной конфигурации необходимо при изменении реквизита Принтер в табличной части ТехОбс автоматически подтянуть связанные данные: картридж (который является владельцем принтера в структуре справочников) и числовой реквизит владельца — СрокЭксплуатации.
Для реализации этой логики на клиенте в модуле управляемой формы создадим обработчик события ПриИзменении для соответствующего поля табличной части. Нам потребуется передать выбранную ссылку на сервер, получить там структуру со связанными данными и заполнить текущую строку табличной части.
Код обработчика на клиенте будет выглядеть следующим образом:
&НаКлиенте
Процедура ТехОбсПринтерПриИзменении(Элемент)
// Получаем текущую строку табличной части
СтрокаТабличнойЧасти = Элементы.ТехОбс.ТекущиеДанные;
Если СтрокаТабличнойЧасти = Неопределено Тогда
Возврат;
КонецЕсли;
// Если принтер не выбран, очищаем зависимые поля
Если Не ЗначениеЗаполнено(СтрокаТабличнойЧасти.Принтер) Тогда
СтрокаТабличнойЧасти.Картридж = ПредопределенноеЗначение("Справочник.Картриджи.ПустаяСсылка");
СтрокаТабличнойЧасти.СрокЭксплуатации = 0;
Возврат;
КонецЕсли;
// Обращаемся к серверному общему модулю за данными
Реквизиты = ОбщийМодульСервер.ПолучитьРеквизитыПринтера(СтрокаТабличнойЧасти.Принтер);
// Заполняем поля строки полученными значениями
СтрокаТабличнойЧасти.Картридж = Реквизиты.Картридж;
СтрокаТабличнойЧасти.СрокЭксплуатации = Реквизиты.СрокЭксплуатации;
КонецПроцедуры
На стороне сервера (в общем модуле с установленными свойствами «Сервер» и «Вызов сервера») мы создаем функцию, которая считывает необходимые данные. Самый простой и очевидный (но, как мы увидим далее, не самый оптимальный) вариант выглядит так:
Функция ПолучитьРеквизитыПринтера(Знач СсылкаПринтер) Экспорт
Результат = Новый Структура;
Результат.Вставить("Принтер", СсылкаПринтер);
// Прямое обращение через точку к реквизитам ссылки
Результат.Вставить("Картридж", СсылкаПринтер.Владелец);
Результат.Вставить("СрокЭксплуатации", СсылкаПринтер.Владелец.СрокЭксплуатации);
Возврат Результат;
КонецФункции
Почему этот подход плох с точки зрения производительности? Когда мы пишем в коде СсылкаПринтер.Владелец.СрокЭксплуатации, платформа 1С на уровне СУБД выполняет неявные запросы. При первом обращении «через точку» платформа полностью считывает весь объект (в данном случае элемент справочника Принтеры) из базы данных в кэш со всеми его реквизитами и табличными частями. Когда мы переходим ко второй точке (Владелец.СрокЭксплуатации), платформа считывает уже весь объект Владельца. Это приводит к избыточному расходу оперативной памяти сервера приложений и создает лишнюю нагрузку на базу данных.
Чтобы избежать неявного получения лишних данных и кэширования тяжелых объектов, правилом хорошего тона в разработке на платформе 1С является использование точечных запросов к СУБД. Напишем универсальную функцию, которая будет динамически строить запрос к метаданным объекта и возвращать только запрашиваемые поля.
Посмотрим на пример такой универсальной функции в общем модуле:
Функция ПолучитьЗначенияРеквизитов(Знач Ссылка, Знач ИменаРеквизитов) Экспорт
Результат = Новый Структура;
// Если ссылка не заполнена, возвращаем пустую структуру
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат Результат;
КонецЕсли;
Запрос = Новый Запрос;
// Формируем текст запроса, обращаясь к таблице класса объекта метаданных
Запрос.Текст =
"ВЫБРАТЬ
| " + ИменаРеквизитов + "
|ИЗ
| " + Ссылка.Метаданные().ПолноеИмя() + " КАК Таблица
|ГДЕ
| Таблица.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
// Создаем структуру с ключами, соответствующими списку реквизитов
Результат = Новый Структура(ИменаРеквизитов);
ЗаполнитьЗначенияСвойств(Результат, Выборка);
КонецЕсли;
Возврат Результат;
КонецФункции
Важная техническая проблема: Если мы попытаемся передать в эту функцию параметр ИменаРеквизитов = "Владелец, Владелец.СрокЭксплуатации", то платформа выдаст синтаксическую ошибку при выполнении строки Результат = Новый Структура(ИменаРеквизитов).
Выясним причину: конструктор объекта Структура принимает в качестве ключей только строки, соответствующие правилам образования идентификаторов в 1С. Имя ключа не может содержать пробелов, спецсимволов и, самое главное, точек. Символ "." в имени ключа недопустим.
Как решить эту проблему? Нам необходимо преобразовать имена полей «через точку» в валидные имена ключей (например, заменяя точки на пустые строки или символы подчеркивания), как в самом тексте запроса (используя синонимы полей КАК), так и в структуре-результате.
Если вы ведете разработку в конфигурации, созданной на базе БСП (Библиотека стандартных подсистем), вам не нужно изобретать велосипед и писать собственные парсеры строк. В БСП эта задача уже решена максимально эффективно и изящно.
В общем модуле ОбщегоНазначения присутствуют готовые функции:
ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита) — идеален для получения одного значения.ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, Реквизиты) — возвращает структуру для нескольких реквизитов одного объекта.ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивСсылок, Реквизиты) — оптимальный метод для групповой обработки (например, при заполнении всей табличной части разом), выполняющий всего один запрос к базе данных.Проанализируем, как решить проблему путей через точку с помощью стандартной функции БСП двумя способами.
Если мы передадим в функцию БСП строку реквизитов, разделенных запятыми, содержащую точки, функция автоматически удалит точки при формировании ключей результирующей структуры.
// Передаем строку с точкой
ИменаПолей = "Владелец, Владелец.СрокЭксплуатации";
Результат = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СТБ.Принтер, ИменаПолей);
// На выходе БСП вернет структуру с ключами:
// Результат.Владелец
// Результат.ВладелецСрокЭксплуатации (точка была автоматически удалена)
СТБ.Картридж = Результат.Владелец;
СТБ.СрокЭксплуатации = Результат.ВладелецСрокЭксплуатации;
Самым гибким, надежным и читаемым вариантом является передача в качестве второго параметра функции БСП не строки, а Структуры-карты. При таком подходе:
Рассмотрим пример реализации автозаполнения строки табличной части с применением этого метода:
&НаКлиенте
Процедура ТехОбсПринтерПриИзменении(Элемент)
СТБ = Элементы.ТехОбс.ТекущиеДанные;
Если СТБ = Неопределено Тогда
Возврат;
КонецЕсли;
Если Не ЗначениеЗаполнено(СТБ.Принтер) Тогда
СТБ.Картридж = ПредопределенноеЗначение("Справочник.Картриджи.ПустаяСсылка");
СТБ.СрокЭксплуатации = 0;
Возврат;
КонецЕсли;
// Формируем структуру-карту для запроса к базе данных
КартаПолей = Новый Структура;
КартаПолей.Вставить("Картридж", "Владелец"); // Свойство "Картридж" получит значение из поля "Владелец"
КартаПолей.Вставить("СрокЭксплуатации", "Владелец.СрокЭксплуатации"); // Свойство "СрокЭксплуатации" получит значение через точку
// Вызываем функцию БСП (выполняется на сервере)
Результат = ОбщегоНазначенияВызовСервера.ЗначенияРеквизитовОбъекта(СТБ.Принтер, КартаПолей);
// Заполняем реквизиты строки ТЧ из структуры с красивыми и понятными ключами
СТБ.Картридж = Результат.Картридж;
СТБ.СрокЭксплуатации = Результат.СрокЭксплуатации;
КонецПроцедуры
Этот способ полностью исключает путаницу с именами ключей, делает код документированным и защищает программу от потенциальных ошибок при изменении структуры метаданных конфигурации.
При работе в управляемом интерфейсе важно следить за тем, какой объем данных циркулирует между клиентским приложением и сервером. Процедура ТехОбсПринтерПриИзменении выполняется на клиенте. Чтобы получить реквизиты, нам в любом случае требуется серверный контекст СУБД.
Правило оптимизации: Избегайте использования стандартных серверных процедур формы (с директивой &НаСервере) для получения простых справочных данных. При контекстном вызове &НаСервере платформа полностью передает на сервер все данные формы (включая всю табличную часть документа, реквизиты шапки и состояние элементов управления), производит сериализацию и десериализацию данных, а затем возвращает весь этот объем обратно на клиент.
Для минимизации трафика применяйте один из двух подходов:
ОбщегоНазначенияВызовСервера). Общие модули не несут в себе контекста формы, передается только ссылка, а возвращается только маленькая структура с результатом.&НаСервереБезКонтекста.Применяя данные стандарты разработки, вы обеспечите быструю и плавную работу интерфейса вашей программы даже на слабых каналах связи и при большом количестве одновременно работающих пользователей.