Implementing Pagination feature in Vanilla JS

Today we will implement a Paginator class which will have the following API :-

// Initialization
const paginator = new Paginator(totalRecords,recordsPerPage,visiblePages);

// Usage
paginator.getActivePage();
paginator.gotoFirstPage();
paginator.gotoPrevPage();
paginator.gotoPage(page);
paginator.gotoNextPage();
paginator.gotoLastPage();
paginator.getVisiblePagesRange();
paginator.getActiveRecordsIndices();

The Class blueprint :-

class Paginator {

  // Private class fields

  #totalRecords;
  #recordsPerPage;
  #visiblePages;

  #noOfPages;
  #activePage;
  #visiblePagesEndRange;

  constructor(totalRecords, recordsPerPage, visiblePages) {
  }

  // Public class functions

  getActivePage(){
  }

  gotoFirstPage() {
  }

  gotoPrevPage() {
  }

  gotoPage(page) {
  }

  gotoNextPage() {
  }

  gotoLastPage() {
  }

  getVisiblePagesRange() {  
  }

  getActiveRecordsIndices() {
  }

For all the explanations below, assume that totalRecords is 346, recordsPerPage and visiblePages are 6.

Let's start with the constructor :-

constructor(totalRecords, recordsPerPage, visiblePages) {
    this.#recordsPerPage = recordsPerPage;
    this.#totalRecords = totalRecords;
    this.#noOfPages = Math.ceil(this.#totalRecords / this.#recordsPerPage);
    this.#visiblePages = visiblePages;
    this.#activePage = 1;
    this.#visiblePagesEndRange = visiblePages;
  }
  • Here we are initializing all our private class fields to certain values. #recordsPerPage, #totalRecords and #visiblePages straight away get initialized to passed constructor parameters.
  • We can get the #noOfPages by dividing #totalRecords by #recordsPerPage.
  • The #activePage as the name denotes is the page which will be active/selected in your pagination UI. It is initialized to 1.
  • The #visiblePagesEndRange will be equivalent to #visiblePages in the beginning and will help in maintaining a page range which comes into picture later on.
getActivePage(){
    return this.#activePage;
  }

The above is a public function to return the private field #activePage.

gotoFirstPage() {
    this.#activePage = 1;
    this.#visiblePagesEndRange = this.#visiblePages;
  }

The above is a public function to set #activePage to 1 and #visiblePagesEndRange to #visiblePages (just like in constructor).

gotoPrevPage() {
    if (this.#activePage > 1) {
      this.#activePage -= 1;
      if (this.#activePage % this.#visiblePages === 0) {
        this.#visiblePagesEndRange = this.#activePage;
      }
    }
  }

The above is a public function which can used to decrement #activePage by 1 every time it is executed. Generally executed on a click of Prev button or a < UI icon.

  • The #activePage can only be decremented if it is greater than 1.
  • Also, suppose the #activePage is currently 7 and this function gets executed, #activePage will change to 6 and it's modulus with #visiblePages will be equivalent to 0. What this means is that the #activePage now belongs to a lower visible page range and it's necessary to reflect that by updating #visiblePagesEndRange by setting it equal to #activePage itself.
gotoPage(page) {
    this.#activePage = page;
  }

The above is a public function which is used to set #activePage to the passed page parameter.

gotoNextPage() {
    if (this.#activePage < this.#noOfPages) {
      this.#activePage += 1;

      if (this.#activePage > this.#visiblePagesEndRange) {
        this.#visiblePagesEndRange += this.#visiblePages;
        this.#visiblePagesEndRange = Math.min(this.#visiblePagesEndRange, this.#noOfPages);
      }
    }
  }

The above is a public function which can be used to increment #activePage by 1 every time it is executed. Generally executed on a click of Next button or a > UI icon.

  • The #activePage can only be incremented if it is less than the #noOfPages.
  • Also, suppose the #activePage is currently 6 and this function gets executed, #activePage will change to 7 but also go out of bounds of current #visiblePagesEndRange so we will update that as well by an amount of #visiblePages so that #visiblePagesEndRange which was earlier 6 now becomes 12.
  • Again, #visiblePagesEndRange cannot exceed the #noOfPages and that's why if adding #visiblePages to it results in an out of bounds, we take that into consideration by taking the minimum as shown above.
gotoLastPage() {
    this.#activePage = this.#noOfPages;
    this.#visiblePagesEndRange = this.#noOfPages;
  }

The above is a public function to set both #activePage and #visiblePagesEndRange to #noOfPages.

getVisiblePagesRange() {
    let beginningVisiblePage;
    let endingVisiblePage;
    if (this.#visiblePagesEndRange % this.#visiblePages === 0) {
      beginningVisiblePage = this.#visiblePagesEndRange - this.#visiblePages + 1;
    }
    else {
      beginningVisiblePage =
      this.#visiblePagesEndRange - (this.#visiblePagesEndRange % this.#visiblePages) + 1;
    }
    endingVisiblePage = this.#visiblePagesEndRange;
    return {
      beginningVisiblePage,
      endingVisiblePage
    };
  }

The above is a public function which is used to retrieve beginningVisiblePage and endingVisiblePage by the means of which you can generate the respective UI page elements dynamically.

  • For the beginningVisiblePage :-

    • If #visiblePagesEndRange % this.#visiblePages is 0, then beginningVisiblePage can be set to #visiblePagesEndRange - this.#visiblePages + 1
    • Otherwise, consider a scenario when the #visiblePagesEndRange will be 58 (this would happen in the last page range). Now 58 % 6 isn't 0 but 4. So we would need to subtract 4 from 58 and add 1 to it to get the correct beginningVisiblePage which will be 55. (Final page range is actually 55,56,57 and 58 for our current example).
  • The endingVisiblePage will always be equal to #visiblePagesEndRange.

getActiveRecordsIndices() {
    let beginningRecordIndex = (this.#activePage - 1) * this.#recordsPerPage;
    let endingRecordIndex = Math.min(
      beginningRecordIndex + this.#recordsPerPage,
      this.#totalRecords
    );
    return { beginningRecordIndex, endingRecordIndex };
  }
}

The above is a public function which is used to retrieve beginningRecordIndex and endingRecordIndex by the means of which you can generate the respective UI record elements dynamically.

  • The beginningRecordIndex will be equal to #activePage-1 multiplied by the #recordsPerPage.
  • The endingRecordIndex will be minimum of beginningRecordIndex + #recordsPerPage and #totalRecords.

Below is a codepen where you can see the Paginator class in action. Here there is an additional #validate function which isn't important to basic implementation. And yes I haven't really applied the best CSS out there !!

I hope you enjoyed reading this piece :D. Also feel free to give any feedback. I just like to make something in vanilla JS every once in a while and not think too much about production readiness while making it. That's the part where you can come in and share your approaches.

29