Aplicación de To Do List con Alpine JS
Ya tenemos todo lo necesario en Alpine, hemos visto las bases de Alpine terminando como extender el mismo frameworks con plugins CDN como el de SortableJS (drag & drop) en Alpine. Ahora, veremos los pasos para crear una app de tipo To Do List como primer proyecto real y funcional.
1. ➕ Crear un To Do (Formulario)
La siguiente funcionalidad que vamos a crear sería la de crear un todo para esto vamos a ampliar un sencillo formulario como el que está viendo en pantalla por aquí un input igualito que tenemos acá:
<form x-on:submit.prevent="save()" class="row g-2 mt-2">
<div class="col-auto">
<label class="col-form-label">Create</label>
</div>
<div class="col-auto">
<input type="text" x-model="task" class="form-control">
</div>
<div class="col-auto">
<button class="btn btn-success" type="submit">Save</button>
</div>
</form>- x-model="task": Sincroniza bidireccionalmente el texto del input con la variable task definida en x-data.
- x-on:submit.prevent="save()": Captura el evento de envío del formulario. El modificador .prevent es crucial, ya que evita el comportamiento predeterminado de HTML (recargar la página), permitiendo que la lógica de la función save() se ejecute localmente.
submit.preventLa función agrega la nueva tarea al array principal (todos) y limpia la variable del formulario:
todos: [],
***
save() {
this.todos.push(
{ completed: false, task: this.task }
)
}2. 🔍 Campo de Búsqueda (Search)
Implementaremos un filtro o campo de búsqueda que filtra el listado de todos en tiempo real usando el método filter de JavaScript.
Implementación del Filtro
Definimos una nueva variable search en x-data para almacenar lo que el usuario escribe.
Creamos una función filterTodo() que aplica la lógica de filtrado.
Reemplazamos la iteración del x-for para que itere sobre el resultado de la función: x-for="t in filterTodo()".
Lógica de la Función filterTodo()
search: "", // Variable para el input de búsqueda
// ...
filterTodo() {
// Usamos el método filter de JavaScript en el array 'todos'
return this.todos.filter((t) =>
// Convertimos todo a minúsculas para una búsqueda sin distinción de mayúsculas/minúsculas
t.task.toLowerCase().includes(this.search.toLowerCase())
);
}La función filterTodo() devuelve un nuevo array que contiene solo los elementos cuya tarea (t.task) incluye el término de búsqueda (this.search). Si el campo de búsqueda está vacío, devuelve todos los elementos:
<div x-data="data()">
<p>Total Task <span x-text="totalTareas"></span></p>
<label>Search
<input type="text" x-model="search">
</label>
<template x-for="t in filterTodo()">
<p>
<template x-if="completed(t)">
<span>Completed</span>
</template>
<template x-if="!t.completed">
<span>Uncompleted</span>
</template>
<span x-text="t.task"></span>
</p>
</template>
</div>Devuelve true significa que forma parte del listado que estamos retornando acá y si no simplemente lo excluye por lo tanto por defecto aquí ya tenemos un listado filtrado otra vez si esto es vacío que sería la condición inicial devolvería todos los elementos aquí también empleamos la función de toLowerCase que es para convertir el texto en minúscula:
return this.todos.filter((t) => t.task.toLowerCase().includes(this.search.toLowerCase())3. ✅ Marcar To Do como Completado
Para permitir al usuario marcar una tarea como completada, usamos un input de tipo checkbox y lo atamos directamente a la propiedad completed del objeto que estamos iterando.
Uso de x-model en el Bucle
<template x-for="t in filterTodo()">
<li class="list-group-item">
<input type="checkbox" x-model="t.completed">
<span x-text="t.task"></span>
</li>
</template>Debido a la reactividad de Alpine.js, al marcar o desmarcar el checkbox, la propiedad t.completed se actualiza inmediatamente en el array original (todos), y cualquier elemento HTML que dependa de esa propiedad también se actualizará (por ejemplo, un texto que indique "Completed").
4. ❌ Eliminar un To Do
Implementaremos la funcionalidad de eliminar una tarea utilizando un botón que invoca una función de JavaScript para remover el elemento del array.
Lógica de la Función remove()
La forma más sencilla de eliminar un elemento de un array en JavaScript mientras se mantiene la reactividad es usando nuevamente el método filter.
remove(todo) {
// Reemplazamos el array 'todos' con un nuevo array filtrado
this.todos = this.todos.filter((t) => t !== todo);
}La condición del filter es crucial: t !== todo. Mantiene todos los elementos (t) que sean distintos al elemento que se le pasó como parámetro (todo). El todo que queremos eliminar es excluido del nuevo array asignado a this.todos.
Implementación en el Template
<button class="btn btn-sm btn-close float-end" @click="remove(t)"></button>Al hacer clic, llamamos a remove(t), pasando el objeto de la tarea actual (t) para su eliminación.
🎨 Instalación y Configuración de Bootstrap 5
Para darle estilo a nuestra aplicación de manera sencilla, implementaremos Bootstrap 5.
¿Qué es Bootstrap?
Bootstrap es un framework basado en CSS y JS que proporciona componentes listos para usar (botones, formularios, grillas, etc.). Para usar sus componentes principales, solo es necesario definir clases preestablecidas y seguir una estructura HTML fija.
Configuración (Solo CSS)
Para este proyecto, solo necesitamos incluir el CSS de Bootstrap a través de un CDN, ya que las funcionalidades de Alpine.js gestionan la interactividad.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">Una vez configurado, simplemente aplicamos las clases de Bootstrap (como form-control, btn btn-success, list-group) a nuestra estructura HTML:
<div x-data="data()" class="container my-3" style="max-width: 500px;">
<div class="card">
<div class="card-header">
<h4>Total To Dos: <span x-text="totalTodos()"></span></h4>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-auto">
<label class="col-form-label">
Search
</label>
</div>
<div class="col-auto">
<input type="text" x-model="search" class="form-control">
</div>
</div>
<form x-on:submit.prevent="save()" class="row g-2 mt-2">
<div class="col-auto">
<label class="col-form-label">Create</label>
</div>
<div class="col-auto">
<input type="text" x-model="task" class="form-control">
</div>
<div class="col-auto">
<button class="btn btn-success" type="submit">Save</button>
</div>
</form>
<ul class="list-group my-3">
<template x-for="t in filterTodo()">
<li class="list-group-item">
<template x-if="completed(t)">
<span>
Completed -
</span>
</template>
<template x-if="!t.completed">
<span>
Uncompleted -
</span>
</template>
<input type="checkbox" x-model="t.completed">
<span x-text="t.task" @click="t.editMode=true" x-show="!t.editMode"></span>
<input type="text" @keyup.enter="t.editMode=false" x-model="t.task" x-show="t.editMode" />
<button class="btn btn-sm btn-close float-end" @click="remove(t)"></button>
</li>
</template>
</ul>
<button class="btn btn-danger" @click="todos = []">Delete All</button>
</div>
</div>
</div>
Acepto recibir anuncios de interes sobre este Blog.
Veremos el desarrollo para implementar una aplicación de To Do List y su CRUD con Alpine JS y usaremos Bootstrap CSS para lo visual.