diff --git a/source/development/index.rst b/source/development/index.rst index f28ed424a..043b32920 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -20,6 +20,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Menus Access control model Messages to users + Pagination Localisation Logging Database migrations diff --git a/source/development/pagination.rst b/source/development/pagination.rst new file mode 100644 index 000000000..52d0d81f1 --- /dev/null +++ b/source/development/pagination.rst @@ -0,0 +1,189 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Pagination +########## + +The Bundle :code:`Chill\MainBundle` provides a **Pagination** api which allow you to easily divide results list on different pages. + +A simple example +**************** + +In the controller, get the :class:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance. + + +.. literalinclude:: pagination/example.php + :language: php + + +Then, render the pagination using the dedicated twig function. + +.. code-block:: html+jinja2 + + {% extends "ChillPersonBundle::layout.html.twig" %} + + {% block title 'Item list'|trans %} + + {% block personcontent %} + + + + {# ... your items here... #} + +
+ + {% if items|length > paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + + +The function :code:`chill_pagination` will, by default, render a link to the 10 previous page (if they exists) and the 10 next pages (if they exists). Assuming that we are on page 5, the function will render a list to :: + + Previous 1 2 3 4 **5** 6 7 8 9 10 11 12 13 14 Next + +Understanding the magic +======================= + +Where does the :code:`$paginator` get the page number ? +------------------------------------------------------- + +Internally, the :code:`$paginator` object has a link to the :code:`Request` object, and it reads the :code:`page` parameter which contains the current page number. If this parameter is not present, the :code:`$paginator` assumes that we are on page 1. + +.. figure:: /static/puml/pagination-sequence.png + + The :code:`$paginator` get the current page from the request. + +Where does the :code:`$paginator` get the number of items per page ? +-------------------------------------------------------------------- + +As above, the :code:`$paginator` can get the number of items per page from the :code:`Request`. If none is provided, this is given by the configuration which is, by default, 50 items per page. + +:code:`PaginatorFactory`, :code:`Paginator` and :code:`Page` +************************************************************ + +:code:`PaginatorFactory` +======================== + +The :code:`PaginatorFactory` may create more than one :code:`Paginator` in a single action. Those :code:`Paginator` instance may redirect to different routes and/or routes parameters. + +.. code-block:: php + + // create a paginator for the route 'my_route' with some parameters (arg1 and arg2) + $paginatorMyRoute = $paginatorFactory->create($total, 'my_route', array('arg1' => 'foo', 'arg2' => $bar); + +Those parameters will override the current parameters. + +The :code:`PaginatorFactory` has also some useful shortcuts : + +.. code-block:: php + + // get current page number + $paginatorFactory->getCurrentPageNumber( ) + // get the number of items per page **for the current request** + $paginatorFactory->getCurrentItemsPerPage( ) + // get the number of the first item **for the current page** + $paginatorFactory->getCurrentPageFirstItemNumber( ) + + +Working with :code:`Paginator` and :code:`Page` +=============================================== + +The paginator has some function to give the number of pages are required to displayed all the results, and give some information about the number of items per page : + +.. code-block:: php + + // how many page count this paginator ? + $paginator->countPages(); // return 20 in our example + + // we may get the number of items per page + $paginator->getItemsPerPage(); // return 20 in our example + +A :code:`Paginator` instance create instance of :code:`Page`, each :code:`Page`, which is responsible for generating the URL to the page number it represents. Here are some possibilities using :code:`Page` and :code:`Paginator` : + +.. code-block:: php + + // get the current page + $page = $paginator->getCurrentPage(); + // on which page are we ? + $page->getNumber(); // return 5 in this example (we are on page 5) + // generate the url for page 5 + $page->generateUrl(); // return '/?page=5 + // what is the first item number on this page ? + $page->getFistItemNumber(); // return 101 in our example (20 items per page) + // what is the last item number on this page ? + $page->getLastItemNumber(); // return 120 in our example + + // we can access directly the next and current page + if ($paginator->hasNextPage()) { + $next = $paginator->getNextPage(); + } + if ($paginator->hasPreviousPage()) { + $previous = $paginator->getPreviousPage(); + } + + // we can access directly to a given page number + if ($paginator->hasPage(10)) { + $page10 = $paginator->getPage(10); + } + + // we can iterate over our pages through a generator + foreach ($paginator->getPagesGenerator() as $page) { + $page->getNumber(); + } + + // check that a page object is the current page + $paginator->isCurrentPage($page); // return false + +.. warning:: + + When calling a page which does not exists, the :code:`Paginator` will throw a `RuntimeException`. Example : + + .. code-block:: php + + // our last page is 10 + $paginator->getPage(99); // out of range => throw `RuntimeException` + + // our current page is 1 (the first page) + $paginator->getPreviousPage; // does not exists (the fist page is always 1) => throw `RuntimeException` + +.. note:: + + When you create a :code:`Paginator` for the current route and route parameters, the :code:`Page` instances will keep the same parameters and routes : + + .. code-block:: php + + // assuming our route is 'my_route', for the pattern '/my/{foo}/route', + // and the current route is '/my/value/route?arg2=bar' + + // create a paginator for the current route and route parameters : + $paginator = $paginatorFactory->create($total); + + // get the next page + if ($paginator->hasNext()) { + $next = $paginator->getNextPage(); + + // get the route to the page + $page->generateUrl(); // will print 'my/value/route?arg2=bar&page=2' + } + + +Having a look to the `full classes documentation may provide some useful information `_. + + +Customizing the rendering of twig's :code:`chill_pagination` +************************************************************ + +You can provide your own layout for rendering the pagination: provides your twig template as a second argument : + +.. code-block:: html+jinja + + {{ chill_pagination(paginator, 'MyBundle:Pagination:MyTemplate.html.twig') }} + +The template will receive the :code:`$paginator` as :code:`paginator` variable. Let's have a look `at the current template `_. + diff --git a/source/development/pagination/example.php b/source/development/pagination/example.php new file mode 100644 index 000000000..bfcac41eb --- /dev/null +++ b/source/development/pagination/example.php @@ -0,0 +1,42 @@ +getDoctrine()->getManager(); + // first, get the number of total item are available + $total = $em + ->createQuery("COUNT (item.id) FROM ChillMyBundle:Item item") + ->getSingleScalarResult(); + + // get the PaginatorFactory + $paginatorFactory = $this->get('chill_main.paginator_factory'); + + // create a pagination instance. This instance is only valid for + // the current route and parameters + $paginator = $paginatorFactory->create($total); + + // launch your query on item. Limit the query to the results + // for the current page using the paginator + $items = $em->createQuery("SELECT item FROM ChillMyBundle:Item item WHERE ") + // use the paginator to get the first item number + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + // use the paginator to get the number of items to display + ->setMaxResults($paginator->getItemsPerPage()); + + return $this->render('ChillMyBundle:Item:list.html.twig', array( + 'items' => $items, + 'paginator' => $paginator + ); + + } + +} + diff --git a/source/static/puml/pagination-sequence.png b/source/static/puml/pagination-sequence.png new file mode 100644 index 000000000..7016be6e2 Binary files /dev/null and b/source/static/puml/pagination-sequence.png differ diff --git a/source/static/puml/pagination-sequence.puml b/source/static/puml/pagination-sequence.puml new file mode 100644 index 000000000..1e6e77d04 --- /dev/null +++ b/source/static/puml/pagination-sequence.puml @@ -0,0 +1,20 @@ +@startuml + +actor "controller, service, ..." as x +participant "paginator" +participant "Request" + +x -> paginator: getCurrentPage() +activate paginator + +paginator -> Request: read the `page` parameter in GET request +activate Request +Request -> paginator +deactivate Request + +paginator -> paginator: construct a page object for current page number +paginator -> x: return the `page` +deactivate paginator + +@enduml +