<section class='app-container'>
  <ul class='list'>

  </ul>
  <div class='pagination__wrapper'>
    <section class='pagination__items'>
      <button class='pagination__navigate-btn' onclick='firstPage()'>First</button>
      <button class='pagination__navigate-btn' onclick='prevPage()'>Prev</button>
      <ul class='pagination__pages'>

      </ul>
      <button class='pagination__navigate-btn' onclick='nextPage()'>Next</button>
      <button class='pagination__navigate-btn' onclick='lastPage()'>Last</button>
    </section>
  </div>
</section>
.pagination__pages {
  display: inline-flex;
  list-style-type: none;
  padding: 0;
}

.pagination__page-btn{
    border:1px solid black;
}

.pagination__navigate-btn {
  border:1px solid black;
}

.pagination__page-btn--active {
  border:1px solid black;
  background: #a3a3ff;
}

.list {
  list-style-type: none;
  padding: 0;
}

.app-container{
  display:flex;
  flex-direction:column;
  align-items:center;
}
// Records to paginate on 
const records = [
    ...Array(346)
      .fill("")
      .map((val, index) => `Name-${val - index}`)
  ];


// Paginator Class
class Paginator {
  #recordsPerPage;
  #totalRecords;
  #noOfPages;
  #visiblePages;
  #activePage;
  #visiblePagesEndRange;
  constructor(totalRecords, recordsPerPage = 1, visiblePages = 1) {
    this.#recordsPerPage = recordsPerPage;
    this.#totalRecords = totalRecords;
    this.#noOfPages = Math.ceil(this.#totalRecords / this.#recordsPerPage);
    this.#visiblePages = visiblePages;
    this.#activePage = 1;
    this.#visiblePagesEndRange = visiblePages;
    // below validations can be improved and really not necessary for bare minimum pagination but 🤷‍♂️
    this.#validate();
  }

  #validate() {
    if (this.#recordsPerPage <= 0) {
      this.#recordsPerPage = 1;
    }
    if (this.#visiblePages <= 0) {
      this.#visiblePages = 1;
    }
    if(this.#totalRecords<=0){
      this.#totalRecords = 1;
    }
    if (this.#noOfPages <= 0) {
      this.#noOfPages = Math.ceil(this.#totalRecords / this.#recordsPerPage);
    }
    if (this.#visiblePagesEndRange <= 0) {
      this.#visiblePagesEndRange = this.#visiblePages;
    }
    if (this.#visiblePages > this.#noOfPages) {
      this.#visiblePages = this.#noOfPages;
      this.#visiblePagesEndRange = this.#visiblePages;
    }
    if (this.#recordsPerPage > this.#totalRecords) {
      this.#recordsPerPage = this.#totalRecords;
    }
  }
  
  getActivePage(){
    return this.#activePage;
  }
  
  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);
      }
    }
  }

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

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

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

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

  getVisiblePagesRange() {
    let beginningVisiblePage;
    let endingVisiblePage;
    //  When the visiblepagesendrange % visiblepages is not zero (which means that all the pages cannot be fit in the visible pages range) then the beginning would be equivalent to visble page range - ((visible page range mod visiblepage range) - 1).
    if (this.#visiblePagesEndRange % this.#visiblePages !== 0) {
      beginningVisiblePage =
this.#visiblePagesEndRange - (this.#visiblePagesEndRange % this.#visiblePages) + 1;
    }
    // else we are always in a place where, current visible page end range - visible page range + 1 will return us the correct beginning position for the page range.
    else {
      beginningVisiblePage = this.#visiblePagesEndRange - this.#visiblePages + 1;
    }
    //Also endingActivePage would be simply equal visiblePagesEndRange.
    endingVisiblePage = this.#visiblePagesEndRange;
    return {
      beginningVisiblePage,
      endingVisiblePage
    };
  }

  getActiveRecordsIndices() {
    // the beginning page index will be current active page multiplied by no of records.
    let beginningRecordIndex = (this.#activePage - 1) * this.#recordsPerPage;
    // the ending page index will be minimum of total records and (beginning + records allowed per page);
    let endingRecordIndex = Math.min(
      beginningRecordIndex + this.#recordsPerPage,
      this.#totalRecords
    );
    return { beginningRecordIndex, endingRecordIndex };
  }
}

// All the render and using Paginator class logic comes here
(function () {
  function nextPage() {
    paginator.gotoNextPage();
    render();
  }

  function prevPage() {
    paginator.gotoPrevPage();
    render();
  }

  function lastPage() {
    paginator.gotoLastPage();
    render();
  }

  function firstPage() {
    paginator.gotoFirstPage();
    render();
  }

  // Delegating event to the parent ul.
  function gotoPage(event) {
    if (event.target.nodeName === "BUTTON") {
      const page = parseInt(event.target.dataset.item);
      paginator.gotoPage(page);
      render();
    }
  }

  const paginationPages = document.querySelector(".pagination__pages");

  paginationPages.addEventListener("click", gotoPage);

  /* paginator object 
  list which is of length 346
  recordsPerPage = 6
  visiblePages = 6  
  */
  const paginator = new Paginator(records.length,6, 6);

  // Method to render the pagination buttons;
  function renderPages() {
    const paginationPages = document.querySelector(".pagination__pages");
    let html = "";
    let {
      beginningVisiblePage,
      endingVisiblePage
    } = paginator.getVisiblePagesRange();
    for (let page = beginningVisiblePage; page <= endingVisiblePage; page++) {
      const pageClass =
        paginator.getActivePage() === page
          ? "pagination__page-btn--active"
          : "pagination__page-btn";
      html += `<li class='pagination__page'>
   <button data-item=${page} class=${pageClass}>${page}</button>
     </li>`;
    }
    paginationPages.innerHTML = html;
  }

  // Method to render the list items
  function renderList() {
    const list = document.querySelector(".list");
    const {
      beginningRecordIndex,
      endingRecordIndex
    } = paginator.getActiveRecordsIndices();
    let html = "";
    for (let index = beginningRecordIndex; index < endingRecordIndex; index++) {
      html += `<li class='list__item'>${records[index]}</li>`;
    }
    list.innerHTML = html;
  }

  // Main render function
  function render() {
    renderPages();
    renderList();
  }

  render();

  this.firstPage = firstPage;
  this.lastPage = lastPage;
  this.nextPage = nextPage;
  this.prevPage = prevPage;
  this.gotoPage = gotoPage;
})();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.