Часто ли вам приходилось писать обработчики фильтрации для ваших данных? Это могут быть массивы для отрисовки таблиц, карточек, списков — чего угодно.
Когда фильтрация статическая, то тут все просто. Стандартных функций map, filter и reduce вполне достаточно. Но что делать, если данные имеют сложную структуру или вложенность, да еще и правил для фильтра может быть достаточно много. Правила могут повторяться, данные изменяться, и чем больше контролов фильтра будет появляться, тем сложнее и неустойчивее будет код обработчика.
Как же решить проблему возрастающей сложности?
Я сам столкнулся с данной проблемой, разрабатывая приложение, которое работает с огромным количеством данных. Постепенно добавлялись все новые и новые фильтры.
Поначалу все шло хорошо, отдельные обработчики фильтров выполняли свои задачи отлично. Со временем стали появляться страницы с незначительными изменениями в уже готовых фильтрах. Код дублировать не хотелось, так что пришлось выделять особенные варианты фильтра и его обработчика. Типовая страница собиралась за час, но вот обработчики для фильтров писались в два раза дольше. Нужно было что-то делать.
В свободное время я пробовал найти библиотеки, которые могли бы помочь решить эту проблему.Самые значимые библиотеки, которые удалось найти:
- ng-table — отрисовывает таблицы и предоставляет возможность простой фильтрации и сортировки. Фильтрации у нас были намного сложнее.
- List.js — как ng-table, только намного меньше функционала.
- filter.js — близко к тому, что было нужно, но не хватало гибкости.
- Isotope — привязывается к DOM элементам. У нас же просто данные.
Нужно было разработать собственное решение, удовлетворяющее следующим требованиям:
- Декларативное описание работы фильтра.
- Переиспользуемые правила.
- Возможность написания своих правил фильтрации.
- Композиция правил как в обычных условиях, с помощью and и or операторов.
- Фильтрация вложенных элементов и групп. Вложенность не фиксированная.
- Возможность задать стратегию фильтрации (к примеру, мы хотим, чтобы, если группа совпала с фильтром, но не совпал ни один из её элементов, все равно вывелась группа со всеми элементами)
В итоге мы имеем библиотеку awesome-data-filter.
Данная библиотека, используя декларативный подход, позволяет составлять сложные правила обработки ваших данных.
Установка
Сначала поставим библиотеку и опробуем ее в действии.
Начнем с простого примера. Предположим, у нас есть следующий массив пользователей:
И объект со значениями фильтра:
Допустим, нужно найти пересечение этих правил.
Используемые правила:
- matchText — поиск подстроки в целевом поле;
- equalProp — полное совпадение значений параметров;
- betweenDates — проверяет вхождение определенной даты в диапазон;
- equalOneOf — хотя бы один из переданных элементов должен соответствовать переданному правилу;
- someInArray — хотя бы один из вложенных элементов объекта должен соответствовать переданному правилу;
- isEmptyArray — проверка на пустой массив;
- lessThen — значение меньше, чем;
- moreThen — значение больше, чем;
- not — функция отрицания возвращаемого функцией значения.
В наших примерах мы будем использовать только matchText и equalProp.
Для получения динамических значений:
- filterField — получение свойства фильтра;
- elementField — получение свойства текущего элемента списка.
Полученная функция filter принимает объект со значениями фильтра и фильтруемые данные в формате groups и elements.
Так как группы обрабатываются отдельно от элементов, они вынесены в отдельное поле. Внутри групп также могут находиться элементы.
В данном случае, так как у нас плоский список элементов, передаем только elements.
Если же заменим and на or, то получим объединение результатов работы 2х правил.
Благодаря функциям filterField, elementField мы можем динамически передавать параметры в созданные правила.
Так же есть функция constValue для передачи константных значений.Условия могут вкладываться друг в друга or(..., matchText, [and([..., matchText, ...]), or([..., ...])])
Также фильтр может работать со вложенными элементами и группами. Рассмотрим на примере ниже:
В таком случае можно передать в конфиг фильтра информацию об обходе данной структуры объекта в поле traversal:
До этого момента передавался только elementFilter параметр, который отвечает за правила фильтрации элементов. Также есть groupFilter для групп.
Группа с названием first group появилась в выборке, и так как фильтр не нашел совпадения по элементам, но нашел совпадение по группе, мы видим отображение всех вложенных элементов списка.В случае с fifth group совпадение было и по элементам, и по группе, поэтому оставляем только один элемент.
Подобные зависимости фильтрации между группами и элементами называются стратегией фильтрации. По умолчанию указана следующая стратегия:
Если стандартная стратегия не подходит для вашего случая, можно написать свою и передать ее в поле фильтра filterStrategy.
Благодаря использованию библиотеки awesome-data-filter можно решить проблему со сложностью обработчиков фильтров и улучшить читабельность кода. Теперь можно визуально сравнить графики сложности обоих подходов.
Если на вашем проекте не используется много фильтров и проект сам по себе небольшой, то вам не нужна эта библиотека. Остальным же можно пробовать внедрять ее постепенно.