Pagination

shopping bag logo

What is pagination?

Pagination (paging) is used to divide the products into pages. Instead of having a long page with all of the products we can limit each page to include a set number of products.

Prepare the pagination <div> wrapper in both the index.html and dashboard.html pages:

<!-- clear contents from inside the
pagination div and add class/id attributes -->
<div id="pagination" class="pagination"></div>

The contents of these pagination areas will be generated using JavaScript. As both pages use the same pagination id the pagination will work on both pages. Create two new variables near the top of the script:

// script.js
let currentPage = 1;
const ITEMS_PER_PAGE = 5;
  • currentPage: this is used to store the current page we are viewing.
  • ITEMS_PER_PAGE: containes the number of products to show on each page. Although not required, a const is often named using upper case. Since we only request 20 products, this number will give us 4 pages to test with.

Limiting products per page

The renderProducts() function currently displays all available products onto the page, modify this function to slice the products we need for the current page:

function renderProducts() {
    const productList = document.getElementById("product-list");
    const productTable = document.getElementById("product-table");

  // add the following 3 lines
    const start = (currentPage - 1) * ITEMS_PER_PAGE;
    const end = start + ITEMS_PER_PAGE;
    const currentProducts = products.slice(start, end);

    if (window.location.pathname.includes("dashboard.html")) {
    // replace products with currentProducts
        renderTableRows(productTable, currentProducts);
    } else {
    // replace products with currentProducts
        renderProductCards(productList, currentProducts);
    }
}
  • products.slice(start, end): the slice array method is used to create a copy of the original (products) array. The new array will be a selection of the values cut from a start and end position of the original array. These positions can be entered as a number or, we are using variables to calculate.
  • products is then replaced with currentProducts to render the filtered version.

The start and end variables are used to slice values from the original products array. Arrays begin at zero, if is we set 5 products per page, page 1 will be values 0-4. Page 2 will be values 5-9 and so on. For the start we need to -1 to account for the array beginning at zero.

Pagination buttons

To create the buttons to select the page, we first need to know how many pages we have. Create a function to handle this at the bottom of the script.js:

function createPaginationButtons() {
  const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
  const pagination = document.getElementById("pagination");
}
  • products.length: We can access the number of values in an array with the length property. This is divided by the products per page we set to give us the number of pages.
  • Math.ceil(): This number of pages caclulation is rounded up to the nearest whole number.

To create a button for each page, add a for loop:

function createPaginationButtons() {
  const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
  const pagination = document.getElementById("pagination");
  for (let i = 1; i <= totalPages; i++) {

  }
}
  • for() {}: The for loop will run the code between the curly braces a number of times. The number of times is determined by 3 optional expressions.
  • let i = 1;: The first expression runs before the code block, this will set a variable to be the number 1.
  • i <= totalPages;: The loop will keep running as long as this condition is true. As long as i is less than/equal to the totalPages variable it will continue.
  • i++: The final expression runs after each loop has completed. This will increment (increase) the i variable by a value of 1. Meaning i will increase in value on each repetition of the loop, until reaching the totalPages value.

The loop runs from 1 to totalPages, creating a button for each page. Complete the contents of the loop to create the HTML button elements:

function createPaginationButtons() {
  const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
  const pagination = document.getElementById("pagination");
  for (let i = 1; i <= totalPages; i++) {
    const button = document.createElement("button");
    button.textContent = i;
    // Ensure only the currentPage button has the "active" class
    if (i === currentPage) {
      button.classList.add("active");
    }
    button.addEventListener("click", function() {
      currentPage = i;
      renderProducts();
      // Refresh pagination buttons to update active class
      createPaginationButtons();
    });
    pagination.appendChild(button);
  }
}

The event listener

The above code attaches an event listener to the button using addEventListener. Event examples include when a HTML page has loaded, a mouse moves over an element, or a keboard button is pressed. Our use case is a button is clicked. This will run a function:

function() {
  currentPage = i;
  renderProducts();
  createPaginationButtons();
}

When a button is clicked, currentPage is updated to i (the clicked page number). We call renderProducts() to display the products for the current page selection. Finally, createPaginationButtons() will refresh the pagination buttons to update the active class.

To run the createPaginationButtons function, call it after fetching the products:

async function fetchProducts() {
    const response = await fetch(API_URL);
    const data = await response.json();
    products = data.data;
  // call function to create buttons
    createPaginationButtons();
    renderProducts();
}

Try this out and you will notice after clicking a page button, the products are added to the previous ones.

Clearing existing products

To resolve this we need to clear the existing products before generating new ones. Update the renderProducts() function to clear the innerHTML for both pages:

if (window.location.pathname.includes("dashboard.html")) {
  // clear content of productTable
  productTable.innerHTML = "";
  renderTableRows(productTable, currentProducts);
} else {
  // clear content of productList
  productList.innerHTML = "";
  renderProductCards(productList, currentProducts);
}

Also clear the previously created buttons at the top of the createPaginationButtons function:

function createPaginationButtons() {
    const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
    const pagination = document.getElementById("pagination");
  // clear buttons
    pagination.innerHTML = "";

Test this out by clicking on the page buttons to switch between products!

Final script.js code

let products = [];

const API_URL = "https://fakerapi.it/api/v2/products?_quantity=20";
let currentPage = 1;
const ITEMS_PER_PAGE = 5;

async function fetchProducts() {
    const response = await fetch(API_URL);
    const data = await response.json();
    products = data.data;
    createPaginationButtons();
    renderProducts();
}
fetchProducts();

function renderProducts() {
    const productList = document.getElementById("product-list");
    const productTable = document.getElementById("product-table");

    const start = (currentPage - 1) * ITEMS_PER_PAGE;
    const end = start + ITEMS_PER_PAGE;
    const currentProducts = products.slice(start, end);

    if (window.location.pathname.includes("dashboard.html")) {
        productTable.innerHTML = "";
        renderTableRows(productTable, currentProducts);
    } else {
        productList.innerHTML = "";
        renderProductCards(productList, currentProducts);
    }
}

function renderProductCards(container, products) {
    products.forEach(function (product) {
        const card = document.createElement("article");
        card.setAttribute("class", "product-card");
        card.innerHTML = `<section class="product-image">
      <img
        src="images/t-shirt-blue.png"
        alt="blue t-shirt with short sleeves"
        class="product-image"
      />
    </section>
    <section class="product-details">
        <h3>${product.name}</h3>
        <p>${product.description}</p>
        <p class="price">$${product.price}</p>
    </section>`;
        container.appendChild(card);
    });
}

function renderTableRows(container, products) {
    products.forEach(function(product) {
        const row = document.createElement("tr");
        row.innerHTML = `
            <td>${product.name}</td>
            <td>$${product.price}</td>
            <td>
                <button class="edit-btn">Edit</button>
                <button class="delete-btn">Delete</button>
            </td>
        `;
        container.appendChild(row);
    });
}

function createPaginationButtons() {
    const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
    const pagination = document.getElementById("pagination");
    pagination.innerHTML = "";
    for (let i = 1; i <= totalPages; i++) {
        const button = document.createElement("button");
        button.textContent = i;
        if (i === currentPage) {
            button.classList.add("active");
        }
        button.addEventListener("click", function () {
            currentPage = i;
            renderProducts();
            createPaginationButtons();
        });
        pagination.appendChild(button);
    }
}