Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

jQuery, event delegation senza segreti

Qualche buona pratica per gestire gli eventi e migliorare le performance
Qualche buona pratica per gestire gli eventi e migliorare le performance
Link copiato negli appunti

jQuery fornisce una serie di metodi per "ascoltare" gli eventi generati sulla pagina (click, hover, etc.) e di poter reagire di conseguenza. Abbiamo così una API unificata che nasconda le differenze sintattiche e funzionali dei vari browser.

Event delegation "ereditaria"

L'event delegation si basa su una caratteristica comune a tutti i browser: un evento innescato su un elemento, ad esempio un link, "risale" automaticamente l'albero del documento notificandosi ad ogni elemento padre in un processo demonimato bubbling.

Sfruttando questa caratteristica è possibile agganciare una funzione handler ad un elemento padre ed intercettare gli eventi innescati dai suoi figli. Un esempio semplicistico può essere realizzato in questo modo:

<p id="toolbar">
  <a href="#" class="edit">modifica</a>
  <a href="#" class="delete">cancella</a>
</p>
$('#toolbar').click(function (event) {
  //event.target è l'elemento su cui l'utente ha cliccato
  var $target = $(event.target);
  //inibisce il comportamento standard del link
  event.preventDefault(); 
  if ($target.hasClass('edit')) {
    alert("azione di modifica");
  } else if ($target.hasClass('delete')) {
    alert("cancella l'elemento!");
  }
});

Lo scopo principale di questa tecnica è quello di limitare il numero di funzioni agganciate agli elementi del DOM in modo da ridurre la memoria occupata dal programma e aumentare le prestazioni.

Nel nostro codice, ad esempio, esempio abbiamo evitato di agganciare una funzione per ogni link delegando la gestione dell'evento all'elemento padre.

Un altro campo di applicazione dell'event delegation è quello del caricamento asincrono di pagine HTML: ipotizziamo di avere una lista di elementi cliccabili che si carica man mano che scrolliamo verso il basso, come succede per Facebook.

Utilizzando la tecnica tradizionale agganceremmo una funzione ad ogni elemento che carichiamo dinamicamente, in quanto verrebbe iniettato nel DOM solo dopo l'inizializzazione dell'applicazione.

Delegando la gestione degli eventi ad un contenitore sempre presente nel DOM, invece, ci basterebbe agganciare una sola funzione, visto che ogni interazione sui nuovi elementi risalirebbe comunque a quel genitore.

Metodi 'live' e 'delegate'

jQuery offre due metodi principali per la gestione dell'event delegation:

  • delegate
  • live (deprecato dalla 1.4)

Il primo ha una sintassi identica a bind ma, a differenza, agisce in background in modo da agganciare la funzione
all'elemento document. Il vantaggio nell'utilizzare live è nel fatto che al suo interno i riferimenti della funzione vengono normalizzati per adattare il contesto a quello di un normale evento:

$(document).bind('click', function(event) {
  //this è il document
  //event.target è il tag su cui l'utente ha cliccato
});
$('a').live('click', function() {
  //this è il tag a su cui l'utente ha cliccato
});

A partire dalla versione 1.4 live è stato deprecato in favore di delegate. In questo metodo la sintassi prevede che, a partire dal contenitore, si definisca l'elemento da ascoltare, l'evento da filtrare e la funzione da lanciare:

$('#contenitore').delegate('a', 'click', function () {
	//this è il tag a su cui l'utente ha cliccato
});

Sebbene la funzionalità rispetto a live non differisca, vi sono due fattori che rendono delegate più performante:

  • Anzitutto agganciamo la funzione ad un elemento specifico distribuendo così tutte le funzioni delegate in maniera organica invece di appesantire indiscriminatamente l'oggetto document.
  • In secondo luogo, il fatto di delegare l'evento ad un elemento prossimo a quello si cui l'utente interagisce, riduce sia il tempo di intercettazione, legato ovviamente al tempo che l'evento scatenato impiega nel processo di bubbling, sia eventuali interferenze da parte di altre interazioni in atto.

Ecco un test di esempio per verificare i benefici di .delegate().

Il metodo 'on'

A partire dalla versione 1.7 il modulo di gestione degli eventi è stato riscritto e uniformato con l'aggiunta del metodo on, il quale in base al numero e alla tipologia di argomenti passati, definisce un evento tradizionale o delegato:

$('#contenitore').on('click', function () {
	//evento lanciato al click sul contenitore
	// uguale a .bind('click', ...)
});
$('#contenitore').on('click', 'a', function () {
	//evento lanciato al click su un link interno
	// uguale a .delegate('a', 'click', ...)
});

Poiché le caratteristiche degli argomenti passati e le funzionalità non cambiano, soprattutto perché gli altri metodi di delegazione sono stati deprecati, è una buona idea usare on per i nuovi progetti.

Inoltre, a livello di performance non vi sono grosse differenze, in quanto nel sorgente della libreria delegate e live sono definiti come alias di on.

Evento DOM Ready e performance

L'evento Ready, è quello invocato quando l'albero del DOM è completamente formato ma le risorse esterne, quali immagini e CSS, non sono ancora del tutto caricati, pertanto merita attenzione.

In jQuery possiamo agganciare una funzione a questo evento come argomento del metodo ready oppure del costruttore $(). Sarà la libreria a gestire le differenze nelle implementazioni dei browser:

//come argomento di .ready()
$(document).ready(function () {
  console.log('DOM caricato');
});
//come argomento del costruttore
$(function () {
  console.log('DOM caricato');
});

Questa tecnica garantisce che tutte le operazioni di selezione e manipolazione vengano eseguite su un documento completo evitando, ad esempio, di ricercare nodi non ancora renderizzati dal browser.

Naturalmente questa garanzia ha un prezzo: se il markup è troppo complesso oppure il rendering subisce dei rallentamenti legati al server o al client, tutte le interazioni non saranno subito disponibili all'utente, compromettendo così l'usabilità dell'applicazione.

Agire prima del DOM Ready

Per limitare questa latenza possiamo anzitutto effettuare il prima possibile le operazioni costose in termini di risorse, come i cicli, e non legate al DOM. Ad esempio potremmo creare in anticipo le opzioni di una select dinamica e iniettarle successivamente nel contenitore:

var opzioni = ['rosso', 'giallo', 'blu'],
	html = '',
	$nodi;
//costruisco il markup delle opzioni
$.each(opzioni, function (val, i) {
	html += '<option value="' + i + '">' + val + '</option>';
});
//creo i nodi del DOM da iniettare
$nodi = $(html);
//inietto i nodi solo dopo il DOM ready
$(document).ready(function () {
	$html.appendTo('select#colori');
});

Delegare a document

In secondo luogo, poiché l'elemento document è sempre disponibile durante l'esecuzione degli script, possiamo utilizzare l'event delegation per fornire le interazioni fondamentali dell'interfaccia senza dovere attendere il caricamento di tutto il documento:

//sostituisce subito l'invio dei form di default
//con un'interazione personalizzata
$(document).on('click', ':submit', function (e) {
	e.preventDefault();
	...
});
$(document).ready(function () { ... });

Utilizzando lo script sopra, appena un bottone di invio sarà visualizzato sul browser, esso risponderà al click con l'interazione voluta.

Come risolvere il FOUC

Un altro effetto collegato al ritardo nel caricamento del DOM è il FOUC (Flash Of Unstyled Content) che si verifica quando un elemento viene mostrato dal browser senza lo stile associato. Un esempio tipico di questo effetto è generato, soprattutto nelle versioni di IE inferiori alla 9, da questo script:

$(document).ready(function () {
	$('#mioBox').hide();
});

Per risolvere il problema utilizzeremo una tecnica usata, fra gli altri, da Modernizr con la quale basta agire sul tag html, anch'esso sempre disponibile, e sulle regole CSS della pagina.

Anzitutto dovremo aggiungere al tag una classe no-js di default, quindi aggiungeremo queste righe all'inizio del nostro script:

// <html class="no-js">
var html = document.documentElement; 
//rimuovo la classe no-js e la sostituisco con 'js'
html.className = html.className.replace(/(^|s)no-js(s|$)/, '$1js$2');

Nel CSS basterà modificare lo stile degli elementi in base alla presenza o meno delle classi no-js e js,
delegando così la presentazione degli elementi al layer corretto:

.no-js #mioBox {
	display: block;
}
.js #mioBox {
	display: none;
}

Link Utili

Ti consigliamo anche