Introduzione
Quando si lavora con JavaScript nel browser, gli eventi del DOM sono uno degli strumenti più importanti per rendere una pagina interattiva. In molti casi basta assegnare un gestore a un singolo elemento con addEventListener. Però, appena l’interfaccia diventa dinamica, le cose si complicano: elementi creati dopo il caricamento iniziale, liste aggiornate via JavaScript, pulsanti inseriti al volo, contenuti che cambiano in base all’utente.
In questi scenari, la delega degli eventi è una tecnica molto utile. Invece di collegare un listener a ogni singolo elemento, si ascolta l’evento su un contenitore comune e si decide, in base al target, come reagire. Questo approccio è più efficiente, più facile da mantenere e soprattutto funziona bene con elementi aggiunti dinamicamente.
In questo tutorial vedremo un caso pratico: gestire il caricamento dinamico di contenuti, come una lista di articoli o card, con pulsanti “Carica altro” e azioni interne agli elementi. È un sotto-argomento utile perché capita spesso in dashboard, cataloghi prodotti, feed e interfacce moderne.
Codice completo
<div id="app">
<h2>Articoli</h2>
<button id="loadMore">Carica altri articoli</button>
<div id="articles">
<article class="card" data-id="1">
<h3>Articolo 1</h3>
<p>Contenuto dell´articolo 1.</p>
<button class="save-btn">Salva</button>
</article>
</div>
</div>
<script>
const articlesContainer = document.querySelector(´#articles´);
const loadMoreBtn = document.querySelector(´#loadMore´);
let nextId = 2;
function createArticle(id) {
const article = document.createElement(´article´);
article.className = ´card´;
article.dataset.id = id;
article.innerHTML = `
<h3>Articolo ${id}</h3>
<p>Questo è un contenuto caricato dinamicamente.</p>
<button class="save-btn">Salva</button>
`;
return article;
}
loadMoreBtn.addEventListener(´click´, () => {
const newArticle = createArticle(nextId);
articlesContainer.appendChild(newArticle);
nextId++;
});
articlesContainer.addEventListener(´click´, (event) => {
const saveButton = event.target.closest(´.save-btn´);
if (!saveButton) return;
const card = saveButton.closest(´.card´);
const articleId = card.dataset.id;
console.log(`Articolo ${articleId} salvato`);
saveButton.textContent = ´Salvato´;
saveButton.disabled = true;
});
</script> Spiegazione
Vediamo il funzionamento del codice passo per passo.
1. Struttura iniziale
Abbiamo un contenitore principale con un pulsante Carica altri articoli e un div che ospita le card. All’inizio è presente un solo articolo, ma il sistema è pensato per aggiungerne altri in modo dinamico.
2. Creazione dinamica degli elementi
La funzione createArticle(id) crea un nuovo elemento <article> e gli assegna un contenuto HTML interno. Ogni articolo riceve un attributo data-id, utile per identificare l’elemento in modo semplice e leggibile.
Questa tecnica è molto comune quando si costruiscono liste o componenti lato client. Invece di scrivere HTML statico nel markup iniziale, si genera il contenuto via JavaScript in base ai dati disponibili.
3. Evento sul bottone “Carica altri articoli”
Il listener sul pulsante loadMoreBtn usa addEventListener(´click´, ...) per creare una nuova card e aggiungerla al contenitore con appendChild. Ogni volta che l’utente clicca, il contatore nextId aumenta e viene generato un nuovo articolo.
Questo è il punto in cui emerge il primo vantaggio della delega: i nuovi pulsanti “Salva” inseriti dentro le card non hanno bisogno di un listener dedicato al momento della creazione.
4. Delegazione degli eventi sul contenitore
Il listener principale è attaccato a #articles, cioè al contenitore comune di tutte le card. Quando avviene un click, il browser risale l’albero degli elementi e l’evento arriva anche al contenitore.
Dentro la callback usiamo event.target.closest(´.save-btn´) per capire se il click è partito da un pulsante con classe save-btn oppure da un suo elemento interno. Questo è importante perché il click potrebbe avvenire su un testo, un’icona o un nodo figlio del bottone.
Se closest non trova nulla, usciamo subito con return. Se invece trova il pulsante, cerchiamo la card padre con saveButton.closest(´.card´) e leggiamo l’id dal dataset.
5. Aggiornamento dell’interfaccia
Dopo il salvataggio simulato, cambiamo il testo del pulsante in Salvato e lo disabilitiamo. Questo è un esempio semplice di feedback visivo utile per l’utente. In un’app reale, al posto del console.log potresti inviare una richiesta al server o aggiornare lo stato dell’applicazione.
Best practice
- Usa la delega quando gli elementi sono dinamici: se un contenuto può essere aggiunto dopo il caricamento, evitare listener separati su ogni singolo elemento semplifica molto il codice.
- Preferisci un contenitore stabile: il listener va messo su un elemento che esiste sempre, come una lista, una sezione o un wrapper principale.
- Filtra con closest(): è più robusto di controllare solo
event.target, perché gestisce correttamente click su elementi annidati dentro il bottone. - Separa creazione e comportamento: la funzione che crea l’HTML dovrebbe occuparsi della struttura, mentre la gestione degli eventi dovrebbe stare in un punto centrale e leggibile.
- Usa data-attributes per identificare gli elementi: attributi come
data-idrendono il codice più chiaro rispetto a soluzioni basate su indici o variabili globali. - Evita listener duplicati: aggiungere un listener a ogni elemento creato può diventare costoso e difficile da mantenere, soprattutto con liste lunghe o aggiornamenti frequenti.
- Gestisci i casi limite: se il target non corrisponde a ciò che ti aspetti, esci subito dalla funzione. Questo riduce errori e comportamenti imprevisti.
Riepilogo
In questo tutorial abbiamo visto come combinare addEventListener e delega degli eventi per gestire contenuti creati dinamicamente. Il caso pratico del caricamento di articoli mostra un vantaggio fondamentale: i nuovi elementi funzionano subito, senza dover registrare un listener per ogni card.
Il flusso è semplice:
- un pulsante aggiunge nuovi contenuti al DOM;
- il contenitore principale ascolta i click;
- con
closest()individuiamo il pulsante corretto; - aggiorniamo l’interfaccia in modo immediato.
Questa tecnica è una delle più utili nello sviluppo frontend moderno, perché unisce pulizia del codice, scalabilità e facilità di manutenzione.
Approfondisci con risorse ufficiali
- MDN - EventTarget.addEventListener(): documentazione completa su come registrare gestori di eventi.
- MDN - Event bubbling: per capire come gli eventi risalgono l’albero del DOM.
- MDN - Element.closest(): utile per filtrare gli eventi in modo robusto.
- MDN - data-* attributes: per usare attributi personalizzati nel markup.
- JavaScript.info - Event delegation: una spiegazione molto chiara con esempi pratici.
