Un evento è un segnale che sta ad indicare che è avvenuto qualcosa. Tutti i nodi DOM generano questi segnali (anche se gli eventi non sono limitati al DOM).
Ecco quindi una lista, degli eventi DOM più utili:
Eventi del mouse:
clickâ quando si clicca col mouse su un elemento (i dispositivi touch lo generano tramite il tocco).contextmenuâ quando si clicca col tasto destro su un elemento.mouseover/mouseoutâ quando il cursore passa sopra/abbandona un elemento.mousedown/mouseupâ quando viene premuto/rilasciato il pulsante del mouse su un elemento.mousemoveâ quando si sposta il mouse.
Eventi da tastiera:
keydownekeyupâ quando viene premuto e rilasciato un tasto.
Eventi degli elementi del form:
submitâ quando lâutente invia un<form>.focusâ quando lâutente attiva il focus su un elemento, ad esempio su un<input>.
Eventi del Document:
DOMContentLoadedâ quando lâHTML viene caricato e processato, e la costruzione del DOM è stata completata.
Eventi dei CSS:
transitionendâ quando termina unâanimazione CSS (CSS-animation).
Ci sono molti altri eventi più specifici, che verranno affrontati in dettaglio nei prossimi capitoli.
Gestori di evento
Per reagire agli eventi possiamo assegnare un gestore (handler). Questo non è altro che una funzione che viene eseguita contestualmente alla generazione di un evento.
I gestori, quindi, sono un modo per eseguire codice JavaScript al verificarsi delle azioni dellâutente ed esistono vari modi per assegnare un evento.
Partiamo dal più semplice.
Attributo HTML
Un gestore può essere impostato in HTML con un attributo chiamato on<event>.
Ad esempio, per assegnare un gestore al click di un input, possiamo usare onclick:
<input value="Cliccami" onclick="alert('Click!')" type="button">
Al click del mouse, il codice dentro onclick verrà eseguito.
Nota bene che dentro onclick useremo gli apici singoli, in quanto lâattributo stesso è già inserito allâinterno di apici doppi. Se ci dimenticassimo che il codice stesse dentro lâattributo, ed usassimo gli apici doppi come in questo caso: onclick="alert("Click!")", il codice non funzionerebbe.
Un attributo HTML non è un buon posto per scrivere tanto codice, quindi è molto meglio creare una funzione JavaScript per poterla richiamare.
In questo esempio, al click viene eseguita la funzione countRabbits():
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Coniglio numero " + i);
}
}
</script>
<input type="button" onclick="countRabbits()" value="Conta i conigli!">
Come sappiamo, gli attributi HTML non sono case-sensitive, quindi scrivere ONCLICK va bene tanto quanto onClick e onCLICKâ¦ma solitamente vengono scritti in minuscolo: onclick.
Proprietà del DOM
Possiamo assegnare un gestore usando una proprietà DOM on<event>.
Ad esempio, elem.onclick:
<input id="elem" type="button" value="Cliccami">
<script>
elem.onclick = function() {
alert('Grazie');
};
</script>
Se il gestore viene assegnato usando un attributo HTML, il browser lo riconosce, crea una nuova funzione partendo dal contenuto dellâattributo e la scrive nella proprietà del DOM.
In sostanza, questa modalità equivale alla precedente.
I due codici messi a confronto, infatti, lavorano alla stessa maniera:
-
Solo HTML:
<input type="button" onclick="alert('Click!')" value="Button"> -
HTML + JS:
<input type="button" id="button" value="Button"> <script> button.onclick = function() { alert('Click!'); }; </script>
Lâunica differenza è che nel primo esempio, lâattributo HTML viene usato per inizializzare il button.onclick, invece nel secondo per inizializzare lo script.
Dal momento che câè solo una proprietà onclick, non è possibile assegnare più di un gestore evento.
Aggiungendo un gestore tramite JavaScript, si va a sovrascrivere il gestore esistente:
<input type="button" id="elem" onclick="alert('Prima')" value="Click me">
<script>
elem.onclick = function() { // sovrascrive il gestore precedente
alert('Dopo'); // viene mostrato solo questo
};
</script>
Per rimuovere un gestore, assegnare elem.onclick = null.
Accedere allâelemento: this
Il valore di this allâinterno di un gestore è lâelemento contenente il gestore.
Qui il button mostra il suo contenuto tramite this.innerHTML:
<button onclick="alert(this.innerHTML)">Cliccami</button>
Possibili errori
Se stai affrontando da poco lâargomento degli eventi, nota bene alcune sottigliezze.
Possiamo impostare come gestore una funzione esistente:
function sayThanks() {
alert('Grazie!');
}
elem.onclick = sayThanks;
Ma attenzione: la funzione deve essere assegnata scrivendo sayThanks, e non sayThanks().
// corretto
button.onclick = sayThanks;
// errato
button.onclick = sayThanks();
Se aggiungessimo le parentesi, allora sayThanks() diverrebbe una chiamata a funzione. Di conseguenza il valore dellâassegnazione dellâultima riga dellâesempio, sarebbe il risultato della chiamata, ossia undefined (dato che la funzione non restituisce nulla), e verrebbe assegnato ad onclick. Ovviamente così non potrebbe andare bene, ed inoltre non sarebbe nemmeno lâeffetto voluto.
â¦Dâaltra parte, però, nel markup abbiamo bisogno delle parentesi:
<input type="button" id="button" onclick="sayThanks()">
La differenza è molto semplice: quando il browser legge lâattributo, crea una funzione che fa da gestore, il cui corpo è il contenuto dellâattributo.
Quindi il markup crea questa proprietà :
button.onclick = function() {
sayThanks(); // <-- il contenuto dell'attributo va a finire qui
};
Non usare setAttribute per i gestori.
Ed ancora, una chiamata del genere non funzionerà :
// un click sul <body> genera errori,
// perché gli attributi sono sempre stringhe, e la funzione diventa una stringa
document.body.setAttribute('onclick', function() { alert(1) });
Il case della proprietà DOM è rilevante.
Assegnare un gestore a elem.onclick, e non a elem.ONCLICK, in quanto le proprietà del DOM sono case-sensitive.
addEventListener
Il problema principale della sopracitata maniera di assegnare i gestori è che non abbiamo modo di assegnare dei gestori multipli a un evento.
Ipotizziamo che una parte del nostro codice serva ad evidenziare un pulsante al click, e che un altro serva a mostrare un messaggio al medesimo click.
Per fare questo sarebbe bello poter assegnare due eventi distinti, ma sappiamo che ogni nuova proprietà DOM con lo stesso nome, sovrascriverà la precedente:
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // sostituisce il gestore precedente
Gli sviluppatori degli standard web hanno intuito la cosa tempo addietro, e hanno suggerito un modo alternativo per trattare i gestori, usando i metodi speciali addEventListener e removeEventListener, i quali non sono affetti da questi problemi.
Ecco la sintassi per aggiungere un gestore:
element.addEventListener(event, handler, [options]);
event- Nome dellâevento, ad esempio
"click". handler- La funzione che fa da gestore.
options- Un oggetto opzionale aggiuntivo con delle proprietà :
once: setrue, il listener viene rimosso automaticamente una volta innescato.capture: la fase in cui deve essere gestito lâevento, argomento affrontato più avanti nel capitolo Bubbling e capturing. Per ragioni storiche,optionspossono essere anchefalse/true, ed è equivale a scrivere{capture: false/true}.passive: setrue, il gestore non chiamerÃpreventDefault(), anche questo, verrà spiegato successivamente nel capitolo Azioni predefinite del browser.
Per rimuovere lâevento, si usa removeEventListener:
element.removeEventListener(event, handler, [options]);
Per rimuovere un gestore dobbiamo passare come parametro, la stessa funzione che abbiamo usato per lâassegnazione.
Il seguente codice non fa quello che ci aspetteremmo:
elem.addEventListener( "click" , () => alert('Grazie!'));
// ....
elem.removeEventListener( "click", () => alert('Grazie!'));
Il gestore non verrà rimosso, perchè removeEventListener prende come parametro unâaltra funzione: è certamente con lo stesso codice, ma questo non ha alcuna rilevanza, dal momento che è un oggetto funzione differente (fanno riferimento a due differenti indirizzi di memoria).
La maniera corretta per farlo è questa:
function handler() {
alert( 'Thanks!' );
}
input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);
Nota bene: se non assegnassimo la funzione a una variabile, non potremmo rimuoverla: non câè alcun modo di ârisalireâ ai gestori assegnati tramite addEventListener.
Chiamate multiple a addEventListener permettono di aggiungere gestori multipli:
<input id="elem" type="button" value="Click me"/>
<script>
function handler1() {
alert('Grazie!');
};
function handler2() {
alert('Grazie di nuovo!');
}
elem.onclick = () => alert("Ciao");
elem.addEventListener("click", handler1); // Grazie!
elem.addEventListener("click", handler2); // Grazie di nuovo!
</script>
Come visto nellâesempio, possiamo impostare i gestori in entrambi i modi sia con lâausilio di una proprietà DOM che di addEventListener. Generalmente però, scegliamo un solo approccio.
addEventListenerEsistono eventi che non possono essere assegnati tramite una proprietà DOM, ma solo con addEventListener.
Un esempio di ciò, è lâevento DOMContentLoaded, innescato quando viene completamente caricato il documento e costruita tutta la struttura del DOM.
// non viene mai eseguito
document.onDOMContentLoaded = function() {
alert("DOM costruito");
};
// in questo modo funziona
document.addEventListener("DOMContentLoaded", function() {
alert("DOM costruito");
});
Conseguentemente, addEventListener è più universale, benché questi eventi siano unâeccezione più che la regola.
Oggetto evento
Per gestire correttamente un evento, vorremmo saperne di più su cosa è avvenuto. Non solamente se è stato un âclickâ o un âkeydownâ, ma, ad esempio, quali erano le coordinate del puntatore? Che tasto è stato premuto? E così via.
Quando câè un evento, il browser crea un oggetto evento (event object), inserisce i dettagli al suo interno e lo passa come argomento al gestore.
Ecco un esempio per ottenere le coordinate dallâoggetto evento:
<input type="button" value="Clicami" id="elem">
<script>
elem.onclick = function(event) {
// mostra il tipo di evento, l'elemento e le coordinate del click
alert(event.type + " su " + event.currentTarget);
alert("Coordinate: " + event.clientX + ":" + event.clientY);
};
</script>
Alcune proprietà dellâoggetto event:
event.type- Tipo di evento, in questo caso è un
"click". event.currentTarget- Lâelemento che ha gestito lâevento. Questo è equivalente a
this, ma se il gestore è una arrow function, o se il suothisè legato a qualcosâaltro, possiamo usareevent.currentTarget. event.clientX / event.clientY- Coordinate del cursore relative alla Window, per eventi del puntatore.
Esistono tante altre proprietà ., molte delle quali dipendono dal tipo di evento: gli eventi della tastiera hanno un gruppo di proprietà , gli eventi del puntatore un altro ancora, e li studieremo più avanti quando andremo a vedere i vari eventi nel dettaglio.
Anche se assegniamo un gestore dentro lâHTML, possiamo usare lâoggetto evento:
<input type="button" onclick="alert(event.type)" value="Event type">
Questo è possibile perché quando il browser legge lâattributo, crea un gestore con questa forma: function(event) { alert(event.type) }. Il primo argomento viene chiamato "event", e il corpo è preso dallâattributo.
Gestori oggetto: handleEvent
Con addEventListener possiamo assegnare non solo una funzione, ma anche un oggetto. Quando viene generato un evento, viene chiamato il suo metodo handleEvent.
Ad esempio:
<button id="elem">Cliccami</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " su " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
Come possiamo osservare, se addEventListener riceve un oggetto come gestore, allora chiama obj.handleEvent(event) nel caso ci sia un evento.
Possiamo usare anche una classe:
<button id="elem">Cliccami</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Premuto un pulsante del mouse";
break;
case 'mouseup':
elem.innerHTML += "...e rilasciato.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
Lâoggetto gestisce entrambi gli eventi. Nota bene che usando addEventListener dobbiamo impostare esplicitamente gli eventi affinché rimangano in ascolto.
Nel nostro esempio, lâoggetto menu rimane in ascolto solamente per mousedown e mouseup, e nessun altro tipo di evento.
Tuttavia, il metodo handleEvent non deve necessariamente fare tutto il lavoro da solo. Può infatti chiamare altri metodi specifici per tipologia di evento:
<button id="elem">Cliccami</button>
<script>
class Menu {
handleEvent(event) {
// mousedown -> onMousedown
let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
this[method](event);
}
onMousedown() {
elem.innerHTML = "Premuto il pulsante del mouse";
}
onMouseup() {
elem.innerHTML += "...e rilasciato.";
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
Qui i gestori sono chiaramente separati, il che può essere più comodo da gestire.
Riepilogo
Ci sono 3 modalità per assegnare dei gestori di evento:
- Attributo HTML:
onclick="...". - Proprietà DOM:
elem.onclick = function. - Metodi:
elem.addEventListener(event, handler[, phase])per aggiungerlo,removeEventListenerper rimuoverlo.
Gli attributi HTML vengono usati raramente, perchè un JavaScript nel bel mezzo di un tag HTML, non solo è un poâ strano, ma è anche avulso dal contesto. Inoltre in questo modo non vi si può inserire dentro tanto codice.
Le proprietà DOM si possono usare, ma non potremo assegnare più di un gestore per un particolare evento. In molti casi questa limitazione non è troppo pesante.
Lâultimo modo è il più flessibile, ma è anche il più lungo da scrivere. Alcuni eventi funzionano solo con questâultima modalità , ad esempio transitionend e DOMContentLoaded (affrontato più avanti). Inoltre addEventListener supporta gli oggetti come gestori di evento. In questo caso, però, verrà chiamato il metodo handleEvent al verificarsi degli eventi.
Non importa come assegni un gestore, in ogni caso il primo argomento passato sarà un oggetto evento, contenente i dettagli su ciò che è avvenuto.
Nei prossimi capitoli, avremo modo di approfondire il tema degli eventi in generale e le loro differenti tipologie.

Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)