Vai al contenuto

Gli eventi

Impara a gestire click, input, submit, scroll e tutti gli eventi del browser.

🎮 Mettilo in pratica: nel corso JavaScript interattivo – progetti ci sono le lezioni Lezione 1 – Il tuo primo click magico e Lezione 5 – Input utente.

➕ Altri esempi: Approfondimento: eventi e progetto To-Do.

Cos'è un Evento?

Gli eventi sono azioni che accadono nel browser: click, digitazione, movimento mouse, caricamento pagina, ecc. JavaScript può "ascoltare" questi eventi e reagire di conseguenza!

Come Funzionano gli Eventi

Pensa agli eventi come a dei "segnali" che il browser invia quando succede qualcosa:

  • 🖱️ L'utente clicca un bottone → evento click
  • ⌨️ L'utente digita in un input → evento input
  • 📜 L'utente scrolla la pagina → evento scroll
  • 📄 La pagina finisce di caricare → evento load

JavaScript può "ascoltare" questi segnali e eseguire codice in risposta!

🎯 In Questa Lezione

  • ✅ addEventListener() - Il modo moderno di gestire eventi
  • ✅ Event Object - Informazioni sull'evento
  • ✅ Tipi di Eventi - Mouse, tastiera, form, window...
  • ✅ Event Delegation - Pattern efficiente
  • ✅ preventDefault() - Bloccare comportamento default
  • ✅ Best Practices - Gestione eventi professionale

addEventListener()

addEventListener() è il metodo moderno (e consigliato) per "ascoltare" eventi su elementi HTML.

  1. Sintassi Base
<button id="btn">Cliccami!</button>
<p id="output"></p>

addEventListener() Base

// Sintassi: elemento.addEventListener('evento', funzione)

const btn = document.querySelector('#btn');
const output = document.querySelector('#output');

// Metodo 1: Funzione normale
btn.addEventListener('click', function() {
    output.textContent = "Bottone cliccato!";
});

// Metodo 2: Arrow function (CONSIGLIATO 🌟)
btn.addEventListener('click', () => {
    output.textContent = "Bottone cliccato!";
});

// Metodo 3: Funzione esterna (riutilizzabile)
function gestisciClick() {
    output.textContent = "Bottone cliccato!";
}

btn.addEventListener('click', gestisciClick);

// ✅ Puoi aggiungere più listener allo stesso evento!
btn.addEventListener('click', () => {
    console.log("Primo listener");
});

btn.addEventListener('click', () => {
    console.log("Secondo listener");
});
// Entrambi vengono eseguiti!
  1. Rimuovere Event Listener

Puoi "smettere di ascoltare" un evento con removeEventListener().

removeEventListener()

const btn = document.querySelector('#btn');

// Definisci funzione PRIMA (serve riferimento!)
function gestisciClick() {
    console.log("Cliccato!");
}

// Aggiungi listener
btn.addEventListener('click', gestisciClick);

// Rimuovi listener
btn.removeEventListener('click', gestisciClick);

// ⚠️ Questo NON funziona (funzioni anonime diverse)
btn.addEventListener('click', () => console.log("A"));
btn.removeEventListener('click', () => console.log("A"));
// Non rimuove nulla! Sono due funzioni diverse

// ✅ Usa riferimento alla funzione
const handler = () => console.log("A");
btn.addEventListener('click', handler);
btn.removeEventListener('click', handler);  // Funziona!

// Esempio: esegui una sola volta
function eseguiUnaVolta() {
    console.log("Eseguito una volta!");
    btn.removeEventListener('click', eseguiUnaVolta);
}

btn.addEventListener('click', eseguiUnaVolta);

// Oppure usa opzione 'once' (moderno)
btn.addEventListener('click', () => {
    console.log("Una volta!");
}, { once: true });

❌ addEventListener vs onclick

const btn = document.querySelector('#btn');

// ❌ VECCHIO MODO - onclick (evita!)
btn.onclick = function() {
    console.log("Click 1");
};

btn.onclick = function() {
    console.log("Click 2");
};
// Solo "Click 2" viene eseguito (sovrascrive!)

// ✅ NUOVO MODO - addEventListener
btn.addEventListener('click', () => {
    console.log("Click 1");
});

btn.addEventListener('click', () => {
    console.log("Click 2");
});
// Entrambi vengono eseguiti!

// Vantaggi addEventListener:
// ✅ Più listener sullo stesso evento
// ✅ Può essere rimosso
// ✅ Opzioni avanzate (once, capture, passive)
// ✅ Standard moderno

Event Object

Quando un evento viene scatenato, JavaScript passa automaticamente un oggetto evento alla funzione handler con informazioni utili!

  1. Accedere all'Event Object

Event Object Base

const btn = document.querySelector('#btn');

// Parametro 'event' (o 'e', 'evt')
btn.addEventListener('click', function(event) {
    console.log(event);  // Oggetto MouseEvent
});

// Arrow function
btn.addEventListener('click', (e) => {
    console.log(e);
});

// Proprietà comuni dell'event object
btn.addEventListener('click', (e) => {
    console.log(e.type);        // "click"
    console.log(e.target);      // Elemento cliccato
    console.log(e.currentTarget); // Elemento con listener (btn)
    console.log(e.timeStamp);   // Timestamp evento
    console.log(e.isTrusted);   // true se evento utente
});
  1. target vs currentTarget

Differenza importante quando hai elementi annidati!

<div id="container">
    <button id="btn">
        <span>Clicca</span>
    </button>
</div>

target vs currentTarget

const container = document.querySelector('#container');

container.addEventListener('click', (e) => {
    console.log('target:', e.target);
    // Elemento EFFETTIVAMENTE cliccato (span, button, o div)

    console.log('currentTarget:', e.currentTarget);
    // Elemento con addEventListener (sempre div#container)
});

// Esempio pratico: sempre usa currentTarget per elemento con listener
const btn = document.querySelector('#btn');

btn.addEventListener('click', (e) => {
    // ❌ Può essere button o span (dipende dove clicchi)
    e.target.classList.add('active');

    // ✅ Sempre il button
    e.currentTarget.classList.add('active');

    // Oppure usa 'this' (non con arrow function!)
    // this.classList.add('active');
});
  1. Proprietà Specifiche per Tipo Evento

Eventi Mouse

elemento.addEventListener('click', (e) => {
    // Posizione mouse
    console.log(e.clientX, e.clientY);  // Coordinate viewport
    console.log(e.pageX, e.pageY);      // Coordinate pagina
    console.log(e.screenX, e.screenY);  // Coordinate schermo

    // Bottoni mouse
    console.log(e.button);  // 0=sinistro, 1=centrale, 2=destro

    // Tasti modificatori
    console.log(e.ctrlKey);   // Ctrl premuto?
    console.log(e.shiftKey);  // Shift premuto?
    console.log(e.altKey);    // Alt premuto?
    console.log(e.metaKey);   // Cmd/Win premuto?
});

Eventi Tastiera

input.addEventListener('keydown', (e) => {
    console.log(e.key);       // Tasto premuto ("a", "Enter", "Escape")
    console.log(e.code);      // Codice fisico ("KeyA", "Enter")
    console.log(e.keyCode);   // Codice numerico (deprecato)

    // Tasti modificatori
    console.log(e.ctrlKey, e.shiftKey, e.altKey);

    // Esempio: Enter per submit
    if (e.key === 'Enter') {
        console.log("Enter premuto!");
    }

    // Esempio: Ctrl+S per salvare
    if (e.ctrlKey && e.key === 's') {
        e.preventDefault();  // Blocca salvataggio browser
        console.log("Salvataggio personalizzato!");
    }
});

Tipi di Eventi

Ci sono molti tipi di eventi diversi! Vediamo i più comuni e utili.

  1. Eventi Mouse

Mouse Events

const elemento = document.querySelector('#elemento');

// click - Click completo (mousedown + mouseup)
elemento.addEventListener('click', (e) => {
    console.log("Cliccato!");
});

// dblclick - Doppio click
elemento.addEventListener('dblclick', (e) => {
    console.log("Doppio click!");
});

// contextmenu - Click destro
elemento.addEventListener('contextmenu', (e) => {
    e.preventDefault();  // Blocca menu contestuale
    console.log("Click destro!");
});

// mousedown - Bottone premuto
// mouseup - Bottone rilasciato
elemento.addEventListener('mousedown', (e) => {
    console.log("Mouse premuto");
});

// mouseenter - Mouse entra (non bubbling)
// mouseleave - Mouse esce (non bubbling)
elemento.addEventListener('mouseenter', (e) => {
    elemento.style.background = 'lightblue';
});

elemento.addEventListener('mouseleave', (e) => {
    elemento.style.background = '';
});

// mouseover - Mouse entra (con bubbling)
// mouseout - Mouse esce (con bubbling)

// mousemove - Mouse si muove
elemento.addEventListener('mousemove', (e) => {
    console.log(`X: ${e.clientX}, Y: ${e.clientY}`);
});
  1. Eventi Tastiera

Keyboard Events

const input = document.querySelector('#input');

// keydown - Tasto premuto (ripete se tenuto)
input.addEventListener('keydown', (e) => {
    console.log(`Premuto: ${e.key}`);
});

// keyup - Tasto rilasciato
input.addEventListener('keyup', (e) => {
    console.log(`Rilasciato: ${e.key}`);
});

// keypress - Tasto carattere (DEPRECATO, usa keydown)

// Esempi pratici
input.addEventListener('keydown', (e) => {
    // Escape per cancellare
    if (e.key === 'Escape') {
        input.value = '';
    }

    // Enter per submit
    if (e.key === 'Enter') {
        console.log("Submit:", input.value);
    }

    // Blocca numeri
    if (e.key >= '0' && e.key <= '9') {
        e.preventDefault();
        console.log("Numeri non ammessi!");
    }

    // Shortcuts
    if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        console.log("Salva");
    }
});
  1. Eventi Form

Form Events

const form = document.querySelector('#form');
const input = document.querySelector('#input');
const select = document.querySelector('#select');

// submit - Form inviato
form.addEventListener('submit', (e) => {
    e.preventDefault();  // IMPORTANTE: blocca invio default!

    const formData = new FormData(form);
    console.log("Form inviato:", Object.fromEntries(formData));
});

// input - Valore cambia (ogni carattere)
input.addEventListener('input', (e) => {
    console.log("Valore corrente:", e.target.value);
});

// change - Valore cambia e perde focus
input.addEventListener('change', (e) => {
    console.log("Valore finale:", e.target.value);
});

// focus - Elemento riceve focus
input.addEventListener('focus', (e) => {
    console.log("Input in focus");
    input.style.borderColor = 'blue';
});

// blur - Elemento perde focus
input.addEventListener('blur', (e) => {
    console.log("Input perde focus");
    input.style.borderColor = '';
});

// select - Testo selezionato
input.addEventListener('select', (e) => {
    console.log("Testo selezionato");
});

// Esempio validazione real-time
input.addEventListener('input', (e) => {
    const value = e.target.value;

    if (value.length < 3) {
        input.style.borderColor = 'red';
    } else {
        input.style.borderColor = 'green';
    }
});
  1. Eventi Window e Document

Window/Document Events

// load - Pagina caricata (immagini, CSS, tutto)
window.addEventListener('load', () => {
    console.log("Pagina completamente caricata!");
});

// DOMContentLoaded - DOM caricato (senza aspettare immagini)
document.addEventListener('DOMContentLoaded', () => {
    console.log("DOM pronto!");
    // ✅ Momento migliore per inizializzare JS
});

// resize - Finestra ridimensionata
window.addEventListener('resize', () => {
    console.log(`Dimensioni: ${window.innerWidth}x${window.innerHeight}`);
});

// scroll - Pagina scrollata
window.addEventListener('scroll', () => {
    console.log(`Scroll: ${window.scrollY}px`);
});

// beforeunload - Prima di lasciare pagina
window.addEventListener('beforeunload', (e) => {
    e.preventDefault();
    e.returnValue = '';  // Mostra conferma
    return '';
});

// Esempio: Sticky header on scroll
const header = document.querySelector('header');

window.addEventListener('scroll', () => {
    if (window.scrollY > 100) {
        header.classList.add('sticky');
    } else {
        header.classList.remove('sticky');
    }
});

// Esempio: Responsive behavior
window.addEventListener('resize', () => {
    if (window.innerWidth < 768) {
        console.log("Mobile");
    } else {
        console.log("Desktop");
    }
});

Event Delegation

Un pattern potente per gestire eventi su molti elementi (anche futuri!) con un solo listener sul parent.

  1. Problema e Soluzione
<ul id="lista">
    <li>Item 1 <button class="delete">X</button></li>
    <li>Item 2 <button class="delete">X</button></li>
    <li>Item 3 <button class="delete">X</button></li>
</ul>
<button id="add">Aggiungi Item</button>

Senza Event Delegation (❌)

// ❌ PROBLEMA: Listener su ogni bottone
const deleteButtons = document.querySelectorAll('.delete');

deleteButtons.forEach(btn => {
    btn.addEventListener('click', (e) => {
        e.target.closest('li').remove();
    });
});

// Problemi:
// 1. Performance: molti listener
// 2. Nuovi elementi NON hanno listener
document.querySelector('#add').addEventListener('click', () => {
    const li = document.createElement('li');
    li.innerHTML = 'Nuovo <button class="delete">X</button>';
    document.querySelector('#lista').append(li);
    // Questo bottone NON funziona! 😱
});

Con Event Delegation (✅)

// ✅ SOLUZIONE: Un listener sul parent
const lista = document.querySelector('#lista');

lista.addEventListener('click', (e) => {
    // Verifica se cliccato è bottone delete
    if (e.target.classList.contains('delete')) {
        e.target.closest('li').remove();
    }
});

// Oppure con matches()
lista.addEventListener('click', (e) => {
    if (e.target.matches('.delete')) {
        e.target.closest('li').remove();
    }
});

// Ora anche elementi futuri funzionano! ✅
document.querySelector('#add').addEventListener('click', () => {
    const li = document.createElement('li');
    li.innerHTML = 'Nuovo <button class="delete">X</button>';
    lista.append(li);
    // Funziona automaticamente! 🎉
});

// Vantaggi:
// ✅ Un solo listener (performance)
// ✅ Funziona con elementi futuri
// ✅ Meno memoria utilizzata
  1. Pattern Avanzato con closest()

Event Delegation Avanzato

const container = document.querySelector('#container');

container.addEventListener('click', (e) => {
    // Trova elemento con data-action
    const action = e.target.closest('[data-action]');

    if (!action) return;  // Nessuna action trovata

    const actionType = action.dataset.action;

    switch(actionType) {
        case 'delete':
            action.closest('.item').remove();
            break;

        case 'edit':
            const item = action.closest('.item');
            console.log("Modifica:", item.dataset.id);
            break;

        case 'like':
            action.classList.toggle('liked');
            break;
    }
});

// HTML
// <div class="item" data-id="1">
//     <button data-action="edit">Modifica</button>
//     <button data-action="delete">Elimina</button>
//     <button data-action="like">Like</button>
// </div>

preventDefault() e stopPropagation()

Metodi per controllare il comportamento degli eventi.

  1. preventDefault() - Blocca Comportamento Default

preventDefault() Esempi

// Form submit
const form = document.querySelector('#form');

form.addEventListener('submit', (e) => {
    e.preventDefault();  // Blocca invio e reload pagina!

    // Gestione custom
    console.log("Form gestito via JavaScript");
});

// Link
const link = document.querySelector('a');

link.addEventListener('click', (e) => {
    e.preventDefault();  // Blocca navigazione

    console.log("Link cliccato ma non seguito");
});

// Click destro
document.addEventListener('contextmenu', (e) => {
    e.preventDefault();  // Blocca menu contestuale

    // Mostra menu custom
    console.log("Menu custom qui");
});

// Checkbox
const checkbox = document.querySelector('#check');

checkbox.addEventListener('click', (e) => {
    if (someCondition) {
        e.preventDefault();  // Blocca check/uncheck
        console.log("Checkbox non modificabile");
    }
});

// Drag & drop
const dropZone = document.querySelector('#drop-zone');

dropZone.addEventListener('dragover', (e) => {
    e.preventDefault();  // Necessario per drop!
});

dropZone.addEventListener('drop', (e) => {
    e.preventDefault();
    console.log("File droppato");
});
  1. stopPropagation() - Ferma Bubbling

Ferma la propagazione dell'evento ai parent.

<div id="parent">
    <div id="child">
        <button id="btn">Click</button>
    </div>
</div>

Event Bubbling

const parent = document.querySelector('#parent');
const child = document.querySelector('#child');
const btn = document.querySelector('#btn');

// Senza stopPropagation
parent.addEventListener('click', () => {
    console.log("Parent cliccato");
});

child.addEventListener('click', () => {
    console.log("Child cliccato");
});

btn.addEventListener('click', () => {
    console.log("Button cliccato");
});

// Click su button stampa:
// "Button cliccato"
// "Child cliccato"    ← bubbling
// "Parent cliccato"   ← bubbling

// Con stopPropagation
btn.addEventListener('click', (e) => {
    e.stopPropagation();  // Ferma bubbling!
    console.log("Button cliccato");
});

// Ora stampa solo:
// "Button cliccato"

// Caso d'uso: Modal/dropdown
const modal = document.querySelector('.modal');
const modalContent = document.querySelector('.modal-content');
const closeBtn = document.querySelector('.close');

// Chiudi cliccando fuori
modal.addEventListener('click', () => {
    modal.style.display = 'none';
});

// NON chiudere cliccando dentro
modalContent.addEventListener('click', (e) => {
    e.stopPropagation();  // Non propaga a modal
});

Esempio Completo: App Interattiva

<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Interactive App</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial; padding: 20px; background: #f5f5f5; }
        .container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; }

        /* Form */
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input, select { width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px; font-size: 16px; }
        input:focus { border-color: #4CAF50; outline: none; }
        input.error { border-color: #f44336; }
        .error-message { color: #f44336; font-size: 14px; margin-top: 5px; display: none; }
        button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; }
        .btn-primary { background: #4CAF50; color: white; }
        .btn-primary:hover { background: #45a049; }

        /* Cards */
        .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 15px; margin-top: 20px; }
        .card { background: #f9f9f9; padding: 15px; border-radius: 8px; transition: transform 0.3s, box-shadow 0.3s; }
        .card:hover { transform: translateY(-5px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
        .card h3 { margin-bottom: 10px; color: #333; }
        .card p { color: #666; margin-bottom: 10px; }
        .card-actions { display: flex; gap: 10px; }
        .btn-edit { background: #2196F3; color: white; padding: 5px 10px; font-size: 14px; }
        .btn-delete { background: #f44336; color: white; padding: 5px 10px; font-size: 14px; }

        /* Modal */
        .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; }
        .modal.active { display: flex; align-items: center; justify-content: center; }
        .modal-content { background: white; padding: 30px; border-radius: 10px; max-width: 500px; width: 90%; }
        .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
        .close { cursor: pointer; font-size: 24px; }

        /* Stats */
        .stats { display: flex; gap: 15px; margin-top: 20px; }
        .stat-box { flex: 1; background: #e3f2fd; padding: 15px; border-radius: 8px; text-align: center; }
        .stat-box h4 { color: #1976d2; margin-bottom: 5px; }
        .stat-box p { font-size: 24px; font-weight: bold; color: #1565c0; }
    </style>
</head>
<body>
    <div class="container">
        <h1>📋 Task Manager Interattivo</h1>

        <!-- Form -->
        <form id="task-form">
            <div class="form-group">
                <label for="title">Titolo *</label>
                <input type="text" id="title" placeholder="Inserisci titolo..." required>
                <div class="error-message" id="title-error">Titolo obbligatorio (min 3 caratteri)</div>
            </div>

            <div class="form-group">
                <label for="description">Descrizione</label>
                <input type="text" id="description" placeholder="Descrizione opzionale...">
            </div>

            <div class="form-group">
                <label for="priority">Priorità</label>
                <select id="priority">
                    <option value="low">Bassa</option>
                    <option value="medium" selected>Media</option>
                    <option value="high">Alta</option>
                </select>
            </div>

            <button type="submit" class="btn-primary">Aggiungi Task</button>
        </form>

        <!-- Stats -->
        <div class="stats" id="stats">
            <div class="stat-box">
                <h4>Totale</h4>
                <p id="stat-total">0</p>
            </div>
            <div class="stat-box">
                <h4>Alta Priorità</h4>
                <p id="stat-high">0</p>
            </div>
            <div class="stat-box">
                <h4>In Corso</h4>
                <p id="stat-active">0</p>
            </div>
        </div>

        <!-- Cards -->
        <div class="cards" id="cards-container"></div>

        <!-- Modal -->
        <div class="modal" id="modal">
            <div class="modal-content">
                <div class="modal-header">
                    <h2 id="modal-title">Modifica Task</h2>
                    <span class="close" id="modal-close">&times;</span>
                </div>
                <form id="edit-form">
                    <div class="form-group">
                        <label for="edit-title">Titolo</label>
                        <input type="text" id="edit-title">
                    </div>
                    <div class="form-group">
                        <label for="edit-description">Descrizione</label>
                        <input type="text" id="edit-description">
                    </div>
                    <div class="form-group">
                        <label for="edit-priority">Priorità</label>
                        <select id="edit-priority">
                            <option value="low">Bassa</option>
                            <option value="medium">Media</option>
                            <option value="high">Alta</option>
                        </select>
                    </div>
                    <button type="submit" class="btn-primary">Salva Modifiche</button>
                </form>
            </div>
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>

JavaScript - script.js (PARTE 1)

// === SELEZIONE ELEMENTI ===
const taskForm = document.querySelector('#task-form');
const titleInput = document.querySelector('#title');
const descriptionInput = document.querySelector('#description');
const prioritySelect = document.querySelector('#priority');
const cardsContainer = document.querySelector('#cards-container');
const modal = document.querySelector('#modal');
const modalClose = document.querySelector('#modal-close');
const editForm = document.querySelector('#edit-form');

// Stats
const statTotal = document.querySelector('#stat-total');
const statHigh = document.querySelector('#stat-high');
const statActive = document.querySelector('#stat-active');

// Error
const titleError = document.querySelector('#title-error');

// === DATI ===
let tasks = [];
let nextId = 1;
let editingTaskId = null;

// === VALIDAZIONE ===
function validateTitle(value) {
    return value.trim().length >= 3;
}

// Validazione real-time
titleInput.addEventListener('input', (e) => {
    const value = e.target.value;

    if (value.length === 0) {
        titleInput.classList.remove('error');
        titleError.style.display = 'none';
    } else if (!validateTitle(value)) {
        titleInput.classList.add('error');
        titleError.style.display = 'block';
    } else {
        titleInput.classList.remove('error');
        titleError.style.display = 'none';
    }
});

// === FUNZIONI ===
function createTaskCard(task) {
    const card = document.createElement('div');
    card.className = 'card';
    card.dataset.id = task.id;
    card.dataset.priority = task.priority;

    const priorityEmoji = {
        low: '🟢',
        medium: '🟡',
        high: '🔴'
    };

    card.innerHTML = `
        <h3>${priorityEmoji[task.priority]} ${task.title}</h3>
        <p>${task.description || 'Nessuna descrizione'}</p>
        <div class="card-actions">
            <button class="btn-edit" data-action="edit">Modifica</button>
            <button class="btn-delete" data-action="delete">Elimina</button>
        </div>
    `;

    return card;
}

function renderTasks() {
    cardsContainer.innerHTML = '';

    tasks.forEach(task => {
        const card = createTaskCard(task);
        cardsContainer.append(card);
    });

    updateStats();
}

function updateStats() {
    statTotal.textContent = tasks.length;
    statHigh.textContent = tasks.filter(t => t.priority === 'high').length;
    statActive.textContent = tasks.length;
}

function addTask(taskData) {
    const newTask = {
        id: nextId++,
        ...taskData
    };

    tasks.push(newTask);
    renderTasks();
}

function deleteTask(id) {
    tasks = tasks.filter(t => t.id !== id);
    renderTasks();
}

function openEditModal(id) {
    const task = tasks.find(t => t.id === id);
    if (!task) return;

    editingTaskId = id;

    document.querySelector('#edit-title').value = task.title;
    document.querySelector('#edit-description').value = task.description;
    document.querySelector('#edit-priority').value = task.priority;

    modal.classList.add('active');
}

function closeModal() {
    modal.classList.remove('active');
    editingTaskId = null;
}

function updateTask(id, taskData) {
    const task = tasks.find(t => t.id === id);
    if (!task) return;

    Object.assign(task, taskData);
    renderTasks();
}

// Continua nella parte 2...

JavaScript - script.js (PARTE 2)

// === EVENT LISTENERS ===

// 1. Submit form principale
taskForm.addEventListener('submit', (e) => {
    e.preventDefault();

    const title = titleInput.value.trim();

    if (!validateTitle(title)) {
        titleInput.classList.add('error');
        titleError.style.display = 'block';
        titleInput.focus();
        return;
    }

    const taskData = {
        title: title,
        description: descriptionInput.value.trim(),
        priority: prioritySelect.value
    };

    addTask(taskData);

    // Reset form
    taskForm.reset();
    titleInput.focus();
});

// 2. Event delegation su cards
cardsContainer.addEventListener('click', (e) => {
    const action = e.target.dataset.action;
    const card = e.target.closest('.card');

    if (!card) return;

    const taskId = parseInt(card.dataset.id);

    switch(action) {
        case 'edit':
            openEditModal(taskId);
            break;

        case 'delete':
            if (confirm('Sicuro di voler eliminare questa task?')) {
                deleteTask(taskId);
            }
            break;
    }
});

// 3. Modal close
modalClose.addEventListener('click', closeModal);

// Click fuori modal per chiudere
modal.addEventListener('click', (e) => {
    if (e.target === modal) {
        closeModal();
    }
});

// 4. Edit form submit
editForm.addEventListener('submit', (e) => {
    e.preventDefault();

    const taskData = {
        title: document.querySelector('#edit-title').value.trim(),
        description: document.querySelector('#edit-description').value.trim(),
        priority: document.querySelector('#edit-priority').value
    };

    updateTask(editingTaskId, taskData);
    closeModal();
});

// 5. Keyboard shortcuts
document.addEventListener('keydown', (e) => {
    // ESC per chiudere modal
    if (e.key === 'Escape' && modal.classList.contains('active')) {
        closeModal();
    }

    // Ctrl/Cmd + K per focus su input
    if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
        e.preventDefault();
        titleInput.focus();
    }
});

// 6. Prevent context menu su cards
cardsContainer.addEventListener('contextmenu', (e) => {
    const card = e.target.closest('.card');

    if (card) {
        e.preventDefault();
        console.log('Menu personalizzato qui');
    }
});

// === INIZIALIZZAZIONE ===
console.log('🚀 App inizializzata!');
titleInput.focus();

// Task di esempio
addTask({
    title: 'Studiare Eventi JavaScript',
    description: 'Completare lezione 14',
    priority: 'high'
});

addTask({
    title: 'Fare la spesa',
    description: 'Latte, pane, frutta',
    priority: 'medium'
});

addTask({
    title: 'Chiamare il dentista',
    description: 'Prenotare visita',
    priority: 'low'
});

💡 Eventi Usati nell'Esempio

  • submit - Form principale e edit
  • input - Validazione real-time
  • click - Bottoni con event delegation
  • keydown - Keyboard shortcuts (ESC, Ctrl+K)
  • contextmenu - Click destro personalizzato
  • preventDefault() - Blocca submit e shortcuts
  • stopPropagation() - Modal click fuori
  • Event delegation - Cards container
  • Data attributes - Actions

Best Practices Eventi

🌟 Regole d'Oro

  1. Usa addEventListener()
// ✅ MODERNO
elemento.addEventListener('click', handler);

// ❌ EVITA
elemento.onclick = handler;  // Legacy
  1. Event Delegation per Liste
// ✅ Un listener sul parent
container.addEventListener('click', (e) => {
    if (e.target.matches('.delete-btn')) {
        // Gestisci
    }
});

// ❌ Listener su ogni elemento
items.forEach(item => {
    item.addEventListener('click', handler);
});
  1. preventDefault() su Form
// ✅ SEMPRE su submit
form.addEventListener('submit', (e) => {
    e.preventDefault();  // Non dimenticare!
    // Gestione custom
});
  1. Debounce Eventi Frequenti

Per eventi come scroll, resize, input che scattano molto spesso.

// Debounce function
function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func(...args), wait);
    };
}

// ✅ Con debounce (performance)
window.addEventListener('scroll', debounce(() => {
    console.log('Scroll!');
}, 200));

// ❌ Senza debounce (troppi trigger!)
window.addEventListener('scroll', () => {
    console.log('Scroll!');  // Eseguito centinaia di volte!
});

🎯 Riepilogo Lezione

Concetto Sintassi Quando Usare
addEventListener el.addEventListener('click', fn) ⭐ Sempre (standard moderno)
Event Object (e) => { e.target } Accedere info evento
preventDefault e.preventDefault() Form, link, keyboard
stopPropagation e.stopPropagation() Fermare bubbling
Event Delegation Listener su parent Liste dinamiche
Debounce Delay esecuzione scroll, resize, input