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

Interazione utente-browser: gli eventi e JavaScript

Tutto sugli eventi JavaScript: dai rollover alle interazioni complesse con l'utente
Tutto sugli eventi JavaScript: dai rollover alle interazioni complesse con l'utente
Link copiato negli appunti

Il Modello d'Evento

In questo articolo vorrei introdurre e descrivere i modelli che i principali browser utilizzano per rappresentare e gestire gli eventi, e le possibilità e i limiti che il JavaScript ha nel gestirli, in modo da evitare possibili conflitti nei vostri script e, cosa ancor più utile, poter sfruttare questi stessi modelli per la creazione di codici veramente efficienti.

Presterò particolare attenzione alla gestione degli eventi nei browser più recenti, che fanno uso del DOM del W3C e dei suoi metodi. Per poter meglio comprendere il contenuto dell'articolo è utile dapprima leggere la guida JavaScript e in particolare la sezione riguardante, per l'appunto, gli eventi.

La maggior parte delle applicazioni e degli script DHTML ha come presupposto l'interazione tra browser e utente. Come è facile intuire, mettere in comunicazione macchina e uomo non è un compito semplice, soprattutto se la complessità delle operazioni è elevata. È necessario un intermediario che traduca alla macchina le intenzioni dell'uomo e all'uomo la logica della macchina.

Questo intermediario è rappresentato dal modello che i browser adottano per descrivere gli eventi. I problemi nascono subito da qui; infatti i principali browser, Explorer e Netscape, sin dalle vecchie versioni, utilizzano modelli differenti. Queste differenze sono rimaste anche nelle ultime versioni nelle quali Netscape ha seguito le specifiche del W3C, mentre Explorer ha seguito una propria logica.

Spesso si tende a tralasciare quest'aspetto per il fatto che fare uso degli eventi, definirli, associarli agli elementi e a funzioni è un'operazione piuttosto intuitiva, che in una normale pagina HTML è eseguita decine di volte. Ma non tutto ciò che è intuitivo è allo stesso tempo, facile da capire. Magari non tutti (me compreso) conoscono le leggi della fisica che stanno dietro alla possibilità di parlare, o quali muscoli sono utilizzati, o a quali frequenze emettiamo i suoni...eppure la gente parla! Ho reso l'idea?

Descrivere il modello d'evento è quindi importante, soprattutto se l'intenzione è creare pagine dinamiche.

Nota: Gli esempi e gli script proposti lungo l'articolo sono stati testati per girare su Netscape 6.x, Mozilla 1.0 (e tutti i browser compatibili con le specifiche del W3C), su Explorer 5.x e superiori. La compatibilità con Opera sarà considerata di volta in volta.

Si sono tralasciate, in generale, le versioni 4 dei browser.

Il Flusso degli Eventi

Consideriamo di avere il seguente pezzo di codice:

<div ID="livello1" onclick="alert('livello 1')">Che cosa succede
 <div ID="livello2" onclick="alert('livello 2')">se premete su
  <div ID="livello3" onclick="alert('livello 3')">questo testo?
  </div>
 </div>
</div>

Cercate di rispondere a questa domanda. Siete in grado di stabilire quali listener (in questo caso le funzioni di alert) risponderanno all'evento generato dalla pressione sul testo e in quale ordine? Provate:

Che cosa succede

se premete su

questo testo?

Come vedete, se premete sulle parole "questo testo?" (contenuto nel livello annidato più internamente), l'evento onclick è attivato non solo su questo livello, ma anche su quelli che lo contengono. L'ordine con il quale l'evento si propaga rispecchia quello con cui sono stati annidati i livelli: dall'interno verso l'esterno.

Questo dovrebbe farvi intuire la natura "mobile" degli eventi, una natura strettamente legata al DOM. Nel nostro caso, infatti, l'evento risale la struttura gerarchica degli elementi e si lascia intercettare anche dagli altri event handler (nell'esempio gli onclick) definiti nei livelli superiori.

Ma come sono definiti gli eventi?

Il World Wide Web Consortium (W3C) definisce l'oggetto Event, attraverso il quale descrive un evento come un flusso (Event flow) che si propaga da un punto di partenza ad uno d'arrivo. Questo Event flow è il processo attraverso il quale un qualsiasi evento generato in una pagina, percorre la struttura gerarchica del documento (DOM) fino ad arrivare al nodo "bersaglio" (target) dell'evento e, una volta raggiunto, ripercorre a ritroso il DOM fino ad uscirne.

In sostanza, il flusso è costituito da una "andata", definita capturing flow, e da un "ritorno", il bubbling flow.

Per esempio, se consideriamo la seguente pagina:

<html>
 <head>
  <title>Il Modello d'evento</title>
 <body>
  <a href="pagina.html">link</a>
 </body>
</html>

Il flusso d'evento che scaturisce dalla pressione del link, è costituito dalla sua fase di capturing, che inizia dall'elemento in cima alla struttura gerarchica della pagina (l'oggetto document) fino ad arrivare all'elemento bersaglio del flusso (il tag <a>):

Fase di capturing del modello d'evento
Fase di capturing del modello d'evento

In questa fase, l'evento passerà per gli elementi più esterni prima di raggiungere l'elemento obiettivo.

Nella seguente fase di bubbling, il flusso d'evento risalirà la struttura del DOM dall'interno verso l'esterno, passando di nuovo per gli elementi gerarchicamente a livello superiore:

Fase di bubbling del modello d'evento
Fase di bubbling del modello d'evento

Nel corso della sua propagazione, in entrambe le direzioni, l'evento si comporta come una voce, in grado di essere ascoltata e interpretata da opportuni elementi, chiamati event listener (le funzioni associate agli eventi), che hanno la capacità di utilizzare, bloccare o ignorare il flusso d'evento.

In parole povere, se un evento è "acceso" da un particolare elemento della pagina, lo stesso evento si accende in tutti i nodi che contengono l'elemento in questione. Se in questi nodi sono presenti degli event listener l'evento sarà poi elaborato.

Inserire gli Event Handlers

Il primo passo per poter sfruttare questi eventi, che scorrono durante la navigazione tra i più disparati elementi della pagina, è quello di definire gli event handler e associarli ai nostri elementi. Questa è un'operazione piuttosto familiare e che non riserva particolari sorprese. Ma dove interviene l'event flow? Consideriamo i vari casi:

Inserire Event Handlers attraverso l'HTML

È il metodo classico, quello usato anche nell'esempio precedente, che inserisce gli event handler all'interno dei tag HTML desiderati. Facciamo un piccolo esempio in cui creiamo un semplice effetto di rollover sul testo

<span style="font-size:13px;color:red;" onmouseover="testo_over(this)" onmouseout="testo_out(this)">
testo con effetto rollover inserito via HTML
</span>

Nel quale le funzioni richiamate hanno il seguente codice:

function testo_over(oggetto){
  oggetto.style.fontStyle="italic"
  oggetto.style.color="white"
  oggetto.style.backgroundColor="red"
  }

function testo_out(oggetto){
  oggetto.style.fontStyle="normal"
  oggetto.style.color="red"
  oggetto.style.backgroundColor="white"
  }

testo con effetto rollover inserito via HTML

Da notare l'uso della keyword this passata come parametro delle funzioni, che crea un riferimento all'oggetto che ha generato l'evento.

Definendo in questo modo gli event handler, si sottintende, che l'evento sarà ascoltato nella sua fase di bubbling; questo risulta abbastanza chiaro se si riconsidera il primo esempio proposto, nel quale definendo attraverso l'HTML gli event handlers, l'evento generato dalla pressione sul livello più interno risaliva verso l'esterno.

Con questa tecnica è possibile definire anche più funzioni associate allo stesso evento, utilizzando la sintassi:

<TAG event_handler="funzione1();funzione2()">
contenuto del TAG
</TAG>

Quindi ad esempio possiamo scrivere:

<span STYLE="font-size:13px;color:red;"
  onmouseover="testo_over(this)"
  onmouseout="testo_out(this);alert('rollover completato')">
secondo testo con effetto rollover inserito via HTML
</span>

secondo testo con effetto rollover inserito via HTML

Inserire Event Handlers attraverso il JavaScript

Anche questo metodo dovrebbe essere noto, soprattutto per chi già ha creato pagine in DHTML. Il metodo consiste nel definire l'event handler, come una proprietà dell'oggetto su cui si vuole intervenire, e assegnargli, come valore, un riferimento alla funzione da eseguire.Ripetiamo l'esempio di prima utilizzando questa tecnica:

<SPAN ID="testo" STYLE="font-size:13px;color:red;">
testo con effetto rollover inserito via JavaScript
</SPAN>

In questo caso ho inserito l'attributo ID per semplificare l'operazione di recupero dell'elemento. L'effetto di rollover sarà assegnato dal seguente script:

function testo_on(){
  this.style.fontStyle="italic"
  this.style.color="white"
  this.style.backgroundColor="red"
  }

function testo_off(){
  this.style.fontStyle="normal"
  this.style.color="red"
  this.style.backgroundColor="white"
  }

window.onload = function() {
  document.getElementById("testo").onmouseover = testo_on
  document.getElementById("testo").onmouseout = testo_off
  }

testo con effetto rollover inserito via JavaScript

Da osservare:

1) Innanzitutto, il fatto che l'event handler è assegnato come una proprietà dell'oggetto, e quindi ne adotta il comportamento; infatti ad esempio, la stessa operazione poteva essere compiuta con la seguente sintassi:

  document.getElementById("testo")["onmouseover"] = testo_on
  document.getElementById("testo")["onmouseout"] = testo_off

2) l'utilizzo di this, differente rispetto alla tecnica precedente, che, in questo caso, può essere usato nel corpo delle funzioni grazie al fatto che definiamo gli event handler direttamente riferendoci all'elemento desiderato.

3) le funzioni sono assegnate agli eventi con una sintassi priva delle parentesi (). Questo perché si deve assegnare all'evento un riferimento alla funzione, che sarà richiamata ogni volta che sull'elemento si verifica l'evento in questione; se avessimo usato la sintassi:

  document.getElementById("testo").onmouseover = testo_on()

all'evento onmouseover si sarebbe assegnato il valore restituito dalla funzione, generando, in questo caso, un errore.

4) L'uso dell'event handler onload, definito come proprietà dell'oggetto window, che garantisce che l'elemento con ID pari a "testo" sia effettivamente stato caricato. Notate Inoltre la definizione della funzione anonima associata all'evento.

Questa seconda tecnica, offre il vantaggio di poter assegnare gli event handler dinamicamente. Se, ad esempio, avessimo il seguente elemento:

<span id="dinamico" style="color:red;font-size:13px"> inseriamo gli event handlers dinamicamente</span>

Possiamo inserire l'effetto di rollover, ad esempio, solo dopo la pressione di un pulsante, nel quale è lanciata la seguente funzione:

function handler_dinamico(){
  document.getElementById("dinamico").onmouseover = testo_on
  document.getElementById("dinamico").onmouseout = testo_off
  }

Verifichiamolo:

inseriamo gli event handlers dinamicamente


Non è possibile, con questa tecnica, eliminare rigorosamente un event handler, però è possibile eliminare l'event listener (cioè la funzione che viene eseguita dall'event handler), sfruttando la possibilità di sovrascrivere le proprietà di un oggetto. Nel nostro esempio basterà assegnare alla "proprietà" onmouseover e onmouseout il valore null. Dimostriamolo:

function elimina_listener(){
  document.getElementById("dinamico").onmouseover = null
  document.getElementById("dinamico").onmouseout = null
  }


Anche quando si assegnano gli event handlers con il JavaScript si sottintende che i vari listener sono definiti nella fase di bubbling degli eventi. Questo si può mostrare riproponendo una versione del primo esempio in cui gli event handlers sono definiti dinamicamente sui sottostanti livelli, con la seguente funzione:

function inserisci_handlers(){
  document.getElementById("div1").onclick = function() {alert("Livello 1")}
  document.getElementById("div2").onclick = function() {alert("Livello 2")}
  document.getElementById("div3").onclick = function() {alert("Livello 3")}
  }

Livello 1

Livello 2

Livello 3

Utilizzando il JavaScript non è possibile inserire più listeners sullo stesso evento, infatti, se ad esempio, scrivessimo:

  elemento.onclick = funzione_1
  elemento.onclick = funzione_2

La seconda riga verrebbe interpretata come una sovrascrittura della "proprietà" onclick, e annullerebbe l'effetto della prima riga.

Lo stesso discorso vale anche se precedentemente fosse stato assegnato un event handler attraverso l'HTML, percui ad esempio:

<elemento onclick="funzione1()">....</elemento>
  elemento.onclick = funzione_2

La seconda riga sovrascrive la proprietà "onclick" annullando l'event handler assegnato via HTML.

È possibile, in ogni modo, aggirare quest'ostacolo assegnando all'event handler una funzione "contenitore" il cui scopo è quello di riunire in una sola chiamata tutte le diverse funzioni che si vogliono associare all'evento. Occorre, però, passare alle varie funzioni il riferimento all'oggetto sul quale si vuole intervenire:

function contenitore{
  funzione_1(this);
  funzione_2(this);
  }

elemento.onclick = contenitore

Ancora una volta non resta che mostrarne un esempio applicato al seguente layer con id="doppio", nel quale come funzione contenitore si fa uso di una funzione anonima, che viene assegnata dinamicamente all'evento onclick:

function colora(ogg){ogg.style.backgroundColor="#000000";ogg.style.color="#CCCCCC";}
function sposta(ogg){ogg.style.left="200px";}

function due_funzioni(){
  document.getElementById("doppio").onclick = function()
{colora(this);sposta(this);}
  }

DOPPIA AZIONE

Nella prima parte di questo articolo abbiamo introdotto il concetto di Evento e abbiamo indicato alcuni metodi nella loro gestione. Gli eventi permettono di far reagire il browser ad una o più azione degli utenti. Per la gestione degli eventi bisogna agire sugli event listener, ossia sulle proprietà che "messe in ascolto" (da 'to listen': ascoltare) su un determinato evento ne regolano la gestione. Gli eventi e i suoi listener sono delle vere e proprie pietre miliari della progettazione interattiva.

Alla fine abbiamo considerato due metodi per attivare o disattivare gli eventi listener a seconda di alcune azioni degli utenti. Abbiamo anche notato che la compatibilità copre solo quei browser che offrono un supporto più o meno completo del DOM W3C.

Ma allora come poter inserire dinamicamente più listener per gli stessi eventi, o rimuoverli in maniera mirata anche in quei browser che non supportano questi strumenti?

La soluzione esiste, ed è sotto gli occhi: il JavaScript. Troppo spesso infatti ci si appoggia con troppa apatia sulla possibilità che i browser offrono di utilizzare metodi destinati ai più disparati scopi. Quando poi le proprietà o i metodi che ci servirebbero non sono supportati o non esistono, spesso ci si arrende senza provare affatto ad aggirare l'ostacolo utilizzando le potenzialità che appunto il JavaScript mette a disposizione.

In questa pagina, vi proporrò uno script molto efficace per la gestione degli event listener, scritto da Aaron Boodman (aaron@youngpup.net), basato su un utilizzo estensivo del JavaScript, che cercherò di spiegare in ogni suo passo.

Il Listener di Aaron Boodman

Lo script realizza un'implementazione per JavaScript dei metodi per l'inserimento e la rimozione di event listeners.

Il meccanismo è realizzato associando ad ogni event handler definito attraverso il JavaScript, una funzione (fire) che richiama tutti i puntatori ai diversi listener definiti per quell'evento. I puntatori, a loro volta, sono memorizzati in un array di oggetti definito come proprietà dell'event handler stesso. Il meccanismo base è quindi:

oggetto.evento = function() { fire(...) }
oggetto.evento.array_di_listener = new Array(Listener1, Listener2, ...)

Quando l'evento è acceso, viene eseguita la funzione fire che, scandendo la lista degli oggetti Listener, esegue tutte le funzioni che abbiamo definito per quell'evento. L'operazione di rimozione di un event listener, si limiterà ad una esclusione dell'elemento desiderato dall'array di oggetti.

Questa potente implementazione ricorda molto la struttura "signals and slots" (segnala e incanala), nella quale, in questo caso, il segnale è rappresentato dall'evento acceso e l'operazione di distribuzione è realizzata dalla funzione fire che dirotta il segnale su tutti i Listener definiti.

                                    |--> Listener 1
                                    |
                                    |--> Listener 2
evento (signals) --> fire (slots) --|
                                    |--> Listener 3
                                    |
                                    |--> ...

Non resta che vedere di che si tratta. Per prima cosa vedremo tutto il codice stampato, nella pagina successiva descriveremo come usarlo e come adattarlo ai nostri scopi, mentre nell'ultima pagina lo analizzeremo punto per punto in modo da insegnarvi per benino come si 'programma' uno scriptino del genere.

Listener : il codice

/*
* =============================================================
* Listener - by Aaron Boodman
* 5/23/2002; Queens, NY.
* http://www.developer-x.com/projects/nodemenu/behaviour/listener.js
*
* http://www.youngpup.net/
*
=============================================================
*/

function Listener(fp, scope, removing) {
  this.fp = fp;
  this.scope = scope;
  this.removing = removing;
}

Listener.add = function(oSource, sEvent, fpDest, oScope, bRunOnce) {
  if (!oSource[sEvent] || oSource[sEvent] == null || !oSource[sEvent]._listeners) {
    oSource[sEvent] = function() { Listener.fire(oSource, sEvent, arguments) };
    oSource[sEvent]._listeners = new Array();
  }
  var idx = this.findForEvent(oSource[sEvent], fpDest, oScope);
  if (idx == -1) idx = oSource[sEvent]._listeners.length;
  oSource[sEvent]._listeners[idx] = new Listener(fpDest, oScope, bRunOnce);
}

Listener.remove = function(oSource, sEvent, fpDest, oScope) {
  if(oSource[sEvent]) {
    var idx = this.findForEvent(oSource[sEvent], fpDest, oScope);
    if (idx != -1) {
      var iLast = oSource[sEvent]._listeners.length - 1;
      oSource[sEvent]._listeners[idx] = oSource[sEvent]._listeners[iLast];
      oSource[sEvent]._listeners.length--;
    }
  }
}

Listener.findForEvent = function(fpEvent, fpDest, oScope) {
  if (fpEvent._listeners) {
    for (var i = 0; i < fpEvent._listeners.length; i++) {
      if (fpEvent._listeners[i].scope == oScope && fpEvent._listeners[i].fp == fpDest) {
        return i;
      }
    }
  }
  return -1;
}

Listener.fire = function(oSourceObj, sEvent, args) {
  if(oSourceObj&&oSourceObj[sEvent]&&oSourceObj[sEvent]._listeners) {

    var lstnr, fp;
    var last = oSourceObj[sEvent]._listeners.length - 1;

    for (var i = last; i >= 0; i--) {
      lstnr = oSourceObj[sEvent]._listeners[i];
      fp = lstnr.fp;

      fp.apply(lstnr.scope, args);

      if (lstnr.removing) Listener.remove(oSourceObj, sEvent, lstnr.fp, lstnr.scope);
    }
  }

  return(-1)
}

if (!Function.prototype.apply) {
  Function.prototype.apply = function(oScope, args) {
    var sarg = [];
    var rtrn, call;

    if (!oScope) oScope = window;
    if (!args) args = [];

    for (var i = 0; i < args.length; i++) {
      sarg[i] = "args["+i+"]";
    }

    call = "oScope.__applyTemp__(" + sarg.join(",") + ");";

    oScope.__applyTemp__ = this;
    rtrn = eval(call);
    return rtrn;
  }
}

Se volete, potete scaricare il file listener.js. Voltiamo pagina e vediamo finalmente come usare il nostro listener.js.

Listener : come usarlo

Prima di entrare nell'analisi dello script, vediamo come utilizzarlo per capire le sue potenzialità. I metodi che ci interessano sono Listener.add per assegnare gli event listener e Listener.remove per rimuoverli.

Consideriamo la loro sintassi:

  • Listener.add

    Il metodo oltre a consentire la definizione di event listener, permette anche di applicare il listener specificato, ad un oggetto differente da quello sul quale è definito. Questo ci consentirà di scrivere le nostre funzioni in maniera più semplice, focalizzando l'attenzione sui risultati più che sul raggio d'azione della funzione stessa. Questo concetto si chiarirà in seguito facendo qualche esempio. La sintassi del metodo è:

    Listener.add(objSource, strEventName, fpListener, objScope, blnRunOnce)

    dove:

    • objSource: è l'oggetto sorgente del flusso d'evento (nella fase di bubbling), rappresenta l'elemento capace di ascoltare l'evento.
    • strEventName: è la stringa che identifica il tipo di evento, ad es. "onclick".
    • fpListener è il puntatore alla funzione che si vuole richiamare in seguito al verificarsi dell'evento, cioè è il puntatore al listener.
    • objScope: è l'oggetto sul quale viene applicato il listener, rappresenta quindi il raggio d'azione (scope) della funzione. Grazie alla presenza di questo parametro è possibile scrivere funzioni più semplici e malleabili.
    • blnRunOnce (true|false) valore booleano che permette di decidere se rimuovere il listener subito dopo il suo primo uso (true), o meno (false).
  • Listener.remove

    Il metodo permette di rimuovere l'event listener specificato dalla lista di quelli definiti per l'oggetto desiderato. La sintassi del metodo è:

    Listener.remove(objSource, strEventName, fpListener, objScope)

    dove gli argomenti della funzione hanno lo stesso significato di quelli del metodo precedente.

Mostriamone il funzionamento riproponendo ancora una volta l'esempio delle pagine precedenti.

Livello 1

Livello 2

Livello 3



Premendo sui bottoni di sopra, si assegneranno degli event handlers sui livelli, che saranno rimossi dopo il loro primo utilizzo.






Il codice usato è il seguente:

function allerta1(){alert("Livello 1")}
function allerta2(){alert("Livello 2")}
function allerta3(){alert("Livello 3")}

function add_e_rem(num) {
  var liv = document.getElementById ? document.getElementById("Liv"+num) :
    document.all ? document.all["Liv"+num] : null
  if(liv)
    Listener.add(liv,"onclick",window["allerta"+num],liv,true);
  else alert("elemento non raggiunto")
  }

function Listener_add(num) {
  var liv = document.getElementById ? document.getElementById("Liv"+num) :
    document.all ? document.all["Liv"+num] : null
  if(liv)
    Listener.add(liv,"onclick",window["allerta"+num],liv,false);
  else alert("elemento non raggiunto")
  }

function Listener_rem(num){
  var liv = document.getElementById ? document.getElementById("Liv"+num) :
    document.all ? document.all["Liv"+num] : null
  if(liv)
    Listener.remove(liv,"onclick",window["allerta"+num],liv);
  else alert("elemento non raggiunto")
  }

Nell'esempio appena proposto l'elemento capace di intercettare l'evento coincide sempre con l'elemento (liv) a cui si riferiscono di volta in volta le funzioni.

Lo script permette però una maggiore flessibilità nell'uso delle funzioni. Consideriamo il seguente esempio di "rollover invertito", nel quale gli eventi, assegnati dinamicamente, sono catturati dal testo "sopra", mentre le funzioni eseguite sono applicate sul testo "sotto".

Il codice HTML è:

<span ID="sopra">Questo testo "ascolta" l'evento onclick</span>
<br>
<br>
<span ID="sotto">Questo testo sarà l'oggetto delle funzioni lanciate</span>

Quello JavaScript è:

function testo_On(){
  this.style.fontStyle="italic"
  this.style.color="white"
  this.style.backgroundColor="red"
}

function testo_Off(){
  this.style.fontStyle="normal"
  this.style.color="black"
  this.style.backgroundColor="white"
}

function rolloverInvertito(){
  var sopra = document.getElementById ? document.getElementById("sopra") :
      document.all ? document.all["sopra"] : null
  var sotto = document.getElementById ? document.getElementById("sotto") :
      document.all ? document.all["sotto"] : null
  if(sopra&&sotto) {
    Listener.add(sopra,"onmouseover",testo_On,sotto,false)
    Listener.add(sopra,"onmouseout",testo_Off,sotto,false)

  }
}

Verifichiamolo:

  Questo testo "ascolta" l'evento onclick
  Questo testo sarà l'oggetto delle funzioni lanciate

Nella prossima pagina lo script Listener sarà analizzato in tutti i suoi aspetti.

Listener: il codice, passo dopo passo

Tuffiamoci tra le righe di questo ottimo script, per capirne tutte le sfumature e imparare una buona tecnica di scripting. Cominciamo:

function Listener(fp, scope, removing) {
  this.fp = fp;
  this.scope = scope;
  this.removing = removing;
}

Questo è il costruttore d'oggetti Listener, che come si può vedere possiede 3 proprietà:

  • fp è il puntatore alla funzione che si vuole richiamare in seguito al verificarsi di un certo evento.
  • scope è la "visibilità" della funzione richiamata, in sostanza definisce il raggio d'azione del listener (fp) richiamato, definendo l'elemento al quale deve essere applicato.
  • removing è una variabile booleana che specifica se l'event listener debba essere cancellato dopo il suo primo uso (true) oppure no (false).

Il seguente è il metodo che ci permette di settare nuovi event listener:

Listener.add = function(oSource, sEvent, fpDest, oScope, bRunOnce) {
  if (!oSource[sEvent] || oSource[sEvent] == null || !oSource[sEvent]._listeners) {
    oSource[sEvent] = function() { Listener.fire(oSource, sEvent, arguments) };
    oSource[sEvent]._listeners = new Array();
  }

Innanzitutto si controlla se l'oggetto al quale si vuole applicare il listener
non abbia già definito l'array _listener.

La riga con !oSource[sEvent]._listeners, infatti, restituisce true se l'array _listener non è definito, cioè se già non si siano definiti altri listener per lo stesso evento sullo stesso oggetto.

In questo caso per l'evento viene creato un "distributore d'eventi", ovvero
viene assegnato all'evento il metodo "fire" (che si occuperà di gestire gli eventi)
e sarà creato un array, relativo all'evento dell'oggetto, che conterrà tutti i suoi
listener.

In particolare, i listener saranno memorizzati in questo vettore sottoforma di oggetti Listener, caratterizzati quindi, oltre che dal puntatore alla funzione da eseguire (fp), anche dalle proprietà scope e removing.

  var idx = this.findForEvent(oSource[sEvent], fpDest, oScope);
  if (idx == -1) idx = oSource[sEvent]._listeners.length;
  oSource[sEvent]._listeners[idx] = new Listener(fpDest, oScope, bRunOnce);
}

Il metodo Listener.findForEvent (che sarà affrontato più dettagliatamente in seguito) ha lo scopo di trovare un listener specifico nella lista di un particolare oggetto (oSource) e di restituire, quindi, il relativo indice nell'array (_listener). Se l'event listener non è presente restituisce -1.

Queste ultime tre righe hanno lo scopo di individuare la prima posizione libera di oSource[sEvent]._listeners per inserire (o sovrascrivere, se già presente) il nuovo listener.

Passiamo al metodo remove:

Listener.remove = function(oSource, sEvent, fpDest, oScope) {
  if(oSource[sEvent]) {
    var idx = this.findForEvent(oSource[sEvent], fpDest, oScope);
    if (idx != -1) {
      var iLast = oSource[sEvent]._listeners.length - 1;
      oSource[sEvent]._listeners[idx] = oSource[sEvent]._listeners[iLast];
      oSource[sEvent]._listeners.length--;
    }
  }
}

Con questa funzione viene eliminato l'event handler desiderato. Nel dettaglio l'oggetto Listener, corrispondente al listener che si vuole rimuovere, viene sovrascritto con l'ultimo Listener dell'evento e dell'oggetto in questione. Dopodichè, viene ridotta la dimensione dell'array (oSource[sEvent]._listeners.length--).

Listener.findForEvent = function(fpEvent, fpDest, oScope) {
  if (fpEvent._listeners) {
    for (var i = 0; i < fpEvent._listeners.length; i++) {
      if (fpEvent._listeners[i].scope == oScope && fpEvent._listeners[i].fp == fpDest) {
        return i;
      }
    }
  }
  return -1;
}

Questo metodo, come accennato, ha lo scopo di scandire l'array dei listener associati allo stesso evento dello stesso oggetto (fpEvent) (ad es: ogg["onclick"]) per cercare se già esista il listener passato come parametro (fpDest) e se abbia la stessa visibilità (scope). In caso positivo restituisce l'indice dell'array trovato, viceversa restituisce -1.

Passiamo ora ad esaminare il metodo fire. Questo è il nucleo di tutto il sistema di gestione degli event listener. La funzione richiama tutti i listeners associati ad un particolare evento su di un particolare oggetto, ogni volta che l'evento è acceso.

Listener.fire = function(oSourceObj, sEvent, args) {
  if(oSourceObj && oSourceObj[sEvent] && oSourceObj[sEvent]._listeners) {
    var lstnr, fp;
    var last = oSourceObj[sEvent]._listeners.length - 1;

    for (var i = last; i >= 0; i--) {
      lstnr = oSourceObj[sEvent]._listeners[i];
      fp = lstnr.fp;

Bisogna notare che il loop deve essere eseguito scandendo l'array dalla fine. Questo perché, durante l'esecuzione dei vari listener, potremmo anche rimuoverne alcuni (qualora sia specificato ). Ciò, come abbiamo visto dal metodo remove, ridurrebbe la dimensione dell'array impedendo la corretta esecuzione di tutti i listener.

      fp.apply(lstnr.scope, args);

Questa è la riga su cui si fonda tutto il sistema.

Infatti lo script si basa sul metodo delle funzioni "apply", che permette di
trasferire un metodo di un oggetto su un'altro senza la necessità di definire
per quest'ultimo un prototipo.

In particolare con questa riga si applica il puntatore fp all'oggetto (lstnr.scope) sul quale si vuole far intervenire il listener stesso (cioè quello a cui punta proprio fp).

Come si può vedere, inoltre, il metodo apply permette anche di passare altri parametri, tra cui anche arguments, il che ci libera dalla necessità di dover conoscere a priori tutti gli argomenti da passare.

Con la seguente riga si verifica se la funzione debba essere eseguita solo la prima volta. In questo caso è rimosso il listener.

      if (lstnr.removing) Listener.remove(oSourceObj, sEvent, lstnr.fp, lstnr.scope);
    }
  }

  return(-1)
}

Lo script si conclude praticamente qui, infatti l'ultima parte è finalizzata ad estendere il metodo apply (fondamentale per il codice) a quei browser, come ad esempio Explorer 4.0, che non lo supportano originariamente.

if (!Function.prototype.apply) {
  Function.prototype.apply = function(oScope, args) {
    var sarg = [];
    var rtrn, call;

    if (!oScope) oScope = window;
    if (!args) args = [];

    for (var i = 0; i < args.length; i++) {
      sarg[i] = "args["+i+"]";
    }

    call = "oScope.__applyTemp__(" + sarg.join(",") + ");";

    oScope.__applyTemp__ = this;
    rtrn = eval(call);
    return rtrn;
  }
}

Questa lunga digressione ci ha mostrato come assegnare gli event handler sia un'operazione strettamente legata al modello d'evento e al suo flusso; ma non è l'unico mezzo per interagire con la propagazione degli eventi.

L'oggetto Event, (che introdurremo prossimamente) permette di sfruttare al massimo il flusso d'evento.


Ti consigliamo anche