Начнём с простого вывода на странице обычного списка ресурсов. Обязательно добейтесь, чтобы нормально работала AJAX-пагинация, потому что своих механизмов работы с AJAX мы писать не будем, а будем использовать методы AJAX, которые есть в pdoPage.
<div id="pdopage"> <div class="rows row"> {'!pdoPage' | snippet : [ 'ajaxMode' => 'default', 'parents' => 0, 'limit' => 3, 'includeTVs' => 'height,weight,speed_type,price', 'tvPrefix' => '', 'tpl' => '@INLINE <div class="col-sm-6 col-md-4"> <div class="thumbnail"> <div class="caption"> <h3>{$pagetitle}</h3> <p>Вес: {$weight}</p> <p>Высота: {$height}</p> <p>Тип: {$speed_type}</p> </div> </div> </div>', ]} </div> {'page.nav' | placeholder} </div>
Для фильтров я буду использовать такую вёрстку. Но вёрстка тут особой роли не играет — главное указать правильные классы в JS-коде.
<form class="form-horizontal" id="filters" action=""> <div class="form-group"> <label class="col-sm-1 control-label">Вес</label> <div class="col-sm-5"> <input type="number" name="weight" class="form-control" placeholder="47"> </div> <label class="col-sm-1 control-label">Высота</label> <div class="col-sm-5"> <input type="number" name="height" class="form-control" placeholder="120"> </div> </div> <div class="form-group"> <label class="col-sm-1 control-label">Тип</label> <div class="col-sm-11"> <div class="btn-group" data-toggle="buttons"> <label class="btn btn-default"> <input type="radio" name="speed_type" value="fast"> Быстрые </label> <label class="btn btn-default"> <input type="radio" name="speed_type" value="slow"> Медленные </label> </div> </div> </div> <div class="form-group"> <label class="col-sm-1 control-label">Цена</label> <div class="col-sm-11"> <div class="btn-group" data-toggle="buttons"> <label class="btn btn-default"> <input type="checkbox" name="price[]" value="0-20000"> до 20 000 руб. </label> <label class="btn btn-default"> <input type="checkbox" name="price[]" value="20000-50000"> 20 000—50 000 руб. </label> <label class="btn btn-default"> <input type="checkbox" name="price[]" value="50000-100000"> 50 000—100 000 руб. </label> <label class="btn btn-default"> <input type="checkbox" name="price[]" value="100000-"> от 100 000 руб. </label> </div> </div> </div> </form>
JS-код обработки фильтров выглядит как-то так:
$(document).on('change keyup', '#filters input', function(){ // Проверяем, что pdoPage подключён if (typeof(pdoPage) == 'undefined') return; // Собираем значения всех фильтров в единый массив var fields = {}; $.each($('#filters').serializeArray(), function(){ // Если параметр не является массивом (чекбоксом), то все просто if (this.name.indexOf('[]') <= 0) { fields[this.name] = this.value; } else { // Для чекбоксов посложнее var name = this.name.replace('[]',''); if (typeof(fields[name]) == 'undefined') { fields[name] = []; } fields[name].push(this.value); } }); // И отправляем этот массив на сервер. $.post(document.location.href, { action: 'filter', fields: fields, // Параметр hash - обязательный (он содержит настройки pdoPage) hash: pdoPage.configs.page.hash }, function(data) { // Просим pdoPage загрузить новый список ресурсов var tmp = document.location.href.split('?'); pdoPage.keys.page = 0; pdoPage.loadPage(tmp[0], pdoPage.configs.page); }); });
Уже сейчас при изменении значения фильтров список ресурсов будет обновляться. Теперь осталось «объяснить», как обрабатывать фильтры. Для этого создаём плагин на событие OnHandleRequest:
<?php if ($modx->context->key == 'mgr' || empty($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') return; switch ($_POST['action']) { case 'filter': $output = array('success' => false, 'message' => ''); // Проверяем, что hash получен и параметры pdoPage существуют if (isset($_POST['hash']) && !empty($_POST['hash']) && isset($_SESSION['pdoPage'][$_POST['hash']]) && !empty($_SESSION['pdoPage'][$_POST['hash']])) { $hash = (string) $_POST['hash']; // Указываем только ТВ, доступные для фильтрации $tvs = array('height','weight','speed_type'); // Наполняем условие выборки $where = array(); foreach ($tvs as $tv) { switch ($tv) { case 'price[]': // Пример обработки чекбоксов if (isset($_POST['fields']['price']) && !empty($_POST['fields']['price'])) { $where_price = []; foreach ($_POST['fields']['price'] as $range) { $value = explode('-', $range); if (count($value) != 2) { continue; } $min = (int) $value[0]; $max = (int) $value[1]; $where_range = 'CAST(`TVprice`.`value` AS DECIMAL(13,3)) >= ' . $min; if ($max) { $where_range .= ' AND CAST(`TVprice`.`value` AS DECIMAL(13,3)) <= ' . $max; } $where_price[] = '(' . $where_range . ')'; } $where[] = '(' . implode(' OR ', $where_price) . ')'; } break; default: if (isset($_POST['fields'][$tv]) && $_POST['fields'][$tv] !== '') { $where[$tv] = $_POST['fields'][$tv]; } break; } } // Добавляем это условие в параметры pdoPage "на лету" $_SESSION['pdoPage'][$hash]['where'] = $where; $output['message'] = $where; $output['success'] = true; } else { $output['message'] = 'Error'; } echo $modx->toJSON($output); die(); break; default: break; }
Теперь наши фильтры работают. Единственное — pdoPage «не знает», что показывать, когда ни один результат не найден. Чтобы такой случай учесть, создадим файл pdopage.custom.js и укажем его в параметре frontend_js:
{'!pdoPage' | snippet : [ 'ajaxMode' => 'default', 'frontend_js' => '/assets/components/pdotools/js/pdopage.custom.js', 'parents' => 0, 'limit' => 3, // ... }
В стандартном коде мы добавим только условие else на случай, если ответ от сервера будет пустым:
// ... pdoPage.loadPage = function (href, config, mode) { // ... $.post(config['connectorUrl'], params, function (response) { if (response && response['total']) { // ... } else { // Добавляем условие else wrapper.find(rows).html('Ничего не найдено'); wrapper.find(pagination).html(''); wrapper.removeClass('loading'); wrapper.css({opacity: 1}); if (config['mode'] == 'default') { $('html, body').animate({scrollTop: wrapper.position().top - 50 || 0}, 0); } } }, 'json'); }; // ...
На этом простой AJAX-фильтр готов.
С Уважением,
Алексей
Можете помочь с дополнением таким.
Добавил в вызов условие:
В него передаются параметры:
Т.е. сравниваются значения ТВ у ресурса и выводятся как надо.
Получается ваш метод работает как Ajax, а мои ссылки перезагружают страницу и фильтр по ajax скидывается.
Вот мне нужно это всё как то соединить. Помогите с решением.
Нужна помощь.
Фильтр переделал.
Ниже должно отображаться, то что выбрали в фильтре, т.е. например так?
И ниже:
А только потом уже результаты… Готов отблагодарить.
Подскажите, пожалуйста, а как сделать так, чтобы результат запроса подгружался не «на лету», а после нажатия кнопки? но при этом страница полностью не перезагружалась?
В результате сейчас получаю два вопроса:
1) при выборе одновременно нескольких чек-боксов в фильтре — выводятся только ресурсы с соответствием только 1 чек-боксу
2) если в ресурсе, в TV отмечено несколько значений, например white||blue, то при выборе фильтра только по чек-боксу white этот товар не будет показан
Буду признателен, как все читатели блога, если вы сможете дополнить функционал.
феном обязательно нужен?
В кэше пдо показывает значение вхере, есил их вписать в ручную при загрузке странице выведет необходимое, но не филттрует, если я включаю феном страница полностью вешается
Сам плагин
сам вывод на странице
Кнопка не отрисовывается в зависиомсти от результата (только при перезагрузки страницы), пример: перезагружаю страницу, кнопка «показать еще», жму на нее до тех пор пока загружать нечего — кнопка прячется. Жму на фильтр — фильтр срабатывает, выводятся нужные ресурсы, но кнопка не появляется, хотя должна, ресурсов больше чем параметре &limit.
Решается это как-то?
И спасибо Илье Уткину за статью, смог сделать фильтрацию по опциям товара
$('.btn-more').show() внутри вызова function(data)
в случае с button — она просто пропадает
Вопрос вот в чем. Подскажите, пожалуйста, как реализовать эту фильтрацию со своим id, вместо id=«pdoPage» и с несколькими id, если на странице несколько независимых выводов pdopage, каждый из которых со своей работающей пагинации по кнопке?
{'!pdoPage' | snippet: [
'includeContent'=>1,
'parents' => 35,
'sortby' => 'publishedon',
'sortdir' => 'DESC',
'limit'=> 12,
'totalVar' => 'reviewsPdo_total',
'pageVarKey' => 'reviewsPdo',
'pageNavVar' => 'reviewsPdo_nav',
'ajaxMode' => 'button',
'ajaxElemMore' => '#reviewsPdo .btn-more',
'ajaxTplMore' => '@INLINE Показать еще',
'ajaxElemWrapper'=> '#reviewsPdo',
'ajaxElemRows' => '#reviewsPdo .rows',
'ajaxElemPagination' => '#reviewsPdo .pagination',
'ajaxElemLink' => '#reviewsPdo .pagination a',
'includeTVs' => '',
'tvPrefix' => '',
'tpl' => '@FILE chunk/reviews/reviews__item.tpl',
'toPlaceholder' => 'reviewsPdo',
]}
{if $_modx->getPlaceholder('reviewsPdo_total') != 0}
{$_modx->getPlaceholder('reviewsPdo')}
{$_modx->getPlaceholder('reviewsPdo_nav')}
{/if}
А как быть с migx? По ним в тема не затронута совсем к сожалению. А на практике таких TV встречается много. Как фильтровать по полю таких TV. Какие изменения потребует плагин на примере ниже, если само TV — migx_size, а поле по которому нужно фильтрануть — size
case 'migx_size':
if (isset($_POST['fields']['migx_size']) && $_POST['fields']['migx_size'] !== '') {
$where['migx_size'] = $_POST['fields']['migx_size'];
}
break;