Есть библиотеки-монстры (типа ORM), которые по сути являются фундаментальными для создаваемого приложения. А есть небольшие, но очень необходимые, выполняющие «точечные» задачи, типа Captcha или Pagination. О модуле для формирования постраничной навигации (т.е. о Pagination) и пойдет речь в данной статье.
Думаю, с необходимостью разбивать содержимое сайта на страницы сталкивался каждый. Это и записи в блоге, и сообщения на форумах, в общем все, что накапливается. Чтобы не формировать длиннющую портянку сообщений, которые возможно никто и читать не будет, покажем первые N элементов, спрятав остальные за постраничной навигацией. Для этого необходимо знать три основных параметра:
- Общее количество записей (
$count
). - Количество записей на одной странице (
$per_page
). - Номер текущей страницы (
$page
).
Первые два нужны для подсчета количества страниц (делим $count
на $per_page
и округляем в большую сторону). Третий нужен для выделения текущей страницы в списке (как правило, она выделяется цветом и не является ссылкой). Приложение должно подготовить эти данные и передать их в объект Pagination, который возьмет на себя дальнейшие расчеты.
Как это будет выглядеть:
$objects = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); $pag_data = array ( 'total_items' => count($objects), 'items_per_page' => 4, ); echo Pagination::factory($pag_data)->render(); |
Запустив, получим примерно такую картинку:
Все в принципе логично. При десяти объектах и выводе их по 4 штуки за раз, получаем три страницы. Так как текущая страница — первая (по умолчанию), ссылки First (переход к первой странице) и Previous (к предыдущей) неактивны (заменены на текст). В контроллере необходимо проверять переменную $_GET['page']
(по умолчанию в навигации используется такой способ передачи номера страницы), например так — $page = max(1, arr::get($_GET, 'page', 1));
, и показывать соответствующие элементы массива $objects
.
Раньше модуль Pagination содержал текстовый контроллер, в котором демонстрировались базовые настройки класса, но сейчас его нет.
Данный пример конечно же надуманный. Обычно используется результат выборки из БД (в частности, ORM-объекты). Давайте посмотрим, как их совместить (заодно — на что еще способна библиотека).
$per_page = 10; $page = $page = max(1, arr::get($_GET, 'page', 1)); $offset = $per_page * ($page-1); $count = ORM::factory('article')->count_all(); $pag_data = array ( 'total_items' => $count, 'items_per_page' => $per_page, 'current_page' => array ( 'source' => 'route', 'key' => 'page' ), 'auto_hide' => TRUE, 'view' => 'pagination/mypagination', ); $articles = ORM::factory('article')->limit($per_page)->offset($offset)->find_all(); echo "<ul>"; foreach($articles as $article) { echo "<li>".$article->title."</li>"; } echo "</ul>"; echo Pagination::factory($page_data)->render(); |
Что изменилось? Количество элементов получаем из БД отдельным запросом (в Ko 2.3.4 был метод count_last_query()
, сейчас получается немного неуклюже). Выборку статей из БД делаем в соответствии с текущей страницей (методы limit()
и offset()
). Для вывода навигации используется свое представление (views/pagination/mypagination.php), а не дефолтное (views/pagination/basic.php).
Отдельно стоит отметить параметр ‘current_page‘. Он определяет способ передачи номера страницы в URL. Помимо привычного ключа в GET-строке (типа http://localhost/articles/?page=2) можно использовать сегменты в маршруте (например, http://localhost/articles/2 или http://loclahost/articles/page2). Мы установили свойство ‘source‘ в ‘route‘, поэтому будет применен метод Route->uri()
с передачей в него дополнительного параметра ‘page‘. По умолчанию используется ‘query_string‘ (т.е. обычная передача через $_GET).
Еще есть параметр ‘auto_hide‘, который позволяет скрывать навигацию, если количество страниц не превышает одной. По умолчанию навигация скрывается.
Метод
render()
возвращает сгенерированный HTML-текст, но не выводит его на экран. Можно в него передать имя представления, вместо установки параметра ‘view‘. Объект Pagination реализовывает метод__toString()
, т.е. можно делатьecho Pagination::factory($pag_data);
без явного вызова методаrender()
.
Что еще?
Настройки объекта Pagination можно менять после его создания. Для этого есть метод setup(array $config)
, который позволяет изменить какие-либо параметры текущего объекта.
Если надо установить только одно значение, использование массивов неудобно. Отдельные параметры могут быть установлены через «магический» метод
__set($key, $value)
. В этом случае просто будет вызванsetup(array($key => $value))
.
Конфигурацию можно вынести в файл config/pagination.php. Вот так, к примеру, объявлена группа настроек по умолчанию:
return array( // Application defaults 'default' => array( 'current_page' => array('source' => 'query_string', 'key' => 'page'), // source: "query_string" or "route" 'total_items' => 0, 'items_per_page' => 10, 'view' => 'pagination/basic', 'auto_hide' => TRUE, ), ); |
Для загрузки массива настроек из файла может использоваться метод config_group($config = 'default')
, который по указанному имени находит и возвращает массив параметров (можно его сразу подставлять в метод setup()
). К сожалению, на данный момент нельзя создать объект Pagination с передачей имени конфигурации, а метод config_group()
недоступен до его создания, т.к. он не статический.
Что интересно, в конфигах можно использовать ссылки на другие конфиги. Для этого есть параметр ‘group‘, который должен содержать имя следующего конфига. Метод config_group()
будет загружать их последовательно, при этом значения, загруженные раньше, не будут затерты новыми. Например:
return array ( 'first' => array( 'total_items' => 0, 'view' => 'pagination/first', 'auto_hide' => TRUE, 'group' => 'second', ), 'second' => array( 'items_per_page' => 10, 'view' => 'pagination/second', ), ); $config = $pag->config_group('first'); |
В данном примере $config
будет содержать значения из конфига ‘first‘ плюс параметр ‘items_per_page‘ из ‘second‘. Параметр ‘view‘ будет проигнорирован.
Создание собственных шаблонов
В Ko3 предлагается только простенький шаблон basic, который навряд ли будет выглядеть привлекательно в нашем приложении. Поэтому лучше создать свой шаблон. Для этого необходимо знать, какие данные поступают в него из библиотеки Pagination:
$current_page
— номер текущей страницы.$total_items
— общее количество записей (объектов).$items_per_page
— количество объектов на странице.$total_pages
— общее количество страниц.$current_first_item
— порядковый номер первого элемента на странице (смещение).$current_last_item
— порядковый номер последнего элемента на странице (смещение).$previous_page
— номер предыдущей страницы. Если текущая страница первая, то FALSE.$next_page
— номер следующей страницы. FALSE, если текущая страница последняя.$first_page
— номер первой страницы. Если мы уже на первой странице, то FALSE, иначе (естественно) 1.$last_page
— номер последней страницы. Для последней страницы вернет FALSE.
Эти свойства доступны на чтение через «магический» метод
__get()
, например$pag->current_page
вернет текущую страницу.
Так как внутри шаблона необходимо формировать ссылки на новые страницы, имеется метод url($page)
, который возвращает URL на основании текущего (используется метод Request->uri()
), с учетом переданного номера страницы $page
.
Вот как бы и все. Замечания (особенно обоснованные ) приветствуются.
Отличная статья, все предельно ясно. Есть такой вопрос: можно ли создать ссылки вида page2 или page-11?
Упс, снова поторопился с вопросом. Нужно просто написать в Route::set что-то вроде (/page-) или (/page_)
Спасибо за статью!)
@Random
Конечно, при использовании current_page['source'] == ‘route’, мы просто передаем в маршрут номер страницы, а там он может быть поставлен в любом месте URL.
Отличная статья. Вычислять offset думаю необязательно, класс pagination может его возвратить сам, а так слишком много кода имхо
Есть у меня привычка (не всегда полезная, это да) такие вот простые вычисления проводить сразу, так логика перед глазами. Хотя, конечно, можно вынести
Pagination::factory()
повыше, а потом пользоваться свойствами$offset
,$current_page
и т.д.Есть у меня например такой uri
http://www.xxxxxxxx.com.ua/catalog/Devochkam/Kofty.html
Делаю пейджинг так
$paging = new Pagination(array
(
‘uri_segment’ => 4,
…..
Так вторая страница будет иметь ссылку
http://www.xxxxxxxx.com.ua/catalog/Devochkam/Kofty.html/2
Как сделать чтобы она была
http://www.xxxxxxxx.com.ua/catalog/Devochkam/2/Kofty.html
Kohana 2.3.4
Спасибо.
Ну, тут вопрос скорее про роутинг, если я Вас правильно понял. Получается, что надо сперва сделать, чтобы вообще в приложении были УРЛ с номером страницы не на 4м месте, а на третьем. Тогда пагинатор будет нормально отрабатывать. Но наверное проще сделать передачу страницы через GET, т.к. роутинг в 2.3.4 так себе
каверзный вопрос.
как сделать, чтобы по дефолту, если пэйдж не указан в урл, загружалась последняя страница.
Проверять в контролере — если параметр == нулл, подставлять значение последней страницы
Сложно это все. В библиотеке во многих местах жестко прописано, что по умолчанию используется страница №1. Возможно, имеет смысл пересмотреть нумерацию страниц?
@Евгений
Понимаете, дело в том, что для ссылок на первую страницу Pagination сгенерирует адрес с page=NULL, т.е. без номера страницы. Проблема ведь именно в генерируемых пагинатором УРЛах…
я просто чего затеял это.
допустим делаем комментарии к чему-то или обсуждения.
хотелось бы, чтобы они размещались на странице с сортировкой по времени ASC.
соответственно на главную выводим последние 10 комментариев.
как дать возможность пользователю переходить к последнему комментарию темы с главной?
мне кажется что если выполнять запрос с подсчётом комментариев в теме и высчитывать номер страницы — это велосипед.
Кстати, в моём нике ссылка на сайт который делаю. Делаю чисто для себя, для практики)
А можно привести код роутинга для данного примера, ни фига не получается.
Помогииите!
Сергей, что именно не получается?
Для самого первого, пишу так:
Route::set(‘page’, ‘((/(/(/))))’, array(‘id’ => ‘[0-9]+’, ‘page’ => ‘[0-9]+)’))
->defaults(array(
‘controller’ => ‘welcome’,
‘action’ => ‘index’,
‘id’ => NULL,
‘page’ => NULL,
));
и везду индекс…
Передаете [current_page][key] == ‘route’? Какой УРЛ получается?
если с роутингом не получается,
почему не юзать стандартный? либо использовать такой конфиг:
либо такой роутинг
Если второй вариант то в конфиге пагинэйшна надо указать вместе query_string — route.
у меня работает и первый и второй вариант.
дубль два
… и …
Только роут можно упростить —
да, но тогда вот такая проблема
http://site.com/controller — будет работать.
http://site.com/controller/ — 404 не найден роут.
это был единственный вариант который я придумал дабы обойти эту проблему.
может что посоветуете?
Почему это не будет работать? Kohana автоматически добавляет в конец URI слэш, так что проблем не должно быть.
минус мне
проверил, действительно работает
раньше в какой-то версии мне эксэпшн по поводу ненайденного роута выдавало. наверное ещё в 3.0.6 или даже раньше.
спасибо за информацию =)
Нашел в пагинаторе баг. Заключается в следующем.
Допустим у меня URL вида
'(--(/))'
если я перехожу первый раз на любую из страниц то все нормально, но ссылки все ссылки ведущие на первую страницу превращаются в .
отдебажил нашел в файле MODPATH.pagination/classes/kohana/pagination.php
на 216 строке условие:
if ($page === 1 AND ! $this->config['first_page_in_url'])
{
$page = NULL;
}
удалил . все заработало
конфигами решить траблу не получилось дальше искать не стал
а еще здесь багнутые комменты
Поправки
*Допустим у меня URL вида
(--(/))
*ссылки ведущие на первую страницу превращаются в
все равно не вышло. ну и хрен с ним