Hoy te mostraré cómo hacer una lista de tareas pendientes con JavaScript puro, sin frameworks. A esto también se le conoce como To Do list.
Este proyecto de lista de pendientes es un proyecto básico para comenzar en este mundo de la programación web.
Solo usaremos JavaScript, arreglos, acceso a los elementos del DOM, eventos y almacenamiento con localstorage.
Lo que tendremos al final será una lista de tareas. A cada tarea podremos marcarla como completada o eliminarla, y también agregar una nueva tarea. Todo será guardado en LocalStorage dentro del navegador web.
Vamos a tener toda la lista de tareas en un arreglo. El arreglo estará lleno de objetos que van a tener la propiedad tarea
y completada
.
Ese arreglo será codificado como JSON y guardado en localstorage. Cada que el usuario modifique una tarea se modificará el arreglo en sí y después se guardará. Al inicio de la aplicación el arreglo se va a recuperar de localStorage.
Por cierto, usaremos las funciones push y splice para trabajar con el arreglo.
Primero veamos la página. Contamos con un input
que será para agregar una nueva tarea a nuestra To-Do list con JavaScript, además de una lista que será la contenedora de todos los elementos.
Todos los elementos tienen un id
para que más tarde los recuperemos desde JavaScript usando querySelector.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lista de tareas con JS - By Parzibyte</title>
<link rel="stylesheet" href="estilo.css">
</head>
<body>
<h2>Lista de tareas pendientes</h2>
<label for="inputNuevaTarea">Agregar nueva tarea: </label>
<input type="text" id="inputNuevaTarea">
<button id="btnAgregarTarea">Ok</button>
<ul id="contenedorTareas"></ul>
<a href="https://parzibyte.me/blog">By Parzibyte</a>
</body>
<script src="script.js"></script>
</html>
Por el momento fíjate en inputNuevaTarea
, btnAgregarTarea
y contenedorTareas
que vamos a recuperar más tarde desde JS:
const $contenedorTareas = document.querySelector("#contenedorTareas"),
$btnGuardarTarea = document.querySelector("#btnAgregarTarea"),
$inputNuevaTarea = document.querySelector("#inputNuevaTarea");
Esta lista debe tener ciertos estilos. Lo único que he agregado es la separación así como el texto tachado para agregar esa clase a los elementos completados:
li {
list-style: none;
margin-bottom: 5px;
}
a.enlace-eliminar {
margin-left: 10px;
text-decoration: none;
font-size: 1rem;
}
.tachado {
text-decoration: line-through;
}
Lo del texto tachado se consigue colocando text-decoration
en line-throug
.
Toda la lista va a vivir en un arreglo pero ese arreglo debe ser guardado de manera permanente para que las tareas pendientes sigan ahí incluso si se navega a otra página o se refresca la pestaña actual.
Lo único que hacemos es codificar y decodificar como JSON para después guardar en el almacenamiento del navegador.
const CLAVE_LOCALSTORAGE = "lista_tareas";
const obtenerTareasDeAlmacenamiento = () => {
const posibleLista = JSON.parse(localStorage.getItem(CLAVE_LOCALSTORAGE));
if (posibleLista) {
return posibleLista;
} else {
return [];
}
};
const guardarTareasEnAlmacenamiento = () => {
localStorage.setItem(CLAVE_LOCALSTORAGE, JSON.stringify(tareas));
};
En este caso la variable tareas
será la lista, y como es una variable global no necesitamos que nos la pasen como argumento.
Vamos a escuchar el clic del botón. Cuando pase eso, recuperamos el valor del input y agregamos una nueva tarea en caso de que no sea una cadena vacía. Por defecto la tarea no está terminada.
// Escuchar clic del botón para agregar nueva tarea
$btnGuardarTarea.onclick = () => {
const tarea = $inputNuevaTarea.value;
if (!tarea) {
return;
}
tareas.push({
tarea: tarea,
terminada: false,
});
$inputNuevaTarea.value = "";
guardarTareasEnAlmacenamiento();
refrescarListaDeTareas();
};
Estoy invocando a guardarTareasEnAlmacenamiento
(que ya expliqué anteriormente) y después a refrescarListaDeTareas
, que vamos a ver a continuación.
Lo que falta es la función más importante: la de recorrer el arreglo de tareas y dibujar toda la lista en el DOM. Es en esta función en donde escuchamos el evento change
del input checkbox y marcamos a la tarea como terminada o como no terminada.
También es aquí donde escuchamos el clic del botón para eliminar una tarea, en donde simplemente modificamos el arreglo y guardamos las tareas en el almacenamiento. Es un poco larga pero queda así:
// Definir función que refresca la lista de tareas a partir del arreglo global
const refrescarListaDeTareas = () => {
$contenedorTareas.innerHTML = "";
for (const [indice, tarea] of tareas.entries()) {
// Crear el enlace para eliminar la tarea
const $enlaceParaEliminar = document.createElement("a");
$enlaceParaEliminar.classList.add("enlace-eliminar");
$enlaceParaEliminar.innerHTML = "×";
$enlaceParaEliminar.href = "";
$enlaceParaEliminar.onclick = (evento) => {
evento.preventDefault();
if (!confirm("¿Eliminar tarea?")) {
return;
}
tareas.splice(indice, 1);
// Guardar los cambios
guardarTareasEnAlmacenamiento(tareas);
refrescarListaDeTareas();
};
// El input para marcar la tarea como terminada
const $checkbox = document.createElement("input");
$checkbox.type = "checkbox";
$checkbox.onchange = function () { // No es una función flecha porque quiero acceder al elemento a través de this
if (this.checked) {
tareas[indice].terminada = true;
} else {
tareas[indice].terminada = false;
}
guardarTareasEnAlmacenamiento(tareas);
refrescarListaDeTareas();
}
// El span que llevará el contenido de la tarea
const $span = document.createElement("span");
$span.textContent = tarea.tarea;
// Y finalmente el elemento de la lista
const $li = document.createElement("li");
// Verificamos si la tarea está marcada para marcar los elementos
if (tarea.terminada) {
$checkbox.checked = true;
$span.classList.add("tachado");
}
$li.appendChild($checkbox);
$li.appendChild($span);
$li.appendChild($enlaceParaEliminar);
$contenedorTareas.appendChild($li);
}
};
En este caso estoy haciendo un for of
para recorrer el arreglo por índice y valor. Me gusta más esta versión que la de usar un forEach.
Ya para terminar obtenemos todas las tareas y refrescamos la lista al inicio de la aplicación:
// Llamar a la función la primera vez
tareas = obtenerTareasDeAlmacenamiento();
refrescarListaDeTareas();
El código completo de JavaScript queda así:
const CLAVE_LOCALSTORAGE = "lista_tareas";
document.addEventListener("DOMContentLoaded", () => {
let tareas = []; // El arreglo global que vamos a manejar
// Declaración de elementos del DOM
const $contenedorTareas = document.querySelector("#contenedorTareas"),
$btnGuardarTarea = document.querySelector("#btnAgregarTarea"),
$inputNuevaTarea = document.querySelector("#inputNuevaTarea");
// Escuchar clic del botón para agregar nueva tarea
$btnGuardarTarea.onclick = () => {
const tarea = $inputNuevaTarea.value;
if (!tarea) {
return;
}
tareas.push({
tarea: tarea,
terminada: false,
});
$inputNuevaTarea.value = "";
guardarTareasEnAlmacenamiento();
refrescarListaDeTareas();
};
const obtenerTareasDeAlmacenamiento = () => {
const posibleLista = JSON.parse(localStorage.getItem(CLAVE_LOCALSTORAGE));
if (posibleLista) {
return posibleLista;
} else {
return [];
}
};
const guardarTareasEnAlmacenamiento = () => {
localStorage.setItem(CLAVE_LOCALSTORAGE, JSON.stringify(tareas));
};
// Definir función que refresca la lista de tareas a partir del arreglo global
const refrescarListaDeTareas = () => {
$contenedorTareas.innerHTML = "";
for (const [indice, tarea] of tareas.entries()) {
// Crear el enlace para eliminar la tarea
const $enlaceParaEliminar = document.createElement("a");
$enlaceParaEliminar.classList.add("enlace-eliminar");
$enlaceParaEliminar.innerHTML = "×";
$enlaceParaEliminar.href = "";
$enlaceParaEliminar.onclick = (evento) => {
evento.preventDefault();
if (!confirm("¿Eliminar tarea?")) {
return;
}
tareas.splice(indice, 1);
// Guardar los cambios
guardarTareasEnAlmacenamiento(tareas);
refrescarListaDeTareas();
};
// El input para marcar la tarea como terminada
const $checkbox = document.createElement("input");
$checkbox.type = "checkbox";
$checkbox.onchange = function () { // No es una función flecha porque quiero acceder al elemento a través de this
if (this.checked) {
tareas[indice].terminada = true;
} else {
tareas[indice].terminada = false;
}
guardarTareasEnAlmacenamiento(tareas);
refrescarListaDeTareas();
}
// El span que llevará el contenido de la tarea
const $span = document.createElement("span");
$span.textContent = tarea.tarea;
// Y finalmente el elemento de la lista
const $li = document.createElement("li");
// Verificamos si la tarea está marcada para marcar los elementos
if (tarea.terminada) {
$checkbox.checked = true;
$span.classList.add("tachado");
}
$li.appendChild($checkbox);
$li.appendChild($span);
$li.appendChild($enlaceParaEliminar);
$contenedorTareas.appendChild($li);
}
};
// Llamar a la función la primera vez
tareas = obtenerTareasDeAlmacenamiento();
refrescarListaDeTareas();
});
Si quieres puedes acceder al código completo aquí. Por otro lado, te dejo una demostración en este enlace y más contenido sobre JavaScript en este link.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.