Implementing Pagination feature in Vanilla JS
Tags: javascript, webdev, uiweekly
Published: 2021-07-04 08:01:45

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

```js
// 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();
```

![let's go](https://jhmediagroup.com/blog/wp-content/uploads/2019/09/lets-go-gif.gif)

The Class blueprint :-

```js
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**.

![initiate](https://media4.giphy.com/media/jU2m0JwzJti3Yoylc5/giphy.gif)

Let's start with the constructor :-

```js
  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.

------------------------------------------------

```js
  getActivePage(){
    return this.#activePage;
  }
```

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

------------------------------------------------

```js
  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).

------------------------------------------------

```js
  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.

------------------------------------------------

```js
  gotoPage(page) {
    this.#activePage = page;
  }
```

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

------------------------------------------------

```js
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.

------------------------------------------------

```js
  gotoLastPage() {
    this.#activePage = this.#noOfPages;
    this.#visiblePagesEndRange = this.#noOfPages;
  }
```

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

------------------------------------------------

```js
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`.

------------------------------------------------

```js
  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 !!

![practical](https://media4.giphy.com/media/26ufaqo3YRPQ24HPW/200.gif)

{% codepen https://codepen.io/lapstjup/pen/NWbrejR %}

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.