Ajax-фильтр с помощью pdoTools

16 Июля 2019 00:40

Состав фильтра, о котором пойдет речь:

  1. pdoTools;
  2. Сниппет, формирующий данные и запускающий pdoTools;
  3. Набор скриптов на Jquery для сбора данных и отправки их сниппету;
  4. Настроенная форма для фильтра.

Для начала, необходимо установить pdoTools, если его нет. На момент создания статьи, актуальная версия pdoTools — 2.12.2-pl.

Далее разберем форму и какие селекторы за что отвечают:

  1. .ajax-form — селектор формы фильтра, из которой будут отправляться данные;
  2. .ajax-start — селектор кнопки «отправить» для сбора данных и отправки (осуществления фильтрации);
  3. .ajax-reset — селектор кнопки сброса фильтра;
  4. .ajax-count — селектор количества найденных ресурсов;
  5. .ajax-container — блок-обертка для всех результатов поиска;
  6. .ajax-item — блок-обертка для каждого отдельного результата.

Схематично это выглядит так:

В самих скриптах можно поменять селекторы на желаемые, если эти не нравятся. Также если используется бутстрап сетка то можно комбинировать классы, например, .row .ajax-container или .col-12 .ajax-item. 

Собственно, сам файл со скриптами:

$(function() {

    //MODx pdoResources Ajax Filter
    //Filter Settings
    var fadeSpeed             = 200, // Fade Animation Speed
        ajaxCountSelector     = '.ajax-count', // CSS Selector of Items Counter
        ajaxContainerSelector = '.ajax-container', // CSS Selector of Ajax Container
        ajaxItemSelector      = '.ajax-item', // CSS Selector of Ajax Item
        ajaxFormSelector      = '.ajax-form', // CSS Selector of Ajax Filter Form
        ajaxFormButtonStart   = '.ajax-start', // CSS Selector of Button Start Filtering
        ajaxFormButtonReset   = '.ajax-reset', // CSS Selector of Button Reset Ajax Form
        sortDownText          = 'По убыванию',
        sortUpText            = 'По возрастанию';

    function ajaxCount() {
        if($('.ajax-filter-count').length) {
            var count = $('.ajax-filter-count').data('count');
            $(ajaxCountSelector).text(count);
        } else {
            $(ajaxCountSelector).text($(ajaxItemSelector).length);
        }
    }ajaxCount();

    function ajaxMainFunction() {
        $.ajax({
            data: $(ajaxFormSelector).serialize()
        }).done(function(response) {
            var $response = $(response);
            $(ajaxContainerSelector).fadeOut(fadeSpeed);
            setTimeout(function() {
                $(ajaxContainerSelector).html($response.find(ajaxContainerSelector).html()).fadeIn(fadeSpeed);
                ajaxCount();
            }, fadeSpeed);
        });
    }

    $(ajaxContainerSelector).on('click', '.ajax-more', function(e) {
        e.preventDefault();
        var offset = $(ajaxItemSelector).length;
        $.ajax({
            data: $(ajaxFormSelector).serialize()+'&offset='+offset
        }).done(function(response) {
            $('.ajax-more').remove();
            var $response = $(response);
            $response.find(ajaxItemSelector).hide();
            $(ajaxContainerSelector).append($response.find(ajaxContainerSelector).html());
            $(ajaxItemSelector).fadeIn();
        });
    })

    $(ajaxFormButtonStart).click(function(e) {
        e.preventDefault();
        ajaxMainFunction();
    })

    $(ajaxFormButtonReset).click(function(e) {
        e.preventDefault();
        $(ajaxFormSelector).trigger('reset');
        $('input[name=sortby]').val('pagetitle');
        $('input[name=sortdir]').val('asc');
        setTimeout(function() {
            $('[data-sort-by]').data('sort-dir', 'asc').toggleClass('button-sort-asc').text(sortUpText);
        }, fadeSpeed);
        ajaxMainFunction();
        ajaxCount();
    })

    $(''+ajaxFormSelector+' input').change(function() {
        ajaxMainFunction();
    })

    $('[data-sort-by]').data('sort-dir', 'asc').click(function() {
        var ths = $(this);
        $('input[name=sortby]').val($(this).data('sort-by'));
        $('input[name=sortdir]').val($(this).data('sort-dir'));
        setTimeout(function() {
            $('[data-sort-by]').not(this).toggleClass('button-sort-asc').text(sortUpText);
            ths.data('sort-dir') == 'asc' ? ths.data('sort-dir', 'desc').text(sortDownText) : ths.data('sort-dir', 'asc').text(sortUpText);
            $(this).toggleClass('button-sort-asc');
        }, fadeSpeed);
        ajaxMainFunction();
    });

});

Далее нам необходимо создать сниппет с любым названием, пусть это будет ajaxFilter. Смысл его заключается в получении данных из фронта, декодировании из json и формировании SQL-запроса для параметра &where используемого pdoTools.

Смотрим сниппет:

<?php
//Filter Fields Settings
$filter = array();

//Radio, Select & Text Fields Type
if($_GET['city']) {
    $filter['city'] = $_GET['city'];
}
if($_GET['production']) {
    $filter['production'] = $_GET['production'];
}
/*
//Two Text Fields From To
if($_GET['area_from']) {
    $filter[] = 'area>='.$_GET['area_from'];
}
if($_GET['area_to']) {
    $filter[] = 'area<='.$_GET['area_to'];
}*/
/*
//Для чекбоксов
if($_GET['garage']) {
    $filter['garage:IN'] = $_GET['garage'];
}
*/
//End Settings

//Sort
if($_GET['sortby']) {
    $sortby = $_GET['sortby'];
} else {
    $sortby = 'pagetitle';
}
if($_GET['sortdir']) {
    $sortdir = $_GET['sortdir'];
} else {
    $sortdir = 'asc';
}
//End Sort
/*
//Offset
$offset = 0;
if($_GET['offset']){
    $offset = $_GET['offset'];
}
*/
if($filter) {
    $where = $modx->toJSON(array($filter));
} else {
    $where = '';
}

$params_count = array(
    'parents' => $parents,
    'limit' => 0,
    'tpl' => '@INLINE ,',
    'select' => 'id',
    'includeTVs' => $fields,
    'showHidden' => '1',
    'where' => $where
);

$count = $modx->runSnippet('pdoResources',$params_count);
$count = count(explode(',',$count))-1;
$modx->setPlaceholder('count',$count);

$params = array(
    'parents' => $parents,
    'limit' => $limit,
    'offset' => $offset,
    'tpl' => $tpl,
    'select' => 'id,pagetitle,introtext,content',
    'includeTVs' => 1,
    'includeTVList' => $fields,
    'showHidden' => '1',
    'sortby' => $sortby,
    'sortdir' => $sortdir,
    'where' => $where
);

$more = $count - $offset - $limit;
$lim = $more > $limit ? $limit : $more;

$button = '';
if($more > 0){
    $button = '<div class="ajax-filter-count" data-count="'.$count.'"><a href="#" class="ajax-more">Загрузить еще '.$lim.' из '.$more.'</a></div>';
}

return $modx->runSnippet('pdoResources',$params).$button;

Пример вызова сниппета для первоначального вывода элементов и дальнейшего взаимодействия:

[[!ajaxFilter?
    &tpl=`tplCatItem`
    &limit=`10`
    &parents=`5`
    &fields=`image,area,floor,garage,price`
]]

       Идея этого фильтра взята вот отсюда, но с некоторыми доработками, а именно, переделан принцип формирования данных практически для каждых типов ввода данных (Чекбоксы, выпадающие списки, текстовые поля, значения "от" и "до"). Понятия не имею почему них указано по-другому т.к. в том виде фильтр малоработоспособен, комментарии с поправками они активно удаляли, но тем не менее, мой вариант проверен и обкатан и все пашет!