Laravel Livewire を使って動的テーブル表示を簡単に作る JavaScriptを１行も書かずに

![livewire_demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6384/9127208f-9889-89cc-24cc-357b8b350806.gif)

# Laravel Livewireとは

CALEB PORZIOが作った Laravel用パッケージ
https://github.com/calebporzio/laracasts-livewire-datatable
https://laravel-livewire.com/
動的なテーブル表示を簡単につくれる

2019/11/26 に CALEB PORZIOがLaracastsにチュートリアル動画をアップした。
https://laracasts.com/series/guest-spotlight/episodes/3

 CALEB PORZIO の チュートリアル動画を参考にして Books動的テーブル表示を作ってみました。view blade, Controller, Modelはほぼ彼のチュートリアル動画の内容と同じです。

## CALEB PORZIOのgithubのサンプルはMySQLで動かない
CALEB PORZIOはsqliteで 動作確認をしているが .envのDB_CONNECTIONをmysqlに変更すると動かない。原因はapp/Http/Livewire/ContactsTable.phpがorder by ``のようなSQLを発行してしまうからで13行目にある public \$sortField; を public \$sortField = 'id';などに変更するとMySQLでも動作するようになる。

# Laravel project 

## Table
tablesとauthorsテーブルを用意してseederでbooksを500レコード authorsを3レコード作成。適当なfactoryも作る。 php artisan migrate; php artisan db:seed でデータを用意。

```
class CreateBooksTable extends Migration {
    public function up() {
        Schema::create('books', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');  // ←追加
            $table->unsignedInteger('author_id');  // ←追加
            $table->timestamps();
        });
    }
}
```

```
class CreateAuthorsTable extends Migration {
    public function up() {
        Schema::create('authors', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');  // ←追加
            $table->timestamps();
        });
    }
}
```

```
class BooksTableSeeder extends Seeder {
    public function run()
    {
        factory(\App\Book::class, 500)->create();  // ←追加
    }
}
```

```
class AuthorsTableSeeder extends Seeder {
    public function run()
    {
        factory(\App\Author::class, 3)->create();  // ←追加
    }
}
```

# LivewireでBooks一覧画面を作る
## Livewireのインストール

コンポーザーでインストール

```composer
composer require livewire/livewire
```


## make:livewireでbladeとClassを作る
```
php artisan make:livewire books-table
```

以下のファイルが作られる
CLASS: app/Http/Livewire/BooksTable.php
VIEW:  resources/views/livewire/books-table.blade.php



## routes/web.php
一行だけ追加

```
Route::get('books', 'BooksController');
```

## app/Author.php
booksテーブルとのリレーションだけ追加。

```PHP
class Author extends Model
{
    protected $guarded = [];

    public function books()
    {
        return $this->hasMany(Book::class);
    }
}
```


## app/Book.php
Authorテーブルへのリレーションを追加。
検索メソッドを追加。(CALEB PORZIOのContacts.phpを参考にしました。ほぼ同じです。)

```PHP
class Book extends Model
{
    protected $guarded = [];

    public static function search(string $query)
    {
        $res = empty($query) ? static::query()
            : static::where('title', 'like', '%' . $query . '%');
        return $res;
    }

    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}
```


## app/Http/Controllers/BooksController.php

```PHP
    public function __invoke(Request $request)
    {
        $books = \App\Book::paginate(); // ←この処理使われていない. $booksはBooksTable.phpで取得するから
        return view('books', ['books' => $books]);
    }
```


## app/Http/Livewire/BooksTable.php
CALEB PORZIOのapp/Http/Livewire/ContactsTable.phpとほぼ同じです。booksテーブル用に少しだけ変更しました。

```PHP
class BooksTable extends Component
{
    use withPagination;

    public $perPage = 10;
    public $search = '';
    public $sortField = 'id';
    public $sortAsc = true;

    public function clear() {
        $this->search = '';
    }

    public function sortBy($field) {
        if ($this->sortField === $field) {
            $this->sortAsc = !$this->sortAsc;
        } else {
            $this->sortAsc = true;
        }
        $this->sortField = $field;
    }

    public function render()
    {
        $books = \App\Book::search($this->search)
            ->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')
            ->paginate($this->perPage);
        return view('livewire.books-table', ['books' => $books]);
    }
}
```


## resources/views/books.blade.php

@livewireAssetsを追加して
@livewire('books-table) でbooks-table.blade.phpを読み込みます。



```php+HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Books</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css">
    @livewireAssets
</head>
<body style="padding:8px;">
<h1>Books</h1>
<div class="container pt-4">
    @livewire('books-table')
</div>
</body>
</html>
```


## /resources/views/livewire/books-table.blade.php

テーブル表示のviewを書きます

wire:model、wire:click.preventの設定があるところが app/Http/Livewire/BooksTable.php と連携するところです。

<input wire:model="search" class="form-control" type="text" placeholder="Search books...">の値はBooksTable.phpの$searchと同期します。

* Books用に変更してありますが、CALEB PORZIOの resources/views/livewire/contacts-table.blade.php とほぼ同じです。 CALEB PORZIOのチュートリアル動画を参照してください。

```html
<div>
    <div class="row mb-4">
        <div class="col form-inline">
            Per Page: &nbsp;
            <select wire:model="perPage" class="form-control">
                <option>10</option>
                <option>15</option>
                <option>25</option>
            </select>
        </div>

        <div class="col">
            <input wire:model="search" class="form-control" type="text" placeholder="Search books...">
        </div>
    </div>

    <div class="row">
        <table class="table">
            <thead>
            <tr>
                <th>ID</th>
                <th>
                    <a wire:click.prevent="sortBy('title')" role="button" href="#">
                        Title
                    </a>
                </th>
                <th>
                    <a wire:click.prevent="sortBy('author_id')" role="button" href="#">
                    Author
                    </a>
                </th>
            </tr>
            </thead>
            <tbody>
            @foreach ($books as $book)
                <tr>
                    <td>{{ $book->id }}</td>
                    <td>{{ $book->title }}</td>
                    <td>{{ $book->author['name'] }}</td>
                </tr>
            @endforeach
            </tbody>
        </table>
    </div>

    <div class="row">
        <div class="col">
            {{ $books->links() }}
        </div>

        <div class="col text-right text-muted">
            Showing {{ $books->firstItem() }} to {{ $books->lastItem() }} out of {{ $books->total() }} results
        </div>
    </div>
</div>

```
# 動作確認

```bash
php artisan serve
```
ブラウザで実際に動かしてみる。
すごい！動く！JavaScriptを１行も書かずに！

CALEB PORZIOすごい！



CALEB PORZIOのチュートリアル動画
https://laracasts.com/series/guest-spotlight/episodes/3