Implementing Pagination feature in Vanilla JS

Have tinkered with JavaScript majorly | I like to solve problems on Stack Overflow, write blog articles, create a side project or do something creative.
Search for a command to run...

Have tinkered with JavaScript majorly | I like to solve problems on Stack Overflow, write blog articles, create a side project or do something creative.
very great article
Introduction Alright, alright, alright !!! I have officially completed three years at Hashnode now. If you have been following my blog for a while, I have this ritual of documenting what all went in the past year at my job. My general process for do...

Introduction Well here we are reflecting on my time at Hashnode the previous year. I have officially completed two years at Hashnode and as a part of ongoing ritual where I document the experience, here is another one.And also, in first quarter of 20...

Introduction One of the things when using the NextJS ecosystem that hasn't yet clicked for me is their Script component. https://nextjs.org/docs/pages/building-your-application/optimizing/scripts I have been running some experiments on how I can le...

Introduction This article will touch upon scenarios that I have encountered while working at Hashnode where some code reviews helped us prevent future rework on the same issue by having a broader perspective. Code Review 0 The Problem Sometime back,...

Introduction You're scrolling through Twitter (I still like calling it that) or Medium and you get a nice view of the sidebar as it snaps to the bottom or top of the page if you try to scroll beyond its boundaries. We will re-create the same experie...

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;
}
#recordsPerPage, #totalRecords and #visiblePages straight away get initialized to passed constructor parameters. #noOfPages by dividing #totalRecords by #recordsPerPage. #activePage as the name denotes is the page which will be active/selected in your pagination UI. It is initialized to 1. #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.
#activePage can only be decremented if it is greater than 1. #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.
#activePage can only be incremented if it is less than the #noOfPages. #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. #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 :-
#visiblePagesEndRange % this.#visiblePages is 0, then beginningVisiblePage can be set to #visiblePagesEndRange - this.#visiblePages + 1#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.
beginningRecordIndex will be equal to #activePage-1 multiplied by the #recordsPerPage. 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.