Laravel + jQueryでサイト内お気に入り機能を実装してみた

# 概要

今回は`Ajax`を利用したサイト内お気に入り機能の実装方法を紹介します

![スクリーンショット 2022-02-03 0.15.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/9fb45609-f1b4-4fec-c70e-a0340103ea6c.png)

# お気に入り登録画面作成

```php:index.blade.php
<meta name="csrf-token" content="{{ csrf_token() }}">
<li>
    <div class="mb-3">
        <label class="form-label">
            <span class="icon_left">
                <i class="fas fa-search"></i>
                <a data-toggle="collapse" href="#collapseFavorite"><small>お気に入りから検索</small></a>
            </span>
        </label>
        <label class="collapse" id="collapseFavorite">
            <input type="checkbox" name="is_search_favorite"
                   value="1" {{$query->get('is_search_favorite') == 1 ? 'checked':null}}>
        </label>
    </div>
</li>
<div class="row">
    <div class="col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6 mb-3">
        @php
            if (\Illuminate\Support\Facades\Session::has('favorite')) {
                $itemIds = array_filter(!is_null((\Illuminate\Support\Facades\Session::get('favorite'))) ? explode(',', (\Illuminate\Support\Facades\Session::get('favorite'))) : []);
            }
            else{
                $itemIds = [];
            }
        @endphp
        <button type="button"
                class="btn-favorite-icon btn btn-lg border border-secondary mb-3 ml-2 btn-block"
                style="background-color: #fefcf6" id="favorite_icon"
                data-id="{{$item->id}}">
            @if(!empty($itemIds) && in_array($item->id,$itemIds))
                <i class="fas fa-star icon-color" id="favorite-icon"></i>
            @else
                <i class="far fa-star" id="favorite-icon"></i>
            @endif
            <small>お気に入り</small>
        </button>
    </div>
</div>
```

## 解説

- POSTパラメーターとしてCSRFトークンを確認するためにトークンを保存します

https://readouble.com/laravel/8.x/ja/csrf.html

```php
<meta name="csrf-token" content="{{ csrf_token() }}">
```

- セッションから現在お気に入りに登録している商品IDを取得します

- `array_filter` :配列内に空文字列が入る場合等を考慮して削除します
- `explode` : 配列を文字列に変換したものをセッションに保存しているので配列に変換する


```php
@php
    if (\Illuminate\Support\Facades\Session::has('favorite')) {
        $itemIds = array_filter(!is_null((\Illuminate\Support\Facades\Session::get('favorite'))) ? explode(',', (\Illuminate\Support\Facades\Session::get('favorite'))) : []);
    }
    else{
        $itemIds = [];
    }
@endphp
```

- セッションにお気に入り情報が存在していて、商品IDが含まれている場合は、アイコンを変更します

```php
@if(!empty($itemIds) && in_array($item->id,$itemIds))
    <i class="fas fa-star icon-color" id="favorite-icon"></i>
@else
    <i class="far fa-star" id="favorite-icon"></i>
@endif
```

# ルーティングを定義します

https://readouble.com/laravel/8.x/ja/routing.html

```php
<?php

use Illuminate\Support\Facades\Route;

Route::post('/set_session/favorite', 'IndexControll@setSession');
```

# お気に入りボタンをクリックした後の処理を追加

```js:index.js
$('[id=favorite_icon]').on('click', function () {
    const id = $(this).data('id');
    const icon = $(this).children('i');
    $.ajaxSetup({
        headers: {
            "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
        },
    });

    $.ajax(
        {
            url: "/set_session/favorite",
            type: "POST",
            data: {
                "item_id": id
            },
            dataType: 'json',
            complete: function (res) {
                if (res.responseJSON.status) {
                    const ids = (res.responseJSON.ids).split(',');

                    if (jQuery.inArray(String(id), ids) !== -1) {
                        icon.removeClass('far fa-star');
                        icon.addClass('fas fa-star icon-color');
                    } else {
                        icon.removeClass('fas fa-star icon-color');
                        icon.addClass('far fa-star');
                    }
                } else {
                    alert('予期せぬエラーが発生しました。');
                }
            }
        }
    )
});
```

##　解説

- 同じIDを複数持つ要素全てにクリックイベントを発火させます

```js
$('[id=favorite_icon]').on('click', function () {}
```

``data-id="{{$item->id}}"`で指定している商品IDを`data`属性から取得します

```js
const id = $(this).data('id');
```

- CSRFトークンをヘッダー情報に設定します

https://readouble.com/laravel/8.x/ja/csrf.html

```js
$.ajaxSetup({
    headers: {
        "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("conte

https://www.flatflag.nir87.com/split-2178

```js
const ids = (res.responseJSON.ids).split(',');
```

- `jQuery.inArray`で配列内にアイテムIDが存在する場合にクラス名を変更します

- `inArray`の戻り値が見つからない場合は`-1`を返す仕様上以下条件で分岐します
- `String(id)`で配列内の要素が`String`型のため型を合わせるために`INT型`から変換します

```js
if (jQuery.inArray(String(id), ids) !== -1) {
    icon.removeClass('far fa-star');
    icon.addClass('fas fa-star icon-color');
} else {
    icon.removeClass('fas fa-star icon-color');
    icon.addClass('far fa-star');
}
```

# お気に入り登録のロジックを構築

```php:IndexControll.php
<?php

namespace App\Http\Controllers;

class IndexController extends Controller
{
   /**
     * お気に入りをセッションに保持する
     * @param Request $request
     * @return JsonResponse
     */
    public function setSession(Request $request): JsonResponse
    {
        try {
            $itemId = $request->input('item_id');
            $offerIds = null;
	    
            // セッションに存在する場合は保持した値を配列に変換する
            if (Session::has('favorite')) {
                $itemIds = !is_null(Session::get('favorite')) ? explode(',', Session::get('favorite')) : [];
            }
	    
	    // お気に入りに1件もなければとりあえず保存する
            if (empty($itemIds)) {
                $itemIds[] = $itemId;
            }

            // お気に入り一覧に存在しない場合は追加・存在する場合は削除
            if (!in_array($itemId, $itemIds)) {
                $itemIds[] = $itemId;
            } else {
                $index = array_search($itemId, $itemIds);
                unset($itemIds[$index]);
            }

            //　セッション更新
            Session::forget('favorite');
            Session::put(['favorite' => implode(',', $itemIds)]);

            return response()->json([
                'status' => true,
                'ids' => Session::get('favorite'),
                'id' => $itemId,
                'message' => 'success',
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'status' => false,
                'message' => $e->getMessage(),
            ]);
        }
    }
}
```

以下のような動きになります
またセッションに保存しているのでページを再レンダリングした場合も保持できます

![video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/b13a656b-13b5-0309-8391-b34412f29202.gif)




# まとめ

今回はサイト内お気に入り機能についてまとめました
いいねしていただけると記事執筆の励みになりますので、参考になったと思った方は是非よろしくお願いします！