Where to Discuss?

Local Group

Preface

Goal: Explaining Glenn McComb Pagination using Math and Table.


1: 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, because I do not want to forget what I learn. An easier place for me to find the modified version of this good code.


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.

Hugo Pagination: Combined Animation

Animation: Stripped Version

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

Hugo 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.

Hugo Pagination: Adjacent Page 5

HTML Preview

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

<ul class="pagination justify-content-center">
  <li class="page-item">
    <a href="/pages/page/3/" class="page-link">3</a>
  </li>
  <li class="page-item">
    <a href="/pages/page/4/" class="page-link">4</a>
  </li>
  <li class="page-item active">
    <span class="page-link page-item">5</span>
  </li>
  <li class="page-item">
    <a href="/pages/page/6/" class="page-link">6</a>
  </li>
  <li class="page-item">
    <a href="/pages/page/7/" class="page-link">7</a>
  </li>
</ul>

We will achieve this with Hugo code.

The Riddle

How do we achieve this ?


3: Prepare

Number of Page for Each Pagination

Since we only have a few article, for tutorial purpose, I change paginator to paginate for each one article. And for a while, I only put 10 article. This way, we can have 10 pagination.

  {{ $paginator := .Paginate (where .Site.Pages "Type" "post") 1 }}

Dictionary

I also use scratch in dictionary. This make our code a little bit different than before

  {{ $paginator := .Paginate (where .Site.Pages "Type" "post") 1 }}
  {{ partial "pagination-adjacent.html" (dict "p" $paginator "s" .Scratch) }}

Layout: List

  {{ $paginator := .Paginate (where .Site.Pages "Type" "post") 1 }}
  {{ partial "pagination-adjacent.html" (dict "p" $paginator "s" .Scratch) }}
  <section id="archive">
  <div class="post-list">
    {{ range $paginator.Pages }}
      {{ partial "summary-blog-list.html" . }}
    {{ end }}
  </div>

Partial: Pagination Code Skeleton

This is just skeleton, as we will discuss this later.

<nav aria-label="Page navigation">
  {{ $s := .s }}
  {{ $p := .p }}

  {{ if gt $p.TotalPages 1 }}
  <ul class="pagination justify-content-center">

    <!-- Variable Initialization -->

    {{- range $p.Pagers -}}
      <!-- Complex page numbers. -->
      {{ if gt $p.TotalPages $max_links }}

        <!-- Lower limit pages. -->
        <!-- Upper limit pages. -->
        <!-- Middle pages. -->

      <!-- Simple page numbers. -->
      {{ else }}
        ...
      {{ end }}
    {{ end }}
  </ul>
  {{ end }}

</nav>

4: Math: Basic Algebra

Assumption

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

# CONST

$totalPost   = 10
$adjacent    = 2

Equation

We should manually, do the math.

EQUATION

$max_links   = ($adjacent * 2) + 1 = 5
$lower_limit =  1 + $adjacent      = 3
$upper_limit = 10 - $adjacent      = 8 

The real code is shown as below:

<nav aria-label="Page navigation">
  {{ $s := .s }}
  {{ $p := .p }}

  {{ if gt $p.TotalPages 1 }}
  <ul class="pagination justify-content-center">

    <!-- Page numbers. -->
    {{- $pagenumber := $p.PageNumber -}}

    <!-- Number of links either side of the current page. -->
    {{ $adjacent_links := 2 }}

    <!-- $max_links = ($adjacent_links * 2) + 1 -->
    {{ $max_links := (add (mul $adjacent_links 2) 1) }}

    <!-- $lower_limit = 1 + $adjacent_links -->
    {{ $lower_limit := (add 1 $adjacent_links) }}

    <!-- $upper_limit = $paginator.TotalPages - $adjacent_links -->
    {{ $upper_limit := (sub $p.TotalPages $adjacent_links) }}

  </ul>
  {{ end }}

</nav>

Again, that source above are, copied and pasted from:

Table

The result is on this table below.

# ALGEBRA

+--------------+-------+-------+-------+-------+-------+
| $pagination  |   1   |   2   |   5   |   7   |  10   |
+--------------+-------+-------+-------+-------+-------+
| VARIABLE                                             |
| $totalPages  |  10   |   5   |   2   |   2   |  N/A  |
| $max_links   |   5   |   5   |   5   |   5   |  N/A  |
| $lower_limit |   3   |   3   |   3   |   3   |  N/A  |
| $upper_limit |   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                                             |
| $totalPages  |  10   |   5   |   2   |   2   |  N/A  |
| $max_links   |   5   |   5   |   5   |   5   |  N/A  |
| $lower_limit |   3   |   3   |   3   |   3   |  N/A  |
| $upper_limit |   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 |                               |
+--------------+-------+-------------------------------+

Adjacent Code

This utilized page_number_flag with complex algorithm.

      <!-- Complex page numbers. -->
      {{ if gt $p.TotalPages $max_links }}

        <!-- Lower limit pages. -->
        <!-- If the user is on a page which is in the lower limit.  -->
        {{ if le $p.PageNumber $lower_limit }}

          <!-- If the current loop page is less than max_links. -->
          {{ if le .PageNumber $max_links }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        <!-- Upper limit pages. -->
        <!-- If the user is on a page which is in the upper limit. -->
        {{ else if ge $p.PageNumber $upper_limit }}

          <!-- If the current loop page is greater than total pages minus $max_links -->
          {{ if gt .PageNumber (sub .TotalPages $max_links) }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        <!-- Middle pages. -->
        {{ else }}
          
          {{ if and ( ge .PageNumber (sub $p.PageNumber $adjacent_links) ) ( le .PageNumber (add $p.PageNumber $adjacent_links) ) }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        {{ end }}

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

        {{ $s.Set "page_number_flag" true }}
      {{ end }}

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

      {{- if eq ($s.Get "page_number_flag") true -}}
      <li class="page-item{{ if eq $pagenumber .PageNumber }} active{{ end }}">
        {{ if not (eq $pagenumber .PageNumber) }} 
          <a href="{{ .URL }}" class="page-link">{{ .PageNumber }}</a>
        {{ else }}
          <span class="page-link page-item">{{ .PageNumber }}</span>
        {{ end }}
      </li>
      {{- end -}}

The Same Riddle

How does it works ?

Really! It is confusing.


6: Math: Conditional

Part: Middle Pages

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

        <!-- Middle pages. -->
        {{ else }}
          
          {{ if and ( ge .PageNumber (sub $p.PageNumber $adjacent_links) ) ( le .PageNumber (add $p.PageNumber $adjacent_links) ) }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        {{ end }}

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

+--------------+-------+
| $pagination  |   1   |
| $adjacent    |   2   |
| $totalPost   |  10   |
+--------------+-------+
| VARIABLE             |
| $totalPages  |  10   |
| $max_links   |   5   |
| $lower_limit |   3   |
| $upper_limit |   8   |
+--------------+-------+-+
| .PageNumber  | 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.

      <!-- Complex page numbers. -->
      {{ if gt $p.TotalPages $max_links }}

        <!-- Lower limit pages. -->
        <!-- If the user is on a page which is in the lower limit.  -->
        {{ if le $p.PageNumber $lower_limit }}

          <!-- If the current loop page is less than max_links. -->
          {{ if le .PageNumber $max_links }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        {{ end }}

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

        {{ $s.Set "page_number_flag" true }}
      {{ end }}

Notice that there is two part of conditional.

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

  • Inner conditional: always result 1..5

Thus, the conditional result in table:

+--------------+-------+-------+--------+
| .PageNumber  | 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   |
| $adjacent    |   2   |
| $totalPost   |  10   |
+--------------+-------+
| VARIABLE             |
| $totalPages  |  10   |
| $max_links   |   5   |
| $lower_limit |   3   |
| $upper_limit |   8   |
+--------------+-------+-+-------+-------+-------+-------+
| .PageNumber  | 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             |
| $totalPages  |  10   |
| $max_links   |   5   |
| $lower_limit |   3   |
| $upper_limit |   8   |
+--------------+-------+-------+---------+
| .PageNumber  | 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 |         |
+--------------+-------+-------+---------+
| .PageNumber  | 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:

<nav aria-label="Page navigation">
  {{ $s := .s }}
  {{ $p := .p }}

  {{ if gt $p.TotalPages 1 }}
  <ul class="pagination justify-content-center">

    <!-- Page numbers. -->
    {{- $pagenumber := $p.PageNumber -}}

    <!-- Number of links either side of the current page. -->
    {{ $adjacent_links := 2 }}

    <!-- $max_links = ($adjacent_links * 2) + 1 -->
    {{ $max_links := (add (mul $adjacent_links 2) 1) }}

    <!-- $lower_limit = 1 + $adjacent_links -->
    {{ $lower_limit := (add 1 $adjacent_links) }}

    <!-- $upper_limit = $paginator.TotalPages - $adjacent_links -->
    {{ $upper_limit := (sub $p.TotalPages $adjacent_links) }}

    {{- range $p.Pagers -}}
      {{ $s.Set "page_number_flag" false }}

      <!-- Complex page numbers. -->
      {{ if gt $p.TotalPages $max_links }}

        <!-- Lower limit pages. -->
        <!-- If the user is on a page which is in the lower limit.  -->
        {{ if le $p.PageNumber $lower_limit }}

          <!-- If the current loop page is less than max_links. -->
          {{ if le .PageNumber $max_links }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        <!-- Upper limit pages. -->
        <!-- If the user is on a page which is in the upper limit. -->
        {{ else if ge $p.PageNumber $upper_limit }}

          <!-- If the current loop page is greater than total pages minus $max_links -->
          {{ if gt .PageNumber (sub .TotalPages $max_links) }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        <!-- Middle pages. -->
        {{ else }}
          
          {{ if and ( ge .PageNumber (sub $p.PageNumber $adjacent_links) ) ( le .PageNumber (add $p.PageNumber $adjacent_links) ) }}
            {{ $s.Set "page_number_flag" true }}
          {{ end }}

        {{ end }}

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

        {{ $s.Set "page_number_flag" true }}
      {{ end }}

      {{- if eq ($s.Get "page_number_flag") true -}}
      <li class="page-item{{ if eq $pagenumber .PageNumber }} active{{ end }}">
        {{ if not (eq $pagenumber .PageNumber) }} 
          <a href="{{ .URL }}" class="page-link">{{ .PageNumber }}</a>
        {{ else }}
          <span class="page-link page-item">{{ .PageNumber }}</span>
        {{ end }}
      </li>
      {{- end -}}
    {{ end }}

  </ul>
  {{ end }}

</nav>

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


What is Next ?

There are, some interesting topic about Pagination in Hugo. Consider continue reading [ Hugo - Pagination - Indicator ].

Thank you for reading.