📑

Laravel10にJetstream LivewireをインストールしてCRUDできるまで

2023/07/15に公開

どうも〜つぶあんです。
新規プロジェクトを立ち上げる時用の個人的なメモ書きです。

新規プロジェクト作成(todoアプリ)

composer create-project --prefer-dist laravel/laravel myTodo

上記のコマンドで作成されたフォルダからvscodeを立ち上げます。

git管理の設定

ソース管理⇨リポジトリを初期化するをクリック。
とりあえず一旦全てのファイルをコミットすると表示される「Branchの発行」をクリック。
そしてGitHubサイトにて新規リポジトリを追加後、以下のコマンドをvscodeで実行。

git remote add origin https://github.com/XXXXX/myTodo.git
git branch -M main
git push -u origin main

これでGitHubで管理できるようになった。

データベースの設定

今回はデータベースにsqliteを使用する。

.env
-   DB_CONNECTION=mysql
+   DB_CONNECTION=sqlite
-   DB_HOST=127.0.0.1
-   DB_PORT=3306
-   DB_DATABASE=laravel
-   DB_USERNAME=root
-   DB_PASSWORD=

DB_CONNECTIONのみsqliteに変えて、そのほかは削除。
そしてdatabase配下にdatabase.sqliteファイルを作成する。

touch database/database.sqlite

Jetstream + livewireのインストール

composer require laravel/jetstream
php artisan jetstream:install livewire
npm install

マイグレーションの実行

php artisan migrate

Vite起動

npm run dev

サーバー起動

php artisan serve

config/app.phpの設定を変更

-    'timezone' => 'UTC',
+    'timezone' => 'Asia/Tokyo',

-    'locale' => 'en',
+    'locale' => 'ja',

-    'faker_locale' => 'en_US',
+    'faker_locale' => 'ja_JP',

日本語化

composer require laravel-lang/publisher laravel-lang/lang laravel-lang/attributes --dev
php artisan lang:add ja

プロフィール画面

プロフィールアイコンを使えるようにするにはFeatures::profilePhotos()がコメントアウトされているので外す。

config/jetstream.php
    'features' => [
        // Features::termsAndPrivacyPolicy(),
        Features::profilePhotos(),//ここ
        // Features::api(),
        Features::teams(['invitations' => true]),
        Features::accountDeletion(),
    ],

プロファイル画像の保存先

プロフィールアイコンがリンク切れになっているので、修正する

.env
-APP_URL=http://localhost
+APP_URL=http://127.0.0.1:8000

モデル作成 + マイグレーション

php artisan make:model Todo -m

マイグレーションファイル編集

database/migrations/xxxx_xx_xx_xxxxxx_create_todos_table.php
public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->id();
        $table->string('title'); //ここ追加
        $table->text('description'); //ここ追加
        $table->timestamps();
    });
}

モデルの更新

app/Models/Todo.php
    use HasFactory;

    protected $fillable = [
        'title',
        'description',
    ];

マイグレーション実行

php artisan migrate

Livewireコンポーネントの作成

php artisan make:livewire Todos

ルーティング作成

routes/web.php
use App\Http\Livewire\Todos;
Route::get('todos', Todos::class)->name('todo');

コンポーネントの更新

app/Http/Livewire/Todos.php
<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Todo;

class Todos extends Component
{

    public $todos, $todo_id, $title, $description;
    public $isOpen = false;

    /**
     * 一覧表示
     */
    public function render()
    {
        $this->todos = Todo::all();
        return view('livewire.Todos.show');
    }

    /**
     * 新規追加ページ
     */
    public function create()
    {
        $this->resetInputFields();
        $this->openModal();
    }

    /**
     * モーダルオープン
     */
    public function openModal()
    {
        $this->isOpen = true;
    }

    /**
     * モーダルクローズ
     */
    public function closeModal()
    {
        $this->isOpen = false;
    }

    /**
     * リセット処理
     */
    public function resetInputFields()
    {
        $this->title = '';
        $this->description = '';
    }

    /**
     * レコード保存
     */
    public function store()
    {
        $this->validate([
            'title' => 'required',
            'description' => 'required',
        ]);
        Todo::updateOrCreate(
            ['id' => $this->todo_id],
            [
                'title' => $this->title,
                'description' => $this->description,
            ]
        );
        session()->flash(
            'message',
            $this->todo_id ? __('Todo Updated Successfully.') : __('Todo Created Successfully.')
        );
        $this->closeModal();
        $this->resetInputFields();
    }

    /**
     * レコード編集
     */
    public function edit($id)
    {
        $todo = Todo::findOrFail($id);
        $this->todo_id = $todo->id;
        $this->title = $todo->title;
        $this->description = $todo->description;
        $this->openModal();
    }

    /**
     * 削除確認
     */
    public function confirmingTodoDeletion() {
        $this->confirmingTodoDeletion = true;
    }

    /**
     * レコード削除
     */
    public function delete($id)
    {
        Todo::find($id)->delete();
        session()->flash('message', 'Todo Deleted Successfully.');
    }
}

bladeファイルの編集と作成

一覧表示ページ

resources/views/livewire/Todos/show.blade.php
<x-slot name="header">
    <h2 class="font-semibold text-xl text-gray-800 leading-tight">
        {{ __('Todo') }}
    </h2>
</x-slot>
<div class="py-12">
    <div class="max-w-7x1 mx-auto sm:px-6 lg:px-8">
        <div class="bg-white overflow-hidden shadow-x1 sm rounded-lg px-4 py-4">

            @if (session()->has('message'))
                <div class="bg-teal-100 border-lt-4 border teal-500 rounded-b text-teal-900 px-4 py-3 shadow-md my-3"
                    role="alert">
                    <div class="flex">
                        <div>
                            <p class="text-sm">{{ session('message') }}</p>
                        </div>
                    </div>
                </div>
            @endif
            <div class="flex flex-row-reverse">
                <x-button wire:click="create()" class="">{{ __('Add Todo') }}</x-button>
            </div>
            @if ($isOpen)
                @include('livewire.Todos.create')
            @endif

            <table class="table-fixed w-full">
                <thead>
                    <tr class="bg-gray-1000">
                        <th class="px-4 py2 w-20">No.</th>
                        <th class="px-4 py-2">{{ __('Title') }}</th>
                        <th class="px-4 py-2 w-20">{{ __('Description') }}</th>
                        <th class="px-4 py-4 w-40">{{ __('Action') }}</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach ($todos as $todo)
                        <tr>
                            <td class="border px-4 py-2">{{ $todo->id }}</td>
                            <td class="border px-4 py-2">{{ $todo->title }}</td>
                            <td class="border px-4 py-2">{{ $todo->description }}</td>
                            <td class="border py-2">
                                <div class="flex justify-around">
                                    <x-button wire:click="edit({{ $todo->id }})" class="">{{ __('Edit') }}
                                    </x-button>
                                    <x-danger-button wire:click="confirmingTodoDeletion" class="">
                                        {{ __('Delete') }}</x-danger-button>
                                </div>
                                                                <x-confirmation-modal wire:model="confirmingTodoDeletion">
                                    <x-slot name="title">
                                        Delete Post
                                    </x-slot>
                                
                                    <x-slot name="content">
                                        本当にこの投稿を削除しますか?一度削除してしまうと、このデータは永遠に失われてしまいます。
                                    </x-slot>
                                
                                    <x-slot name="footer">
                                        <x-secondary-button wire:click="$toggle('confirmingTodoDeletion')" wire:loading.attr="disabled">
                                            No
                                        </x-secondary-button>
                                
                                        <x-danger-button class="ml-2" wire:click="delete({{ $todo->id }})" wire:loading.attr="disabled">
                                            Yes
                                        </x-danger-button>
                                    </x-slot>
                                </x-confirmation-modal>

                            </td>
                        </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
    </div>
</div>

新規追加ページ

resources/views/livewire/Todos/create.blade.php
<div class="fixed z-10 inset-0 overflow-y-auto ease-out duration-400">
    <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
        <div class="fixed inset-0 tansition-opacity">
            <div class="absolute inset-0 bg-black opacity-80" wire:click="closeModal()"></div>
        </div>
        <span class="hidden sm:inline-block sm:aligh-middle"></span>
        <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
            role="dialog" aria-modal="true" aria-labelledby="modal-headline">
            <form>
                <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
                    <div class="">

                        <div class="mb-4">
                            <x-label for="title" class="mb-2">{{ __('Title') }}</x-label>
                            <x-input type="text" class="w-full" id="title" placeholder="{{ __('Enter title') }}"
                                wire:model="title" />
                            @error('title')
                                <span class="text-red-500">{{ $message }}</span>
                            @enderror
                        </div>

                        <div class="mb-4">
                            <x-label for="description" class="mb-2">{{ __('Description') }}</x-label>
                            <x-input type="text" class="w-full" id="description" placeholder="{{ __('Enter description') }}"
                                wire:model="description" />
                            @error('description')
                                <span class="text-red-500">{{ $message }}</span>
                            @enderror
                        </div>

                    </div>
                </div>
                <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
                    <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
                        <x-button wire:click.prevent="store()" type="button" class="">
                            {{ __('Save') }}
                        </x-button>
                    </span>
                    <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
                        <x-secondary-button wire:click="closeModal()" type="button" class="">
                            {{ __('Cancel') }}
                        </x-secondary-button>
                    </span>
                </div>
            </form>
        </div>
    </div>
</div>

とりあえず以上の作業で初期設定&TodoのCRUDは動作すると思います。
以上つぶあんの備忘録でした。

Discussion