Thin Table Pagination: A new addition to my JavaScript repository

on in JavaScript Pagination
Last modified on

Table of Contents

I wrote about client-side JavaScript pagination before, but I have never stopped searching for the perfect pagination method, both from a performance and a code size point of view. The idea pagination script should be small and load fast.

The script I coded is for modern browsers only, and it is fast enough to be included in the Thin UI library.

This is a JavaScript script that adds pagination functionality to HTML tables. Specifically, the script allows users to display only a certain number of rows per page – by dividing the table rows into pages – and navigate between pages using a set of page navigation links.

The demo

The code

Without further ado ;) here is the JavaScript:

function addPagerToTables(tables, rowsPerPage = 10) {
  // if the tables argument is a string, convert it to a NodeList of table elements
  tables = typeof tables == "string" ? document.querySelectorAll(tables) : tables;

  // loop through each table element and call the addPagerToTable function with the same rowsPerPage value
  for (let table of tables) {
    addPagerToTable(table, rowsPerPage);
  }
}

function addPagerToTable(table, rowsPerPage = 10) {
  // get all rows in the table body
  let tBodyRows = table.querySelectorAll("tBody tr");
  // calculate the total number of pages based on the number of rows and rowsPerPage
  let numPages = Math.ceil(tBodyRows.length / rowsPerPage);

  // get the total number of columns in the table
  let colCount = [].slice
    .call(table.querySelector("tr").cells)
    .reduce((a, b) => a + parseInt(b.colSpan), 0);

  // add a footer row to the table with a navigation div
  table
    .createTFoot()
    .insertRow().innerHTML = `<td colspan=${colCount}><div class="nav"></div></td>`;

  // if there's only one page, return (no need to add navigation links)
  if (numPages == 1) {
    return;
  }

  // generate the navigation links based on the number of pages
  for (i = 0; i < numPages; i++) {
    let pageNum = i + 1;

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

  // display the first page of rows
  changeToPage(table, 1, rowsPerPage);

  // add a click event listener to each navigation link
  for (let navA of table.querySelectorAll(".nav a")) {
    navA.addEventListener("click", (e) => {
      e.preventDefault();

      // call the changeToPage function with the selected page number
      changeToPage(table, parseInt(e.target.innerHTML), rowsPerPage);
    });
  }
}

function changeToPage(table, page, rowsPerPage) {
  // calculate the start and end indexes of the rows to display
  let startItem = (page - 1) * rowsPerPage;
  let endItem = startItem + rowsPerPage;

  // get all navigation links and table rows
  let navAs = table.querySelectorAll(".nav a");
  let tBodyRows = table.querySelectorAll("tbody tr");

  // loop through each navigation link and table row
  for (let nix = 0; nix < navAs.length; nix++) {
    // if the current navigation link is the selected page, add the "active" class; otherwise, remove it
    if (nix == page - 1) {
      navAs[nix].classList.add("active");
    } else {
      navAs[nix].classList.remove("active");
    }

    // show or hide each table row based on whether it falls within the selected page's range
    for (let trix = 0; trix < tBodyRows.length; trix++) {
      tBodyRows[trix].style.display =
        trix >= startItem && trix < endItem ? "table-row" : "none";
    }
  }
}

How it works

Here’s how it works. The script defines three functions:

  1. addPagerToTables⁣ – this function accepts two arguments: tables, which can be either a string selector or an array of table elements, and rowsPerPage, which is the number of rows to display per page. It loops through each table element, calling the addPagerToTable function with the same rowsPerPage value.
  2. addPagerToTable⁣ – this function accepts two arguments: table, which is the table element to which to add pagination, and rowsPerPage, which is the number of rows to display per page. It calculates the total number of pages based on the number of rows in the table, adds a footer row to the table with a navigation <div>, and generates the page navigation links based on the number of pages. It also calls the changeToPage function to initially display the first page of rows, and adds a click event listener to each navigation link that calls the changeToPage function with the selected page number.
  3. changeToPage⁣ – this function accepts three arguments: table, which is the table element being paginated, page, which is the page number to display, and rowsPerPage, which is the number of rows to display per page. It calculates the start and end indexes of the rows to display based on the selected page number and rows per page, adds or removes the active class from the selected navigation link, and shows or hides each row based on whether it falls within the selected page’s range.

The code compresses really well down to 964 bytes:

function addPagerToTables(e,t=10){e="string"==typeof e?document.querySelectorAll(e):e;for(let l of e)addPagerToTable(l,t)}function addPagerToTable(e,t=10){let l=e.querySelectorAll("tBody tr"),a=Math.ceil(l.length/t),r=[].slice.call(e.querySelector("tr").cells).reduce(((e,t)=>e+parseInt(t.colSpan)),0);if(e.createTFoot().insertRow().innerHTML=`<td colspan=${r}><div class="nav"></div></td>`,1!=a){for(i=0;i<a;i++){let t=i+1;e.querySelector(".nav").insertAdjacentHTML("beforeend",`<a href="#" rel="${i}">${t}</a> `)}changeToPage(e,1,t);for(let l of e.querySelectorAll(".nav a"))l.addEventListener("click",(l=>{l.preventDefault(),changeToPage(e,parseInt(l.target.innerHTML),t)}))}}function changeToPage(e,t,l){let a=(t-1)*l,r=a+l,o=e.querySelectorAll(".nav a"),n=e.querySelectorAll("tbody tr");for(let e=0;e<o.length;e++){e==t-1?o[e].classList.add("active"):o[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].style.display=e>=a&&e<r?"table-row":"none"}}

How to use

When you have your HTML table in place, with a correct structure (i.e. include <tbody> and <tfoot>), call it like this:

addPagerToTables('.my-table', 10);

Where 10 is the number of visible rows.

That’s it, it doesn’t even need CSS.