PageBlocks стал ещё мощнее — теперь с пагинацией, сортировкой и фильтрацией
PageBlocks получил мощный апдейт — теперь вы можете реализовать пагинацию, сортировку и фильтрацию ваших данных с нуля буквально за пару строк кода. Всё работает как через обычные GET-параметры (?page=2), так и через человекочитаемые URL (например, /page-2), что идеально для SEO и красоты.

ДЕМО
Основные настройки:
1. Отображение общего количества элементов
2. Контейнер для вывода товаров
3. Блок пагинации
1. Настройка роутинга
Добавьте в core/App/routes/web.php:
2. Создание контроллера:
Разместите в core/APP/Http/Controllers/ProductController.php:
Базовая реализация:
Настройки кнопки:
Для включения бесконечного скролла достаточно двух параметров:
Базовые параметры сортировки:
Обновленная разметка с псевдонимами:
1. Настройка маршрута:
2. Добавляем метод sort для контроллера ProductController
Обновленный маршрут:
Для вывода фильтров используем сниппет pbFilters.
Базовый пример использования:
Обязательно нужно добавить параметр filterFields в сниппет, который выводит сами продукты — чтобы PageBlocks знал, какие фильтры учитывать при запросах:
Параметры сниппета, который выводит данные, для фильтрации URL
Чтобы фильтры выглядели как красивые сегменты URL, можно сделать так:
Тогда URL будет выглядеть так:
1. Добавляем маршрут и обработку
Регистрируем маршрут, который поймает фильтры:
2. В контроллере реализуем метод:
Нужно в настройках сниппета указать:
И обновить маршруты так:
На фронте доступна глобальная JavaScript-переменная pbPagination, которая позволяет программно управлять пагинацией, сортировкой и фильтрацией без перезагрузки страницы.
События инициируются через CustomEvent с префиксом pb: и могут быть отменяемыми (в случае pb:before).
Доступные события
pb:before
pb:progress:start
Перед отправкой запроса, если `showProgress = true`
pb:progress:end
после получения ответа или ошибки, если `showProgress = true`
pb:success
при успешном ответе (`response.ok === true`)
pb:after
после успешной вставки данных в DOM (если `expect === 'html'`)
pb:error
при ошибке HTTP (например, 404, 500)
pb:fail
при исключении или сетевой ошибке
pb:abort
если запрос был отменён вручную или другим запросом с тем же `cancelKey`
По умолчанию все значения в PageBlocks хранятся в JSON-формате (в поле values) — это удобно и гибко. Но есть нюанс: фильтрация по JSON-полям не использует индексы, и при большом количестве данных это может заметно замедлить выборку.
Чтобы ускорить фильтрацию, можно воспользоваться компонентом ExtraFields и расширить таблицу нужными полями с индексами.
Варианты улучшения
Допустим, товар может находиться в нескольких категориях — это множественное значение. Значит, поле category нужно реализовать как связь many-to-many.
1. Создаём список категорий
Можно сделать это как обычную таблицу, но лучше создать их как ресурсы — вдруг в будущем категории станут полноценными страницами. (Если вы используете контроллеры, то это не имеет значения.)
2.Добавляем поле category в наши продукты
Больше ничего делать не нужно.
PageBlocks автоматически определит, что это поле типа relationship, сделает JOIN с нужной таблицей и применит условия фильтрации напрямую.
MODX 2 / 3
PHP ^7.4
PageBlocks предлагает мощный и гибкий подход к пагинации, фильтрации и сортировке — без лишнего кода, с красивым URL и полной свободой кастомизации.
Всё работает «из коробки» — но при этом легко расширяется:
можно подключать контроллеры, ускорять выборки через ExtraFields, строить связи через `relationship` и контролировать всё с помощью Laravel-подобных маршрутов.
Если вы ещё не используете PageBlocks — сейчас самое время попробовать.

ДЕМО
Пагинация
Основные настройки:
'showPagination' => 1,
'pageVar' => 'page',
'pageLink' => '?{pageVar}={page}',
- showPagination — включает или отключает пагинацию (1 — включена, 0 — выключена).
- pageVar —имя GET-параметра, обозначающего текущую страницу (по умолчанию page).
- pageLink — шаблон для генерации ссылок. Может быть классическим ?page=2 или красивым URL, вроде /page-2. По умолчанию ?{pageVar}={page}
Пример через GET-параметры:
{'!pbResources' | snippet: [
'fieldName' => 'products',
'tpl' => '@FILE chunks/product.tpl',
'toPlaceholder' => 'pls.products', // Результат записываем в плейсхолдер
'limit' => 12,
'showPagination' => 1,
'pageLink' => '?page={page}' // Стандартное значение можно опустить
]}
Вывод данных на странице
1. Отображение общего количества элементов
<span id="pb-total">
{'pls.total' | placeholder}
</span>
Дополнительные параметры:- targetTotal — изменяет ID HTML-элемента
- plsTotal — задает имя плейсхолдера
2. Контейнер для вывода товаров
<div id="pb-items">
{'pls.products' | placeholder}
</div>
Кастомизация- targetItems — меняет ID контейнера
3. Блок пагинации
<nav id="pb-pagination" aria-label="Page navigation">
{'pls.pagination' | placeholder}
</nav>
Настройки:- targetPagination — изменяет ID навигации
- plsPagination — задает имя плейсхолдера
Реализация через ЧПУ (чистые URL)
'pageLink' => '/page-{page}'
Что для этого нужно:
1. Настройка роутинга
Добавьте в core/App/routes/web.php:
Route::get('/page-{page}', 'ProductController@index');
2. Создание контроллера:
Разместите в core/APP/Http/Controllers/ProductController.php:
class ProductController extends Controller
{
public function index(int $page)
{
// Указываем ресурс, где отображаются продукты
$this->modx->resource = $this->modx->getObject(\modResource::class, 1);
return view('file:templates/base', [
'page' => $page, // передаём номер страницы в шаблон, если нужен
]);
}
}
Теперь ваш URL /page-2 будет отдавать вторую страницу товаров.Кнопка «Загрузить еще»
Базовая реализация:
<div class="d-flex js-center">
{'pls.loadmore'|placeholder}
</div>
Настройки кнопки:
'plsLoadmore' => 'pls.loadmore',
'clsLoadmore' => 'btn btn-lg btn-outline-secondary w-100',
'tplLoadmore' => '@INLINE <button type="button" class="{$classes}" data-pbloadmore>{"pb.btn.loadmore" | lexicon}</button>'
- plsLoadmore — имя плейсхолдера для кнопки;
- clsLoadmore — CSS-классы для стилизации;
- tplLoadmore — шаблон кнопки.
Бесконечная прокрутка (infinite scroll)
Для включения бесконечного скролла достаточно двух параметров:
'infiniteScroll' => 1,
'infiniteOffsetScroll' => 200, // сколько пикселей от низа до подгрузки
Сортировка
Базовые параметры сортировки:
'sortVar' => 'sort',
'sortLink' => '?{sortVar}={sort}',
- sortVar — имя переменной, отвечающей за текущую сортировку. По умолчанию — sort.
- sortLink — шаблон для формирования ссылок сортировки. Может быть в формате GET (?sort=...) или «чистым» URL (/sort-...).
<div class="form-floating ms-auto" data-pbsort>
<select name="sort" class="form-select" aria-label="Sorting">
<option value="id-asc" selected>Oldest first</option>
<option value="id-desc">Newest first</option>
<option value="price-asc">From cheap to expensive</option>
<option value="price-desc">From expensive to cheap</option>
<option value="rating-asc">Rating asc</option>
<option value="rating-desc">Rating desc</option>
</select>
<label for="floatingSelect">Sort products</label>
</div>
Кастомизация значений сортировки
Для более понятных URL можно использовать псевдонимы через параметр sortNames:'sortNames' => 'expensive==price-desc,cheap==price-asc'
при такой настройки в URL будет добавлен параметр ?sort=expensive, вместо ?sort=price-descОбновленная разметка с псевдонимами:
<div class="form-floating ms-auto" data-pbsort>
<select name="sort" class="form-select" aria-label="Sorting">
<option value="id-asc" selected>Oldest first</option>
<option value="id-desc">Newest first</option>
<option value="cheap">From cheap to expensive</option>
<option value="expensive">From expensive to cheap</option>
<option value="rating-asc">Rating asc</option>
<option value="rating-desc">Rating desc</option>
</select>
<label for="floatingSelect">Sort products</label>
</div>
Пример через «чистый» URL
'sortLink' => '/{sortVar}={sort}',
1. Настройка маршрута:
Route::get('/sort-{sortName}', 'ProductController@sort');
2. Добавляем метод sort для контроллера ProductController
public function sort(string $sortName)
{
$this->modx->resource = $this->modx->getObject(\modResource::class, 1);
[$sortby, $sortdir] = $this->getSort($sortName);
return view('file:templates/base', [
'sortby' => $sortby,
'sortdir' => $sortdir,
'sortName' => $sortName
]);
}
public function getSort(string $sortName): array
{
[$sortby, $sortdir] = explode('-', $sortName);
switch ($sortName) {
case 'cheap':
$sortby = 'price';
$sortdir = 'asc';
break;
case 'expensive':
$sortby = 'price';
$sortdir = 'desc';
break;
}
return [$sortby, $sortdir];
}
Комбинирование пагинации и сортировки в ЧПУ
Для работы комбинированных URL вида /page-2/sort-cheap:'pageLink' => '/page-{page}',
'sortLink' => '/sort-{sort}',
Обновленный маршрут:
Route::get('/page-{page}/{sort?}', 'ProductController@index');
Такой маршрут обрабатывает оба варианта: /page-6 и /page-6/sort-cheapФильтрация
Для вывода фильтров используем сниппет pbFilters.
Базовый пример использования:
{'!pbFilters' | snippet: [
'fieldName' => 'products',
'filterFields' => 'price,category,brand,size,color',
// price — range input
'tpl.item.price' => '@FILE chunks/filters/price.tpl',
// brand — select
'tpl.wrapper.brand' => '@FILE chunks/filters/select.tpl',
'tpl.item.brand' => '@INLINE <option value="{$value}">{$name}</option>',
// size — radio buttons
'tpl.item.size' => '@FILE chunks/filters/radio.tpl',
]}
Обязательно нужно добавить параметр filterFields в сниппет, который выводит сами продукты — чтобы PageBlocks знал, какие фильтры учитывать при запросах:
'filterFields' => 'price,category,brand,size,color',
Параметры сниппета, который выводит данные, для фильтрации URL
'filterLink' => '?{name}={value}', // фильтр в виде ?price=100&brand=nike
- filterLink — шаблон для формирования ссылок фильтров.
Фильтры в «чистом» URL
Чтобы фильтры выглядели как красивые сегменты URL, можно сделать так:
'filterLink' => '/{name}-{value}',
'filterSeparator' => ';',
Тогда URL будет выглядеть так:
/filter/category-10002;brand-nike/
1. Добавляем маршрут и обработку
Регистрируем маршрут, который поймает фильтры:
Route::get('/filter/{filters}', 'ProductController@filter');
2. В контроллере реализуем метод:
public function filter(string $filters)
{
$this->modx->resource = $this->modx->getObject(\modResource::class, 1);
return view('file:templates/base');
}
Фильтры + пагинация + сортировка
Чтобы объединить фильтрацию с пагинацией и сортировкой:/filter/category-10002;brand-nike/page-2/sort-cheap
Нужно в настройках сниппета указать:
'filterLink' => '/{name}-{value}',
'pageLink' => '/page-{page}',
'sortLink' => '/sort-{sort}',
И обновить маршруты так:
Route::get('/filter/{filters}/{page?}', 'ProductController@filter'); // фильтры с пагинацией
Route::get('/filter/{filters}/{page?}/sort-{sortName}', 'ProductController@filter'); // фильтры + пагинация + сортировка
Метод filter в контроллере при этом можно оставить без измененийJavaScript API
На фронте доступна глобальная JavaScript-переменная pbPagination, которая позволяет программно управлять пагинацией, сортировкой и фильтрацией без перезагрузки страницы.
pbPagination.next() // загружает следующую страницу
pbPagination.next('beforeend') // подгружает следующую страницу (аналог кнопки "загрузить еще")
pbPagination.prev() // загружает предыдущую страницу
pbPagination.loadPage(2) // загружает необходимую страницу
pbPagination.sort('price', 'desc') // обновляет сортировку
pbPagination.sort('cheap') // если заполнен параметр sortNames
pbPagination.filter('brand', 'adidas') // добавляем фильтр
pbPagination.filter('brand', '') // удаляет фильтр brand
pbPagination.filter('category', [10002,10003]) // добавляет несколько значений для фильтра
События
Все запросы в PageBlocks выполняются через класс pbFetch, который генерирует на каждом этапе события. Это удобно для кастомизации поведения без необходимости переписывать логику.События инициируются через CustomEvent с префиксом pb: и могут быть отменяемыми (в случае pb:before).
Доступные события
pb:before
- method — метод запроса (`GET`, `POST`)
- url — адрес запроса
- target — селектор DOM-элемента, куда вставлять ответ
- options — все переданные опции (`headers`, `body`, `form`, `expect`, и т.п.)
document.addEventListener('pb:before', (e) => {
console.log('Запрос собирается отправиться:', e.detail.url);
// Пример отмены запроса
if (e.detail.url.includes('admin')) {
e.preventDefault(); // Запрос не отправится
}
});
pb:progress:start
Перед отправкой запроса, если `showProgress = true`
- url — адрес запроса
document.addEventListener('pb:progress:start', (e) => {
console.log('Индикатор загрузки включён для:', e.detail.url);
});
pb:progress:end
после получения ответа или ошибки, если `showProgress = true`
- url — адрес запроса
document.addEventListener('pb:progress:end', (e) => {
console.log('Индикатор загрузки выключен:', e.detail.url);
});
pb:success
при успешном ответе (`response.ok === true`)
- method
- url
- target
- response — объект Response
document.addEventListener('pb:success', (e) => {
console.log('Успешный ответ:', e.detail.response);
});
pb:after
после успешной вставки данных в DOM (если `expect === 'html'`)
- method
- url
- target
- response — объект Response
- data — HTML-строка или JSON-ответ
document.addEventListener('pb:after', (e) => {
console.log('Ответ вставлен в DOM:', e.detail.target);
});
pb:error
при ошибке HTTP (например, 404, 500)
- method
- url
- target
- status
- statusText
document.addEventListener('pb:error', (e) => {
alert(`Ошибка ${e.detail.status}: ${e.detail.statusText}`);
});
pb:fail
при исключении или сетевой ошибке
- method
- url
- target
- error — объект ошибки
document.addEventListener('pb:fail', (e) => {
console.error('Ошибка запроса:', e.detail.error);
});
pb:abort
если запрос был отменён вручную или другим запросом с тем же `cancelKey`
- method
- url
- target
document.addEventListener('pb:abort', (e) => {
console.warn('Запрос отменён:', e.detail.url);
});
Производительность
По умолчанию все значения в PageBlocks хранятся в JSON-формате (в поле values) — это удобно и гибко. Но есть нюанс: фильтрация по JSON-полям не использует индексы, и при большом количестве данных это может заметно замедлить выборку.
Чтобы ускорить фильтрацию, можно воспользоваться компонентом ExtraFields и расширить таблицу нужными полями с индексами.
Варианты улучшения
- ENUM — хорошо подходит для одиночных значений, например brand.
- SET — можно использовать для множественного выбора, но: поиск по SET не использует индекс.
- Связанная таблица — самое производительное и гибкое решение для множественных связей. В PageBlocks есть поддержка таких полей через relationship.
Допустим, товар может находиться в нескольких категориях — это множественное значение. Значит, поле category нужно реализовать как связь many-to-many.
1. Создаём список категорий
Можно сделать это как обычную таблицу, но лучше создать их как ресурсы — вдруг в будущем категории станут полноценными страницами. (Если вы используете контроллеры, то это не имеет значения.)
Tab::make('Categories')
->position(1)
->fields([
Field::make('Categories') // name = categories
->type('table')
->resource()
])
2.Добавляем поле category в наши продукты
Field::make('Category')
->label('Categories')
->type('relationship')
->relationType('many_to_many')
->primaryTable(\pbResource::class) // наша текущая таблица
->relatedTable(\pbResource::class) // таблица категорий
->fieldName('categories') // имя поля для получения только категорий
->displayField('pagetitle')
->valueField('id'),
Больше ничего делать не нужно.
PageBlocks автоматически определит, что это поле типа relationship, сделает JOIN с нужной таблицей и применит условия фильтрации напрямую.
Поддержка
MODX 2 / 3
PHP ^7.4
Заключение
PageBlocks предлагает мощный и гибкий подход к пагинации, фильтрации и сортировке — без лишнего кода, с красивым URL и полной свободой кастомизации.
Всё работает «из коробки» — но при этом легко расширяется:
можно подключать контроллеры, ускорять выборки через ExtraFields, строить связи через `relationship` и контролировать всё с помощью Laravel-подобных маршрутов.
Если вы ещё не используете PageBlocks — сейчас самое время попробовать.
Поблагодарить автора
Отправить деньги
Комментарии: 27
А ещё говорят я компоненты не для новичков пишу))) Вот где хардкор, хотя очень интересно, надеюсь в этом году будет время попробовать.
Есть в планах написать документацию?
Есть в планах написать документацию?
Документация есть — https://pageblocks.boshnik.com/
Основная часть материалов уже доступна, остальное скоро будет добавлено.
Основная часть материалов уже доступна, остальное скоро будет добавлено.
Поясните мне, кто может: что такое pbShop? Тут упоминается, но информации в гугле и заметках автора нет.
Только ИИ сообщает нечто интересное: disk.yandex.ru/i/sZyQIu2xi7YBXw, хотя сам не знает, где вообще взять pbShop :-)
Только ИИ сообщает нечто интересное: disk.yandex.ru/i/sZyQIu2xi7YBXw, хотя сам не знает, где вообще взять pbShop :-)
pbShop — интернет-магазин, разрабатываемый на основе PageBlocks
А что значит на основе Pageblocks? Без minishop'а?
да, только Pageblocks
Где можно посмотреть информацию? Откуда-то ИИ гугла взял )
Он только разрабатывается. Документации нет.
Вероятно ИИ из будущего: disk.yandex.ru/i/BEDdd2ZG9uWunQ
у меня яндекс не работает (даже через впн), я не вижу скрины
По моему когда-то такое название было у Evolution, отсюда и история в поисковике
Все проще, ИИ просто выдумывает. Он чтобы дать ответ придумывает, то чего не было.
Магазин в составе единой экосистемы pb очень интересен, особенно для modx3. Нет ли каких-то сроков, когда можно будет «пощупать»? Или пока хотя бы планируемый функционал.
Нет ли каких-то сроков, когда можно будет «пощупать»?в планах до 15.07, если все будет ок.
Или пока хотя бы планируемый функционал.Пока, что базовый функционал.
Крутое обновление, наконец то фильтр на Modx 3 будет.
Pageblocks растёт такими темпами, что скоро ему и Modx не нужен будет.
Pageblocks растёт такими темпами, что скоро ему и Modx не нужен будет.
Если будет интеграция или своя реализация seofilter будет действительно круто!
Не пользовался seofilter, бегло посмотрел, и вот что могу сказать:
Создаете контроллер для страницы и там уже в зависимости от URL прописываете нужные метатеги и текст.
Чего нет:
— статистики, которую тоже можно самому реализовать в контроллере
— хлебные крошки — не тестировал.
Если у вас есть реальный проект/пример — с удовольствием напишу подробную статью, как это реализовать через PageBlocks
Создаете контроллер для страницы и там уже в зависимости от URL прописываете нужные метатеги и текст.
Чего нет:
— статистики, которую тоже можно самому реализовать в контроллере
— хлебные крошки — не тестировал.
Если у вас есть реальный проект/пример — с удовольствием напишу подробную статью, как это реализовать через PageBlocks
Сорри за оффтоп, вопрос по FastPaginate.
Он присутствует в репозитории, прекрасно работает, но поддомен с примерами и документацией в настоящее время недоступен.
Это временная история или компонент более не поддерживается?
PS кстати, по ExtraFields та же история.
Он присутствует в репозитории, прекрасно работает, но поддомен с примерами и документацией в настоящее время недоступен.
Это временная история или компонент более не поддерживается?
PS кстати, по ExtraFields та же история.
FastPaginate действительно больше не будет поддерживаться!
extrafields.boshnik.com/ — все работает, документация скоро будет обновлена
extrafields.boshnik.com/ — все работает, документация скоро будет обновлена
Понятно, спасибо )
Basket это не та корзина, та — cart.
А pbFilters умеет работать с товарами miniShop2 и свойствами товара, которые созданы через msFieldsManager?
Компонент не будет работать с miniShop2 и даже с TV параметрами.
Очень хороший у вас компонент получился. Еще не пробовал, но думаю скоро попробую его в деле.
Очень шустро смотрю фильтрует товары, мне прям очень нравится, скажите не тестировали на большем количестве параметров? Скорость не сильно падает?
Я так понимаю pbShop еще в разработке, не подскажите, когда примерно будет готова корзина и оформление заказов? Так сказать, минимальный набор для запуска нового интернет-магазина. А то miniShop3 не понятно, когда появится и еще более призрачно появления компонента для фильтрации товаров для MODX3.
Очень шустро смотрю фильтрует товары, мне прям очень нравится, скажите не тестировали на большем количестве параметров? Скорость не сильно падает?
Я так понимаю pbShop еще в разработке, не подскажите, когда примерно будет готова корзина и оформление заказов? Так сказать, минимальный набор для запуска нового интернет-магазина. А то miniShop3 не понятно, когда появится и еще более призрачно появления компонента для фильтрации товаров для MODX3.
Спасибо.
Скажите не тестировали на большем количестве параметров? Скорость не сильно падает?Пробовал на VPS с базой из 100 000 товаров — работает быстро. Больше всего на скорость влияет подсчет кол-ва, но я его вынес в отдельный запрос. Поэтому, при условии оптимизации базы и наличия индексов, должно хорошо работать и с миллионом.
Я так понимаю pbShop еще в разработкев планах до 15.07, если все будет ок.
в планах до 15.07, если все будет ок.есть какие-то подвижки?
Занят был версией 2.8.0
pageblocks.boshnik.com/ru/changelog
так что теперь переноситься на 1-2 месяца
pageblocks.boshnik.com/ru/changelog
так что теперь переноситься на 1-2 месяца
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.