ssg  
Article Series

This article series contain few sections.


Obsolete Repository:
This article use obsolete repository. Please refer to below repository for a more recent articles.

Complete Repository

Where to Discuss?

Local Group

Preface

Goal: Explaining Glenn McComb Pagination using Math and Table.

Preview

This is what we want to achieve in this tutorial.

Jekyll Pagination: Adjacent Animation

Notice that this is just the middle pagination. We will combine it later with indicator to make it pretty.

Hugo Pagination: Combined Animation

The later figure will be discussed in the next article.

More Recent Code

I have made a better code with Bulma utilizing site.pagination.permalink which you can examine at:


1: Prepare

Artefact that we need.

Source

I respect copyright. The code below copied, and pasted from:

I made a slight modification. But of course the logic remain the same.

I mostly write it down in my blog, so I can find my custom code easily.

Includes: Pagination: Adjacent

Create an empty artefact. Source code used in this tutorial, is available at this repository:

  • /_includes/pagination/03-adjacent.html

Pages: Blog List

Set this blog list page:

  {% include pagination/03-adjacent.html %}

How Does It Works ?

It is basically just mark every number with flag. If the flag is true, the page will be shown, and it will not be shown if the flag value is false.

  {% if total_pages > 1 %}
  <ul class="pagination justify-content-center">

    <!-- Page numbers. -->
    {% for page in (1..total_pages) %}
      <!-- Flag Calculation -->
    
      <!-- Show Pager: Output Here -->

    {% endfor %}

  </ul>
  {% endif %}

Skeleton

To show how complex it is. This is just skeleton, as we will discuss this later.

{% capture spaceless %}
<!-- Variable Initialization -->
{% endcapture %}

<nav aria-label="Page navigation">
  {% if total_pages > 1 %}
  <ul class="pagination justify-content-center">

    <!-- Page numbers. -->
    {% for page in (1..total_pages) %}
      {% capture spaceless %}
      <!-- Flag Calculation -->
    
      {% if total_pages > link_max %}
      <!-- Complex page numbers. -->

        <!-- Lower limit pages. -->
        <!-- Upper limit pages. -->
        <!-- Middle pages. -->
      {% else %}
      <!-- Simple page numbers. -->
      {% endif %}
      {% endcapture %}

      <!-- Show Pager: Output Here -->
    {% endfor %}

  </ul>
  {% endif %}

</nav>

2: Preview: General

It is not easy to explain by words. Let me explain what we want to achive by using these images below. The blue box, is the active page. We have from first page (1), to last page (10).

Animation: Combined Version

This is the complete version. We will achieve this later.

Jekyll Pagination: Combined Animation

Animation: Stripped Version

I’m following Glenn McComb code, combined with my own code, and this is the result.

Jekyll Pagination: Adjacent Animation

Sample: An Example

Consider get one frame, a sample, because we need an example. This is what we want to achieve in this tutorial.

Jekyll Pagination: Adjacent Page 5

HTML Preview

The HTML that we want to achieve in this article, is similar as below.

<nav aria-label="Page navigation">

  <ul class="pagination justify-content-center">
      <li class="page-item ">
          <a class="page-link" href="/demo-jekyll/pages/blog-3">3</a>
      </li>
      
      <li class="page-item ">
          <a class="page-link" href="/demo-jekyll/pages/blog-4">4</a>
      </li>

      <li class="page-item  active">
          <span class="page-link">5</span>
      </li>

      <li class="page-item ">
          <a class="page-link" href="/demo-jekyll/pages/blog-6">6</a>
      </li>

      <li class="page-item ">
          <a class="page-link" href="/demo-jekyll/pages/blog-7">7</a>
      </li>
  </ul>

</nav>

We will achieve this with Jekyll code.

The Riddle

How do we achieve this ?


4: Math: Basic Algebra

Assumption

Consider our previous example, a blog post contain ten posts. This time with two adjacent link offset. It means, two indicators before selected page, and another two indicators after selected page,

# CONST

$totalPost   = 10
$link_offset = 2

Equation

We should manually, do the math.

EQUATION

$link_max    = ($link_offset * 2) + 1 = 5
$limit_lower =  1 + $link_offset      = 3
$limit_upper = 10 - $link_offset      = 8 

The real code is shown as below:

{% capture spaceless %}
  {% assign total_pages   = paginator.total_pages %}
  {% assign page_current  = paginator.page %}

  {% assign link_offset   = 2 %}  
  {% assign link_max      = link_offset | times: 2 | plus: 1 %}
  
  {% assign limit_lower   = link_offset | plus: 1 %}
  {% assign limit_upper   = total_pages  | minus: link_offset %}
{% endcapture %}

Again, that source above are, ported from:

With some additional variable:

{% capture spaceless %} 
  {% assign min_lower     = link_max %}  
  {% assign max_upper     = total_pages | minus: link_max %}
    
  {% assign lower_offset  = page_current | minus: link_offset %}  
  {% assign upper_offset  = page_current | plus: link_offset %}  
{% endcapture %}

We calculate these additinal variable earlier, to avoid complexity with limitation of Jekyll Liquid.

Table

The result is on this table below.

# ALGEBRA

+--------------+-------+-------+-------+-------+-------+
| $pagination  |   1   |   2   |   5   |   7   |  10   |
+--------------+-------+-------+-------+-------+-------+
| VARIABLE                                             |
| $pages_total |  10   |   5   |   2   |   2   |  N/A  |
| $link_max    |   5   |   5   |   5   |   5   |  N/A  |
| $limit_lower |   3   |   3   |   3   |   3   |  N/A  |
| $limit_upper |   8   |   3   |   0   |   0   |  N/A  |
+--------------+-------+-------+-------+-------+-------+

5: Preview: Detail

Consider, have a look at the pagination below in a stripped down model.

Structure

This will only show one part:

  • Middle Pagination: Glenn McComb

Each Pagination

Consider, have a look at the animation above, frame by frame. I’m going to do some reverse engineering, to accomplish better understanding on how this pagination works.

We have from first page (1), to last page (10).

Hugo Pagination: Adjacent Page 1

Hugo Pagination: Adjacent Page 2

Hugo Pagination: Adjacent Page 3

Hugo Pagination: Adjacent Page 4

Hugo Pagination: Adjacent Page 5

Hugo Pagination: Adjacent Page 6

Hugo Pagination: Adjacent Page 7

Hugo Pagination: Adjacent Page 8

Hugo Pagination: Adjacent Page 9

Hugo Pagination: Adjacent Page 10

Table

We can rewrite the table with additional rows as below.

+--------------+-------+-------+-------+-------+-------+
| $pagination  |   1   |   2   |   5   |   7   |  10   |
+--------------+-------+-------+-------+-------+-------+
| VARIABLE                                             |
| $pages_total |  10   |   5   |   2   |   2   |  N/A  |
| $link_max    |   5   |   5   |   5   |   5   |  N/A  |
| $limit_lower |   3   |   3   |   3   |   3   |  N/A  |
| $limit_upper |   8   |   3   |   0   |   0   |  N/A  |
+--------------+-------+-------+-------+-------+-------+
| MIDDLE PAGINATION                                    |
| $pgNum =  1  | 1..5  | 1..5  | 1..2  | 1..2  |-------+
| $pgNum =  2  | 1..5  | 1..5  | 1..2  | 1..2  |       |
| $pgNum =  3  | 1..5  | 1..5  |-------+-------+       |
| $pgNum =  4  | 2..6  | 1..5  |                       |
| $pgNum =  5  | 3..7  | 1..5  |                       |
| $pgNum =  6  | 4..8  |-------+                       |
| $pgNum =  7  | 5..9  |                               |
| $pgNum =  8  | 6..10 |                               |
| $pgNum =  9  | 6..10 |                               |
| $pgNum = 10  | 6..10 |                               |
+--------------+-------+-------------------------------+

Flag Calculation

This utilized page_number_flag with complex logic.

    {% capture spaceless %}
      <!-- Flag Calculation -->
      {% assign page_current_flag = false %}

      {% if total_pages > link_max %}
      <!-- Complex page numbers. -->

        <!-- Lower limit pages. -->
        <!-- If the user is on a page which is in the lower limit.  -->
        {% if page_current <= limit_lower %}
          <!-- If the current loop page is less than max_links. -->
          {% if page <= min_lower %}
            {% assign page_current_flag = true %}
          {% endif %}

        <!-- Upper limit pages. -->
        <!-- If the user is on a page which is in the upper limit. -->
        {% elsif page_current >= limit_upper %}
          <!-- If the current loop page is greater than total pages minus $max_links -->
          {% if page > max_upper %}
            {% assign page_current_flag = true %}
          {% endif %}

        <!-- Middle pages. -->
        {% else %}
          
          {% if (page >= lower_offset) and (page <= upper_offset) %}
            {% assign page_current_flag = true %}
          {% endif %}

        {% endif %}

      {% else %}
      <!-- Simple page numbers. -->

        {% assign page_current_flag = true %}
      {% endif %}
    {% endcapture %}

Show Time

And showing the number whenever the page_number_flag comes out. With similar code as previous article.

      <!-- Show Pager. -->
      {% if page_current_flag == true %}
      <li class="page-item {% if page == page_current %} active{% endif %}">
        {% if page == page_current %} 
          <span class="page-link">
            {{ page }}
          </span>
        {% elsif page == 1 %}
          <a class="page-link"
             href="{{ site.baseurl }}{{ paginate_root }}"
           >1</a>
        {% else %}
          <a class="page-link"
             href="{{ site.paginate_path | relative_url | replace: ':num', page }}"
           >{{ page }}
          </a>
        {% endif %}
      </li>
      {% endif %}

The Same Riddle

How does it works ?

Really! It is confusing.

Don’t be panic!


6: Math: Conditional

Part: Middle Pages

This is already discussed in, so I won’t explain it nomore.

{% capture spaceless %} 
  {% assign page_current  = paginator.page %}
    
  {% assign lower_offset  = page_current | minus: link_offset %}  
  {% assign upper_offset  = page_current | plus: link_offset %}  
{% endcapture %}

        <!-- Middle pages. -->
        {% else %}
          
          {% if (page >= lower_offset) and (page <= upper_offset) %}
            {% assign page_current_flag = true %}
          {% endif %}

        {% endif %}

What you need to know is the conditional result in table:

+--------------+-------+
| $pagination  |   1   |
| $link_offset |   2   |
| $pages_total |  10   |
+--------------+-------+
| VARIABLE             |
| $link_max    |   5   |
| $limit_lower |   3   |
| $limit_upper |   8   |
+--------------+-------+-+
| page_number  | adjacent|
+--------------+---------+
| $pgNum =  1  |  1..3   |
| $pgNum =  2  |  1..4   |
| $pgNum =  3  |  1..5   |
| $pgNum =  4  |  2..6   |
| $pgNum =  5  |  3..7   |
| $pgNum =  6  |  4..8   |
| $pgNum =  7  |  5..9   |
| $pgNum =  8  |  6..10  |
| $pgNum =  9  |  7..10  |
| $pgNum = 10  |  8..10  |
+--------------+---------+

Part: Lower Limit Pages

Consider stripped more for each part.

{% capture spaceless %} 
  {% assign link_offset   = 2 %}  
  {% assign link_max      = link_offset | times: 2 | plus: 1 %}
  
  {% assign limit_lower   = link_offset | plus: 1 %}
  {% assign min_lower     = link_max %}  
{% endcapture %}

      <!-- Flag Calculation -->
      {% assign page_current_flag = false %}

      {% if total_pages > link_max %}
      <!-- Complex page numbers. -->

        <!-- Lower limit pages. -->
        <!-- If the user is on a page which is in the lower limit.  -->
        {% if page_current <= limit_lower %}

          <!-- If the current loop page is less than max_links. -->
          {% if page <= min_lower %}
            {% assign page_current_flag = true %}
          {% endif %}

        {% endif %}

      {% else %}
      <!-- Simple page numbers. -->

        {% assign page_current_flag = true %}
      {% endif %}

Notice that there is two part of conditional.

  • Outer conditional: result true for the first three row, as defined by $limit_lower.

  • Inner conditional: always result 1..5

Thus, the conditional result in table:

+--------------+-------+-------+--------+
| page_number  | lower | l max | result |
+--------------+-------+-------+--------+
| $pgNum =  1  |   T   | 1..5  |  1..5  |
| $pgNum =  2  |   T   | 1..5  |  1..5  |
| $pgNum =  3  |   T   | 1..5  |  1..5  |
| $pgNum =  4  |       | 1..5  |        |
| $pgNum =  5  |       | 1..5  |        |
| $pgNum =  6  |       | 1..5  |        |
| $pgNum =  7  |       | 1..5  |        |
| $pgNum =  8  |       | 1..5  |        |
| $pgNum =  9  |       | 1..5  |        |
| $pgNum = 10  |       | 1..5  |        |
+--------------+-------+-------+--------+

Combined: All Conditional

Now we have all the logic combined at once.

+--------------+-------+
| $pagination  |   1   |
| $link_offset |   2   |
+--------------+-------+
| VARIABLE             |
| $pages_total |  10   |
| $max_links   |   5   |
| $lower_limit |   3   |
| $upper_limit |   8   |
+--------------+-------+-+-------+-------+-------+-------+
| page_number  | adjacent| lower | l max | upper | u max |
+--------------+---------+-------+-------+-------+-------+
| $pgNum =  1  |  1..3   |   T   | 1..5  |       | 6..10 |
| $pgNum =  2  |  1..4   |   T   | 1..5  |       | 6..10 |
| $pgNum =  3  |  1..5   |   T   | 1..5  |       | 6..10 |
| $pgNum =  4  |  2..6   |       | 1..5  |       | 6..10 |
| $pgNum =  5  |  3..7   |       | 1..5  |       | 6..10 |
| $pgNum =  6  |  4..8   |       | 1..5  |       | 6..10 |
| $pgNum =  7  |  5..9   |       | 1..5  |       | 6..10 |
| $pgNum =  8  |  6..10  |       | 1..5  |   T   | 6..10 |
| $pgNum =  9  |  7..10  |       | 1..5  |   T   | 6..10 |
| $pgNum = 10  |  8..10  |       | 1..5  |   T   | 6..10 |
+--------------+---------+-------+-------+-------+-------+

Final Result

As a conclusion table.

+--------------+-------+
| VARIABLE             |
| $pages_total |  10   |
| $max_links   |   5   |
| $lower_limit |   3   |
| $upper_limit |   8   |
+--------------+-------+-------+---------+
| page_number  | lower | upper | adjacent|
+--------------+-------+-------+---------+
| $pgNum =  1  | 1..5  |       |         |
| $pgNum =  2  | 1..5  |       |         |
| $pgNum =  3  | 1..5  |       |         |
| $pgNum =  4  |       |       |  2..6   |
| $pgNum =  5  |       |       |  3..7   |
| $pgNum =  6  |       |       |  4..8   |
| $pgNum =  7  |       |       |  5..9   |
| $pgNum =  8  |       | 6..10 |         |
| $pgNum =  9  |       | 6..10 |         |
| $pgNum = 10  |       | 6..10 |         |
+--------------+-------+-------+---------+
| page_number  | if elsif else | result  |
+--------------+---------------+---------+
| $pgNum =  1  |               |  1..5   |
| $pgNum =  2  |               |  1..5   |
| $pgNum =  3  |               |  1..5   |
| $pgNum =  4  |               |  2..6   |
| $pgNum =  5  |               |  3..7   |
| $pgNum =  6  |               |  4..8   |
| $pgNum =  7  |               |  5..9   |
| $pgNum =  8  |               |  6..10  |
| $pgNum =  9  |               |  6..10  |
| $pgNum = 10  |               |  6..10  |
+--------------+---------------+---------+

7: Summary: Navigation: Adjacent

Now you can enjoy the complete adjacent code as below:

{% capture spaceless %}

  {% comment %}
  Pagination links 
  * https://glennmccomb.com/articles/how-to-build-custom-hugo-pagination/
  {% endcomment %}

  {% if page.paginate_root == nil %}
    {% assign paginate_root = "/" %}
  {% else %}    
    {% assign paginate_root = page.paginate_root %}
  {% endif %}

  {% assign total_pages   = paginator.total_pages %}
  {% assign page_current  = paginator.page %}

  {% assign link_offset   = 2 %}  
  {% assign link_max      = link_offset | times: 2 | plus: 1 %}
  
  {% assign limit_lower   = link_offset | plus: 1 %}
  {% assign limit_upper   = total_pages  | minus: link_offset %}
  
  {% assign min_lower     = link_max %}  
  {% assign max_upper     = total_pages | minus: link_max %}
    
  {% assign lower_offset  = page_current | minus: link_offset %}  
  {% assign upper_offset  = page_current | plus: link_offset %}  
{% endcapture %}

<nav aria-label="Page navigation">

  {% if total_pages > 1 %}
  <ul class="pagination justify-content-center">

    <!-- Page numbers. -->
    {% for page in (1..total_pages) %}
    
    {% capture spaceless %}
      <!-- Flag Calculation -->
      {% assign page_current_flag = false %}

      {% if total_pages > link_max %}
      <!-- Complex page numbers. -->

        <!-- Lower limit pages. -->
        <!-- If the user is on a page which is in the lower limit.  -->
        {% if page_current <= limit_lower %}
          <!-- If the current loop page is less than max_links. -->
          {% if page <= min_lower %}
            {% assign page_current_flag = true %}
          {% endif %}

        <!-- Upper limit pages. -->
        <!-- If the user is on a page which is in the upper limit. -->
        {% elsif page_current >= limit_upper %}
          <!-- If the current loop page is greater than total pages minus $max_links -->
          {% if page > max_upper %}
            {% assign page_current_flag = true %}
          {% endif %}

        <!-- Middle pages. -->
        {% else %}
          
          {% if (page >= lower_offset) and (page <= upper_offset) %}
            {% assign page_current_flag = true %}
          {% endif %}

        {% endif %}

      {% else %}
      <!-- Simple page numbers. -->

        {% assign page_current_flag = true %}
      {% endif %}
    {% endcapture %}

      <!-- Show Pager. -->
      {% if page_current_flag == true %}
      <li class="page-item {% if page == page_current %} active{% endif %}">
        {% if page == page_current %} 
          <span class="page-link">
            {{ page }}
          </span>
        {% elsif page == 1 %}
          <a class="page-link"
             href="{{ site.baseurl }}{{ paginate_root }}"
           >1</a>
        {% else %}
          <a class="page-link"
             href="{{ site.paginate_path | relative_url | replace: ':num', page }}"
           >{{ page }}
          </a>
        {% endif %}
      </li>
      {% endif %}
    {% endfor %}

  </ul>
  {% endif %}

</nav>

Notice that this is not the final code, as we want to add some indicator and cosmetic later.

Caveat

That code above will dump a lot of comments.

<!-- Show Pager. -->

Jekyll Demo: Useless Comment

Even in capture liquid tag. We also need to remove them later, in final code.

As an alternative, there is also built in Jekyll Liquid comment. But it is hard to read. At least for me.


What is Next ?

Consider continue reading [ Jekyll Pagination - Indicator ].

Thank you for reading.