LaravelのページリンクをVueで作る

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、Laravelはバージョンが8.xになった現在でも野心的に新機能を追加ていますが、そういった中でも初めて見た時に「これはすごい!」と思ったのが・・・・・・

ページリンク(パジネーション)

でした。

この機能は、例えば以下のようにpaginate()を使ってDBからデータを取ってくるとします。

$users = \App\Models\User::paginate();

すると、なんと以下のようにするだけで、ページ送りリンク(パジネーション)を表示してくれるんですね。

{{ $users->links() }}

表示例はこちら。

※ こちらはBootstrapを使った場合です。Laravel 8.xBootstrapを使ったページリンクを表示する方法は おまけ をご参照ください。

実はこのページリンク、見た目はシンプルなんですが実は自分でつくるのは結構めんどうだったりするので、とても重宝していました。

しかし、最近私の環境ではVue + Ajaxでデータを取得することが多くなったので、Laravelのページリンクを使うことは減ってきました。

そして、今回のテーマが立ちはだかりました。

VueでもLaravelと同じようなページリンクを作りたい(けっこう切実)

と。

そこで❗

今回はLaravelで取得したデータを使ってVueでも同じようにページリンクを作る方法をご紹介します。ぜひ学習の参考になりましたら嬉しいです😊✨
(最後に今回実際に開発したソースコード一式をダウンロードできますよ👍)

「動画の編集ってめちゃくちゃ
大変なんですね・・・💧」

開発環境: Laravel 8.x、Vue 3、axios 0.19

前提として

テーブルはどこでもいいのですが、以下のように50件程度のデータを用意しておいてください。

もしデータを用意していない場合は、以下の項目を参考にしてダミーのユーザーデータを作っておいてください。

📝 参考記事: 新しいFactoryでテストユーザーをつくってログインしてみる

ルートをつくる

まずは以下2つのルートを追加します。

  • Vue のページリンクを表示するルート
  • Ajax でデータを取得するルート

実際にはこうなります。

routes/web.php

Route::get('vue_pagination', function(){ return view('vue_pagination'); });
Route::get('users', function(){ return \App\Models\User::paginate(5); });

ビューをつくる

続いて上のルートで設定したビュー(テンプレート)を作ります。

resources/views/vue_pagination.blade.php

<html>
<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
</head>
<body>
<div id="app" class="p-3">
    <!-- ⑥ 取得したデータの表示 -->
    <div v-for="user in users.data" v-if="users">
        <div v-text="user.name"></div>
    </div>
    <br>
    <!-- ⑦ 独自コンポーネントを実行する -->
    <v-pagination :data="users" :link-max="7" @page-move="onMovePage"></v-pagination>
</div>
<script src="https://unpkg.com/vue@3.0.2/dist/vue.global.prod.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script>

    // ① ページリンク・コンポーネントを定義する
    const paginationComponent = {
        // ② プロパティを定義
        props: {
            data: {
                type: Object,
                default: {}
            },
            linkMax: {
                type: Number,
                default: 5
            }
        },
        methods: {
            moveTo(page) {

                document.activeElement.blur(); // クリックされたときのフォーカスを外す
                this.$emit('page-move', parseInt(page)); // ③ 独自のイベントを送出

            },
            movePrev() {

                const page = Math.max(this.currentPage - 1, 1); // ⑤-1 1より少ないときは1になる
                this.moveTo(page);

            },
            moveNext() {

                const page = Math.min(this.currentPage + 1, this.lastPage); // ⑤-2 最大ページより大きいときは最大ページになる
                this.moveTo(page);

            },
            classes(page) {

                return (parseInt(page) === this.currentPage) ? 'active' : '';

            }
        },
        computed: {
            hasData() {

                return (Object.keys(this.data).length > 0);

            },
            currentPage() {

                return parseInt(this.data.current_page);

            },
            lastPage() {

                return parseInt(this.data.last_page);

            },
            pages() { // ④ 表示すべきページ番号を計算

                let pages = [];
                const lastPage = this.lastPage;

                for(let i = 1 ; i <= lastPage ; i++) {

                    pages.push(i);

                }

                if(pages.length > this.linkMax) {

                    const pageIndex = this.currentPage - 1;
                    const leftMaxPage = Math.floor(this.linkMax * 0.5);
                    const rightMaxPage = this.linkMax - leftMaxPage;
                    const leftDiff = pageIndex - leftMaxPage;
                    const rightDiff = pageIndex + rightMaxPage - pages.length;
                    let start = (leftDiff >= 0)
                        ? leftDiff - Math.max(0, rightDiff)
                        : 0;
                    const end = start + this.linkMax;
                    pages = pages.slice(start, end);

                }

                return pages;

            }
        },
        template: `
            <div v-if="hasData">
                <ul class="pagination">
                    <li class="page-item">
                        <a class="page-link" href="#" aria-label="Previous" @click.prevent="movePrev()">
                            <span aria-hidden="true">&laquo;</span>
                        </a>
                    </li>
                    <li class="page-item" v-for="p in pages" :class="classes(p)">
                        <a class="page-link" href="#" v-text="p" @click.prevent="moveTo(p)"></a>
                    </li>
                    <li class="page-item">
                        <a class="page-link" href="#" aria-label="Next" @click.prevent="moveNext()">
                            <span aria-hidden="true">&raquo;</span>
                        </a>
                    </li>
                </ul>
            </div>
        `
    };

    Vue.createApp({
        data() {
            return {
                users: {}
            }
        },
        methods: {
            onMovePage(page = 1) { // ⑧ Ajaxでデータを取得する

                const url = '/users?page='+ page;
                axios.get(url)
                    .then(response => {

                        this.users = response.data;

                    });

            }
        },
        mounted() {

            // ⑨ ページ表示後すぐデータを取得
            this.onMovePage();

        }
    })
    .component('v-pagination', paginationComponent) // Vueにコンポーネントをセットする
    .mount('#app');

</script>
</body>
</html>

今回は、ここがメインですのでひとつずつご紹介します。

① ページリンク・コンポーネントを定義する

まず、Vueコンポーネントを定義します。

目的は、以下のようにするだけでLaravelと同じようなページリンクを表示できるようにするためです。(つまり、使い回しができるので次からラクできるというわけですね👍)

<v-pagination :data="data"></v-pagination>

定義したコンポーネントは、以下のようにセットすることで有効になります。

Vue.createApp({
    // 省略
})
.component('v-pagination', paginationComponent) // 👈 Vueにコンポーネントをセットする
.mount('#app');

② プロパティを定義

コンポーネントに以下2つのデータがセットできるようにプロパティを定義します。

  • data: Laravel の pagenate() で取得したデータ
  • linkMax: ページリンクを最大いくつ表示するか ※

※ 例えば、以下は5のとき(初期値は5にしてます)

そして、以下が7のときです。

③ 独自のイベントを送出

独自のイベントpage-moveを送っているのですが、これはページリンクがクリックされたときに実行されれるもので、コンポーネントで指定した@page-move="******"の部分が実行されることになります。

<v-pagination :data="users" @page-move="onMovePage"></v-pagination>

④ 表示すべきページ番号を計算

pages()は、「表示すべきページリンクがどこからどこまでなのか?」を計算する部分になります。

⑤ 次へと前へのリンクがクリックされたときの挙動

この部分で、「ありえないページ番号」の場合は修正をしてからpage-moveイベントを実行するようにしています。

⑥ 取得したデータの表示

ここで、Ajaxで取得したデータの一覧表示をしています。

⑦ 独自コンポーネントを実行する

有効になったコンポーネントを実際に使っている部分です。

コンポーネントの定義でも紹介しましたが、dataの部分にはLaravelpaginate()で取得したデータをセットすることになります。

<v-pagination :data="users" @page-move="onMovePage"></v-pagination>

また、@page-move="*****"の部分は独自に設定したイベント「page-move」が送出されたときにonMovePageが実行されるようにしています。

⑧ Ajaxでデータを取得する

axiosという「Ajax送信のためのJavaScriptパッケージ」を使ってデータ取得しています。取得するのは、ルートとして作った以下の部分です。

Route::get('users', function(){ return \App\Models\User::paginate(); });

なお、onMovePageが呼ばれるのは、ページ表示後すぐと、ページリンクがクリックされた時(つまり、page-moveイベントが送出された時)の2ヵ所です。(そのため、共通化しています)

⑨ ページ表示後すぐデータを取得

mounted()はページが表示されるとすぐに実行されるメソッドなので、この中でデータ取得するようにしています。

テストしてみる

では、実際にテストしてみましょう❗
まずは、「http://******/vue_pagination」にブラウザでアクセスします。

一覧とページリンクが表示されています。
では、「»(次へ)」リンクをクリックしてみましょう。

ページが移動になり、一覧データも変更になっています。
では、次に6ページ目のリンクをクリックしてみましょう。

はい❗

6ページ目が表示されて、さらに、12ページ目のリンクは表示されていません。(最大7件表示なので)

成功です😊✨

ダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードできます。

LaravelのページリンクをVueで作る

※ ただし、テストデータなどはご自身で用意していただく必要があります。

おまけ:Laravel 8.xのページリンクをBootstrapで表示する方法

Laravel 8.xからはメインのCSSフレームワークがTailwindCssに変更になっていますので、長年利用されていたBootstrapではページリンクがうまく表示されなくなっています。

そのため、もしBootstrapをつかってページリンクを表示したい場合はAppServiceProviderに以下の内容を追加してみてください。

app/Providers/AppServiceProvider.php

<?php

// 省略

use Illuminate\Pagination\Paginator; // 👈 ここを追加しました

class AppServiceProvider extends ServiceProvider
{
    // 省略

    public function boot(Charts $charts)
    {
        Paginator::useBootstrap(); // 👈 ここを追加しました
    }
}

おわりに

ということで、今回はLaravelのページリンクをVueでも使えるようにしてみました。

コンポーネントで実装しているので、また別の場所で必要になっても簡単に<v-pagination></v-pagination>タグを使うだけで実装できますよ👍

なお、今回はVue 3を使っていますが、Vue 2でもpaginationComponentの中身はそのままでつかえると思います。

ぜひ皆さんも試してみてくださいね。

ではでは〜❗

「今年は、新しい事業とかも考えよっかな・・・」

開発のご依頼お待ちしております 😊✨ お問い合わせ
また、こちらもお待ちしております。
  • 実案件の開発サポート: 詳細
  • ツイッターのフォロー: 詳細
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly