Задача: сделать "реверсивный" пагинатор для Yii
Что имеется в виду под "реверсивным"? На примере новостей - это когда необходимо, чтобы самая первая новость всегда оставалась на первой странице, а остальные добавляемые новости уже размещались на 2, 3 и так далее страницах.
Картинка для понимания:
Зачем это нужно? Структура сайта не подвергается глобальным изменениям. Мы может открыть 10-ую страницу (поставить закладку, человек придет с поисковика) и там будет то, что ему нужно. При "обычном" подходе при каждом добавлении новости все смещается. И если человек придет на эту же 10-ую страницу - то совершенно не факт, что он найдет нужную информацию на этой странице (сам очень часто натыкаюсь на такое на различных форумах).
Будем "издеваться" над классом CLinkPager. Идем в protected/components и создаем файл ReverseLinkPager.php со следующим содержимым:
Код - пример #1
|
<?php class ReverseLinkPager extends CLinkPager { } |
Итого имеем полный аналог встроенного пагинатора, но уже "свой", и можем смело издеваться над ним.
Самым первым под нож попадает метод createPageButtons. Какие требования к кнопочкам пагинации должны быть?
* Страница без параметра page (читай - индексная) должна показываться как последняя;
* Для самой первой страницы должен отображаться параметр page=1;
* Кнопка "Предыдущая" - должна уменьшать номер страницы и быть справа;
* Кнопка "Следующая" - должна увеличивать номер страницы и быть слева;
* Кнопка "Первая" - вести на последнюю страницу по счету;
* Кнопка "Последняя" - вести на первую страницу.
Химичим. Основная проблема над которой приходится поломать голову - если не передается номер страницы, то getCurrentPage() возвращает 0. И даже если передается page=1, то всеравно на выходе 0.
Поэтому сама по себе реверсивная пагинация получается легко, за исключением проблем с первой и последней страницами:
Код - пример #2
|
class ReverseLinkPager extends CLinkPager { public $nextPageLabel = '< Следующая'; public $prevPageLabel = 'Предыдущая >'; protected function createPageButtons(){ if(($pageCount=$this->getPageCount())<=1) return array(); list($beginPage,$endPage)=$this->getPageRange(); // currentPage is calculated in getPageRange() $currentPage=$this->getCurrentPage(false); $buttons=array(); // first page $buttons[]=$this->createPageButton( $this->firstPageLabel, $pageCount-1, self::CSS_FIRST_PAGE, $currentPage>=$pageCount-1, false ); // next page if(($page=$currentPage+1)>=$pageCount-1) $page=$pageCount-1; $buttons[]=$this->createPageButton( $this->nextPageLabel, $page, self::CSS_NEXT_PAGE, $currentPage>=$pageCount-1,false ); // internal pages for($i=$endPage;$i>=$beginPage;--$i){ $buttons[]=$this->createPageButton( $i+1, $i, self::CSS_INTERNAL_PAGE, false, $i==$currentPage ); } // prev page if(($page=$currentPage-1)<0) $page=0; // last page $buttons[]=$this->createPageButton( $this->lastPageLabel, 0, self::CSS_LAST_PAGE, $currentPage<=0, false ); |
Если посмотрите на оригинальный код метода, то видно, что:
* Ссылки на первую/последнюю страницу изменились;
* Ссылки на предыдщую/следующую тоже;
* Цикл вывода промежуточных ссылок изменился, теперь он идет от большего к меньшему, чем и обеспечивает обратный отсчет ссылок.
Осталась проблема с первой и последней страницами. Чтобы её решить придется приложить руку к классу CPagination.
Создаем в /protected/components новый файл ReverseCPagination и расширяем в нем класс CPagination:
Код - пример #3
|
<?php class ReverseCPagination extends CPagination { } |
Из CPagination нас интересуют два метода: getOffset() - который используется при применении лимитов к $criteria и createPageUrl() - который отвечает за формирование ссылок. Меняем createPageUrl() так, что бы параметр page=1 отображался для первой страницы и не отображался для последней:
Код - пример #4
|
public function createPageUrl($controller,$page){ $params=$this->params===null ? $_GET : $this->params; if($page<$this->getPageCount()-1) $params[$this->pageVar]=$page+1; else unset($params[$this->pageVar]); return $controller->createUrl($this->route,$params); } |
С getOffset() получается немного хитрее. Нам нужно проверить, не пришло ли нам параметра, указывающего на номер страницы. Если нет или пришел, но какой-то "неправильный" (вне допустимого диапазона [1,getPageCount] ), то указываем, что мы на последней странице. Дополнительно меняем код так, чтобы функция возвращала "правильное" смещение не от 0, а от последней страницы.
В итоге получается такой код (условия разнес в 2 разных, чтобы не было каши):
Код - пример #5
|
public function getOffset() { if ($this->getCurrentPage() == 0 && !isset($_GET[$this->pageVar])) { $this->setCurrentPage($this->getPageCount() - 1); } |
Собственно говоря, на этом всё.
Пользоваться полученным кодом так же, как и оригинальным:
* В контроллере что-то вроде такого:
Код - пример #6
|
$criteria = new CDbCriteria; $criteria->condition = 'status='.$status; $criteria->order = 'id DESC'; $pages = new ReverseCPagination(Messages::model()->count($criteria)); $pages->pageSize = 2; $pages->applyLimit($criteria); |
* В представлении:
Код - пример #7
|
$this->widget('ReverseLinkPager', array( 'pages'=>$pages, )); |
Ну и картинки того, что получилось в итоге:
* Индексная страница:
* Гуляем по страницам:
* Последняя (она же самая первая):
Обсуждаем на форуме, привествуются исправления и пожелания.
PS: Оформил в виде расширения.