Livewire ToDo List with in-place edit and delete
Bootstrap 5 Livewire
Demo
-
Cumque laboriosam et autem ducimus.
-
Voluptatem rerum aliquam amet laborum.
-
Omnis mollitia molestiae numquam quas dolorem.
-
Iusto exercitationem et ad beatae alias aut.
-
Corporis aspernatur et debitis rerum ipsa totam.
This is a pro component. Get Livewiredemos pro access to view / download the component code.
Component Class
<?php
namespace App\Http\Livewire;
use Carbon\Carbon;
use Livewire\Component;
use App\Models\DummyTask;
class ToDoList extends Component
{
public string $taskTitle = '';
public ?DummyTask $editing;
//public $tasks;
protected $rules=[
'taskTitle' => 'required|max:255',
'editing.title' => 'required|string|min:6',
];
public function mount()
{
//$this->tasks = DummyTask::orderBy('created_at','desc')->get();
}
public function render()
{
return view('livewire.to-do-list', [
'tasks' => DummyTask::orderBy('completed_at', 'asc')->orderBy('created_at','desc')->get()
]);
}
public function addTask(){
$this->validateOnly('taskTitle');
DummyTask::create([
'title' => $this->taskTitle
]);
$this->reset('taskTitle');
}
public function updateTaskStatus(DummyTask $task){
$task->completed_at = isset($task->completed_at) ? null : Carbon::now();
$task->save();
}
public function deleteTask(DummyTask $task){
$task->delete();
}
public function selectTaskForEdit(DummyTask $task){
$this->editing = $task;
}
public function cancelEdit(){
$this->editing = null;
}
public function updateTask(){
$this->validateOnly('editing.title');
$this->editing->save();
$this->editing = null;
}
}
Component View File
<div>
<div class="row justify-content-center my-3">
<div class="col-md-10 bg-white border shadow py-3 px-5">
<div class="fw-bold fs-5 text-indigo my-3">Aswesome ToDo List</div>
<div class="row">
<div class="col-md-12">
<div class="input-group mb-3">
<input type="text" class="form-control py-2 @error('taskTitle') is-invalid @enderror" wire:keydown.enter="addTask" placeholder="Whats needs to be done?" aria-label="taskTitle" wire:model="taskTitle">
<button class="btn bg-indigo text-white px-3 float-end" wire:click="addTask">Add</button>
@error('taskTitle')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<ul class="list-group list-group-flush">
@foreach($tasks as $task)
<li class="list-group-item" x-show="!editing" :wire:key="'task-details-'.$task->id">
<div class="d-flex justify-content-between align-items-end">
@if(isset($editing) && $editing->id == $task->id)
<div class="form-group flex-fill">
<input wire:key="editField" type="text" wire:keydown.enter="updateTask" class="form-control @error('editing.title') is-invalid @enderror" wire:model="editing.title"/>
@error('editing.title')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<div class="ms-3">
<i class="fas fa-save text-success cursor-pointer me-1" wire:click="updateTask()"></i>
<i class="fas fa-window-close text-secondary cursor-pointer" wire:click="cancelEdit()"></i>
</div>
@else
<div>
<input class="form-check-input me-1 p-2 align-sub" type="checkbox" {{isset($task->completed_at) ? 'checked' : ''}} value="1" wire:click="updateTaskStatus({{ $task->id }})">
<span class="{{isset($task->completed_at) ? 'text-decoration-line-through' : ''}}">{{$task->title}}</span>
</div>
<div>
<i class="fas fa-edit text-orange cursor-pointer me-1" wire:click="selectTaskForEdit({{ $task->id }})"></i>
<i class="fas fa-trash-alt text-danger cursor-pointer" wire:click="deleteTask({{ $task->id }})"></i>
</div>
@endif
</div>
</li>
@endforeach
</ul>
</div>
</div>
</div>
</div>
</div>
Usage
@livewire('to-do-list')
Documentation
Let's go through the ToDo List Component step by step.
Rendering Component
Rendering the component if strightforward, along with the component's view file we send all the available tasks to the view page
public function render()
{
return view('livewire.to-do-list', [
'tasks' => DummyTask::orderBy('completed_at', 'asc')->orderBy('created_at','desc')->get()
]);
}
In the view file we loop over the tasks variable and display the task title along with edit and delete button for each of them
@foreach($tasks as $task)
..
.
@endforeach
Adding new Task
To add a new task we have defined a new variable inside the Component named $taskTitle
Inside the view file we have binded this property to an input field using wire:model
directive
Notice that we have defined two actions one on enter keydown on input field and another on pressing the add button. They both call the addTask
method.
public function addTask(){
$this->validateOnly('taskTitle');
DummyTask::create([
'title' => $this->taskTitle
]);
$this->reset('taskTitle');
}
Deleting and Marking the Task Complete
These two actions are simple, we call the component method to delete and update the database fields on user action on the view file.
Editing the task
To achieve in place edit we have defined a new field in the component named public ?DummyTask $editing;
When the user clicks on the edit button, we simply assign the associated task to the editing field.
public function selectTaskForEdit(DummyTask $task){
$this->editing = $task;
}
In the view file, we show the input field in place of task title if the editing task id matches the id in the loop. Also we have used model property binding directly to bind the input field to the editing task. editing.title
We call the update method, once user clicks on the save button.
That's about it.
Download Code
Download the Component class, view file and other required files
This is a pro component. Get Livewiredemos pro access to view / download the component code.