
mFilter
Фасетная фильтрация для MODX 3 с поддержкой SEO URL


Размещение отдельных фильтров вне основной формы.
Разместить некоторые фильтры (например, сортировку или быстрые фильтры) отдельно от основной формы фильтрации.
Использовать JavaScript API для управления фильтрами из любого места на странице.
<!-- Шапка каталога (вне формы) -->
<div class="catalog-header">
<div class="catalog-header__sort">
<label>Сортировка:</label>
<select id="external-sort">
<option value="pagetitle-asc">По названию (А-Я)</option>
<option value="pagetitle-desc">По названию (Я-А)</option>
<option value="Data.price-asc">Сначала дешёвые</option>
<option value="Data.price-desc">Сначала дорогие</option>
<option value="publishedon-desc">Сначала новые</option>
</select>
</div>
<div class="catalog-header__total">
Найдено: <span data-mfilter-total></span>
</div>
</div>
<!-- Основная форма в сайдбаре -->
<aside class="catalog-sidebar">
[[!mFilterForm]]
</aside>
<!-- Результаты -->
<main class="catalog-content">
<div data-mfilter-results>
[[!mFilter? ...]]
</div>
</main>document.addEventListener('DOMContentLoaded', function() {
const sortSelect = document.getElementById('external-sort');
sortSelect.addEventListener('change', function() {
const [field, dir] = this.value.split('-');
const mfilter = window.mFilter.getInstance();
// Установить сортировку через техпараметры
mfilter.setTechParam('sort', this.value);
mfilter.submit();
});
// Синхронизация при загрузке
document.addEventListener('mfilter:success', function(e) {
if (e.detail.tech?.sort) {
sortSelect.value = e.detail.tech.sort;
}
});
});<!-- Быстрые фильтры (вне формы) -->
<div class="quick-filters">
<button class="quick-filter" data-filter="new" data-value="1">
Новинки
</button>
<button class="quick-filter" data-filter="popular" data-value="1">
Популярные
</button>
<button class="quick-filter" data-filter="sale" data-value="1">
Со скидкой
</button>
</div>
<!-- Основная форма -->
[[!mFilterForm]]document.querySelectorAll('.quick-filter').forEach(button => {
button.addEventListener('click', function() {
const key = this.dataset.filter;
const value = this.dataset.value;
const mfilter = window.mFilter.getInstance();
// Toggle фильтра
const current = mfilter.getState().filters[key];
if (current && current.includes(value)) {
mfilter.removeFilter(key);
this.classList.remove('active');
} else {
mfilter.setFilter(key, [value]);
this.classList.add('active');
}
mfilter.submit();
});
});
// Синхронизация состояния кнопок
document.addEventListener('mfilter:success', function(e) {
document.querySelectorAll('.quick-filter').forEach(button => {
const key = button.dataset.filter;
const value = button.dataset.value;
const active = e.detail.filters[key]?.includes(value);
button.classList.toggle('active', active);
});
});<!-- Поиск вне формы -->
<div class="catalog-search">
<input type="text" id="catalog-search-input" placeholder="Поиск товаров...">
<button id="catalog-search-btn">Найти</button>
</div>
[[!mFilterForm]]const searchInput = document.getElementById('catalog-search-input');
const searchBtn = document.getElementById('catalog-search-btn');
function doSearch() {
const query = searchInput.value.trim();
const mfilter = window.mFilter.getInstance();
if (query) {
mfilter.setFilter('pagetitle', query);
} else {
mfilter.removeFilter('pagetitle');
}
mfilter.submit();
}
searchBtn.addEventListener('click', doSearch);
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
doSearch();
}
});<!-- Отдельный блок с ценой -->
<div class="price-filter-external">
<h4>Цена</h4>
<div class="price-inputs">
<input type="number" id="price-min" placeholder="От">
<span>—</span>
<input type="number" id="price-max" placeholder="До">
</div>
<button id="price-apply">Применить</button>
</div>
<!-- Форма без ценового фильтра -->
[[!mFilterForm?
&filters=`vendor,color,size`
]]const priceMin = document.getElementById('price-min');
const priceMax = document.getElementById('price-max');
const priceApply = document.getElementById('price-apply');
priceApply.addEventListener('click', function() {
const mfilter = window.mFilter.getInstance();
const min = priceMin.value ? parseInt(priceMin.value) : null;
const max = priceMax.value ? parseInt(priceMax.value) : null;
if (min !== null || max !== null) {
mfilter.setFilter('price', {
min: min,
max: max
});
} else {
mfilter.removeFilter('price');
}
mfilter.submit();
});
// Синхронизация при загрузке и после AJAX
document.addEventListener('mfilter:success', function(e) {
const price = e.detail.filters.price;
if (price) {
priceMin.value = price.min || '';
priceMax.value = price.max || '';
} else {
priceMin.value = '';
priceMax.value = '';
}
});<!-- Меню категорий с производителями -->
<nav class="category-menu">
<div class="category-menu__item">
<a href="/catalog/electronics/">Электроника</a>
<ul class="vendor-submenu">
<li><a href="#" data-vendor="apple">Apple</a></li>
<li><a href="#" data-vendor="samsung">Samsung</a></li>
<li><a href="#" data-vendor="xiaomi">Xiaomi</a></li>
</ul>
</div>
</nav>document.querySelectorAll('[data-vendor]').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const vendor = this.dataset.vendor;
const mfilter = window.mFilter.getInstance();
// Сбросить все фильтры и установить только производителя
mfilter.reset();
mfilter.setFilter('vendor', [vendor]);
mfilter.submit();
});
});Если фильтр есть и в форме, и снаружи — синхронизируйте состояние:
document.addEventListener('mfilter:success', function(e) {
// Обновить внешние элементы
syncExternalFilters(e.detail.filters);
// Обновить счётчики
document.querySelectorAll('[data-mfilter-total]').forEach(el => {
el.textContent = e.detail.total;
});
});
function syncExternalFilters(filters) {
// Синхронизировать select сортировки
const sortSelect = document.getElementById('external-sort');
if (sortSelect && filters.sort) {
sortSelect.value = filters.sort;
}
// Синхронизировать быстрые фильтры
document.querySelectorAll('.quick-filter').forEach(btn => {
const key = btn.dataset.filter;
const value = btn.dataset.value;
btn.classList.toggle('active', filters[key]?.includes(value));
});
}mfilter:success для синхронизации