Доброго дня.
Возникла задача загрузки табелей из файлов ексель в ЗУП и захотелось мне красоты навести, фильтры там всякие, так эта статья и появилась.
Некоторые задумки подобрал в этой статье Фильтр в отчете, табличном документе на основе фильтра Excel
Файл читаем в реквизит формы ТаблДокументДляРазбора и инициализируем возможные значения отборов для фильтров, в нашем случае фильтра три - "Месяц", "Подразделение" и "ФизЛицо".
&НаСервере
Процедура ПрочитатьФайл(АдресФайла)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
ДвоичныеДанные.Записать(ИмяФайла);
ТаблДокументДляРазбора.Прочитать(ИмяФайла);
ТаблДокументДляРазбора.ФиксацияСлева = 9;
ОбластьПерваяСтрока = ТаблДокументДляРазбора.Область("R1:R4");
ОбластьПерваяСтрока.ВысотаСтроки = 15;
//Сброс фильтров по строкам перед открытием
СнятьФильтры(ТаблДокументДляРазбора);
АдресВХ = ПоместитьВоВременноеХранилище(ТаблДокументДляРазбора, ЭтаФорма.УникальныйИдентификатор);
//выгрузка в ТЗ
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблДокументДляРазбора.Область(5, // - первая строка - заголовки
НомерКолонкиПериод, // - номера колонок фильтров
ТаблДокументДляРазбора.ВысотаТаблицы,
НомерКолонкиПодразделение
));
// Инициализация отбора
ТабДанные = Построитель.Результат.Выгрузить();
текПолеОтбора = "УчастокПоШтатке";
ТабДанные.Свернуть(текПолеОтбора);
ТабДанные.Сортировать(текПолеОтбора + " Возр");
текМассивЗначений = табДанные.ВыгрузитьКолонку(текПолеОтбора);
Элементы.ОтборПодразделениеСтрока.СписокВыбора.ЗагрузитьЗначения(текМассивЗначений);
ТабДанные = Построитель.Результат.Выгрузить();
текПолеОтбора = "Фамилия_Имя_Отчество";
ТабДанные.Свернуть(текПолеОтбора);
ТабДанные.Сортировать(текПолеОтбора + " Возр");
текМассивЗначений = табДанные.ВыгрузитьКолонку(текПолеОтбора);
Элементы.ОтборСотрудникСтрока.СписокВыбора.ЗагрузитьЗначения(текМассивЗначений);
ТабДанные = Построитель.Результат.Выгрузить();
текПолеОтбора = "Месяц";
ТабДанные.Свернуть(текПолеОтбора);
ТабДанные.Сортировать(текПолеОтбора + " Возр");
текМассивЗначений = табДанные.ВыгрузитьКолонку(текПолеОтбора);
Элементы.ОтборПериодСтрока.СписокВыбора.ЗагрузитьЗначения(текМассивЗначений);
КонецПроцедуры
&НаСервере
Процедура СнятьФильтры(ТаблДокументДляРазбора)
ОбластьСтрока = ТаблДокументДляРазбора.Область(1,,ТаблДокументДляРазбора.ВысотаТаблицы);
ОбластьСтрока.Видимость = Истина;
КонецПроцедуры
Отборы инициализируем с помощью построителя запроса, для этого в него передаем, область табличного документа с данными на которые будет накладываться фильтр. Ограничим загружаемую в построитель область. Нужно помнить, что первая строка передаваемой области станет заголовками таблицы значений, которую вернет построитель. Поэтому у меня - это строка 5, последняя строка - ТаблДокументДляРазбораюВысотаТаблицы. По колонкам ограничим область номерами колонок наших фильтров. В данном коде самая первая колонка это "НомерКолонкиПериод" (6), последняя - "НомерКолонкиПодразделение"(10).
Выполняем Построитель, выгружаем данные в таблицу значений и формируем списки выбора для полей фильтров.
Потом прописываем обработчики для полей фильтров на форме. Для этого можно использовать событие "ПриИзменении". Либо события "ОбработкаВыбора" и "Очистка", в обработчике события "Очистка" я выключаю использование фильтра, для этого для каждого фильтра добавлен реквизит флОчисткаОтбораХХХ тип булево, где ХХХ - имя фильтра. Эти реквизиты инициализируются при создании формы.
Код обработчиков "ОбработкаВыбора", "Очистка" и функция применения фильтра представлена ниже.
&НаКлиенте
Процедура ОтборСотрудникСтрокаОбработкаВыбора(Элемент, ВыбранноеЗначение, ДополнительныеДанные, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ОтборСотрудникСтрока = ВыбранноеЗначение;
флОчисткаОтбораСотрудник = Ложь;
ПрименитьФильтр();
КонецПроцедуры
&НаКлиенте
Процедура ОтборСтрокаОчистка(Элемент, СтандартнаяОбработка)
Если Элемент.Имя = "ОтборПериодСтрока" Тогда
флОчисткаОтбораПериод = Истина;
ИначеЕсли Элемент.Имя = "ОтборПодразделениеСтрока" Тогда
флОчисткаОтбораПодразделение = Истина;
ИначеЕсли Элемент.Имя = "ОтборСотрудникСтрока" Тогда
флОчисткаОтбораСотрудник = Истина;
КонецЕсли;
ПрименитьФильтр();
КонецПроцедуры
&НаКлиенте
Процедура ПрименитьФильтр()
текОтборПериодСтрока = СокрЛП(ОтборПериодСтрока);
текОтборПодразделениеСтрока = СокрЛП(ОтборПодразделениеСтрока);
текОтборСотрудникСтрока = СокрЛП(ОтборСотрудникСтрока);
Для НомерСтроки = 6 По ТаблДокументДляРазбора.ВысотаТаблицы Цикл
текВидимость = Истина;
//Обработаем применение фильтров
//фильтр Период
текОбластьПериод = ТаблДокументДляРазбора.Область(НомерСтроки, НомерКолонкиПериод, НомерСтроки, НомерКолонкиПериод);
//фильтр Подразделение
текОбластьПодразделение = ТаблДокументДляРазбора.Область(НомерСтроки, НомерКолонкиПодразделение, НомерСтроки, НомерКолонкиПодразделение);
//фильтр Сотрудник
текОбластьСотрудник = ТаблДокументДляРазбора.Область(НомерСтроки, НомерКолонкиФИО, НомерСтроки, НомерКолонкиФИО);
Если Не флОчисткаОтбораПериод Тогда
текВидимость = текВидимость И (СтрСравнить(текОтборПериодСтрока, СокрЛП(текОбластьПериод.Текст)) = 0);
КонецЕсли;
Если Не флОчисткаОтбораПодразделение Тогда
текВидимость = текВидимость И (текОтборПодразделениеСтрока = СокрЛП(текОбластьПодразделение.Текст));
КонецЕсли;
Если Не флОчисткаОтбораСотрудник Тогда
текВидимость = текВидимость И (текОтборСотрудникСтрока = СокрЛП(текОбластьСотрудник.Текст));
КонецЕсли;
ОбластьСтрока = ТаблДокументДляРазбора.Область(НомерСтроки, , НомерСтроки);
ОбластьСтрока.Видимость = текВидимость;
КонецЦикла;
КонецПроцедуры
Вот, казалось бы, и все. Но тесты привели к странным результатам, первый выбранный фильтр применялся 28 секунд на файле в 35 тысяч строк, изменение фильтра отрабатывало примерно 2 секунды.
Результаты замера производительности ниже
Как видно из замера, основные потери при первом запуске происходят при сравнении текста ячейки табличного документ со значением фильтра.
Насколько я понимаю, там происходит неявный вызов сервера, данные кэшируются и потом фильтр отрабатывает быстро без обращения к серверу.
Чтобы избежать такой просадки производительности, было принято решение использовать фильтр на сервере без контекста. При первой реализации данной функции время запуска только увеличилось, т.к. приходилось передавать табличный документ с клиента на сервер через временное хранилище.
Если не использовать временное хранилище, а просто передавать Табличный документ параметром процедуры с директивой &НаКлиентеНаСервереБезКонтекста, то ускорения не происходит. Так как происходит все тот же неявный вызов сервера.
Чтобы каждый раз не передавать документ на сервер, был создан реквизит "АдресВХ" который инициализируется при чтении файла, т.о. у нас на сервере есть копия табличного документа.
С которым мы можем работать без контекста формы.
В итоге получили вот такую функцию. Функция возвращает массив номеров строк табличного документа, которые нужно показать.
&НаСервереБезКонтекста
Функция ПрименитьФильтрБК(АдресВХ, НомерКолонкиПериод, флОчисткаОтбораПериод, ОтборПериодСтрока, НомерКолонкиПодразделение, флОчисткаОтбораПодразделение, ОтборПодразделениеСтрока, НомерКолонкиФИО, флОчисткаОтбораСотрудник, ОтборСотрудникСтрока)
РезультатНомераОтображаемыхСтрок = Новый Массив;
времТД = ПолучитьИзВременногоХранилища(АдресВХ);
текОтборПериодСтрока = СокрЛП(ОтборПериодСтрока);
текОтборПодразделениеСтрока = СокрЛП(ОтборПодразделениеСтрока);
текОтборСотрудникСтрока = СокрЛП(ОтборСотрудникСтрока);
Для НомерСтроки = 6 По времТД.ВысотаТаблицы Цикл
текВидимость = Истина;
//Обработаем применение фильтров
//фильтр Период
текОбластьПериод = времТД.Область(НомерСтроки, НомерКолонкиПериод, НомерСтроки, НомерКолонкиПериод);
//фильтр Подразделение
текОбластьПодразделение = времТД.Область(НомерСтроки, НомерКолонкиПодразделение, НомерСтроки, НомерКолонкиПодразделение);
//фильтр Сотрудник
текОбластьСотрудник = времТД.Область(НомерСтроки, НомерКолонкиФИО, НомерСтроки, НомерКолонкиФИО);
Если Не флОчисткаОтбораПериод Тогда
текВидимость = текВидимость И (СтрСравнить(текОтборПериодСтрока, СокрЛП(текОбластьПериод.Текст)) = 0);
КонецЕсли;
Если Не флОчисткаОтбораПодразделение Тогда
текВидимость = текВидимость И (текОтборПодразделениеСтрока = СокрЛП(текОбластьПодразделение.Текст));
КонецЕсли;
Если Не флОчисткаОтбораСотрудник Тогда
текВидимость = текВидимость И (текОтборСотрудникСтрока = СокрЛП(текОбластьСотрудник.Текст));
КонецЕсли;
Если текВидимость Тогда
РезультатНомераОтображаемыхСтрок.Добавить(НомерСтроки);
КонецЕсли;
КонецЦикла;
Возврат РезультатНомераОтображаемыхСтрок;
КонецФункции
Ниже код обработчика выбора фильтра с вызовом функции ПрименитьФильтрБК()
&НаКлиенте
Процедура ОтборПериодСтрокаОбработкаВыбора(Элемент, ВыбранноеЗначение, ДополнительныеДанные, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ОтборПериодСтрока = ВыбранноеЗначение;
флОчисткаОтбораПериод = Ложь;
//ПрименитьФильтр();
МассивСтрокДляОтображения = ПрименитьФильтрБК(АдресВХ, НомерКолонкиПериод, флОчисткаОтбораПериод, ОтборПериодСтрока, НомерКолонкиПодразделение, флОчисткаОтбораПодразделение, ОтборПодразделениеСтрока, НомерКолонкиФИО, флОчисткаОтбораСотрудник, ОтборСотрудникСтрока);
ОбластьСтрока = ТаблДокументДляРазбора.Область(6,,ТаблДокументДляРазбора.ВысотаТаблицы);
ОбластьСтрока.Видимость = Ложь;
Для каждого текЭлементМассива Из МассивСтрокДляОтображения Цикл
ОбластьСтрока = ТаблДокументДляРазбора.Область(текЭлементМассива,,текЭлементМассива);
ОбластьСтрока.Видимость = Истина;
КонецЦикла;
КонецПроцедуры
Как видно, тут все просто: получаем массив строк для отображения с помощью функции ПрименитьФильтрБК, скрываем все строки с данными, а потом включаем видимость у нужных строк.
Скриншоты результатов замера, выводы делайте сами.
P.s. Данная статья не претендует на истину в последней инстанции и появилась как результат поисков причины просадки производительности при наложении фильтров на стороне клиента. Задача решалась в рамках другой более объемной задачи, просьба не сильно пинать за корявость кода. )