laravel new livewire
composer require livewire/livewire
<livewire:hello-world />
php artisan make:livewire hello-world
<div>
Current time is {{ time() }}
</div>
<div>
Current time is {{ time() }}
<button wire:click="$refresh">Refresh</button>
</div>
php artisan make:livewire counter
<div>
Count: 1
</div>
class Counter extends Component
{
public $count = 1;
public function render()
{
return view('livewire.counter');
}
}
Bu şekilde belirttiğimde artık view dosyasında da bu değişkene bakabiliyorum.
<div>
Count: {{ $count }}
</div>
<div>
Count: {{ $count }}
<button wire:click="increment">+</button>
</div>
Counter.php class dosyasında
public function increment()
{
$this->count++;
}
dediğimizde dinamiklik oluşuyor.
wire:click.window
dediğimizde window level click dinlenir.wire:click.throttle.1000ms
Bu şekilde yaptığımda ne kadar basarsam basayım her saniyede bir click kabul eder.<div>
Count: {{ $count }}
<button wire:click="increment(2)">+</button>
</div>
public function increment($by)
{
$this->count = $this->count + $by;
}
public $todos = [
'Take out trash',
'Do dishes',
];
<div>
<ul>
@foreach($todos as $todo)
<li>{{ $todo }}</li>
@endforeach
</ul>
</div>
wire:model
demen gerekiyor.<input type="text" wire:model="todo"> {{-- most basic --}}
<input type="text" wire:model.live="todo"> {{-- her değişimde network request yapmasını istersen --}}
<input type="text" wire:model.live.debounce.700ms="todo"> {{-- değişim sonrası pasifleşmeye delay --}}
<input type="text" wire:model.live.throttle.5ms="todo">
<input type="text" wire:model.change="todo">
<input type="text" wire:model.blur="todo">
Class
public $todo = '';
public $todos = [
'Take out trash',
'Do dishes',
];
public function add()
{
$this->todos[] = $this->todo;
// $this->todo = '';
$this->reset('todo');
}
View
<form wire:submit="add">
<input type="text" wire:model="todo">
<input type="text" wire:model.live="todo">
<input type="text" wire:model.live.debounce.5ms="todo">
<input type="text" wire:model.live.throttle.5ms="todo">
<input type="text" wire:model.change="todo">
<input type="text" wire:model.blur="todo">
<button type="submit" >Add</button>
</form>
<ul>
@foreach($todos as $todo)
<li>{{ $todo }}</li>
@endforeach
</ul>
public $todos = [];
public function mount()
{
$this->todos = Todo::where('is_done', false)->get();
}
public function updated($property, $value)
{
$this->$property = strtoupper($value);
dd($property, $value);
}
public function updatedTodo($value)
{
// $this->validate();
$this->todo = strtoupper($value);
}
Route::get('/', [VisitorController::class, 'index']);
Sonra bunun Controller'ı var VisitorController
ve onun içinde index
fonksiyonu.
public function index()
{
return view('todo-index');
}
Sonra da todo-index.blade.php dosyası var ve onun içine eklediğimiz todo component'imiz var.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Livewire Öğrenme</title>
</head>
<body>
<livewire:counter />
</body>
</html>
Ve bütün bunlara ek olarak componentin controller logic'ini taşıyan Livewire component class dosyası
) ve onun görüntüsü blade dosyası (counter.blade.php) dosyaları var. Bu noktada Laravel Controller, Laravel Controller function ve Laravel View dosyalarını bypass ederek direkt Livewire componenti page component yapsak ne kaybederiz? Cevap: Hiçbirşey!
Route::get('/', Todo::class);
Route::get('/counter', Counter::class);
php artisan livewire:layout
commandi ile yapabiliriz. (veya kendimiz de views/components/layouts/app.blade.php) dosyasını oluşturabiliriz. Page Component shell'i olan default app.blade.php dosyası şu şekilde:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? 'Page Title' }}</title>
</head>
<body>
{{ $slot }}
</body>
</html>
{{ $slot }}
kısmı componentin oturacağı alan. Bunu istediğim gibi güncelleyebilirim.
#[Title('Counter')]
ile bir attribute belirlemem yeterli.
#[Title('Counter')]
class Counter extends Component
{
Dediğimde sayfada komponent slot'unun dışında $title'a da ekleyebiliyorum.
<head>
<title>@yield('title', 'Default Title')</title>
<meta name="description" content="@yield('meta_description', 'Default description')">
</head>
<body>
{{ $slot }} <!-- Livewire components get injected here -->
</body>
@extends('layouts.app')
@section('title', 'Dynamic Page Title')
@section('meta_description', 'This is a dynamic meta description for SEO.')
<div>
<h1>My Livewire Component</h1>
<p>Content goes here...</p>
</div>
namespace App\Http\Livewire;
use Livewire\Component;
class MyComponent extends Component
{
public $title = "Dynamic Page Title";
public $metaDescription = "This is a dynamic meta description for SEO.";
public function render()
{
return view('livewire.my-component')
->with([
'title' => $this->title,
'metaDescription' => $this->metaDescription,
]);
}
}
bu durumda app.blade.php dosyasında da değişken olarak göstermem gerekir ve duruma göre değişebilir hale gelir.
<head>
<title>{{ $title ?? 'Default Title' }}</title>
<meta name="description" content="{{ $metaDescription ?? 'Default description' }}">
</head>
<body>
{{ $slot }}
</body>
class ShowPosts extends Component
{
public $posts;
public function mount()
{
$this->posts = Post::all();
}
public function render()
{
return view('livewire.show-posts');
}
}
ama bunu yapınca mount anında çektiğin Posts'u hard-coded olarak kabul ediyorsun ve sadece bir property'ye hapsederek onunla çalışıyorsun.
Halbuki senden başkası bir değişiklik yaparsa o zaman bu gerçeği yansıtmıyor oluyor. Direkt property olarak almak yerine computed property olarak alabilirsin veya direkt render içinde view'e ekle:
class ShowPosts extends Component
{
public function render()
{
return view('livewire.show-posts', [
'posts' => Post::all(),
]);
}
}
Böyle olunca livewire her component render ettiği anda DB'den taze olarak Post'ların son halini çeker.
class ShowPosts extends Component
{
public function render()
{
return view('livewire.show-posts', [
'posts' => Post::all(),
]);
}
}
View sayfasında tablo yapıyorum. Ama çok önemli!!! Ne zaman foreach'le birşeyleri listelersem listeleyeyim... mutlaka key vermem lazım.
<table>
<tbody>
@foreach($posts as $post)
<tr wire:key="{{ $post->id }}">
<td>{{ $post->title }}</td>
<td>{{ str($post->content)->words(8) }}</td>
<td><button type="button"
wire:click="delete({{$post->id}})"
wire:confirm="Are you sure you want to delete this post?">
Delete</button>
</td>
</tr>
@endforeach
</tbody>
</table>
Componentte delete fonksiyonunu oluştur.
public function delete(Post $post)
{
$post->delete();
}
GÜVENLİK AÇIĞI: Kullanıcı browser üzerinden deleteId'yi değiştirebilir. Yanlış birşeylerin silinmesini önlemek için normal bir controller'da yaptığın gibi validation yapman lazım. Bu kişi buna yetkili mi???
<form wire:submit="save">
<label>
<span>Title</span>
<input type="text" wire:model="title">
@error('title')<em>{{ $message }}</em>@enderror
</label>
<label>
<span>Content</span>
<textarea wire:model="content"></textarea>
@error('content')<em>{{ $message }}</em>@enderror
</label>
<button type="submit">Save</button>
</form>
class CreatePost extends Component
{
public $title = '';
public $content = '';
public function save()
{
$this->validate([
'title' => 'required',
'content' => 'required',
]);
Post::create([
'title' => $this->title,
'content' => $this->content,
]);
}
}
veya validation'daki validation notlarını property declaration da yapabiliriz. Bu durumda fonksiyon içinde sadece $this->validate()
dememiz yeterli olur.
class CreatePost extends Component
{
#[Rule('required', as: 'Makale başlığı', message: __('myapp.validations.title_required'))]
#[Rule('min:4', message: __('myapp.validations.too_short'))]
public $title = '';
#[Rule('required')]
public $content = '';
public function save()
{
$this->validate();
Post::create([
'title' => $this->title,
'content' => $this->content,
]);
}
}
<script src="//unpkg.com/alpinejs" defer></script>
<div x-data="{ count: 10}">
<span x-text="count"></span>
<button x-on:click="count++">+</button>
</div>
Alpine.js ile Livewire componentin ile direkt bağ kurabilirsin
Current Title: <span x-text="$wire.title"></span>
Livewire componentinin bütün proplarına $wire.
üzerinden erişim sağlayabilirsin.
Current Title: <span x-text="$wire.title.toUpperCase() + ' Title'"></span>
Başka interaction türleri
<button x-text="$wire.title = ''">Clear Title</span>
Hatta Livewire componentindeki metodları bile çağırabilirsin.
<button x-text="$wire.save()">Save Form</span>
<textarea wire:model="content"></textarea>
<small>Characters: <span x-text="$wire.content.length"></span></small>