Eventi personalizzati

Introduzione agli Eventi personalizzati
Ormai yutti conoscono gli elementi di base – click, mouseover, focus, blur, submit, etc. – derivanti dall’interazione dell’utente con il browser web.

Gli eventi personalizzati consentono di farsi un’idea del mondo della programmazione orientata a oggetti (in inglese event-driven programming). In questo capitolo useremo il sistema di eventi personalizzati di jQuery per creare una semplice applicazione di ricerca Twitter.

In un primo momento può essere difficile da capire il requisito di utilizzare eventi personalizzati, visto che gli eventi convenzionali permettono di soddisfare tutte le esigenze. Tuttavia, gli eventi personalizzati offrono un modo nuovo di pensare la programmazione in JavaScript. Invece di concentrarsi sull’elemento che esegue un’azione, gli eventi personalizzati pongono l’attenzione sull’elemento in cui si verifica l’azione. Questo concetto offre diversi vantaggi:

  • Il comportamento dell’elemento obiettivo può essere eseguito da diversi elementi utilizzando lo stesso codice.
  • I comportamenti possono essere eseguiti in più o simili elementi obiettivi alla volta.
  • I comportamenti sono associati più chiaramente con l’elemento di obiettivo, rendendo il codice più facile da leggere e manutenere.

Un esempio è il modo migliore per spiegare la questione. Supponiamo di avere una lampada a incandescenza in una stanza di una casa. La lampada è attualmente accessa. La stessa è controllata da due interruttori a tre posizioni e un clapper (switch attivato per applausi):

[code lang=”html”] <div class="room" id="kitchen">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
[/code]
Eseguendo il clapper o qualcuno degli interruttori, lo stato della lampada cambia. Agli interruttori o al clapper non si curano se la lampada è accesa o spenta, vogliono solo cambiare il loro stato.

Senza l’utilizzo di eventi personalizzati, è possibile scrivere la routine come segue:

[code lang=”javascript”] $(‘.switch, .clapper’).click(function() {
var $light = $(this).parent().find(‘.lightbulb’);
if ($light.hasClass(‘on’)) {
$light.removeClass(‘on’).addClass(‘off’);
} else {
$light.removeClass(‘off’).addClass(‘on’);
}
});
[/code]
D’altra parte, con gli eventi personalizzati, il codice appare così:
[code lang=”javascript”] $(‘.lightbulb’).bind(‘changeState’, function(e) {
var $light = $(this);
if ($light.hasClass(‘on’)) {
$light.removeClass(‘on’).addClass(‘off’);
} else {
$light.removeClass(‘off’).addClass(‘on’);
}
});

$(‘.switch, .clapper’).click(function() {
$(this).parent().find(‘.lightbulb’).trigger(‘changeState’);
});
[/code]

Qualcosa di importante è successo: il comportamento della lampada è stata cambiata, prima erano sugli interruttori e nel clapper, ora si trova nella stessa lampada.

E ‘anche possibile fare un esempio po’ più interessante. Supponiamo di aggiungere un’altra stanza nella casa, insieme ad uno switch generale, come illustrato di seguito:

[code lang=”html”] <div class="room" id="kitchen">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
<div class="room" id="bedroom">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
<div id="master_switch"></div>
[/code]
Se c’è una lampada accesa in casa, è possibile spegnerla tramite l’interruttore generale, nello stesso modo quando ci sono luci spente, è possibile accenderle con questo interruttore. Per fare questo, si aggiungono due eventi personalizzati in più alla lampada: turnOn e turnOff. Attraverso una logica nell’evento changeState si decide quale evento personalizzato usare:
[code lang=”javascript”] $(‘.lightbulb’)
.bind(‘changeState’, function(e) {
var $light = $(this);
if ($light.hasClass(‘on’)) {
$light.trigger(‘turnOff’);
} else {
$light.trigger(‘turnOn’);
}
})
.bind(‘turnOn’, function(e) {
$(this).removeClass(‘off’).addClass(‘on’);
})
.bind(‘turnOff’, function(e) {
$(this).removeClass(‘off’).addClass(‘on’);
});

$(‘.switch, .clapper’).click(function() {
$(this).parent().find(‘.lightbulb’).trigger(‘changeState’);
});

$(‘#master_switch’).click(function() {
if ($(‘.lightbulb.on’).length) {
$(‘.lightbulb’).trigger(‘turnOff’);
} else {
$(‘.lightbulb’).trigger(‘turnOn’);
}
});
[/code]

Si noti come il comportamento dell’interruttore generale è stato collegato all’interruttore, mentre il comportamento delle lampade appartenengo alle lampade.

Se siete abituati alla programmazione orientata agli oggetti, è possibile pensare gli eventi personalizzati come metodi degli oggetti. In generale, l’oggetto che appartiene al metodo si crea dal selettore jQuery. Vincolare l’evento personalizzato changeState a tutti gli elementi $(‘.light’) è simile ad avere una classe chiamata Light con un metodo changeState, e poi istanziare nuovi oggetti Light per ogni elemento.
Ricapitolando:. $.fn.bind e $.fn.trigger
Nel mondo di eventi personalizzati, ci sono due metodi importanti di jQuery:. $.fn.bind e $.fn.trigger. Ne capitolo dedicato agli eventi è stato spiegato l’uso di questi due metodi per lavorare con gli eventi dell’utente, in questo capitolo è importante ricordare due punti:

  • Il metodo $.fn.bind prende come argomenti un tipo di evento e una funzione di gestione degli eventi. Opzionalmente, può ricevere informazioni associate all’evento come secondo argomento, spostando come terzo argomento la funzione gestore degli eventi. Qualunque informazione passata sarà disponibile alla funzione attraverso della proprietà data dell’oggetto dell’evento. A sua volta, la funzione del gestore degli eventi riceve l’oggetto dell’evento come primo argomento.
  • Il metodo $.fn.trigger prende come argomenti il tipo di evento e facoltativamente può prendere un array di valori. Questi valori saranno passati alla funzione del gestore degli eventi come argomentidopo dell’oggetto dell’evento.

Di seguito è riportato un esempio d’uso di $.fn.bind e $.fn.trigger in cui si utilizza le informazioni personali in entrambi i casi:

[code lang=”javascript”] $(document).bind(‘myCustomEvent’, { foo : ‘bar’ }, function(e, arg1, arg2) {
console.log(e.data.foo); // ‘bar’
console.log(arg1); // ‘bim’
console.log(arg2); // ‘baz’
});

$(document).trigger(‘myCustomEvent’, [ ‘bim’, ‘baz’ ]);
[/code]

Un esempio di applicazione

Per dimostrare la potenza di eventi personalizzati, svilupperà un semplice strumento per la ricerca di Twitter. Questo strumento offrirà diversi modi per effettuare una ricerca dall’utente: inserirendo il termine da ricercare in una casella di testo o consultando i “temi caldi” di Twitter.
I risultati di ciascun termine verranno visualizzati in un contenitore di risultati; questi risultati si potranno espandere, comprimere, aggiornare e rimuovere, singolarmente o l’insieme.

l risultato finale dell’applicazione sarà la seguente:
Iniziazione

Inizia con un HTML di base:
[code lang=”html”] <h1>Twitter Search</h1>
<input type="button" id="get_trends"
value="Load Trending Terms" />

<form>
<input type="text" class="input_text"
id="search_term" />
<input type="submit" class="input_submit"
value="Add Search Term" />
</form>

<div id="twitter">
<div class="template results">
<h2>Search Results for
<span class="search_term"></span></h2>
</div>
</div>
[/code]

L’HTML ha un contenitore (#twitter) per il widget, un modello per i risultati (nascosto con i CSS) e un semplice modulo in cui l’utente può digitare il termine di ricerca.

Ci sono due tipi di elementi su cui agire: il contenitore di risultati e il contenitore Twitter.

I contenitori di risultati è cuore dell’applicazione. Si creerà un’estensione per preparare ogni contenitore una volta che questo viene aggiunto al contenitore Twitter. Inoltre, tra le altre cose, l’estensione vincolerà gli eventi personalizzati per ogni contenitore e aggiungerà, in alto a destra di ogni contenitore, pulsanti che eseguiranno azioni. Ogni contenitore di risultati dovrà avere i seguenti eventi personalizzati:
refresh

Segnala che l’informazione del contenitore si sta aggiornando e spara la petizione che cerca i dati correlato al termine di ricerca.
populate
Ricevere l’informazione in JSON e l’utilizza per riempire il contenitore.
remove
Rimuove il contenitore dalla pagina dopo che l’utente conferma l’azione. Tale conferma può essere omessa se si passa true come secondo parametro al gestore degli eventi. L’evento rimuove anche il termine associato nel contenitore di risultati dell’oggetto globale che contiene i termini di ricerca.
collapse
Aggiunge una classe al contenitore, il quale nesconderà il risultato attraverso i CSS. Cambierà anche il testo del pulsante da “Colapse” a “Expand”.
expand
Rimuovere la classe al contenitore che aggiunge l’evento collapse. Cambierà anche il testo del pulsante da “Expand” a “Colapse”.
Inoltre, l’estensione è responsabile per l’aggiunta dei pulsanti di azione al contenitore, vincolando un evento click per ogni pulsante e utilizzando la classe di ogni item per determinare quale evento personalizzato verrà eseguito su ogni contenitore di risultati.
[code lang=”javascript”] $.fn.twitterResult = function(settings) {
return this.each(function() {
var $results = $(this),
$actions = $.fn.twitterResult.actions =
$.fn.twitterResult.actions ||
$.fn.twitterResult.createActions(),
$a = $actions.clone().prependTo($results),
term = settings.term;

$results.find(‘span.search_term’).text(term);

$.each(
[‘refresh’, ‘populate’, ‘remove’, ‘collapse’, ‘expand’],
function(i, ev) {
$results.bind(
ev,
{ term : term },
$.fn.twitterResult.events[ev] );
}
);

// utilizza la classe di ogni azione per
// determinare quale evento si svolgerà
// nel riquadro dei risultati
$a.find(‘li’).click(function() {
// passa l’elemento <li> cliccato
// sulla funzione in modo da poterlo
// manipolare, se necessario
$results.trigger($(this).attr(‘class’), [ $(this) ]);
});
});
};

$.fn.twitterResult.createActions = function() {
return $(‘<ul class="actions" />’).append(
‘<li class="refresh">Refresh</li>’ +
‘<li class="remove">Remove</li>’ +
‘<li class="collapse">Collapse</li>’
);
};

$.fn.twitterResult.events = {
refresh : function(e) {
// indica che i risultati sono
// in fase di aggiornamento
var $this = $(this).addClass(‘refreshing’);

$this.find(‘p.tweet’).remove();
$results.append(‘<p class="loading">Loading …</p>’);

// ottiene le informazioni da Twitter
// in formato jsonp
$.getJSON(
‘http://search.twitter.com/search.json?q=’ +
escape(e.data.term) + ‘&rpp=5&callback=?’,
function(json) {
$this.trigger(‘populate’, [ json ]);
}
);
},

populate : function(e, json) {
var results = json.results;
var $this = $(this);

$this.find(‘p.loading’).remove();

$.each(results, function(i,result) {
var tweet = ‘<p class="tweet">’ +
‘<a href="http://twitter.com/’ +
result.from_user +
‘">’ +
result.from_user +
‘</a>: ‘ +
result.text +
‘ <span class="date">’ +
result.created_at +
‘</span>’ +
‘</p>’;
$this.append(tweet);
});

// indica che i risultati
// sono già stati aggiornati
$this.removeClass(‘refreshing’);
},

remove : function(e, force) {
if (
!force &&
!confirm(‘Rimuovere pannello per il termine ‘ + e.data.term + ‘?’)
) {
return;
}
$(this).remove();

// indica che si non avrà
// un pannello per il termine
search_terms[e.data.term] = 0;
},

collapse : function(e) {
$(this).find(‘li.collapse’).removeClass(‘collapse’)
.addClass(‘expand’).text(‘Expand’);

$(this).addClass(‘collapsed’);
},

expand : function(e) {
$(this).find(‘li.expand’).removeClass(‘expand’)
.addClass(‘collapse’).text(‘Collapse’);

$(this).removeClass(‘collapsed’);
}
};
[/code]

Il contenitore Twitter, ha solo due eventi personalizzati:
getResults
Riceve un termine di ricerca e verifica se c’è un contenitore di risultati per il termine. Se non esiste, aggiunge un contenitore utilizzando il modello dei risultati, lo configura utilizzando l’estensione $.fn.twitterResult (vedi sopra) e quindi esegue l’evento refresh per caricare correttamente i risultati. Infine, salva il termine di ricerca per evitare di dover richiedere i dati per il termine..
getTrends
Consulta a Twitter l’elenco delle prime 10 “parole calde”, interagisce con loro ed esegue l’evento getResults su ciascuno, in modo che si aggiunga un contenitore di risultati per ciascun termine.
Collegamenti nel contenitore Twitter:
[code lang=”javascript”] $(‘#twitter’)
.bind(‘getResults’, function(e, term) {
// si verifica che non ci sia
// un contenitore per il termine
if (!search_terms[term]) {
var $this = $(this);
var $template = $this.find(‘div.template’);

// fa una copia del modello div e la inserisce
// come il primo contenitore di risultati
$results = $template.clone().
removeClass(‘template’).
insertBefore($this.find(‘div:first’)).
twitterResult({
‘term’ : term
});

// carica il contenuto utilizzando
// l’evento personalizzato "refresh"
// vincolato al contenitore dei risultati
$results.trigger(‘refresh’);
search_terms[term] = 1;
}
})
.bind(‘getTrends’, function(e) {
var $this = $(this);
$.getJSON(‘http://search.twitter.com/trends.json?callback=?’, function(json) {
var trends = json.trends;
$.each(trends, function(i, trend) {
$this.trigger(‘getResults’, [ trend.name ]);
});
});
});
[/code]

Finora, si è scritto un sacco di codice che non fa nulla, che non è male. Si sono specificati tutti i comportamenti che si desiderano per gli elementi essenziali e si è creato una base solida per creare rapidamente l’interfaccia.

Di seguito, si collega la casella di ricerca e pulsante per caricare i “temi caldi”. Nella casella, il termine digitato viene catturato e passato, allo stesso tempo che si esegue getResults. D’altra parte, cliccando sul pulsante per caricare i “temi caldi”, si esegue l’evento getTrends:

[code lang=”javascript”] $(‘form’).submit(function(e) {
e.preventDefault();
var term = $(‘#search_term’).val();
$(‘#twitter’).trigger(‘getResults’, [ term ]);
});

$(‘#get_trends’).click(function() {
$(‘#twitter’).trigger(‘getTrends’);
});
[/code]

Aggiugendo pulsanti con un ID apropriato, è possibile rimuovere, collassare, espandere e aggiornare tutti i contenitori di risultati, allo stesso tempo. Per il pulsante che rimuove il contenitore, notare che si sta passando true al gestore di eventi come il secondo argomento, indicando che non si vuole una conferma da parte dell’utente per rimuovere il contenitore.
[code lang=”javascript”] $.each([‘refresh’, ‘expand’, ‘collapse’], function(i, ev) {
$(‘#’ + ev).click(function(e) { $(‘#twitter div.results’).trigger(ev); });
});

$(‘#remove’).click(function(e) {
if (confirm(‘Remove all results?’)) {
$(‘#twitter div.results’).trigger(‘remove’, [ true ]);
}
});
[/code]

Conclusione
Gli eventi personalizzati rappresentano un modo nuovo di pensare il codice: essi sottolineano l’obiettivo di un comportamento e non nell’elemento che lo attiva. Se si prende il tempo dall’inizio per spiegare le parti della vostra applicazione, così come i comportamenti che queste parti hanno bisogno di esibire, gli eventi personalizzati forniscono un potente strumento per “parlare” con quelle parti, sia uno alla volta o in massa.

Una volta che i comportamenti sono stati descritti, diventa banale da eseguire da qualunque luogo, consentendo la creazione rapida e la sperimentazione di opzioni di interfaccia. Infine, eventi personalizzati sono in grado di migliorare la leggibilità del codice e la sua manutenzione, rendendo chiara la relazione tra un elemento e il suo comportamento.

È possibile visualizzare l’applicazione completa dei files demos/custom-events/custom-events.html e demos/custom-events/js/custom-events.js che compongono questo libro.

Crediti
Fondamentali di jQuery
Di Rebecca Murphey
http://github.com/rmurphey/jqfundamentals
Con il contributo di James Padolsey, Paolo Irish e gli altri. Per una storia completa dei contributi visitare il repository GitHub.
Copyright © 2011

Concesso in licenza da Rebecca Murphey sotto la Creative Commons Attribution-Share Alike 3.0 United States license Siete liberi di copiare, distribuire, trasmettere, e remix questo lavoro, a condizione di attribuire l’opera a Rebecca Murphey come l’autore originale e il repository di riferimento GitHub per il lavoro. Se alteri, trasformi o sviluppi quest’opera, puoi distribuire l’opera risultante solo con la stessa, simile o di una licenza compatibile. Una delle condizioni di cui sopra può essere revocata se si ottiene il permesso del detentore del copyright. Ogni volta che usi o distribuisci quest’opera, devi farlo secondo i termini della licenza di quest’opera. Il modo migliore per farlo è con un link per la licenza.