Paulund

VueJS Laravel Pagination Component

In this tutorial we're going to build a VueJS component you can use in your Laravel 5.4 projects when dealing with pagination. A component is one of the most powerful features of VueJS they allow you to encapsulate reusable code you can use in multiple places throughout your application.

Pagination

Laravel provides you with a very easy to use pagination functionality out of the box. All you have to do is use the paginate method on your database queries. Instead of using the get() method to return all the results you'll use paginate( $perPage ); passing in the amount of items you want to return per page.

$posts = DB::table('posts')->get();

$posts = DB::table('posts')->paginate(10);

This will return a LengthAwarePaginator class that also returns meta information you can use to build a pagination component in your HTML. Data such as

{
   "total": 50,
   "per_page": 10,
   "current_page": 1,
   "last_page": 4,
   "next_page_url": "http://paulund.dev?page=2",
   "prev_page_url": null,
   "from": 1,
   "to": 10,
   "data":[
        {
            // Result Object
        },
        {
            // Result Object
        }
   ]
}

Using the data properties current_page, last_page we can build a previous and next pagination buttons. With a range of page number in the middle.

VueJS Pagination Component

Using the component we need to pass in two pieces of data, first the pagination information stated above, second we need to define how many each side page number ranges we want, these are defined with prop variables.

Props

props: {
    pagination: {
        type: Object,
        required: true
    },
    eachSide: {
        type: Number,
        default: 5,
        required: false
    }
},

Computed Methods

To work out the page number range either side of the current page number we need a computed method to fetch the page numbers.

Within this method we need to work out where the current page is and add required pages either side.

computed: {
    pagesNumber() {
        let pagesArray = [];
        let firstPage = 1;
        let lastPage = this.pagination.last_page;
        
        if (this.eachSide !== '') {
            if (this.pagination.current_page - this.eachSide > firstPage) {
                firstPage = this.pagination.current_page - this.eachSide
            }
            if (this.eachSide + this.pagination.current_page < lastPage) {
                lastPage = this.eachSide + this.pagination.current_page
            }
        }
        if (firstPage !== 1) {
            pagesArray.push(1);
        }
        for (let page = firstPage; page <= lastPage; page++) {
            pagesArray.push(page);
        }
        if (lastPage !== this.pagination.last_page) {
            pagesArray.push(this.pagination.last_page);
        }
        return pagesArray;
    }
},

Change Page

When the user clicks on a number of a next/previous button we need to change the page. For this we create a method which will change the current page and then emit an event up to the parent component. The parent component can then listen to this event and do what it needs to do when changing page number.

methods: {
    changePage ( page ) {
        this.pagination.current_page = page;
        this.$emit('paginate');
    }
}

Pagination HTML

Finally with the component logic setup we can add the HTML. This needs a previous button a next button and a page number range.

On the click event of these buttons we need to call the changePage method passing in the page we want to use as the current page.


<template>
    <nav>
        <ul class="pagination">
            <li v-if="pagination.current_page > 1">
                <a href="javascript:void(0)" aria-label="Previous" v-on:click.prevent="changePage(pagination.current_page - 1)">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
            <li v-for="page in pagesNumber" :class="{'active': page == pagination.current_page}">
                <a href="javascript:void(0)" v-on:click.prevent="changePage(page)">{{ page }}</a>
            </li>
            <li v-if="pagination.current_page < pagination.last_page">
                <a href="javascript:void(0)" aria-label="Next" v-on:click.prevent="changePage(pagination.current_page + 1)">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </ul>
    </nav>
</template>

Full Pagination Component

<template>
    <nav>
        <ul class="pagination">
            <li v-if="pagination.current_page > 1">
                <a href="javascript:void(0)" aria-label="Previous" v-on:click.prevent="changePage(pagination.current_page - 1)">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
            <li v-for="page in pagesNumber" :class="{'active': page == pagination.current_page}">
                <a href="javascript:void(0)" v-on:click.prevent="changePage(page)">{{ page }}</a>
            </li>
            <li v-if="pagination.current_page < pagination.last_page">
                <a href="javascript:void(0)" aria-label="Next" v-on:click.prevent="changePage(pagination.current_page + 1)">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </ul>
    </nav>
</template>

<style scoped>
    .pagination {
        display: flex;
        list-style: none;
        justify-content: center;
    }
</style>

<script>
    export default{
        props: {
            pagination: {
                type: Object,
                required: true
            },
            eachSide: {
                type: Number,
                default: 5,
                required: false
            }
        },
        computed: {
            pagesNumber() {
                let pagesArray = [];
                let firstPage = 1;
                let lastPage = this.pagination.last_page;
                
                if (this.eachSide !== '') {
                    if (this.pagination.current_page - this.eachSide > firstPage) {
                        firstPage = this.pagination.current_page - this.eachSide
                    }
                    if (this.eachSide + this.pagination.current_page < lastPage) {
                        lastPage = this.eachSide + this.pagination.current_page
                    }
                }
                if (firstPage !== 1) {
                    pagesArray.push(1);
                }
                for (let page = firstPage; page <= lastPage; page++) {
                    pagesArray.push(page);
                }
                if (lastPage !== this.pagination.last_page) {
                    pagesArray.push(this.pagination.last_page);
                }
                return pagesArray;
            }
        },
        methods: {
            changePage ( page ) {
                this.pagination.current_page = page;
                this.$emit('paginate');
            }
        }
    }
</script>

Include Pagination On Page

Now we can use this pagination component anywhere in our application, just import it into your parent component.

import pagination from 'pagination.vue';

Add it to your component list.

components: {
    pagination
}

Then add it to your HTML passing in the pagination data and the click event for when the buttons are clicked.

To change the posts on the pagination click event we need to listen on the paginate event and call the getPosts() methods changing the current page

<pagination v-bind:pagination="meta.pagination" v-on:paginate="getPosts(meta.pagination.current_page)"></pagination>