Livewire ToDo List with in-place edit and delete

Bootstrap 5 Livewire

Demo

Awesome ToDo List
  • testtes
  • rwrwe
  • Cheese
  • eee
  • eee!!! @ fasfdsagdas
  • wefwefwefwef
  • test of my livewire
  • Mon premier titre
  • One
  • okkk
  • dddddd

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.