Leçon 7 / 16

Centraliser les interactions avec le DOM

25 min JavaScript — modularité, performance et API navigateur Gratuit

Objectif : Relier les événements du formulaire et de la liste à des actions applicatives nommées.

Quand une interface grandit, des écouteurs ajoutés à plusieurs endroits rendent les interactions difficiles à suivre. Vous allez centraliser la lecture des événements et la conversion en actions, afin que le reste de l’application puisse traiter des intentions explicites comme « ajouter cette tâche » ou « supprimer cette tâche ».

Vous avez déjà défini l’état et les actions dans les leçons précédentes. Ici, vous ne modifiez ni la fonction réductrice ni la forme des tâches : vous organisez le passage entre les événements du navigateur et ces actions.

Un adaptateur DOM pour les événements

Un adaptateur DOM est un module qui connaît les éléments et événements de la page, mais ne décide pas lui-même de l’évolution de l’état. Dans ce projet, `dom.js` intercepte la soumission du formulaire et les interactions de la liste, puis transmet des objets action à une fonction fournie par `main.js`.

Repérer les commandes dans la liste

Le HTML de référence fournit déjà les points d’ancrage nécessaires au formulaire et à la liste. Pour reconnaître les commandes d’une tâche lorsque le rendu les ajoutera, vous ajoutez des attributs `data-action` et `data-id` aux éléments concernés : les attributs décrivent leur rôle fonctionnel, tandis que les classes restent réservées à la présentation.

index.htmlHTML
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="theme-color" content="#f4f6f8">
  <title>Gestionnaire de tâches</title>
  <link rel="stylesheet" href="style.css">
  <script type="module" src="js/main.js"></script>
</head>
<body>
  <main class="taches" aria-labelledby="titre-taches">
    <header class="taches__entete">
      <h1 id="titre-taches">Gestionnaire de tâches</h1>
      <p>Organisez vos tâches et suivez votre progression.</p>
    </header>

    <section class="taches__progression" aria-labelledby="titre-progression" data-progression>
      <h2 id="titre-progression">Progression</h2>
      <p><span data-terminees>0</span> tâche(s) terminée(s) sur <span data-total>0</span></p>
      <progress class="taches__barre" data-barre value="0" max="0" aria-label="Progression des tâches">0%</progress>
    </section>

    <form class="taches__formulaire" data-formulaire>
      <label for="nouvelle-tache">Nouvelle tâche</label>
      <input class="taches__champ" id="nouvelle-tache" name="tache" type="text" required maxlength="200" autocomplete="off">
      <button type="submit">Ajouter</button>
    </form>

    <section aria-labelledby="titre-liste">
      <h2 id="titre-liste">Mes tâches</h2>
      <ul class="taches__liste" data-liste aria-live="polite"></ul>
    </section>

    <footer class="taches__commandes">
      <button class="taches__copier" type="button" data-copier>Copier le résumé</button>
    </footer>
    <p class="taches__message" data-message role="status" aria-live="polite"></p>
  </main>
</body>
</html>
Aperçu du rendu
Chargement de l'aperçu…

L’adaptateur vérifie les éléments attendus au démarrage, ce qui rend une erreur de structure plus facile à repérer. Le clic sur la liste est délégué au conteneur ; l’action est déterminée grâce à l’attribut fonctionnel de l’élément le plus proche, et l’identifiant de tâche est lu sur la ligne correspondante.

js/dom.jsJavaScript
export function enregistrerInteractions(racine, envoyerAction) {
  const formulaire = racine.querySelector("[data-formulaire]");
  const liste = racine.querySelector("[data-liste]");

  if (!formulaire || !liste) {
    throw new Error("Le formulaire ou la liste des tâches est absent.");
  }

  formulaire.addEventListener("submit", function gererSoumission(evenement) {
    evenement.preventDefault();

    const donnees = new FormData(formulaire);
    const libelle = String(donnees.get("tache") ?? "").trim();

    if (libelle === "") {
      return;
    }

    envoyerAction({
      type: "tache/ajouter",
      tache: {
        id: crypto.randomUUID(),
        libelle,
        terminee: false
      }
    });

    formulaire.reset();
  });

  liste.addEventListener("click", function gererClicListe(evenement) {
    if (!(evenement.target instanceof Element)) {
      return;
    }

    const commande = evenement.target.closest("[data-action]");
    const ligne = commande?.closest("[data-id]");

    if (!commande || !ligne || !liste.contains(commande)) {
      return;
    }

    const id = ligne.dataset.id;

    if (commande.dataset.action === "basculer") {
      envoyerAction({ type: "tache/basculer", id });
    } else if (commande.dataset.action === "supprimer") {
      envoyerAction({ type: "tache/supprimer", id });
    }
  });
}
Aperçu du rendu
Chargement de l'aperçu…

La soumission du formulaire est empêchée pour éviter le rechargement de la page. Le libellé est transmis dans une tâche accompagnée d’un identifiant ; les actions de bascule et de suppression transmettent, elles, l’identifiant de la tâche visée. Le rendu de ces commandes sera pris en charge dans une leçon ultérieure : la délégation est déjà en place pour les recevoir.

Faire évoluer l’état dans main.js

Le module principal fournit une fonction d’envoi à l’adaptateur. Cette fonction applique chaque action à l’état courant avec `reduireEtat` ; l’état obtenu est affiché dans la console pour vérifier le branchement, sans ajouter ici de logique de rendu.

js/main.jsJavaScript
import { etatInitial } from "./etat.js";
import { reduireEtat } from "./reducteur.js";
import { enregistrerInteractions } from "./dom.js";

let etat = etatInitial;
const racine = document.querySelector(".taches");

if (!racine) {
  throw new Error("Le conteneur principal des tâches est absent.");
}

function appliquerAction(action) {
  etat = reduireEtat(etat, action);
  console.log("État après action :", etat);
}

enregistrerInteractions(racine, appliquerAction);
Aperçu du rendu
Chargement de l'aperçu…

Pour vérifier le formulaire, ouvrez la page depuis un serveur local, saisissez un libellé et validez : une action d’ajout est appliquée et le nouvel état apparaît dans la console. La liste est encore vide, car aucun rendu de tâche n’est ajouté à cette étape ; lorsque des commandes y seront présentes, le même écouteur parent saura traiter leurs clics.

À retenir

  • Centralisez les écouteurs et la lecture du DOM dans `dom.js`.
  • Convertissez chaque événement en une action explicite avant de toucher à l’état.
  • Déléguez les interactions de liste à un conteneur stable et repérez les commandes avec des attributs `data-*`.
  • Laissez `main.js` appliquer les actions avec `reduireEtat` ; le module d’adaptation ne décide pas des transitions.

La prochaine étape consistera à conserver les tâches dans le navigateur afin de les restaurer après une nouvelle visite.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.