Aller au contenu
PROJET WEB DYNAMIQUE

Pagination

Pagination avec Laravel et Inertia.js

La pagination est essentielle pour afficher une grande quantité de données sur plusieurs pages tout en réduisant la charge sur le client et le serveur. Avec Laravel, la méthode paginate() génère des données paginées. Utilisée avec Inertia.js, elle devient encore plus simple et fluide.

Comment fonctionne la pagination avec paginate() ?

Laravel fournit une méthode puissante appelée paginate() qui renvoie :

  • Une collection de données (par page).
  • Des métadonnées utiles comme le nombre total d’éléments, la page actuelle, les URLs pour la navigation, etc.

Exemple de réponse JSON générée par paginate() :

{
  "data": [
    { "id": 1, "name": "John Doe" },
    { "id": 2, "name": "Jane Smith" }
  ],
  "current_page": 1,
  "last_page": 5,
  "per_page": 10,
  "total": 50,
  "prev_page_url": null,
  "next_page_url": "http://example.com?page=2",
  "links": [
    { "url": null, "label": "« Previous", "active": false },
    { "url": "http://example.com?page=1", "label": "1", "active": true },
    { "url": "http://example.com?page=2", "label": "2", "active": false },
    { "url": "http://example.com?page=3", "label": "3", "active": false },
    { "url": "http://example.com?page=4", "label": "4", "active": false },
    { "url": "http://example.com?page=5", "label": "5", "active": false },
    { "url": "http://example.com?page=2", "label": "Next »", "active": false }
  ]
}

Différences entre Axios/Fetch et Inertia.js pour la pagination

Axios/Fetch Inertia.js
Nécessite des appels API séparés pour chaque page. Les données sont directement envoyées depuis Laravel.
Gestion manuelle des états de chargement et des erreurs. Gestion implicite des transitions et des erreurs.
Réponse paginée doit être formatée côté client. Les données sont directement utilisables avec les props.
Risque de duplication de logique côté frontend et backend. La logique reste centralisée côté backend.

Avec Inertia.js, la navigation entre les pages est fluide et ne nécessite aucun rechargement, ce qui améliore l’expérience utilisateur.

Le backend avec Laravel

Laravel propose la méthode paginate() pour fournir une réponse paginée contenant :

  • Les données pour la page en cours.
  • Les métadonnées utiles (total des éléments, page actuelle, liens vers les pages précédentes et suivantes, etc.).

Exemple d’implémentation backend

use App\Models\User;
use Inertia\Inertia;

Route::get('/users', function () {
    $users = User::paginate(10); // Renvoie une collection paginée
    return Inertia::render('Users', ['users' => $users]);
});

La méthode paginate() fournit une réponse paginée structurée, qui sera envoyée au frontend via Inertia.

Le composant Vue.js pour la page des utilisateurs

Voici le fichier complet de la page Users.vue, qui affiche une liste d’utilisateurs et gère la pagination.

<script setup>
import Pagination from './Pagination.vue'; // Importation du composant Pagination

// Récupération des données paginées transmises par le backend via les props d'Inertia
defineProps(['users']);
</script>

<template>
  <div>
    <h1 class="text-xl font-bold mb-4">Liste des Utilisateurs</h1>
    <!-- Liste des utilisateurs -->
    <ul>
      <li
        v-for="user in users.data"
        :key="user.id"
        class="mb-2 p-2 border rounded bg-gray-50"
      >
        {{ user.name }}
      </li>
    </ul>
    <!-- Composant de Pagination -->
    <Pagination :pagination="users" />
  </div>
</template>

Le composant réutilisable de pagination

Voici un composant Vue.js complet pour gérer la pagination, basé sur les données renvoyées par paginate().

<script setup>
import { Link } from "@inertiajs/vue3";
import { computed } from "vue";

const { pagination } = defineProps({
    pagination: {
        type: Object,
        required: true,
        validator: (obj) => {
            return (
                obj.hasOwnProperty("current_page") &&
                obj.hasOwnProperty("total") &&
                obj.hasOwnProperty("per_page")
            );
        },
    },
});

const showingFrom = computed(
    () => (pagination.current_page - 1) * pagination.per_page + 1
);
const showingTo = computed(() =>
    Math.min(pagination.current_page * pagination.per_page, pagination.total)
);
</script>

<template>
    <nav
        v-if="pagination.last_page > 1"
        role="navigation"
        aria-label="Pagination Navigation"
        class="flex items-center justify-between"
    >
        <!-- Version mobile -->
        <div class="flex justify-between flex-1 sm:hidden">
            <Link
                v-if="pagination.prev_page_url"
                :href="pagination.prev_page_url"
                preserve-scroll
                class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300"
            >
                Previous
            </Link>
            <span
                v-else
                class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600"
            >
                Previous
            </span>

            <Link
                v-if="pagination.next_page_url"
                :href="pagination.next_page_url"
                preserve-scroll
                class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300"
            >
                Next
            </Link>
            <span
                v-else
                class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600"
            >
                Next
            </span>
        </div>

        <!-- Version desktop -->
        <div
            class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"
        >
            <div>
                <p class="text-sm text-gray-700 leading-5 dark:text-gray-400">
                    Showing
                    <span class="font-medium">{{ showingFrom }}</span>
                    to
                    <span class="font-medium">{{ showingTo }}</span>
                    of
                    <span class="font-medium">{{ pagination.total }}</span>
                    results
                </p>
            </div>

            <div>
                <span
                    class="relative z-0 inline-flex rtl:flex-row-reverse shadow-sm rounded-md"
                >
                    <!-- Bouton Previous -->
                    <Link
                        v-if="pagination.prev_page_url"
                        :href="pagination.prev_page_url"
                        preserve-scroll
                        class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600"
                    >
                        <svg
                            class="w-5 h-5"
                            fill="currentColor"
                            viewBox="0 0 20 20"
                        >
                            <path
                                fill-rule="evenodd"
                                d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
                                clip-rule="evenodd"
                            />
                        </svg>
                    </Link>
                    <span
                        v-else
                        class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5 dark:bg-gray-800 dark:border-gray-600"
                    >
                        <svg
                            class="w-5 h-5"
                            fill="currentColor"
                            viewBox="0 0 20 20"
                        >
                            <path
                                fill-rule="evenodd"
                                d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
                                clip-rule="evenodd"
                            />
                        </svg>
                    </span>

                    <!-- Numéros de page -->
                    <template
                        v-for="link in pagination.links.slice(1, -1)"
                        :key="link.label"
                    >
                        <span
                            v-if="link.active"
                            class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-blue-600 bg-blue-50 border border-gray-300 cursor-default leading-5 dark:bg-gray-800 dark:border-gray-600"
                            >{{ link.label }}</span
                        >
                        <Link
                            v-else
                            :href="link.url"
                            preserve-scroll
                            class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400"
                            >{{ link.label }}</Link
                        >
                    </template>

                    <!-- Bouton Next -->
                    <Link
                        v-if="pagination.next_page_url"
                        :href="pagination.next_page_url"
                        preserve-scroll
                        class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600"
                    >
                        <svg
                            class="w-5 h-5"
                            fill="currentColor"
                            viewBox="0 0 20 20"
                        >
                            <path
                                fill-rule="evenodd"
                                d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                                clip-rule="evenodd"
                            />
                        </svg>
                    </Link>
                    <span
                        v-else
                        class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5 dark:bg-gray-800 dark:border-gray-600"
                    >
                        <svg
                            class="w-5 h-5"
                            fill="currentColor"
                            viewBox="0 0 20 20"
                        >
                            <path
                                fill-rule="evenodd"
                                d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                                clip-rule="evenodd"
                            />
                        </svg>
                    </span>
                </span>
            </div>
        </div>
    </nav>
</template>

Pourquoi cette structure ?

  1. Propre et réutilisable : Le composant Pagination est autonome et peut être utilisé partout où la pagination est nécessaire.
  2. Centralisation de la logique : Toute la logique de pagination est gérée côté Laravel.
  3. Transparence d’Inertia.js : Avec Inertia, les transitions entre les pages sont fluides et sans rechargement complet.

Cette structure permet de créer une expérience utilisateur agréable tout en maintenant une architecture propre et modulaire.