Advanced Pagination with ellipsis

Situation

I am developing a blog using the RainLab.Blog plugin where I want the Post List component to paginate with 7 posts a page. However, the default output for the pagination does not include ellipsis to shorten the pagination, so I tried copying this snippet from the forum plugin which works very well, this is my implementation:

<div class="pagination">
	{% if posts.currentPage > 1 %}
		<a href="{{ this.page.baseFileName | page({ (blogPosts.pageParam): (posts.currentPage - 1) }) }}" class="previous">Prev</a>
	{% endif %}

	{# How many pages to display around the current page #}
	{% set radius = 1 %}

	{% set startOffset = max((posts.currentPage - 1) - radius, 0) %}

	{% if (startOffset + 2 * radius + 1) > (posts.lastPage - 1) %}
		{% set startOffset = max(posts.lastPage - 2 * radius - 1, 0) %}
	{% endif %}

	{% set pageLinks = [] %}

	{% for page in 1..posts.lastPage %}
		{% set pageLinks = pageLinks | merge([page]) %}
	{% endfor %}

	{% set activeBlock = pageLinks | slice(startOffset, 2 * radius + 1) %}

	{% set pageSet = [] %}

	{% if startOffset > 0 %}
		{% set pageSet = pageSet | merge([1]) %}

		{% if startOffset > 1 %}
			{% set pageSet = pageSet | merge(['...']) %}
		{% endif %}
	{% endif %}

	{% set pageSet = pageSet | merge(activeBlock) %}

	{% set diffToEnd = (posts.lastPage - 1) - (startOffset + 2 * radius + 1) + 1 %}

	{% if diffToEnd > 0 %}
		{% if diffToEnd > 1 %}
			{% set pageSet = pageSet | merge(['...']) %}
		{% endif %}

		{% set pageSet = pageSet | merge([posts.lastPage]) %}
	{% endif %}

	{% for page in pageSet %}
		{% if page == '...' %}
			<span class="extra">&hellip;</span>
		{% else %}
			<a href="{{ this.page.baseFileName | page({ (blogPosts.pageParam): page }) }}" class="page {{ posts.currentPage == page ? 'active' : null }}">{{ page }}</a>
		{% endif %}
	{% endfor %}

	{% if posts.lastPage > posts.currentPage %}
		<a href="{{ this.page.baseFileName | page({ (blogPosts.pageParam): (posts.currentPage + 1) }) }}" class="next">Next</a>
	{% endif %}
</div>

Results

This implementation gets me very far in the sense that the pagination is no longer outputting every single page as a link, but as I find the reference code not very self-documenting, I’m struggling to tweak it to suit my needs.

Desired Outcome

I want not only the current page to be padded with two page links in either direction, but also on both ends of the pagination.

So instead of something like this:
< Prev 1 … 14 15 16 17 18 … 100 Next >

I want something like this:
< Prev 1 2 3 … 14 15 16 17 18 … 98 99 100 Next >

How would I go about padding by two pages from the first page and last page?
Cheers, guys.

Hi @OleKristianMoller-Ha

This is a tough one. The current logic is designed to build a middle set of pages and use the first and last pages as endcaps - 1 and posts.lastPage. Expanding the end caps to be three pages in length would require a rewrite of this logic.

However, you might be able to tweak the current code to expand the endcaps to a predefined set of padded numbers - [1,2,3] and [lastPage - 2, lastPage - 1, lastPage].

It’s not a perfect solution but might give some ideas. First define the size of the endcaps.

{% set padding = 3 %}

Then define them using a counter, and include logic to exclude page numbers we already know about (activeBlock).

{% set activeBlock = pageLinks | slice(startOffset, 2 * radius + 1) %}

{% set startBlock = [] %}
{% for pad in 1..padding %}
    {% if pad not in activeBlock %}
        {% set startBlock = startBlock | merge([pad]) %}
    {% endif %}
{% endfor %}

{% set endBlock = [] %}
{% for pad in padding..1 %}
    {% if posts.lastPage - pad + 1 not in activeBlock %}
        {% set endBlock = endBlock | merge([posts.lastPage - pad + 1]) %}
    {% endif %}
{% endfor %}

Then instead of merging in a static endcap, we merge in the new ones we created.

{% set pageSet = pageSet | merge([1]) %}{# old endcap #}
{% set pageSet = pageSet | merge(startBlock) %}{# new endcap #}

{% set pageSet = pageSet | merge([posts.lastPage]) %}{# old endcap #}
{% set pageSet = pageSet | merge(endBlock) %}{# new endcap #}

Here is a complete example:

{% if posts.currentPage > 1 %}
    <a href="{{ this.page.baseFileName | page({ page: (posts.currentPage - 1) }) }}" class="previous">Prev</a>
{% endif %}

{# How many pages to display around the current page #}
{% set radius = 1 %}

{# How many pages to display at the start and end #}
{% set padding = 3 %}

{% set startOffset = max((posts.currentPage - 1) - radius, 0) %}

{% if (startOffset + 2 * radius + 1) > (posts.lastPage - 1) %}
    {% set startOffset = max(posts.lastPage - 2 * radius - 1, 0) %}
{% endif %}

{% set pageLinks = [] %}

{% for page in 1..posts.lastPage %}
    {% set pageLinks = pageLinks | merge([page]) %}
{% endfor %}

{% set activeBlock = pageLinks | slice(startOffset, 2 * radius + 1) %}

{% set startBlock = [] %}
{% for pad in 1..padding %}
    {% if pad not in activeBlock %}
        {% set startBlock = startBlock | merge([pad]) %}
    {% endif %}
{% endfor %}

{% set endBlock = [] %}
{% for pad in padding..1 %}
    {% if posts.lastPage - pad + 1 not in activeBlock %}
        {% set endBlock = endBlock | merge([posts.lastPage - pad + 1]) %}
    {% endif %}
{% endfor %}

{% set pageSet = [] %}

{% if startOffset > 0 %}
    {% set pageSet = pageSet | merge(startBlock) %}

    {% if startOffset > 1 + padding %}
        {% set pageSet = pageSet | merge(['...']) %}
    {% endif %}
{% endif %}

{% set pageSet = pageSet | merge(activeBlock) %}

{% set diffToEnd = (posts.lastPage - 1) - (startOffset + 2 * radius + 1) + 1 %}

{% if diffToEnd > 0 %}
    {% if diffToEnd > 1 %}
        {% set pageSet = pageSet | merge(['...']) %}
    {% endif %}

    {% set pageSet = pageSet | merge(endBlock) %}
{% endif %}

{% for page in pageSet %}
    {% if page == '...' %}
        <span class="extra">&hellip;</span>
    {% else %}
        <a href="{{ this.page.baseFileName | page({ page: page }) }}" class="page {{ posts.currentPage == page ? 'active' : null }}">{{ page }}</a>
    {% endif %}
{% endfor %}

{% if posts.lastPage > posts.currentPage %}
    <a href="{{ this.page.baseFileName | page({ page: (posts.currentPage + 1) }) }}" class="next">Next</a>
{% endif %}
1 Like

Magnificent! This solution yields exactly the results I’m after!

1 Like