Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Randy_91's avatar

Pagination in Nova 4 custom tool

How do I get pagination working in the following Tool.vue file

<template>
    <LoadingView :loading="initialLoading">


        <Heading :level="1" class="mb-3 flex items-center">
            <span>Adverts</span>
        </Heading>

        <div class="flex mb-6">
            <IndexSearchInput
                :placeholder="searchPlaceholder"
            />
            <div class="w-full flex items-center" >
                <!-- Create / Attach Button -->
                <CreateResourceButton
                    :label="createButtonLabel"
                    :singular-name="singularName"
                    :resource-name="resourceName"
                    class="flex-shrink-0 ml-auto mb-6"
                />
            </div>
        </div>
        <Card>
            <ResourceTableToolbar>

            </ResourceTableToolbar>
            <LoadingView :loading="loading">
                <table class="w-full divide-y divide-gray-100 dark:divide-gray-700"
                       data-testid="resource-table">
                    <thead class="bg-gray-50 dark:bg-gray-800">
                    <tr>
                        <th class="td-fit uppercase text-xxs text-gray-500 tracking-wide pl-5 pr-2 py-2"><span class="sr-only">Selected Resources</span></th>
                        <th class="text-left px-2 whitespace-nowrap uppercase text-gray-500 text-xxs tracking-wide py-2">ID</th>
                        <th class="text-left px-2 whitespace-nowrap uppercase text-gray-500 text-xxs tracking-wide py-2">Headline</th>
                        <th class="text-left px-2 whitespace-nowrap uppercase text-gray-500 text-xxs tracking-wide py-2">Start Date</th>
                        <th class="text-left px-2 whitespace-nowrap uppercase text-gray-500 text-xxs tracking-wide py-2">End Date</th>
                    </tr>
                    </thead>
                    <tbody class="divide-y divide-gray-100 dark:divide-gray-700">
                    <tr class="group" v-for="(item, index) in items" :key="index">
                        <td class="py-2 cursor-pointer td-fit pl-5 pr-5 dark:bg-gray-800 group-hover:bg-gray-50 dark:group-hover:bg-gray-900"><input type="checkbox" class="checkbox" aria-label="Select Resource 1" data-testid="adverts-items-0-checkbox" dusk="1-checkbox"></td>
                        <td class="px-2 py-2 whitespace-nowrap cursor-pointer dark:bg-gray-800 group-hover:bg-gray-50 dark:group-hover:bg-gray-900">
                            <div class="text-left"> <span class="whitespace-nowrap">{{ item.id }}</span></div>
                        </td>
                        <td class="px-2 py-2 whitespace-nowrap cursor-pointer dark:bg-gray-800 group-hover:bg-gray-50 dark:group-hover:bg-gray-900">{{ item.headline }}</td>
                        <td class="px-2 py-2 whitespace-nowrap cursor-pointer dark:bg-gray-800 group-hover:bg-gray-50 dark:group-hover:bg-gray-900">{{ item.start_date }}</td>
                        <td class="px-2 py-2 whitespace-nowrap cursor-pointer dark:bg-gray-800 group-hover:bg-gray-50 dark:group-hover:bg-gray-900">{{ item.end_date }}</td>
                    </tr>
                    </tbody>
                </table>
                <pagination-links
                    v-if="resourceResponse"
                    :resource-name="resourceName"
                    :resources="resources"
                    :resource-response="resourceResponse"
                    @previous="selectPreviousPage"
                    @next="selectNextPage">
                </pagination-links>
            </LoadingView>
        </Card>
    </LoadingView>
</template>

<script>
import axios from 'axios';
import { Paginatable, PerPageable } from 'laravel-nova';

export default {

    mixins: [
        Paginatable,
        PerPageable
    ],

    data() {
        return {
            items: [],
            initialLoading: true,
            loading:false,
            createButtonLabel: 'Create New Advert',
            searchPlaceholder: 'Search Adverts',
            singularName: 'Advert',
            resourceName: 'Adverts',
        };
    },
    mounted() {
        this.getAdverts();
    },
    methods: {
        getAdverts() {
            this.initialLoading = false;
            this.loading = true;
            axios.get('/nova-vendor/manager/index')
                .then(response => {
                    this.items = response.data;
                    this.loading = false;
                })
                .catch(error => {
                    console.log(error);
                });

        },
        computed: {
            /**
             * Return the heading for the view
             */
            headingTitle() {
                if (this.initialLoading) {
                    return '&nbsp;'
                } else {
                    return 'Adverts'
                }
            },
        }
    }
}
</script>

<style>
/* Your custom styles here */
</style>```
0 likes
9 replies
LaryAI's avatar
Level 58

To get pagination working in the provided Tool.vue file, you can use the Paginatable mixin provided by Laravel Nova. Here's how you can modify the code to add pagination:

  1. Import the Paginatable mixin at the top of the file:
import { Paginatable } from 'laravel-nova';
  1. Add the Paginatable mixin to the mixins array:
mixins: [
    Paginatable
],
  1. Modify the getAdverts method to use the paginate method provided by the Paginatable mixin:
getAdverts() {
    this.initialLoading = false;
    this.loading = true;
    this.paginate(axios.get('/nova-vendor/manager/index'))
        .then(response => {
            this.items = response.data;
            this.loading = false;
        })
        .catch(error => {
            console.log(error);
        });
},
  1. Add the pagination-links component to the template:
<pagination-links
    v-if="resourceResponse"
    :resource-name="resourceName"
    :resources="resources"
    :resource-response="resourceResponse"
    @previous="selectPreviousPage"
    @next="selectNextPage">
</pagination-links>

Note: Make sure to wrap the code examples with three backticks when posting on the Laracasts forum.

Randy_91's avatar

Lary's answer did not work as I get the following error:

WARNING in ./resources/js/pages/Tool.vue?vue&type=script&lang=js (./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/pages/Tool.vue?vue&type=script&lang=js) 4:11-22 export 'Paginatable' (imported as 'Paginatable') was not found in 'laravel-nova' (possible exports: CopiesToClipboard, DependentFormField, Errors, FieldValue, FormEvents, FormField, HandlesFieldAttachments, HandlesFormRequest, HandlesPanelVisibility, HandlesUploads, HandlesValidationErrors, HasCards, Localization, MetricBehavior, PreventsFormAbandonment, PreventsModalAbandonment, mapProps, useCopyValueToClipboard, useLocalization)

KaiHiwatari's avatar

@Randy_91 Thanks for this Randy did you install the package using npm within your custom tool? would love a code snippet if possible

KaiHiwatari's avatar

@Randy_91 Hi buddy just wondering if you had a chance to upload the snippet - I have been trying to make this work for a while but have had no luck :(

Randy_91's avatar

@KaiHiwatari it took quite a bit of work and effort to get it working. Ill see if i can u get something to work with it later.

Randy_91's avatar

install the package using npm within your custom tool.

Then I create a vue component with the following code

<template>
    <RenderlessPagination
        :data="data"
        :limit="limit"
        :keep-length="keepLength"
        @pagination-change-page="onPaginationChangePage"
    >
        <template #default="slotProps">
            <div class="rounded-b-lg">
                <nav class="flex justify-between items-center"
                    v-bind="$attrs"
                    aria-label="Pagination"
                    v-if="slotProps.computed.total > slotProps.computed.perPage"
                >
                    <button
                        :class="{
                          'text-xs font-bold py-3 px-4 focus:outline-none rounded-bl-lg focus:ring focus:ring-inset text-primary-500 hover:text-primary-400 active:text-primary-600': slotProps.computed.prevPageUrl,
                          'text-xs font-bold py-3 px-4 focus:outline-none rounded-bl-lg focus:ring focus:ring-inset text-gray-300 dark:text-gray-600': !slotProps.computed.prevPageUrl
                        }"
                            :disabled="!slotProps.computed.prevPageUrl"
                            v-on="slotProps.prevButtonEvents"
                    >
                        <slot name="prev-nav">
                            Previous
                        </slot>
                    </button>
                    <span class="text-xs px-4">
                        {{ currentPageRange(slotProps.computed) }}
                    </span>
                    <button
                        :class="{
                          'text-xs font-bold py-3 px-4 focus:outline-none rounded-br-lg focus:ring focus:ring-inset text-primary-500 hover:text-primary-400 active:text-primary-600': slotProps.computed.nextPageUrl,
                          'text-xs font-bold py-3 px-4 focus:outline-none rounded-bl-lg focus:ring focus:ring-inset text-gray-300 dark:text-gray-600': !slotProps.computed.nextPageUrl
                        }"
                        :disabled="!slotProps.computed.nextPageUrl"
                        v-on="slotProps.nextButtonEvents"
                    >
                        <slot name="next-nav">
                            Next
                        </slot>
                    </button>
                </nav>
            </div>
        </template>
    </RenderlessPagination>
</template>

<script>
import RenderlessPagination from 'laravel-vue-pagination/src/RenderlessPagination.vue';

export default {
    inheritAttrs: false,

    emits: ['pagination-change-page'],

    components: {
        RenderlessPagination,
    },

    props: {
        data: {
            type: Object,
            default: () => ({}),
        },
        limit: {
            type: Number,
            default: 0,
        },
        keepLength: {
            type: Boolean,
            default: false,
        },
        perPage: {
            type: Number,
            default: 10
        },
    },

    methods: {
        onPaginationChangePage(page) {
            this.$emit('pagination-change-page', page, this.computedPerPage);
        },
        currentPageRange(computed) {
            const currentPage = computed.currentPage;
            const perPage = this.computedPerPage;
            const total = computed.total;

            if (total === 0) {
                return '';
            }

            const start = (currentPage - 1) * perPage + 1;
            const end = Math.min(currentPage * perPage, total);

            return `${start}-${end} of ${total}`;
        },
    },
    computed: {
        computedPerPage() {
            // Use the perPage prop if available, otherwise use a default value
            return this.perPage;
        },
        // ... other computed properties
    }
};
</script>```

I then included that in my tool.vue
```import ROPagination from "../components/ROPagination.vue";
<ROPagination :data="items" :limit="currentPageCount" :keep-length="true" @pagination-change-

You will need to update your tool to provide the props e.g. items, limit and currentPageCount. And also update your laravel controller to accept this

1 like

Please or to participate in this conversation.