Vue.js - A simple pagination

In this post, I will implement a simple client-side pagination in Vue.js

A few things to note

  • The purpose of this tutorial is understanding and creating pagination on client side with Vue.js, so the implementation is really basic.
  • Thereby it may not be the best solution, and non functional requirements (performance etc) is not guaranteed.
  • For styling, I’m using bulma pagination
  • I’m using version 2.5.11

General concept/idea

  • For reusability, pagination logic should be in a separated component
  • The parent component (where pagination is included) should not be handling any pagination logic, except displaying data according to current page

Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
name: 'pagination',
props: {
    total: {
        type: Number,
        default: 0
    },
},
data() {
    return {
        elements: [], // store pages object to be rendered
        totalPages: 0, // total pages
        current: 1, // current page
        itemsPerPage: 10, // self-explanatory
        pageCount: 5 // how many pages you want to display
    }
},

Most of the properties are pretty self-explanatory, except pageCount. It’s total pages which we show to user.

pageCount=5

page-count-sample.png

pageCount=7

page-count-7-sample.png

Next is component’s methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
methods: {
    add(s, f) {
        for (var i = s; i <= f; i++) {
            this.elements.push({
                type: 'page',
                page: i
            })
        }
    },
    addBreak() {
        this.elements.push({
            type: 'ellipse-break'
        });
    },
    first() {
        // Add first page
        this.elements.push({
            type: 'page',
            page: 1
        });
    },
    last() {
        // Add last page
        this.elements.push({
            type: 'page',
            page: this.totalPages
        }, )
    },
    ...
}

Explanation:

  • add: simply creating a new page and add it to elements. For example, add(1,5) will create 5 page buttons.
  • addBreak: add ellipse break.
  • first: add first page.
  • last: add last page.

Let’s continue with other methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
methods: {
    ...
    paginate() {
        this.elements = [];
        this.totalPages = Math.ceil(this.total / this.itemsPerPage);
        let t = Math.floor(this.pageCount/2);
        if (this.totalPages < t) {
            // Case without any breaks
            this.add(1, this.totalPages);
        } else if (this.current >= this.totalPages - t) {
            // Case with breaks at beginning
            this.first();
            this.addBreak();
            this.add(this.current - t, this.totalPages);
        } else if (this.current <= t + 1) {
            // Case with breaks at end
            this.add(1, this.current + t);
            this.addBreak();
            this.last();
        } else {
            // Case with breaks at beginning and end
            this.first();
            this.addBreak();
            this.add(this.current - t, this.current + t);
            this.addBreak();
            this.last();
        }
    },
    onChange: function(page) {
        this.current = page;
        this.paginate();
        let from = (this.current * this.itemsPerPage) - this.itemsPerPage;
        let to = (this.current * this.itemsPerPage);
        this.$emit('showPagingData', {
            from: from,
            to: to
        });
    }
}

Explanation:

  • onChange: called when user clicked on page button. Here we calculate the records we have to show on clicked page, then emit an event to parent component.
  • paginate: this method will be called every time we need to calculate and render paging (after search, user clicked on page buttton etc). There are 4 cases we need to check:
    • total pages < number of pages we want to display
    • current page is near the end, so we have to add break at the beginning
    • current page is near the beginning, so we have to add break at the end
    • current page is kinda in the middle, so we have to add break at both the beginning and the end

Template

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<template>
    <div id="pagination">
        <nav class="pagination is-centered" role="navigation" aria-label="pagination">
            <a class="pagination-previous" v-if="current > 1" @click="onChange(current - 1)">Previous</a>
            <a class="pagination-next" v-if="totalPages > 1 && current < totalPages" @click="onChange(current + 1)">Next</a>
            <ul class="pagination-list">
                <li v-for="element in elements" :key="element.page" :current="current">
                    <template v-if="element.type=='page'">
                        <a :class="['pagination-link', { 'is-current': current === element.page }]" v-on:click="onChange(element.page)">
                            {{ element.page }}
                        </a>
                    </template>
                    <template v-else>
                        <li><span class="pagination-ellipsis">&hellip;</span></li>
                    </template>
                </li>
            </ul>
        </nav>
    </div>
</template>

The most important part here is rendering the page buttons.

1
2
3
4
5
<li v-for="element in elements" :key="element.page" :current="current">
    <a class="pagination-previous" v-if="current > 1" @click="onChange(current - 1)">Previous</a>
    <a class="pagination-next" v-if="totalPages > 1 && current < totalPages" @click="onChange(current + 1)">Next</a>
    ...
</li>

We will loop through elements array where we store pages object, check condition if we have to render Previous and Next button or not.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<ul class="pagination-list">
    <li v-for="element in elements" :key="element.page" :current="current">
        <template v-if="element.type=='page'">
            <a :class="['pagination-link', { 'is-current': current === element.page }]" v-on:click="onChange(element.page)">
                {{ element.page }}
            </a>
        </template>
        <template v-else>
            <li><span class="pagination-ellipsis">&hellip;</span></li>
        </template>
    </li>
</ul>

With each element, if it’s a page, then render a button and assign click event. If it’s a break, then insert break character.

Parent component

1
2
<pagination :total="facilityList.length" @showPagingData="showPagingData">
</pagination>

Here we passed total data count, and register showPagingData event handler.

1
2
3
showPagingData: function(val) {
    this.pagingData = this.facilityList.slice(val.from, val.to);
}

Result

demo.gif

Things to improve

  • Add First and Last button?
  • Allow passing parameter from parent component
  • Moving data display logic to paging component