Есть библиотеки-монстры (типа 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 или даже раньше.
спасибо за информацию =)