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.
Animation: Stripped Version
I’m following Glenn McComb code, combined with my own code, and this is the result.
Sample: An Example
Consider get one frame, a sample, because we need an example. This is what we want to achieve in this tutorial.
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
-
- themes/tutor-05/layouts/archives/list.html
- gitlab.com/…/layouts/archives/list.html.
{{ $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.
- themes/tutor-05/layouts/partials/pagination-adjacent.html : gitlab.com/…/partials/pagination-adjacent.html.
<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).
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:
- themes/tutor-05/layouts/partials/pagination-adjacent.html : gitlab.com/…/partials/pagination-adjacent.html.
<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.