Eventi JavaScript
Impara a gestire click, input, submit, scroll e tutti gli eventi del browser
🎪 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!
- ✅ 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.
<button id="btn">Cliccami!</button> <p id="output"></p>
// 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!
Puoi “smettere di ascoltare” un evento con 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 });
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!
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
});
Differenza importante quando hai elementi annidati!
<div id="container">
<button id="btn">
<span>Clicca</span>
</button>
</div>
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');
});
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?
});
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.
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}`);
});
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");
}
});
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';
}
});
// 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.
<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>
// ❌ 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! 😱
});
// ✅ 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
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.
// 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");
});
Ferma la propagazione dell'evento ai parent.
<div id="parent">
<div id="child">
<button id="btn">Click</button>
</div>
</div>
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">×</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>
// === 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...
// === 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'
});
submit- Form principale e editinput- Validazione real-timeclick- Bottoni con event delegationkeydown- Keyboard shortcuts (ESC, Ctrl+K)contextmenu- Click destro personalizzatopreventDefault()- Blocca submit e shortcutsstopPropagation()- Modal click fuori- Event delegation - Cards container
- Data attributes - Actions
✅ Best Practices Eventi
// ✅ MODERNO
elemento.addEventListener('click', handler);
// ❌ EVITA
elemento.onclick = handler; // Legacy
// ✅ 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);
});
// ✅ SEMPRE su submit
form.addEventListener('submit', (e) => {
e.preventDefault(); // Non dimenticare!
// Gestione custom
});
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!
});
| 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 |