Molti eventi vengono emessi al verificarsi di determinate azioni del browser.
Per esempio:
- Un click su un link â inizializza la navigazione verso il suo URL.
- Un click su un pulsante di invio di un form â inizializza lâinvio dello stesso al server.
- Premendo il pulsante del mouse e spostandoci su un testo â lo si seleziona.
Quando gestiamo un evento con JavaScript, potremmo non volere che la corrispondente azione del browser avvenga, vorremmo invece implementare un altro comportamento.
Prevenire le azioni del browser
Ci sono due modi per comunicare al browser che non vogliamo che esegua lâazione predefinita:
- Il più comune è quello di usare il metodo
event.preventDefault(), incluso nellâoggettoevent. - Se il gestore viene assegnato tramite
on<event>(e non tramiteaddEventListener), allora restituirefalsesortirà lo stesso effetto.
In questo HTML un click su un link non innescherà la navigazione verso lâURL, e di fatto il browser non farà nulla:
<a href="/" onclick="return false">Clicca qui</a>
o
<a href="/" onclick="event.preventDefault()">qui</a>
Nel prossimo esempio useremo questa tecnica per creare un menù potenziato tramite JavaScript.
false da un gestore è unâeccezioneIl valore restituito da un gestore di eventi solitamente viene ignorato.
Lâunica eccezione è il return false di un gestore assegnato con lâuso di on<event>.
In tutti gli altri casi, il valore del return viene ignorato. Nello specifico, non ha alcun senso restituire true.
Esempio: il menù
Consideriamo un menù di questo tipo:
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
Ecco come appare applicando qualche stile CSS:
Gli elementi del menù sono implementati come links HTML <a>, e non come pulsanti <button>. Ci sono tante ragioni per fare ciò, per esempio:
- A molte persone piace usare âtasto destroâ â âapri in una nuova finestraâ. Se usassimo
<button>oppure<span>, questa funzionalità non potrebbe essere usata. - I motori di ricerca seguono i link
<a href="...">nel processo di indicizzazione.
Questo è il motivo per cui usiamo <a> nel markup. Ma dato che normalmente intendiamo gestire i click tramite JavaScript, dovremo prevenire le azioni predefinite del browser.
Come in questo caso:
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // ...possono derivare dai dati scaricati dal server, dalla generazione della UI, etc.
return false; // previene l'azione del browser (non reindirizza verso l'URL)
};
Se omettessimo return false, subito dopo lâesecuzione del nostro codice, il browser compirebbe la sua âazione predefinitaâ â navigando quindi verso lâURL impostato nellâhref. E non è ciò che vogliamo, dal momento che stiamo gestendo noi stessi il click.
A proposito, qui lâutilizzo dellâevent delegation rende il nostro menù molto flessibile. Possiamo aggiungere liste annidate e stilizzarle usando i CSS per farle âscendere a moâ di tendinaâ.
Certi eventi scorrono in una sola direzione. Se preveniamo il primo evento, quello seguente (conseguentemente correlato) non ci sarà .
Ad esempio, il mousedown su un campo <input> porta al focus su di esso, e a seguire allâevento focus. Se preveniamo lâevento mousedown, il focus non ci sarà .
Proviamo a cliccare sul primo <input> â verrà innescato lâevento focus. Cliccando una seconda volta, no.
<input value="Il focus funziona" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Cliccami">
Questo perché lâazione del browser viene annullata sul mousedown. Il focus sarà ancora possibile se usiamo un altro modo per entrare nellâinput. Ad esempio con il tasto Tab per spostasi dal primo input al secondo. Ma non più con il click del mouse.
Lâopzione âpassiveâ del gestore
Lâopzione facoltativa passive: true di addEventListener segnala al browser che il gestore non chiamerà preventDefault().
Perché dovrebbe essere necessaria una cosa del genere?
Ci sono alcuni eventi come touchmove su dispositivi mobile (quando lâutente sposta le dita lungo lo schermo), che di default causerebbero lo scrolling, ma ciò può essere evitato con preventDefault() sul gestore.
E così quando il browser rileva questo evento, prima elabora tutti i vari gestori, ed infine, solo nel caso in cui preventDefault non sia stato chiamato da nessuno dei gestori, procede con lo scrolling. Questa cosa potrebbe causare dei ritardi non voluti o dei âjittersâ a video sulla UI.
Lâopzione passive: true comunica al browser che il gestore non annullerà lo scrolling e il browser scrollerà la pagina immediatamente fornendo unâesperienza con la massima fluidità , con gli eventi che verranno gestiti allâoccorrenza.
Per alcuni browser (Firefox, Chrome), passive è impostato a true di default per gli eventi touchstart e touchmove.
event.defaultPrevented
La proprietà event.defaultPrevented sarà true se lâazione predefinita sarà stata prevenuta, altrimenti sarà false.
Relativamente a questo, câè un caso dâuso interessante.
Ricordate nel capitolo Bubbling e capturing quando abbiamo discusso di event.stopPropagation() e del perché interrompere il bubbling fosse una cattiva idea?
Qualche volta, possiamo usare event.defaultPrevented, per avvisare gli altri gestori che lâevento è stato gestito.
Vediamo un esempio pratico.
Di default il browser, nellâevento contextmenu (tasto destro del mouse) mostra il menù contestuale con le opzioni standard. Possiamo prevenirlo e mostrare il nostro, come in questo esempio:
<button>Il click sul tasto destro mostra il menù contestuale</button>
<button oncontextmenu="alert('Mostra il nostro menu'); return false">
Il click sul tasto destro mostra il nostro menù contestuale
</button>
Adesso, in aggiunta a questo menù, ci piacerebbe implementare un menù contestuale sul documento.
Al click sul tasto destro, dovrebbe comparire il relativo menù contestuale.
<p>Click sul tasto destro per il menù contestuale del documento</p>
<button id="elem">Clicca qui col tasto destro per il menu contestuale del pulsante</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contestuale del pulsante");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contestuale del documento");
};
</script>
Il problema è che così facendo, cliccando su elem, otterremmo due menù: quello del pulsante (lâevento va risalendo per via del bubbling) e quello del documento.
Come possiamo evitarlo? Una delle soluzioni è fare questo ragionamento: âQuando gestiamo il click sul tasto destro nel gestore del pulsante, interrompiamo il bubblingâ e usiamo event.stopPropagation():
<p>Click sul tasto destro per il documento</p>
<button id="elem">Click sul tasto destro per il menù del pulsante (sistemato con event.stopPropagation)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Menu contestuale del pulsante");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contestuale del documento");
};
</script>
A questo punto il menù del pulsante funzionerà come previsto. Ma il prezzo sarà alto, perché a quel punto negheremo per sempre lâaccesso alle informazioni relative ai click sul tasto destro, a qualunque altro codice esterno, inclusi contatori che raccolgono statistiche e così via. Ed è una cosa poco saggia.
Una soluzione alternativa potrebbe essere quella di controllare nel gestore del document se lâazione predefinita sia stata prevenuta. Se così fosse, significherebbe che lâevento è stato gestito, e quindi non sarà necessario gestirlo nuovamente.
<p>Click sul tasto destro per il menu del documento (aggiunto un controllo per event.defaultPrevented)</p>
<button id="elem">Click sul tasto destro per il menù del pulsante</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contestuale del pulsante");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Menu contestuale del documento");
};
</script>
Ora funziona tutto correttamente. Se abbiamo elementi annidati, ed ognuno di essi ha un suo menù contestuale, funzionerà anche questo. Dobbiamo solo assicurarci di controllare event.defaultPrevented in ogni gestore di contextmenu.
Come possiamo chiaramente notare, event.stopPropagation() ed event.preventDefault() (conosciuto anche come return false) sono due cose diverse. Non sono relazionate tra loro.
Ci sono pure dei modi alternativi per implementare i menù contestuali annidati. Uno di questi è quello di avere un singolo oggetto globale con un solo gestore per document.oncontextmenu, e metodi che ci permettono di gestire altri gestori al suo interno.
Lâoggetto catturerà ogni click sul tasto destro, controllando tra i suoi gestori ed eseguire quello appropriato.
Ma in questo caso ogni pezzo di codice che vuole implementare un menù contestuale, dovrebbe conoscere lâesistenza di questo oggetto e del suo supporto, invece di avere il proprio gestore per contextmenu.
Riepilogo
Ci sono tante azioni predefinite del browser:
mousedownâ comincia una selezione (spostare il mouse per continuare a selezionare).clicksu<input type="checkbox">â check/uncheck sullâinput.submitâ cliccando su<input type="submit">o premendo su Enter dentro un campo del form, scatena questo evento, ed il browser invia il form subito dopo.keydownâ premendo un tasto può portare ad aggiungere un carattere dentro un campo, o altre azioni.contextmenuâ viene scatenato al click sul tasto destro, e lâazione che ne deriva è quella di mostrare il menù contestuale del browser.- â¦e molti altriâ¦
Tutte le azione predefinite possono essere prevenute se vogliamo gestire gli eventi esclusivamente tramite JavaScript.
Per prevenire unâazione predefinita â si possono usare event.preventDefault() oppure return false. Il secondo metodo è valido solo con gestori assegnati con on<event>.
Lâopzione passive: true di addEventListener comunica al browser che lâazione non sarà prevenuta. La sua utilità si palesa per eventi su dispositivi mobiles, come ad esempio touchstart e touchmove, per comunicare al browser che non deve attendere lâesecuzione di tutti i gestori prima di effettuare lo scrolling.
Se lâazione predefinita è stata prevenuta, il valore di event.defaultPrevented diventa true, altrimenti è false.
Tecnicamente, prevenendo le azioni predefinite del browser e aggiungendo JavaScript, possiamo personalizzare il comportamento di qualunque elemento. Per esempio, possiamo fare in modo che un link <a> si comporti come un pulsante, e un pulsante <button> come un link (redirezione su un altro URL o cose del genere).
Generalmente però, dovremmo mantenere il significato semantico degli elementi HTML. Per esempio, <a> dovrebbe comportare una navigazione, e non essere un pulsante.
Non è âsolamente una cosa buonaâ, ciò rende lâHTML migliore in termini di accessibilità .
Inoltre se consideriamo lâesempio con <a>, notiamo bene che: un browser ci permette di default di aprire questi links in una nuova finestra (cliccando sul tasto destro e con altri mezzi). E agli utenti questo piace. Ma se invece creiamo un pulsante, che si comporta come se fosse un link usando JavaScript, e che appaia come se fosse un link con lâausilio dei CSS, le funzionalità del browser, che specificatamente dedicate agli elementi <a>, non funzioneranno per il pulsante.
Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)