Контент


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.

Теги: , .


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

Будьте в курсе обсуждения, подпишитесь на 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 комментариев.

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

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

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

  14. Сергей пишет:

    А можно привести код роутинга для данного примера, ни фига не получается.
    Помогииите!

  15. biakaveron пишет:

    Сергей, что именно не получается?

  16. Сергей пишет:

    Для самого первого, пишу так:
    Route::set(‘page’, ‘((/(/(/))))’, array(‘id’ => ‘[0-9]+’, ‘page’ => ‘[0-9]+)’))
    ->defaults(array(
    ‘controller’ => ‘welcome’,
    ‘action’ => ‘index’,
    ‘id’ => NULL,
    ‘page’ => NULL,

    ));
    и везду индекс…

  17. Сергей пишет:

    Route::set('page', '(<controller>(/<action>(/<id>(/<page>))))', array('id' => '[0-9]+', 'page' => '[0-9]+)'))
    	->defaults(array(
    		'controller' => 'welcome',
    		'action'     => 'index',
    		'id'     => NULL,
    		'page'      => NULL,
     
    	)

  18. biakaveron пишет:

    Передаете [current_page][key] == ‘route’? Какой УРЛ получается?

  19. Jah пишет:

    если с роутингом не получается,

  20. Jah пишет:

    почему не юзать стандартный? либо использовать такой конфиг:

    		$page_data = array
    			(
    			  'total_items'     =&gt; $count,
    			  'items_per_page'  =&gt; $per_page,
    			  'current_page'    =&gt; array
    			  (
    				  'source'     	=&gt; 'query_string',
    				  'key'         =&gt; 'page'
    			  ),
    			  'auto_hide'       =&gt; TRUE,
    			  'view'			=&gt; 'pagination',
    			);

    либо такой роутинг

    Route::set('default', '((/)((/)((/page)))')
    	-&gt;defaults(array(
    		'controller' =&gt; 'index',
    		'action'     =&gt; 'index',
    	));

    Если второй вариант то в конфиге пагинэйшна надо указать вместе query_string — route.

    у меня работает и первый и второй вариант.

  21. Jah пишет:

    дубль два

    		$page_data = array
    			(
    			  'total_items'     => $count,
    			  'items_per_page'  => $per_page,
    			  'current_page'    => array
    			  (
    				  'source'     	=> 'query_string',
    				  'key'         => 'page'
    			  ),
    			  'auto_hide'       => TRUE,
    			  'view'			=> 'pagination',
    			);


    … и …

    Route::set('default', '(<controller>(/)(<action>(/)(<id>(/page<page>)))')
    	->defaults(array(
    		'controller' => 'index',
    		'action'     => 'index',
    	));

  22. biakaveron пишет:

    Только роут можно упростить —

    (<controller>(/<action>(/<id>(/page<page>)))

  23. Jah пишет:

    да, но тогда вот такая проблема :)
    http://site.com/controller — будет работать.
    http://site.com/controller/ — 404 не найден роут.

    это был единственный вариант который я придумал дабы обойти эту проблему.

    может что посоветуете? :)

  24. biakaveron пишет:

    Почему это не будет работать? Kohana автоматически добавляет в конец URI слэш, так что проблем не должно быть.

  25. Jah пишет:

    минус мне :)
    проверил, действительно работает :)
    раньше в какой-то версии мне эксэпшн по поводу ненайденного роута выдавало. наверное ещё в 3.0.6 или даже раньше.

    спасибо за информацию =)

  26. ya-raul пишет:

    Нашел в пагинаторе баг. Заключается в следующем.
    Допустим у меня URL вида '(--(/))'
    если я перехожу первый раз на любую из страниц то все нормально, но ссылки все ссылки ведущие на первую страницу превращаются в .
    отдебажил нашел в файле MODPATH.pagination/classes/kohana/pagination.php
    на 216 строке условие:
    if ($page === 1 AND ! $this->config['first_page_in_url'])
    {
    $page = NULL;
    }

    удалил . все заработало :)
    конфигами решить траблу не получилось дальше искать не стал

  27. ya-raul пишет:

    а еще здесь багнутые комменты :D
    Поправки
    *Допустим у меня URL вида (--(/))
    *ссылки ведущие на первую страницу превращаются в

  28. ya-raul пишет:

    все равно не вышло. ну и хрен с ним :)



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

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