При разработке сложных аналитических интерфейсов часто возникает задача отображения связанных списков в режиме «мастер — деталь»: верхняя таблица показывает свернутые агрегированные данные, а нижняя должна динамически отображать детальные строки, соответствующие выбранным позициям в верхней таблице. Если в верхней таблице пользователь выделяет сразу несколько строк через Shift или Ctrl, стандартный линейный перебор строк на клиенте начинает вызывать заметные задержки интерфейса.
Разберем причины падения производительности, ограничения стандартных механизмов платформы 1С и эффективные способы решения этой задачи.
Рассмотрим типовую ошибку при реализации такой связи. Данные исходной большой таблицы (например, 1000 строк) хранятся в реквизите формы, а в событии ПриАктивизацииСтроки свернутой таблицы запускается цикл вида «для каждой выделенной строки проверить все строки исходной таблицы». При таком подходе вычислительная сложность алгоритма составляет O(N * M), где N — общее количество строк, а M — количество выделенных строк.
Ситуация усугубляется следующими факторами:
ПриАктивизацииСтроки вызывается при каждом перемещении курсора, смене фокуса и при выделении диапазона строк. Расчет запускается многократно еще до того, как пользователь закончил выбор.ДанныеФормыКоллекция. Обращение к свойствам элементов этой коллекции на стороне клиента выполняется медленнее, чем аналогичные операции с объектом ТаблицаЗначений на сервере.Попытка использовать стандартное свойство элемента формы ОтборСтрок для подчиненной таблицы сталкивается с ограничением платформы: свойство принимает только тип ФиксированнаяСтруктура. Это позволяет устанавливать отбор исключительно по жесткому равенству полей по схеме «И» (например, Поле1 = Значение1 И Поле2 = Значение2). Задать условие типа «ИЛИ» или использовать оператор «В Списке» через ОтборСтрок невозможно, поэтому для нескольких выделенных строк данный механизм не подходит.
Проанализируем способ снижения вычислительной сложности алгоритма на клиенте с квадратичной O(N * M) до линейной O(N + M) с помощью объекта Соответствие.
Разберем по шагам реализацию этого механизма:
КлючСвязи, объединяющую значения группировочных колонок через разделитель.Соответствие, где ключом выступает составная строка, а значением — Истина.Посмотрим на пример клиентского кода:
&НаКлиенте
Процедура ТаблицаШапкаПриАктивизацииСтроки(Элемент)
// Собираем ключи выделенных строк в коллекцию Соответствие
СоответствиеКлючей = Новый Соответствие;
Для Каждого ИдентСтроки Из Элементы.ТаблицаШапка.ВыделенныеСтроки Цикл
ТекСтрока = ТаблицаШапка.НайтиПоИдентификатору(ИдентСтроки);
Если ТекСтрока <> Неопределено Тогда
Ключ = XMLСтрока(ТекСтрока.Ссылка1) + "_" + XMLСтрока(ТекСтрока.Ссылка2);
СоответствиеКлючей.Вставить(Ключ, Истина);
КонецЕсли;
КонецЦикла;
// Перезаполняем подчиненную таблицу детализации
ТаблицаДетализация.Очистить();
Для Каждого СтрИсходная Из ТаблицаИсходная Цикл
Если СоответствиеКлючей[СтрИсходная.КлючСвязи] <> Неопределено Тогда
НоваяСтр = ТаблицаДетализация.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтр, СтрИсходная);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если детальная таблица содержит десятки тысяч строк, операции по ее фильтрации эффективнее перенести на сторону сервера с использованием механизма индексации объекта ТаблицаЗначений.
Выясним, как работает этот подход:
Индексы.Добавить().НайтиСтроки(), передавая структуру отбора. При наличии индекса платформа 1С осуществляет бинарный поиск мгновенно.Посмотрим на серверную реализацию:
&НаСервере
Процедура ОбновитьДетализациюНаСервере(МассивВыделенныхКлючей)
// ТаблицаЗначенийХранилище предварительно проиндексирована:
// ТаблицаЗначенийХранилище.Индексы.Добавить("Ссылка1, Ссылка2");
ТаблицаРезультат = ТаблицаЗначенийХранилище.СкопироватьКолонки();
Для Каждого КлючПара Из МассивВыделенныхКлючей Цикл
Отбор = Новый Структура("Ссылка1, Ссылка2", КлючПара.Ссылка1, КлючПара.Ссылка2);
НайденныеСтроки = ТаблицаЗначенийХранилище.НайтиСтроки(Отбор);
Для Каждого СтрНайдено Из НайденныеСтроки Цикл
ЗаполнитьЗначенияСвойств(ТаблицаРезультат.Добавить(), СтрНайдено);
КонецЦикла;
КонецЦикла;
ЗначениеВРеквизитФормы(ТаблицаРезультат, "ТаблицаДетализация");
КонецПроцедуры
Рассмотрим альтернативный архитектурный вариант, который полностью снимает проблему синхронизации двух независимых таблиц элементов формы: вывод данных в один элемент с типом ДеревоЗначений.
Разберем преимущества этого подхода:
Ссылка1, Ссылка2 на верхнем уровне и детальными записями по Ссылка3 на подчиненном уровне.Для исключения эффекта «залипания» формы при быстром перемещении стрелками клавиатуры по строкам таблицы рекомендуется использовать отложенный вызов пересчета (паттерн Debounce).
В обработчике ПриАктивизацииСтроки вызывается метод ПодключитьОбработчикОжидания("ОбновитьДетализацию", 0.1, Истина) с параметром однократного вызова. Если пользователь продолжает перемещаться по списку, предыдущий таймер сбрасывается и переподключается, а расчет запускается только после остановки курсора.