Pagination Component using Vue 2.0

Pagination is one of the basic component we will need with any application. Let's create a pagination component using Bootstrap and Vue.js, and in the course of development we will see about custom events in Vue.js.

As I always suggest, let's decide how the component look from outside by defining what are the properties and events we will need.

<!--Method 1 -->
<pagination :current-page="currentPage"
            :total-pages="totalPages"
            @page-changed="handleIt">
</pagination>

<!--Method 2 -->
<pagination :current-page="currentPage"
            :total-items="totalItems"
            :items-per-page="itemsPerPage"
            @page-changed="handleIt">
</pagination>

Note we are going to use Vue.js 2.0.

We are going to have two different interfaces for different scenarios. This covers two possible scenarios:

  • You know total number of pages you need
  • You know total number of items and items per page

So the properties have to be defined for the component accordingly

export default {

  props: {

    // Current Page
    currentPage: {
      type: Number,
      required: true
    },

    // Total number of pages
    totalPages: Number,

    // Items per page
    itemsPerPage: Number,

    // Total items
    totalItems: Number,

    // Visible Pages
    visiblePages: {
      type: Number,
      default: 5,
      coerce: (val) => parseInt(val)
    }

  }

  /*
    More to come
   */
}

visiblePages is an optional property which defines how many pages should be displayed in the pagination component. It will be

Let's start building the component by defining the template first.

<template>
<ul class="pagination">
  <li>
    <a href="#" @click.prevent="pageChanged(1)" aria-label="Previous">
      <span aria-hidden="true">&laquo;</span>
    </a>
  </li>
  <li v-for="n in paginationRange" :class="activePage(n)">
    <a href="#" @click.prevent="pageChanged(n)">{{ n }}</a>
  </li>
  <li>
    <a href="#" @click.prevent="pageChanged(lastPage)" aria-label="Next">
      <span aria-hidden="true">&raquo;</span>
    </a>
  </li>
</ul>
</template>

The above template will display a simple Bootstrap styled Pagination.

The template is a lot expressive and self-explanatory. We have First Page & Last Page arrows at two ends and a set of pages in the middle with corresponding events attached to it. paginationRange is a computed property which helps us limit the number of pages that should be displayed when we have two many pages.

Time to code the two computed properties which we will need to make this component: lastPage and paginationRange.

lastPage has to be calculated based on the inputs given. If the totalPages property is given then it has to be taken as the lastPage, otherwise lastPage has to be calculated from itemsPerPage & totalItems.

  computed: {
    lastPage () {
      if (this.totalPages) {
        return this.totalPages
      } else {
        return this.totalItems % this.itemsPerPage === 0
          ? this.totalItems / this.itemsPerPage
          : Math.floor(this.totalItems / this.itemsPerPage) + 1
      }
    }

    paginationRange () {
        /**
         * Code for pagination range here
         */
    }
}    

When there are 100 pages, we don't want to display all 100 pages. Only a set of pages around the current selected page is enough to be displayed. paginationRange computed property will take care of that with a little bit of logic and math.

paginationRange () {
  let start =
    this.currentPage - this.visiblePages / 2 <= 0
    ? 1 : this.currentPage + this.visiblePages / 2 > this.lastPage
    ? Util.lowerBound(this.lastPage - this.visiblePages + 1, 1)
    : Math.ceil(this.currentPage - this.visiblePages / 2)

  let range = []

  for (let i = 0; i < this.visiblePages && i < this.lastPage; i++) {
    range.push(start + i)
  }

  return range
}

Here Util is a custom utility object. It's a good practice to place any logic which can be useful somewhere else into a separate class or object for better reusabilty.

Now we need to highlight the active page and if you watch the template closely we have :class="activePage(n)" which does the job. This function has to be defined within the methods section of the component.

methods: {
  activePage (pageNum) {
    return this.currentPage === pageNum ? 'active' : ''
  }
}

Last but the most needed part is the pageChanged event which will trigger the handler bound from the parent component. We are going to make a method which is bound to the click event of the page links in the Pagination. This method will the emit the event so that the listeners can handle it.

pageChanged (pageNum) {
  this.$emit('page-changed', pageNum)
}

In case if you are trying the same with Vue 1.0, then replace $emit with $dispatch.

Now let's see how to use this in the parent component.

<template>
<div class="container">
    <div id="app" class="well">
        <h1>This is page {{pageOne.currentPage}}</h1>
    </div>
    <pagination :current-page="pageOne.currentPage"
                :total-pages="pageOne.totalPages"
                @page-changed="pageOneChanged">
    </pagination>
</div>

</template>

<script>
import Pagination from './Pagination.vue'

export default {

    components : { Pagination },

    data() {
        return {
            pageOne: {
                currentPage: 1,
                totalPages: 10
            }
        }
    },

    methods: {
        pageOneChanged (pageNum) {
            this.pageOne.currentPage = pageNum
        }
    }
}
</script>

Thats it, you should have nice and good looking pagination component as shown below. You can clone and run from the Github repo.

Pagination Demo

Fareez Ahamed

SAP Developer, Full Stack Web Developer, Laravel & Vue.js fanatic