Définir un état unique pour les tâches
Objectif : Représenter les tâches dans un tableau d’objets et mettre à jour cet état lors d’un ajout, d’une suppression ou d’un changement de statut.
Une tâche ajoutée dans la page disparaît dès que vous reconstruisez la liste si le DOM est votre seul support. Pour rendre les actions cohérentes, vous allez conserver les tâches dans un état applicatif en mémoire et produire l’affichage à partir de cet état. Cette étape ne sauvegarde pas encore les données après fermeture du navigateur : le stockage sera traité dans une leçon ultérieure.
Modéliser les tâches dans un état unique
Une tâche est un objet contenant un identifiant, un titre et un statut. Le tableau taches regroupe tous ces objets ; il devient la source de vérité du gestionnaire. Les fonctions modifient ce tableau, puis le rendu vide et reconstruit la liste en fonction de son contenu, au lieu de modifier isolément une ligne comme si elle constituait l’état lui-même.
Chaque tâche reçoit un identifiant stable. Ici, un compteur attribue des identifiants numériques croissants pendant l’exécution de la page ; ils permettent de retrouver précisément une tâche lors d’une action déléguée. Ils ne sont pas encore conservés après un rechargement : cette limite disparaîtra avec la persistance abordée plus tard.
Mettre à jour sans modifier le tableau en place
Pour basculer le statut d’une tâche, map() crée un nouveau tableau : l’objet correspondant à l’identifiant ciblé est remplacé par une copie dont la propriété terminee a la valeur inverse. Les autres objets sont conservés. Pour supprimer une tâche, filter() crée un tableau qui exclut l’identifiant demandé.
Cette mise à jour immuable rend le changement explicite : vous remplacez la référence du tableau au lieu de modifier une ligne de l’interface à la main. La fonction de rendu efface ensuite les anciennes lignes et reconstruit la liste depuis le nouvel état. Si vous supprimez une tâche, elle ne peut donc pas réapparaître lors du prochain rendu.
Fichiers à cet état du projet
L’HTML ci-dessous reprend le squelette du projet sans en changer les classes ni les attributs. Remplacez entièrement index.html et js/main.js par ces fichiers ; style.css reste inchangé. Les commandes de filtre et de chargement restent présentes dans l’interface, mais leur comportement sera assemblé dans les leçons prévues à cet effet.
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gestionnaire de tâches</title>
<link rel="stylesheet" href="style.css">
<script type="module" src="js/main.js"></script>
</head>
<body class="atelier-page">
<header class="atelier-entete">
<h1>Gestionnaire de tâches</h1>
<p>Organisez vos tâches et suivez votre progression.</p>
</header>
<main class="atelier-principal">
<section class="atelier-progression" aria-labelledby="titre-progression">
<h2 id="titre-progression">Progression</h2>
<p><span data-progression-texte>0 tâche terminée sur 0</span></p>
<progress class="atelier-progression__barre" data-progression max="100" value="0">0%</progress>
</section>
<form class="atelier-formulaire" data-formulaire>
<label for="nouvelle-tache">Nouvelle tâche</label>
<input class="atelier-champ" id="nouvelle-tache" name="titre" type="text" required>
<button class="atelier-bouton" type="submit">Ajouter</button>
</form>
<div class="atelier-actions">
<div class="atelier-filtres" role="group" aria-label="Filtrer les tâches">
<button class="atelier-bouton" type="button" data-filtre="toutes" aria-pressed="true">Toutes</button>
<button class="atelier-bouton" type="button" data-filtre="actives" aria-pressed="false">À faire</button>
<button class="atelier-bouton" type="button" data-filtre="terminees" aria-pressed="false">Terminées</button>
</div>
<button class="atelier-bouton" type="button" data-charger>Charger des tâches</button>
</div>
<p class="atelier-message" data-message role="status" aria-live="polite"></p>
<ul class="atelier-liste" data-liste aria-label="Liste des tâches"></ul>
<template data-modele-tache>
<li class="atelier-tache" data-id="">
<input type="checkbox" data-action="basculer">
<span data-titre></span>
<button class="atelier-bouton" type="button" data-action="supprimer">Supprimer</button>
</li>
</template>
</main>
</body>
</html>const formulaire = document.querySelector('[data-formulaire]');
const champTitre = document.querySelector('#nouvelle-tache');
const liste = document.querySelector('[data-liste]');
const modele = document.querySelector('[data-modele-tache]');
if (!formulaire || !champTitre || !liste || !modele) {
throw new Error('Un élément nécessaire au gestionnaire est introuvable.');
}
// Le tableau en mémoire est la source unique des tâches affichées.
let taches = [];
let prochainId = 1;
function creerLigne(tache) {
const fragment = modele.content.cloneNode(true);
const ligne = fragment.querySelector('.atelier-tache');
const caseTerminee = fragment.querySelector('[data-action="basculer"]');
const titre = fragment.querySelector('[data-titre]');
if (!ligne || !caseTerminee || !titre) {
throw new Error('Le modèle de tâche est incomplet.');
}
ligne.dataset.id = String(tache.id);
caseTerminee.checked = tache.terminee;
titre.textContent = tache.titre;
return fragment;
}
function afficherTaches() {
liste.replaceChildren();
for (const tache of taches) {
liste.append(creerLigne(tache));
}
}
function ajouterTache(evenement) {
evenement.preventDefault();
const donnees = new FormData(formulaire);
const titre = String(donnees.get('titre') ?? '').trim();
if (titre === '') {
return;
}
const nouvelleTache = {
id: prochainId,
titre,
terminee: false
};
prochainId += 1;
taches = [...taches, nouvelleTache];
afficherTaches();
formulaire.reset();
champTitre.focus();
}
function traiterAction(evenement) {
const cible = evenement.target;
if (!(cible instanceof Element)) {
return;
}
const commande = cible.closest('[data-action]');
const ligne = cible.closest('.atelier-tache');
if (!commande || !ligne || !liste.contains(commande)) {
return;
}
const id = Number(ligne.dataset.id);
const action = commande.dataset.action;
if (action === 'basculer' && evenement.type === 'change') {
taches = taches.map((tache) =>
tache.id === id ? { ...tache, terminee: commande.checked } : tache
);
afficherTaches();
}
if (action === 'supprimer' && evenement.type === 'click') {
taches = taches.filter((tache) => tache.id !== id);
afficherTaches();
}
}
function traiterChangement(evenement) {
traiterAction(evenement);
}
function traiterClic(evenement) {
traiterAction(evenement);
}
function demonter() {
formulaire.removeEventListener('submit', ajouterTache);
liste.removeEventListener('change', traiterChangement);
liste.removeEventListener('click', traiterClic);
window.removeEventListener('pagehide', demonter);
}
formulaire.addEventListener('submit', ajouterTache);
liste.addEventListener('change', traiterChangement);
liste.addEventListener('click', traiterClic);
window.addEventListener('pagehide', demonter);
// Un rendu initial vide confirme que l’affichage dépend du tableau d’état.
afficherTaches();Au chargement, la liste est vide. Après ajout, une tâche apparaît ; cocher sa case met à jour son statut dans l’état, et cliquer sur « Supprimer » retire l’objet puis sa ligne. Toutes ces opérations restent en mémoire jusqu’au rechargement de la page.À retenir
- Un tableau d’objets en mémoire représente l’ensemble des tâches ; l’interface est reconstruite depuis ce tableau.
- Chaque tâche possède un identifiant stable pendant l’exécution, un titre et un statut.
- Utilisez map() pour remplacer une tâche et filter() pour en retirer une sans modifier le tableau en place.
- Cette étape ne conserve pas les données après un rechargement ; la persistance sera ajoutée ensuite.