Как реализовать автозаполнение реквизитов табличной части при выборе элемента в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке документов в среде «1С:Предприятие» часто возникает задача автоматического заполнения связанных полей в строке табличной части. Например, при выборе конкретного оборудования (принтера) в строке должны автоматически заполниться поля его владельца (контрагента, подразделения) и сопутствующие технические характеристики (например, срок эксплуатации).

В этой статье мы подробно разберем, как правильно реализовать такой функционал на управляемых формах. Мы проанализируем различные подходы — от самого простого и быстрого решения «в лоб» до профессиональной реализации с использованием универсальных запросов и механизмов Библиотеки стандартных подсистем (БСП). Также мы выясним, почему обращение к реквизитам «через точку» на клиенте крайне нежелательно и как обойти ограничение платформы на использование точек в ключах структуры.

Подход 1. Простое решение через специализированную функцию общего модуля

Рассмотрим ситуацию, когда в самописной конфигурации необходимо при изменении реквизита Принтер в табличной части ТехОбс автоматически подтянуть связанные данные: картридж (который является владельцем принтера в структуре справочников) и числовой реквизит владельца — СрокЭксплуатации.

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

Код обработчика на клиенте будет выглядеть следующим образом:


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

На стороне сервера (в общем модуле с установленными свойствами «Сервер» и «Вызов сервера») мы создаем функцию, которая считывает необходимые данные. Самый простой и очевидный (но, как мы увидим далее, не самый оптимальный) вариант выглядит так:


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

Почему этот подход плох с точки зрения производительности? Когда мы пишем в коде СсылкаПринтер.Владелец.СрокЭксплуатации, платформа 1С на уровне СУБД выполняет неявные запросы. При первом обращении «через точку» платформа полностью считывает весь объект (в данном случае элемент справочника Принтеры) из базы данных в кэш со всеми его реквизитами и табличными частями. Когда мы переходим ко второй точке (Владелец.СрокЭксплуатации), платформа считывает уже весь объект Владельца. Это приводит к избыточному расходу оперативной памяти сервера приложений и создает лишнюю нагрузку на базу данных.

Подход 2. Универсальная функция на базе облегченных запросов

Чтобы избежать неявного получения лишних данных и кэширования тяжелых объектов, правилом хорошего тона в разработке на платформе 1С является использование точечных запросов к СУБД. Напишем универсальную функцию, которая будет динамически строить запрос к метаданным объекта и возвращать только запрашиваемые поля.

Посмотрим на пример такой универсальной функции в общем модуле:


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

Важная техническая проблема: Если мы попытаемся передать в эту функцию параметр ИменаРеквизитов = "Владелец, Владелец.СрокЭксплуатации", то платформа выдаст синтаксическую ошибку при выполнении строки Результат = Новый Структура(ИменаРеквизитов).

Выясним причину: конструктор объекта Структура принимает в качестве ключей только строки, соответствующие правилам образования идентификаторов в 1С. Имя ключа не может содержать пробелов, спецсимволов и, самое главное, точек. Символ "." в имени ключа недопустим.

Как решить эту проблему? Нам необходимо преобразовать имена полей «через точку» в валидные имена ключей (например, заменяя точки на пустые строки или символы подчеркивания), как в самом тексте запроса (используя синонимы полей КАК), так и в структуре-результате.

Подход 3. Использование механизмов БСП (модуль «ОбщегоНазначения»)

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

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

  1. ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита) — идеален для получения одного значения.
  2. ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, Реквизиты) — возвращает структуру для нескольких реквизитов одного объекта.
  3. ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивСсылок, Реквизиты) — оптимальный метод для групповой обработки (например, при заполнении всей табличной части разом), выполняющий всего один запрос к базе данных.

Проанализируем, как решить проблему путей через точку с помощью стандартной функции БСП двумя способами.

Способ А. Автоматическое исключение точек

Если мы передадим в функцию БСП строку реквизитов, разделенных запятыми, содержащую точки, функция автоматически удалит точки при формировании ключей результирующей структуры.


// Передаем строку с точкой
ИменаПолей = "Владелец, Владелец.СрокЭксплуатации";
Результат = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СТБ.Принтер, ИменаПолей);

// На выходе БСП вернет структуру с ключами:
// Результат.Владелец
// Результат.ВладелецСрокЭксплуатации (точка была автоматически удалена)
СТБ.Картридж = Результат.Владелец;
СТБ.СрокЭксплуатации = Результат.ВладелецСрокЭксплуатации;

Способ Б. Использование структуры-карты (рекомендуемый вариант)

Самым гибким, надежным и читаемым вариантом является передача в качестве второго параметра функции БСП не строки, а Структуры-карты. При таком подходе:

Рассмотрим пример реализации автозаполнения строки табличной части с применением этого метода:


&НаКлиенте
Процедура ТехОбсПринтерПриИзменении(Элемент)
    
    СТБ = Элементы.ТехОбс.ТекущиеДанные;
    Если СТБ = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    Если Не ЗначениеЗаполнено(СТБ.Принтер) Тогда
        СТБ.Картридж = ПредопределенноеЗначение("Справочник.Картриджи.ПустаяСсылка");
        СТБ.СрокЭксплуатации = 0;
        Возврат;
    КонецЕсли;
    
    // Формируем структуру-карту для запроса к базе данных
    КартаПолей = Новый Структура;
    КартаПолей.Вставить("Картридж", "Владелец"); // Свойство "Картридж" получит значение из поля "Владелец"
    КартаПолей.Вставить("СрокЭксплуатации", "Владелец.СрокЭксплуатации"); // Свойство "СрокЭксплуатации" получит значение через точку
    
    // Вызываем функцию БСП (выполняется на сервере)
    Результат = ОбщегоНазначенияВызовСервера.ЗначенияРеквизитовОбъекта(СТБ.Принтер, КартаПолей);
    
    // Заполняем реквизиты строки ТЧ из структуры с красивыми и понятными ключами
    СТБ.Картридж = Результат.Картридж;
    СТБ.СрокЭксплуатации = Результат.СрокЭксплуатации;
    
КонецПроцедуры

Этот способ полностью исключает путаницу с именами ключей, делает код документированным и защищает программу от потенциальных ошибок при изменении структуры метаданных конфигурации.

Оптимизация клиент-серверного взаимодействия

При работе в управляемом интерфейсе важно следить за тем, какой объем данных циркулирует между клиентским приложением и сервером. Процедура ТехОбсПринтерПриИзменении выполняется на клиенте. Чтобы получить реквизиты, нам в любом случае требуется серверный контекст СУБД.

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

Для минимизации трафика применяйте один из двух подходов:

  1. Используйте вызовы функций из **серверных общих модулей** (как в нашем примере с БСП: ОбщегоНазначенияВызовСервера). Общие модули не несут в себе контекста формы, передается только ссылка, а возвращается только маленькая структура с результатом.
  2. Если общего модуля нет, объявляйте в модуле формы легковесные бесконтекстные серверные функции с директивой &НаСервереБезКонтекста.

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

← На главную