Créer une To-Do List en JavaScript est le projet idéal pour progresser : tu y manipules le DOM, les événements, les tableaux et la sauvegarde des données. Dans ce tutoriel, on va plus loin qu’une simple liste : filtres, modification par double-clic, sauvegarde dans le localStorage et réorganisation par glisser-déposer. Tout le code est dans l’article, sans framework ni bibliothèque.

🟨 JavaScript💾 localStorage🖱️ Glisser-déposer⏱️ 15 min de lecture

Si tu as déjà suivi notre Quiz App en JavaScript ou notre calculatrice en JavaScript, cette To-Do List en JavaScript est l’étape suivante : tu vas apprendre à gérer un état (la liste des tâches) et à le garder entre deux visites.

Voici la To-Do List en JavaScript que tu vas créer, capturée à partir du code exact de cet article :

To-Do List en JavaScript : liste de tâches avec tâches terminées
La To-Do List en JavaScript terminée : tâches actives, tâches terminées et compteur.

Ce que tu vas apprendre avec cette To-Do List en JavaScript

🧱 Manipuler le DOMCréer des éléments avec createElement et les mettre à jour.
💾 localStorageSauvegarder les tâches pour les retrouver après un rechargement.
🔍 FiltresAfficher toutes les tâches, les actives ou les terminées.
✏️ ÉditionModifier une tâche par double-clic, valider ou annuler au clavier.
🖱️ Drag and DropRéordonner la liste avec l’API HTML5 de glisser-déposer.
🛡️ SécuritéÉviter les failles XSS grâce à textContent.

Étape 1 : la structure du projet To-Do List en JavaScript

Crée un dossier todo-list avec les trois fichiers de la To-Do List en JavaScript. Aucun serveur n’est nécessaire : il suffit d’ouvrir index.html dans ton navigateur.

todo-list/
├── index.html   # La structure de la page
├── style.css    # Le design
└── app.js       # La logique de l’application
structure du projet

Étape 2 : le HTML (index.html)

La page de la To-Do List en JavaScript contient un formulaire pour ajouter une tâche, trois boutons de filtre, une liste vide (remplie par JavaScript) et un pied de page avec le compteur. Les attributs data-filtre permettront à JavaScript de savoir quel filtre a été cliqué.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>To-Do List en JavaScript</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="app">
    <h1>📝 Mes tâches</h1>

    <form id="form-tache" class="form">
      <input id="champ-tache" type="text" placeholder="Ajouter une tâche…"
             maxlength="120" autocomplete="off" required>
      <button type="submit">Ajouter</button>
    </form>

    <nav class="filtres" aria-label="Filtrer les tâches">
      <button type="button" data-filtre="toutes" class="actif">Toutes</button>
      <button type="button" data-filtre="actives">Actives</button>
      <button type="button" data-filtre="terminees">Terminées</button>
    </nav>

    <ul id="liste" class="liste"></ul>

    <footer class="pied">
      <span id="compteur">0 tâche restante</span>
      <button type="button" id="vider">Supprimer les terminées</button>
    </footer>
    <p class="astuce">Double-clic pour modifier · Glisser-déposer pour réordonner</p>
  </main>

  <script src="app.js"></script>
</body>
</html>
index.html
💡
Pourquoi un formulaire ?

Avec une balise <form>, la touche Entrée ajoute la tâche automatiquement, et l’attribut required empêche d’envoyer un champ vide.

Étape 3 : le CSS (style.css)

Un design simple et moderne pour ta To-Do List en JavaScript : une carte blanche centrée sur un dégradé bleu. Les classes .terminee, .glisse et .survol sont ajoutées par JavaScript pour afficher l’état de chaque tâche.

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: linear-gradient(135deg, #1e3a8a, #2563eb 55%, #0ea5e9);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 48px 16px;
  color: #0f172a;
}

.app {
  width: 100%;
  max-width: 480px;
  background: #fff;
  border-radius: 20px;
  padding: 28px 24px;
  box-shadow: 0 20px 50px rgba(2, 6, 23, .35);
}

h1 { margin: 0 0 20px; font-size: 1.6rem; }

.form { display: flex; gap: 8px; }

.form input {
  flex: 1;
  padding: 12px 14px;
  border: 2px solid #e2e8f0;
  border-radius: 12px;
  font-size: 1rem;
}

.form input:focus { outline: none; border-color: #2563eb; }

button {
  cursor: pointer;
  border: 0;
  border-radius: 12px;
  font: inherit;
}

.form button {
  padding: 0 18px;
  background: #2563eb;
  color: #fff;
  font-weight: 600;
}

.filtres { display: flex; gap: 6px; margin: 18px 0 10px; }

.filtres button {
  padding: 6px 12px;
  background: #f1f5f9;
  color: #475569;
  font-size: .9rem;
}

.filtres button.actif { background: #dbeafe; color: #1d4ed8; font-weight: 600; }

.liste { list-style: none; margin: 0; padding: 0; }

.tache {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 10px;
  border-bottom: 1px solid #f1f5f9;
  background: #fff;
  cursor: grab;
}

.tache.glisse { opacity: .4; }
.tache.survol { border-top: 2px solid #2563eb; }

.tache input[type="checkbox"] { width: 20px; height: 20px; accent-color: #2563eb; }

.tache .texte { flex: 1; word-break: break-word; }

.tache.terminee .texte { text-decoration: line-through; color: #94a3b8; }

.tache .edition {
  flex: 1;
  padding: 6px 8px;
  border: 2px solid #2563eb;
  border-radius: 8px;
  font: inherit;
}

.tache .supprimer {
  background: none;
  color: #cbd5e1;
  font-size: 1.2rem;
  padding: 2px 6px;
}

.tache .supprimer:hover { color: #ef4444; }

.vide { text-align: center; color: #94a3b8; padding: 24px 0; }

.pied {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 14px;
  font-size: .9rem;
  color: #64748b;
}

.pied button { background: none; color: #64748b; padding: 4px; }
.pied button:hover { color: #ef4444; }

.astuce { margin: 14px 0 0; font-size: .8rem; color: #94a3b8; text-align: center; }
style.css

Étape 4 : le code de la To-Do List en JavaScript (app.js)

Le fichier app.js, qui contient toute la logique de la To-Do List en JavaScript, est découpé en 8 parties. Copie-les les unes à la suite des autres dans le même fichier : ensemble, elles forment le code complet.

4.1 L’état et les éléments du DOM

Toute la To-Do List en JavaScript repose sur un tableau taches. Chaque tâche est un objet { id, texte, terminee }. On récupère aussi une seule fois les éléments HTML dont on aura besoin.

// ===== 1. État de l'application =====
const CLE_STOCKAGE = 'todo-lmcode';
let taches = charger();
let filtre = 'toutes';
let idGlisse = null;

// ===== 2. Éléments du DOM =====
const form = document.getElementById('form-tache');
const champ = document.getElementById('champ-tache');
const liste = document.getElementById('liste');
const compteur = document.getElementById('compteur');
const boutonVider = document.getElementById('vider');
const boutonsFiltre = document.querySelectorAll('[data-filtre]');
app.js

4.2 La sauvegarde dans le localStorage

Pour que ta To-Do List en JavaScript garde les tâches, on utilise le localStorage. Il ne stocke que du texte. On transforme donc le tableau en chaîne JSON pour l’enregistrer, et inversement pour le relire. Le try/catch évite un plantage si les données sont corrompues. (sources : MDN : localStorage, MDN : JSON.stringify)

// ===== 3. Sauvegarde dans le localStorage =====
function charger() {
  try {
    return JSON.parse(localStorage.getItem(CLE_STOCKAGE)) || [];
  } catch {
    return [];
  }
}

function sauvegarder() {
  localStorage.setItem(CLE_STOCKAGE, JSON.stringify(taches));
}
app.js

4.3 Les actions sur les tâches

Dans la To-Do List en JavaScript, chaque action modifie le tableau, puis appelle miseAJour(), qui sauvegarde et réaffiche la liste. C’est le principe clé : on modifie les données, puis on redessine l’interface.

// ===== 4. Actions sur les tâches =====
function ajouter(texte) {
  taches.push({ id: Date.now(), texte, terminee: false });
  miseAJour();
}

function basculer(id) {
  const tache = taches.find(t => t.id === id);
  tache.terminee = !tache.terminee;
  miseAJour();
}

function supprimer(id) {
  taches = taches.filter(t => t.id !== id);
  miseAJour();
}

function modifier(id, texte) {
  const tache = taches.find(t => t.id === id);
  if (texte) tache.texte = texte;
  else taches = taches.filter(t => t.id !== id); // texte vide = suppression
  miseAJour();
}

function deplacer(idSource, idCible) {
  const depart = taches.findIndex(t => t.id === idSource);
  const arrivee = taches.findIndex(t => t.id === idCible);
  const [tache] = taches.splice(depart, 1);
  taches.splice(arrivee, 0, tache);
  miseAJour();
}
app.js

4.4 L’affichage

La fonction afficher() vide la To-Do List en JavaScript et la reconstruit à partir des tâches filtrées. Le texte de la tâche est inséré avec textContent et non innerHTML : si un utilisateur tape du code HTML, il s’affiche comme du texte et ne s’exécute pas. (source : MDN : textContent)

// ===== 5. Affichage =====
function tachesFiltrees() {
  if (filtre === 'actives') return taches.filter(t => !t.terminee);
  if (filtre === 'terminees') return taches.filter(t => t.terminee);
  return taches;
}

function creerElement(tache) {
  const li = document.createElement('li');
  li.className = 'tache' + (tache.terminee ? ' terminee' : '');
  li.draggable = true;
  li.dataset.id = tache.id;

  const caseACocher = document.createElement('input');
  caseACocher.type = 'checkbox';
  caseACocher.checked = tache.terminee;
  caseACocher.setAttribute('aria-label', 'Marquer comme terminée');
  caseACocher.addEventListener('change', () => basculer(tache.id));

  const texte = document.createElement('span');
  texte.className = 'texte';
  texte.textContent = tache.texte; // textContent : pas de faille XSS
  texte.addEventListener('dblclick', () => passerEnEdition(li, texte, tache));

  const boutonSupprimer = document.createElement('button');
  boutonSupprimer.className = 'supprimer';
  boutonSupprimer.textContent = '✕';
  boutonSupprimer.setAttribute('aria-label', 'Supprimer la tâche');
  boutonSupprimer.addEventListener('click', () => supprimer(tache.id));

  li.append(caseACocher, texte, boutonSupprimer);
  activerGlisserDeposer(li);
  return li;
}

function passerEnEdition(li, texte, tache) {
  const champEdition = document.createElement('input');
  champEdition.className = 'edition';
  champEdition.value = tache.texte;
  li.replaceChild(champEdition, texte);
  champEdition.focus();

  let fini = false; // évite de valider deux fois
  const valider = () => {
    if (fini) return;
    fini = true;
    modifier(tache.id, champEdition.value.trim());
  };
  champEdition.addEventListener('blur', valider);
  champEdition.addEventListener('keydown', e => {
    if (e.key === 'Enter') valider();
    if (e.key === 'Escape') {
      fini = true; // annule la modification
      afficher();
    }
  });
}

function afficher() {
  liste.innerHTML = '';
  const visibles = tachesFiltrees();

  if (visibles.length === 0) {
    const vide = document.createElement('li');
    vide.className = 'vide';
    vide.textContent = 'Aucune tâche ici 🎉';
    liste.appendChild(vide);
  } else {
    visibles.forEach(t => liste.appendChild(creerElement(t)));
  }

  const restantes = taches.filter(t => !t.terminee).length;
  compteur.textContent = `${restantes} tâche${restantes > 1 ? 's' : ''} restante${restantes > 1 ? 's' : ''}`;

  boutonsFiltre.forEach(b => b.classList.toggle('actif', b.dataset.filtre === filtre));
}

function miseAJour() {
  sauvegarder();
  afficher();
}
app.js

4.5 Le glisser-déposer

Pour réordonner la To-Do List en JavaScript, chaque tâche a l’attribut draggable. On écoute les événements dragstart, dragover et drop. L’appel à e.preventDefault() dans dragover est indispensable : sans lui, le navigateur refuse le dépôt. (source : MDN : API HTML Drag and Drop)

// ===== 6. Glisser-déposer (API HTML5 Drag and Drop) =====
function activerGlisserDeposer(li) {
  li.addEventListener('dragstart', () => {
    idGlisse = Number(li.dataset.id);
    li.classList.add('glisse');
  });

  li.addEventListener('dragend', () => li.classList.remove('glisse'));

  li.addEventListener('dragover', e => {
    e.preventDefault(); // indispensable pour autoriser le dépôt
    li.classList.add('survol');
  });

  li.addEventListener('dragleave', () => li.classList.remove('survol'));

  li.addEventListener('drop', e => {
    e.preventDefault();
    li.classList.remove('survol');
    const idCible = Number(li.dataset.id);
    if (idGlisse !== null && idGlisse !== idCible) deplacer(idGlisse, idCible);
    idGlisse = null;
  });
}
app.js

4.6 Les événements et le premier affichage

// ===== 7. Événements =====
form.addEventListener('submit', e => {
  e.preventDefault();
  const texte = champ.value.trim();
  if (!texte) return;
  ajouter(texte);
  champ.value = '';
  champ.focus();
});

boutonsFiltre.forEach(bouton => {
  bouton.addEventListener('click', () => {
    filtre = bouton.dataset.filtre;
    afficher();
  });
});

boutonVider.addEventListener('click', () => {
  taches = taches.filter(t => !t.terminee);
  miseAJour();
});

// ===== 8. Premier affichage =====
afficher();
app.js

Étape 5 : tester ta To-Do List en JavaScript

Ouvre index.html dans ton navigateur pour lancer ta To-Do List en JavaScript, ajoute quelques tâches, coche-les, puis recharge la page : elles sont toujours là. Teste aussi les filtres et la modification par double-clic.

To-Do List en JavaScript : filtre des tâches actives
Le filtre « Actives » masque les tâches terminées.
To-Do List en JavaScript : modification d’une tâche par double-clic
Double-clic : la tâche devient modifiable.

Pour voir ce que ta To-Do List en JavaScript a enregistré, ouvre les outils de développement (F12), onglet Console, et tape :

JSON.parse(localStorage.getItem('todo-lmcode'))

// Pour tout effacer :
localStorage.removeItem('todo-lmcode')
console du navigateur

Les erreurs fréquentes dans une To-Do List en JavaScript

ProblèmeSolution
Les tâches disparaissent au rechargementVérifie que sauvegarder() est appelée dans miseAJour().
Cannot read properties of nullLe script est chargé avant le HTML : garde la balise <script> juste avant </body>.
Le glisser-déposer ne fonctionne pasIl manque e.preventDefault() dans dragover.
Le glisser-déposer ne marche pas sur mobileNormal : l’API Drag and Drop HTML5 vise surtout la souris. Voir les exercices ci-dessous.

Améliorer ta To-Do List en JavaScript : 5 exercices

  1. Date d’échéance : ajoute un champ date et affiche les tâches en retard en rouge.
  2. Priorités : ajoute une priorité (haute, normale, basse) avec une couleur.
  3. Mode sombre : un bouton qui bascule une classe sur <body>, mémorisée dans le localStorage.
  4. Mobile : gère le réordonnancement tactile avec les Pointer Events.
  5. Application installable : transforme-la en PWA avec notre tutoriel Créer une PWA installable.

FAQ

Faut-il un serveur pour cette To-Do List en JavaScript ?

Non. Ta To-Do List en JavaScript fonctionne en local : ouvre simplement index.html dans ton navigateur : tout fonctionne en local.

Où sont stockées les tâches ?

Les tâches de la To-Do List en JavaScript sont stockées dans le localStorage du navigateur, sur ton ordinateur. Elles restent après un rechargement, mais ne sont pas synchronisées entre appareils.

Pourquoi utiliser textContent plutôt que innerHTML ?

textContent affiche le texte tel quel. innerHTML interpréterait le HTML saisi par l’utilisateur, ce qui ouvre la porte aux failles XSS.

Combien de données peut-on stocker dans le localStorage ?

Quelques mégaoctets selon le navigateur, largement suffisant pour une liste de tâches.

To-Do List en JavaScript : le résumé à retenir

  • Un tableau d’objets représente l’état de l’application.
  • Chaque action modifie le tableau, puis sauvegarde et réaffiche.
  • localStorage + JSON pour garder les données.
  • textContent pour la sécurité, draggable et drop pour le glisser-déposer.

Conclusion

Tu as créé une To-Do List en JavaScript complète, sans framework, avec des fonctionnalités qu’on retrouve dans de vraies applications. Le principe de cette To-Do List en JavaScript, « modifier les données, puis réafficher » est exactement celui de React ou Vue : tu es prêt pour l’étape suivante. Pour sauvegarder ton projet, suis notre guide Git et GitHub pour débutant.

Sources

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *