To-Do List App con Laravel Jetstream y Livewire.

Adrian Galicia • December 6, 2020

Introducción

En esta ocasión vamos a desarrollar una lista de tareas haciendo uso del siguiente stack:

Generando nuestro proyecto

El primer paso para empezar a desarrollar nuestra aplicación es instalar Laravel Jetstream eligiendo el stack Livewire

Drag Racing

Generamos el modelo y la migración

php artisan make:model TodoItem -m 

El comando anterior nos ayuda a generar el Modelo TodoItem y la migración create_todo_items_table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTodoItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todo_items', function (Blueprint $table) {
            $table->id();
            $table->string('description');
            $table->boolean('done')->default(false);
            $table->unsignedBigInteger('user_id');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('todo_items');
    }
}
php artisan migrate

Creando una nueva sección en nuestro dashboard

Vamos a copiarnos el contenido de la vista inicial que viene con Laravel Jetstream a la vista que utilizaremos para nuestra lista de tareas

cp resources/views/dashboard.blade.php resources/views/todo.blade.php

El siguiente paso es crear nuestros componentes Livewire

php artisan make:livewire todo-list.form

El comando anterior genera los siguientes archivos:

php artisan make:livewire todo-list.show

El comando anterior genera los siguientes archivos:

Ahora vamos a agregar nuestra ruta para poder acceder a la sección Mi lista de tareas en el archivo routes/web.php

# routes/web.php
...
Route::middleware(['auth:sanctum', 'verified'])->get('/todo', function () {
    return view('todo');
})->name('todo');
...

Posteriormente vamos a modificar nuestra barra de navegación agregando la entrada Mi lista de tareas en el archivo resources/views/navigation-dropdown.blade.php

# resources/views/navigation-dropdown.blade.php

   <!-- Navigation Links -->
                <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                    <x-jet-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
                        {{ __('Dashboard') }}
                    </x-jet-nav-link>

                    <x-jet-nav-link href="{{ route('todo') }}" :active="request()->routeIs('todo')">
                        {{ __('Mi lista de tareas') }}
                    </x-jet-nav-link>
                </div>

Ahora toca customizar nuestra vista todo en la que se van a renderizar los componentes que creamos .

# resources/views/todo.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Mi lista de tareas') }}
        </h2>
    </x-slot>

    <div>
        <div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
            @livewire('todo-list.form')
        </div>
    </div>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                @livewire('todo-list.show')
            </div>
        </div>
    </div>
</x-app-layout>

Desarrollando el formulario para crear tareas

Para crear tareas vamos a desarrollar un formulario, vamos a iniciar con el frontend editando el archivo resources/views/livewire/todo-list/form.blade.php, en seguida les dejo el código que vamos a utilizar para este propósito

# resources/views/livewire/todo-list/form.blade.php

<x-jet-form-section submit="createTodoItem">
    <x-slot name="title">
        {{ __('Crear nueva tarea') }}
    </x-slot>

    <x-slot name="description">
        {{ __('Crea una nueva tarea en la lista de tareas') }}
    </x-slot>

    <x-slot name="form">
        <div class="col-span-6 sm:col-span-4">
            <x-jet-label for="todoItem.description" value="{{ __('Descripción de la tarea') }}" />
            <x-jet-input id="todoItem.description" type="text" class="mt-1 block w-full" wire:model.defer="todoItem.description" autocomplete="description" />
            <x-jet-input-error for="todoItem.description" class="mt-2" />
        </div>
    </x-slot>

    <x-slot name="actions">
        <x-jet-action-message class="mr-3" on="todoItemCreated">
            {{ __('Guardado') }}
        </x-jet-action-message>

        <x-jet-button>
            {{ __('Guardar') }}
        </x-jet-button>
    </x-slot>
</x-jet-form-section>

Ahora tenemos listo nuestro formulario pero por ahora no responde a ninguna acción, para que nuestro formulario esté completo vamos a escribir las acciones en nuestro archivo app/Http/Livewire/TodoList/Form.php en seguida les dejo el código

# app/Http/Livewire/TodoList/Form.php

<?php

namespace App\Http\Livewire\TodoList;

use App\Models\TodoItem;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Form extends Component
{
    public TodoItem $todoItem;

    protected array $rules = [
        'todoItem.description' => 'required|min:6',
    ];

    public function mount()
    {
        $this->todoItem = app(TodoItem::class);
    }

    public function render()
    {
        return view('livewire.todo-list.form');
    }

    public function createTodoItem(): void
    {
        $this->validate();
        $this->todoItem->user_id = Auth::user()->id;
        $this->todoItem->save();
        $this->emit('todoItemCreated');
    }
}

Con esto queda completo nuestro formulario pero aún no podemos ver las tareas que hemos creado hasta el momento, en la siguiente sección veremos el código de nuestro componente encargado de mostrar nuestra lista de tareas.

Listado las tareas

Para mostrar nuestra lista de tareas lo haremos en nuestro archivo app/Http/Livewire/TodoList/Show.php, en seguida les dejo el código que escribí para este propósito

# app/Http/Livewire/TodoList/Show.ph

<?php

namespace App\Http\Livewire\TodoList;

use App\Models\TodoItem;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Show extends Component
{
    public Collection $todoItems;

    protected $listeners = ['todoItemCreated'];

    public function render()
    {
        $this->todoItems = TodoItem::where('user_id', Auth::user()->id)->orderBy('done')->get();
        return view('livewire.todo-list.show');
    }

    public function todoItemCreated():void
    {
        $this->render();
    }
}

Ahora el archivo que vamos a editar es resources/views/livewire/todo-list/show.blade.php que será el encargado de renderizar nuestra lista de tareas en el frontend

# resources/views/livewire/todo-list/show.blade.php

<div>
    <table class="table-auto w-full">
        <thead class="py-2 px-3 sticky top-0 border-b border-gray-200 bg-gray-200">
        <tr>
            <th class="px-4 py-2 w-1"></th>
            <th class="px-4 py-2 text-left">{{__('Descripción')}}</th>
            <th class="px-4 py-2 w-1"></th>
        </tr>
        </thead>
        <tbody>
        @forelse ($todoItems as $todoItem)
            <tr @if($loop->even)class="bg-grey-200"@endif>
                <td class="border-dashed border-t border-gray-200  px-4 py-2">
                    <label
                        class="text-teal-500 inline-flex justify-between items-center hover:bg-gray-200 px-2 py-2 rounded-lg cursor-pointer">
                        <input type="checkbox" class="form-checkbox rowCheckbox focus:outline-none focus:shadow-outline"
                            wire:click="toggleState({{$todoItem->id}})" {{ $todoItem->done ? 'checked' : '' }}>
                    </label>
                </td>
                <td class="border-dashed border-t border-gray-200  px-4 py-2">
                    <p class="{{ $todoItem->done ? 'line-through' : '' }}">{{ $todoItem->description }}</p>
                </td>
                <td class="border-dashed border-t border-gray-200  px-4 py-2">
                    <div wire:click="deleteTodoItem({{ $todoItem->id }})">
                        <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather stroke-current text-red-600 feather-x cursor-pointer hover:text-red-400 rounded-full w-5 h-5 ml-2">
                                <line x1="18" y1="6" x2="6" y2="18"></line>
                                <line x1="6" y1="6" x2="18" y2="18"></line>
                        </svg>
                    </div>
                </td>
                </td>
            </tr>
        @empty
             <tr>
                 <td colspan="3" class="text-center">
                    {{ __('Aún no has creado tareas') }}
                 </td>
             </tr>
        @endforelse
        </tbody>
    </table>
</div>

Listo ahora podemos ver nuestra lista de tareas, veremos que existe un checkbox para completar la tarea y un botón que es para borrar pero aún falta escribir el código de la función Actualizar status y Borrar

Creando las acciones para actualizar y borrar tarea

Vamos a agregar las acciones actualizar y borrar tarea, para esto tenemos que actualizar nuestro archivo app/Http/Livewire/TodoList/Show.php en seguida les dejo el código

# app/Http/Livewire/TodoList/Show.php

<?php

namespace App\Http\Livewire\TodoList;

use App\Models\TodoItem;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Show extends Component
{
    public Collection $todoItems;

    protected $listeners = ['todoItemCreated'];

    public function render()
    {
        $this->todoItems = TodoItem::where('user_id', Auth::user()->id)->orderBy('done')->get();
        return view('livewire.todo-list.show');
    }

    public function todoItemCreated():void
    {
        $this->render();
    }

    public function toggleState(TodoItem $todoItem): void
    {
        $todoItem->done = !$todoItem->done;
        $todoItem->save();
    }

    public function deleteTodoItem(TodoItem $todoItem): void
    {
        $todoItem->delete();
    }
}

Con este último paso queda lista nuestra app de Lista de Tareas con Laravel Livewire, Laravel Jetstream. Siéntanse libres de comentar si tienen alguna duda o inconveniente con el código trataré de apoyarlos, éxito a todos y Happy Coding.