Контент


Ko3: Pagination

Есть библиотеки-монстры (типа 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.

Вот как бы и все. Замечания (особенно обоснованные ;) ) приветствуются.

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в Kohana3.

Теги: , .


Комментарии (13)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. Random пишет:

    Отличная статья, все предельно ясно. Есть такой вопрос: можно ли создать ссылки вида page2 или page-11?

  2. Random пишет:

    Упс, снова поторопился с вопросом. Нужно просто написать в Route::set что-то вроде (/page-) или (/page_)

  3. Konstantin пишет:

    Спасибо за статью!)

  4. BIakaVeron пишет:

    @Random
    Конечно, при использовании current_page['source'] == ‘route’, мы просто передаем в маршрут номер страницы, а там он может быть поставлен в любом месте URL.

  5. KotDev пишет:

    Отличная статья. Вычислять offset думаю необязательно, класс pagination может его возвратить сам, а так слишком много кода имхо

  6. BIakaVeron пишет:

    Есть у меня привычка (не всегда полезная, это да) такие вот простые вычисления проводить сразу, так логика перед глазами. Хотя, конечно, можно вынести Pagination::factory() повыше, а потом пользоваться свойствами $offset, $current_page и т.д.

  7. Евгений пишет:

    Есть у меня например такой 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
    Спасибо.

  8. biakaveron пишет:

    Ну, тут вопрос скорее про роутинг, если я Вас правильно понял. Получается, что надо сперва сделать, чтобы вообще в приложении были УРЛ с номером страницы не на 4м месте, а на третьем. Тогда пагинатор будет нормально отрабатывать. Но наверное проще сделать передачу страницы через GET, т.к. роутинг в 2.3.4 так себе ;)

  9. Jah пишет:

    каверзный вопрос.
    как сделать, чтобы по дефолту, если пэйдж не указан в урл, загружалась последняя страница.

  10. Евгений пишет:

    Проверять в контролере – если параметр == нулл, подставлять значение последней страницы

  11. biakaveron пишет:

    Сложно это все. В библиотеке во многих местах жестко прописано, что по умолчанию используется страница №1. Возможно, имеет смысл пересмотреть нумерацию страниц? ;)

  12. biakaveron пишет:

    @Евгений
    Понимаете, дело в том, что для ссылок на первую страницу Pagination сгенерирует адрес с page=NULL, т.е. без номера страницы. Проблема ведь именно в генерируемых пагинатором УРЛах…

  13. Jah пишет:

    я просто чего затеял это.
    допустим делаем комментарии к чему-то или обсуждения.
    хотелось бы, чтобы они размещались на странице с сортировкой по времени ASC.
    соответственно на главную выводим последние 10 комментариев.

    как дать возможность пользователю переходить к последнему комментарию темы с главной? :)

    мне кажется что если выполнять запрос с подсчётом комментариев в теме и высчитывать номер страницы – это велосипед.

    Кстати, в моём нике ссылка на сайт который делаю. Делаю чисто для себя, для практики)



Можно включить подсветку кода: <code><pre lang="">...</pre><code>
Разрешены некоторые HTML теги

или используйте trackback.