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.
Top comments (3)
One usability issue here: the active page should always be in the middle of the rendered buttons, if there are enough pages to pad it. Otherwise, well executed, even though I prefer a functional approach that only calculates which buttons with which properties to show depending on the input that is slightly more reusable and agnostic of page content.
Hey thanks for the feedback. I didn't quite get the functional approach. Can you elaborate a bit more for my benefit ?
Without providing a fully working example, instead of having a Pagination object, you merely use a pure function (same input always produces same output) to calculate the pagination that should be displayed: