Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A gist for pagination in Twig, based on the total number of pages, the current page and some URL-settings.
{#
Source: http://dev.dbl-a.com/symfony-2-0/symfony2-and-twig-pagination/
Updated by: Simon Schick <simonsimcity@gmail.com>
Parameters:
* currentFilters (array) : associative array that contains the current route-arguments
* currentPage (int) : the current page you are in
* paginationPath (string) : the route name to use for links
* showAlwaysFirstAndLast (bool) : Always show first and last link (just disabled)
* lastPage (int) : represents the total number of existing pages
#}
{% spaceless %}
{% if lastPage > 1 %}
{# the number of first and last pages to be displayed #}
{% set extremePagesLimit = 3 %}
{# the number of pages that are displayed around the active page #}
{% set nearbyPagesLimit = 2 %}
<div class="pagination">
{% if currentPage > 1 %}
<a href="{{ path(paginationPath, currentFilters|merge({page: currentPage-1})) }}">Previous</a>
{% for i in range(1, extremePagesLimit) if ( i < currentPage - nearbyPagesLimit ) %}
<a href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a>
{% endfor %}
{% if extremePagesLimit + 1 < currentPage - nearbyPagesLimit %}
<span class="sep-dots">...</span>
{% endif %}
{% for i in range(currentPage-nearbyPagesLimit, currentPage-1) if ( i > 0 ) %}
<a href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a>
{% endfor %}
{% elseif showAlwaysFirstAndLast %}
<span class="disabled">Previous</span>
{% endif %}
<a href="{{ path(paginationPath, currentFilters|merge({ page: currentPage })) }}"
class="active">{{ currentPage }}</a>
{% if currentPage < lastPage %}
{% for i in range(currentPage+1, currentPage + nearbyPagesLimit) if ( i <= lastPage ) %}
<a href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a>
{% endfor %}
{% if (lastPage - extremePagesLimit) > (currentPage + nearbyPagesLimit) %}
<span class="sep-dots">...</span>
{% endif %}
{% for i in range(lastPage - extremePagesLimit+1, lastPage) if ( i > currentPage + nearbyPagesLimit ) %}
<a href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a>
{% endfor %}
<a href="{{ path(paginationPath, currentFilters|merge({page: currentPage+1})) }}">Next</a>
{% elseif showAlwaysFirstAndLast %}
<span class="disabled">Next</span>
{% endif %}
</div>
{% endif %}
{% endspaceless %}
@SimonSimCity

This comment has been minimized.

Copy link
Owner Author

@SimonSimCity SimonSimCity commented Feb 27, 2013

If you don't know how to use this - here's a short explanation:
Save the code visible here in a file. You can f.e. call it pagination.html.twig.

Now put some code like this into your template at the position, where the page-browser should appear (can as well be multiple times ... f.e. before and after your list of items):

{% include 'pagination.html.twig' with {
    currentFilters: { myFilter: filtervariables },
    currentPage: page,
    paginationPath: "myroute",
    lastPage: totalPages,
    showAlwaysFirstAndLast: true
} only %}

Feel free to star, fork, comment and whatever you can do with this code :)
It's licensed using the MIT license.

@SimonSimCity

This comment has been minimized.

Copy link
Owner Author

@SimonSimCity SimonSimCity commented Feb 27, 2013

If you're using this script outside of Symfony2, make sure you have a function called path() that handles url-generation ... http://twig.sensiolabs.org/doc/advanced.html#functions

@TiMiXx

This comment has been minimized.

Copy link

@TiMiXx TiMiXx commented Mar 28, 2013

Ok version without path() function and bootstrap integration

{% if lastPage > 1 %}

    {# the number of first and last pages to be displayed #}
    {% set extremePagesLimit = 3 %}

    {# the number of pages that are displayed around the active page #}
    {% set nearbyPagesLimit = 2 %}

    <div class="pagination pagination-centered">
        <ul>
        {% if currentPage > 1 %}
            <li><a href="{{ paginationPath }}{{ currentPage-1 }}">&larr;</a></li>

            {% for i in range(1, extremePagesLimit) if ( i < currentPage - nearbyPagesLimit ) %}
                <li><a href="{{ paginationPath }}{{ i }}">{{ i }}</a></li>
            {% endfor %}

            {% if extremePagesLimit + 1 < currentPage - nearbyPagesLimit %}
                <li class="disabled"><a href="#">...</a></li>
            {% endif %}

            {% for i in range(currentPage-nearbyPagesLimit, currentPage-1) if ( i > 0 ) %}
                <li><a href="{{ paginationPath }}{{ i }}">{{ i }}</a></li>
            {% endfor %}
        {% elseif showAlwaysFirstAndLast %}
            <li><a href="#">Previous</a></li>
        {% endif %}

        <li class="active"><a href="#">{{ currentPage }}</a></li>

        {% if currentPage < lastPage %}
            {% for i in range(currentPage+1, currentPage + nearbyPagesLimit) if ( i <= lastPage ) %}
                <li><a href="{{ paginationPath }}{{ i }}">{{ i }}</a></li>
            {% endfor %}

            {% if  (lastPage - extremePagesLimit) > (currentPage + nearbyPagesLimit) %}
               <li class="disabled"><a href="#">...</a></li>
            {% endif %}

            {% for i in range(lastPage - extremePagesLimit+1, lastPage) if ( i > currentPage + nearbyPagesLimit ) %}
               <li><a href="{{ paginationPath }}{{ i }}">{{ i }}</a></li>
            {% endfor %}

            <li><a href="{{ paginationPath }}{{ currentPage+1 }}">&rarr;</a></li>
        {% elseif showAlwaysFirstAndLast %}
                <li><a href="{{ paginationPath }}{{ currentPage+1 }}">Next</a></li>
        {% endif %}
                </ul>
    </div>
{% endif %}
@ambroisemaupate

This comment has been minimized.

Copy link

@ambroisemaupate ambroisemaupate commented Nov 4, 2015

Thanks for this great gist!

@cppobj

This comment has been minimized.

Copy link

@cppobj cppobj commented Nov 19, 2015

Thank you!

@anonbeat

This comment has been minimized.

Copy link

@anonbeat anonbeat commented May 27, 2016

Thank you!!

@omerucel

This comment has been minimized.

Copy link

@omerucel omerucel commented Aug 12, 2016

Without path function, we can use url_encode function. http://twig.sensiolabs.org/doc/filters/url_encode.html

{{ paginationPath ~ '?' ~ currentFilters|merge({page: i})|url_encode }}
@happygrizzly

This comment has been minimized.

Copy link

@happygrizzly happygrizzly commented Oct 5, 2016

This is great! Thank you:) God bless you))

@3kynox

This comment has been minimized.

Copy link

@3kynox 3kynox commented Nov 22, 2016

Just fantastic, works great.

My splitting implementation using twig on a php standalone project :

#index.php

<?php

/*
 * MVC Website Entry - Only exposed file
 */

require_once('../vendor/autoload.php');
require('../src/controller/controller.php');

$ctrl = new Controller();

try {
    if (isset($_GET['action'])) {
        if ($_GET['action'] == 'game') {
            $ctrl->game();
        } elseif ($_GET['action'] == 'game_tutorial') {
            $ctrl->game_tutorial();
        } elseif ($_GET['action'] == 'game_leaderboard') {
            $ctrl->game_leaderboard(!isset($_GET['page']) ? 1 : $_GET['page']);
        }
    } else {
        $ctrl->home();
    }
}
catch (Exception $e) {
    echo '<html><body>' . $e->getMessage() . '</body></html>';
}

GET ('page'] is the important thing here. Followed by controller :

#controller.php

...
public function game_leaderboard($currentPage) {
        $leaderboard = getLeaderboard();
        $limit = 50; // leaderboard rows limit
        $offset = ($currentPage - 1) * $limit; // offset
        $totalItems = count($leaderboard); // total items
        $totalPages = ceil($totalItems / $limit);
        $itemsList = array_splice($leaderboard, $offset, $limit);

        $template = $this->twig->loadTemplate('viewLeaderboard.html.twig');
        echo $template->render(array(
            'leaderboard' => $itemsList,
            'currentPage' => $currentPage,
            'totalPages' => $totalPages
        ));
    }

getLeaderboard is just an api call where I get the itemList, then splitted, and finally sent to twig view.

@GitHub-AndreLuis

This comment has been minimized.

Copy link

@GitHub-AndreLuis GitHub-AndreLuis commented Dec 22, 2016

Thank you!!

@amcosta

This comment has been minimized.

Copy link

@amcosta amcosta commented Jan 28, 2017

Great job.

Thank you

@gtrocks

This comment has been minimized.

Copy link

@gtrocks gtrocks commented Feb 18, 2017

Hi ,

can you put the all code in single desk so, its easy for me to use ..

@TiMiXx using their post created a new file pagination.html.twig and included it in other template where i have to show the pagination but what changes i have to do on controller files to run it finally ?
please help i am new in symphony 2.

@ardentsword

This comment has been minimized.

Copy link

@ardentsword ardentsword commented Aug 23, 2017

I required default settings for the input, it cleans up my templates a lot. If anyone needs it:

        {% if currentFilters is not defined %}{% set currentFilters = {} %}{% endif %}
        {% if paginationPath is not defined %}{% set paginationPath = app.request.attributes.get('_route') %}{% endif %}
        {% if showAlwaysFirstAndLast is not defined %}{% set showAlwaysFirstAndLast = true %}{% endif %}

The currentFilters are empty by default, the showAlwaysFirstAndLast is set to true and paginationPath is set to a variable automatically passed along in Symfony (3) with contains the current route.

Another (a bit shorter) way to include the template is:

{{ include('pagination.html.twig', { 
    currentFilters: { myFilter: filtervariables },
    currentPage: page,
    paginationPath: "myroute",
    lastPage: totalPages,
    showAlwaysFirstAndLast: true
}) }}

Which can be reduced to this with the default vars:

{{ include('pagination.html.twig', { 
    currentPage: page,
    lastPage: totalPages
}) }}

I also modified my script a bit to work with Bootstrap (v4, maybe lower, untested) buttons, which looks quite refined in my opinion. Might be useful for someone:

{% spaceless %}
    {% if lastPage > 1 %}

        {# the number of first and last pages to be displayed #}
        {% set extremePagesLimit = 3 %}

        {# the number of pages that are displayed around the active page #}
        {% set nearbyPagesLimit = 2 %}

        {% if currentFilters is not defined %}{% set currentFilters = {} %}{% endif %}
        {% if paginationPath is not defined %}{% set paginationPath = app.request.attributes.get('_route') %}{% endif %}
        {% if showAlwaysFirstAndLast is not defined %}{% set showAlwaysFirstAndLast = true %}{% endif %}

        <nav aria-label="Page navigation example">
        <ul class="pagination">
            {% if currentPage > 1 %}
                <li class="page-item"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({page: currentPage-1})) }}">Previous</a></li>

                {% for i in range(1, extremePagesLimit) if ( i < currentPage - nearbyPagesLimit ) %}
                    <li class="page-item"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a></li>
                {% endfor %}

                {% if extremePagesLimit + 1 < currentPage - nearbyPagesLimit %}
                    <span class="sep-dots">...</span>
                {% endif %}

                {% for i in range(currentPage-nearbyPagesLimit, currentPage-1) if ( i > 0 ) %}
                    <li class="page-item"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a></li>
                {% endfor %}
            {% elseif showAlwaysFirstAndLast %}
                <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
            {% endif %}

            <li class="page-item active"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({ page: currentPage })) }}">{{ currentPage }}</a></li>

            {% if currentPage < lastPage %}
                {% for i in range(currentPage+1, currentPage + nearbyPagesLimit) if ( i <= lastPage ) %}
                    <li class="page-item"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a></li>
                {% endfor %}

                {% if  (lastPage - extremePagesLimit) > (currentPage + nearbyPagesLimit) %}
                    <span class="sep-dots">...</span>
                {% endif %}

                {% for i in range(lastPage - extremePagesLimit+1, lastPage) if ( i > currentPage + nearbyPagesLimit ) %}
                    <li class="page-item"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({page: i})) }}">{{ i }}</a></li>
                {% endfor %}

                <li class="page-item"><a class="page-link" href="{{ path(paginationPath, currentFilters|merge({page: currentPage+1})) }}">Next</a></li>
            {% elseif showAlwaysFirstAndLast %}
                <li class="page-item disabled"><a class="page-link" href="#">Next</a></li>
            {% endif %}
        </ul>
        </nav>
    {% endif %}
{% endspaceless %}

Thanks a lot for creating this, helped safe me a lot of headache!

Fork: https://gist.github.com/ardentsword/4286d4bc71cb783657f6aa1ab52176fd

edit: Fix typo mentioned by @lemmingz

@lemmingz

This comment has been minimized.

Copy link

@lemmingz lemmingz commented Sep 17, 2017

@ardentsword you have a typo with currentFilers vs currentFilters which always empties the current filters.

@brittonwalker

This comment has been minimized.

Copy link

@brittonwalker brittonwalker commented Nov 14, 2017

@SimonSimCity Thank you so much for making this, you are my hero.

@GSAnthony

This comment has been minimized.

Copy link

@GSAnthony GSAnthony commented Nov 23, 2017

Thank you for this gist and all other versions of this gist.

@ardentsword

This comment has been minimized.

Copy link

@ardentsword ardentsword commented Dec 2, 2017

Another slight improvement for the issue I ran into today, when currentFilters is not defined, get the current route params:

{% if currentFilters is not defined %}{% set currentFilters = app.request.attributes.get('_route_params') %}{% endif %}
@ra0ued

This comment has been minimized.

Copy link

@ra0ued ra0ued commented Jan 30, 2018

Thank you!

@ljayz

This comment has been minimized.

Copy link

@ljayz ljayz commented Feb 4, 2018

Thank you!

@alexcarpenter

This comment has been minimized.

Copy link

@alexcarpenter alexcarpenter commented Apr 18, 2018

Might add aria-current="page" to the active pagination link for better accessibility.

@Grawl

This comment has been minimized.

Copy link

@Grawl Grawl commented Jul 11, 2018

I just updated it for my next work project using UIkit.

I added scope object and used all options from it.

Also I added two methods of URL building:

  • page number in URL query parameters like /news?page=4
  • page number directly after paginationPath option like /news/4

I replaced path() function with macros with |url_encode filter to convert an object to URL query string.

Here's a fork: https://gist.github.com/Grawl/5ebe19aa808e9b4371938a697b1597e8

Will be good to add an option to modify page URL query key to get /news?p=4 or /news?pagination=4

@KaiCMueller

This comment has been minimized.

Copy link

@KaiCMueller KaiCMueller commented Aug 27, 2018

Thank you for the great work! I adapted your approach to build a paginator for the Symfony EasyAdmin bundle: https://gist.github.com/KaiCMueller/6692ee84f51341acf582e2103f05f3d4

@Lopton

This comment has been minimized.

Copy link

@Lopton Lopton commented Jun 29, 2019

As a warning to newer users Twig is deprecating the if appended to the end of the for statements

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.