Laravel Breeze & Vue.js pagination component
Tags: laravel, vue, webdev, frontend
Published: 2023-09-15 08:33:18

Laravel have a pagination feature. It's help blade template to generate a pagination links. This feature is really useful for blade template.

But how do we display it in Javascript framework?

Here is example we can use in Vue.js framework. There is 2 method development for developer to use Vue.js framework. 

- [Laravel Breeze (Inertia.js with Vue.js)](#laravel-breeze-inertiajs-with-vuejs)
- [External Vue.js Framework](#external-vuejs-framework)

I will show example for both.

<hr>

### Laravel Breeze (Inertia.js with Vue.js)

First, create a backend that return the pagination.
```php
<?php

namespace App\Http\Controllers;

use App\Models\Activity;
use Inertia\Inertia;

class ActivityController extends Controller
{
    public function index()
    {
        $activities = Activity::paginate(10)->withQueryString();

        return Inertia::render('/activities/index', [
            'activities' => $activities,
        ]);
    }
}

```

In page file, we can use the component like this:

```vue
<script setup>
import PaginationBar from '../components/PaginationBar.vue'

const props = defineProps({
    activities: Object,
});

</script>

<template>
    <PaginationBar :links="props.activities.links" />
</template>
```

We create a pagination component file (`resources/js/components/PaginationBar.vue`).

```vue
<script setup>
import { Link } from "@inertiajs/vue3";
import { onMounted, ref, watch } from "vue";

const props = defineProps({
    links: Array,
});

const buttons = ref([]);
const button_prev_url = ref(null);
const button_next_url = ref(null);

const init = (limit) => {
    let links = props.links;
    let selected_index = null;

    // remove prev & next
    links.shift();
    links.pop();

    // select index
    links.map((link, i) => {
        if (link.active) selected_index = i;
    });

    button_prev_url.value = links[selected_index - 1]?.url;
    button_next_url.value = links[selected_index + 1]?.url;

    buttons.value = links;
};

watch(
    () => props.links,
    () => init(),
);

onMounted(() => init());
</script>

<template>
    <div v-if="buttons.length > 1">
        <div class="flex flex-wrap justify-center">
            <Link
                class="px-4 py-2 ml-2 mr-4 text-sm rounded-lg"
                :class="
                    button_prev_url
                        ? 'text-slate-500 focus:text-main-500 hover:bg-main-50 font-bold'
                        : 'text-slate-300'
                "
                :href="button_prev_url"
            >
                Previous
            </Link>
            <template v-for="(btn, i) in buttons" v-bind:key="i">
                <Link
                    class="px-4 py-2 mx-0.5 text-sm font-bold rounded-lg hover:bg-main-50 focus:text-main-500"
                    :class="
                        btn.active ? 'bg-main-600 text-white' : 'text-slate-500'
                    "
                    :href="btn.url"
                >
                    {{ btn.label }}
                </Link>
            </template>
            <Link
                class="px-4 py-2 ml-4 mr-2 text-sm rounded-lg"
                :class="
                    button_next_url
                        ? 'text-slate-500 focus:text-main-500 hover:bg-main-50 font-bold'
                        : 'text-slate-300'
                "
                :href="button_next_url"
            >
                Next
            </Link>
        </div>
    </div>
</template>

```

<hr>

### External Vue.js Framework

For external, it's a bit challenging because Laravel pagination passing the backend url instead of frontend url.

Let's try create an API that return the pagination.
```php
<?php

namespace App\Http\Controllers;

use App\Models\Activity;
use Illuminate\Http\Request;

class ActivityController extends Controller
{
    public function index(Request $request)
    {
        $activities = Activity::paginate(10)->withQueryString();

        return response()->json($activities);
    }
}
```

In page file, we can use the component like this:
```vue
<script setup>
import { ref, onUpdated, onMounted } from 'vue'
import PaginationBar from '../components/PaginationBar.vue'

const activities = ref(null);

const load = () => {
    // Fetch API here and set it response as activities value.
}

onUpdated(() => load())
onMounted(() => load())
</script>

<template>
    <PaginationBar route_name="activities" :links="activities.links" />
</template>
```

We create a pagination component file (`src/components/PaginationBar.vue`).

```vue
<script setup>
import { onMounted, ref, watch } from "vue";
import { RouterLink } from 'vue-router'

const props = defineProps({
    links: Array,
    route_name: String,
});

const buttons = ref([]);
const button_prev_label = ref(null);
const button_next_label = ref(null);

const init = () => {
    let links = props.links;
    let selected_index = null;

    // remove prev & next
    links.shift();
    links.pop();

    // select index
    links.map((link, i) => {
        if (link.active) selected_index = i;
    });

    button_prev_label.value = links[selected_index - 1]?.label;
    button_next_label.value = links[selected_index + 1]?.label;

    buttons.value = links;
}

watch(
    () => props.links,
    () => init(),
);

onMounted(() => init());
</script>

<template>
    <div v-if="buttons.length > 1" class="flex justify-center my-5">
        <div class="flex flex-wrap justify-center py-2 min-w-2/5 rounded-lg shadow">
            <RouterLink
                class="px-4 py-2 ml-2 mr-4 text-sm rounded-lg"
                :class="button_prev_label ? 'text-slate-300 focus:text-white-300 hover:bg-slate-800 font-bold' : 'text-slate-500'"
                :to="{ name: props.route_name, query: { page: button_prev_label } }"
            >
                Previous
            </RouterLink>
            <template v-for="(btn, i) in buttons" v-bind:key="i">
                <div
                    v-if="btn.label == '...'"
                    class="px-4 py-2 mx-0.5 text-sm font-bold rounded-lg text-slate-500"
                >
                    {{ btn.label }}
                </div>
                <RouterLink
                    v-else
                    class="px-4 py-2 mx-0.5 text-sm font-bold rounded-lg hover:bg-slate-800 focus:text-main-500"
                    :class="btn.active ? 'bg-slate-800 text-white' : 'text-slate-500'"
                    :to="{ name: props.route_name, query: { page: btn.label } }"
                >
                    {{ btn.label }}
                </RouterLink>
            </template>
            <RouterLink
                class="px-4 py-2 ml-4 mr-2 text-sm rounded-lg"
                :class="button_next_label ? 'text-slate-300 focus:text-white-300 hover:bg-slate-800 font-bold' : 'text-slate-500'"
                :to="{ name: props.route_name, query: { page: button_next_label } }"
            >
                Next
            </RouterLink>
        </div>
    </div>
</template>
```

As you can see, I use props `routename` so that I can pass a route name from Vue.js router and not from Laravel router.

This is example I have. Anyone can use and maybe enhance for a better use case.

Thank you.