LoginSignup
2
5

More than 3 years have passed since last update.

Laravelでほんの少しハイレベルな検索機能を作ってみた。(初心者向け)

Last updated at Posted at 2021-01-29

こんにちは、しろうです。

現在、Laravelで小説サイトを作成しています。

そんな中で、(自分的には)ちょっとだけハイレベルな検索機能を作ってみたので、忘れないようにメモとして残しておきます。

検索機能を実装したい人はぜひ、参考にしてください。

もしミスとかあればコメントして頂けると大変有り難いですm(_ _)m

前置き

・クラス名とかひどいのあるかと思いますが、気にしないで頂けると幸いです。
・Formファサードを使ったり、素のformを使ったり混在していますが、許してくださいm(_ _)m
・cssは貼り付けていないので、皆さん側で好きなように変更してください。

今回作る検索機能

・複数条件での絞り込み検索ができる。(例:キーワードとジャンルで絞り込む。)
・現在の検索条件がタグとして表示される。
・タグを削除すれば、検索条件から削除される。

検索機能のイメージ動画

ちなみにイメージ動作は下記の通りです。

ezgif.com-gif-maker.gif

下記のように、他のページからジャンルとかをクリックしたら、そのジャンル名で検索してくれる機能もつけてます。

ezgif.com-gif-maker (1).gif

さて、じゃあどんどん作っていきます。

とりあえず検索機能を作る

viewファイルの作成(検索項目の部分)

スクリーンショット 2021-01-29 13.53.31.jpg

とりあえず検索項目を作成していきます。

今回は「キーワード検索」と「ジャンル検索」のみを作成しています。
また、CSSは貼り付けないので、皆さんの方で好きなように変更よろしくお願いしますm(_ _)m

search.blade.php
{{ Form::open(['route'=>'search','method'=>'GET','id'=>'side_search_form','autocomplete'=>"off"]) }}
<div class="search_keyword">
    <div class="search">
        <input name="keyword" value="{{request('keyword')}}" type="search" class="searchTerm" placeholder="キーワード検索">
        <button type="submit" class="searchButton" form="side_search_form">
            <i class="fa fa-search fa-xs"></i>
        </button>
    </div>
</div>

<div class="search_genre search_side_item_border">
    <h4>ジャンル</h4>
    <div class="ripples_radio">
        {{ Form::radio('genre','', isset(request()->genre) ? false :true, ['id'=>'search_genre_none'])
        }}
        {{ Form::label('search_genre_none', '指定なし', []) }}
    </div>
    @foreach ($genres as $index => $genre)
    <div class="ripples_radio">
        {{ Form::radio('genre',$genre, $genre == request()->genre ? true :false,
        ['id'=>'search_genre'.$index]) }}
        {{ Form::label('search_genre'.$index, $genre, []) }}
    </div>
    @endforeach
</div>

{{ Form::close() }}

解説していきます。

'autocomplete'=>"off"・・・これをつけると検索履歴が表示されなくなります。お好みでどうぞ。
value="{{request('keyword')}}"・・・このようにするとvalue値が「keywordというパラメーター(クエリ文字列)の値」になります。
requestメソッド・・・リクエストデータを取得できるやつです。

@foreach ($genres as $index => $genre)・・・この$genresはコントローラーから検索できるジャンル名を返しています。単純にジャンル名を1つずつ処理しているだけです。

下記少しわかりにくいかと思います。

{{ Form::radio('genre',$genre, $genre == request()->genre ? true :false,
        ['id'=>'search_genre'.$index]) }}

Form::radioは第三引数がtrueならcheckedをつけるので、ジャンル名とパラメーターのジャンル名(現在検索しているジャンル名)が一致するなら、checkedをつけるようにしています。

例えば、http://127.0.0.1:8000/search?genre=恋愛(現世)とかなら、request()->genreによって、恋愛(現世)という文字列を取得できます。

なので、$genre == request()->genreとすることで、現在検索しているジャンルボタンにのみcheckedをつけることができます。

指定なしというラジオボタンもほしいので、下記コードで作成しています。

{{ Form::radio('genre','', isset(request()->genre) ? false :true, ['id'=>'search_genre_none'])}}
{{ Form::label('search_genre_none', '指定なし', []) }}

value属性''(空文字)にしておけば、コントローラー側の処理でいい感じにできます。(解説は後ほど。)

web.phpにルーティングを追加

とりあえず、なんでもいいのでルーティングを追加します。

web.php
Route::get('search', 'UsersController@search')->name('search');

ジャンル用のファイルを作成

今回はconfig/getValue/radio.phpにジャンル用の配列を作成しました。

config/getValue/radio.php
<?php
return [
    'genre' =>[
        '1' =>'恋愛(異世界)',
        '2'=>'恋愛(現世)',
        '3'=>'ラブコメ',
        '4'=>'ホラー',
        '5'=>'推理(ミステリー小説)',
        '6'=>'異世界ファンタジー',
        '7'=>'現代ファンタジー',
        '8'=>'コメディ',
        '9'=>'SF',
        '10'=>'詩・エッセイ・童話',
        '11'=>'歴史・戦国',
        '12'=>'その他',
    ],
];

このようにすることでconfig('getValue.radio.genre')とすれば、ジャンル名の配列を取得することができます。

コントローラー内でこのジャンル名の配列を取得します。

コントローラーに処理を追加

次にコントローラー側に検索処理を記述していきます。

UsersController.php

public function search(Request $request)
    {
       //SQL文を書くためにqueryメソッドを使う。
        $query = Novel::query();

       //ジャンル名を取得。(viewファイルに返すだけ。)
        $genres = config('getValue.radio.genre');

      //keywordがあるかどうか。
        if ($request->filled('keyword')) {
            //検索キーワードとタイトルが一致するレコードを絞り込む
            $query->where('title', 'like', '%'.$request->get('keyword').'%');
        }

      //genreがあるかどうか。
        if ($request->filled('genre')) {
            //検索ジャンルとジャンル名が一致するレコードのidをgenresテーブルから取得
            $genre_id = Genre::where('name', $request->genre)->first()->id;
            //genre_idが一致するレコードをnovelsテーブルから絞り込む
            $query->where('genre_id', $genre_id);
        }

       //条件に一致するレコードを作成日で降順に並び替えて取得
        $novels = $query->latest('novels.created_at')->paginate(50);

        //viewファイルは好きなファイルにしてください。
        return view("search", compact('novels', 'genres'));
    }

たぶん、ほとんど読めばわかるんじゃないかと思います。

$request->filled('genre')を使えば、リクエストに値が存在して、かつ、空でない場合にtrueを返してくれます。

似たものに$request->has('genre')というのがありますが、これだと空であってもtrueになります。

それだと、下記のような指定なし(value値が空文字列)の場合でも実行されてしまうので、今回はfilledメソッドを使用しています。

{{ Form::radio('genre','', isset(request()->genre) ? false :true, ['id'=>'search_genre_none'])}}
{{ Form::label('search_genre_none', '指定なし', []) }}

ちなみにlatest()を使えば、降順に並び替えてくれます。(下記の通り)


$query->latest('novels.created_at')->paginate(50);

あとは、好きなようにviewファイル側でデザインしてあげれOK。

試しにsearch.blade.phpとかで{{dd($novels)}}とかで中身を確認してみてください。

現在の検索条件を表示する機能の作成

スクリーンショット 2021-01-29 14.04.43.jpg

なんて説明すればいいかわかりませんが、ここからは上記の画像のやつを作っていきます。笑

方法としては、そこまで難しくありませんので、ご安心ください!!!

まずはview側に処理を追加

とりあえず、viewファイルに処理を追加していきます。

下記のようにすれば現在の検索条件を表示することができます。

search.blade.php
<div class="search_condition">
    <ul>
        @if(!empty(request()->keyword))
        <li class="search_condition_item">
            {{request()->keyword }}<a href="keyword" class="search_condition_a"><i class="fas fa-times search_condition_delete"></i></a>
        </li>
        @endif

        @if(!empty(request()->genre))
        <li class="search_condition_item">
            {{request()->genre }}<a href="genre" class="search_condition_a"><i class="fas fa-times search_condition_delete"></i></a>
        </li>
        @endif
    </ul>
</div>

見て大体わかると思いますので、ざっくり説明していきます。

まずempty()は引数が空ならtrueを返すので、!(エクスクラメーション)をつけて、中身が空ではない時にif文内のhtmlが表示されるようにします。

そしてif文の条件を「!empty(request()->keyword)」みたいな感じにすることで、現在keywordで検索をしているのかどうか、がわかります。(このkeywordというのはinputのname属性のことです。)

例えば、http://127.0.0.1:8000/search?keyword=&genre=みたいな感じで、keywordが空になっているなら、処理は実行されません。

逆にhttp://127.0.0.1:8000/search?keyword=人生楽しいね&genre=とかだと、「人生楽しいね」というキーワードで検索されていることになるので、if文内の処理が実行されます。

そして、if文内の処理は下記のようにします。

<li class="search_condition_item">
      {{request()->keyword }}<a href="keyword" class="search_condition_a"><i class="fas fa-times search_condition_delete"></i></a>
</li>

まず{{request()->keyword }}とすることで「人生楽しいね」が表示されます。
でもって、aタグのhref属性keywordと記述して、JavaScriptでこいつを操作していきます。

JavaScriptを記述

ここからはJavaScript(今回はjQuery)の出番です。

なんでもいいので、JavaScritのファイルを作って、下記のように記述してください。

search.js
$(function () {
    $('.search_condition_a').each(function () {
       //href属性を取得
        attr = $(this).attr('href');
       //初期化
        var url = new URL(window.location);
       //パラメーター削除
        url.searchParams.delete(attr);
        if (url.search) {
            $(this).attr('href', 'search' +url.search);
        } else {
            $(this).attr('href', 'search');
        }
    });
});

まず、$('.search_condition_a').each(function () {}とすることで、search_condition_aクラスの要素(aタグの部分)をループさせています。

attr = $(this).attr('href');とすることで、ループされたaタグのhref属性を取得しています。

例えば、下記の場合はkeywordという文字列が取得できます。

<a href="keyword" class="search_condition_a">

次にURLオブジェクトを作成し、searchParamsメソッドdeleteメソッドを使用して、先ほど取得した、href属性の初期値と一致するパラメーターを削除します。

//初期化(URLオブジェクトの作成)
var url = new URL(window.location);
//パラメーター削除
 url.searchParams.delete(attr);

もう少し詳しく解説していきます。

URLオブジェクトとは、URLを作成したり、編集したりするときに使えるメソッドが沢山入っている、JavaScriptが用意してくれている便利なやつです。()

参考:URL()

そして、引数にはwindow.location として、現在開いているURLを与えます。

次にsearchParamsで、URLのパラメーター(URLの?以降の部分)を取得、deleteメソッドで引数に与えたものと一致するパラメーターを削除します。

例えば、attrkeywordという文字列が入っている場合は、url.searchParams.delete(attr);によって、

http://127.0.0.1:8000/search?keyword=人生楽しいね&genre=恋愛(異世界)
からkeywordの部分が削除されるので、
http://127.0.0.1:8000/search?genre=恋愛(異世界)
こんな感じになります。

最後にパラメーターがあるかないかで条件分岐させています。
こうしないと、他のページから単一条件で検索ページに飛んだときに、1つのパラメーターを削除すると、全てのパラメーターがなくなり、期待通りの動作をしてくれなかったからです。(もっと良い方法もあるかもです...)

if (url.search) {
            $(this).attr('href', 'search' +url.search);
        } else {
            $(this).attr('href', 'search');
        }

url.searchとすることで、URLのパラメーターを取得できます。

あとはattrメソッドを使って、href属性の値を変更してあげればOK。

これでタグの部分は完成です。

別ページから条件検索をしたい時

別ページから検索したい時は下記のように、直接href属性にパラメーターを仕込んでおけばOK。

test.blade.php
<a href="{{route('search')}}?genre={{$novel->genre->name}}">{{$novel->genre->name}}</a>

並び替え機能も実装

スクリーンショット 2021-01-29 16.15.34.jpg

ついでに並び替え機能の紹介もしておきます。(「新着順」と「更新順」だけ)

viewファイルに追加

下記のような感じで実装しました。

search.blade.php
<div class="search_sort">
    <button
        class="sort_btn {{strpos(request()->fullUrl(), 'new') !== false  || strpos(request()->fullUrl(), 'sort') === false ? 'sort_active' : ''}}"
        name="sort" value="new" form="side_search_form" type="submit">
        <span class="spot"></span>新着順
    </button>

    <button class="sort_btn {{strpos(request()->fullUrl(), 'update') !== false ? 'sort_active' : ''}}"
        name="sort" value="update" form="side_search_form" type="submit">
        <span class="spot"></span>更新順
    </button>
</div>

順に解説していきます。

まずformタグの外側でボタンを作る時はform属性formタグのid属性を指定します。

下記の2つの部分は現在のURLによってsort_activeクラスを付与するかどうか三項演算子を利用して、決定しています。

{{strpos(request()->fullUrl(), 'new') !== false  || strpos(request()->fullUrl(), 'sort') === false ? 'sort_active' : ''}}

//省略

{{strpos(request()->fullUrl(), 'update') !== false ? 'sort_active' : ''}}

strpos()は文字列の中に指定した文字列があるかどうかを判定するメソッドです。
request()->fullUrl()で現在のURLが取得できます。

コントローラーに処理を追加

最後に下記のように処理を追加・変更します。

SearchController.php
public function search(Request $request){

//省略

  if ($request->filled('sort') && $request->sort === 'new') {
      $query->latest('novels.created_at');
  }elseif ($request->filled('sort') && $request->sort === 'update') {
      $query->latest('novels.updated_at');
  } else {
      $query->latest('novels.created_at');
  }

  //こっちのlatestは消す。
  $novels = $query->paginate(50);

  return view("search", compact('novels', 'genres'));

}

これで並び替え機能も完成です。

簡単簡単。

最後に

これで一応、イメージ動画みたいな感じの検索機能が実装できたかと思います。

昔から検索機能を作るのは苦手なので、少し苦労しました...なんか条件分岐が多いですし疲れますね。(コードのせいかも。)

駆け足だったので、わかりにくい部分があるかも・・・その時はコメントしてくださいませm(_ _)m

たぶん、もう少し仕様変更とかしますが、ひとまずこれにて終了です!お疲れ様でした。

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5