В процессе разработки интерфейсов на платформе «1С:Предприятие 8.3» часто возникает задача организации связанных списков по принципу «главный — подчиненный» (Master-Detail). Типичный пример такой задачи — отображение списка номенклатуры в одной таблице, а перечня её дополнительных реквизитов и их конкретных значений — во второй таблице на той же форме. При этом состав и значения реквизитов должны динамически обновляться при перемещении курсора по списку товаров.
В рамках этой статьи мы подробно проанализируем архитектурные ошибки, которые часто допускают разработчики при попытке решить эту задачу, а также разберем по шагам три надежных способа реализации этой функциональности с примерами кода и оптимизацией производительности.
Прежде чем перейти к рабочим алгоритмам, давайте разберем, почему простые на первый взгляд решения из обсуждений на форумах часто оказываются неработоспособными или приводят к критическому снижению производительности системы.
Ошибка 1. Попытка работы с базой данных на клиенте. Часто начинающие разработчики пытаются обратиться к менеджерам объектов или планам видов характеристик непосредственно в клиентских процедурах:
&НаКлиенте
Процедура ВывестиДопРеквизиты()
ТекущаяСтрока = Элементы.ТаблицаНоменклатуры.ТекущиеДанные;
// Ошибка! ПВХ недоступен на клиенте
ДополнительныеРеквизиты = ПланыВидовХарактеристик.Получить(ТекущаяСтрока.Номенклатура.Категория);
КонецПроцедуры
Такой код вызовет ошибку компиляции на клиенте. Любое обращение к данным информационной базы, чтение реквизитов через точку от ссылочных типов (кроме реквизитов, уже находящихся в данных формы) должно выполняться строго на стороне сервера.
Ошибка 2. Использование no-code связывания через категории. Предложение связать путь к данным подчиненной таблицы напрямую через свойства категории (например, указав путь вида Элементы.Таблица1.ТекущиеДанные.Номенклатура.Категория.ДополнительныеРеквизиты) содержит две серьезные проблемы:
---
Рассмотрим универсальный способ, который будет стабильно работать в любой конфигурации 1С. Суть метода заключается в том, что при активизации строки номенклатуры мы передаем ссылку на выбранный товар на сервер, выполняем запрос к его табличной части дополнительных реквизитов и загружаем результат в реквизит формы (ТаблицуЗначений).
Для реализации этого способа выполним следующие шаги:
ТаблицаЗначений (назовем его ТаблицаСвойств).Свойство (тип ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения) и Значение (тип Характеристика.ДополнительныеРеквизитыИСведения).Теперь напишем обработчики событий в модуле управляемой формы:
&НаКлиенте
Процедура СписокНоменклатурыПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
// Если строка пустая или список очищен, очищаем и подчиненную таблицу
Объект.ТаблицаСвойств.Очистить();
Возврат;
КонецЕсли;
// Вызываем серверный метод для получения актуальных данных
ЗаполнитьДопРеквизитыНаСервере(ТекущиеДанные.Номенклатура);
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьДопРеквизитыНаСервере(ВыбраннаяНоменклатура)
// Очищаем предыдущие данные в таблице формы
Объект.ТаблицаСвойств.Очистить();
// Формируем запрос к табличной части выбранного элемента номенклатуры
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураДополнительныеРеквизиты.Свойство КАК Свойство,
| НоменклатураДополнительныеРеквизиты.Значение КАК Значение
|ИЗ
| Справочник.Номенклатура.ДополнительныеРеквизиты КАК НоменклатураДополнительныеРеквизиты
|ГДЕ
| НоменклатураДополнительныеРеквизиты.Ссылка = &Номенклатура";
Запрос.УстановитьПараметр("Номенклатура", ВыбраннаяНоменклатура);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
// Быстро выгружаем результат запроса напрямую в объект данных формы
Объект.TableСвойств.Загрузить(РезультатЗапроса.Выгрузить());
КонецЕсли;
КонецПроцедуры
Этот способ работает максимально прозрачно и производительно за счет использования метода Загрузить(), который мгновенно заполняет таблицу на форме без построчного обхода коллекции.
---
Если ваша внешняя обработка или форма разрабатывается для типовой конфигурации (например, 1С:ERP, Управление торговлей 11, Комплексная автоматизация 2, УНФ), то изобретать велосипед с написанием прямых SQL-подобных запросов не обязательно. В таких системах развернута БСП (Библиотека стандартных подсистем), в которой есть готовый программный интерфейс для работы со свойствами.
Выясним причину, почему это выгодно: механизмы БСП автоматически учитывают иерархию наборов свойств, общие реквизиты, а также корректно обрабатывают типы значений характеристик, исключая необходимость ручной настройки связей параметров выбора.
Заменим серверный метод из предыдущего примера на интеграционный вызов функции БСП:
&НаСервере
Процедура ЗаполнитьДопРеквизитыЧерезБСП(ВыбраннаяНоменклатура)
Объект.ТаблицаСвойств.Очистить();
// Используем функцию общего модуля БСП для получения таблицы заполненных свойств
ТаблицаЗначенийСвойств = УправлениеСвойствами.ПолучитьЗначенияСвойств(ВыбраннаяНоменклатура);
// Переносим полученные данные в реквизит формы
Для Каждого СтрокаСвойства Из ТаблицаЗначенийСвойств Цикл
НоваяСтрока = Объект.ТаблицаСвойств.Добавить();
НоваяСтрока.Свойство = СтрокаСвойства.Свойство;
НоваяСтрока.Значение = СтрокаСвойства.Значение;
КонецЦикла;
КонецПроцедуры
Применение метода УправлениеСвойствами.ПолучитьЗначенияСвойств() гарантирует, что ваша обработка будет вести себя стандартно по отношению к остальным объектам конфигурации.
---
Проанализируем сценарий работы пользователя: он открывает список номенклатуры и зажимает клавишу "Вниз" на клавиатуре, чтобы быстро пролистать справочник в поисках нужной позиции.
Событие ПриАктивизацииСтроки срабатывает на каждый шаг перемещения курсора. Если в обработчике этого события прописан прямой серверный вызов (как в Способе 1 и 2), программа пошлет десятки запросов к серверу за одну секунду. База данных не будет успевать отдавать ответы, интерфейс у пользователя начнет дергаться, а сервер приложений получит паразитную нагрузку.
Чтобы решить эту проблему, реализуем механизм задержки вызова сервера с использованием обработчика ожидания. Разберем по шагам, как это работает:
Посмотрим на пример реализации такого паттерна:
&НаКлиенте
Процедура СписокНоменклатурыПриАктивизацииСтроки(Элемент)
// Шаг 1. Отключаем ранее запущенный обработчик ожидания, если он был активен
ОтключитьОбработчикОжидания("Подключаемый_ЗаполнитьДопРеквизиты");
// Шаг 2. Подключаем обработчик ожидания заново с минимальным интервалом времени (0.3 сек)
// Параметр "Однократно" устанавливаем в Истина, чтобы процедура не зацикливалась
ПодключитьОбработчикОжидания("Подключаемый_ЗаполнитьДопРеквизиты", 0.3, Истина);
КонецПроцедуры
&НаКлиенте
Процедура Подключаемый_ЗаполнитьДопРеквизиты()
// Шаг 3. Получаем текущие данные из таблицы формы
ТекущиеДанные = Элементы.СписокНоменклатуры.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
// Только в этот момент происходит реальное обращение к серверу
ЗаполнитьДопРеквизитыНаСервере(ТекущиеДанные.Номенклатура);
Иначе
Объект.ТаблицаСвойств.Очистить();
КонецЕсли;
КонецПроцедуры
Использование этого несложного приема позволяет кардинально повысить отзывчивость интерфейса вашей обработки. Пользователь сможет быстро скроллить список товаров без малейших задержек в отображении, а нагрузка на сервер 1С снизится в несколько раз.
Выбирайте наиболее подходящий для вашей конфигурации метод (прямой запрос для кастомных структур или вызовы БСП для типовых решений) и обязательно применяйте оптимизацию через обработчик ожидания для обеспечения максимального комфорта работы пользователей.