Description

Any time you want to display multiple pages of data or content using your own Lava template, it's up to you to provide the user interface for allowing users to navigate between those pages. This recipe will show you how to create a new Pager shortcode that can be reused over and over for building the necessary UI.

Shortcode Details

Create a new Lava Shortcode with the following details:

  • Name: Pager
  • Tag Name: pager
  • Tag Type: Inline
  • Description: Generates a list of sequential links for navigating to multiple pages of content

Documentation:

<p><strong>Usage:</strong></p>
<pre style="position: relative;">{[ pager current:'1' total:'1' rooturl:'/podcasts/sermons' buttoncount:'5' size:'medium' class:'top' usequery:'false' ]}<div class="open_grepper_editor" title="Edit &amp; Save To Grepper"></div><div class="open_grepper_editor" title="Edit &amp; Save To Grepper"></div></pre>
<ul>
<li><strong>rooturl</strong>&nbsp;(string, optional) – The root URL to link to (excluding any page number information). If left blank, the root URL will be pulled from the current page URL.</li>
<li><strong>fragment</strong>&nbsp;(string, optional, default:<code>blank</code>) – The fragment or anchor name that should be appended to the final URL</li>
<li><strong>current</strong>&nbsp;(integer, optional, default:<code>1</code>) – The current page number</li>
<li><strong>total</strong>&nbsp;(integer, optional, default:<code>1</code>) – The total number of pages</li>
<li><strong>buttoncount</strong>&nbsp;(integer, optional, default:<code style="background-color: rgb(255, 255, 255);">5</code>) – The total number of page number buttons to display. The button closest to the middle of the range will be the current page and will be inactive.<br></li>
<li><strong>size</strong>&nbsp;(string, optional, default:<code>medium</code>) – The physical size to the pager at. Valid options are <code>small</code>, <code>medium</code> and <code>large</code>.</li>
<li><strong>class</strong>&nbsp;(string, optional, default:<code>blank</code>) – CSS class to add to the outer-most <code>nav</code> element of the pager</li>
<li><strong>usequery</strong>&nbsp;(boolean, optional, default:<code>false</code>) – Should the current page number be added to the querystring instead of the page route?</li>
<li><strong>querykey</strong>&nbsp;(string, optional, default:<code>p</code>) – The key for the query parameter that will contain the current page number.</li>
</ul>

Shortcode Markup:

{%- assign current = current | Default:'0' | AsInteger -%}
{%- assign total = total | Default:'0' | AsInteger -%}
{%- assign buttoncount = buttoncount | Default:'5' | AsInteger -%}
{%- if total > 1 -%}
    {%- assign PLACEHOLDER = '[[PAGENUM]]' -%}
    {% comment %} calculate the page number range for the buttons {% endcomment %}
    {%- if total > buttoncount -%}
        {% comment %} there are more pages than buttons {% endcomment %}
        {%- assign remaining = total | Minus:current -%}
        {%- assign beforeCount = buttoncount | Minus:1 | DividedBy:2 | Floor -%}
        {%- assign afterCount = buttoncount | Minus:beforeCount | Minus:1 -%}
        {%- if remaining <= afterCount -%}
            {% comment %} there are fewer remaining pages than half the button count {% endcomment %}
            {%- assign first = total | Plus:1 | Minus:buttoncount -%}
            {%- assign beforeCount = current | Minus:first -%}
            {%- assign afterCount = buttoncount | Minus:beforeCount | Minus:1 -%}
            {%- assign last = total -%}
        {%- else -%}
            {% comment %} there are more remaining pages than half the button count {% endcomment %}
            {%- assign first = current | Minus:beforeCount -%}
            {%- if first < 1 %}{% assign first = 1 %}{% endif -%}
            {%- assign last = first | Plus:buttoncount | Minus:1 -%}
            {%- if last > total %}{% assign last = total %}{% endif -%}
        {%- endif -%}
    {%- else -%}
        {% comment %} there enough buttons for every page {% endcomment %}
        {%- assign beforeCount = current | Minus:1 -%}
        {%- assign first = 1 -%}
        {%- assign last = total -%}
    {%- endif -%}
    {% comment %} determine the pager size CSS class {% endcomment %}
    {%- case size -%}
        {%- when 'small' %}{% assign sizeClass = ' pagination-sm' -%}
        {%- when 'large' %}{% assign sizeClass = ' pagination-lg' -%}
        {%- else %}{% assign sizeClass = '' -%}
    {%- endcase -%}
    {%- assign queryParams = 'Global' | Page:'QueryString' -%}
    {%- assign queryString = '' -%}
    {%- assign queryKey = querykey | Default:'p' -%}
    {%- comment %} build a new querystring {% endcomment -%}
    {%- if queryParams != empty -%}
        {%- assign firstItem = true -%}
        {%- for queryParam in queryParams -%}
            {%- assign kvParam = queryParam | PropertyToKeyValue -%}
            {%- comment %} exclude any page number related parameters {% endcomment -%}
            {%- if kvParam.Key != queryKey and  kvParam.Key != 'PageId' -%}
                {%- capture queryString %}{% if firstItem == false %}{{ queryString }}&{% endif %}{{ kvParam.Key }}={{ kvParam.Value }}{% endcapture -%}
                {%- assign firstItem = false -%}
            {%- endif -%}
        {%- endfor -%}
    {%- endif -%}
    {% comment %} build the page number parameter portion of the URL template {% endcomment %}
    {%- assign useQuery = usequery | Default:'false' | AsBoolean -%}
    {%- if useQuery == true -%}
        {%- if queryString != '' or rooturl contains '?' -%}
            {%- capture pageNumTemplate %}&{{ queryKey }}={{ PLACEHOLDER }}{% endcapture -%}
        {%- else -%}
            {%- capture pageNumTemplate %}?{{ queryKey }}={{ PLACEHOLDER }}{% endcapture -%}
        {%- endif -%}
    {%- else -%}
        {%- assign pageNumTemplate = '/page/' | Append:PLACEHOLDER -%}
    {%- endif -%}
    {% comment %} set up the fragment if one was provided {% endcomment %}
    {%- assign fragment = fragment | Default:'' -%}
    {%- if fragment != '' %}{% assign fragment = fragment | Prepend:'#' %}{% endif -%}
    {% comment %} build the URL template {% endcomment %}
    {%- assign urlTemplate = '' -%}
    {%- assign rooturl = rooturl | Default:'' | Append:'|' | Replace:'/|' | Replace:'|' -%}
    {%- if rooturl == '' -%}
        {%- comment %} rooturl is empty so generate one from the current page URL {% endcomment -%}
        {%- assign pageURL = 'Global' | Page:'Url' -%}
        {%- assign rootPath = pageURL | Url:'localpath' -%}
        {%- if useQuery == true -%}
            {%- assign rootpath = rootPath | Append:'|' | Replace:'/|' | Replace:'|' -%}
            {%- capture urlTemplate %}{{ rootpath }}{% if queryString != '' %}?{{ queryString }}{% endif %}{{ pageNumTemplate }}{{ fragment }}{% endcapture -%}
            {%- capture firstPageURL %}{{ rootpath }}{% if queryString != '' %}?{{ queryString }}{% endif %}{{ fragment }}{% endcapture -%}
        {%- else -%}
            {%- comment %} remove page number segments from route if necessary {% endcomment -%}
            {%- capture segments %}/page/{{ current }}{% endcapture -%}
            {%- assign rootPath = rootPath | Replace:segments,'' -%}
            {%- comment %} get rid of any trailing slash {% endcomment -%}
            {%- assign rootpath = rootPath | Append:'|' | Replace:'/|' | Replace:'|' -%}
            {%- capture urlTemplate %}{{ rootpath }}{{ pageNumTemplate }}{% if queryString != '' %}?{{ queryString }}{% endif %}{{ fragment }}{% endcapture -%}
            {%- capture firstPageURL %}{{ rootpath }}{% if queryString != '' %}?{{ queryString }}{% endif %}{{ fragment }}{% endcapture -%}
        {%- endif -%}
    {%- else -%}
        {%- comment %} generate the root URL {% endcomment -%}
        {%- capture urlTemplate %}{{ rooturl }}{{ pageNumTemplate }}{{ fragment }}{% endcapture -%}
        {%- capture firstPageURL %}{{ rooturl }}{{ fragment }}{% endcapture -%}
    {%- endif -%}
    {%- stylesheet id:'Pager' compile:'less' -%}
        .grid-paging 
        {
            .grid-pager ul { margin: 0 6px 0 0; }
            .pagination>.active>span 
            {
                min-width: 23px;
                padding: 5px 12px;
                margin: 0 8px 0 0;
            }
            .pagination>.disabled>span:hover { cursor: default; }
        }
    {%- endstylesheet -%}
<nav aria-label="Page navigation" {%- if class != '' %} class="{{ class }}"{% endif %}>
    <ul class="pagination{{ sizeClass }}">
    {% comment %} first page button {% endcomment %}
    {%- if first > 1 -%}
        <li class="first">
            <a href="{{ firstPageURL }}" aria-label="First Page" title="Return to the first page">
                <i class="fa fa-step-backward"></i>
            </a>
        </li>
    {%- endif -%}
    {% comment %} previous page button {% endcomment %}
    {%- if current > 1 -%}
        {%- if current > 2 -%}
            {%- assign num = current | Minus:1 -%}
            {%- capture url -%}{{ urlTemplate | Replace:PLACEHOLDER, num }}{% endcapture -%}
        {%- else -%}
            {%- assign url = firstPageURL -%}
        {%- endif -%}
        <li class="prev">
            <a href="{{ url }}" aria-label="Previous Page" title="Go to the previous page: {{ current | Minus:1 }}">
                <i class="fa fa-lg fa-caret-left"></i>
            </a>
        </li>
    {%- endif -%}
    {%- if first > 1 -%}
        <li class="ellipsis disabled"><span><i class="fa fa-ellipsis-h"></i></span></li>
    {%- endif -%}
    {% comment %} individual page buttons {% endcomment %}
    {%- for i in (first..last) -%}
        {%- if i == current -%}
        <li class="active" title="Current page: {{ i }}"><span>{{ i }}</span></li>
        {%- else -%}
            {%- if i > 1 -%}
                {%- capture url -%}{{ urlTemplate | Replace:PLACEHOLDER, i }}{% endcapture -%}
            {%- else -%}
                {%- assign url = firstPageURL -%}
            {%- endif -%}
        <li class="page"><a href="{{ url }}" aria-label="Page {{ i }}" title="Go to page {{ i }}">{{ i }}</a></li>
        {%- endif -%}
    {%- endfor -%}
    {%- if last < total -%}
        <li class="ellipsis disabled"><span><i class="fa fa-ellipsis-h"></i></span></li>
    {%- endif -%}
    {% comment %} next page button {% endcomment %}
    {%- if current < total -%}
        {%- assign num = current | Plus:1 -%}
        <li class="next">
            <a href="{{ urlTemplate | Replace:PLACEHOLDER, num }}" aria-label="Next Page" title="Go to the next page: {{ current | Plus:1 }}">
                <i class="fa fa-lg fa-caret-right"></i>
            </a>
        </li>
    {%- endif -%}
    {% comment %} last page button {% endcomment %}
    {%- if last < total -%}
        <li class="last">
            <a href="{{ urlTemplate | Replace:PLACEHOLDER, total }}" aria-label="Last Page" title="Go to the last page: {{ total }}">
                <i class="fa fa-step-forward"></i>
            </a>
        </li>
    {%- endif -%}
    </ul>
</nav>
{%- endif -%}

Parameters:

  • rooturl (blank)
  • fragment (blank)
  • current 0
  • total 0
  • buttoncount 5
  • size medium
  • class (blank)
  • usequery false
  • querykey p

Enabled Lava Commands:

No Lava commands required

Using the Pager Shortcode

This short code takes care of building the page navigation UI, but it's up to you to provide the current page number and total number of pages, and to split your content into the necessary chunks. Here's some basic sample Lava to get you started:

{%- assign pageSize = 25 -%}
{%- assign pageNum = 'Global' | PageParameter:'p' | Default:'1' | AsInteger -%}
{%- assign itemCount = YOURITEMS | Size -%}
{%- assign pageCount = itemCount | DividedBy:pageSize | Ceiling -%}
{%- assign skipCount = pageNum | Minus:1 | Times:pageSize -%}
{%- if pageNum > pageCount %}{{ '~/page-not-found' | PageRedirect }}{% endif -%}
{%- for item in YOURITEMS limit:pageSize offset:skipCount -%}
    Your super fancy Lava content item magic goes here…
{%- endfor -%}
{%- if pageCount > 1 -%}
    {[ pager current:'{{ pageNum }}' total:'{{ pageCount }}' buttoncount:'10' size:'small' usequery:'true' ]}
{%- endif -%}

By default, the pager uses the basic Bootstrap styles, but I made sure to include enough CSS class names in the HTML output for you to customize it to look however you want.

Follow Up

Please don't hesitate to leave a comment or hit me up on Rock Chat (@JeffRichmond) if you have questions or find any issues with this recipe.


Change Log

  • 2021-06-22 - Initial version
  • 2022-01-04 - Bugfixes
  • 2023-06-21 - Added querykey parameter to support alternate page number query parameter keys