📑
Laravel10にJetstream LivewireをインストールしてCRUDできるまで
どうも〜つぶあんです。
新規プロジェクトを立ち上げる時用の個人的なメモ書きです。
新規プロジェクト作成(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