[pdoTools] 1.9.6-rc Ajax пагинация в pdoPage

Предлагаю вам обновление pdoTools с одной очень интересной функцией — возможностью работы pdoPage через ajax.

С этого момент pdoPage может выдавать JSON и прерывать работу движка при соответствии запроса трём характеристикам:
  1. Запрос сделан при помощи XMLHttpRequest, то есть — ajax.
  2. В запросе содержится переменная, указанная у сниппета в &pageVarKey. По умолчанию, это page.
  3. У сниппета включен параметр &ajax=`1`.
То есть, по умолчанию достаточно просто включить &ajax и отправлять к странице GET запросы типа:
$.get('document.html?page=5', function(response) {
	console.log(response);
}, 'json');
И в ответ вы получите JSON c результатами работы, пагинацией и служебными данными: номер страницы, сколько всего страниц и сколько всего результатов.

На этом сайте и на bezumkin.ru все страницы с пагинацией уже переведены на работу через ajax — можно тестировать, а под катом вас ждёт готовый код для внедрения у себя.

Написать универсальный javascript для всех сайтов с пагинацией не представляется возможным, хотя бы потому, что я не знаю, какие у вас там используются идентификаторы на странице. Так что показываю готовый код с комментариями, который вы легко сможете адаптировать для себя.

Первым делом оборачиваем вызов pdoPage в блоки с идентификаторами:
<div id="tickets-wrapper">
	<div id="tickets-rows">
		[[!pdoPage?
			&element=`getTickets`
			&parents=`0`
			&ajax=`1`
		]]
	</div>
	[[!+page.nav]]
</div>
Внутри [[+page.nav]] у нас div с классом pagination — так в pdoPage по умолчанию.

Теперь вешаем javascript на эту разметку:
// Работаем только при поддержке браузером History Api
if (window.history && history.pushState) {
	var JustLoaded = true;

	// Задаём функцию загрузки страницы
	function loadPage(href) {
		// Обёртка всего блока
		var parent = $('#tickets-wrapper');
		// Индикация загрузки контента
		parent.css({opacity: .3});
		// Если загружается первая страница, то у неё нет параметра ?page=1 -
		// нужно добавить самостоятельно, иначе pdoPage не отзовётся
		if (!href.match(/page=\d+/)) {
			href += href.match(/\?/)
				? '&page=1'
				: '?page=1';
		}
		// Запрос на сервер по указанной ссылке
		$.get(href, function(response) {
			// Дальше работаем только если пришел нормальный ответ
			if (response && response['total']) {
				// Вставляем контент и пагинацию в заранее подготовленные элементы
				parent.find('#tickets-rows').html(response['output']);
				parent.find('.pagination').html(response['pagination']);
				// Убираем индикацию загрузки
				parent.css({opacity: 1});
			}
		}, 'json');
	}

	// Вешаем обработчик ссылок в нашей разметке на кнопки пагинации
	$(document).on('click', '#tickets-wrapper .pagination a', function(e) {
		e.preventDefault();
		var href = $(this).prop('href');
		history.pushState({href: href}, '', href);
		loadPage(href);
		JustLoaded = false;
	});

	// Вешаем обработчик на переключение кнопок браузера взад-вперёд
	$(window).on('popstate', function(e) {
		if (!JustLoaded && e.originalEvent.state && e.originalEvent.state['href']) {
			loadPage(e.originalEvent.state['href']);
		}
		JustLoaded = false;
	});

	// При первой загрузке страницы помещаем в историю текущий адрес
	history.replaceState({href: window.location.href}, '');
}
Выглядит страшно, но если убрать все комментарии, то останется одна небольшая функция и 2 обработчика событий.

В итоге, вы получаете нормальную ajax навигацию в современных браузерах, с сохранением параметров в url, и обычную загрузку страниц поисковиками и древним IE6.

Вот еще пара наворотов, которые можно добавить после
parent.css({opacity: 1});

1. Прокрутка контента страницы вверх:
$('html, body').animate({
	scrollTop: parent.position().top + 50 || 0
}, 0);
К parent.position().top можно прибавить несколько пикселей для более точного позиционирования. На стандартной верстке Bootstrap3 нужно плюсовать высоту навигационной панели — 50px.

2. Если вы используете мой сниппет Title, то неплохо бы эмулировать его работу и при ajax навигации.
var delimeter = ' / ';
var title = [];
var tmp = $('title').text().split(delimeter);
for (var i = 0; i < tmp.length; i++) {
	if (i === tmp.length - 1 && response.page && response.page > 1) {
		title.push('стр. ' + response.page + ' из ' + response.pages);
	}
	if (!tmp[i].match(/^стр\./)) {
		title.push(tmp[i]);
	}
}
$('title').text(title.join(delimeter));

Всем успешных ajax навигаций!
Василий Наумкин
26 октября 2014, 04:32
modx.pro
10
8 916
+10

Комментарии: 44

Василий Краковецкий
26 октября 2014, 11:01
+1
И это теперь из коробки?! Ну спасибо! Больше не нужно костыли собственного изготовления!
    Andrei D.
    26 октября 2014, 16:00
    0
    Вот это новость!
      Владимир Дремучий
      26 октября 2014, 19:22
      0
      Работает все великолепно. Спасибо за ajax.
      Василий Столейков
      27 октября 2014, 08:39
      0
      Классно! Компоненты постепенно переходят на ajax из коробки — становится всё приятнее с ними работать. Спасибо!
        Максим Кузнецов
        28 октября 2014, 10:23
        0
        Нашел небольшой баг с js-скриптом — если с обычной страницы перейти на страницу с пагинацией (или наоборот) и после воспользоваться кнопками браузера следующая/предыдущая страница — поле контента (даже если возвращаешься на страницу без пагинации) применяет свойство opacity: .3 и останавливается на этом.
          Василий Наумкин
          28 октября 2014, 11:37
          0
          Это из-за
          // При первой загрузке страницы помещаем в историю текущий адрес
          history.replaceState({href: window.location.href}, '');

          Как сделать лучше — не придумал.
            Василий Наумкин
            06 ноября 2014, 05:37
            0
            Добавил индикацию первой загрузки через переменную JustLoaded — проблема решена.
            Пашок
            Пашок
            08 декабря 2014, 11:24
            0
            БлагоДарю, Василий! Порадовал!
              Андрей
              21 декабря 2014, 03:45
              0
              Приветствую. Есть вопрос по поводу запроса $.get(href, function(response){… — он возвращает объект, в котором responseText = содержимое страницы, откуда берутся response['output'] и response['pagination'] что-то не совсем пойму.
              Пишу, так как в виде из коробки не работает. Стопориться как раз на запросе.
                Василий Наумкин
                21 декабря 2014, 07:23
                0
                С этого момент pdoPage может выдавать JSON и прерывать работу движка при соответствии запроса трём характеристикам:
                1. Запрос сделан при помощи XMLHttpRequest, то есть — ajax.
                2. В запросе содержится переменная, указанная у сниппета в &pageVarKey. По умолчанию, это page.
                3. У сниппета включен параметр &ajax=`1`.

                Что-то из этого ты упустил.
                  Андрей
                  21 декабря 2014, 13:29
                  0
                  [[!pdoPage?
                                  &element=`msProducts`
                                  &tpl=`catalogListitem`
                                  &includeThumbs=`270х320`
                                  &limit=`12`
                                  &totalVar=`total_products`
                                  &tplPageWrapper=`
                                  @INLINE
                                  <div class="beta-pagination text-center"><div class="pagination">[[+prev]][[+pages]][[+next]]</div></div>
                                  `
                                  &tplPageActive=` @INLINE <a class="is-active">[[+pageNo]]</a>`
                                  &tplPagePrev=`@INLINE <a href="[[+href]]"><i class="fa fa-chevron-left"></i></a>`
                                  &tplPageNext=`@INLINE <a href="[[+href]]"><i class="fa fa-chevron-right"></i></a>`
                                  &tplPage=`@INLINE <a href="[[+href]]">[[+pageNo]]</a>`
                                  &tplPagePrevEmpty=``
                                  &tplPageNextEmpty=``
                                  ]]
                              [[!+page.nav]]
                  Не могу понять, что не так, js такой же, как в примере.
                  prntscr.com/5j94e2
                  prntscr.com/5j94il
                    Василий Наумкин
                    21 декабря 2014, 14:06
                    0
                    Ты действительно не понимаешь, что сниппету нужно указать &ajax=`1`?
                      Андрей
                      21 декабря 2014, 14:32
                      0
                      Извиняюсь, параметр был включен, я скопировал уже после того, как убрал его. Так что не в этом дело.
                        Василий Наумкин
                        21 декабря 2014, 14:36
                        0
                        Версия pdoTools 1.9.6 или выше?
                          Андрей
                          21 декабря 2014, 14:41
                          0
                          Блин, 1.9.3. В этом проблема. Еще раз извиняюсь, был уверен что последняя версия стоит, так как не горела кнопка «обновить»
              Kirill A. Rusanov
              26 декабря 2014, 11:00
              0
              Замечен небольшой косяк. Например, видно на главной странице этого замечательного сайта. После перехода на вторую страницу через пагинатор, нажатие на кнопку «назад» в браузере ни к чему не приводит. Потом, после следующих кликов по пагинатору все ок. Проблема в переменной JustLoaded, решение: JustLoaded = false в обработчике кликов для пагинатора.
                Василий Наумкин
                26 декабря 2014, 11:39
                0
                Поправил, спасибо!
                  Wassi Wassinen
                  26 декабря 2014, 17:39
                  0
                  Василий, если есть возможность, подскажи, как сделать кнопку «Показать еще 50 (зависит от лимита на странице) товаров» на аяксе вместо обычной пагинации? Очень востребованная штуковина. Пример можно посмотреть на юлмарте.

                  Если штука сложная, готов проспонсировать доработку в стандартный pdoPage.

                  Заранее благодарен.
                    Василий Наумкин
                    26 декабря 2014, 17:40
                    0
                    Я такие штуки не люблю, потому что нельзя дать ссылку на конкретную страницу.
                      Wassi Wassinen
                      26 декабря 2014, 17:53
                      0
                      А некий якорь вставлять в выдачу каждых N товаров?
                        Василий Наумкин
                        26 декабря 2014, 18:03
                        0
                        А потом что? При открытии страницы сразу делать 5 ajax запросов, чтобы загрузить 5 страниц и дойти до 6й?

                        Или грузить сразу 6ю и 2 ссылки «загрузить до» и «загрузить после»?

                        И какой профит, по сравнению с нормальными страницами? Я его не вижу. Разве что радоваться, что «каквконтактике».
                          Wassi Wassinen
                          27 декабря 2014, 02:38
                          0
                          Мне до вконтактиков дела нет, хотя и у них есть чему поучиться. Пользователям удобно. Им важно не ссылки давать, а товар искать. Кнопку Ткнул и тебе подгрузились еще тридцать товаров. Заскроллил вперед и вернулся к предыдущему экрану без всяких «назад-вперед » и переключения пагинации. Тем более, что доля мобильных посещений выросла с 7% до внушительных 32%. На мобильном проскроллить страницу вниз и вверх в разы удобнее. А в пагинацию попасть пальцем — это отдельная история.

                          Сайт опрашивали анонимно. От 10к посещений в сутки. Сайт-каталог. 71,4% выбрали подгрузку товаров по кнопке, как наиболее удобную.

                          Это я не к тому, что я весь из себя прав. В блого-социальной нише пагинация функциональнее. Когда же речь идет о каталоге с фильтрами — клиентам незачем ссылаться на пагинацию, они скидывают ссылки на товар или лайкают во вконтактике или лицокниге.

                          Спасибо, что ответил.
                            Василий Наумкин
                            27 декабря 2014, 07:44
                            0
                            Аргументы хорошие и логичные. Есть, правда, и контраргументы:
                            • The “footer” of the page will be typically impossible to reach.
                            • Currently there is no way to cancel or opt-out of the behavior.
                            • There is no permalink to a given state of the page.
                            • Dynamically adding more content to the page increases the memory footprint of the browser. Depending on the browser, this could account for around 50megs of RAM.
                            • Analytics will not immediately capture the event, so custom configuration is required.
                            www.infinite-scroll.com/

                            Для добавления «в коробку» нужно решить хотя бы основную проблему — прямую ссылку на страницу. Вконтакте есть хороший пример такой работы.

                            Если ты готов оплатить 3500 рублей, то я добавлю в pdoPage ajax навигацию двумя способами: по страницам и прокруткой.
                              Wassi Wassinen
                              27 декабря 2014, 15:42
                              0
                              Деньги переведу в понедельник. Рад, что ты прислушался. Спасибо!
                              Вконтакте схема немного другая. Там контент подгружается динамически при прокрутке по вертикали. Это действительно нагружает систему. Поэтому в каталогах используют кнопку «Показать еще 30 товаров». Они подгружаются на ту же страницу. Это ничем по ресурсоемкости не отличается от пагинации и в то же время очень удобно. Именно о таком решении я рассказывал.
                                Wassi Wassinen
                                27 декабря 2014, 15:44
                                +1
                                Т.е., в контексте коммерции (магазина) интересен именно такой вариант — подгрузка по кнопке.
                                  Wassi Wassinen
                                  28 декабря 2014, 14:54
                                  +2
                                  Отправил через спасибо 3500Р.
                  Николай
                  17 июля 2015, 11:47
                  0
                  Василий, добрый день. Заметил такую особенность. Если вызывать pdoPage кэшированным и на странице уже есть вывод, например крошек, то в крошки выводится контент из pdoPage. С чем это может быть связано? Также при кэшируемом вызове, первый раз пагинация отображается, после пропадает.
                    Василий Наумкин
                    17 июля 2015, 11:53
                    0
                    Сниппет для пагинации нельзя вызывать кэшированным.

                    Очень надеюсь, что не придётся объяснять — почему.
                      Николай
                      17 июля 2015, 11:54
                      0
                      Окей, возможно что-то не дочитал в документации. Исправлюсь.
                        Василий Наумкин
                        17 июля 2015, 12:33
                        1
                        0
                        Видимо, объяснить всё же нужно.

                        Сниппет пагинации должен принимать параметр с номером страницы, чтобы выводить те или иные ресурсы, в зависимости от него. Соотвественно, он должен вызываться всегда, при каждой загрузке страницы. А кэшированные сниппеты вызываются только один раз — в этом сам смысл кэширования.

                        Отсюда следует простейший вывод, что такие сниппеты, как pdoPage и getPage нельзя кэшировать в принципе, иначе они не будут работать.
                          Николай
                          17 июля 2015, 12:41
                          0
                          Спасибо за доходчивое объяснение. Вопрос снят.
                    Николай
                    17 июля 2015, 12:43
                    0
                    Василий, немного не в теме пишу, но к сожалению ответа не получил ни от кого вот по какому вопросу.
                    modx.pro/help/5339/. Если не сложно, не могли бы вы подсказать, как правильно делать. А то приходится для фильтров использовать getProducts, а у него есть свои недостатки.
                      Сергей
                      12 октября 2015, 21:12
                      0
                      Василий приветствую! Сколько ты возьмешь денег что бы записать подробный видео урок о том как выводить настроить pdoPage что бы он выводил ресурсы, и можно было переключатся по ресурсам, без перезагрузки общей страницы. Простите что пишу своими словами, только что начинаю изучать не знаю терминологию.
                        Сергей
                        12 октября 2015, 21:14
                        0
                        Вот мой чанк ни чего не работает

                        [[!pdoPage?
                        		                &element=`pdoResources`
                        		                &showHidden=`1`
                        		                &limit=`6`
                        		                &pageLimit=`5`
                        			            &parents=`3`
                        			            &cache=`1`
                                                &cacheTime=`2600000`
                        			            &cache_user=`1`
                        			            &pageNavVar=`nav-page`
                        			            &ajax=`1`
                        			            &tpl=`ListPortTpl`
                        			            &includeTVs=`port-teg, image-small, image-big`
                        		            ]]
                        точнее все работает, в url ( /portfolio/?page=2), но идет перезагрузка общей страницы. А нужно что бы только перезагружалась область вывода ресурсов в определенном блоке на сайте.
                          Сергей
                          12 октября 2015, 21:15
                          0
                          Если оборачивать в
                          <div id="pdopage">
                              <div class="rows">
                          </div>
                              [[!+page.nav]]
                          </div>
                          то почему то 1 страница работает, на 2 переключает, но ресурсы не активные....((( как это сделать не понятно
                            Евгений Лебедев
                            12 апреля 2017, 20:37
                            0
                            Возникла проблема при использовании ajax пагинации с помощью кнопки «Загрузить еще» и мультиязычности. В шаблоне &tpl используется перевод фразы [[%babel.download? &topic=`translate` &namespace=`babel`]]. При загрузке страницы перевод выводится в том правильном контексте. Но после попытки подгрузить следующие страницы аяксом перевод срабатывает в контексте, который настроен по умолчанию. На сайте два языка: русский и английски. По умолчанию используется русский. Однако когда в английском варианте сайта начинаешь подгружать другие страницы, то перевод выводит русский. Если в шаблон вставить [[++cultureKey]] то выводит после подгрузки «ru», а должно «en». Как поправить?
                              Евгений Лебедев
                              12 апреля 2017, 21:45
                              0
                              Нашел для себя пока решение вот таким способом: [[*context_key:is=`web`:then=`Скачать`:else=`Download`]]
                              Владислав
                              11 июня 2017, 03:48
                              0
                              Приветствую! Мучаюсь с проблемой… надеюсь на помощь
                              При ajax-загрузке контента с помощью pdopage перестают работать скрипты слайдеров и галерей, потому что они работают только с контентом, который был загружен во время загрузки страницы. Перепробовал уже
                              $('body').on( 'click', '.btn-more', function() { });
                              и
                              pdoPage.callbacks['after'] = function(config, response) { };
                              и
                              $(document).ajaxSuccess(function() { };

                              ничто не отрабатывает правильно. При вызове обычного alert, изначально появляется модальное окно, а уже потом подгружается контент, из чего делаю вывод, что при клике по кнопке с классом .btn-more изначально загружаются скрипты, а уже потом подгружается контент с помощью ajax, с которым подгруженные скрипты уже не работают. Помогите понять как решить проблему
                                Владислав
                                12 июня 2017, 05:44
                                0
                                Всем привет еще раз!
                                Получилось заставить работать галерею с помощью ajaxStop. Правда тут возникла другая сложность — после подгрузки контента ajax'ом функция, которая была отработана во время изначальной загрузки страницы продолжает работать и после загрузки контента ajax'ом, при этом дублируя результаты отработки после каждого нажатия на кнопку btn-more. Пока не могу понять как обнулить результаты отработки функции и одновременно снова загрузить скрипт.

                                $(function(){
                                var $gallery = $('.rows a').simpleLightbox();
                                
                                $gallery.on('show.simplelightbox', function(){
                                console.log('Requested for showing');
                                })
                                .on('shown.simplelightbox', function(){
                                console.log('Shown');
                                })
                                .on('close.simplelightbox', function(){
                                console.log('Requested for closing');
                                })
                                .on('closed.simplelightbox', function(){
                                console.log('Closed');
                                })
                                .on('change.simplelightbox', function(){
                                console.log('Requested for change');
                                })
                                .on('next.simplelightbox', function(){
                                console.log('Requested for next');
                                })
                                .on('prev.simplelightbox', function(){
                                console.log('Requested for prev');
                                })
                                .on('nextImageLoaded.simplelightbox', function(){
                                console.log('Next image loaded');
                                })
                                .on('prevImageLoaded.simplelightbox', function(){
                                console.log('Prev image loaded');
                                })
                                .on('changed.simplelightbox', function(){
                                console.log('Image changed');
                                })
                                .on('nextDone.simplelightbox', function(){
                                console.log('Image changed to next');
                                })
                                .on('prevDone.simplelightbox', function(){
                                console.log('Image changed to prev');
                                })
                                .on('error.simplelightbox', function(e){
                                console.log('No image found, go to the next/prev');
                                console.log(e);
                                });
                                });
                                $(document).ajaxStop(function() {
                                
                                var $gallery = $('.rows a').simpleLightbox();
                                
                                });
                                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                                  44