Создание и сопровождение сайтов

Блог. Yii. Реверсивный пагинатор в Yii

Поиск

Задача: сделать "реверсивный" пагинатор для 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;
$buttons[]=$this->createPageButton( $this->prevPageLabel, $page, self::CSS_PREVIOUS_PAGE, $currentPage<=0, false );
 
 
        // last page
        $buttons[]=$this->createPageButton(
            $this->lastPageLabel,
            0,
            self::CSS_LAST_PAGE,
            $currentPage<=0,
            false
        );
return $buttons; } }

     Если посмотрите на оригинальный код метода, то видно, что:

* Ссылки на первую/последнюю страницу изменились;
* Ссылки на предыдщую/следующую тоже;
* Цикл вывода промежуточных ссылок изменился, теперь он идет от большего к меньшему, чем и обеспечивает обратный отсчет ссылок.

    Осталась проблема с первой и последней страницами. Чтобы её решить придется приложить руку к классу 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);
    }
if ($this->getCurrentPage() == 0 && isset($_GET[$this->pageVar]) && ($_GET[$this->pageVar] < 1 || $_GET[$this->pageVar] > $this->getPageCount())) { $this->setCurrentPage($this->getPageCount() - 1); }
return ($this->getPageCount() - $this->getCurrentPage() - 1) * $this->getPageSize(); }

    Собственно говоря, на этом всё.

    Пользоваться полученным кодом так же, как и оригинальным:

* В контроллере что-то вроде такого:

Код - пример #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: Оформил в виде расширения.