
FlatFilters
Преимущества
- Не требует установки на сервер сторонних библиотек или сервисов типа ElasticSearch или Sphinx.
- Высокая скорость фильтрации (менее 1 секунды при 100 000 товаров).
- Простота настройки, при использовании стандартных классов.
- Фильтрация по множественным значениям.
- Умеет фильтровать пользователей.
- Умеет фильтровать по полям тип migx с глубиной вложенности не более 1.
- Кастомизация логики с помощью плагинов.
Особенности
- Не умеет показывать количество совпадений по отдельным фильтрам.
- Умеет блокировать значения фильтров, которые точно вернут пустой результат.
- Возвращает результат в виде строки со списком id, но не готовый html или объект.
- Нет встроенного поиска.
Базовое использование
Установка зависимостей
Перед установкой компонента убедитесь, что у вас установлен компонент SendIt версии не ниже 2.0.0.
Установка компонента
Компонент доступен на сайте modstore.pro, поэтому вы можете установить его через стандартный установщик. Убедитесь что версия PHP на вашем сервере не ниже 7.4.
Версия для MODX 3
Существует отдельная сборка компонента для MODX Revolution 3. Отличия, которые нужно учитывать при использовании:
- Требования: MODX 3.x, PHP 8.1+, SendIt 3.1+, pdoTools (pdoResources).
- Фильтрация товаров работает с MiniShop3 (вместо miniShop2).
- Админка построена на Vue 3 + PrimeVue и берёт общий фронтенд-стек из VueTools (Import Map). Поэтому для работы страниц конфигураций необходим установленный компонент VueTools — без него админка выведет предупреждение и не смонтируется.
Логика работы (конфигурации, индексация, сниппеты, чанки, события, разметка фронтенда) полностью совпадает с версией для MODX 2. Отличия в написании кастомных классов описаны в разделе Разработка.
Добавление конфигурации
В верхнем меню в пункте "Пакеты" находим "Фильтрация". Кликаем и переходим к списку конфигураций фильтров, который изначально пуст.
Нажимаем кнопку "Добавить конфигурацию". Выбираем тип конфигурации: ресурсы, товары или пользователи. Вы можете добавить свой тип по этой инструкции.
Подсказка
Вы также можете копировать любую из существующих конфигураций, нажав жёлтую (или оранжевую или вторую слева в колонке Действия) кнопку в списке конфигурацией.
Настройка конфигурации
На открывшейся странице вводим любое понятное название конфигурации, оно необходимо только для того, чтобы отличать одну конфигурацию от другой.
Шаг указывает на количество сущностей индексируемых за раз. Лучше всего оставить значение по умолчанию 100, если у вас производительный сервер можете увеличить.
Для ресурсов и товаров можно указать Родителей, которые ограничат выборку ресурсов для индексации. Для пользователей можно указать Группы.
Подсказка
В системной настройке ff_show_parents_panel_for можно указать для каких типов конфигураций показывать панель выбора родителей.
Подсказка
В системной настройке ff_show_groups_panel_for можно указать для каких типов конфигураций показывать панель выбора групп.
Добавление фильтров
После этого добавляем список фильтров. Фильтры могут иметь значение по умолчанию, в этом случае они не будут показаны пользователям сайта, но будут ограничивать выборку при фильтрации.
Подсказка
Чтобы задать значение по умолчанию выберите знак сравнения и укажите само значение.
Каждому фильтру необходимо задать тип поля и тип сравнения. Тип поля определяет в каком формате будет хранится значение фильтра в БД, а тип сравнения определяет как будет сравниваться значение при фильтрации и, косвенно, в каком чанке (список, чекбоксы, слайдер, выбор даты) фильтр будет выведен на фронте.
Подсказка
Поля для фильтрации можно искать по ключу или по названию. Чтобы сопоставить ключу название добавьте соответсвующую запись словаря в пространство имён flatfilters в тему default в формате ff_frontend_ключ-фильтра.
Подсказка
Некоторые типы полей и типы сравнения несовместимы, поэтому все недоступные для данного типа поля типы сравнения блокируются.
Подсказка
Для типа сравнения множественный в SQL-запросе будет использован оператор IN значения внутри него будут строкой.
Подсказка
Для добавления фильтров по полям типа migx используйте ключ в формате tvname_fieldname.
Индексация конфигурации
Сохраняем конфигурацию и возвращаемся к общему списку. Тут находим созданную конфигурацию и нажимаем на зелёную кнопку Индексация.
Ждем пока бледно-зелёный фон заполнит всю строку и станет ярко-зелёным.
Время ожидания зависит от производительности вашего сервера и количества объектов требующих индексации.
Подсказка
Если индексация будет прервана, то повторное нажатие на кнопку Индексация возобновит процесс с момента остановки.
Подготовка шаблона
Внимание
Укажите список ID шаблонов, в которых будет использоваться фильтрация, в системной настройке ff_tpls.
Пока ждём можно подготовить шаблон. Там нужно вызвать сниппет ffGetFilterForm для вывода формы с фильтрами.
{set $pageLimit = 8}
{set $configId = 1}
{'!ffGetFilterForm' | snippet: [
'configId' => $configId,
'wrapper' => 'tpl.ffForm',
'priceTplOuter' => 'tpl.ffRange',
'favoriteTplOuter' => 'tpl.ffCheckbox',
'newTplOuter' => 'tpl.ffCheckbox',
'popularTplOuter' => 'tpl.ffCheckbox',
'colorTplOuter' => 'tpl.ffCheckboxGroupOuter',
'colorTplRow' => 'tpl.ffCheckboxGroup',
'defaultTplOuter' => 'tpl.ffSelect',
'defaultTplRow' => 'tpl.ffOption',
'createdonTplOuter' => 'tpl.ffDateRange',
]}Теперь добавим блок для показа метаинформации: количество результатов, выбранные фильтры, время фильтрации.
{'tpl.Info' | chunk}Следом нужно вызывать сниппет Pagination для отображения результатов.
{set $presetName = 'filters.presetName' | placeholder}
<div class="row" data-pn-result="filters">
{'!Pagination' | snippet: [
'configId' => $configId,
'snippet' => '!Pagination',
'render' => '!msProducts',
'presetName' => $presetName,
'pagination' => 'filters',
'resultBlockSelector' => '[data-pn-result="filters"]',
'resultShowMethod' => 'insert',
'hashParams' => 'filtersHash,sortby',
'noDisabled' => 1,
'tplEmpty' => '@INLINE <p>Товаров удовлетворяющих заданным параметрам не найдено.</p>',
'limit' => $pageLimit,
'parents' => 0,
'sortby' => ['Data.weight' => 'ASC'],
'tpl' => 'Чанк-вывода-товара',
'includeTVs' => 'modifications',
'includeThumbs' => 'small',
'showUnpublished' => 1
]}
</div>И наконец сам блок пагинации не забудьте:
<!-- PAGINATION -->
{set $totalPages = 'filters.totalPages' | placeholder}
{set $limit = 'filters.limit' | placeholder}
<div data-pn-pagination="filters" data-pn-type="" class="{$totalPages < 2 ? 'v_hidden' : ''} d-flex justify-content-between flex-wrap py-5" style="gap:10px;">
<button class="btn btn-warning w-100" type="button" data-pn-more>Загрузить ещё</button>
<div class="d-flex align-items-center" style="gap:10px;">
<button type="button" class="btn btn-primary" data-pn-first="1">❮❮</button>
<button type="button" class="btn btn-primary" data-pn-prev>❮</button>
<input type="number" name="filterspage" data-pn-current data-si-preset="{$presetName}" form="filterForm" min="1" max="{$totalPages}"
value="{$.get['filterspage']?:1}">
<p class="d-flex align-items-center mb-0">из
<span data-pn-total="">{$totalPages?:1}</span>
</p>
<button type="button" class="btn btn-primary" data-pn-next>❯</button>
<button type="button" class="btn btn-primary" data-pn-last="{$totalPages}">❯❯</button>
</div>
<p class="mb-0">Показывать по <input type="number" name="limit" data-pn-limit form="filterForm" min="1" max="96" value="{$limit?:12}"></p>
</div>Логика работы
При создании конфигурации фильтров вы выбираете её тип. Каждому типу соответствуют два класса-обработчика: для индексации и для фильтрации.
Подсказка
Соответствие типов и классов устанавливается в файле, путь к которому указывается в системной настройке ff_path_to_types, значение по умолчанию components/flatfilters/types.inc.php
Осторожно
Не редактируйте файл components/flatfilters/types.inc.php. Если требуется внести в него изменения - сделайте копию в другом месте.
При сохранении конфигурации в базе данных создаётся новая таблица для хранения индексов, она будет иметь имя ff_indexes_id-конфигурации. В этой таблице будут два обязательных поля id и rid, остальные поля соответствуют выбранным вами фильтрам.
Подсказка
При изменении типа фильтра и количества фильтров следует повторно произвести индексацию ресурсов.
Именно индексация позволяет обеспечить высокую производительность данного решения в сравнении с mFilter2. Актуальность индексов поддерживается за счёт работы плагинов на различные события, связанные с определенными действиями над фильтруемыми объектами: добавление, изменение, удаление.
На этом подготовительный этап заканчивается и в работу вступают сниппеты.
Первый ffGetFilterForm выводит форму фильтрации и запускает фильтрацию, результаты фильтрации сохраняются в сессию.
Сниппет Pagination получает результат из сессии и рендерит его с помощью указанного вами в параметре render сниппета.
При изменении значений в фильтрах фильтрация будет запущена снова.
Фильтрация работает через плагин на событие OnBeforePageRender. В плагине вызывается метод getFilterResult(), который возвращает список id ресурсов, и устанавливается несколько важных значений.
Осторожно
FlatFilters не занимается рендерингом результатов и пагинацией.
Подсказка
Значения фильтров для пустой формы (когда пользователь ещё ничего не выбрал) зависят только от содержимого индекса, поэтому они кэшируются и не пересчитываются на каждый заход. Кэш автоматически сбрасывается при переиндексации конфигурации (в том числе при сохранении, изменении или удалении отдельного объекта).
Сортировка
Сортировка результатов задаётся ключом sortby. Есть два места, где его можно указать:
1. Значение по умолчанию — параметр sortby сниппета Pagination. Задаётся массивом ключ => направление:
'sortby' => ['Data.price' => 'ASC'],2. Выбор пользователя — поле формы с атрибутом data-ff-filter="sortby" (обычно <select>). Значение опции задаётся строкой ключ|направление:
<option value="Data.price|ASC">Сначала дешёвые</option>
<option value="Resource.publishedon|DESC">Сначала новые</option>Из чего складывается ключ
Ключ определяет колонку, по которой идёт сортировка, а префикс — источник этой колонки:
| Форма ключа | Источник | Доступно для типов |
|---|---|---|
имя_фильтра (без префикса) | Колонка индексной таблицы (для товаров — колонка товара) | ресурсы, товары, пользователи |
Resource.поле | Поле ресурса из таблицы site_content (pagetitle, publishedon, createdon…) | ресурсы, товары |
Data.поле | Поле товара из таблицы данных miniShop | только товары |
id | ID ресурса / объекта | все |
rid | ID записи в индексной таблице | ресурсы, пользователи (для товаров недоступен) |
Внимание
Префикс Data. работает только для конфигураций типа «товары». Для ресурсов сортируйте по имени фильтра (колонке индекса) или по полю Resource.поле.
Допустимые ключи и безопасность
Список допустимых ключей формируется автоматически из колонок индексной таблицы и полей основной таблицы (site_content, а для товаров — таблицы данных miniShop). Ключ, которого нет в этом списке, молча отбрасывается — сортировка по нему не применяется. Это защищает ORDER BY от передачи произвольного SQL.
Отсюда следствие: расширить набор доступных сортировок можно только двумя способами — добавить нужное поле как фильтр в конфигурацию (тогда появится колонка индекса) либо сортировать по уже существующему полю site_content / товара. Направление принимает значения ASC или DESC.
Системные настройки
Подсказка
ff_allowed_tpls позволяет указать шаблоны, которые будут участвовать в индексации и для которых будут выбраны TV поля.
| Ключ | Описание | Значение |
|---|---|---|
| ff_allowed_tpls | Разрешённые для индексации шаблоны | |
| ff_connector | Имя сниппета-коннектора | ffConnector |
| ff_js_config_path | Путь JavaScript конфигурации | ./flatfilters.inc.js |
| ff_js_path | Путь JavaScript фронтенда | assets/components/flatfilters/js/web/flatfilters.js |
| ff_path_to_types | Путь к файлу с перечислением доступных типов конфигураций | components/flatfilters/types.inc.php |
| ff_preset_names | Имя используемых пресетов | {"filtering":"flatfilters","disabling":"ff_disabling","total":"ff_total"} |
| ff_show_groups_panel_for | Показывать панель выбора группы для | customers |
| ff_show_parents_panel_for | Показывать панель выбора родителя для | resources,products |
| ff_tpls | Шаблоны, в которых выводятся фильтры | 1 |
