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, thenbeginningVisiblePage
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 correctbeginningVisiblePage
which will be 55. (Final page range is actually 55,56,57 and 58 for our current example).
- If
-
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 ofbeginningRecordIndex + #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.