Skip to content
← 1001 Widgets
N° 006 · ●○○

To-do list

A to-do list: add, tick off, delete. The classic that forces you to separate data from display.

HTMLJS
What you'll learn
  • Keep tasks in an array of objects
  • Redraw the list from the data
  • Listen to every click with a single listener
Why this language

Pourquoi une liste de tâches ? C'est le premier vrai petit logiciel : on ajoute, on coche, on supprime. Une grande partie du web n'est qu'une variation de ces trois gestes.

La règle d'or : les données d'abord. On garde un tableau de tâches, et on redessine la liste à partir de lui. Jamais l'inverse.

Pourquoi en JavaScript simple ? Un tableau, une fonction d'affichage, deux écouteurs. On voit exactement ce que React fera plus tard à votre place.

How it works
  1. LE HTML

    Un formulaire pour ajouter, une liste vide que le script remplit.

    <form id="ajout">
      <input id="texte" placeholder="Nouvelle tâche" required>
      <button>Ajouter</button>
    </form>
    <ul id="liste"></ul>
  2. L'ÉTAT

    Une seule source de vérité : le tableau. La page n'en est que le reflet.

    let taches = [];
  3. AFFICHER

    On vide la liste et on la reconstruit depuis le tableau. textContent protège des balises tapées par l'utilisateur.

    function afficher() {
      const ul = document.getElementById('liste');
      ul.innerHTML = '';
      taches.forEach(t => {
        const li = document.createElement('li');
        li.dataset.id = t.id;
        li.innerHTML = '<label><input type="checkbox"><span></span></label><button>Supprimer</button>';
        li.querySelector('span').textContent = t.texte;
        li.querySelector('input').checked = t.faite;
        ul.append(li);
      });
    }
  4. AJOUTER

    preventDefault empêche le formulaire de recharger la page.

    document.getElementById('ajout').addEventListener('submit', e => {
      e.preventDefault();
      const champ = document.getElementById('texte');
      taches.push({ id: Date.now(), texte: champ.value.trim(), faite: false });
      champ.value = '';
      afficher();
    });
  5. COCHER ET SUPPRIMER

    Un seul écouteur sur la liste suffit : c'est la délégation d'événements.

    document.getElementById('liste').addEventListener('click', e => {
      const li = e.target.closest('li');
      if (!li) return;
      const id = Number(li.dataset.id);
      if (e.target.matches('input')) {
        taches = taches.map(t => t.id === id ? { ...t, faite: !t.faite } : t);
        afficher();
      } else if (e.target.matches('button')) {
        taches = taches.filter(t => t.id !== id);
        afficher();
      }
    });
Pitfalls

PIÈGE 1 : Injecter le texte avec innerHTML

Pitfall

Mauvais : li.innerHTML = t.texte. Une tâche nommée <img src=x onerror=alert(1)> exécute du code dans la page.

Fix

Correct : textContent pour tout ce qui vient de l'utilisateur.

PIÈGE 2 : Un écouteur par tâche

Pitfall

Mauvais : un addEventListener sur chaque case, à chaque affichage. Ils s'empilent et la mémoire grossit.

Fix

Correct : un seul écouteur sur la liste, et e.target pour savoir où on a cliqué.

PIÈGE 3 : Modifier la page au lieu des données

Pitfall

Mauvais : barrer le texte directement dans le DOM. Au prochain affichage, la modification disparaît.

Fix

Correct : changer le tableau, puis appeler afficher().

PERSONNALISER
  • Un compteur « 3 tâches restantes » avec taches.filter(t => !t.faite).length.
  • Trois filtres, Toutes, À faire et Faites, appliqués juste avant l'affichage.
  • Un double clic sur une tâche qui la transforme en champ texte pour la renommer.
OPTIMISATION
  • Pour des centaines de tâches, ne recréez que la ligne qui change au lieu de toute la liste.
  • Rangez le tableau dans localStorage à chaque modification : c'est la variante « Liste de tâches qui se souvient ».
VERS REACT

taches devient un useState, et afficher() disparaît : React redessine tout seul.

Chaque <li> reçoit key={t.id}, jamais l'index du tableau.

On ne modifie jamais le tableau : on en crée un nouveau avec map et filter, exactement comme ici.

Variants
Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.