Laravelで検索機能にも対応した無限スクロールを自作する方法

Laravelで検索機能にも対応した無限スクロールを自作する方法

5 回閲覧されました

みなさんこんにちは、jonioです。

無限スクロールを実装したくてネットを検索すると私が探した限りではライブラリを使った実装しか見つかりません。

ライブラリを使うとカスタムがやりにくいので自作したかったのですが記事を見つけることができなかったので自作しました。

自作したい人がきっといるはずなのでこの記事を残します。

Laravelのバージョンとフロントエンドの言語

10で動作確認していますがコントローラーのコードを見る限り10より低いバージョンにも対応している気がします。

フロントエンドの言語はjQueryです。

テーブルとカラム

使用するテーブルは記事に対するテーブルで名称をpostsとします。

使用するカラムはtitleでtitleカラムに対して検索キーワードで検索できるようにします。

ルーティング

web.phpのコードを下記にします。

Route::controller(DemoController::class)->group(function () {
    Route::get('/top', 'index')->name('top.index');

    Route::get('/top/infinity-scroll', 'infinityScroll')->name('top.infinity-scroll');
});

infinityScrollアクションは無限スクロール用です。

モデルとアクション

Post.phpのコードを下記にします。

public static function modelPost() {
    return Post::whereHas('category', function($query) {
        $query->where('name', 'デモ');
    });
}

コントローラーのコードを下記にします。

public function index(Request $request)
{
    $keyword = $request->input('keyword');

    if (!empty($keyword)) {
        $demo_posts = modelPost::studyPost()->where(function($query) use ($keyword) {
            $query->where('title', 'LIKE', "%{$keyword}%");
        })->paginate(1);
    } else {
        $demo_posts = Post::studyPost()->paginate(1);
    }

    return view('top.index', compact('demo_posts', 'keyword'));
}

5行目〜12行目は検索した場合としてない場合の記事の表示で5〜9行目は検索をした場合で9行目〜11行目は検索をしていない場合です。

テンプレート

テンプレートのコードを下記にします。

<div class="d-flex align-items-center mt-5">
    <p class="mb-2">記事の検索</p>
</div>
<form action="{{ route('top.index') }}" method="GET">
    <input type="text" name="keyword">
    <input type="submit" value="検索">
</form>
@if (!empty($keyword))
    <h2 class="mt-5 mb-2 d-inline-block"><span>{{ $keyword }}</span>の検索結果</h2>
    <a class="d-i-block ml-2" href="{{ route('top.index') }}">検索をやり直す</a>
@endif
<div class="d-flex flex-wrap mt-4 study-wrap">
    @forelse($sdemo_posts as $post)
        <a class="post-wrap" href="">
            <h3 class="post-title">{{ $post->title }}</h3>
        </a>
    @empty
        記事がまだありません
    @endforelse
</div>
<div id="loading" style="text-align:center; display:none;">
    <p>読み込み中...</p>
</div>

無限スクロール用のアクションとjQuery

無限スクロール用のアクションのコードを下記にします。

public function infinityScroll(Request $request)
{
    $page = $request->input('page', 1);

    $keyword = $request->input('keyword');

    $query = Post::studyPost();

    if (!empty($keyword)) {
        $query->where(function($q) use ($keyword) {
            $q->where('title', 'LIKE', "%{$keyword}%")->orWhere('content', 'LIKE', "%{$keyword}%");
        });
    }

    $per_page = 1;

    $posts = $query->paginate($per_page, ['*'], 'page', $page);

    return response()->json([
        'data' => $posts->items(),
        'next_page' => $posts->nextPageUrl(),
    ]);
}

3行目はページネーションの番号で最初の値が1で1づつ増えていきます。

例えば下記のURLの3の部分の数値です。

http://localhost/top/infinity-scroll?page=3

17行目の「[‘*’]」は取得するカラムを指定しますが「*」と記述した場合は該当するテーブル(今回だとpostsテーブル)の全てのカラムを取得します。

特定のカラムに絞りたい場合は例えばidカラム・titleカラム・contentカラムに絞る場合「[‘id’, ‘title’, ‘content’]」と記述します。

「page」は無限スクロールをした時のURLに含めるパラメーターです。

例えば下記のURLのpageの部分です。

http://localhost/top/infinity-scroll?page=3

20行目の「items()」は現在のページ(1ページ目・2ページ目とか)のデータを配列の形式で取得するメソッドです。

下記の例は1ページで3つのデータを取得する場合です。

[
    {"id": 1, "title": "Post 1"},    
    {"id": 2, "title": "Post 2"},
    {"id": 3, "title": "Post 3"}
]

21行目の「nextPageUrl()」は現在のページの次のページにURLを返すメソッドです。

例えば現在のページが1の場合の「$posts->nextPageUrl()」の出力結果は下記になります。

http://localhost/top/infinity-scroll?page=2

「page=2」の2は現在のページの次のページ番号が2だからです。

次はjQueryです。

jQueryのコードを下記にします。

const $loading = $("#loading");

let next_page_url = "{{ route('top.infinity-scroll', ['page' => 2]) }}";

const keyword = "{{ $keyword ?? '' }}";

if (keyword) {
    next_page_url = next_page_url + "&keyword=" + encodeURIComponent(keyword);
}

let loading_flag = false;

$(window).on("scroll", function () {
    if ($(window).scrollTop() + $(window).height() >= $(document).height() - 200) {
        if (next_page_url && !loading_flag) {
            loading_flag = true;

            $("#loading").show();

            $.ajax({
                url: next_page_url,
                method: "GET",
                success: function (response) {
                    if (response.data.length > 0) {
                        response.data.forEach(post => {
                            $(".study-wrap").append(`
                                <a class="post-wrap" href="">
                                    <h3 class="post-title">${post.title}</h3>
                                </a>
                            `);
                        });
                        next_page_url = response.next_page ? response.next_page + (keyword ? `&keyword=${encodeURIComponent(keyword)}` : '') : null;
                    } else {
                        next_page_url = null;
                    }

                    $("#loading").hide();

                    loading_flag = false;
                },
                error: function () {
                    $("#loading").hide();
                    
                    loading_flag = false;
                }
            });
        }
    }
});

8行目は検索した時の無限スクロール時のURLです。

URLに検索キーワードを含めています。

例えば下記でキーワードは「私」で検索しています。

http://localhost/top/infinity-scroll?page=2&keyword=%E7%A7%81

「私」がありませんが上記の「%E7%A7%81」の部分に含まれています。

「encodeURIComponent」は検索キーワードに「=」や「&」などの特殊文字を含んでいても不具合が出ないようにエスケープする為に記述していてそれの影響でこういう表示になっています。

32行目のnullは検索してデータが見つからなかった場合で34行目のnullは検索で見つかったデータを全て表示してもうデータがない場合です。

これで完成です。