Leçon 11 / 16

Regrouper les modifications du DOM

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

Objectif : Regrouper la création des lignes de tâches et leur insertion dans le DOM afin de limiter les modifications successives de la liste.

Quand la liste contient plusieurs tâches, créer chaque ligne directement dans la page multiplie les modifications du DOM. Vous allez préparer toutes les lignes à l’écart de la page, puis les insérer ensemble. Le rendu reste cohérent avec l’état courant, y compris après une suppression ou une restauration des données.

Construire les lignes hors de la page

Un DocumentFragment est un conteneur temporaire de nœuds. Vous pouvez y créer les éléments de chaque tâche sans les ajouter immédiatement à la page, puis transférer son contenu à la liste avec une seule opération d’insertion. Le fragment lui-même ne devient pas un élément visible : ses enfants sont déplacés vers la liste.

Dans rendu.js, la fonction ci-dessous reconstruit la liste à partir des tâches reçues. Elle conserve les attributs data-* utilisés par les interactions et les classes prévues par le contrat du projet. Le libellé est affecté avec textContent : une tâche saisie par l’utilisateur est ainsi traitée comme du texte, et non comme du code HTML.

js/rendu.jsJavaScript
export function rendreListe(liste, taches) {
  const fragment = document.createDocumentFragment();

  for (const tache of taches) {
    const element = document.createElement("li");
    element.className = "taches__item";
    element.dataset.id = tache.id;

    const caseTache = document.createElement("input");
    caseTache.className = "taches__case";
    caseTache.type = "checkbox";
    caseTache.checked = tache.terminee;
    caseTache.dataset.action = "basculer";
    caseTache.setAttribute("aria-label", `Terminer : ${tache.libelle}`);

    const libelle = document.createElement("span");
    libelle.className = "taches__libelle";
    libelle.textContent = tache.libelle;

    const supprimer = document.createElement("button");
    supprimer.className = "taches__supprimer";
    supprimer.type = "button";
    supprimer.dataset.action = "supprimer";
    supprimer.textContent = "Supprimer";
    supprimer.setAttribute("aria-label", `Supprimer : ${tache.libelle}`);

    element.append(caseTache, libelle, supprimer);
    fragment.append(element);
  }

  liste.replaceChildren(fragment);
}
Aperçu du rendu
Chargement de l'aperçu…

Les identifiants des tâches et les valeurs de data-action correspondent aux interactions centralisées dans dom.js. Chaque ligne conserve donc les mêmes points d’ancrage fonctionnels après son remplacement. La méthode replaceChildren retire le contenu précédent et insère les nouveaux nœuds lors d’un même appel.

Raccorder le rendu à l’état

Pour que l’extrait soit exécutable, le fichier main.js ci-dessous montre le raccordement complet avec les modules déjà présents à cette étape du projet. À chaque action, l’état est remplacé par le résultat de reduireEtat, enregistré, puis la liste est reconstruite en groupe. Le formulaire, les actions sur les tâches, l’observation de la progression et la copie du résumé restent raccordés à leurs adaptateurs existants.

js/main.jsJavaScript
import { etatInitial } from "./etat.js";
import { reduireEtat } from "./reducteur.js";
import { chargerTaches, enregistrerTaches } from "./persistance.js";
import { connecterDOM } from "./dom.js";
import { rendreListe } from "./rendu.js";
import { observerProgression } from "./progression.js";
import { copierResume } from "./clipboard.js";

const liste = document.querySelector("[data-liste]");
const formulaire = document.querySelector("[data-formulaire]");
const champ = document.querySelector("#nouvelle-tache");
const elementMessage = document.querySelector("[data-message]");
const panneauProgression = document.querySelector("[data-progression]");
const nombreTerminees = document.querySelector("[data-terminees]");
const nombreTotal = document.querySelector("[data-total]");
const barre = document.querySelector("[data-barre]");

let etat = { ...etatInitial, taches: chargerTaches() };
let progressionVisible = false;

function afficherProgression() {
  const total = etat.taches.length;
  const terminees = etat.taches.filter((tache) => tache.terminee).length;

  nombreTerminees.textContent = String(terminees);
  nombreTotal.textContent = String(total);
  barre.max = total;
  barre.value = terminees;
  barre.textContent = total === 0 ? "0%" : `${Math.round((terminees / total) * 100)}%`;
}

function afficherEtat() {
  rendreListe(liste, etat.taches);

  if (progressionVisible) {
    afficherProgression();
  }
}

function appliquerAction(action) {
  etat = reduireEtat(etat, action);
  enregistrerTaches(etat.taches);
  afficherEtat();
}

function surCopier() {
  copierResume(etat, elementMessage);
}

connecterDOM({ formulaire, champ, liste, surAction: appliquerAction, surCopier });
observerProgression(panneauProgression, () => {
  progressionVisible = true;
  afficherProgression();
});
afficherEtat();
Aperçu du rendu
Chargement de l'aperçu…

Le HTML de référence ne change pas : main.js trouve la liste et les autres éléments à partir de leurs attributs data-*. Après l’insertion, le navigateur affiche une ligne par tâche ; si la liste est vide, son contenu est simplement remplacé par un fragment vide. Le rendu demeure ainsi une représentation complète et prévisible de la liste portée par l’état.

À retenir

  • Créez les lignes dans un DocumentFragment plutôt que de les insérer une par une dans la page.
  • Remplacez le contenu de la liste avec un seul appel à replaceChildren.
  • Gardez les classes et attributs data-* attendus par les interactions existantes.
  • Utilisez textContent pour afficher le libellé comme texte et non comme HTML.

Dans la prochaine leçon, vous réduirez les calculs répétés en mémorisant certains résultats dérivés de l’état.

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.