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