Introduzione
Quando lavori con il DOM, prima o poi ti trovi davanti a un problema molto comune: hai tanti elementi simili, magari creati in modo dinamico, e vuoi assegnare un comportamento a ciascuno di essi. Il primo approccio che viene in mente è aggiungere un listener a ogni elemento singolarmente. Funziona, ma non è sempre la scelta migliore, soprattutto se gli elementi cambiano nel tempo.
In questo tutorial vediamo un sotto-argomento molto pratico: come usare la delega degli eventi per gestire una lista dinamica di card con pulsanti “Rimuovi”. È un caso reale, utile in applicazioni web, dashboard, TODO list e interfacce con contenuti generati al volo.
L’obiettivo è capire non solo come scrivere il codice, ma anche perché la delega è spesso più efficiente e più semplice da mantenere rispetto all’aggiunta di molti listener separati.
Codice completo
// HTML di esempio:
// <ul id="task-list">
// <li class="task-card" data-id="1">
// <span>Studiare addEventListener</span>
// <button class="remove-btn">Rimuovi</button>
// </li>
// </ul>
//
// <button id="add-task">Aggiungi task</button>
const taskList = document.querySelector(´#task-list´);
const addTaskButton = document.querySelector(´#add-task´);
let nextId = 4;
addTaskButton.addEventListener(´click´, () => {
const newTask = document.createElement(´li´);
newTask.className = ´task-card´;
newTask.dataset.id = String(nextId);
newTask.innerHTML = `
<span>Nuova task #${nextId}</span>
<button class="remove-btn">Rimuovi</button>
`;
taskList.appendChild(newTask);
nextId++;
});
// Delegazione: un solo listener sul contenitore
taskList.addEventListener(´click´, (event) => {
// Verifica se il click arriva da un bottone con classe remove-btn
const removeButton = event.target.closest(´.remove-btn´);
// Se il click non riguarda un bottone "Rimuovi", esce subito
if (!removeButton) return;
// Trova la card padre da rimuovere
const taskCard = removeButton.closest(´.task-card´);
if (!taskCard) return;
// Qui potresti usare taskCard.dataset.id per logica applicativa
console.log(´Rimozione task con id:´, taskCard.dataset.id);
taskCard.remove();
}); Spiegazione
Il codice mostra una tecnica molto utile: invece di registrare un event listener su ogni singolo pulsante “Rimuovi”, ne registriamo uno solo sul contenitore della lista, cioè taskList. Questo approccio si chiama delega degli eventi.
1. Creazione dinamica degli elementi
Il pulsante “Aggiungi task” crea nuovi elementi li con dentro un testo e un pulsante. Questi elementi non esistevano all’inizio, quindi se avessimo associato i listener solo al caricamento della pagina, i nuovi pulsanti non avrebbero avuto alcun comportamento.
Con la delega, invece, non importa quando un elemento viene inserito nel DOM: il listener sul contenitore intercetta comunque il click.
2. Come funziona event.target
Dentro il gestore dell’evento, event.target rappresenta l’elemento effettivamente cliccato. Se l’utente clicca sul testo del bottone o su un’icona interna, il target potrebbe non essere esattamente il bottone stesso. Per questo motivo usiamo closest(´.remove-btn´), che risale nel DOM fino a trovare il primo elemento corrispondente al selettore.
3. Perché usare closest()
closest() è molto utile quando il click può avvenire su un figlio interno del bottone. Ad esempio, se nel pulsante inserisci un’icona <svg> o uno <span>, il target potrebbe essere quell’elemento interno. Con closest(´.remove-btn´) ottieni sempre il bottone corretto.
4. Rimozione dell’elemento padre
Una volta trovato il bottone, cerchiamo la card più vicina con closest(´.task-card´). In questo modo identifichiamo l’intero elemento da eliminare. Il metodo remove() lo toglie dal DOM in modo semplice e leggibile.
5. Perché questo approccio è migliore
- Più scalabile: un solo listener gestisce molti elementi.
- Compatibile con contenuti dinamici: funziona anche per elementi creati dopo il caricamento.
- Più facile da mantenere: meno codice ripetuto, meno punti di errore.
- Più efficiente: riduce il numero di listener attivi nella pagina.
Best practice
- Usa la delega quando gli elementi sono molti o dinamici. È il caso ideale per liste, tabelle, menu, notifiche e componenti ripetuti.
- Seleziona un contenitore stabile. Il listener va messo su un elemento che non viene rimosso o ricreato continuamente.
- Controlla sempre il target. Non fidarti del click in modo generico: verifica che l’evento provenga davvero dall’elemento desiderato.
- Preferisci closest() rispetto a controlli manuali complessi. È più leggibile e robusto, soprattutto con markup annidato.
- Evita logiche troppo pesanti nel listener delegato. Se il contenitore gestisce molti eventi diversi, separa le responsabilità con funzioni dedicate.
- Usa attributi data-* per associare dati utili agli elementi, ad esempio un id, uno stato o un tipo di azione.
Errori comuni da evitare
- Mettere il listener su un elemento che non contiene davvero gli eventi da intercettare.
- Usare
event.targetsenza verificare se il click arriva da un figlio interno. - Creare listener individuali per ogni elemento in liste molto grandi, quando basta la delega.
- Rimuovere elementi senza controllare che il nodo trovato sia corretto.
Riepilogo
La delega degli eventi è una delle tecniche più utili quando lavori con il DOM in JavaScript. In questo tutorial abbiamo visto un caso pratico: una lista dinamica di task con pulsanti di rimozione. Invece di assegnare un listener a ogni bottone, abbiamo usato un solo listener sul contenitore e abbiamo filtrato il click con event.target.closest().
Il risultato è un codice più pulito, più efficiente e soprattutto più adatto a interfacce che cambiano nel tempo. Se stai costruendo componenti dinamici, questa è una soluzione che vale la pena conoscere bene.
Approfondisci con risorse ufficiali
- MDN - EventTarget.addEventListener(): documentazione completa su come registrare eventi in JavaScript.
- MDN - Event.target: per capire quale elemento ha originato l’evento.
- MDN - Element.closest(): fondamentale per la delega e il filtraggio degli eventi.
- MDN - DOM events: panoramica generale sul sistema degli eventi nel browser.
