05. Mengedit Chirp
Mari tambahkan fitur yang tidak ada di platform mikroblog bertema burung populer lainnya — kemampuan untuk mengedit Chirp!
Memperbarui komponen
Mari kita mulai dengan memperbarui komponen Livewire chirps.list yang sudah ada agar memiliki formulir edit untuk Chirp yang sudah ada. Formulir edit ini akan menjadi komponen Livewire bersarang (nested component) yang akan kita buat nanti.
Pertama, kita akan menggunakan komponen x-dropdown yang sudah disertakan dalam Breeze. Selain itu, kita akan membuat dropdown yang hanya akan terlihat oleh penulis asli Chirp tersebut. Di dalam dropdown ini, kita akan menambahkan tautan yang akan memicu aksi edit pada komponen. Metode ini akan menetapkan properti editing ke Chirp yang ingin kita edit. Kita akan menggunakan properti ini untuk menampilkan formulir edit secara kondisional.
Kita juga akan menampilkan indikasi jika sebuah Chirp telah diedit dengan membandingkan tanggal created_at milik Chirp dengan tanggal updated_at-nya:
<?php use App\Models\Chirp; use Illuminate\Database\Eloquent\Collection; use Livewire\Attributes\On; use Livewire\Volt\Component; new class extends Component { public Collection $chirps;+ + public ?Chirp $editing = null; public function mount(): void { $this->getChirps(); } #[On('chirp-created')] public function getChirps(): void { $this->chirps = Chirp::with('user') ->latest() ->get(); }+ + public function edit(Chirp $chirp): void+ {+ $this->editing = $chirp;+ + $this->getChirps();+ } }; ?> <div class="mt-6 bg-white shadow-sm rounded-lg divide-y"> @foreach ($chirps as $chirp) <div class="p-6 flex space-x-2" wire:key="{{ $chirp->id }}"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> <div class="flex-1"> <div class="flex justify-between items-center"> <div> <span class="text-gray-800">{{ $chirp->user->name }}</span> <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small>+ @unless ($chirp->created_at->eq($chirp->updated_at))+ <small class="text-sm text-gray-600"> · {{ __('edited') }}</small>+ @endunless </div>+ @if ($chirp->user->is(auth()->user()))+ <x-dropdown>+ <x-slot name="trigger">+ <button>+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">+ <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />+ </svg>+ </button>+ </x-slot>+ <x-slot name="content">+ <x-dropdown-link wire:click="edit({{ $chirp->id }})">+ {{ __('Edit') }}+ </x-dropdown-link>+ </x-slot>+ </x-dropdown>+ @endif </div>- <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>+ @if ($chirp->is($editing)) + <livewire:chirps.edit :chirp="$chirp" :key="$chirp->id" />+ @else+ <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>+ @endif </div> </div> @endforeach </div>
<?php use App\Models\Chirp; use function Livewire\Volt\{on, state}; $getChirps = fn () => $this->chirps = Chirp::with('user')->latest()->get(); -state(['chirps' => $getChirps]); +state(['chirps' => $getChirps, 'editing' => null]); on(['chirp-created' => $getChirps]);+ +$edit = function (Chirp $chirp) {+ $this->editing = $chirp;+ + $this->getChirps();+}; ?> <div class="mt-6 bg-white shadow-sm rounded-lg divide-y"> @foreach ($chirps as $chirp) <div class="p-6 flex space-x-2" wire:key="{{ $chirp->id }}"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> <div class="flex-1"> <div class="flex justify-between items-center"> <div> <span class="text-gray-800">{{ $chirp->user->name }}</span> <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small>+ @unless ($chirp->created_at->eq($chirp->updated_at))+ <small class="text-sm text-gray-600"> · {{ __('edited') }}</small>+ @endunless </div>+ @if ($chirp->user->is(auth()->user()))+ <x-dropdown>+ <x-slot name="trigger">+ <button>+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">+ <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />+ </svg>+ </button>+ </x-slot>+ <x-slot name="content">+ <x-dropdown-link wire:click="edit({{ $chirp->id }})">+ {{ __('Edit') }}+ </x-dropdown-link>+ </x-slot>+ </x-dropdown>+ @endif </div>- <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>+ @if ($chirp->is($editing)) + <livewire:chirps.edit :chirp="$chirp" :key="$chirp->id" />+ @else+ <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>+ @endif </div> </div> @endforeach </div>
Membuat formulir edit
Selanjutnya, mari kita buat komponen Livewire chirps.edit:
php artisan make:volt chirps/edit --class
php artisan make:volt chirps/edit
Ini akan membuat komponen Livewire baru di resources/views/livewire/chirps/edit.blade.php. Mari perbarui konten komponen Livewire tersebut untuk menampilkan formulir untuk mengedit Chirp.
Perhatikan bahwa, meskipun kita hanya menampilkan tombol edit kepada penulis Chirp, kita juga perlu mengizinkan (authorize) permintaan tersebut di server untuk memastikan bahwa memang penulis Chirp tersebut yang meminta agar Chirp diperbarui:
<?php +use App\Models\Chirp; +use Livewire\Attributes\Validate; use Livewire\Volt\Component; new class extends Component {+ public Chirp $chirp; + + #[Validate('required|string|max:255')]+ public string $message = '';+ + public function mount(): void+ {+ $this->message = $this->chirp->message;+ }+ + public function update(): void+ {+ $this->authorize('update', $this->chirp);+ + $validated = $this->validate();+ + $this->chirp->update($validated);+ + $this->dispatch('chirp-updated');+ }+ + public function cancel(): void+ {+ $this->dispatch('chirp-edit-canceled');+ } }; ?> <div>- // + <form wire:submit="update"> + <textarea+ wire:model="message"+ class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"+ ></textarea>+ + <x-input-error :messages="$errors->get('message')" class="mt-2" />+ <x-primary-button class="mt-4">{{ __('Save') }}</x-primary-button>+ <button class="mt-4" wire:click.prevent="cancel">Cancel</button>+ </form> </div>
<?php -use function Livewire\Volt\{state}; +use function Livewire\Volt\{mount, rules, state}; + +state(['chirp', 'message']);+ +rules(['message' => 'required|string|max:255']);+ +mount(fn () => $this->message = $this->chirp->message);+ +$update = function () {+ $this->authorize('update', $this->chirp);+ + $validated = $this->validate();+ + $this->chirp->update($validated);+ + $this->dispatch('chirp-updated');+};+ +$cancel = fn () => $this->dispatch('chirp-edit-canceled'); ?> <div>- // + <form wire:submit="update"> + <textarea+ wire:model="message"+ class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"+ ></textarea>+ + <x-input-error :messages="$errors->get('message')" class="mt-2" />+ <x-primary-button class="mt-4">{{ __('Save') }}</x-primary-button>+ <button class="mt-4" wire:click.prevent="cancel">Cancel</button>+ </form> </div>
Terakhir, kita perlu memperbarui komponen chirps.list untuk mendengarkan baik event chirp-updated maupun chirp-edit-canceled.
Jika event chirp-updated dikirim, kita perlu memperbarui daftar Chirp. Jika event chirp-edit-canceled dikirim, kita perlu menyetel properti editing ke null sehingga formulir edit tidak lagi ditampilkan:
<?php
use App\Models\Chirp; use Illuminate\Database\Eloquent\Collection; use Livewire\Attributes\On; use Livewire\Volt\Component; new class extends Component {
public Collection $chirps; public ?Chirp $editing = null; public function mount(): void { $this->getChirps(); } #[On('chirp-created')] public function getChirps(): void { $this->chirps = Chirp::with('user') ->latest() ->get(); } public function edit(Chirp $chirp): void { $this->editing = $chirp; $this->getChirps(); }+ + #[On('chirp-edit-canceled')]+ #[On('chirp-updated')] + public function disableEditing(): void+ {+ $this->editing = null;+ + $this->getChirps();+ } }; ?> <div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
@foreach ($chirps as $chirp) <div class="p-6 flex space-x-2" wire:key="{{ $chirp->id }}"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> <div class="flex-1"> <div class="flex justify-between items-center"> <div> <span class="text-gray-800">{{ $chirp->user->name }}</span> <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small> @unless ($chirp->created_at->eq($chirp->updated_at)) <small class="text-sm text-gray-600"> · {{ __('edited') }}</small> @endunless </div> @if ($chirp->user->is(auth()->user())) <x-dropdown> <x-slot name="trigger"> <button> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor"> <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" /> </svg> </button> </x-slot> <x-slot name="content"> <x-dropdown-link wire:click="edit({{ $chirp->id }})"> {{ __('Edit') }} </x-dropdown-link> </x-slot> </x-dropdown> @endif </div> <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p> @if ($chirp->is($editing)) <livewire:chirps.edit :chirp="$chirp" :key="$chirp->id" /> @else <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p> @endif </div> </div> @endforeach </div>
<?php use App\Models\Chirp; use function Livewire\Volt\{on, state}; $getChirps = fn () => $this->chirps = Chirp::with('user')->latest()->get(); +$disableEditing = function () { + $this->editing = null;+ + return $this->getChirps();+}; state(['chirps' => $getChirps, 'editing' => null]); -on(['chirp-created' => $getChirps]); +on([ + 'chirp-created' => $getChirps,+ 'chirp-updated' => $disableEditing,+ 'chirp-edit-canceled' => $disableEditing,+]); $edit = function (Chirp $chirp) { $this->editing = $chirp; $this->getChirps(); }; ?> <div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
@foreach ($chirps as $chirp) <div class="p-6 flex space-x-2" wire:key="{{ $chirp->id }}"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> <div class="flex-1"> <div class="flex justify-between items-center"> <div> <span class="text-gray-800">{{ $chirp->user->name }}</span> <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small> @unless ($chirp->created_at->eq($chirp->updated_at)) <small class="text-sm text-gray-600"> · {{ __('edited') }}</small> @endunless </div> @if ($chirp->user->is(auth()->user())) <x-dropdown> <x-slot name="trigger"> <button> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor"> <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" /> </svg> </button> </x-slot> <x-slot name="content"> <x-dropdown-link wire:click="edit({{ $chirp->id }})"> {{ __('Edit') }} </x-dropdown-link> </x-slot> </x-dropdown> @endif </div> <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p> @if ($chirp->is($editing)) <livewire:chirps.edit :chirp="$chirp" :key="$chirp->id" /> @else <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p> @endif </div> </div> @endforeach </div>
Otorisasi
Secara default, metode authorize akan mencegah semua orang untuk dapat memperbarui Chirp. Kita dapat menentukan siapa yang diizinkan untuk memperbaruinya dengan membuat sebuah Kebijakan Model dengan perintah berikut:
php artisan make:policy ChirpPolicy --model=Chirp
Ini akan membuat kelas kebijakan (policy) di app/Policies/ChirpPolicy.php yang dapat kita perbarui untuk menentukan bahwa hanya penulis yang berwenang untuk memperbarui sebuah Chirp:
<?php
namespace App\Policies; use App\Models\Chirp; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; class ChirpPolicy {
use HandlesAuthorization; /** * Tentukan apakah pengguna dapat melihat model apa saja. */ public function viewAny(User $user): bool { // } /** * Tentukan apakah pengguna dapat melihat model yang ditentukan. */ public function view(User $user, Chirp $chirp): bool { // } /** * Tentukan apakah pengguna dapat membuat model. */ public function create(User $user): bool { // } /** * Menentukan apakah pengguna dapat memperbarui model. */ public function update(User $user, Chirp $chirp): bool {- //+ return $chirp->user()->is($user); }
/** * Menentukan apakah pengguna dapat menghapus model. */ public function delete(User $user, Chirp $chirp): bool { // } /** * Menentukan apakah pengguna dapat merestorasi model. */ public function restore(User $user, Chirp $chirp): bool { // } /** * Menentukan apakah pengguna dapat menghapus model secara permanen. */ public function forceDelete(User $user, Chirp $chirp): bool { // } }
Coba sekarang
Saatnya menguji! Silakan edit beberapa Chirp menggunakan menu dropdown. Jika Anda mendaftarkan akun pengguna lain, Anda akan melihat bahwa hanya penulis Chirp yang dapat mengeditnya.