Je me suis récemment posé la question de  comment faire pour avoir plusieurs pagerfanta dans la même page lorsque l’on fait un site sous Symfony2. Cerise sur le gâteau, la page doit afficher des tableaux (ceux à paginer) en AJAX. Pour information, pagerFanta est par défaut prévu pour utiliser des paramètre en POST/GET dans l’url. Plusieurs solutions simples s’offrent à vous :

  • Avoir un paramètre par pager et passer les urls du style http://www.exemple.com/shop?productsPage=2&categoryPage=3&userPage=4&otherPager=5. Très moche !
  • Faire des sous-pages avec des iframes. Encore pire !
  • Utiliser une seule variable pour la pagination et accepter le fait que tout les pagers avancent et reculent de façon synchronisée. Envisageable, mais pas terrible en terme d’expérience utilisateur. En plus il vous faudra gérer les cas où tous les pagers n’ont pas le même nombre de pages, ce qui est plus que probable

  Donc la solution n’est pas là. Il va falloir chercher un peu plus compliqué. Voici ma solution détaillée : Le principe est simple. Avoir un contrôleur par pager voulu. Et utiliser à la fois le rendu des fragments introduit dans symfony 2.2 et des appels asynchrones en Ajax (jquery en l’occurrence). Il nous faut donc :

Un contrôleur pour chaque pager

public function showProductsAction($catId, $page)
{
    $repository = $this->getDoctrine()->getManager()->getRepository('MyBundle:Category');
    $category   = $repository->findOneById($catId);

    if (!$category) {
        throw $this->createNotFoundException(
            'No category found : '.$catId
        );
    }

    $adapter    = new ArrayAdapter($category->getProducts());
    $pagerfanta = new Pagerfanta($adapter);

    $productsPaged = $pagerfanta
        ->setMaxPerPage(10)
        ->setCurrentPage($page)
        ->getCurrentPageResults();

    return $this->render('MyBundle::pagedProducts.html.twig', array(
        'category'      => $category,
        'productsPaged' => $productsPaged,
        'pager'         => $pagerfanta, ));
    }
}

 Un template pour notre tableau

{# MyBundle::pagedProducts.html.twig #}

{% if productsPaged is not empty %}
    <table class="table table-striped">
        <thead>
            <td>{{ 'Product Name'  | trans }}</td>
            <td>{{ 'Quantity'      | trans }}</td>
            <td>{{ 'Price'         | trans }}</td>
            <td>{{ 'Stock'         | trans }}</td>
        </thead>
        <tbody>
        {% for product in productsPaged %}</ul>
            <tr>
                <td><a href="#">{{ product.getName()     }}</a></td>
                <td><a href="#">{{ product.getQuantity() }}</a></td>
                <td><a href="#">{{ product.getPrice()    }}</a></td>
                <td><a href="#">{{ product.getStock()    }}</a></td>
            </tr>
        {% endfor %}
        </tbody>
    </table>

{# Pager #}
    {% if pager.haveToPaginate %}
        <ul class="pagination">

            {# back #}
            {% set state = '' %}
            {% if  pager.getCurrentPage()  == 1 %}
                {% set state = 'disabled' %}
            {% endif %}
            <li class="{{ state }}"><a onclick="loadTablePage({{ category.id }}, {{ pager.getCurrentPage() -1 }})">&laquo;</a></li>

            {# pages 1 2 3 ... #}
            {% for givenPage in 1..pager.getNbPages() %}
                {% set state = '' %}
                {% if  pager.getCurrentPage()  == givenPage %}
                    {% set state = 'active' %}
                {% endif %}
                <li class="{{ state }}"><a onclick="loadTablePage({{ category.id }}, {{ givenPage }})">{{ givenPage }}</a></li>
            {%  endfor %}

            {# forward #}
            {% set state = '' %}
            {% if  pager.getCurrentPage() == pager.getNbPages() %}
                {% set state = 'disabled' %}
            {% endif %}
            <li class="{{ state }}" ><a onclick="loadTablePage({{ category.id }}, {{ pager.getCurrentPage() +1}})">&raquo;</a></li>
        </ul>
    {% else %}
        <p>{{ 'No entries found' | trans }}</p>
    {% endif %}
{% endif %}

 Inclure le template dans notre template global:

{# Products.html.twig #}

{# products table #}
<div class="row-fluid">
    <h1>{{ 'Products' | trans }}</h1>
    <div id="product-table">
        {{  render(controller('MyBundle::showProducts',
        { 'categoryId': category.id , 'page': page }), { 'strategy': 'hinclude'}) }}
    </div>
</div>
```Nb: j'utilise ici la stratégie hinclude qui permet d'avoir un rendu asynchrone lors du chargement de la page. Résultat la page est plus rapide à s'afficher compte tenu du nombre de tableaux c'était un must-have.

Une fonction Javascript:
========================

function loadTablePage(categoryId, page) { console.log(‘loadTablePage: ' + categoryId + ‘,’ + page); $.ajax({ url: ‘/app.php/pagers/products/'+ categoryIdId + ‘/page/’ + page, method: ‘POST’, success: function(content) {$('#products-table’).html(content);} }) }


Et pour lier tout çà une route dans mon fichier de configuration :
==================================================================

# routing.yml

products_table: pattern: /pagers/products/{categoryId}/page/{page} defaults: { _controller: MyBundle::showProductsTable, page:1 } requirements: _method: POST page: d+ categoryId: d+