Paginacja [KO3.2]

Jak wiadomo w Kohanie 3.2 zrezygnowano z modułu Paginacji, tłumacząc iż powinien za to odpowiadać model. Developerzy tą decyzją trochę przysporzyli kłopotów webmasterom, gdyż nie pokazano jak tego dokonywać, a moduł Pagninacji z poprzednich wersji wykorzystujący routing przestał działać z racji iż zmieniono Route uri().

Powstało wiele różnych modyfikacji hakujących i poprawiających (czasami nie do końca) moduł z poprzednich wersji. Aby nie mieć problemów z paginacją polecam pagination ko3.2

Rozpakowujemy moduł do folderu /modules, zmieniamy nazwę na pagination i włączamy moduł w bootstrapie
/application/bootstrap.php

Kohana::modules(array(
	 // other modules
         'pagination'  => MODPATH.'pagination',        // Pagination KO3.2
	));

Jeśli chcemy korzystać z paginacji z query_string, dodającej do linku np.

...?page=2

Pomijamy ten krok, jeśli chcemy mieć linki:

.../2

Należy dodać odpowiedni routing. To jaki zależy już, w jakich kontrolerach będziemy chcieli jej używać. Załóżmy sytuację, że będzie to domyślny routing + dodatkowy parametr jako paginacja. Zmieniamy domyślny routing w bootstrapie:

Route::set('default', '(<controller>(/<action>(/<id>(/<page>))))', array('page' => '\d+'))
    ->defaults(array(
        'controller' => 'default', //nazwa głównego kontrolera
        'action'     => 'index',
    ));

W takim przypadku należy jednak pamiętać, iż jeśli nie wystąpi parametr id to page może być źle odczytane, gdyż z linku wynikać będzie że nie przesłano page tylko id.

Routing wczytywany jest pierwszy pasujący do reguły, zatem przed defaultowym możemy dodać taki, w którym nie ma id po akcji, tylko od razu page i zdefiniować, w których kontrolerach i akcjach tak jest:

Route::set('pagination', '<controller>(/<action>(/<page>))', array('controller' => 'article|comment', 'action' => 'pending|activated|deleted', 'page' => '\d+'))
    ->defaults(array(
        'controller' => 'default',
        'action'     => 'index',
));

W takim przypadku dla kontrolerów article lub comment i akcji pending, activated i deleted page jest bezpośrednio po akcji (bez parametru id).

Popatrzmy teraz na config paginacji:

<?php defined('SYSPATH') or die('No direct script access.');

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,
		'first_page_in_url' => FALSE,
	),

);

Jak widać, domyślnie moduł korzysta z query_string (?page=), wszystkich elementów do paginacji jest 0 (bo nie wiadomo tu co mamy dzielić na strony), elementów na stronę jest 10, domyślny widok to basic (wyświetla wszystkie numeny stron, floating zastępuje kropkami przy większej ilości), autoukrywanie paska ze stronami (jeśli jest mniej elementów niż items_per_page) jest włączone, pierwsza strona nie jest wyświetlana w linku.

Jeśli chcielibyśmy coś zmienić, to nie w /modules/pagination/config/pagination.php, tylko kopiujemy go do /application/config/pagination.php i tu zmieniamy. Jest też możliwość ustawienia odpowiednich opcji przy tworzeniu paginacji. Ja nie zmieniałem nic, więc pokażę też jak to zrobić w kontrolerze.

Używanie paginacji jest dość proste. Wystarczy zliczyć wszystkie elementy i dać limit, offset. Tak zatem wyglądać może akcja do obsługi paginacji:

public function action_activated()
    {
        $this->template->content = View::factory('article_activated')
            ->bind('articles', $articles);
        
        $all = DB::query(1, 'SELECT `id` FROM `articles` WHERE `state`=1')->execute()->count();
        $pagination = Pagination::factory(array(
            'current_page'   => array('source' => 'route', 'key' => 'page'),
            'total_items'    => $all,
            'items_per_page' => 20,
            'view'           => 'pagination/floating',
        ));

        $limit = $pagination->items_per_page;
        $offset = $pagination->offset;

        $articles = DB::query(1, 'SELECT * FROM `articles` WHERE `state`=1 ORDER BY `id` DESC LIMIT :limit OFFSET : offset')->parameters(array(':limit' => $limit, ': offset' => $offset))->execute();
        $this->template->content->pagination = $pagination; 
    }

Najpierw wczytujemy widok i deklarujemy zmienną articles przez referencję (jeśli wystąpi to ją przesyłamy)

$this->template->content = View::factory('article_activated')
            ->bind('articles', $articles);

Następnie zliczamy wszystkie aktywne artykuły (status = 1):

$all = DB::query(1, 'SELECT `id` FROM `articles` WHERE `state`=1')->execute()->count();

Lub jeśli wolimy ORM:

$orm = ORM::factory('article')->where('state', '=', 1);
$all = $orm->count_all();

Przypisujemy do zmiennej pagination paginację i ustalamy właściwości (wymagane jest tylko total_itams, resztę pobierze z konfigu), tj. używanie routingu, ile jest wszystkich elementów, ile elementów na stronę i widok floating:

$pagination = Pagination::factory(array(
            'current_page'   => array('source' => 'route', 'key' => 'page'),
            'total_items'    => $all,
            'items_per_page' => 20,
            'view'           => 'pagination/floating',
        ));

Przypisujemy do zmiennych limit i offset z paginacji, które wykorzystamy do zapytania, aby ograniczyć wyniki:

$limit = $pagination->items_per_page;
$offset = $pagination->offset;

Pobieramy wyniki dla danej strony:

$articles = DB::query(1, 'SELECT * FROM `articles` WHERE `state`=1 ORDER BY `id` DESC LIMIT :limit OFFSET : offset')->parameters(array(':limit' => $limit, ': offset' => $offset))->execute();

Pomiędzy ‚: offset’ nie powinno być spacji.

Lub jeśli wolimy ORM:

$articles = $orm->order_by('id', 'DESC')->limit($limit)->offset($offset)->find_all();

Na koniec przesyłamy do widoku pasek z paginacją:

$this->template->content->pagination = $pagination; 

A w widoku wyświetlamy wszystkie wpisy

foreach ($articles as $article)
{
    //pojedynczy artykuł
}

A pod nimi pasek paginacji:

echo $pagination;

Paginacja sama utworzy odpowiednią ilość podstron i jeśli będzie potrzeba wyświetli linki.

19 Odpowiedzi :“Paginacja [KO3.2]”

  1. yyy napisał:

    Wszystko okej, tylko jak wrzucić kropki, aby nie wyświetlało wszystkich stron paginacji?

    • Mariusz napisał:

      Kropki (przy większej ilości podstron) wyświetla/zamienia widok floating

      'view' => 'pagination/floating',
      

      Można go ustawić jako domyślny w configu, lub tak jak tu przy tworzeniu paginacji. Jeśli jego używasz to po prostu za mało podstron masz. Możesz skopiować ten widok do /application/views/pagination/floating.php i ustawić ile na początku/końcu oraz przed i po obecnej stronie ma wyświetlać.
      Domyślnie wygląda tak:

      First Previous 1 2 3 ... 22 23 24 25 26 [27] 28 29 30 31 32 ... 48 49 50 Next Last
      

      Czyli wyświetla 3 z początku i końca, oraz 5 przed i 5 po obecnej stronie. Więc musisz mieć z 19 stron paginacji, aby zobaczyć kropki.

  2. dev napisał:

    witam,
    nie wiem czemu nie działają mi metody order_by() count_all();

    dziwna sprawa bo pozatym orm wydaje się działac normalnie.

    • Mariusz napisał:

      Aby posortować wg id malejąco daje się order_by() przed find_all()

      ORM::factory...->order_by('id', 'DESC')->find_all();
      

      Natomiast, aby zliczyć daje się count_all() zamiast find_all():

      ORM::factory...->count_all();
      

      Czy tak właśnie robisz?

  3. Call to undefined method Kohana::config()
    
    

    dziwny błąd, nie może załadować configa? ;/

    • Mariusz napisał:

      Chyba nie masz najnowszej wersji modułu (jest on w developerskiej linii i przy pobieraniu z githuba mogło zaciągnąć starą), gdyż powinno być:

      Kohana::$config->load()
      

      Wejdź w link z postu i nadpisz klasę:
      /pagination/classes/kohana/pagination.php

  4. Pobrałem z innego konta ;/ podmieniłem i śmiga aż miło ;)

  5. A jednak, dopiero teraz zauważyłem, że linki do kolejnych stron generuje mi razem z index.phpp for example: /index.php/weblog/index/2

    ../index.php/… jest wynikiem jakieś błędnej konfiguracji może?

  6. white napisał:

    Witam,

    co może być powodem, że linki do paginacji poprawne są dopiero gdy jest się na http://www.domena.pl/kontroller/akcja
    bo gdy jestem na http://www.domena.pl/kontroller to linki generowane do paginacji odwoluja się do http://www.domena.pl/kontroller/akcja i dopiero wtedy linki są już poprawne (www.domena.pl/kontroller/akcja/page/1 itd)

    • Mariusz napisał:

      Zapewne brak routingu. Jeśli wpiszesz np.

      http://www.domena.pl/kontroller/page/2
      

      to skąd aplikacja ma wiedzieć, że słowo page nie jest akcją, a liczba 2 parametrem id?
      Standardowo jest kontroler/akcja/parametr. Jeśli dodałeś page na końcu do defaultowego routingu, to aby odczytać page, url musi zawierać wszystkie elementy poprzedzające.
      Rozwiązania:
      1. Dodaj przed defaultowym routingiem routing do konkretnego kontrolera i akcji (np. bez parametru, albo bez akcji), wtedy będzie oznaczało że to co po kontrolerze to właśnie page.
      2. Możesz użyć query_string, zamiast routingu nry stron będzą w _GET.

  7. white napisał:

    Dodałem routing, jednak to nic nie zmieniło

    Route::set('pagination', 'main/index(/<page>)', array('page' => '\d+'))
        ->defaults(array(
            'controller' => 'main',
            'action'     => 'index',
    ));
    
    • Mariusz napisał:

      Czy w linku też masz akcję index (np. localhost/main/index/3)? Jak jest to też nie działa? Daj w kontrolerze:

      echo Debug::vars($this->request);
      

      I zobacz czy występuje tam page.

      • white napisał:

        tak linki są dobre gdy jestem na domena.pl/main/index/ , ale jak na domena.pl/main/ to juz linki nie zawierają paginacji tylko odwolanie do domena.pl/main/index/

        • white napisał:

          a no i dziwne bo routing, głowny zamiast ‚pagination’ wczytuje

          protected _uri => string(32) "(<controller>(/<action>(/<id>)))"
        • Mariusz napisał:

          Routing domyślny wykonuje się jeśli nie ma nic przed nim.
          Standardowo akcja index jest ukryta, więc to co po kontrolerze w linku jest wykrywane jako akcja.
          Albo zrobić to na niedefaultowej akcji, np. lista (kontroler/lista/2), albo (jeśli nie masz innych akcji w tym kontrolerze) w routingu dać po kontrolerze od razu page.

          A czy paginacja działa, tylko linki są z index, czy jak w linku nie ma index to w ogóle nie działa?
          PS.
          Gdybyś zamienił na query_string, to nie potrzeba routingu, miałbyś domena.com/main?page=2, ale jeśli chcesz dla SEO to trzeba z routingiem próbować.

  8. Rozumiem, że

    $limit = $pagination->items_per_page;

    odwołuje się do

    'items_per_page' => 20

    ale gdzie jest atrybut offset dla obiektu pagination?

    $pagination->offset;
    • Mariusz napisał:

      Jest on klasie Pagination. Jest wyliczany automatycznie przez Paginację na podstawie items_per_page i nru strony.

Dodaj komentarz

Dodając kod PHP używaj tagów: [php][/php]

*