Наверх

Простой AJAX-фильтр для MODX с помощью pdoPage

Да, ещё один способ реализации Ajax-фильтра на сайте =)

Начнём с простого вывода на странице обычного списка ресурсов. Обязательно добейтесь, чтобы нормально работала 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-фильтр готов.

16 комментариев

  1. Алексей Архипенко 11 марта 2019, 14:39(Комментарий был изменён) # 0
    Что-то не работает фильтр, а куда последний код добавлять нужно?

    С Уважением,

    Алексей
    1. Илья Уткин 11 марта 2019, 14:50 # 0
      Надо скопировать стандартный JS-файл от pdoTools и в этой копии внести нужные изменения.
    2. Ishvan 07 апреля 2019, 14:10 # 0
      Уже сейчас при изменении значения фильтров список ресурсов будет обновляться. Теперь осталось «объяснить», как обрабатывать фильтры. Для этого создаём плагин на событие OnHandleRequest:
      вот дошел до сюда… но не происходит изменений в списках ресурсов
      1. Илья Уткин 08 апреля 2019, 07:08 # 0
        Изменений и не должно быть. Список должен просто обновляться — «мигать».
      2. Илья 20 апреля 2019, 23:39 # 0
        Огромное спасибо. Метод рабочий.
        Можете помочь с дополнением таким.
        Добавил в вызов условие:
        &where=`[[!#GET.where]]`
        В него передаются параметры:
        <div class="dropdown-menu">
        <a class="dropdown-item" href="[[~[[*id]]]]">Все</a>
        <div class="dropdown-divider"></div>
        <a class="dropdown-item" href="[[~[[*id]]]]?where=team_p1 > team_p2">Победы</a>
        <a class="dropdown-item" href="[[~[[*id]]]]?where=team_p1 = team_p2">Ничьи</a>
        <a class="dropdown-item" href="[[~[[*id]]]]?where=team_p1 < team_p2">Поражения</a>
        </div>
        
        Т.е. сравниваются значения ТВ у ресурса и выводятся как надо.
        Получается ваш метод работает как Ajax, а мои ссылки перезагружают страницу и фильтр по ajax скидывается.
        Вот мне нужно это всё как то соединить. Помогите с решением.
        1. Илья Уткин 22 апреля 2019, 15:52 # 0
          Вам нужно переделывать ваши ссылки на AJAX-поля, как speed_type в примере.
          1. Илья 23 апреля 2019, 09:13(Комментарий был изменён) # 0
            Добрый день!
            Нужна помощь.
            Фильтр переделал.
            Сезон - Вид игры - Турнир - это фильтр
            Ниже должно отображаться, то что выбрали в фильтре, т.е. например так?
            Фильтр: 2018, Регби-15, Чемпионат Москвы
            И ниже:
            Всего матчей: 22
            Побед: 10
            Ничьих: 5
            Поражений: 7
            А только потом уже результаты… Готов отблагодарить.
        2. Илья 22 апреля 2019, 16:03 # 0
          Спасибо за ответ. Так и сделал.
          1. Дмитрий 07 июня 2019, 18:09 # 0
            А как тут сделать ценовой ползунок?
            1. Николай 12 июня 2019, 19:43 # 0
              Спасибо большое за такое замечательное и функциональное решение!!!
              Подскажите, пожалуйста, а как сделать так, чтобы результат запроса подгружался не «на лету», а после нажатия кнопки? но при этом страница полностью не перезагружалась?
              1. Андрей 30 июля 2019, 13:56 # 0
                Подскажите пожалуйста, никак не могу составить условие between, такое ощущение, что передается строка, а не целое число. Вот код:
                // фильтр цена
                            if(!empty($_POST['fields']['price_from']) && empty($_POST['fields']['price_to'])) {
                                $where['cost:>='] = (int) $_POST['fields']['price_from'];
                            }
                            elseif(!empty($_POST['fields']['price_from']) && !empty($_POST['fields']['price_to'])  && (int)$_POST['fields']['price_to'] != 10000) {
                                $where[] = array(
                                        'cost:>=' => (int) $_POST['fields']['price_from'],
                                        'AND:cost:<=' => (int) $_POST['fields']['price_to']
                                    );
                            }elseif(!empty($_POST['fields']['price_to'])  && (int)$_POST['fields']['price_to'] != 10000) {
                                $where['cost:<='] = (int) $_POST['fields']['price_to'];
                            }
                1. Илья Уткин 30 июля 2019, 14:02 # 0
                  ТВ в MODX хранятся в виде текста. Вам нужно добавить в запрос преобразование типа данных. Вот, например, сортировка с преобразованием типа: ilyaut.ru/cheats/sorting-on-tv-number-for-pdoresources/
                  1. Андрей 30 июля 2019, 15:27 # 0
                    Спасибо переписал, заработало, может кому поможет:
                    $where[] = 'CAST(`TVcost`.`value` AS DECIMAL(13,3)) >= ' . (int)$_POST['fields']['price_from'] . ' AND CAST(`TVcost`.`value` AS DECIMAL(13,3)) <= ' . (int)$_POST['fields']['price_to'];
                2. Кирилл 02 августа 2019, 15:02 # 0
                  Здравствуйте. Спасибо за статью, у меня получилось сделать фильтр как написано и все работает. Мне не хватает функционала фильтрации и не достает опыта доработать. Я хочу сделать фильтр с чек-боксами, чтобы был множественный выбор. Например, цвет: белый, синий, красный. В ресурсах сделать TV color с чек-боксами: white||blue||red. В html добавил в плагине указал $tvs = array('color');
                  В результате сейчас получаю два вопроса:
                  1) при выборе одновременно нескольких чек-боксов в фильтре — выводятся только ресурсы с соответствием только 1 чек-боксу
                  2) если в ресурсе, в TV отмечено несколько значений, например white||blue, то при выборе фильтра только по чек-боксу white этот товар не будет показан
                  Буду признателен, как все читатели блога, если вы сможете дополнить функционал.
                  1. Islam 29 августа 2019, 05:14 # 0
                    Доброго времени суток. Хотел узнать, нашли ли Вы решение данной проблемы?
                    1. Илья Уткин 29 августа 2019, 08:10 # 0
                      Это не такая простая задача, как кажется на первый взгляд. Тут надо серьёзно дорабатывать выборку и фильтрацию. Если у вас есть небольшой бюджет, напишите мне на ilyautkin@mail.ru — обсудим стоимость и я вам помогу. Потом решение можно будет оформить и выложить для всеобщего доступа.

                  Авторизация

                  через сервис Loginza:

                  Подписка или RSS

                  Буду присылать новые статьи — никакого спама



                  Шаблоны MODX

                  1 2 Дальше »

                  Объектная
                  модель
                  MODX