Делать решено на примере каталога книг, просто потому что в рабочих планах есть необходимость реализовывать похожий функционал. Ну и читать тоже люблю, да ))
Что получилось, можно смотреть здесь:
Инструменты:
– MODx Revolution версии 2.3.5-PL– плагин getResources 1.6.1-pl
– плагин getPages 1.2.4-pl
– плагин Hits 1.3.0-pl
– руки, голова
Поехали:
Шаг 1. Подготовка
1.1. Устанавливаем MODx
1.2. Устанавливаем плагины
Ставим: собственно сам getResources (для выборки ресурсов), getPages (для разбивки выборки на подстраницы), Hits (для подсчета популярности того или иного ресурса, нужно для сортировки популярности). Все плагины есть в Установщике.Отдельно пара слов про Hits – его пришлось немного модифицировать, чтобы в число посещений ресурса считалось не каждое обновление страницы, а только одно на каждую сессию. Для этого в коде стандартного плагина Hits (из Установщика) были сделаны такие изменения (добавленные строки выделены жирным):
if($depth < 1) $depth = 1;
if (!isset($_SESSION['count_hits'])) $_SESSION['count_hits'] = array();
if($punch && $amount) {
if (!in_array($punch, $_SESSION['count_hits'])) {
$hit = $modx->getObject('Hit',array(
'hit_key' => $punch
));
if($hit) {
// increment the amount
$hit->set('hit_count',($knockout ? 0 : (integer)$hit->get('hit_count')) + $amount);
$hit->save();
} else {
// create a new hit record
$hit = $modx->newObject('Hit');
$hit->fromArray(array(
'hit_key' => $punch,
'hit_count' => $amount
));
$hit->save();
}
$_SESSION['count_hits'][] = $punch;
// записываем число просмотров в tv-параметр
$currentResource = $modx->getObject('modResource', $punch);
$currentResource->setTVValue('hits', $hit->hit_count);
}
}
Что конкретно добавлено:
– задаем переменную в сессии, чтобы сохранять в нее id просмотренных ресурсов
– в код записи числа просмотров для ресурса добавили проверку, есть ли уже этот ресурс в сессии
– если страница ранее еще не просматривалась, то увеличиваем для нее hits и записываем ее id в сессию
– добавлено сохранение числа просмотров в TV-параметр ресурса (его создадим чуть позже, и зачем это нужно, тоже далее поясню)
Дополнительно я поставила пакет BootstrapForMODX – чтобы не возиться со стилями, а использовать Bootstrap.
1.3. Создаем шаблоны
Мне понадобилось 3 шаблона:1.3.1. Шаблон для главной страницы
(именно на ней будет выводиться наш ajax-каталог)В нем ничего особого нет:
<!DOCTYPE html>
<html>
<head>
[[$header]]
</head>
<body>
<div class="container">
[[*content]]
</div>
[[$footer]]
<script src="/js/catalog.js"></script>
</body>
</html>
Только дополнительно к стилям и скриптам Bootstrap подключаем файл /js/catalog.js, где напишем скрипты для нашего каталога.
1.3.2. Шаблон элемента
Шаблон для страницы отдельной книги, например, http://catalog.26th.ru/catalog/devid-mitchell/oblachnyij-atlas/Пока это только основа шаблона:
<!DOCTYPE html>
<html>
<head>
[[$header]]
</head>
<body>
<div class="container">
<div class="page-header">
<h1><a href="/">Каталог книг</a> <small>(на MODx Revolution)</small></h1>
<p>Пример каталога элементов на getResources с Ajax-подгрузкой</p>
</div>
<div class="row" style="margin-bottom:30px;">
<div class="col-md-3 item-left">
Здесь будет картинка книги
</div>
<div class="col-md-9">
Здесь будет название, описание и прочая информация
</div>
</div>
</div>
[[$footer]]
</body>
</html>
Мы добавим в него вывод информации позже. Также в нем не вызывается catalog.js
1.3.3. Пустой шаблон
Очень простой:[[*content]]
Понадобится нам для той странички, у которой мы ajax-ом будем запрашивать данные. На ней лишний код не нужен, поэтому пустой шаблон создаем обязательно.
1.4. Создаем вспомогательные ресурсы
Для книг я планирую указывать их жанры. И хочу, чтобы добавлять дополнительные жанры впоследствии можно было без затруднений.Для этого я создам ресурс «Жанры» и в нем создам дочерние ресурсы, где в pagetitle укажу название жанра.
Важно: поле «Псевдоним» (alias) у каждого жанра я заполняю на латинице и при этом добавляю в начале и в конце звездочку, вот так:
Зачем нужны эти звездочки – ниже расскажу.
1.5. Создаем дополнительные TV-параметры
В последних версиях MODx tv-параметры названы просто Дополнительными полями, но привычка – сильная вещь, поэтому – «tv-параметры».Наши элементы (книги) будут обладать дополнительными свойствами:
– Список жанров
– Формат (электронная, бумажная или аудиокнига)
– Обложка (картинка)
– Язык
– Цена
– Год издания
– Число просмотров (как показатель популярности)
Для этих свойств создаем TV-параметры:
В последней строке мы выбираем в «Возможные значения» те самые жанры из папки с id=5 (это id нашей папки «Жанры»). Таким образом, когда мы добавим новые жанры, они автоматически отобразятся в возможных значениях этого TV-параметра.
Также не забываем поставить галочку у шаблона «Шаблон элемента» на вкладке «Доступно для шаблонов».
1.6. Создаем страницы элементов
Для них сделаем отдельную папку (ресурс-контейнер) «Каталог», чтобы страницы элементов не «путались под ногами». Важно: у ресурса «Каталог» выбираем шаблон «Пустой шаблон».В папке «Каталог» создаем ресурсы по авторам (для товаров это были бы категории). И уже в папке каждого автора создаем страницы для конкретных книг. Для них ставим «Шаблон элемента».
Заполняем информацию по каждой книге:
– Название
– Псевдоним
– Аннотация
– Содержимое (там я указала отрывок книги)
– Дополнительные поля
Дополнительное поле «Число просмотров» заполнять не нужно – его будет перезаписывать плагин Hits, когда число посещений для данного ресурса будет увеличиваться.
Шаг 2. Ajax-подгрузка контента
Итак, подготовка закончена, у нас есть необходимые плагины и ресурсы, список которых надо выводить на сайте.Для начала настроим просто вывод списка ресурсов. Сделаем это в ресурсе «Каталог» (который у нас на «Пустом шаблоне» – пусть это вас не смущает).
Для вывода списка используем getResources:
[[getResources?
&limit=`8`
&parents=`2`
&hideContainers=`1`
&tpl=`tplList`
&includeTVs=`1`
]]
Здесь мы задаем вывод 8 ресурсов из папки «Каталог» (id=2), исключая контейнеры (то есть ресурсы с авторами), по шаблону из чанка tplList, и при это указываем, что нам понадобятся и значения tv-параметров ресурсов.
Чанк tplList пока содержит только название ресурса со ссылкой на его страницу:
<p><a href="/[[~[[+id]]]]" target="_blank">[[+pagetitle]]</a></p>
Сортировку не задаю, ее добавим позже.
Итак, открываем http://catalog.26th.ru/catalog/ и видим список ресурсов:
Но нам понадобятся не только 8 первых ресурсов, но и все остальные тоже.
Поэтому используем плагин getPage, чтобы получить список всех доступных ресурсов, разбитый на подстраницы:
[[!getPage?
&elementClass=`modSnippet`
&element=`getResources`
&limit=`8`
&parents=`2`
&hideContainers=`1`
&tpl=`tplList`
&includeTVs=`1`
]]
[[+page.nav]]
Его параметры совпадают с параметрами getResources, ибо getPage не делает выборку, он лишь разбивает на подстраницы то, что выдает getResources. Поэтому заменяем только начало и вызов делаем некешируемым.
Плейсхолдер page.nav отвечает за вывод навигации.
Обновяем наш список и видим, что появились ссылки на подстраницы:
Собственно в ссылках на подстраницы лишь добавлен параметр page:
http://catalog.26th.ru/catalog/?page=2
Таким образом, у нас есть список ресурсов, и именно его мы будем запрашивать ajax-ом. Так как в результате нам будет нужен только сам список, а не навигация, то строчку
[[+page.nav]]
убираем.Итого в коде ресурса «Каталог» имеем:
[[!getPage?
&elementClass=`modSnippet`
&element=`getResources`
&limit=`8`
&parents=`2`
&hideContainers=`1`
&tpl=`tplList`
&includeTVs=`1`
]]
Теперь открываем редактирование главной страницы. Для нее выставляем «Шаблон каталога».
И пишем такой код:
<div class="page-header">
<h1>Каталог книг<small>(на MODx Revolution)</small></h1>
<p>Пример каталога элементов на getResources с Ajax-подгрузкой</p>
</div>
<div class="row">
<div class="col-md-9">
<div class="sort">Здесь будет сортировка</div>
<div id="catalog"></div>
<button class="btn btn-block btn-warning" id="more" data-show="0" style="display:none;">Еще</button>
</div>
<div class="col-md-3">
<div class="filter">Здесь будет фильтр</div>
</div>
</div>
На данном шаге важно – мы создали блок #catalog, в который будем подгружать наш список, и кнопку «Еще» (правда, сразу же ее скрыли).
Теперь открываем файл http://catalog.26th.ru/js/catalog.js и в нем прописываем функции для подгрузки списка ресурсов:
// адрес нашего списка ресурсов (где идет вывод getResources)
var uri = '/catalog/';
$(document).ready(function() {
// загружаем начальный блок
loadCatalog(0, 1);
// клик на кнопку "Еще"
$('#more').on('click', function() {
var showPage = $(this).data('show');
var preloadPage = parseInt(showPage) + 1;
loadCatalog(showPage, preloadPage);
});
});
function loadCatalog(showPage, preloadPage) {
// скрываем кнопку "Еще"
$('#more').hide();
// показываем блок с ранее загруженным контентом и прокручиваем к нему
if (showPage != 0) {
$('#page' + showPage).show('slow');
$('html,body').animate({ scrollTop: $('#page' + showPage).offset().top - 100 }, 1000);
}
// создаем блок под новую загрузку
$('#catalog').append('<div id="page' + preloadPage + '"></div>');
uri = uri + '?page=' + preloadPage;
// загружаем ajax-ом контент следующей страницы, но не показываем его
$.ajax({
url: uri,
cache: false,
success: function(html) {
if (html != '') {
$('#more').data('show', preloadPage);
$('#more').show();
$('#page' + preloadPage).hide();
$('#page' + preloadPage).html(html);
if (preloadPage == 1) loadCatalog(1, 2);
}
}
});
}
Изначально я написала скрипт, который запрашивал содержимое очередной страницы (page=1, page=2, ...) и тут же выводил ее. Но когда доступные элементы заканчивались, получалось, что кнопка «Еще» показывается, а ресурсов уже нет, и ничего не подгружается.
Поэтому я решила сделать предварительную загрузку (preload) — чтобы и мне заранее можно было узнать, а есть ли еще элементы, и пользователям вроде как польза – меньше ждать загрузки контента. Вот так невольно и улучшаем юзабилити ))
Поэтому скрипт стал работать таким образом:
При загрузке страницы запрашивается содержимое с адреса http://catalog.26th.ru/catalog/, вставляется в блок
<div id="page1">
и остается скрытым, затем тут же запрашивается содержимое http://catalog.26th.ru/catalog/?page=2, вставляется в блок <div id="page2">
и остается скрытым, а блок <div id="page1">
тем временем показывается. То есть при первой загрузке страницы мы дважды обращаемся ajax-ом к странице http://catalog.26th.ru/catalog/У кнопки «Еще» в атрибут data-show записываем номер последнего загруженного блока (то есть именно его мы должны показать по клику на кнопке).
Далее, когда мы жмем «Еще»:
– Скрываем эту кнопку
– Показываем блок с номером из data-show этой кнопки
– Затем подгружаем следующую подстраницу с http://catalog.26th.ru/catalog/
– Важно (почему собственно и не показываем сразу этот подгруженный контент): сначала смотрим, а вернулось ли нам хоть что-нибудь. Ибо если элементы кончились, то мы получим пустой ответ. А если мы получили пустой ответ, то в этом случае кнопку «Еще» оставляем скрытой – потому что она более не нужна, ведь подгружать нечего.
Всё, теперь можем открыть нашу главную страницу и посмотреть, как подгружается по 8 строчек с названиями наших элементов.
Теперь настроим чанк tplList, чтобы карточки элементов были похожи собственно на карточки. Окончательный вид у него такой:
[[+idx:mod=`4`:is=`1`:then=`<div class="row">`]]
<div class="col-md-3 list-item text-center">
<p class="list-item-image">
<img src="/[[+tv.image]]" alt="" class="img-thumbnail">
<span class="label label-primary label-lang">[[+tv.lang]]</span>
[[showFormats?content=`[[+tv.format]]`]]
</p>
<h5><a href="/[[~[[+id]]]]" target="_blank">[[+pagetitle]]</a></h5>
<p class="grey"><small>[[getAuthor?id=`[[+parent]]`]], [[+tv.year]] г.</small></p>
<p><small>[[showCategories?content=`[[+tv.category]]`]]</small></p>
<p class="bg-info list-item-price">[[+tv.price]] руб.</p>
<p class="grey"><small>Просмотров: [[+tv.hits]]</small></p>
</div>
[[+idx:mod=`4`:is=`0`:then=`</div>`]]
Пойдем по порядку:
[[+idx]]
– плейсхолдер, который содержит порядковый номер выводимого элемента. Т.к. я использую Bootstrap, то у меня выводится по 4 элемента в строке – и мне нужно добавлять разрывы строк в сплошной список. Поэтому перед 1-ым, 5-ым, 9-ым и т.д. элементом я добавляю <div class="row">
, а после каждого 4-го, 8-го, 12-го и т.д. добавляю закрывающий </div>
.Далее
[[+tv.image]], [[+tv.lang]], [[+tv.year]], [[+tv.price]], [[+tv.hits]]
– выводим значения соответствующих tv-параметров.Еще остаются параметры Формат, Жанры и Автор. Если мы просто напишем
[[+tv.format]]
, то получим, например, electro||audio, что нам, конечно, не подходит.Поэтому пишем сниппет
[[showFormats?content=`[[+tv.format]]`]]
для распарсивания значений в человеческий вид. Его код:<?php
$formats = explode('||', $content);
$arrFormats = array(
'electro' => 'электр.',
'casual' => 'бум.',
'audio' => 'аудио'
);
foreach($formats as $format) {
$out .= '<span class="label label-success">' . $arrFormats[$format] . '</span>';
}
if ($out != '') echo '<span class="formats">' . $out . '</span>';
Аналогично создаем сниппет
[[showCategories?content=`[[+tv.category]]`]]
для вывода списка жанров:<?php
$out = '';
$categories = explode('||', $content);
foreach($categories as $categ) {
$query = $modx->newQuery('modResource', array('parent'=>5, 'alias'=>$categ));
$query->sortby('pagetitle','ASC');
$resources = $modx->getCollection('modResource', $query);
foreach($resources as $resource) {
if ($resource->published == true && $resource->deleted != true && $resource->hidemenu != true) $out .= $resource->pagetitle . ', ';
}
}
return trim($out, ', ');
И простенький сниппет
[[getAuthor?id=`[[+parent]]`]]
для вывода имени автора (это название родительской категории):<?php
if ((int)$id > 0) {
$resource = $modx->getObject('modResource', $id);
return $resource->pagetitle;
}
Обратите внимание, в хедере подключены дополнительные стили для каталога:
http://catalog.26th.ru/css/catalog.css
В итоге получаем такие карточки:
Вот только просмотры пока везде по нулям – давайте исправим. Для этого займемся шаблоном «Шаблон элемента», вставляем в него код:
<!DOCTYPE html>
<html>
<head>
[[$header]]
</head>
<body>
<div class="container">
<div class="page-header">
<h1><a href="/">Каталогкниг</a><small>(на MODx Revolution)</small></h1>
<p>Пример каталога элементов на getResources с Ajax-подгрузкой</p>
</div>
<div class="row" style="margin-bottom:30px;">
<div class="col-md-3 item-left">
<p style="margin-top:25px;"><img src="/[[*image]]" alt="" class="img-thumbnail"></p>
<span class="label label-primary label-lang">[[*lang]]</span>
[[showFormats?content=`[[*format]]`]]
<p class="bg-info list-item-price">[[*price]] руб.</p>
<p class="grey"><small>Просмотров: [[!Hits? &punch=`[[*id]]` &hit_keys=`[[*id]]` &tpl=`hitsID`]]</small></p>
</div>
<div class="col-md-9">
<h2>[[*pagetitle]]</h2>
<p>[[getAuthor?id=`[[*parent]]`]], [[*year]] г.</p>
<p class="grey">[[showCategories?content=`[[*category]]`]]</p>
<p style="margin-top:20px;"><b>Аннотация:</b></p>
<p>[[*introtext]]</p>
<p style="margin-top:20px;"><b>Отрывок:</b></p>
[[*content]]
</div>
</div>
</div>
[[$footer]]
</body>
</html>
В нем используем
[[*image]]
и т.д. для вывода наших дополнительных полей (и те же вспомогательные сниппеты, что были в чанке tplList).Отличием является вызов сниппета
[[!Hits? &punch=`[[*id]]` &hit_keys=`[[*id]]` &tpl=`hitsID`]]
Именно он:
– Прибавляет +1 к числу просмотров текущего ресурса (для этого передаем id в &punch)
– Записывает число просмотров в tv-параметр hits (соответствующие изменения в код плагина мы вносили выше)
– И выводит число просмотров (в &hit_keys указываем, просмотры какого ресурса нам нужны), используя при этом чанк hitsID, который содержит только одну строчку (собственно, число просмотров):
[[+hit_count]]
Итого на отдельной странице, например, на http://catalog.26th.ru/catalog/u-nesbe/syin/ получаем такой контент:
Число просмотров – слева под картинкой.
Теперь при каждом посещении страницы книги (при каждой новой сессии) у ресурса увеличивается счетчик просмотров.
В списке тоже видим это число просмотров:
Итак, радуемся – каталог с динамической подгрузкой контента (без перезагрузки страницы) готов!
Шаг 3. Сортировка
Для добавления сортировки (а далее и фильтра) будем использовать get-параметры.Поэтому строчка uri = uri + '?page=' + preloadPage; в нашем js-скрипте перестанет работать – ибо она будет «затирать» все параметры сортировки или фильтра.
Чтобы этого не происходило, добавим в код функцию для добавления (замены, удаления) get-параметра в URI и будем использовать ее:
function setAttr(prmName,val) {
var res = '';
var d = uri.split("?");
var base = d[0];
var query = d[1];
if(query) {
var params = query.split("&");
for(var i = 0; i < params.length; i++) {
var keyval = params[i].split("=");
if(keyval[0] != prmName) {
res += params[i] + '&';
}
}
}
if (val != '') res += prmName + '=' + val;
return base + '?' + res;
}
Таким образом, строчка с uri принимает вид:
uri = setAttr('page', preloadPage);
Теперь мы можем запрашивать вторую, третью и прочие страницы, оставляя неизменными параметры сортировки.
Далее добавляем кнопки для сортировки на Главную страницу (вместо строчки «Здесь будет сортировка»):
<div class="btn-group" role="group">
<button data-sort="hits" data-sortdir="desc" type="button" class="btn btn-xs btn-default active">По популярности<span class="glyphicon glyphicon-arrow-down"></span></button>
<button data-sort="hits" data-sortdir="asc" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-arrow-up"></span></button>
</div>
<div class="btn-group" role="group">
<button data-sort="price" data-sortdir="desc" type="button" class="btn btn-xs btn-default">По цене<span class="glyphicon glyphicon-arrow-down"></span></button>
<button data-sort="price" data-sortdir="asc" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-arrow-up"></span></button>
</div>
<div class="btn-group" role="group">
<button data-sort="year" data-sortdir="desc" type="button" class="btn btn-xs btn-default">По году издания<span class="glyphicon glyphicon-arrow-down"></span></button>
<button data-sort="year" data-sortdir="asc" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-arrow-up"></span></button>
</div>
<div class="btn-group" role="group">
<button data-sort="title" data-sortdir="desc" type="button" class="btn btn-xs btn-default">По названию<span class="glyphicon glyphicon-arrow-down"></span></button>
<button data-sort="title" data-sortdir="asc" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-arrow-up"></span></button>
</div>
Добавили 4 вида сортировки – по популярности, по цене, по году издания, по названию. Каждая сортировка доступна по убыванию и возрастанию. По умолчанию будем сортировать по популярности по убыванию.
Чтобы передавать информацию о параметре сортировки и направлению, используем атрибуты data-sort и data-sortdir.
Теперь по клику на той или иной кнопке нам надо:
– Выделить ее активной
– Сформировать нужный адрес, например, /catalog/?sortby=price&sortdir=asc, чтобы получить нужный список ресурсов
– Очистить блок
<div id="catalog">
– Подгрузить в него контент с нового адреса (с параметрами сортировки)
Добавляем в /js/catalog.js (в $(document).ready(function(){):
$('.sort button').on('click', function() {
$('.sort button').removeClass('active');
$(this).addClass('active');
uri = setAttr('sortby', $(this).data('sort'));
uri = setAttr('sortdir', $(this).data('sortdir'));
$('#catalog').html('');
loadCatalog(0, 1);
return false;
});
Но пока наш каталог на http://catalog.26th.ru/catalog/ не умеет обрабатывать параметры sortby и sortdir – так давайте исправим это.
В getResources за сортировку отвечают параметры:
&sortby – сортировка по какому-либо полю ресурса (кроме tv-параметров), например, сортировка по дате публикации: &sortby=`{«publishedon»:«DESC»}`
&sortbyTV – сортировка по tv-параметру
&sortdirTV – направление сортировки по tv-параметру (desc|asc)
&sortbyTVType – тип сортировки (string|integer)
В &sortbyTVType сразу прописываем `integer`, ибо все tv-параметры, по которым мы сортируем – числа (год, цена, число просмотров).
В &sortdirTV нам нужно записывать ASC или DESC в зависимости от get-параметра sortdir. Напишем небольшой сниппет
[[!sortbyTV]]
, который будет считывать get-параметр и подставлять нужное значение:<?php
$sort_direction = (isset($_GET['sortdir']) && $_GET['sortdir'] == 'asc') ? 'ASC' : 'DESC';
return $sort_direction;
В &sortbyTV нам нужно указывать необходимый tv-параметр. Вот, кстати, зачем понадобилось записывать число просмотров отдельно в tv-параметр – чтобы можно было использовать его в &sortbyTV. Только здесь нужно помнить, что если сортировка идет по названию, то здесь должно быть пусто. Делаем еще один сниппет
[[!sortbyTV]]
:<?php
$sort_name = isset($_GET['sortby']) ? strip_tags($_GET['sortby']) : 'hits';
if ($sort_name != 'title') return $sort_name;
return '';
Если сортировка идет по названию, мы должны указать это (и направление сортировки) в параметре &sortby – здесь воспользуемся сниппетом
[[!sort]]
:<?php
$sort_name = isset($_GET['sortby']) ? strip_tags($_GET['sortby']) : 'hits';
$sort_direction = (isset($_GET['sortdir']) && $_GET['sortdir'] == 'asc') ? 'ASC' : 'DESC';
if ($sort_name == 'title') return '"pagetitle":"' . $sort_direction . '",';
return '';
Таким образом, код ресурса «Каталог» становится таким:
[[!getPage?
&elementClass=`modSnippet`
&element=`getResources`
&tpl=`tplList`
&limit=`8`
&includeTVs=`1`
&parents=`2`
&hideContainers=`1`
&sortby=`{[[!sort]]"publishedon":"DESC"}`
&sortbyTV=`[[!sortbyTV]]`
&sortdirTV=`[[!sortdirTV]]`
&sortbyTVType=`integer`
]]
Заметьте, в параметре &sortby добавлено «publishedon»:«DESC» на тот случай, если, например, сортировка идет по числу просмотров – чтобы элементы с одинаковым числом просмотров сортировались по дате добавления.
И снова небольшая порция радости – сортировка готова.
Шаг 4. Фильтр
Добавление фильтра делаем аналогично сортировке.Добавляем html-код для фильтра в «Главную страницу» вместо строчки «Здесь будет фильтр»:
<p><b>Фильтр</b></p>
<div class="filter-group btn-group" role="group">
<button data-filter="lang" data-value="ru" type="button" class="btn btn-sm btn-default">RU</button>
<button data-filter="lang" data-value="en" type="button" class="btn btn-sm btn-default">EN</button>
</div>
<div class="filter-group btn-group" role="group">
<button data-filter="format" data-value="electro" type="button" class="btn btn-sm btn-default">Электронные</button>
<button data-filter="format" data-value="casual" type="button" class="btn btn-sm btn-default">Бумажные</button>
<button data-filter="format" data-value="audio" type="button" class="btn btn-sm btn-default">Аудио</button>
</div>
<div class="filter-group btn-group-vertical" role="group">
[[filterCategory]]
</div>
Здесь сниппет
[[filterCategory]]
выводит кнопки для Жанров, его код:<?php
$out = '';
$query = $modx->newQuery('modResource', array('parent'=>5));
$query->sortby('pagetitle','ASC');
$resources = $modx->getCollection('modResource', $query);
foreach($resources as $resource) {
if ($resource->published == true && $resource->deleted != true && $resource->hidemenu != true) $out .= '<button data-filter="category" data-value="' . trim($resource->alias, "*") . '" type="button" class="btn btn-sm btn-default"><span class="glyphicon glyphicon-unchecked"></span> ' . $resource->pagetitle . '</button> ';
}
return $out;
И добавляем обработку клика по кнопкам фильтра в catalog.js (после обработки кликов по кнопкам сортировки):
$('.filter .filter-group button').on('click', function() {
var filter_group = $(this).parent('.filter-group');
var active = $(this).hasClass('active');
var filter = $(this).data('filter');
var value = '';
if (filter == 'category') { // категорийможетбытьвыбранонесколько (checkbox)
$(this).toggleClass('active');
var categories = '';
$('.filter button[data-filter="category"].active').each(function() {
categories += $(this).data('value') + '|';
});
value = categories.substr(0, categories.length - 1);
}
else { // остальные фильтры (язык и формат) - только один вариант (radiobutton)
filter_group.find('button').removeClass('active');
if (!active) {
$(this).addClass('active');
value = $(this).data('value');
}
}
uri = setAttr(filter, value);
$('#catalog').html('');
loadCatalog(0, 1);
return false;
});
В нем учитывается, что Язык и Формат можно выбрать только какой-то один. А вот жанров можно отметить несколько – тогда будут выводиться те книги, у которых в списке жанров есть все выбранные жанры.
Таким образом, кликая по кнопкам фильтра, мы формируем адреса вида (выбранные жанры записываем в параметр category через вертикальную черту):
/catalog/?lang=ru&format=electro&category=child
/catalog/?lang =ru&category=child|fantastic
И теперь осталось опять же научить страницу catalog.26th.ru/catalog/ разбираться с этими новыми параметрами.
За фильтрацию по tv-параметрам в getResources отвечает параметр &tvFilters. Например, для строки:
/catalog/?lang=ru&category=child|fantastic
мы должны указать в этом параметре:
&tvFilters=`lang==ru,category==%*child*%,category==%*fantastic*%`
перечисление через запятую означает условие И, % соответствуют поиску по LIKE%...%
Теперь немного о звездочках. Что было бы, если бы мы не добавили их в alias страниц жанров.
Допустим, есть два жанра с похожими alias: Классика (classic) и Неоклассика (neoclassic).
И есть две книги:
– Книга_1 относится к жанрам Классика и Детективы, поэтому у нее tv-параметр category=classic||detectiv (разделитель || мы задали при добавлении tv-параметра)
– Книга_2 относится к жанру Неоклассика, поэтому у нее tv-параметр category=neoclassic
Если мы захотим выбрать только те ресурсы, у которых среди жанров есть Классика, то напишем &tvFilters=`category==%classic%`. И под такой фильтр попадут и Книга_1, и Книга_2 (т.к. поиск идет просто по строке) – а нам нужна только Книга_1.
Если же в alias жанров добавить звездочки, то получится, что у Книги_1 category=*classic*||*detectiv*, а у Книги_2 category=*neoclassic*
Поэтому при фильтре по &tvFilters=`category==%*classic*%` мы получим только Книгу_ 1.
Далее пишем сниппет
[[!filter]]
для формирования &tvFilters из get-параметров:<?php
$out = array();
$lang = isset($_GET['lang']) ? strip_tags($_GET['lang']) : '';
$format = isset($_GET['format']) ? strip_tags($_GET['format']) : '';
$category = isset($_GET['category']) ? strip_tags($_GET['category']) : '';
$categories = explode('|', $category);
if ($lang != '') $out[] = 'lang==' . $lang;
if ($format != '') $out[] = 'format==%' . $format . '%';
if ($category != '') {
foreach ($categories as $categ) {
$out[] = 'category==%*' . $categ . '*%';
}
}
return implode(',', $out);
Теперь код ресурса «Каталог» такой:
[[!getPage?
&elementClass=`modSnippet`
&element=`getResources`
&tpl=`tplList`
&limit=`8`
&includeTVs=`1`
&parents=`2`
&hideContainers=`1`
&sortby=`{[[!sort]]"publishedon":"DESC"}`
&sortbyTV=`[[!sortbyTV]]`
&sortdirTV=`[[!sortdirTV]]`
&sortbyTVType=`integer`
&tvFilters=`[[!filter]]`
]]
Облегченно выдыхаем – фильтр настроен!
Заключение
Итоговый результат – на http://catalog.26th.ru/Скрипт каталога – http://catalog.26th.ru/js/catalog.js
Дополнительные стили для каталога – http://catalog.26th.ru/css/catalog.css
Единственное, что не сделала – если юзер «натыкает» сортировку и фильтры, а затем из полученного списка перейдет на какую-нибудь страницу книги – то вся его выбранная сортировка пропадет, поэтому когда он нажмет «Назад» в браузере – то снова попадет на список по умолчанию.
Получается, надо бы записывать в сессию еще и настройки сортировки и фильтра (и количество подгруженных страниц). В дальнейшем, думаю, сделаю. Пока же добавила target="_blank" для ссылок на отдельные страницы ))
Если есть вопросы, замечания, уточнения – пишите в комментариях к статье. Критика приветствуется, но конструктивная.
А всем кто дочитал статью подарок — бесплатная книга по юзабилити «Как избежать обыденных ошибок». Получить книгу можно здесь.
Автор: Дарья Севостьянова, руководитель отдела разработки Сервиса 1PS.RU
Воеводский Михаил 10.07.2015 10:30 #
Теперь критика :)
В шаге 4 в сниппете [[filterCategory]] происходит перемешивание логики и внешнего вида. Так не стоит делать. HTML необходимо вынести в чанк, например [[$filterOuter]]:
Сниппету полезно добавить, хотя бы, 2 параметра: &tpl, &parent:
После этого код сниппета принимает вид:
Весь второй шаг меняем на использование pdoTools и pdoPage в частности: docs.modx.pro/components/pdotools/snippets/pdopage, раздел «Поддержка AJAX».
А там, заодно, и getResources полезно поменять на pdoResources :)
Это те моменты, которые бросились в глаза при беглом прочтении.
Антон Пастухов 15.07.2015 21:13 #
getResources, getPage и т.д. — дополнения, а вовсе не «плагины», плагины в MODX — это колбеки на события, грубо говоря.
Создавать пустой шаблон не надо, он создан по умолчанию и доступен всегда.
Dimension 30.09.2015 19:43 #
Сейчас сам борюсь с данной проблемой, если у кого-какое мысли — пишите не стесняйтесь :)
Вараника 02.10.2015 17:07 #
Dimension 02.10.2015 17:14 #
Также если используется фильтр tagmanager и фильтрует по определенному tv, то подружать ресурсы из пагинации совсем проблематично
Дарья Севостьянова 05.10.2015 07:25 #
1. В админке MODx элементы лежат в подпапках у основной папки «Каталог»: скриншот — catalog.26th.ru/images/screen_category.png
То есть если мы заходим в подраздел, например «Терри Пратчетт», то мы просто должны запрашивать только элементы этого раздела (а все остальные фильтры и сортировки оставить как есть). Для этого в дополнении GetResources собственно и есть параметр parents.
2. Поэтому в текущем коде (приведенном в статье) я, во-первых, перенесла код
из главной страницы в шаблон «Шаблон каталога».
Затем открыла подраздел (то есть ресурс «Терри Пратчетт») и выставила для него тоже шаблон «Шаблон каталога».
Теперь на странице раздела
catalog.26th.ru/catalog/terri-pratchett/
выводится полный каталог.
Надо ограничить его до раздела.
3. В коде шаблона «Шаблон каталога» внесла правки:
заменила
на
То есть я сначала определяю переменную uri как просто /catalog/
Затем, если мы находимся в подразделе (id:ne=`1`), то добавляю к uri параметр cat_id, куда записываю ID раздела.
Затем вызываю остальные скрипты (только из /js/catalog.js удалила первую строчку «var uri = '/catalog/';» ).
4. И последнее:
в коде страницы «Каталог», где собственно вызывается [[!getPage?
заменила
на
(код сниппета parents, думаю, можно не приводить, он просто считывает GET-параметр cat_id, либо возвращает 2, если такого параметра нет)
И все, рабочий пример тут:
catalog.26th.ru/catalog/terri-pratchett/
это подраздел, но работают все фильтры и сортировки именно в пределах раздела
Dimension 05.10.2015 11:36 #
Также в идеале надо, чтобы URL страницы тоже менялся при фильтрах\подрузке товаров, при этом желательно без get параметров (без? в URL). А также возможность вставки текста — все это надо для SEO.
Вот seofilter.itlogic-ua.com/ наглядный пример SEO фильтра.
Было бы очень круто, если такой же получилось сделать
Дарья Севостьянова 05.10.2015 12:16 #
>> чтобы URL страницы тоже менялся при фильтрах\подрузке товаров, при этом желательно без get параметров (без? в URL). А также возможность вставки текста — все это надо для SEO.
Все это, конечно, можно сделать. Просто в js-скрипте формируете URL по своим правилам, а уже на странице /catalog/ «распарсиваете» его как вам нужно.
Про вставку текстов надо думать отдельно.
Вот только сперва надо будет подумать, а нужен ли вам тогда тут ajax.
Dimension 05.10.2015 14:15 #
Также в catalog.js у Вас не удалена строчка var uri = '/catalog/';
Проделал всё выше написанное на локалке — подгрузка товаров из категории так и не работает.
URL откуда должны браться товары — не меняется — так и остается /catalog/
SSMaker.ru/510177eb/
Сейчас пытаюсь сделать как описано тут modx.ws/urok-modx-ajax-zagruzka-resursov, но пока тоже безрезультатно((
Искатель 17.12.2015 01:15 #
Владимир 19.12.2015 19:10 #
на
Тем самым мы будем парсить ту же страницу на которой и сидим, только данные будем выводить из определенного блока #catalog>.event
Пример реализации: sochicb.com/guest/events/
Сергей 12.01.2016 15:10 #
Дарья Севостьянова 13.01.2016 03:37 #
Олег 20.02.2016 23:33 #
Подскажите пожалуйста как переделать что аякс работал при клике по пагинации, а кнопки Еще вообще не было? и не добавлять продукты, а следующие вставлять на место предыщих, как обычная пагинация, только без перезагрузки!
Заранее спасибо!
Дарья Севостьянова 24.02.2016 12:33 #
Убираете кнопку «Еще». На клик по ссылкам пагинации отправляете ajax-запрос, в котором передаете конкретный номер страницы.
Результат ajax-запроса выводите в блок с id=«elements»:
Но вам лучше взять готовый сниппет PdoPage: docs.modx.pro/components/pdotools/snippets/pdopage
Он все это умеет — работать как обычная пагинация, но без перезагрузки страницы.
Я в своем примере не использовала его именно из-за необходимости подгружать новые элементы к уже показанным.
А вам PdoPage лучше подойдет.
Егор 26.05.2016 19:36 #
Застопорился на последнем пункте, подскажите пожалуйста.
1. Вывод ресурсов через getPage здесь
2. Код сниппета фильтра в общем то такой же как у вас + getPage:
3. Здесь можно посмотреть наименования категорий в data-value списка li и глянуть catalog.js в source, может там что сломал.
Егор 26.05.2016 20:54 #
soldat 30.05.2016 13:09 #
Дарья Севостьянова 06.06.2016 04:43 #
soldat 06.06.2016 06:56 #
Т.е. Я использовал $array = array и вместо квадратных скобок закругленые.
С вашим примером у меня почему то не получалось
Сервис 1PS.RU 06.06.2016 07:32 #
В статье заменила там квадратные скобки на array(), чтобы в заблуждение никого больше не вводить.
soldat 06.06.2016 07:45 #
Павел 13.08.2016 12:52 #
complex.18tr.ru/proektyi-i-czenyi/
Данных в массиве ведь нет
Двоишник 17.09.2016 08:31 #
helen85 14.09.2016 16:08 #
В ресурсе статьи пишу:
[[!getPage?
@articlePaging
&elementClass=`modSnippet`
&element=`getResources`
&showHidden=`1`
&hideContainers=`1`
&limit=`4`
&tpl=`article`
&sortby=`id`
&sortdir=`ASC`
&includeTVs=`image`
&includeContent=`1`
&parents=`5`
&pageNavVar=`page.nav`]]
[[!+page.nav]]
Отдельный файл article.js
js точно такое же как и вас.
И в шаблоне главной страницы:
Дарья Севостьянова 19.09.2016 07:36 #
helen85 20.09.2016 15:27 #
Двоишник 17.09.2016 01:15 #
Дарья Севостьянова 19.09.2016 07:41 #
У меня же эта страница либо возвращает список ресурсов (если они есть), либо пустую строку (если ресурсов нет). Поэтому в коде в ajax-запросе к этой странице кнопка «Еще» показывается только когда мы что-то получили в ответ. А если получили пустую строку, то кнопка не показывается.
То есть посмотрите, что у вас возвращает ajax-запрос в случае, когда показывать нечего (может, там действительно пробел какой-нить затесался).
Двоишник 19.09.2016 12:32 #
Каталог у меня на пустом шаблоне:
Скрин
Не совсем понимаю как посмотреть что у меня возвращает ajax-запрос?
Посмотрел что происходит когда нажимаешь на кнопку, появляются 2 блока один пустой один скрытый и так до бесконечности:
Скрин
Дарья Севостьянова 19.09.2016 13:07 #
вот тут:
добавьте вывод alert:
Здесь переменная html — это и есть то, что возвращает ajax-запрос.
То есть посмотрите, какой алерт выдается когда подгружать уже должно быть нечего: «12» или что-то есть между «1» и «2»
Двоишник 19.09.2016 13:21 #
О чём это говорит, для меня это тёмный лес?
Дарья Севостьянова 19.09.2016 18:17 #
Попробуйте в скрипте /js/catalog.js в коде функции
function loadCatalog(showPage, preloadPage)
заменить
на
Двоишник 19.09.2016 18:39 #
Что несёт в себе этот кусочек кода какие функции что он изменил?
Можно ли сделать чтобы, содержимое загруженного контента, было видно в коде HMTL страницы?
Ищё странный момент, у Вас в каталоге загрузка карточек заметно быстрее чем у меня, у меня это дольше всё происходит, с чем это может быть связано?
Прошу прощения за множество вопросов.
Дарья Севостьянова 19.09.2016 19:16 #
>> Можно ли сделать чтобы, содержимое загруженного контента, было видно в коде HMTL страницы?
Не совсем понятен этот вопрос.
>> Ищё странный момент, у Вас в каталоге загрузка карточек заметно быстрее чем у меня
Вряд ли смогу ответить. Возможно, это из-за каких-нибудь настроек Денвера.
Двоишник 19.09.2016 19:28 #
>> Не совсем понятен этот вопрос.
Я имел виду, что если в браузере посмотреть код страницы то там то что подгружено нету, есть фильтр есть сортировка а между ими: div catalog, можно ли сделать чтобы поисковики видели этот контент?
Двоишник 19.09.2016 22:43 #
Дарья а как можно сделать чтобы, выводилось сколько найдено карточек, например нажал на детективы показывается сколько было найдено?
Дарья Севостьянова 20.09.2016 04:51 #
Делаем так:
1. В код ресурса «Каталог» (/catalog/) перед
добавляем строчку
Туда будет записываться общее число найденных карточек. Делаем его скрытым, чтобы это число не показывалось перед каждыми 8-ю карточками.
2. Далее в шаблоне для Каталога перед строчкой
добавляем строку
Сюда мы будем вставлять число карточек из нашего запроса к /catalog/
3. Вносим изменения в скрипт /js/catalog.js
В нем ajax-запрос принимает такой вид (комментарии в коде):
Должно заработать вот как тут — catalog.26th.ru/
Двоишник 20.09.2016 11:40 #
Двоишник 20.09.2016 12:12 #
Дарья Севостьянова 20.09.2016 13:16 #
То, что верхние карточки остаются на странице, никак не увеличивает нагрузку (они же уже не перезагружаются заново).
Двоишник 20.09.2016 13:23 #
Двоишник 20.09.2016 12:44 #
Дарья Севостьянова 20.09.2016 13:14 #
добавьте строчку
Тогда будет показывать «Найдено: 0».
Двоишник 20.09.2016 13:22 #
Двоишник 21.09.2016 12:36 #
Посоветуйте Пожалуйста что делать?
Двоишник 20.09.2016 13:32 #
Дарья Севостьянова 20.09.2016 15:13 #
Подсказка: Что сейчас происходит при клике по кнопкам фильтра — составляется переменная uri, которая хранит параметры фильтра (они дописываются к ajax-запросу, и поэтому подгружаются нужные карточки).
Итого что вам нужно сделать: в скрипте везде, где задается переменная uri, дописать ее сохранение в localStorage (пример сохранения есть в той ссылке, которую вы указали).
И затем в скрипте в самом начале перед кодом
сначала считывать uri из localStorage, а затем вызывать loadCatalog
Двоишник 20.09.2016 16:24 #
Честно говоря навряд ли я смогу это сделать.
Двоишник 19.09.2016 14:04 #
Можно Вам в личку написать и как это сделать? У меня сайт на Denvere, я бы мог скинуть данные для входа на Пк, чтобы Вы смогли посмотреть?
Двоишник 19.09.2016 17:15 #
Самому не получается не как решить эту проблему,
Двоишник 18.09.2016 22:26 #
Дарья Севостьянова 19.09.2016 07:50 #
В статье тоже поправила, спасибо!
По кнопке «Еще» отписала в комментарии выше.
soldat 20.09.2016 20:52 #
Дарья Севостьянова 10.10.2016 08:10 #
Опишу кратко по шагам (уж простите, что в скриншотах, но копировать сюда целиком код совсем неудобно):
1. Параметр родителя (автора) будем передавать в GetPage в параметр &parents:
catalog.26th.ru/images/filter_authors_1.png
2. Где сниппет !parents делает простую вещь — получает get-параметр cat_id:
catalog.26th.ru/images/filter_authors_2.png
3. В «Шаблоне каталога» добавим чекбоксы для Авторов:
catalog.26th.ru/images/filter_authors_3.png
4. Где сниппет filterAuthors (скопирован почти полностью с filterCategory) выводит список доступных категорий с авторами:
catalog.26th.ru/images/filter_authors_4.png
в параметр data-filter пишем cat_id (это название нашего get-параметра для авторов)
5. Внесем небольшое изменение в сниппет filter:
catalog.26th.ru/images/filter_authors_5.png
(чтобы ниже использовать один кусок js-кода для жанров и для авторов)
6. В файле /js/catalog.js изменения коснулись строк 30, 33 и 34:
catalog.26th.ru/images/filter_authors_6.png
Итого: по клику на чекбоксы с авторами из них формируется параметр cat_id, который содержит номера категорий (авторов) через запятую, все это аяксом передается на сниппет getPage, который выводит ресурсы из указанных категорий.
Результат тут — catalog.26th.ru/ (справа под жанрами теперь выводятся авторы)
Двоишник 22.10.2016 18:51 #
Я всё не могу разобраться как сделать чтобы при перезагрузки страницы выбранные пункты меню сохранялись при помощи localStorage.
Научите пожалуйста, как это делается?
На форумах в большинстве случаев футболят или молчат, советуют обратиться к создателю каталога.
Дарья Севостьянова 23.10.2016 06:52 #
В скрипте catalog.js вам нужно дополнить обработку событий «клик по кнопкам сортировки», «клик по кнопкам фильтра» — там нужно:
1. Сохранять в localStorage значение uri.
2. Сохранять в localStorage какие фильтры отмечены (или какая сортировка выбрана).
Затем нужно в самом начале скрипта (перед «загружаем начальный блок» считывать uri из localStorage).
И нужно добавить функции, которые будут брать значения выставленных фильтров и сортировки из localStorage и расставлять их в фильтрах (чтобы были нужные галочки отмечены в фильтре).
Алгоритм, я думаю, понятен. Писать полностью код за вас не буду, простите, не имею на то времени. И это не тема данной статьи.
Не можете разобраться самостоятельно — есть множество курсов по javascript, там преподаватели с радостью вам помогут и все разъяснят.
Двоишник 23.10.2016 17:01 #
Далее я создал снипет showCategories2 там тоже вставил весь код скопировав с showCategories и изменил parent номер папки жанров на номер нужной папки. Далее я создал твполе: category2 аналогичное category, в возможных значениях указал id нужной папки.
Далее я в скрипте дописал так: на следующем шаге я зашёл в тупик, пробовал создать снипет filter2 и прописать там везде category2 но не работает подскажите пожалуйста как это сделать правильно если конечно это относится к теме статьи?
Двоишник 24.09.2016 03:42 #
У меня последняя клавиатура осталась, две уже не подлежат восстановлению)
Дарья Севостьянова 10.10.2016 08:12 #
Двоишник 07.10.2016 01:10 #
Дарья Севостьянова 10.10.2016 08:16 #
Первая показывает блок с результатами, вторая прокручивает страницу к началу этого блока.
Подробнее об анимации в jQuery можете прочитать здесь: jqbook.net.ru/jquery/Effects
Двоишник 10.10.2016 14:40 #
Двоишник 07.11.2016 09:09 #
пример:
Детективы (20)
Зарубежная литература (5)
Где нужно внести изменения, в скрипте и снипете filtercategory, правильно я понимаю?
Павел 25.01.2017 13:08 #
А если элементов будет 11 то div class=«row» останется не закрытым, что делать в таком случае?
Александр Тюкалов 04.02.2018 10:22 #
Константин 31.01.2017 11:31 #
И пришлось колхозить сниппет для получения кол-ва ресурсов:
т.к. использовать заначение из [[+total]] у меня не получилось, видимо что-то с типом данных…
Владислав 18.02.2017 08:42 #
/catalog/?awarded=ok&page=1, когда добавляем еще один параметр соответственно получаем: ?awarded=ok&filter=audio&page=1&_=1487399524217. Можно ли сделать что бы первый выбранный параметр awarded (либо любой другой) при выборе второго параметра затирался и мы получили просто &filter=audio? Помогите пожалуйста, 2 день не получается, да и c js я на ты. Спасибо! С меня печенька :D
lampa17 31.01.2018 13:10 #
Александр Тюкалов 04.02.2018 10:19 #
– плагин getPages 1.2.4-pl => – плагин getPage (!s) 1.2.4-pl
Исправьте!
Max Brannigan 15.11.2024 08:48 #
«Теперь открываем файл catalog.26th.ru/js/catalog.js и в нем прописываем функции для подгрузки списка ресурсов:»<br />
<br />
Далее автор приводит код, который нужно добавить в js-файл. Я не пойму, этот файл изначально пустой или там уже присутствует какой-то код?<br />
Если у кого-то сохранилось, можете прислать ссылку на полный файл?<br />
Ветка старая, ссылки на файлы, которые приводит автор, уже не работают.<br />
Заранее спасибо.