Постраничный вывод - это способ вывода большого количества объектов на сайте. Классический пример применения этого способа - список страничек вида "1 2 3 4 ..." под результатами поиска в поисковых системах. Записи в блоге, товары в каталоге, фотографии в альбоме, новости в ленте - все это списки, потенциально содержащие большое количество узлов. Вывод их единым списком на одной странице может быть либо сильно замедлен, либо вообще невозможен. В этой главе мы расскажем о различных подходах к организации постраничного вывода узлов средствами XS2.
С точки зрения XS2 постраничный вывод, это вывод некоторого диапазона из списка узлов, выбранных из базы. К примеру, перед нами стоит задача вывести все товары, находящиеся в некоторой категории в каталоге, по десять товаров на странице. Для этого нам понадобится решить две подзадачи:
На странице показать только те товары из общей выборки, которые относятся к данной странице (показ диапазона)
Под списком товаров из диапозона показать панель с номерами страниц для пролистывания
Для того, чтобы облегчить жизнь разработчику при решении этих подзадач, создан свободно распространяемый модуль XS2 "Постраничный вывод" (pager). После подключения этого модуля к проекту и включения его метода "init" в процессор метода, разработчик может вызывать функции, обеспечивающие вывод диапазонов узлов и расчет кол-ва страниц.
Ниже приведен код шаблона, выводящего товары текущей категории каталога. Следует обратить особое внимание на то, что метод, в котором используются функции из модуля "pager", должны обязательно иметь процессор, в котором включен метод init: xs2Fetch(array('mod'=>"pager" ,'met'=>'init')).
Разберем подробнее код Листинга 28. В строке №1 мы выбираем все товары из категории, идентификатор которой передан в метод через первый параметр. В строке №2 мы "выкусываем" из списка всех товаров категории, только товары из диапазона, относящегося к текущей странице (она передается через GET-параметр "p"). Для этого мы используем функцией из модуля "pager" pager_getPage. Она имеет следующие параметры:
nodes - полный список узлов, из которого надо получить диапазон.
nodesOnPage - по сколько узлов на странице нужно выводить.
page - текущая страница, для которой нужно вернуть диапазон узлов.
var - имя переменной Smarty, в которую следует вернуть диапазон.
В нашем примере мы выводим по десять товаров на странице. Это значит, что если, например, в категории имеется 32 товара, то для страницы 3, функция pager_getPage вернет товары с 21 по 30 включительно, а для страницы 4 - с 31 по 32.
В строках №№4-7 мы производим вывод товаров из текущего диапазона. В строках №№12-16 мы обеспечиваем валидное значение текущей страницы: если номер страницы не передан через параметр "p", мы считаем, что производится вывод первой страницы. В строке №17 мы определяем количество страниц исходя из общего количества узлов. Для этого мы используем функцию из модуля "pager" pager_getPage. Она имеет следующие параметры:
nodes - полный список узлов.
nodesOnPage - по сколько узлов на странице нужно выводить.
var - имя переменной Smarty, в которую следует вернуть количество страниц.
В строках 18-28 мы выводим номера страниц со ссылками на них. Обратите внимание, что функция pager_getPage, как и ожидается, возвращает целое число страниц. Это значит, что если мы хотим вывести 32 товара по 10 штук на странице, то функция pager_getPage вернет значение 4.
Все функции модуля "pager" доступны как в шаблонах, так и в процессорах. Список параметров в обоих вариантах использования одинаков, за исключением параметра "var", специфичного только для Smarty.
Часто при постраничном выводе, вместо вывода всех номеров страниц, используется только две ссылки: "вперед" и "назад" ("следующие" и "предыдущие","более ранние" и "более поздние" и т.п.). Особенно популярен такой подход в блогах, где пользователю не особенно важно знать общее количество страниц, а важно иметь возможность последовательно читать более старые или, наоборот, более свежие записи. Рассмотрим следующий пример реализации. В Листинге 29 мы выводим по 10 сообщений блога на странице, а под очередной порцией сообщений, выводим кнопки "более ранние" и "более поздние".
Здесь мы использовали функции модуля "pager" pager_hasNextPage и pager_hasPrevPage, которые определяют, имеются ли следующая и предыдушая страницы, на основании общего списка узлов, числа узлов на одной странице и текущего номера страницы.
Функции модуля "pager" имеют серьезный недостаток: для правильной работы они требуют передачи им полного списка узлов. Это означает, если общее число узлов - 1000, а на странице выводится всего 10 из них, то выбрать все равно придется тысячу. При небольших объемах данных, эта особенность не оказывает существенного влияния на скорость работы, однако, если речь идет о тысячах узлов, замедления при выводе становятся заметными. В XS2 версии 1 не существует координального решения этой проблемы. Можно лишь до некоторой степень оптимизировать объем выборки. Ниже приведен пример такой оптимизации, направленной на экономию оперативной памяти.
В строке №1 мы производим выборку всех сообщений текущего блога, при этом с помощью параметра "fileds" мы указываем, что для каждого узла нам необходимо вернуть только поле "NodId". В следующей строке мы получаем диапазон узлов, соответствующий текущей странице. Строки №№5-8 формируют массив, содержащий идентификаторы узлов диапазона. И наконец, строка №9 производит выборку только полей диапазона с полным списком их узлов. Таким образом, мы существенно экономим память и ресурсы, поскольку не заставляем ядро XS2 обрабатывать те узлы, которые все равно не будут выведены на странице.
Приведенный выше пример оптимизации лишь отчасти решает проблему производительности на больших массивах данных. Поэтому во второй версии XS2 задача получения диапазонов узлов из выборки решена координально на уровне ядра.
Все функции, осуществляющие выборку узлов во второй версии XS2, снабжены дополнительным параметром "offset". С помощью него можно указать, сколько узлов функция должна пропустить от начала выборки. Таким образом, в сочетании с уже имеющимся у тех же функций параметром "limit" этот параметр позволяет осуществлять "вырезку" диапазона узлов еще до их возврата из функции, средствами ядра системы. В большинстве случаев ядро XS2 возложит задачу по "вырезке" диапазона на драйвер БД, а это означает, что по сравнению со способом, описанным выше, "offset" - черезвычайно эффективный инструмент оптимизации выборок.
Еще одно новшество второй версии, позволяющее существенно сократить накладные расходы при организации постраничного вывода, это новая функция XS2 API "xs2CountNodes". Она позволяет быстро определить количество узлов в предполагаемой выборке, не осуществляя саму выборку. Дело в том, что в первой версии XS2 не существует возможности определить количество узлов в выборке, не запросив их. Использование параметра "offset" не имело бы никакого смысла, если этот недостаток первой версии не был бы устранен. Функция "xs2CountNodes" имеет следующие параметры:
mod - модуль, в котором производится подсчет узлов;
NType - тип узлов, которые необходимо подсчитать;
condition - условие, с помощью которого производится выборка узлов, подлежащих подсчету. Важно понимать, что с помощью условия, можно задать выборку, идентичную функциям "xs2GetChildren" и xs2GetKnee и т.д.;
var - этот параметр используется только в шаблонах. В нем указывается имя переменной Smarty, в которую необходимо вернуть результат вызова.
Следует обратить особое внимание на то, что с помощью функции "xs2CountNodes" можно вернуть количество узлов для любой функции выборок XS2 API. Например, для того, чтобы подсчитать, сколько узлов вернул бы вот такой вызов:
{xs2GetChildren mod="schedule" ParId=12 NType="lesson" var="lessons"} нужно вызвать функцию xs2CountNodes со следующими параметрами:
{xs2CountNodes mod="schedule" NType="lesson" condition="ParId=12" var="countLessons"}.
Приведем пример совместного использования параметра "offset", функции "xs2CountNodes" и возможностей модуля "pager" во второй версии XS2. Для наглядности мы решим ту же задачу, что и в Листинге 28.
В строке №1 мы выясняем общее количество товаров с помощью функции "xs2CountNodes". В строке №2 мы используем функцию "pager_getPageOffset" из модуля "pager" для выяснения смещения первого узла текущей страницы относительно начала выборки. Функция "pager_getPageOffset" имеет следующией парамтры:
nodesCount - общее количество узлов;
nodesOnPage - количество узлов на странице;
page - номер текущей страницы;
var - этот параметр используется только в шаблонах. В нем указывается имя переменной Smarty, в которую необходимо вернуть результат вызова.
В строке №19 мы используем встроенную функцию Smarty "math" для того, чтобы вычислить общее количество страниц.Строки №№20-29 идентичны Листингу 28.
Постраничный вывод узлов - это лишь один из способов организации просмотра больших массивов узлов. В последнее время он теряет популярность по двум причинам:
существенно выросла пропускная способность каналов, что сделало излишним разбиение на страницы в тех случаях, когда общее количество узлов составляет 10-50 штук;
рост уровня требований к удобству веб-интерфейсов все чаще приводит к тому, что при проектировании новых сайтов разработчики избегают "перелистовалки" как неюзабильный элемент управления.
Однако, если перед разработчиком стоит задача организовать постраничный вывод, XS2 располагает всеми необходимыми инструментами для ее быстрого решения.