Client-Side JavaScript Pagination

on in JavaScript DOM, JavaScript Pagination
Last modified on

  • Updated on October 24, 2023 to add a third solution, faster and easier to implement.
  • Updated on May 4, 2021 to add a second solution, based on JSON data.
  • Updated on August 30, 2019 to use the new ES syntax.
  • Updated on May 25, 2018 to hide Next and Previous links on first and last page.

Table of Contents

Server-side pagination is needed when you have to display hundreds of records. You may fetch results from a database using an offset and loading a single page for each HTTP request.

A long time ago, I migrated from List.js to server-side pagination in order to scale a massive result set. For smaller ones – less than 1000 – and when the query is optimized, you can use client-side navigation (i.e., JavaScript pagination).

JavaScript Pagination – Solution #1

If your result set is small, it’s possible to fully load it. In my case, I have an HTML table (a div-based structure is also possible) which needs to be paginated.

Usage

To use this JavaScript pagination script, follow the steps below:

How to implement JavaScript pagination?

  1. Add the JavaScript to your page footer (see full script below).
  2. Add CSS to style the navigation bar (see an example below).
  3. Define an ID on the table you want to scroll.
  4. Place an empty DOM element where you want to display the navigation bar.
  5. Initialize the pager.

That’s all.

If you have a huge table, it may be fully displayed before the JavaScript in the footer will be executed. To avoid this, you can set the table as hidden by default using CSS, and make it visible using JavaScript after the pager.showPage() call.

<table id="pager" class="wp-list-table widefat striped posts">
    <thead>
        <tr>
            <th>Column #1</th>
            <th>Column #2</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th>Info #1</th>
            <th>Info #2</th>
        </tr>
        <tr>
            <th>Info #1</th>
            <th>Info #2</th>
        </tr>
        <tr>
            <th>Info #1</th>
            <th>Info #2</th>
        </tr>
    </tbody>
</table>

<div id="pageNavPosition" class="pager-nav"></div>

<script src="PagerJS.js" defer></script>

<script>
let pager = new Pager('pager', 3);

pager.init();
pager.showPageNav('pager', 'pageNavPosition');
pager.showPage(1);
</script>

JavaScript: PagerJS.js

/* eslint-env browser */
/* global document */

function Pager(tableName, itemsPerPage) {
    'use strict';

    this.tableName = tableName;
    this.itemsPerPage = itemsPerPage;
    this.currentPage = 1;
    this.pages = 0;
    this.inited = false;

    this.showRecords = function (from, to) {
        let rows = document.getElementById(tableName).rows;

        // i starts from 1 to skip table header row
        for (let i = 1; i < rows.length; i++) {
            if (i < from || i > to) {
                rows[i].style.display = 'none';
            } else {
                rows[i].style.display = '';
            }
        }
    };

    this.showPage = function (pageNumber) {
        if (!this.inited) {
            // Not initialized
            return;
        }

        let oldPageAnchor = document.getElementById('pg' + this.currentPage);
        oldPageAnchor.className = 'pg-normal';

        this.currentPage = pageNumber;
        let newPageAnchor = document.getElementById('pg' + this.currentPage);
        newPageAnchor.className = 'pg-selected';

        let from = (pageNumber - 1) * itemsPerPage + 1;
        let to = from + itemsPerPage - 1;
        this.showRecords(from, to);

        let pgNext = document.querySelector('.pg-next'),
            pgPrev = document.querySelector('.pg-prev');

        if (this.currentPage == this.pages) {
            pgNext.style.display = 'none';
        } else {
            pgNext.style.display = '';
        }

        if (this.currentPage === 1) {
            pgPrev.style.display = 'none';
        } else {
            pgPrev.style.display = '';
        }
    };

    this.prev = function () {
        if (this.currentPage > 1) {
            this.showPage(this.currentPage - 1);
        }
    };

    this.next = function () {
        if (this.currentPage < this.pages) {
            this.showPage(this.currentPage + 1);
        }
    };

    this.init = function () {
        let rows = document.getElementById(tableName).rows;
        let records = (rows.length - 1);

        this.pages = Math.ceil(records / itemsPerPage);
        this.inited = true;
    };

    this.showPageNav = function (pagerName, positionId) {
        if (!this.inited) {
            // Not initialized
            return;
        }

        let element = document.getElementById(positionId),
            pagerHtml = '<span onclick="' + pagerName + '.prev();" class="pg-normal pg-prev">«</span>';

        for (let page = 1; page <= this.pages; page++) {
            pagerHtml += '<span id="pg' + page + '" class="pg-normal pg-next" onclick="' + pagerName + '.showPage(' + page + ');">' + page + '</span>';
        }

        pagerHtml += '<span onclick="' + pagerName + '.next();" class="pg-normal">»</span>';

        element.innerHTML = pagerHtml;
    };
}

Sample CSS

.pager-nav {
    margin: 16px 0;
}
.pager-nav span {
    display: inline-block;
    padding: 4px 8px;
    margin: 1px;
    cursor: pointer;
    font-size: 14px;
    background-color: #FFFFFF;
    border: 1px solid #e1e1e1;
    border-radius: 3px;
    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
.pager-nav span:hover,
.pager-nav .pg-selected {
    background-color: #f9f9f9;
    border: 1px solid #CCCCCC;
}

JavaScript Pagination – Solution #1 – Demo

See the Pen JavaScript Pagination by Ciprian (@ciprian) on CodePen.

JavaScript Pagination – Solution #2

The second solution is able to get data either manually, client-side generated, or via JSON, server side.

How to implement JavaScript pagination?

  1. Add the JavaScript to your page footer (see the script below or the Codepen demo).
  2. Add the HTML template.
  3. Add your JSON data.
  4. Initialize the pagination.

The HTML is a simple placeholder for the data:

<div id="listing-table"></div>

<p>
    <a href="#" id="btn-prev">Prev</a>
    <a href="#" id="btn-next">Next</a>
</p>

<p>Page <span id="page"></span></p>

The JavaScript is split into 3 parts. The pagination helpers functions:

/**
 * Get the number of items per page based on the length of the JSON object
 * and the number of records per page (set via global variable)
 */
function jsp_num_pages() {
    return Math.ceil(jsp_json_object.length / jsp_records_per_page);
}

/**
 * Change current page to previous page (-1)
 */
function jsp_prev_page() {
    if (jsp_current_page > 1) {
        jsp_current_page--;
        jsp_change_page(jsp_current_page);
    }
}

/**
 * Change current page to next page (+1)
 */
function jsp_next_page() {
    if (jsp_current_page < jsp_num_pages()) {
        jsp_current_page++;
        jsp_change_page(jsp_current_page);
    }
}

/**
 * Change page, display items in
 the table and show/hide the navigation buttons.
 * This function can be further split into several functional elements, thus
 * simplifying the functions and allowing for more complexity (better pagination
 * styling, better table layout and so on).
 */
function jsp_change_page(page) {
    const btn_prev = document.getElementById('btn-prev');
    const btn_next = document.getElementById('btn-next');
    const listing_table = document.getElementById('listing-table');
    let page_span = document.getElementById('page');
 
    if (page < 1) {
        page = 1;
    }
    if (page > jsp_num_pages()) {
        page = jsp_num_pages();
    }

    listing_table.innerHTML = '';

    for (let i = (page - 1) * jsp_records_per_page; i < (page * jsp_records_per_page) && i < jsp_json_object.length; i++) {
        listing_table.innerHTML += `${jsp_json_object[i].json_item}<br>`;
    }
    page_span.innerHTML = `${page}/${jsp_num_pages()}`;

    btn_prev.style.display = (page === 1) ? 'none' : 'inline-block';
    btn_next.style.display = (page === jsp_num_pages()) ? 'none' : 'inline-block';
}

Finally, we need to initialize the pagination and listen to page changes.

window.onload = () => {
    document.getElementById('btn-prev').addEventListener('click', (e) => {
        e.preventDefault();
        jsp_prev_page();
    });

    document.getElementById('btn-next').addEventListener('click', (e) => {
        e.preventDefault();
        jsp_next_page();
    });

    jsp_change_page(1);
};

This second solution has no styling (for demo purposes) and is more flexible when adding new pagination items.

JavaScript Pagination – Solution #2 – Demo

See the Pen JSP #3 by Ciprian (@ciprian) on CodePen.

JavaScript Pagination – Solution #3

A third solution, and the fastest of them all, is below. I am currently using it for my Active Analytics plugin for WordPress.

JavaScript Pagination
JavaScript Pagination

Here’s a step-by-step breakdown of how I accomplish this:

addPagerToTables

This function is responsible for adding pagination to multiple HTML tables at once. It takes as input either a CSS selector to select the tables or a NodeListOf HTMLTableElement objects. It also accepts an optional parameter rowsPerPage to specify the number of rows to display per page.

NodeList objects are collections of nodes, usually returned by properties such as Node.childNodes and methods such as document.querySelectorAll().

https://developer.mozilla.org/en-US/docs/Web/API/NodeList
  • If a CSS selector is provided (tables is a string), it uses document.querySelectorAll to select all matching tables.
  • For each selected table, it calls the addPagerToTable function to add pagination.

addPagerToTable

This function adds pagination controls to a single HTML table.

  • It selects all the rows within the table’s <tbody> element using table.querySelectorAll('tbody tr').
  • It calculates the number of pages required to display the data using Math.ceil(tBodyRows.length / rowsPerPage).
  • The function determines the number of columns in the table by examining the first row of the table’s <thead> using the <tr> and <td> elements’ properties.
  • It creates a table footer (<tfoot>) and inserts a row with a single cell that spans all columns, which will contain the pagination navigation links.
  • If there’s only one page of data, it returns immediately, as pagination is unnecessary.
  • If there are multiple pages, it creates pagination links for each page. These links are added to the previously created navigation cell in the footer.
  • The function then sets an event listener on each pagination link. When clicked, the links trigger the changeToPage function to switch to the selected page.

changeToPage

This function handles the logic for changing the visible page when a pagination link is clicked.

  • It calculates the index of the first and last item to display on the current page based on the page and rowsPerPage parameters.
  • It selects all the pagination links within the navigation cell and iterates over them.
  • For each link, it adds the active class to the link that corresponds to the current page, marking it as the active page. All other links have their active class removed.
  • It iterates over all rows in the table’s <tbody>. For each row, it sets its display style to table-row if it falls within the range of rows to be displayed on the current page, or none if it should be hidden.

JavaScript Pagination – Solution #3 – Code

The script employs HTML structure manipulation to add pagination controls, calculate page numbers, and manage which rows are displayed. It also uses event handling to respond to user clicks on pagination links, updating the display accordingly.

This script is a self-contained solution for adding pagination to HTML tables, making it easier to display large datasets in manageable chunks.

/**
 * Adds a pagination control to multiple HTML tables.
 *
 * @param {string | NodeListOf<HTMLTableElement>} tables - The HTML table(s) or a CSS selector to select tables.
 * @param {number} [rowsPerPage=10] - The number of rows to display per page.
 */
function addPagerToTables(tables, rowsPerPage = 10) {
    tables = typeof tables == 'string' ? document.querySelectorAll(tables) : tables;

    for (let table of tables) {
        addPagerToTable(table, rowsPerPage);
    }
}

/**
 * Adds a pagination control to a single HTML table.
 *
 * @param {HTMLTableElement} table - The HTML table to which pagination will be added.
 * @param {number} [rowsPerPage=10] - The number of rows to display per page.
 */
function addPagerToTable(table, rowsPerPage = 10) {
    let tBodyRows = table.querySelectorAll('tBody tr');
    let numPages = Math.ceil(tBodyRows.length / rowsPerPage);

    let colCount = [].slice.call(table.querySelector('tr').cells).reduce((a, b) => a + parseInt(b.colSpan), 0);

    table.createTFoot().insertRow().innerHTML = `<td colspan=${colCount}><div class="nav"></div></td>`;

    if (numPages == 1) {
        return;
    }

    for (i = 0; i < numPages; i++) {
        let pageNum = i + 1;

        table.querySelector('.nav')
            .insertAdjacentHTML(
                'beforeend',
                `<a href="#" rel="${i}">${pageNum}</a> `
            );

    }

    changeToPage(table, 1, rowsPerPage);

    for (let navA of table.querySelectorAll('.nav a')) {
        navA.addEventListener('click', e => {
            e.preventDefault();

            changeToPage(table, parseInt(e.target.innerHTML), rowsPerPage);
        });
    }
}

/**
 * Changes the visible page of a paginated table.
 *
 * @param {HTMLTableElement} table - The paginated HTML table to change pages on.
 * @param {number} page - The page number to switch to.
 * @param {number} rowsPerPage - The number of rows to display per page.
 */
function changeToPage(table, page, rowsPerPage) {
    let startItem = (page - 1) * rowsPerPage;
    let endItem = startItem + rowsPerPage;
    let navAs = table.querySelectorAll('.nav a');
    let tBodyRows = table.querySelectorAll('tbody tr');

    for (let nix = 0; nix < navAs.length; nix++) {
        if (nix == page - 1) {
            navAs[nix].classList.add('active');
        } else {
            navAs[nix].classList.remove('active');
        }

        for (let trix = 0; trix < tBodyRows.length; trix++) {
            tBodyRows[trix].style.display =
                (trix >= startItem && trix < endItem)
                    ? 'table-row'
                    : 'none';
        }
    }
}

if (document.querySelector('.last-hits-table--all')) {
    addPagerToTables('.last-hits-table--all', 10);
}

JavaScript Pagination – Solution #3 – Demo

Find a Codepen demo here, or see embedded below.

See the Pen JavaScript Pagination – Solution #3 by Ciprian (@ciprian) on CodePen.

Related posts