ITSkillsCenter
Développement Web

Tutoriel : Créer une to-do list interactive avec JavaScript

5 min de lecture

Ce que vous allez construire

Une application de to-do list complète avec ajout, suppression, modification et persistance des données dans le localStorage. Ce projet couvre les concepts fondamentaux de JavaScript : manipulation du DOM, gestion des événements, localStorage, et structuration du code. Vous pouvez tester le résultat final directement dans votre navigateur.

Le HTML

<div class="todo-app">
  <h1>Mes tâches</h1>
  
  <form id="todoForm" class="todo-form">
    <input type="text" id="todoInput" placeholder="Ajouter une tâche..." required>
    <button type="submit">Ajouter</button>
  </form>
  
  <div class="todo-filters">
    <button class="filter-btn active" data-filter="all">Toutes</button>
    <button class="filter-btn" data-filter="active">Actives</button>
    <button class="filter-btn" data-filter="completed">Terminées</button>
  </div>
  
  <ul id="todoList" class="todo-list"></ul>
  
  <p class="todo-count"><span id="todoCount">0</span> tâche(s) restante(s)</p>
</div>

Le CSS

.todo-app {
  max-width: 500px;
  margin: 40px auto;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.todo-form {
  display: flex;
  gap: 8px;
  margin-bottom: 20px;
}

.todo-form input {
  flex: 1;
  padding: 12px;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  font-size: 16px;
}

.todo-form input:focus {
  outline: none;
  border-color: #4a90d9;
}

.todo-form button {
  padding: 12px 24px;
  background: #4a90d9;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 16px;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border-bottom: 1px solid #f0f0f0;
  transition: opacity 0.3s;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #999;
}

.todo-text {
  flex: 1;
  font-size: 16px;
}

.todo-delete {
  background: none;
  border: none;
  color: #e74c3c;
  cursor: pointer;
  font-size: 18px;
  opacity: 0;
  transition: opacity 0.2s;
}

.todo-item:hover .todo-delete {
  opacity: 1;
}

.filter-btn {
  padding: 6px 16px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 20px;
  cursor: pointer;
  margin-right: 8px;
}

.filter-btn.active {
  background: #4a90d9;
  color: white;
  border-color: #4a90d9;
}

Le JavaScript complet

// État de l'application
let todos = JSON.parse(localStorage.getItem('todos')) || [];
let currentFilter = 'all';

// Éléments du DOM
const form = document.getElementById('todoForm');
const input = document.getElementById('todoInput');
const list = document.getElementById('todoList');
const countEl = document.getElementById('todoCount');
const filterBtns = document.querySelectorAll('.filter-btn');

// Générer un ID unique
function generateId() {
  return Date.now().toString(36) + Math.random().toString(36).substr(2);
}

// Sauvegarder dans localStorage
function save() {
  localStorage.setItem('todos', JSON.stringify(todos));
}

// Afficher les tâches
function render() {
  const filtered = todos.filter(todo => {
    if (currentFilter === 'active') return !todo.completed;
    if (currentFilter === 'completed') return todo.completed;
    return true;
  });

  list.innerHTML = filtered.map(todo => 
    '<li class="todo-item ' + (todo.completed ? 'completed' : '') + '" data-id="' + todo.id + '">' +
      '<input type="checkbox" ' + (todo.completed ? 'checked' : '') + '>' +
      '<span class="todo-text">' + escapeHtml(todo.text) + '</span>' +
      '<button class="todo-delete">✕</button>' +
    '</li>'
  ).join('');

  // Compter les tâches actives
  const activeCount = todos.filter(t => !t.completed).length;
  countEl.textContent = activeCount;
}

// Échapper le HTML pour éviter les XSS
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// Ajouter une tâche
form.addEventListener('submit', (e) => {
  e.preventDefault();
  const text = input.value.trim();
  if (!text) return;

  todos.push({ id: generateId(), text, completed: false });
  save();
  render();
  input.value = '';
  input.focus();
});

// Cocher/décocher et supprimer (délégation d'événements)
list.addEventListener('click', (e) => {
  const item = e.target.closest('.todo-item');
  if (!item) return;
  const id = item.dataset.id;

  if (e.target.type === 'checkbox') {
    const todo = todos.find(t => t.id === id);
    todo.completed = !todo.completed;
    save();
    render();
  }

  if (e.target.classList.contains('todo-delete')) {
    todos = todos.filter(t => t.id !== id);
    save();
    render();
  }
});

// Double-clic pour éditer
list.addEventListener('dblclick', (e) => {
  if (!e.target.classList.contains('todo-text')) return;
  const item = e.target.closest('.todo-item');
  const id = item.dataset.id;
  const todo = todos.find(t => t.id === id);

  const editInput = document.createElement('input');
  editInput.type = 'text';
  editInput.value = todo.text;
  editInput.className = 'todo-edit';
  e.target.replaceWith(editInput);
  editInput.focus();

  function finishEdit() {
    const newText = editInput.value.trim();
    if (newText) {
      todo.text = newText;
      save();
    }
    render();
  }

  editInput.addEventListener('blur', finishEdit);
  editInput.addEventListener('keydown', (ev) => {
    if (ev.key === 'Enter') finishEdit();
    if (ev.key === 'Escape') render(); // Annuler
  });
});

// Filtres
filterBtns.forEach(btn => {
  btn.addEventListener('click', () => {
    filterBtns.forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    currentFilter = btn.dataset.filter;
    render();
  });
});

// Affichage initial
render();

Concepts clés dans ce projet

  • localStorage : les données persistent entre les sessions du navigateur. JSON.stringify pour sauvegarder, JSON.parse pour lire.
  • Délégation d’événements : un seul écouteur sur la liste parente gère les clics sur tous les éléments enfants, même ceux ajoutés dynamiquement.
  • Échappement HTML : la fonction escapeHtml() empêche les attaques XSS si un utilisateur tape du HTML dans le champ.
  • État centralisé : toute l’application est pilotée par le tableau todos. On modifie l’état, on sauvegarde, on re-render.

Exercice d’extension

  1. Ajoutez un bouton « Supprimer les tâches terminées »
  2. Ajoutez le drag & drop pour réordonner les tâches
  3. Ajoutez des dates d’échéance avec un code couleur (rouge si en retard)
  4. Ajoutez des catégories (Travail, Personnel, Urgent)
#application #javascript #todo
Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 350.000 FCFA
Parlons de Votre Projet
Publicité

Articles Similaires