À propos de ce cours
Chapitre 13 — Les événements · 13.5 preventDefault et la délégation d'événements
• annuler l'action par défaut du navigateur avec
preventDefault(); • connaître les actions par défaut les plus courantes ;
• écrire un seul écouteur pour de nombreux éléments grâce à la délégation ;
• utiliser
closest() et contains() pour identifier l'élément concerné.1. preventDefault : annuler l'action du navigateur
Beaucoup d'événements ont une action par défaut: un clic sur un lien ouvre la page, un clic sur une case la coche,
une touche dans un champ écrit un caractère, l'envoi d'un formulaire recharge la page, le clic droit ouvre un menu…
ev.preventDefault() annule cette action, sans arrêter la propagation.
📄 l5_preventdefault.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<p><a href="https://www.exemple.tn" id="lien">Lien vers un site extérieur</a></p>
<label class="choix"><input type="checkbox" id="verrou" checked> Option verrouillée (impossible à décocher)</label>
<label>Code (chiffres uniquement) <input id="code" inputmode="numeric"></label>
<pre id="sortie"></pre>
<script>
const ecrire = (t) => (document.querySelector("#sortie").textContent += t + "\n");
// 1. Un lien : on demande confirmation avant de quitter le site
document.querySelector("#lien").addEventListener("click", (ev) => {
ev.preventDefault(); // le navigateur ne suit pas le lien
ecrire(`Lien intercepté : ${ev.currentTarget.href}`);
});
// 2. Une case : son changement d'état est annulé
document.querySelector("#verrou").addEventListener("click", (ev) => {
ev.preventDefault();
ecrire("Case : le clic est annulé, elle reste cochée");
});
// 3. Un champ : on refuse les touches qui ne sont pas des chiffres
document.querySelector("#code").addEventListener("keydown", (ev) => {
const autorisees = ["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"];
if (!/^\d$/.test(ev.key) && !autorisees.includes(ev.key)) {
ev.preventDefault();
ecrire(`Touche « ${ev.key} » refusée (defaultPrevented = ${ev.defaultPrevented})`);
}
});
</script>
</body>
</html>

Après un clic sur le lien, sur la case, puis la saisie de « 12a3 » (le champ contient « 123 »)
Annuler une action attendue peut dérouter l'utilisateur : un lien qui ne mène nulle part, une case qu'on ne peut pas décocher… Donnez toujours un retour visible (message, confirmation). Et pour refuser des caractères, la validation du chapitre 14 est souvent plus adaptée : on peut coller du texte sans déclencher
keydown! 2. La délégation d'événements
Grâce à la remontée, un clic sur n'importe quel bouton d'une liste arrive aussi sur la liste elle-même. Plutôt que de poser un
écouteur sur chaque bouton, on en pose un seul sur le parent et on regarde ev.target pour savoir ce qui a été cliqué.

Figure 1 — Un écouteur pour toute la liste, même les éléments ajoutés plus tard.
📄 l5_delegation.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<style>
#liste { list-style: none; padding: 0; display: grid; gap: 6px; }
#liste li { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: #f5f7f8; border-radius: 8px; }
#liste li.faite span { text-decoration: line-through; color: #90a4ae; }
#liste li button { margin-left: auto; padding: 4px 10px; background: #c62828; }
#liste li .fait { margin-left: 0; background: #2e7d32; }
</style>
</head>
<body>
<form id="ajout" class="btns">
<input id="texte" placeholder="Nouvelle tâche" required>
<button>Ajouter</button>
</form>
<ul id="liste">
<li><button class="fait" aria-label="Marquer comme faite">✓</button><span>Réviser le chapitre 13</span><button class="suppr">Supprimer</button></li>
<li><button class="fait" aria-label="Marquer comme faite">✓</button><span>Faire le TP galerie</span><button class="suppr">Supprimer</button></li>
</ul>
<pre id="sortie"></pre>
<script>
const liste = document.querySelector("#liste");
const ecrire = (t) => (document.querySelector("#sortie").textContent = t);
// UN SEUL écouteur, sur le parent, pour TOUS les boutons — même ceux ajoutés plus tard
liste.addEventListener("click", (ev) => {
const bouton = ev.target.closest("button"); // l'élément cliqué peut être un enfant du bouton
if (!bouton || !liste.contains(bouton)) return;
const tache = bouton.closest("li");
if (bouton.classList.contains("suppr")) {
ecrire(`Supprimée : « ${tache.querySelector("span").textContent} »`);
tache.remove();
} else if (bouton.classList.contains("fait")) {
tache.classList.toggle("faite");
ecrire(`Basculée : « ${tache.querySelector("span").textContent} »`);
}
});
// Ajout dynamique : aucun écouteur à ajouter sur les nouveaux boutons
document.querySelector("#ajout").addEventListener("submit", (ev) => {
ev.preventDefault();
const champ = document.querySelector("#texte");
const li = document.createElement("li");
li.innerHTML = '<button class="fait" aria-label="Marquer comme faite">✓</button><span></span><button class="suppr">Supprimer</button>';
li.querySelector("span").textContent = champ.value; // textContent : pas d'injection de HTML
liste.append(li);
champ.value = "";
ecrire(`Ajoutée. Nombre d'écouteurs sur la liste : toujours 1`);
});
</script>
</body>
</html>

Après l'ajout d'une tâche, son marquage comme faite, puis la suppression de la première
ev.target.closest("button")remonte de l'élément cliqué jusqu'au bouton le plus proche : cela fonctionne même si l'on clique sur une icône à l'intérieur du bouton.liste.contains(bouton)vérifie que ce bouton fait bien partie de la liste.- Les tâches ajoutées après coup fonctionnent immédiatement, sans nouvel écouteur.
- Le texte saisi est inséré avec
textContent, jamais avecinnerHTML: sinon, un utilisateur pourrait injecter du HTML ou du script.
Avantages : moins d'écouteurs (moins de mémoire), aucun oubli pour les éléments créés dynamiquement, un seul endroit à lire et à corriger.
•
preventDefault() annule l'action par défaut (lien, case, saisie, envoi, menu…), sans arrêter la propagation.• Délégation : un écouteur sur le parent,
ev.target.closest(sélecteur) pour trouver l'élément concerné.• La délégation fonctionne pour les éléments ajoutés plus tard.
•
stopPropagation empêche la délégation de fonctionner : à éviter dans les enfants.🛠 Activité 5 — Un tableau de notes éditable
Mission : Créez un tableau d'élèves (nom, note) avec, sur chaque ligne, les boutons « + 1 », « − 1 » et « Supprimer », et un formulaire pour ajouter un élève.
Questions pour avancer (répondez dans votre compte rendu, puis appliquez dans votre projet) :
- Combien d'écouteurs faudrait-il sans délégation pour 30 élèves ? Avec délégation ?
- Écrivez l'écouteur unique. Comment distinguez-vous les trois boutons ? Utilisez un attribut
data-actionplutôt qu'une classe : pourquoi est-ce plus clair ? - Ajoutez une icône (
<span>) dans chaque bouton. Que vautev.targetquand on clique sur l'icône ? Votre code fonctionne-t-il encore ? Corrigez avecclosest(). - La note doit rester entre 0 et 20. Où placez-vous cette vérification ?
- Un double-clic sur une note la rend modifiable (champ texte) ; Entrée valide, Échap annule. Utilisez la délégation pour
dblclicketkeydown. Quand faut-ilpreventDefault()? - Insérez un nom d'élève contenant
<b>test</b>, d'abord avecinnerHTMLpuis avectextContent. Que constatez-vous ? Quel risque cela représente-t-il ?
Je vérifie / je rends :
- ☐ Le tableau fonctionnel avec un seul écouteur de clic.
← 13.4 La propagation des événements : bubbling, capture et stopPropagation13.6 TP 13 : Galerie photos interactive →