- 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
- JavaScript Pagination – Solution #1
- JavaScript Pagination – Solution #2
- JavaScript Pagination – Solution #3
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?
- Add the JavaScript to your page footer (see full script below).
- Add CSS to style the navigation bar (see an example below).
- Define an ID on the table you want to scroll.
- Place an empty DOM element where you want to display the navigation bar.
- 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?
- Add the JavaScript to your page footer (see the script below or the Codepen demo).
- Add the HTML template.
- Add your JSON data.
- 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.
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.
https://developer.mozilla.org/en-US/docs/Web/API/NodeList
NodeList
objects are collections of nodes, usually returned by properties such asNode.childNodes
and methods such asdocument.querySelectorAll()
.
- If a CSS selector is provided (
tables
is a string), it usesdocument.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 usingtable.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
androwsPerPage
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 theiractive
class removed. - It iterates over all rows in the table’s
<tbody>
. For each row, it sets its display style totable-row
if it falls within the range of rows to be displayed on the current page, ornone
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.