Skip to content
mFilter
mFilter
Фасетная фильтрация для MODX 3 с поддержкой SEO URL
  1. Компоненты
  2. mFilter
  3. Фронтенд
  4. Плейсхолдеры

Плейсхолдеры

Справочник по всем плейсхолдерам и переменным, которые mFilter выставляет в MODX и чанки.

Синтаксис

MODX-плейсхолдер mfilter.something доступен из шаблона тремя способами:

  • MODX-тег: [[+mfilter.something]]
  • Fenom (pdoTools), через массив $_pls: {$_pls['mfilter.something']}
  • Внутри Fenom-чанков pdoTools (getChunk/parseChunk) — как обычная переменная: {$something} (только если она была передана в parseChunk явно, см. секции по чанкам ниже).

Внимание

Записи вида {$mfilter.something} (dot-notation через переменную $mfilter) не работают — такой переменной в Fenom-scope нет. Используйте $_pls['mfilter.something'].

Глобальные плейсхолдеры

Устанавливаются плагином mfilter при OnHandleRequest для страницы каталога с активными фильтрами, и/или сниппетом mFilter во время рендера. Доступны в шаблоне ресурса и любом чанке, вызванном ниже по цепочке.

Активные фильтры и URL

ПлейсхолдерТипОписание
mfilter.filtersarrayАктивные фильтры: ['brand' => ['apple'], 'color' => ['red']]
mfilter.activeFiltersarrayСиноним mfilter.filters
mfilter.base_uristringURI страницы каталога без сегмента фильтров (/catalog/)
mfilter.filter_uristringСегмент фильтров, добавляемый после base_uri (brand--apple/color--red/)
mfilter.baseIdsarrayID товаров текущей выборки (устанавливается сниппетом mFilter)

Сортировка и пагинация

ПлейсхолдерТипОписание
mfilter.sortstringТекущая сортировка: "pagetitle-asc", "price-desc"
mfilter.sortBystringТолько поле: "pagetitle", "price"
mfilter.sortDirstringТолько направление: "asc" / "desc"
mfilter.limitintТекущий лимит на странице
mfilter.defaultLimitintДефолтный лимит из настроек (для сравнения)
mfilter.pageintТекущая страница
mfilter.tplstringАктивный шаблон карточки ("tpl1", "tpl2")

SEO

Устанавливаются, когда есть хотя бы один активный фильтр. На нефильтрованных страницах — пустые (noindexfalse).

ПлейсхолдерТипОписание
mfilter.seo.titlestringSEO title из SEO Templates
mfilter.seo.h1stringSEO H1
mfilter.seo.descriptionstringSEO meta description
mfilter.seo.canonicalstringCanonical URL (при noindex указывает на страницу без фильтров)
mfilter.seo.noindexboolФлаг noindex (см. Системные настройки)
mfilter.seo.textstringПроизвольный SEO-текст из SEO Templates

Прочее

ПлейсхолдерТипОписание
mfilter.hashstringХэш конфигурации формы (для валидации AJAX)
mfilter.resultsstringHTML отрендеренных карточек (только при &toPlaceholders=1 в сниппете mFilter)
mfilter.paginationstringHTML пагинации (только при &toPlaceholders=1)

toPlaceholders=1

Использование &toPlaceholders=1 в вызове сниппета mFilter разбивает вывод на mfilter.results и mfilter.pagination без оборачивающего tplOuter-чанка. Теряются data-mfilter-results, data-page-count и вся SSR-инициализация — фронтенд-JS не сможет её подхватить. Используйте обычный вывод сниппета и оборачивающий чанк tplOuter.

Использование в шаблоне ресурса

SEO-теги в <head>

html
<title>{$_pls['mfilter.seo.title'] ?: $_modx->resource.pagetitle}</title>
<meta name="description" content="{$_pls['mfilter.seo.description'] ?: $_modx->resource.description}">

{if $_pls['mfilter.seo.canonical']}
    <link rel="canonical" href="{$_pls['mfilter.seo.canonical']}">
{/if}

{if $_pls['mfilter.seo.noindex']}
    <meta name="robots" content="noindex, follow">
{/if}

Логика фолбэков: если фильтров нет, mfilter.seo.* пусты, и ?: возвращает стандартное значение ресурса.

Почему noindex, follow, а не noindex, nofollow

JS-часть при AJAX-обновлении фильтров всегда выставляет content="noindex, follow" — это лучше для SEO, чем nofollow (link equity со страницы каталога с фильтрами продолжает распределяться по товарам). Чтобы SSR-разметка не рассогласовывалась с AJAX-состоянием, в шаблоне тоже указывайте follow.

H1 и SEO-текст в теле страницы

Обязательные маркеры для AJAX

<h1> и контейнер SEO-текста нужно явно пометить атрибутом или классом — иначе JS не найдёт их при AJAX-фильтрации и они обновятся только после F5. У страницы может быть несколько <h1> (hero, sidebar), поэтому просто «первый h1» не подходит.

  • <h1> — атрибут data-mfilter-h1 или класс mfilter-h1
  • SEO-текст — атрибут data-mfilter-seo-text или класс mfilter-seo-text
html
<h1 data-mfilter-h1>{$_pls['mfilter.seo.h1'] ?: $_modx->resource.pagetitle}</h1>

<div data-mfilter-seo-text{if !$_pls['mfilter.seo.text']} style="display:none"{/if}>
    {$_pls['mfilter.seo.text']}
</div>

При AJAX-фильтрации JS сам управляет display контейнера SEO-текста (скрывает при пустом значении, показывает при заполненном), поэтому inline-display:none при пустом SSR-значении — только чтобы контейнер не мелькал до первой фильтрации.

Хлебные крошки с фильтрами

html
<nav class="breadcrumbs">
    <a href="/">Главная</a>
    /
    <a href="{$_pls['mfilter.base_uri'] ?: $_modx->makeUrl($_modx->resource.id)}">
        {$_modx->resource.pagetitle}
    </a>
    {if $_pls['mfilter.seo.h1']}
        / <span>{$_pls['mfilter.seo.h1']}</span>
    {/if}
</nav>

Проверка «есть ли активные фильтры»

Отдельного плейсхолдера hasFilters нет — проверяйте сам массив:

html
{if $_pls['mfilter.filters']}
    <a href="{$_pls['mfilter.base_uri']}" class="reset-all">Сбросить все фильтры</a>
{/if}

Условная сортировка/лимит в UI-контролах

html
<select data-mfilter-sort>
    <option value="pagetitle-asc" {if $_pls['mfilter.sort'] == 'pagetitle-asc'}selected{/if}>А-Я</option>
    <option value="price-asc" {if $_pls['mfilter.sort'] == 'price-asc'}selected{/if}>Сначала дешевле</option>
</select>

<select data-mfilter-limit>
    <option value="12" {if $_pls['mfilter.limit'] == 12}selected{/if}>12</option>
    <option value="24" {if $_pls['mfilter.limit'] == 24}selected{/if}>24</option>
</select>

Переменные внутри чанков

При рендере своих чанков pdoTools передаёт им данные напрямую как Fenom-переменные (не через $_pls).

Чанки mFilter (карточки товаров)

tplOuter (обёртка результатов):

ПеременнаяОписание
$rowsHTML всех карточек товаров
$paginationHTML пагинации
$totalКоличество найденных товаров
$pageТекущая страница
$pageCountВсего страниц
$limitТоваров на странице
$hashХэш конфигурации формы

tpl1, tpl2 и другие (карточка одного товара) — обычные pdoResources/msProducts-переменные ресурса:

html
<div class="product-card" data-id="{$id}">
    <img src="{$image}" alt="{$pagetitle}">
    <h3>{$pagetitle}</h3>
    <div class="price">{$price | number:0} ₽</div>
</div>

Чанки mFilterForm (форма фильтров)

tplOuter:

ПеременнаяОписание
$filtersHTML всех фильтров, объединённых через tplFilter.outer
$hashХэш конфигурации формы (для AJAX)
$resourceIdID текущего ресурса каталога

tplFilter (обёртка одного фильтра):

ПеременнаяОписание
$keyКлюч фильтра (vendor, color, price…)
$labelНазвание фильтра
$typeТип: default, number, boolean, parents, ms3_categories, colors, vendors, date
$itemsHTML значений фильтра
$activeCountСколько значений этого фильтра выбрано

tplItem (одно значение — checkbox/radio):

ПеременнаяОписание
$keyКлюч фильтра
$valueЗначение
$slugСлаг для URL
$labelОтображаемый текст
$countКоличество товаров
$activeЗначение выбрано (bool)
$disabledЗначение недоступно (нет товаров при текущих остальных фильтрах)
$multipleМножественный выбор (checkbox vs radio)

tplBoolean (переключатель да/нет):

ПеременнаяОписание
$key, $value, $label, $count, $activeКак в tplItem

tplColor (цветовой свотч):

ПеременнаяОписание
$key, $value, $label, $activeКак в tplItem
$hexHEX-код цвета

tplSlider (range-фильтр):

ПеременнаяОписание
$keyКлюч фильтра
$labelНазвание
$min, $maxДоступный диапазон в текущей выборке
$minValue, $maxValueВыбранные пользователем значения
$stepШаг
$prefix, $suffixПрефикс/суффикс единицы измерения

Чанки mFilterSelected (блок «Выбрано»)

tplOuter:

ПеременнаяОписание
$itemsHTML всех выбранных значений
$totalОбщее количество выбранных значений

tplGroup (группа значений одного фильтра):

ПеременнаяОписание
$key, $labelКлюч и название фильтра
$itemsHTML значений внутри группы

tplItem (одна chip):

ПеременнаяОписание
$keyКлюч фильтра
$valueЗначение (машиночитаемое)
$valueLabelОтображаемый текст

tplReset (кнопка «сбросить всё»):

ПеременнаяОписание
$urlURL страницы без фильтров

Использование в JavaScript

Есть два пути.

Через window.mFilter (клиентский API)

javascript
const instance = window.mFilter.getInstance();

instance.state.filters;      // текущие фильтры
instance.state.sort;         // сортировка
instance.state.limit;        // лимит
instance.setFilter('brand', ['apple']);
instance.submit();

Полный список методов — в JS API.

Через события

javascript
document.addEventListener('mfilter:success', function (e) {
    console.log(e.detail.filters, e.detail.total, e.detail.seo);
});

e.detail.seo содержит те же данные, что попадают в mfilter.seo.* плейсхолдеры при SSR. Полный список событий — в Events.

Примеры

Пустые результаты

html
{if $total == 0}
    <div class="empty-results">
        <p>По вашему запросу ничего не найдено.</p>
        {if $_pls['mfilter.filters']}
            <p>Попробуйте <a href="{$_pls['mfilter.base_uri']}">сбросить фильтры</a>.</p>
        {/if}
    </div>
{/if}

$total — из tplOuter чанка mFilter. mfilter.filters — глобальный плейсхолдер.

Условная канонизация

Если у фильтрованной страницы noindex, canonical указывает на исходный ресурс (без фильтров). Иначе — на текущий URL.

html
{if $_pls['mfilter.seo.canonical']}
    <link rel="canonical" href="{$_pls['mfilter.seo.canonical']}">
{else}
    <link rel="canonical" href="{$_modx->makeUrl($_modx->resource.id, '', '', 'full')}">
{/if}

Логирование фильтрации в аналитику

html
<script>
document.addEventListener('mfilter:success', function (e) {
    if (typeof gtag !== 'undefined') {
        gtag('event', 'filter_apply', {
            filter_count: Object.keys(e.detail.filters).length,
            total: e.detail.total,
        });
    }
});
</script>