Mini-défi : todo list persistante
Une todo list persistante, sans framework.
Objectifs
Assembler DOM, événements et localStorage en une application complète : une liste de tâches qui survit au rechargement.
Énoncé
Construisez todo.html + todo.js :
- Un champ de saisie et un bouton « Ajouter ».
- La liste des tâches affichée en
<ul>; chaque ligne porte un bouton « Supprimer ». - Les tâches sont stockées dans localStorage : fermez l'onglet, rouvrez — tout est là.
- Bonus : cocher une tâche la marque visuellement terminée (classe CSS), état également persisté.
Contraintes
- Aucun rechargement de page lors des opérations.
- Une seule source de vérité : le tableau JS dans localStorage. L'affichage se recalcule depuis lui (pattern render).
- Code découpé en fonctions :
ajouterTache(texte),supprimerTache(index),sauvegarder(),afficher().
Indices
Sur le modèle de données : stockez des objets, pas des chaînes :
[{ texte: "Apprendre le DOM", fait: false }, ...]
Sur les boutons supprimer : créez-les dynamiquement avec createElement et attachez un listener qui connaît son index (fermeture sur i).
Sur le rendu : le plus simple est de vider le <ul> (liste.innerHTML = "") puis de reconstruire toutes les lignes à chaque changement.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Solution complète
<!-- todo.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Todo</title>
<style>
.fait { text-decoration: line-through; color: gray; }
li { display: flex; gap: 8px; align-items: center; }
</style>
</head>
<body>
<h1>Mes tâches</h1>
<form id="formulaire">
<input type="text" id="saisie" placeholder="Nouvelle tâche" required>
<button>Ajouter</button>
</form>
<ul id="liste"></ul>
<script src="todo.js"></script>
</body>
</html>
// todo.js
let taches = [];
function charger() {
const brute = localStorage.getItem("taches");
if (brute) {
taches = JSON.parse(brute);
}
}
function sauvegarder() {
localStorage.setItem("taches", JSON.stringify(taches));
}
function afficher() {
const liste = document.querySelector("#liste");
liste.innerHTML = "";
taches.forEach(function (tache, i) {
const li = document.createElement("li");
const coche = document.createElement("input");
coche.type = "checkbox";
coche.checked = tache.fait;
coche.addEventListener("change", function () {
taches[i].fait = coche.checked;
sauvegarder();
afficher();
});
const span = document.createElement("span");
span.textContent = tache.texte;
if (tache.fait) span.classList.add("fait");
const croix = document.createElement("button");
croix.textContent = "×";
croix.addEventListener("click", function () {
taches.splice(i, 1);
sauvegarder();
afficher();
});
li.appendChild(coche);
li.appendChild(span);
li.appendChild(croix);
liste.appendChild(li);
});
}
document.querySelector("#formulaire").addEventListener("submit", function (event) {
event.preventDefault();
const saisie = document.querySelector("#saisie");
const texte = saisie.value.trim();
if (!texte) return;
taches.push({ texte: texte, fait: false });
saisie.value = "";
sauvegarder();
afficher();
});
charger();
afficher();
Ce que cet exercice a installé
- Modèle → rendu : les données vivent dans le tableau ; l'écran n'en est que le reflet recalculé. C'est le principe directeur de tous les frameworks que vous apprendrez (Angular inclus) — ici vous venez de l'implémenter à la main.
- Persistance explicite : chaque mutation appelle
sauvegarder(). Oublier ce réflexe produit des données fantômes qui disparaissent. - Listeners contextuels : chaque bouton supprime SA ligne grâce à l'index capturé.
Vous savez désormais construire une application navigateur complète sans framework — c'est exactement la base qu'Angular automatisera au chapitre 37.