javascriptを書かずに動的でリアクティブなアプリが作れるlivewireですが、既存のjavascriptやjQueryで行っていた事をlivewireでも実現しようとすると「はて、どうすれば良いだろうか?」と手が止まることがあります。直近でドラッグ&ドロップで並び替え可能なテーブルをlivewireで実装する機会があり調べたので忘れない内にまとめたいと思います。

ドラッグ&ドロップで並び替え可能なテーブル

例えば、以下の様なテーブルです。

従来はjquery-uiのsortable()を使用して対応していました。こちらをlivewireで実装するにはどうすれば良いでしょうか?

livewire-sortable

livewire-sortableというパッケージがあったので試してみます。まずは検証の為のページをlivewireで用意しましょう。livewireはversion 3.xを使用していますのでご注意を。

web.php

まず、ルーティングに/demoへアクセスした際にdemoブレードを返却するように設定します。


use Illuminate\Support\Facades\Route;

Route::get('/demo', function () {
    return view('demo');
});

demo.blade.php

blade側は以下の様に作成しました。デザインの為にbootstrap5をcdnで読み込んでいます。livewireを使用する為、headの閉じタグ直前に@livewireStyles、bodyの閉じタグの直前に@livewireScriptsをセットし、sortable-tableコンポーネントを作成して読み込む事にしました。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    @livewireStyles
  </head>
  <body>

    <div class="container my-5">
      <h1>Sortable Table!</h1>
      <livewire:sortable-table />
    </div>
    @livewireScripts
  </body>
</html>

livewire componentの作成

demo.blade.phpで読み込んでいるComponentを作成しましょう。以下のartisanコマンドを実行して下さい。

% php artisan make:livewire sortable-table
 COMPONENT CREATED  🤙

CLASS: app/Livewire/SortableTable.php
VIEW:  resources/views/livewire/sortable-table.blade.php

SortableTable.phpsortable-table.blade.phpが作成されたはずです。作成された SortableTable.phpを以下のように編集します。プロパティに$usersを追加してmount()で初期化しています。$usersfirst_name, last_name, display_orderプロパティを持つmodelコレクションです。


namespace App\Livewire;

use App\Models\User;
use Livewire\Component;

class SortableTable extends Component
{
    public $users;

    public function mount()
    {
        $this->users = User::all();
    }

    public function render()
    {
        return view('livewire.sortable-table');
    }
}

そして以下がviewファイル。livewireが@foreachのループ内で各要素を識別できるようにwire:keyを指定しています。表示する$userdisplay_order順にソートしています。

<div class="col-lg-8 px-0">
    <table class="table table-hover table-bordered">
        <thead class="table-dark">
          <tr>
            <th>#</th>
            <th>First</th>
            <th>Last</th>
          </tr>
        </thead>
        <tbody>
          @foreach ($users->sortBy('display_order') as $user)
            <tr wire:key="user-{{ $user->id }}">
                <th>{{ $user->display_order }}</th>
                <td>{{ $user->first_name }}</td>
                <td>{{ $user->last_name }}</td>
            </tr>
          @endforeach
        </tbody>
    </table>
</div>

php artisan serverでビルドインサーバを起動して/demoへアクセスしてみましょう。冒頭で表示したシンプルなテーブルが表示されたかと思います。準備が整ったのでlivewire-sortableを使って並び替え出来るように変更してみましょう。

livewire-sortableを追加

今回は簡易的な導入なのでdemo.blade.phpにてCDNで追加します。@livewireScriptsの直後にscriptタグを追加してlivewire-sortableのCDNを読み込みます。livewire-sortableのプラグインより先にlivewireのスクリプトが読み込まれる必要がある為、必ず@livewireScriptsより後にセットして下さい。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    @livewireStyles
  </head>
  <body>

    <div class="container my-5">
      <h1>Sortable Table!</h1>
      <livewire:sortable-table />
    </div>
    @livewireScripts
    {{-- ここに追加↓ --}}
    <script src="https://cdn.jsdelivr.net/gh/livewire/sortable@v1.x.x/dist/livewire-sortable.js"></script>
  </body>
</html>

次にsortable-table.blade.phpを編集して並び替え出来るようにします。Githubには特に詳しい解説はありませんが、wire:sortableを並び替えする要素の親要素に追加、指定する値は並び替えを行った際に実行されるメソッド名です。wire:sortable.itemは並び替える対象の要素に付与します。最後にwire:sortable.handleを並び替え操作(ドラッグ&ドロップ)を行う要素に追加します。

<div class="col-lg-8 px-0">
    <table class="table table-hover table-bordered">
        <thead class="table-dark">
          <tr>
            <th>#</th>
            <th>First</th>
            <th>Last</th>
          </tr>
        </thead>
        <tbody wire:sortable="updateOrder">
          @foreach ($users->sortBy('display_order') as $user)
            <tr wire:sortable.item="{{ $user->id }}" wire:key="{{ $user->id }}">
                <th wire:sortable.handle>{{ $user->display_order }}</th>
                <td>{{ $user->first_name }}</td>
                <td>{{ $user->last_name }}</td>
            </tr>
          @endforeach
        </tbody>
    </table>
</div>

ここまで実装できたら試しに画面を更新して並び替え操作をしてみましょう。#の列をドラッグすると列が並び替えできると思います。しかし、ドロップすると以下の様なエラーが発生します。これはwire:sortableに指定したメソッド、updateOrderが未実装な為です。

ではSortableTable.phpにupdateOrder()を実装してみましょう。以下の例では並び替えた後の順番通りにdisplay_orderの値を更新しています。


namespace App\Livewire;

use App\Models\User;
use Livewire\Component;

class SortableTable extends Component
{
    public $users;

    public function mount()
    {
        $this->users = User::all();
    }

    public function render()
    {
        return view('livewire.sortable-table');
    }

    public function updateOrder($items)
    {
        $items = collect($items)->pluck('order', 'value');

        foreach ($this->users as $user) {
            $newDisplayOrder = (int) $items[$user->id];

            // 順番が変わっているならデータ更新
            if ($user->display_order != $newDisplayOrder) {
                $user->update(['display_order' => $newDisplayOrder]);
            }
        }
    }
}

updateOrder()の引数に$itemsという連想配列が渡されています。$itemsは連想配列で各要素はordervalueという値を持ちます。

orderは並び替え後の順番、valuewire:sortable.itemにて指定した値、つまり$userのidです。実装したupdateOrder()内では最初に$itemsを扱い易いようにidをキー、新しい順番をvalueとするcollectionに変換しています。

$items = collect($items)->pluck('order', 'value');

そして、$usersプロパティをループで回して各userのdisplay_orderが変更されていないかチェック、変更されているならupdate()でDBの値を更新しています(注意:UserのModelクラスにおいて$fillableにdisplay_orderを含むことを忘れずに!)。ページを更新して並び替えを行ってみて下さい。並び替えると同時に#の番号が振り直され、DBに保存されているuserのdisplay_orderが更新されているかと思います。

まとめ

如何でしたでしょうか?ドラッグ&ドロップによる並び替え操作は割とよくあるパターンなのでパッケージが開発されており深く悩む事無く実装できました。javascriptやjQueryなどで対応してきた動的なサイトというのはバリエーションに富んでおり、都合よくlivewireに置き換えられないケースがあるかと思います。そんな時は使えるパッケージが無いか探してみたり、Livewire公式のライブラリであるfluxなどで使えるコンポーネントが無いか探してみると良いかもしれません。

メルマガ購読の申し込みはこちらから。

By hikaru