Eventi nel DOM con addEventListener e delega: un approccio pratico per gestire interazioni complesse

by Anastasia P.
SHARE
Eventi nel DOM con addEventListener e delega: un approccio pratico per gestire interazioni complesse
© Guida-HTML5.it

Introduzione

Quando lavori con il DOM in JavaScript, prima o poi ti trovi davanti a un problema molto comune: tanti elementi simili, tante interazioni, e il rischio di scrivere codice duplicato o poco manutenibile. In questi casi, addEventListener è lo strumento base per registrare eventi in modo chiaro e flessibile, mentre la delega degli eventi ti permette di ascoltare un solo evento su un contenitore e gestire anche gli elementi creati dinamicamente.

In questo tutorial scegliamo un sotto-argomento molto utile e spesso sottovalutato: come usare la delega per gestire azioni diverse sullo stesso contenitore, ad esempio pulsanti “Modifica”, “Elimina” e “Preferito” in una lista di task. È un caso reale, frequente nelle dashboard, nei carrelli e nelle applicazioni CRUD.

L’obiettivo non è solo capire la teoria, ma imparare a scrivere un codice pulito, scalabile e facile da estendere.

Codice completo

<div id="app">
  <h2>Task manager</h2>

  <form id="taskForm">
    <input id="taskInput" type="text" placeholder="Inserisci una nuova attività" />
    <button type="submit">Aggiungi</button>
  </form>

  <ul id="taskList"></ul>
</div>

<script>
  const form = document.querySelector(´#taskForm´);
  const input = document.querySelector(´#taskInput´);
  const taskList = document.querySelector(´#taskList´);

  // Stato iniziale: alcune attività di esempio
  const tasks = [
    { id: 1, text: ´Studiare addEventListener´, favorite: false },
    { id: 2, text: ´Provare la delega degli eventi´, favorite: true },
    { id: 3, text: ´Refactor del codice´, favorite: false }
  ];

  function renderTasks() {
    taskList.innerHTML = ´´;

    tasks.forEach(task => {
      const li = document.createElement(´li´);
      li.dataset.id = task.id;

      li.innerHTML = `
        <span class="task-text">${task.favorite ? ´★ ´ : ´´}${task.text}</span>
        <button type="button" data-action="favorite">Preferito</button>
        <button type="button" data-action="edit">Modifica</button>
        <button type="button" data-action="delete">Elimina</button>
      `;

      taskList.appendChild(li);
    });
  }

  form.addEventListener(´submit´, (event) => {
    event.preventDefault();

    const text = input.value.trim();
    if (text === ´´) return;

    tasks.push({
      id: Date.now(),
      text,
      favorite: false
    });

    input.value = ´´;
    renderTasks();
  });

  // Delega degli eventi: un solo listener per tutti i pulsanti della lista
  taskList.addEventListener(´click´, (event) => {
    const button = event.target.closest(´button[data-action]´);

    // Se il click non arriva da un pulsante valido, esci subito
    if (!button || !taskList.contains(button)) return;

    const action = button.dataset.action;
    const taskItem = button.closest(´li´);
    const taskId = Number(taskItem.dataset.id);

    const taskIndex = tasks.findIndex(task => task.id === taskId);
    if (taskIndex === -1) return;

    if (action === ´delete´) {
      tasks.splice(taskIndex, 1);
      renderTasks();
      return;
    }

    if (action === ´favorite´) {
      tasks[taskIndex].favorite = !tasks[taskIndex].favorite;
      renderTasks();
      return;
    }

    if (action === ´edit´) {
      const currentText = tasks[taskIndex].text;
      const newText = prompt(´Modifica il testo della task:´, currentText);

      if (newText === null) return;

      const trimmed = newText.trim();
      if (trimmed === ´´) return;

      tasks[taskIndex].text = trimmed;
      renderTasks();
    }
  });

  renderTasks();
</script>

Spiegazione

Il codice sopra risolve un problema tipico: una lista di elementi con più pulsanti per ogni riga. Se provassi ad attaccare un listener a ogni singolo bottone, ogni volta che aggiungi, rimuovi o aggiorni un task dovresti anche gestire nuovamente i listener. Questo approccio funziona, ma diventa presto inefficiente e fragile.

1. Rendering della lista

La funzione renderTasks() costruisce il contenuto della lista a partire dall’array tasks. Ogni elemento contiene un <li> con un attributo data-id per identificare in modo univoco il task. Dentro il task vengono inseriti tre pulsanti con data-action diverso:

  • favorite per alternare lo stato preferito
  • edit per modificare il testo
  • delete per rimuovere il task

Questa scelta è molto utile perché separa la logica visiva dall’azione da eseguire. In altre parole, il pulsante non dice “cosa fare” con codice duplicato, ma espone solo un’etichetta semantica leggibile dal listener centrale.

2. Gestione dell’invio del form

Il listener sul form intercetta l’evento submit. Qui usiamo event.preventDefault() per evitare il refresh della pagina. Dopo un controllo minimo sull’input, aggiungiamo un nuovo oggetto all’array e rilanciamo il rendering.

Questo è un pattern molto comune nelle applicazioni frontend: aggiorno lo stato, poi ridisegno l’interfaccia. Anche se il codice non usa framework, il principio è lo stesso che trovi in React, Vue o Svelte.

3. Delega degli eventi sulla lista

La parte più importante è questa:

taskList.addEventListener(´click´, ...)

Invece di ascoltare il click su ogni singolo bottone, ascoltiamo il click sull’intero contenitore <ul>. Quando avviene un click, controlliamo se il target reale dell’evento è un pulsante con attributo data-action. Per farlo usiamo event.target.closest(), che è molto più robusto del semplice event.target, perché funziona anche se l’utente clicca su un elemento interno del bottone, come un’icona o uno span.

Questa è la logica:

  • il click arriva sul contenitore
  • si risale al bottone più vicino con closest()
  • si legge l’azione con button.dataset.action
  • si identifica il task tramite li.dataset.id
  • si esegue l’operazione corretta con una struttura condizionale

4. Perché questa soluzione è utile

Il vantaggio principale è la scalabilità. Anche se la lista cresce, il numero di listener resta uno solo. Inoltre, quando ricrei il contenuto della lista con innerHTML, non perdi la gestione degli eventi: il listener è attaccato al contenitore, non ai singoli elementi distrutti e ricreati.

Un altro vantaggio è la manutenibilità. Se domani vuoi aggiungere un pulsante “Duplica”, ti basta inserire un nuovo data-action e aggiungere un nuovo ramo nella logica del listener.

Best practice

  • Usa data-attribute per distinguere le azioni: data-action e data-id rendono il codice chiaro e facilmente estendibile.
  • Preferisci closest() quando il click può avvenire su elementi annidati dentro il bottone.
  • Valida sempre i dati in ingresso: controlla stringhe vuote, valori nulli e ID non trovati.
  • Evita listener duplicati: se gli elementi sono molti o dinamici, la delega è quasi sempre la scelta migliore.
  • Rendi il rendering una funzione separata: così la UI dipende dallo stato e non da manipolazioni sparse nel codice.
  • Usa type="button" nei pulsanti interni al form: evita invii accidentali del form quando premi un pulsante di azione.
  • Non abusare di innerHTML in contesti complessi: va bene per esempi e interfacce semplici, ma in casi più delicati valuta creazione nodi con createElement.

Riepilogo

In questo tutorial hai visto come combinare addEventListener e delega degli eventi per gestire una lista dinamica con più azioni per elemento. Il punto chiave è spostare l’ascolto dell’evento su un contenitore comune e usare attributi come data-action per capire quale comportamento eseguire.

Questo approccio è particolarmente utile quando:

  • gli elementi vengono creati o rimossi dinamicamente
  • hai più azioni sullo stesso componente
  • vuoi evitare codice ripetitivo e listener sparsi
  • cerchi una struttura più facile da mantenere nel tempo

Se impari a leggere bene event.target, closest() e i data-attribute, avrai in mano una tecnica fondamentale per scrivere interfacce JavaScript più robuste e professionali.

Approfondisci con risorse ufficiali

  • MDN - EventTarget.addEventListener(): documentazione completa su come registrare eventi nel DOM.
  • MDN - Event.target: per capire da quale elemento parte realmente l’evento.
  • MDN - Element.closest(): utile per risalire al nodo più vicino che soddisfa un selettore.
  • MDN - data-* attributes: guida ufficiale all’uso degli attributi personalizzati nel markup.
  • MDN - Event delegation: approfondimento concettuale sulla delega degli eventi.

SHARE