Il metodo di lavoro è il seguente: in primo luogo, si dedicherà del tempo per capire un concetto per poi fare un esercizio correlato. Alcuni degli esercizi possono anche essere banali, mentre altre non lo sono. L’obiettivo è quello di imparare a risolvere in maniera semplice, ciò che normalmente si risolverebbe con jQuery. Soluzioni per tutti gli esercizi sono inclusi nello stesso materiale d’apprendimento.
Ottenere il materiale didattico- Firefox Browser Web
- L’estensione Firebug per Firefox
- Un editor di testo (come Notepad + + per Windows, gedit / Kate TextMate per Linux o Mac OS X)
- Per le sezioni su Ajax: un server locale (come WAMP o MAMP) o un client FTP/SSH per accedere a un server remoto.
Per migliorare le prestazioni della pagina, il codice JavaScript deve essere incluso alla fine del HTML. Inoltre, quando si lavora in un ambiente di produzione con più file JavaScript, devono essere combinati in un unico file.
1.1: Esempio di codice JavaScript in linea
<script>
console.log('ciao');
</script>
<script src='/js/jquery.js'></script>
Ogni debugger offre:
- Un editore multi-line per sperimentare con JavaScript;
- Un ispettore per rivedere il codice generato nella pagina;
- Un visualizzazione di rete o risorse, di esaminare le richieste che vengono fatte.
Quando si scrive codice JavaScript, è possibile utilizzare uno dei seguenti metodi per inviare messaggi alla console del debugger:
console.log()per inviare e registrare messaggi generali;console.dir()per registrare un oggetto e visualizzarne le proprietà;console.warn()per registrare messaggi di alert;console.error()per registrare i messaggi di errore;
Esistono altri metodi per utilizzarli dalla console, ma questi possono variare a seconda del browser. La console offre inoltre la possibilità di impostare punti d’interruzione e osservare le espressioni nel codice per facilitare il debug.
Anche così, per completare alcuni esercizi, sarà necessario consultare la documentazione ufficiale di jQuery. Imparare a trovare le risposte è una parte importante del processo di apprendimento.
Ecco alcuni suggerimenti per affrontare i problemi:
- Per prima cosa, assicurarsi di aver compreso il problema si sta tentando di risolvere.
- Poi, sapere a quali oggetti si dovranno accedere per risolvere il problema e decidere come accedervi. È possibile utilizzare Firebug per verificare se si sta ottenendo il risultato atteso.
- Infine, scoprire cosa deve essere fatto con questi elementi per risolvere il problema. Può essere utile prima di iniziare, scrivere commenti che spiega cosa dovrà fare.
Non abbiate paura di sbagliare. E neanche provare, al primo tentativo, di scrivere un codice perfetto. Commettere errori e sperimentare soluzioni fa parte del processo di apprendimento e aiuterà a essere uno sviluppatore migliore. È possibile trovare nella cartella /solutions esempi di soluzioni agli esercizi del libro.
1.3: Un codice di esempio
// Il codice di esempio apparirà in questo modo
I richiami appariranno in questo modo.
Le note su qualche tema appariranno di questa forma.
- Perché il materiale ha lo scopo di apprendimento e di insegnamento, è tradotto in italiano formale (voi).
- Molti termini tecnici sono nominati nella sua traduzione in italiano. Tuttavia, per tenere riferimento, si spiega anche come si chiamano in inglese.
- Gli esempi e le soluzioni agli esercizi non sono completamente tradotti. Questo perché, quando si lavora su un progetto reale, il codice trovato su altri siti potrebbe essere scritto in inglese. Eppure, i commenti incorporati nei codici sono stati tradotti così come altri testi particolari per facilitare la comprensione.
A continuazione ci sono una serie di eccellenti risorse da utilizzare durante l’apprendimento. La più importante di tutte è il codice sorgente di jQuery, che contiene (in formato non compresso) una documentazione completa attraverso i commenti. La biblioteca non è una scatola nera – la sua comprensione aumenterà in modo esponenziale ogni volta che la si rivede – ed è altamente consigliabile memorizzare il link nel vostro browser preferito per averla come guida di riferimento.
Il capitolo si rivolge a chi non conosce il linguaggio, e copre i concetti di base e problemi comuni che possono sorgere quando si lavora con esso. Inoltre, la sezione può essere utile a chi utilizza altri linguaggi di programmazione per capire le peculiarità di JavaScript.
Se siete interessati a saperne di più sul linguaggio, si può leggere il libro JavaScript: The Good Parts che Douglas Crockford ha scritto.
2.1: Dichiarazione semplice di una variabile
var foo = 'ciao mondo';
var foo = 'ciao mondo';
2 * 3 + 5; // ritorna 11; il prodotto si esegue prima
2 * (3 + 5); // ritorna 16; per le parentesi, la somma si esegue prima
var foo = function() {
console.log('ciao');
};
2.5: Concatenazione
var foo = 'ciao';
var bar = 'mondo';
console.log(foo + ' ' + bar); // 'ciao mondo'
2 * 3;
2 / 3;
var i = 1;
var j = ++i; // incremento anticipato: j è uguale a 2; i è uguale a 2
var k = i++; // incremento posticipato: k è uguale a 2; i è uguale a 3
2.8: Somma vs. concatenazione
var foo = 1;
var bar = '2';
console.log(foo + bar); // errore: la console di debug mostra 12
var foo = 1;
var bar = '2';
// il costruttore 'Number' obbliga alla
// stringa di comportarsi come un numero
console.log(foo + Number(bar)); // la console di debug mostra 3
2.10: Forzare una stringa per agire come un numero (somma unaria)
console.log(foo + +bar);
2.11: Operatori Logici AND e OR
var foo = 1;
var bar = 0;
var baz = 2;
foo || bar; // ritorna 1, il quale è vero (true)
bar || foo; // ritorna 1, il quale è vero (true)
foo && bar; // ritorna 0, il quale è falso (false)
foo && baz; // ritorna 2, il quale è vero (true)
baz && foo; // ritorna 1, il quale è vero (true)
È possibile consultare la sezione “Elementi Vero e Falso” per ulteriori dettagli su quali valori vengono valutati come true e quali vengono valutati come false.
Talvolta si può notare che alcuni sviluppatori utilizzino questa logica per il controllo del flusso invece di utilizzare la dichiarazione if. Per Esempio:
// fare qualcosa con foo se foo è vero
foo && doSomething(foo);
// stabilire bar uguale a baz se baz è vero;
// caso contrario, stabilire bar uguale al
// valore di createBar()
var bar = baz || createBar();
2.12: Operatori di confronto
var foo = 1;
var bar = 0;
var baz = '1';
var bim = 2;
foo == bar; // ritorna false
foo != bar; // ritorna true
foo == baz; // ritorna true; fare attenzione!
foo === baz; // ritorna false
foo !== baz; // ritorna true
foo === parseInt(baz); // ritorna true
foo > bim; // ritorna false
bim > baz; // ritorna true
foo <= baz; // ritorna true
2.13: Controllo del flusso
var foo = true;
var bar = false;
if (bar) {
// questo codice non verrà mai eseguito
console.log('ciao!');
}
if (bar) {
// questo codice non si eseguirà
} else {
if (foo) {
// questo codice si eseguirà
} else {
// si eseguirà se foo e bar sono falsi (false)
}
}
In una linea singola, quando si scrive un’istruzione if, le parentesi non sono strettamente necessari, ma è raccomandato in quanto rende il codice molto più leggibile.
È necessario essere consapevoli di non definire funzioni con lo stesso nome più volte all’interno di istruzioni if/else, giacché si potrebbe ottenere risultati inaspettati.
2.14: Valori che ritornano il vero true
'0';
'qualunque stringa';
[]; // un array vuoto
{}; // un oggetto vuoto
1; // qualsiasi numero diverso da zero
0;
''; // una stringa vuota
NaN; // la variabile JavaScript "not-a-number" (non è un numero)
null; // un valore nullo
undefined; // // fare attenzione -- (undefined) può essere ridefinito
switch (foo) {
case 'bar':
alert('il valore è bar');
break;
case 'baz':
alert('il valore è baz');
break;
default:
alert('impostazione predefinita, questo codice verrà eseguito');
break;
}
Per Esempio:
var stuffToDo = {
'bar' : function() {
alert('il valore è bar');
},
'baz' : function() {
alert('il valore è baz');
},
'default' : function() {
alert('impostazione predefinita, questo codice verrà eseguito');
}
};
if (stuffToDo[foo]) {
stuffToDo[foo]();
} else {
stuffToDo['default']();
}
2.18: Cicli
// mostra nella console 'tentativo 0', 'tentativo 1', ..., 'tentativo 4'
for (var i=0; i<5; i++) {
console.log('tentativo ' + i);
}
for ([espressioneIniziale]; [condizione]; [incrementoDellaEspressione])
[corpoCiclo]
Lo stato condizione viene eseguita prima di ogni iterazione, e restituisce un valore che decide se il ciclo deve continuare oppure no. Se lo stato condizionale valuta un valore come falso il ciclo si interrompe.
Lo stato incrementoDellaEspressione viene eseguito alla fine di ogni ripetizione e offre l’opportunità di modificare lo stato di variabili importanti. Di solito, questo stato comporta l’incremento o il decremento di un contatore.
Il corpoCiclo è il codice da eseguire in ogni iterazione del ciclo. In genere ci sono più istruzioni che devono essere eseguiti e così li avvolgono in un blocco ({…}).
Un tipico ciclo utilizzando for:
2.19: Un tipico ciclo utilizzando for
for (var i = 0, limit = 100; i < limit; i++) {
// Questo blocco di codice viene eseguito 100 volte
console.log('Attualmente a ' + i);
// Nota: L'ultimo record da visualizzare
// nella console sarà "Attualmente a 99"
}
for ([espressioneIniziale]; [condizione]; [incrementoDellaEspressione])
[corpoCiclo]
2.20: Un típico bucle utilizzando while
var i = 0;
while (i < 100) {
// Questo blocco di codice verrà eseguito 100 volte
console.log('Attualmente a ' + i);
i++; // incrementa la variabile i
}
2.21: Ciclo utilizzando while con la combinazione della condizione e l'incremento
var i = -1;
while (++i < 100) {
// Questo blocco di codice verrà eseguito 100 volte
console.log('Attualmente a ' + i);
}
do [corpoCiclo] while ([condizione])
do {
// Anche quando la condizione è falsa
// il corpo del ciclo viene eseguito almeno una volta.
alert('Ciao!');
} while (false);
2.23: Fermare un ciclo con break
for (var i = 0; i < 10; i++) {
if (qualcosa) {
break;
}
}
2.24: Saltare alla seguente iterazione di un ciclo
for (var i = 0; i < 10; i++) {
if (qualcosa) {
continue;
}
// La seguente dichiarazione sarà eseguita
// se la condizione 'something' non si compie
console.log('I have been reached');
}
abstractbooleanbreakbytecasecatchcharclassconstcontinuedebuggerdefaultdeletedodoubleelseenumexportextendsfinalfinallyfloatforfunctiongotoifimplementsimportininstanceofintinterfacelongnativenewpackageprivateprotectedpublicreturnshortstaticsuperswitchsynchronizedthisthrowthrowstransienttrytypeofvarvoidvolatilewhilewith
2.25: Un vettore semplice
var myArray = [ 'ciao', 'mondo' ];
var myArray = [ 'ciao', 'mondo' ];
var myArray = [ 'ciao', 'mondo' ];
console.log(myArray.length); // mostra '2' nella console
var myArray = [ 'ciao', 'mondo' ];
myArray[1] = 'modificato';
2.29: Aggiungere elementi ad un array
var myArray = [ 'ciao', 'mondo' ];
myArray.push('nuovo');
var myArray = [ 'c', 'i', 'a', 'o' ];
var myString = myArray.join(''); // 'ciao'
var mySplit = myString.split(''); // [ 'c', 'i', 'a', 'o' ]
È interessante notare che in JavaScript, quasi tutto è un oggetto – array, funzioni, numeri, anche le stringhe – e tutti possiedono proprietà e metodi.
var myObject = {
sayHello : function() {
console.log('ciao');
},
myName : 'Rebecca'
};
myObject.sayHello(); // si chiama il metodo sayHello, che
// visualizza sulla console 'Ciao'
console.log(myObject.myName); // si chiama la proprietà myName, che
// mostra 'Rebecca' nella console
var myObject = {
validIdentifier: 123,
'qualche stringa': 456,
99999: 789
};
Gli oggetti letterali possono essere molto utile per l’organizzazione del codice, per maggiori informazioni potete leggere l’articolo (in inglese) Using Objects to Organize Your Code di Rebecca Murphey.
Le funzioni possono essere creati in diversi modi:
2.32: Dichiarazione di una funzione
function foo() { /* fa qualcosa */ }
var foo = function() { /* fa qualcosa */ }
Utilizzo delle Funzioni
var greet = function(person, greeting) {
var text = greeting + ', ' + person;
console.log(text);
};
greet('Rebecca', 'Ciao');
var greet = function(person, greeting) {
var text = greeting + ', ' + person;
return text;
};
console.log(greet('Rebecca','ciao'));
var greet = function(person, greeting) {
var text = greeting + ', ' + person;
return function() { console.log(text); };
};
var greeting = greet('Rebecca', 'Ciao');
greeting();
2.37: Una funzione anonima autoeseguibile
(function(){
var foo = 'Ciao mondo';
})();
console.log(foo); // indefinito (undefined)
2.38: Passare una funzione anonima come argomento
var myFn = function(fn) {
var result = fn();
console.log(result);
};
// mostra nella console 'Ciao mondo'
myFn(function() { return 'Ciao mondo'; });
var myFn = function(fn) {
var result = fn();
console.log(result);
};
var myOtherFn = function() {
return 'Ciao mondo';
};
// mostra nella console 'Ciao mondo'
myFn(myOtherFn);
Per ciò, è pratica comune usare l’operatore typeof quando si tratta di determinare il tipo di un valore specifico.
2.40: Determinare il tipo tra differenti variabili
var myFunction = function() {
console.log('ciao');
};
var myObject = {
foo : 'bar'
};
var myArray = [ 'a', 'b', 'c' ];
var myString = 'ciao;
var myNumber = 3;
typeof myFunction; // ritorna 'function'
typeof myObject; // ritorna 'object'
typeof myArray; // ritorna 'object' -- fare attenzione
typeof myString; // ritorna 'string';
typeof myNumber; // ritorna 'number'
typeof null; // ritorna 'object' -- fare attenzione
if (myArray.push && myArray.slice && myArray.join) {
// probabilmente è un array
// (questo stile è chiamato, in inglese, "duck typing")
}
if (Object.prototype.toString.call(myArray) === '[object Array]') {
// definitivamente è un array;
// questa è considerata la forma più robusta
// per determinare se un valore è un array.
}
- Se la funzione viene invocata utilizzando Function.call o Function.apply,
thisavrà il valore del primo argomento passato al metodo. Se l’argomento è nullo (null) o indefinito (undefined),thisfarà riferimento all’oggetto globale (l’oggettowindow); - Se la funzione a invocare viene creata utilizzando Function.bind,
thissarà il primo argomento che viene passato alla funzione, al momento della sua creazione; - Se la funzione è invocata come metodo di un oggetto,
thisfarà riferimento a detto oggetto; - Altrimenti, se la funzione viene invocata come una funzione indipendente, non legata ad alcun oggetto,
thisfarà riferimento all’oggetto globale.
var myObject = {
sayHello : function() {
console.log('Ciao! il mio nome è ' + this.myName);
},
myName : 'Rebecca'
};
var secondObject = {
myName : 'Colin'
};
myObject.sayHello();
// registra 'Ciao, il mio nome è Rebecca'
myObject.sayHello.call(secondObject);
// registra 'Ciao, il mio nome è Colin'
var myName = 'the global object',
sayHello = function () {
console.log('Ciao, il mio nome è ' + this.myName);
},
myObject = {
myName : 'Rebecca'
};
var myObjectHello = sayHello.bind(myObject);
sayHello(); // registra 'Ciao, il mio nome è oggetto globale'
myObjectHello(); // registra 'Ciao, il mio nome è Rebecca'
var myName = 'oggetto globale',
sayHello = function() {
console.log('Ciao, il mio nome è ' + this.myName);
},
myObject = {
myName : 'Rebecca'
},
secondObject = {
myName : 'Colin'
};
myObject.sayHello = sayHello;
secondObject.sayHello = sayHello;
sayHello(); // registra 'Ciao, il mio nome è oggetto globale'
myObject.sayHello(); // registra 'Ciao, il mio nome è Rebecca'
secondObject.sayHello(); // registra 'Ciao, il mio nome è Colin'
var myNamespace = {
myObject : {
sayHello : function() {
console.log('Ciao, il mio nome è ' + this.myName);
},
myName : 'Rebecca'
}
};
var hello = myNamespace.myObject.sayHello;
hello(); // registra 'Ciao, il mio nome è undefined'
var myNamespace = {
myObject : {
sayHello : function() {
console.log('Ciao!, il mio nome è ' + this.myName);
},
myName : 'Rebecca'
}
};
var obj = myNamespace.myObject;
obj.sayHello(); // registra 'Ciao!, il mio nome è Rebecca'
Quando una variabile viene dichiarata all’interno di una funzione utilizzando la parola chiave var, è disponibile solo per il codice all’interno della funzione – tutto il codice al di fuori della funzione non può accedere alla variabile. D’altra parte, le funzioni definite all’interno della funzione potranno accedere alla variabile dichiarata.
Le variabili dichiarate all’interno della funzione senza la parola chiave var non rimangono nell’ambito di applicazione della stessa funzione – JavaScript cercherà il luogo dove la variabile è stata precedentemente dichiarata, e nel caso che non sia stata dichiarata, sarà definita nell’ambito globale, il quale può portare a risultati inattesi;
2.44: Le funzioni hanno accesso alle variabili definite all'interno dello stesso ambito
var foo = 'Ciao';
var sayHello = function() {
console.log(foo);
};
sayHello(); // mostra nella console 'Ciao'
console.log(foo); // mostra anche nella console 'Ciao'
var sayHello = function() {
var foo = 'Ciao';
console.log(foo);
};
sayHello(); // mostra nella console 'Ciao'
console.log(foo); // non mostra niente nella console
var foo = 'mondo';
var sayHello = function() {
var foo = 'ciao';
console.log(foo);
};
sayHello(); // mostra nella console 'ciao'
console.log(foo); // mostra nella console 'mondo'
var myFunction = function() {
var foo = 'Ciao';
var myFn = function() {
console.log(foo);
};
foo = 'mondo';
return myFn;
};
var f = myFunction();
f(); // registra 'mondo' -- errore
// a self-executing anonymous function
(function() {
var baz = 1;
var bim = function() { alert(baz); };
bar = function() { alert(baz); };
})();
console.log(baz); // La console non mostra nulla,
// visto che baz è definita nell'ambito
// della funzione anonima
bar(); // bar è definita al di fuori della funzione
// anonima dopo essere stata dichiarata senza la
// parola chiave var; inoltre, come è stata
// definita all'interno della stessa portata di
// baz, è possibile interrogare il valore del baz,
// anche se questa sia definita all'interno
// della portata della funzione anonima
bim(); // bim non è definito per essere accessibile
// al di fuori della funzione anonima,
// quindi viene visualizzato un errore
Nell’sempio 2.47 il modo in cui le funzioni hanno accesso per cambiare il valore delle variabili. Lo stesso comportamento si verifica nelle funzioni create all’interno dei cicli – la funzione “osserva” il cambiamento nella variabile, anche dopo che la funzione sia stata definita, risultando che ad ogni click appaia una finestra di avviso mostrando il valore 5.
2.49: Come impostare il valore di i?
/* questo non si comporta come desiderato */
/* ogni click visualizzerà una finestra di avviso con il valore 5 */
for (var i=0; i<5; i++) {
$('<p>click me</p>').appendTo('body').click(function() {
alert(i);
});
}
/* soluzione: "clausurare" il valor di i all'interno di createFunction */
var createFunction = function(i) {
return function() { alert(i); };
};
for (var i=0; i<5; i++) {
$('<p>click me</p>').appendTo('body').click(createFunction(i));
}
2.51: Utilizzando una chiusura per accedere simultaneamente ad istanze di oggetti interni ed esterni
var outerObj = {
myName : 'esterno',
outerFunction : function () {
// fornisce un riferimento allo stesso oggetto outerObj
// da utilizzare all'interno di innerFunction
var self = this;
var innerObj = {
myName : 'interno',
innerFunction : function () {
console.log(self.myName, this.myName);
// registra 'esterno interno'
}
};
innerObj.innerFunction();
console.log(this.myName);
// registra 'esterno'
}
};
outerObj.outerFunction();
3.1: Il blocco $(document).ready()
$(document).ready(function() {
console.log('il ocumento è pronto!');
});
3.2: Forma abbreviata per $(document).ready()
$(function() {
console.log('il ocumento è pronto!');
});
3.3: Passare il nome di una funzione al posto di una funzione anonima
function readyFn() {
// codice da eseguire quando il documento è pronto
}
$(document).ready(readyFn);
Di seguito sono elencate alcune tecniche comuni per la selezione degli elementi:
3.4: Selezione di elementi in base al loro ID
$('#myId'); // si noti che l'ID deve essere univoco per ogni pagina
$('div.myClass'); // se si specifica il tipo di elemento,
// si migliora le prestazioni della selezione
$('input[name=first_name]'); // attenzione, che può essere molto lento
$('#contents ul.people li');
$('a.external:first'); // il primo elemento <a> con la classe 'external'
$('tr:odd'); // gli elementi <tr> dispari di una tabella
$('#myForm :input'); // gli elementi del tipo input
$('div:visible'); // gli elementi <div> visibili
$('div:gt(2)'); // gli elementi <div> tranne i primi tre
$('div:animated'); // gli elementi <div> attualmente animati
Per avere un riferimento, il seguente è il frammento di codice che utilizza jQuery per determinare quando un elemento è visibile o meno. I commenti sono stati inseriti per rendere più chiara la sua comprensione:
jQuery.expr.filters.hidden = function( elem ) {
var width = elem.offsetWidth, height = elem.offsetHeight,
skip = elem.nodeName.toLowerCase() === "tr";
// Scelta di Selettori ha 0 di altezza,
// 0 di larghezza e non è <tr>?
return width === 0 && height === 0 && !skip ?
// allora deve essere nascosto (hidden)
true :
// ma se ha larghezza e l'altezza e non è un <tr>
width > 0 && height > 0 && !skip ?
// allora deve essere visibile
false :
// se siamo qui è perché l'elemento ha larghezza
// e altezza, ma è anche un <tr>, quindi verifica
// il valore dello stile display applicato attraverso
// i CSS per decidere se è nascosto o meno
jQuery.curCSS(elem, "display") === "none";
};
jQuery.expr.filters.visible = function( elem ) {
return !jQuery.expr.filters.hidden( elem );
};
jQuery fornisce molti selettori basati sugli attributi che consentono di fare delle selezioni in base ai contenuti degli attributi utilizzando semplificazioni con delle espressioni regolari.
// trovare tutti gli <a> cui attributi finiscono in "thinger"
$("a[rel$='thinger']");
Per saperne di più su questo argomento, Paul Irish ha fatto una grande presentazione sui miglioramenti delle prestazioni Javascript (in inglese), diverse diapositive centrate sui selettori.
if ($('div.foo')) { ... }
Invece di utilizzare il codice di prima, la cosa da fare è chiedere il numero di elementi che ha la selezione che è stata eseguita. E’ possibile fare usando la proprietà JavaScript, length. Se la risposta è 0, la condizione restituisce false, altrimenti (più di 0 elementi), la condizione è vera.
3.9: Valutare se una selezione ha degli elementi
if ($('div.foo').length) { ... }
3.10: Salvare le selezioni su una variabile
var $divs = $('div');
Una volta che la selezione è memorizzata nella variabile, può essere utilizzato in combinazione con i metodi di jQuery e il risultato sarà lo stesso se si utilizza la selezione originale.
3.11: Affinamento delle selezioni
$('div.foo').has('p'); // l'elemento div.foo contiene elementi <p>
$('h1').not('.bar'); // l'elemento h1 non possiede la clase 'bar'
$('ul li').filter('.current');// un item di una lista non ordinata che
// possiede la classe 'current'
$('ul li').first(); // il primo item de una lista non ordinata
$('ul li').eq(5); // el sesto item de una lista non ordinata
:buttonSeleziona elementi <button> e con l’attributo type=”button”:checkboxSeleziona elementi <input> con l’attributotype=”checkbox”:checkedSeleziona elementi <input> del tipo checkbox selezionati:disabledSeleziona elementi del form che sono disabilitati:enabledSeleziona elementi del form che sono abilitati:fileSeleziona elementi <input> con l’attributo type=”file”:imageSeleziona elementi <input> con l’attributo type=”image”:inputSeleziona elementi <input>, <textarea> e <select>:passwordSeleziona elementi <input> con el atributo type=”password”:radioSeleziona elementi <input> con l’attributo type=”radio”:resetSeleziona elementi <input> con l’attributo type=”reset”:selectedSeleziona elementi <options> che sono selezionati:submitSeleziona elementi <input> con l’attributo type=”submit”:textSeleziona elementi <input> con l’attributo type=”text”
$('#myForm :input'); // ottiene tutti gli elementi input nel form #myForm
Concatenazione
3.13: Concatenando
$('#content').find('h3').eq(2).html('testo per il terzo elemento h3!');
3.14: Formattazione del codice incatenato
$('#content')
.find('h3')
.eq(2)
.html('testo per il terzo elemento h3!');
3.15: Ripristinare la selezione originale utilizzando il metodo $.fn.end
$('#content')
.find('h3')
.eq(2)
.html('testo per il terzo elemento h3!')
.end() // ripristina tutti gli elementi h3 in #content
.eq(0)
.html('testo per il primo elemento h3!');
3.16: Il metodo $.fn.html utilizzato come impostatore
$('h1').html('ciao mondo');
$('h1').html();
$('h1').css('fontSize'); // restituisce una stringa come "19px"
$('h1').css('font-size'); // funziona lo stesso
// imposta una proprietà singola CSS
$('h1').css('fontSize', '100px');
// imposta molteplici proprietà CSS
$('h1').css({ 'fontSize' : '100px', 'color' : 'red' });
Utilizzare Classi per applicare Stili CSS
3.20: Lavorare con le classi
var $h1 = $('h1');
$h1.addClass('big');
$h1.removeClass('big');
$h1.toggleClass('big');
if ($h1.hasClass('big')) { ... }
Il codice riportato nell’esempio “Dimensioni – Metodi di base” è solo un breve riassunto delle funzionalità sui rapporti dimensionali di jQuery, tutti i dettagli possono essere trovati http://api.jquery.com/category/dimensions/.
3.21: Dimensioni - Metodi di base
$('h1').width('50px'); // imposta la larghezza di tutti gli elementi H1
$('h1').width(); // ottiene la larghezza del primo elemento H1
$('h1').height('50px');// imposta l'altezza di tutti gli elementi H1
$('h1').height(); // Ottiene l'altezza del primo elemento H1
$('h1').position(); // Restituisce un oggetto che contiene informazioni
// circa la posizione del primo elemento relativo a
// "offset" (posizione) dal suo elemento genitore
Il metodo $.fn.attr funge sia da metodo getter che da metodo setter. Inoltre, come metodo di $.fn.css, se usato come metodo setter può accettare una serie di parole chiave-valore o un oggetto che contiene più serie.
3.22: Impostare attributi
$('a').attr('href', 'allMyHrefsAreTheSameNow.html');
$('a').attr({
'title' : 'con i titoli è la stessa cosa!',
'href' : 'somethingNew.html'
});
3.23: Ottenere attributi
$('a').attr('href'); // restituisce il primo valore dell'attributo href
// appartenente al elemento <a> del documento
In http://api.jquery.com/category/traversing/ può trovare una documentazione completa sui metodi di attraversamento del DOM (in inglese traversing) che ha jQuery.
$('h1').next('p'); // seleziona l'elemento successivo <p>
// immediatamente dopo <h1>
$('div:visible').parent(); // seleziona il contenitore padre
// di un elemento <div> visibile
$('input[name=first_name]').closest('form'); // seleziona dal più
// vicino <form> un input
$('#myList').children(); // seleziona tutti gli elementi figlio
// di #myList
$('li.selected').siblings(); // seleziona tutti gli elementi fratelli
// del elemento <li>
3.25: Interagire in una selezione
$('#myList li').each(function(idx, el) {
console.log(
'L\'elemento ' + idx +
'ha il seguente html: ' +
$(el).html()
);
});
La documentazione completa sui metodi di manipolazione può essere trovata in http://api.jquery.com/category/manipulation/.
Ottenre e Impostare Informazioni negli Elementi
$.fn.htmlOttiene o imposta il contenuto HTML di un elemento.$.fn.textOttiene o imposta il contenuto in texto piano dell’elemento; nel caso di pasarli come argomento del codice HTML, questo sarà ripulito dai sui tag.$.fn.attrOttiene o imposta il valore di un determinato attributo.$.fn.widthOttiene o imposta la larghezza in pixels del primo elemento della selezione come un intero.$.fn.heightOttiene o imposta l’altezza in pixeles del primo elemento della selezione come un intero.$.fn.positionOttiene un oggetto con informazioni sulla posizione del primo elemento della selezione relativa al primo elemento genitore posizionato. Questo metodo è solo ottenitore (getter).$.fn.valOttiene o imposta il valore (value) negli elementi dei forms.
$('#myDiv p:first')
.html('Nuovo <strong>primo</strong> parragrafo!');
- Volere mettere il/gli elementi selezionati rispetto ad altri elementi
- Volere mettere un elemento rispetto ad altri elementi selezionati.
Per esempio, jQuery fornisce metodi $.fn.insertAfter e $.fn.after. Il metodo $.fn.insertAfter colloca il/gli elementi selezionati dopo l’elemento che è stato passato come argomento, mentre il metodo $.fn.after colloca l’elemento passato come argomento dopo l’elemento selezionato. Altri metodi seguono anche questo modello:
$.fn.insertBeforee$.fn.before;$.fn.appendToe$.fn.append;- e
$.fn.prependToe$.fn.prepend.
L’uso di uno o un altro metodo dipende dagli elementi che hanno selezionato e il tipo di riferimento che si desidera salvare rispetto all’elemento che si sta muovendo.
3.27: Spostare oggetti con differenti approcci
// fare il che primo elemento della lista sia l'ultimo
var $li = $('#myList li:first').appendTo('#myList');
// un altro approccio allo stesso problema
$('#myList').append($('#myList li:first'));
// Va notato che non c'è modo di accedere
// alla lista degli items che è stata spostata,
// giacché restituisce la lista stessa
3.28: Ottenere una copia del elemento
// copiare il primo elemento della lista
// e spostarlo alla fine dello stesso
$('#myList li:first').clone().appendTo('#myList');
D’altra parte, il $.Fn.detach rimuove anche l’elemento, ma conserva le informazioni e gli eventi associati ad esso, essendo utile in caso di necessità di reinserire l’elemento nel documento.
D’altra parte, se si desidera mantenere l’elemento, ma si ha bisogno di rimuovere i uso contenuto, è possibile utilizzare il metodo $.fn.empty, il quale “svuoterà” il contenuto HTML dell’elemento.
3.29: Creare nuovi elementi
$('<p>Questo è un nuovo paragrafo</p>');
$('<li class="nuovo">nuovo item della lista</li>');
$('<a/>', {
html : 'Questo è un <strong>nuovo</strong> link',
'class' : 'nuovo',
href : 'foo.html'
});
Quando si crea un elemento, non è immediatamente aggiunto alla pagina, ma se deve farlo in combinazione con un metodo.
3.31: Creare un nuovo elemento nella pagina
var $myNewElement = $('<p>Nuovo elemento</p>');
$myNewElement.appendTo('#content');
$myNewElement.insertAfter('ul:last');
// eleminerà l'elemento <p> esistente in #content!
$('ul').last().after($myNewElement.clone());
// clonare l'elemento <p> per avere due versioni
Tuttavia, la maggior parte delle volte si desidererà fare riferimento all’elemento aggiunto, e quindi, se si salva in una variabile non c’è bisogno di selezionarlo più tardi.
3.32: Creare e aggiungere allo stesso tempo un elemento alla pagina
$('ul').append('<li>item della lista</li>');
var myItems = [], $myList = $('#myList');
for (var i=0; i<100; i++) {
myItems.push('<li>item ' + i + '</li>');
}
$myList.append(myItems.join(''));
3.33: Manipolazione di un singolo attributo
$('#myDiv a:first').attr('href', 'newDestination.html');
$('#myDiv a:first').attr({
href : 'newDestination.html',
rel : 'super-special'
});
$('#myDiv a:first').attr({
rel : 'super-special',
href : function(idx, href) {
return '/new/' + href;
}
});
$('#myDiv a:first').attr('href', function(idx, href) {
return '/new/' + href;
});
- Selezionare tutti gli elementi div che hanno classe “module”.
- Specificare tre selezioni che possono selezionare il terzo item della lista non ordinata #myList. Qual è il migliore da usare e perché?
- Selezionare l’elemento label dell’elemento input mediante un selettore di attributo.
- Verificare quanti elementi della pagina sono nascosti (suggerimento:. .length)
- Verificare quante immagini sulla pagina hanno l’attributo alt.
- Selezionare tutte le righe dispari del corpo della tabella.
- Selezionare tutte le immagini sulla pagina, registrare nella console l’attributo alt di ogni immagine.
- Selezionare l’elemento di input, poi andare al form e aggiungere una classe ad esso.
- Selezionare l’item che ha la classe di “current” nella lista #myList e rimuovere la classe, poi aggiungere la classe “current” al successivo item della lista.
- Selezionare l’elemento select all’interno di #special; per poi dirigersi verso il pulsante submit.
- Selezionare il primo item della lista nell’elenemto #slideshow; aggiungere la classe “current” ad esso e poi aggiungere la classe “disabled” agli elementi fratelli.
- Aggiungere 5 nuovi elementi alla fine della lista non ordinata # myList. Suggerimento:
Copia codicefor (var i = 0; i<5; i++) { ... } - Rimuovere gli elementi dispari dalla lista.
- Aggiungere un altro elemento h2 e un altro paragrafo all’ultimo div.module.
- Aggiungere un’altra opzione all’elemento select, danno all’opzione aggiunta il valore “Wednesday”.
- Aggiungere un nuovo div.module alla pagina dopo l’ultima pagina; e quindi aggiungere una copia di una delle immagini esistenti nel nuovo div.
$('h1').remove();
Tuttavia, ci sono metodi che fanno parte del namespace $ e sono considerati come metodi del nucleo di jQuery.
Queste distinzioni possono essere molto confuse per i nuovi utenti. Per evitare confusione, ricordare questi due punti:
- I metodi utilizzati nelle selezioni si trovano all’interno dello spazio dei nomi $.fn, e automaticamente ricevono e restituiscono una selezione in sè.
- Metodi nello spazio dei nomi $ sono generalmente metodi per diverse utilità, non lavorano con le selezioni, non vengono passati argomenti e valore di ritorno può variare.
Ci sono alcuni casi in cui i metodi dell’oggetto e del nucleo hanno gli stessi nomi, come succede con $.each e $.fn.each. In questi casi, è necessario fare attenzione a leggere attentamente la documentazione per sapere quale oggetto usare in modo corretto.
$. Questi metodi sono di grande aiuto per svolgere compiti di routine di programmazione.Di seguito alcuni esempi, per una documentazione completa visita http://api.jquery.com/category/utilities/.
$.trim
$.trim(' stringa con più spazi vuoti ');
// restituisce 'stringa con più spazi vuoti'
$.each([ 'foo', 'bar', 'baz' ], function(idx, val) {
console.log('elemento ' + idx + 'es ' + val);
});
$.each({ foo : 'bar', baz : 'bim' }, function(k, v) {
console.log(k + ' : ' + v);
});
var myArray = [ 1, 2, 3, 5 ];
if ($.inArray(4, myArray) !== -1) {
console.log('valore trovato!');
}
var firstObject = { foo : 'bar', a : 'b' };
var secondObject = { foo : 'baz' };
var newObject = $.extend(firstObject, secondObject);
console.log(firstObject.foo); // 'baz'
console.log(newObject.foo); // 'baz'
var firstObject = { foo : 'bar', a : 'b' };
var secondObject = { foo : 'baz' };
var newObject = $.extend({}, firstObject, secondObject);
console.log(firstObject.foo); // 'bar'
console.log(newObject.foo); // 'baz'
var myFunction = function() { console.log(this); };
var myObject = { foo : 'bar' };
myFunction(); // restituisce l'oggetto window
var myProxyFunction = $.proxy(myFunction, myObject);
myProxyFunction(); // restituisce l'oggetto myObject
var myObject = {
myFn : function() {
console.log(this);
}
};
$('#foo').click(myObject.myFn);
// registra el elemento DOM #foo
$('#foo').click($.proxy(myObject, 'myFn'));
// registra myObject
4.1: Verificare il tipo di un determinato valore
var myValue = [1, 2, 3];
// Utilizzare l'operatore typeof di JavaScript
// per controllare tipi primitivi
typeof myValue == 'string'; // false (falso)
typeof myValue == 'number'; // false (falso)
typeof myValue == 'undefined'; // false (falso)
typeof myValue == 'boolean'; // false (falso)
// Utilizzare l'operatore di uguaglianza rigorosa
// per controllare i valori nulli
myValue === null; // false (falso)
// Utilizzare metodi di jQuery per testare
// i tipi non primitivi
jQuery.isFunction(myValue); // false (falso)
jQuery.isPlainObject(myValue); // false (falso)
jQuery.isArray(myValue); // true (vero)
4.2: Salvare e recuperare informazioni relative a un elemento
$('#myDiv').data('keyName', { foo : 'bar' });
$('#myDiv').data('keyName'); // { foo : 'bar' }
Per esempio, se si vuole stabilire una relazione tra l’item di una lista e il div che è all’interno di questo item, è possibile farlo ogni volta che si interagisce con l’item, ma una soluzione migliore è farlo una sola volta, salvando un puntatore al div utilizzando il metodo $.fn.data:
4.3: Relazione tra gli elementi utilizzando il metodo $.fn.data
$('#myList li').each(function() {
var $li = $(this), $div = $li.find('div.content');
$li.data('contentDiv', $div);
});
// poi, non si deve tornare a cercare al div;
// è possibile leggerlo dall'informazione
// associata all'item della lista
var $firstLi = $('#myList li:first');
$firstLi.data('contentDiv').html('nuovo contenuto');
Per tali situazioni, jQuery fornisce l’oggetto $.support e $.browser (quest’ultimo non più utilizzato). Una documentazione completa su questi oggetti si trovano in http://api.jquery.com/jQuery.support/ e http://api.jquery.com/jQuery.browser/
L’obiettivo di $.support è quello di determinare quali caratteristiche supporta il browser.
L’oggetto $.browser rileva il tipo di browser e la sua versione. Questo oggetto è deprecato (anche se nel breve termine non è previsto l’eliminazione dal nucleo della libreria) e si raccomanda utilizzare l’oggetto $.support per questi scopi.
Quando jQuery è impostato in modo “non-conflitto”, la libreria offre la possibilità di assegnare un nome per sostituire la variabile $.
4.4: Mettere jQuery in modo non-conflitto
// la libreria prototype utilizza anche $
<script src="prototype.js"></script>
// si carica jquery nella pagina
<script src="jquery.js"></script>
// se inizializza $j in modo "non-conflitto"
<script>var $j = jQuery.noConflict();</script>
4.5: Utilizzare $ all'interno di una funzione anonima auto-eseguibile
<script src="prototype.js"></script>
<script src="jquery.js"></script>
<script>
jQuery.noConflict();
(function($) {
// codice va qui, e può utilizzare $
})(jQuery);
</script>
$this fa riferimento all’elemento in cui si verifica l’evento.Per maggiori dettagli sugli eventi in jQuery, è possibile consultare http://api.jquery.com/category/events/.
La funzione del gestore di eventi è in grado di ricevere un oggetto. Questo oggetto può essere utilizzato per determinare la natura del evento o, per esempio, evitare il suo comportamento predefinito.
Per maggiori informazioni sull’oggetto dell’evento, visitare http://api.jquery.com/category/events/event-object/.
$('p').click(function() {
console.log('click');
});
$('p').bind('click', function() {
console.log('click');
});
$('input').bind(
'click change', // è possibile collegare più eventi
// all'elemento
{ foo : 'bar' }, // deve essere passato come argomento
// le informazioni associatea
function(eventObject) {
console.log(eventObject.type, eventObject.data);
// registra il tipo di evento e l'informazione
// associata { foo : 'bar' }
}
);
$('p').one('click', function() {
console.log('fatto click la prima volta!');
$(this).click(function() { console.log('fatto click di nuovo!'); });
});
$('p').unbind('click');
var foo = function() { console.log('foo'); };
var bar = function() { console.log('bar'); };
$('p').bind('click', foo).bind('click', bar);
$('p').unbind('click', bar); // foo è lagato all'evento click
5.7: Spazio di Nomi per gli Eventi
$('p').bind('click.myNamespace', function() { /* ... */ });
$('p').unbind('click.myNamespace');
$('p').unbind('.myNamespace');
// scollega tutti gli eventi con
// lo spazio dei nomi myNamespace
5.8: Collegare più eventi a un elemento
$('p').bind({
'click': function() {
console.log('fatto click!');
},
'mouseover': function() {
console.log('il mouse è sopra!');
}
});
pageX, pageYLa posizione del mouse quando l’evento si è verificato, relativa la parte superiore e sinistra della pagina.typeIl tipo di evento (per esempio “click”).whichIl pulsante o tasto premuto.dataInformazioni passate quando l’evento viene eseguito.targetL’elemento DOM che ha inizializzato l’evento.preventDefault()Annulla l’azione predefinita dell’evento (per esempio: seguire un link).stopPropagation()Ferma la diffusione dell’evento su altri elementi.
D’altra parte, il funzione controller ha anche accesso all’elemento DOM che ha inizializzato l’evento attraverso la parola chiave this. Per convertire l’elemento DOM in un oggetto jQuery (e potere utilizzare i metodi della libreria) deve essere scritto $(this), come illustrato di seguito:
var $this = $(this);
$('a').click(function(e) {
var $this = $(this);
if ($this.attr('href').match('evil')) {
e.preventDefault();
$this.addClass('evil');
}
});
5.10: Sparare un gestore di eventi nel modo giusto
var foo = function(e) {
if (e) {
console.log(e);
} else {
console.log('questa azione non proviene da un evento!');
}
};
$('p').click(foo);
foo(); // invece di eseguire $('p').trigger('click')
La delega di eventi ha alcuni benefici, anche se non avete in programma di aggiungere altri elementi alla pagina. Il tempo necessario per legare i gestori di eventi a centinaia di elementi non è un lavoro banale; se si dispone di un grande insieme di elementi, è consigliabile utilizzare la delegazione di eventi a un elemento contenitore.
$('#myUnorderedList').delegate('li', 'click', function(e) {
var $myListItem = $(this);
// ...
});
$('#myUnorderedList li').live('click', function(e) {
var $myListItem = $(this);
// ...
});
5.13: Scollegare Eventi Delegati
$('#myUnorderedList').undelegate('li', 'click');
$('#myUnorderedList li').die('click');
$.fn.hover
$('#menu li').hover(function() {
$(this).toggleClass('hover');
});
5.15: La funzione ausiliaria toggle
$('p.expander').toggle(
function() {
$(this).prev().addClass('open');
},
function() {
$(this).prev().removeClass('open');
}
);
- Impostare il valore dell’elemento input uguale al valore dell’elemento label;
- Aggiungere la classe “hint” all’elemento input;
- Remuover l’elemento label;
- Collegare un evento focus nell’elemento input per rimuovere il testo di suggerimento e la classe “hint”;
- Collegare un evento blur nel input per ripristinare il testo di suggerimento e la classe “hint” nel caso in cui un testo non hintviene inserito.
Quali altre considerazioni si dovrebbero prendere in considerazione se si desidera applicare questa funzionalità a un sito vero e proprio?
- Nascondere titti gli elementi div.module;
- Creare una lista non ordinata prima del primo div.module per utilizzarla come schede;
- Interagire con ogni div.module usando $.fn.each. Per ciascuna, utilizzare il testo dell’elemento h2 come testo per l’elemento della lista non ordinata; Interactuar con cada div utilizando $.fn.each.
- Collegare un evento click per ogni elemento nella lista in modo che:
- Visualizzare il div corrispondente e nascondere l’altro;
- Aggiungere la classe “current” al ítem selezionato;
- Rimuovere la classe “current” su l’altro item della lista;
- Infine, mostrare la prima scheda.
Per la documentazione completa sui diversi tipi di effetti si possono visitare http://api.jquery.com/category/effects/.
$.fn.show Visualizza l’elemento selezionato.$.fn.hide Nasconde l’elemento selezionato.$.fn.fadeIn In forma animata, modifica l’opacità dell’elemento selezionato al 100%$.fn.fadeOut In forma animata, modifica l’opacità dell’elemento selezionato al 0%$.fn.slideDown Consente di visualizzare l’elemento selezionato con un movimento a scorrimento verticale.$.fn.slideUp Nasconde l’elemento selezionato con un movimento a scorrimento verticale.$.fn.slideToggle Visualizza o nasconde l’elemento selezionato con un movimento a scorrimento verticale, a seconda che l’elemento sia correntemente visibile o meno.
$('h1').show();
6.2: Impostare la durata degli effetti
$('h1').fadeIn(300); // dissolvenza in 300ms
$('h1').fadeOut('slow'); // utilizzo di una definizione di velocità interna
jQuery.fx.speeds
speeds: {
slow: 600,
fast: 200,
// Velocità predefinita
_default: 400
}
6.3: Aumentare la velocità con jQuery.fx.speeds
jQuery.fx.speeds.blazing = 100;
jQuery.fx.speeds.turtle = 2000;
6.4: Eseguire del codice quando un'animazione è completata
$('div.old').fadeOut(300, function() { $(this).remove(); });
6.5: Eseguire una funzione di retrochiamata, anche se non c'è nessun elemento da animare
var $thing = $('#nonexistent');
var cb = function() {
console.log('realizzato!');
};
if ($thing.length) {
$thing.fadeIn(300, cb);
} else {
cb();
}
6.6: Effetti personalizzati con $.fn.animate
$('div.funtimes').animate(
{
left : "+=50",
opacity : 0.25
},
300, // durata
function() { console.log('done!'); // retrochiamata
});
$.fn.animate, ma è possibile farlo attraverso l’estensione color plugin.Più tardi nel libro si discuterà l’uso delle estensioni.
Dalla versione 1.4 della libreria, è possibile impostare il tipo di transizione per ogni proprietà utilizzando il metoso $.fn.animate.
6.7: Transizione easing per ogni proprietà
$('div.funtimes').animate(
{
left : [ "+=50", "swing" ],
opacity : [ 0.25, "linear" ]
},
300
);
$.fn.stop$.fn.delay
$('h1').show(300).delay(1000).hide(300);
jQuery.fx.off- Cliccando su uno qualsiasi dei titolari del div #blog, si dovrebbe visualizzare il testo con un effetto scorrevole;
- Facendo clic su un altro titolo, si dovrebbe nascondere il paragrafo mostrato con un effetto di scorrimento e mostrare di nuovo il paragrafo corrispondente anche con un effetto di scorrimento.
Suggerimento: Non dimenticare di usare il selettore :visible.
- Passando il mouse su una voce del menu, si dovrebbe visualizzare un sottomenu se esiste;
- Quando non è posizionato sopra un item, il sottomenu si dovrebbe nascondere.
Per fare questo, utilizzare il metodo $.fn.hover per aggiungere o rimuovere una classe al sottomenu per controllare se deve essere nascosto o visibile (Il file /exercises/css/styles.css include una classe “hover” per questo scopo).
- Spostare l’elemento #slideshow alla parte superiore della pagina;
- Scrivere un codice per la visualizzazione degli items in maniera ciclica, mostrando un item per alcuni secondi, poi nasconderlo con un effetto fade out e mostrare il seguente item con l’effetto di dissolvenza fade in;
- Una volta raggiunto l’ultimo elemento della lista, ricominciare con il primo;
Per una sfida più grande, creare un area di navigazione sotto lo slideshow che mostra quante immagini ci sono e su quale si trova (suggerimento: $.fn.prevAll può essere utile).
Le richieste Ajax vengono eseguite dal codice JavaScript, il quale invia una richiesta a un URL e quando riceve una risposta, una funzione di ritorno può essere eseguita il quale riceve come argomento la risposta del server e ne fa qualcosa con questa. Visto che la risposta è asincrona, il resto del codice dell’applicazione continua a funzionare, quindi è imperativo che una funzione di ritorno venga eseguita per gestire la risposta.
Attraverso vari metodi, jQuery fornisce il supporto per AJAX, che consente astrarre le differenze che possono esistere tra i browser. I metodi in questione sono $.get(), $.getScript(), $.getJSON(), $.post() e $().load().
Sebbene la definizione di Ajax ha la parola “XML”, la maggior parte delle applicazioni non utilizzare questo formato per il trasporto dei dati, ma al suo posto utilizza testo formato HTML o informazioni in JSON (JavaScript Object Notation).
In generale, l’Ajax non funziona in domini diversi. Tuttavia, ci sono delle eccezioni, come i servizi che forniscono informazioni in formato JSONP (JSON with Padding), che permettono funzionalità limitate in domini diversi.
GET vs. POST
Il metodo GET dovrebbe essere utilizzato per operazioni non distruttive – cioè, operazioni in cui si sta “ottenendo” dati dal server, e non “modificando”. Per Esempio, una query per una ricerca potrebbe essere una richiesta GET. D’altra parte, le richieste GET possono essere memorizzate nella cache del browser e può generare comportamenti imprevedibili se non attesi. In generale, le informazioni inviate al server, vengono inviati in una stringa di dati (in inglese query string).
Il metodo POST dovrebbe essere utilizzato per operazioni distruttive – cioè operazioni in cui vengono incorporate informazioni nel server. Per Esempio, quando un utente salva un articolo in un blog, questa azione dovrebbe usare POST. D’altra parte, con questo tipo di metodo non viene memorizzato nella cache del browser. Inoltre, una stringa di query può essere parte della URL, ma le informazioni tendono ad essere inviate separatamente.
texthtmlscriptjsonSi consiglia di utilizzare i meccanismi che possiede il linguaggio lato server per generare le informazioni in JSON.
jsonpxmlAsincronismo
var response;
$.get('foo.php', function(r) { response = r; });
console.log(response); // undefined - indefinito!
$.get('foo.php', function(response) { console.log(response); });
L’altra eccezione è quando si fa una richiesta di riceverà una risposta in formato JSONP. In questo caso, il fornitore della risposta dovrebbe rispondere alla richiesta con uno script che può essere caricato utilizzando il tag <script>, evitando così la limitazione ad effettuare richieste dallo stesso dominio. Questa risposta avrà le informazioni richieste, contenuta in una funzione.
È generalmente preferibile utilizzare il metodo $.ajax piuttosto che altri, visto che offre più funzionalità e la sua configurazione è molto comprensibile.
$.ajax
7.1: Utilizzare il metodo $.ajax
$.ajax({
// l'URL per la richiesta
url : 'post.php',
// invio di informazioni
// (è anche possibile utilizzare una stringa di dati)
data : { id : 123 },
// specificare se si tratta di una richiesta POST o GET
type : 'GET',
// il tipo di informazioni
// che ci si attende come risposta
dataType : 'json',
// codice da eseguire se la richiesta viene accettata;
// la risposta è passata come argomento alla funzione
success : function(json) {
$('<h1/>').text(json.title).appendTo('body');
$('<div class="content"/>')
.html(json.html).appendTo('body');
},
// codice da eseguire se la richiesta fallisce;
// sono passati come argomenti alla funzione
// l'oggetto della petizione in codice grezzo
// e lo stato della richiesta
error : function(xhr, status) {
alert('Spiacente, c\'è stato un problema!');
},
// codice da eseguire indipendentemente dal
// fatto se la richiesta riesca.
complete : function(xhr, status) {
alert('Richiesta fatta!');
}
});
$.ajaxasynccachecompletecontextdatadataTypeerrorjsonpsuccesstimeouttraditionaltypeurl$.ajax.$.ajax, e non necessita manipolare o gestire gli errori, esistono altri metodi più adatti per le richieste Ajax (sebbene, come notato in precedenza, questi sono basati al metodo $.ajax con valori prestabiliti di configurazione).I metodi che fornisce la libreria sono:
$.get$.post$.getScript$.getJSONurldatasuccess callbackdata type
// ottiene il testo piano o html
$.get('/users.php', { userId : 1234 }, function(resp) {
console.log(resp);
});
// aggiunge uno script alla pagina e quindi
// esegue la funzione specificata
$.getScript('/static/js/myScript.js', function() {
functionFromMyScript();
});
// ottiene i dati in formato JSON dal server
$.getJSON('/details.php', function(resp) {
$.each(resp, function(k, v) {
console.log(k + ' : ' + v);
});
});
$.fn.load7.3: Utilizzare il metodo $.fn.load per riempire un elemento
$('#newContent').load('/foo.html');
$('#newContent').load('/foo.html #myDiv h1:first', function(html) {
alert('Contenuto aggiornato!');
});
7.5: Trasformare le informazioni da una form in una stringa di dati
$('#myForm').serialize();
$('#myForm').serializeArray();
// crea una struttura come questa:
[
{ name : 'field1', value : 123 },
{ name : 'field2', value : 'hello world' }
]
7.7: Usare YQL e JSONP
$.ajax({
url : 'http://query.yahooapis.com/v1/public/yql',
// viene aggiunto come parametro il nome della funzione
// di ritorno, come specificato nel servizio di YQL
jsonp : 'callback',
// si indica jQuery che è atteso informazioni
// in formato JSONP
dataType : 'jsonp',
// si indica al servizio di YQL quale è l'informazione
// che si desidera e che la si vuole in formato JSON
data : {
q : 'select title,abstract,url from search.news where query="cat"',
format : 'json'
},
// si esegue una funzione se
// la richiesta è andata a buon fine
success : function(response) {
console.log(response);
}
});
7.8: Mostra/Nascondi un indicatore utilizzando Eventi di Ajax
$('#loading_indicator')
.ajaxStart(function() { $(this).show(); })
.ajaxStop(function() { $(this).hide(); });
- Creare un elemento div dopo il titolo di ogni titolo di articolo del blog e salvare un riferimento ad essi nell’elemento titolo utilizzando $.fn.data.
- Vinculare un evento click al titolo, il quale utilizzerà il metodo $.fn.load per caricare in ogni div creato il contenuto appropriato dal file /exercises/data/blog.html. Non dimenticare di disattivare il comportamento predefinito dell’evento click.
Da notare che ogni titolo di articolo del blog in index.html include un link a questo articolo. Sfruttare l’attributo href di ogni link per ottenere il contenuto particolare di blog.html. Dopo aver ottenuto il valore dell’attributo, utilizzare il seguente form per elaborare le informazioni e convertirla in un selettore per l’uso in combinazione con $.fn.load:
var href = 'blog.html#post1';
var tempArray = href.split('#');
var id = '#' + tempArray[1];
- Aggiungere un elemento div dopo il form all’interno dell’elemento #specials; sarà lì il luogo dove si metteranno le informazioni da ottenere.
- Vincolare l’evento change sull’elemento select; quando si apporta una modifica nella selezione, inviare una richiesta Ajax a /exercises/data/specials.json.
- Quando la richiesta restituisce una risposta, utilizzare il valore selezionato nella select (suggerimento: $.fn.val) per trovare le informazioni appropriate nella risposta JSON.
- Aggiungere un po’ di HTML con le informazioni ottenute nel div creato in precedenza.
- Infine rimuovere il pulsante di invio (submit) del form.
Notare che ogni volta che cambia la selezione, si esegue una richiesta Ajax. Come si potrebbe modificare il codice per eseguire solo una richiesta e salvare le informazioni per trarre vantaggio quando si torna a modificare la selezione?
Lo scopo di un’estensione è di eseguire un’azione utilizzando un insieme di elementi, proprio come fanno, per esempio, i metodi fadeOut o addClass della libreria.
È possibile effettuare le proprie estensioni e usarle privatamente nel progetto o è anche possibile pubblicarli in modo che altri potranno usufruire.
(function($){
$.fn.myNewPlugin = function() {
return this.each(function(){
// fare qualcosa
});
};
}(jQuery));
$.fn.myNewPlugin = function() { //...
(function($){
//...
}(jQuery));
Per ora, internamente l’estensione è:
$.fn.myNewPlugin = function() {
return this.each(function(){
// fare qualcosa
});
};
var somejQueryObject = $('#something');
$.fn.myNewPlugin = function() {
alert(this === somejQueryObject);
};
somejQueryObject.myNewPlugin();
// mostra un avviso con 'true'
Per interagire con l’insieme di elementi, è necessario fare un ciclo, che è facilmente ottenibile con il metodo each():
$.fn.myNewPlugin = function() {
return this.each(function(){
});
};
(function($){
$.fn.showLinkLocation = function() {
return this.filter('a').each(function(){
$(this).append(
' (' + $(this).attr('href') + ')'
);
});
};
}(jQuery));
// Esempio di utilizzo:
$('a').showLinkLocation();
<!-- Prima che l'estensione sia chiamata: -->
<a href="page.html">Foo</a>
<!-- Dopo che l'estensione sia chiamata: -->
<a href="page.html">Foo (page.html)</a>
(function($){
$.fn.showLinkLocation = function() {
return this.filter('a').append(function(){
return ' (' + this.href + ')';
});
};
}(jQuery));
Di seguito è riportato un altro eEsempio di estensione. In questo caso, non è tenuto a fare un ciclo su ogni elemento perché la funzionalità è delegata direttamente in un altro metodo di jQuery:
(function($){
$.fn.fadeInAndAddClass = function(duration, className) {
return this.fadeIn(duration, function(){
$(this).addClass(className);
});
};
}(jQuery));
// Esempio di utilizzo:
$('a').fadeInAndAddClass(400, 'finishedFading');
Tuttavia, la qualità può variare notevolmente tra le estensioni. Molti sono ampiamente testati e ben mantenute, ma alcuni sono state create in fretta e poi ignorate, senza seguire le buone prassi.
Google è il miglior strumento per trovare le estensioni (anche se il team di jQuery sta lavorando per migliorare la sua repository di estensioni). Una volta trovata l’estensione, si consiglia di consultare la newsletter di jQuery o il canale IRC #jquery per ottenere il parere di altri circa l’estensione.
Assicurarsi che l’estensione sia ben documentata, e che offono esempi di utilizzo. Stare attenti anche con le estensioni che fanno più del necessario, queste possono arrivare a sovraccaricare la pagina. Per ulteriori suggerimenti su come individuare un’estensione mediocre, è possibile leggere l’articolo (in inglese) I segni di un plugin per jQuery scritto male da Remy Sharp (http://remysharp.com/2010/06/03/signs-of-a-poorly-written-jquery-plugin/).
Dopo aver selezionato l’estensione, è necessario aggiungerla alla pagina. In primo luogo, scaricare l’estensione, scompattarla (se necessario) e spostarla nella cartella dell’applicazione. Infine inserirla utilizzando l’elemento script (dopo l’inclusione di jQuery).
La maggior parte delle estensioni sono metodi creati all’interno del namespace $.fn. jQuery assicura che un metodo chiamato sull’oggetto jQuery sia in grado di accedere a detto oggetto attraverso this. In contrasto, l’estensione dovrebbe garantire per restituire lo stesso oggetto ricevuto (salvo diversa indicazione).
Di seguito si mostra un esempio:
8.1: Creare una estensione per aggiungere e rimuovere una clesse in un elemento quando accade l'evento hover
// definizione dell'estensione
(function($){
$.fn.hoverClass = function(c) {
return this.hover(
function() { $(this).toggleClass(c); }
);
};
})(jQuery);
// utilizzare l'estensione
$('li').hoverClass('hover');
In questo articolo, si sviluppa un’estensione chiamata
$.fn.hilight, che fornisce un supporto per l’estensione metadata (se presente) e fornisce un metodo decentrato per impostare opzioni globali o istanze dell’estensione.8.2: Il modello di sviluppo di estensioni per jQuery spiegato da Mike Alsup
//
// creare una chiusura
//
(function($) {
//
// definizione dell'estensione
//
$.fn.hilight = function(options) {
debug(this);
// generazione delle opzioni principali
// prima di interagire
var opts = $.extend({}, $.fn.hilight.defaults, options);
// ittera e riformatta ogni elemento
return this.each(function() {
$this = $(this);
// generazione delle opzioni specifiche
// per ogni elemento
var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
// aggiornamento degli stili per ogni elemento
$this.css({
backgroundColor: o.background,
color: o.foreground
});
var markup = $this.html();
// si chiama la funzione per la formattazione
markup = $.fn.hilight.format(markup);
$this.html(markup);
});
};
//
// funzione privata per la depurazione
//
function debug($obj) {
if (window.console && window.console.log)
window.console.log('hilight selection count: ' + $obj.size());
};
//
// definire ed esporre la funzione di formattazione
//
$.fn.hilight.format = function(txt) {
return '<strong>' + txt + '</strong>';
};
//
// opzioni predefinite
//
$.fn.hilight.defaults = {
foreground: 'red',
background: 'yellow'
};
//
// fine della chiusura
//
})(jQuery);
Mentre la maggior parte delle estensioni per jQuery sono esenti da manutenzione dello stato (stateless in inglese) – vale a dire, estensioni che vengono eseguiti su un solo elemento, essendo l’unica interazione – c’è un grande numero di funzionalità che non si utilizzano nello schema di base dove si sviluppano estensioni.
Per colmare questa lacuna, jQuery UI (jQuery User Interface) ha implementato un sistema più avanzato di estensioni. Questo sistema permette di gestire gli stati e ammette più funzioni da essere esposte in una singola estensione. Questo sistema è chiamato widget factory e fa parte della versione 1.8 di jQuery UI attraverso jQuery.widget, ma può essere utilizzato anche senza dipendere da jQuery UI.
Per dimostrare le capacità di widget factory, si creerà un’estensione che avrà come funzionalità sarà barra di avanzamento.
Per ora, l’estensione permetterà solo d’impostare il valore della barra di avanzamento una volta. Questo sarà fatto chiamando jQuery.widget con due parametri: il nome dell’estensione da creare e un oggetto letterale contenente le funzioni supportate. Quando l’estensione sarà chiamata, una sua istanza verrà creata e tutte le funzioni si eseguiranno nel contesto di tale istanza.
Ci sono due differenze importanti rispetto a una estensione standard per jQuery: in primo luogo, il contesto è un oggetto, non un elemento DOM; in secondo luogo, il contesto è sempre un unico oggetto, mai una collezione.
8.3: Una semplice estensione con mantenimento dello stato utilizzando widget factory di jQuery UI
$.widget("nmk.progressbar", {
_create: function() {
var progress = this.options.value + "%";
this.element
.addClass("progressbar")
.text(progress);
}
});
$("<div></div>")
.appendTo( "body" )
.progressbar({ value: 20 });
Come si mostra sotto, è possibile specificare valori predefiniti per ogni opzione. Questi valori dovrebbero essere basati sull’uso più comune dell’estensione.
8.5: Impostare le opzioni di default per un widget
$.widget("nmk.progressbar", {
// opzioni predefinite
options: {
value: 0
},
_create: function() {
var progress = this.options.value + "%";
this.element
.addClass( "progressbar" )
.text( progress );
}
});
8.6: Creare metodi nel Widget
$.widget("nmk.progressbar", {
options: {
value: 0
},
_create: function() {
var progress = this.options.value + "%";
this.element
.addClass("progressbar")
.text(progress);
},
// crea un metodo pubblico
value: function(value) {
// non si passa alcun valore,
// agisce quindi come metodo ottenitore (getter)
if (value === undefined) {
return this.options.value;
// passando un valore, agisce quindi
// come metodo impostatore (setter)
} else {
this.options.value = this._constrain(value);
var progress = this.options.value + "%";
this.element.text(progress);
}
},
// crea un metodo privato
_constrain: function(value) {
if (value > 100) {
value = 100;
}
if (value < 0) {
value = 0;
}
return value;
}
});
8.7: Chiamare i metodi in un'istanza dell'estensione
var bar = $("<div></div>")
.appendTo("body")
.progressbar({ value: 20 });
// ottiene il valore corrente
alert(bar.progressbar("value"));
// aggiorna il valore
bar.progressbar("value", 50);
// ottiene di nuovo il valore
alert(bar.progressbar("value"));
8.8: Risponde quando l'opzione è impostata
$.widget("nmk.progressbar", {
options: {
value: 0
},
_create: function() {
this.element.addClass("progressbar");
this._update();
},
_setOption: function(key, value) {
this.options[key] = value;
this._update();
},
_update: function() {
var progress = this.options.value + "%";
this.element.text(progress);
}
});
8.9: Fornire funzioni di (callback) retrochiamata
$.widget("nmk.progressbar", {
options: {
value: 0
},
_create: function() {
this.element.addClass("progressbar");
this._update();
},
_setOption: function(key, value) {
this.options[key] = value;
this._update();
},
_update: function() {
var progress = this.options.value + "%";
this.element.text(progress);
if (this.options.value == 100) {
this._trigger("complete", null, { value: 100 });
}
}
});
Se l’estensione avrà qualche funzionalità che potrebbero essere annullate dall’utente, il modo migliore è quello di creare funzioni di callback annullabile. L’utente può annullare una funzione di callback o il suo evento associato nello stesso modo che viene annullato qualunque evento nativo: chiamando a event.preventDefault() o usando return false.
8.10: Vincolare a eventi del widget
var bar = $("<div></div>")
.appendTo("body")
.progressbar({
complete: function(event, data) {
alert( "Funzione di callback" );
}
})
.bind("progressbarcomplete", function(event, data) {
alert("Eventi bolla e molti gestori di supporto per una flessibilità estrema.");
alert("Il valore della barra di avanzamento è " + data.value);
});
bar.progressbar("option", "value", 100);
Perché l’istanza dell’estensione è direttamente legata all’elemento DOM, è possibile accedere all’istanza dell’estensione direttamente. Questo permette di chiamare i metodi direttamente nell’istanza dell’estensione invece di passare il nome del metodo come una stringa, dando la possibilità di accedere alle proprietà dell’estensione.
var bar = $("<div></div>")
.appendTo("body")
.progressbar()
.data("progressbar" );
// chiamare un metodo direttamente
// nell'istanza dell'estensione
bar.option("value", 50);
// accedere alle proprietà
// nell'istanza dell'estensione
alert(bar.options.value);
$.nmk.progressbar.prototype.reset = function() {
this._setOption("value", 0);
};
8.11: Aggiungere un metodo destroy al widget
$.widget( "nmk.progressbar", {
options: {
value: 0
},
_create: function() {
this.element.addClass("progressbar");
this._update();
},
_setOption: function(key, value) {
this.options[key] = value;
this._update();
},
_update: function() {
var progress = this.options.value + "%";
this.element.text(progress);
if (this.options.value == 100 ) {
this._trigger("complete", null, { value: 100 });
}
},
destroy: function() {
this.element
.removeClass("progressbar")
.text("");
// chiama alla funzione base destroy
$.Widget.prototype.destroy.call(this);
}
});
$('#myTable').stripe('#cccccc');
Molte di queste pratiche si basano sulla presentazione jQuery Anti-Patterns for Performance (in inglese) di Paul irlandese.
Salva la lunghezza del loops
var myLength = myArray.length;
for (var i = 0; i < myLength; i++) {
// fare delle cose
}
// male
$.each(myArray, function(i, item) {
var newListItem = '<li>' + item + '</li>';
$('#ballers').append(newListItem);
});
// meglio: fare questo
var frag = document.createDocumentFragment();
$.each(myArray, function(i, item) {
var newListItem = '<li>' + item + '</li>';
frag.appendChild(newListItem);
});
$('#ballers')[0].appendChild(frag);
// o questo
var myHtml = '';
$.each(myArray, function(i, item) {
html += '<li>' + item + '</li>';
});
$('#ballers').html(myHtml);
// MALE
if ($eventfade.data('currently') != 'showing') {
$eventfade.stop();
}
if ($eventhover.data('currently') != 'showing') {
$eventhover.stop();
}
if ($spans.data('currently') != 'showing') {
$spans.stop();
}
// BENE
var $elems = [$eventfade, $eventhover, $spans];
$.each($elems, function(i,elem) {
if (elem.data('currently') != 'showing') {
elem.stop();
}
});
// MALE
$(document).ready(function() {
$('#magic').click(function(e) {
$('#yayeffects').slideUp(function() {
// ...
});
});
$('#happiness').load(url + ' #unicorns', function() {
// ...
});
});
// MEGLIO
var PI = {
onReady : function() {
$('#magic').click(PI.candyMtn);
$('#happiness').load(PI.url + ' #unicorns', PI.unicornCb);
},
candyMtn : function(e) {
$('#yayeffects').slideUp(PI.slideCb);
},
slideCb : function() { ... },
unicornCb : function() { ... }
};
$(document).ready(PI.onReady);
Selettori basati sugli ID
// veloce
$('#container div.robotarm');
// super-veloce
$('#container').find('div.robotarm');
$.fn.find è più veloce perché la prima selezione utilizza il motore di selezione interno Sizzle -. Mentre la selezione realizzata unicamente per ID utilizza document.getElementById(), che è estremamente veloce grazie alla è una funzione nativa del browser web.
// non ottimizzato
$('div.data .gonzalez');
// ottimizzato
$('.data td.gonzalez');
Evitare specificità eccessiva
$('.data table.attendees td.gonzalez');
// molto meglio: eliminare il mezzo, se possibile,
$('.data td.gonzalez');
$('.buttons > *'); // molto lento
$('.buttons').children(); // molto meglio
$('.gender :radio'); // selezione universale implicita
$('.gender *:radio'); // stesa cosa, ma in modo esplicito
$('.gender input:radio'); // molto meglio
Inoltre, la delegazione di eventi permette di aggiungere nuovi elementi contenitori alla pagina, senza dover vincolare i loro gestori di eventi.
// sbagliato (se ci sono molti elementi nella lista)
$('li.trigger').click(handlerFn);
// meglio: delegazione di eventi con $ fn.live.
$('li.trigger').live('click', handlerFn);
// molto meglio:. delegazione di evento con $ fn.delegate
// permette specificare un contesto in modo facile
$('#myList').delegate('li.trigger', 'click', handlerFn);
var $table = $('#myTable');
var $parent = $table.parent();
$table.detach();
// ... si aggiungono molte celle alla tabella
$parent.append($table);
// corretto fino a 20 elementi, lento dopo i 20 elementi
$('a.swedberg').css('color', '#asd123');
$('<style type="text/css">a.swedberg { color : #asd123 }</style>')
.appendTo('head');
// regolare
$(elem).data(key,value);
// 10 volte più veloce
$.data(elem,key,value);
// SBAGLIATO: il codice esegue tre funzioni
// senza verificare se ci sono
// nella selezione
$('#nosuchthing').slideUp();
// MEGLIO
var $mySelection = $('#nosuchthing');
if ($mySelection.length) { $mySelection.slideUp(); }
// MOLTO MEGLIO: aggiunge una estensione doOnce
jQuery.fn.doOnce = function(func){
this.length && func.apply(this);
return this;
}
$('li.cartitems').doOnce(function(){
// fare qualcosa
});
// antico
var test = 1;
var test2 = function() { ... };
var test3 = test2(test);
// migliore modo
var test = 1,
test2 = function() { ... },
test3 = test2(test);
(function(foo, bar) { ... })(1, 2);
// antico
if (type == 'foo' || type == 'bar') { ... }
// migliore
if (/^(foo|bar)$/.test(type)) { ... }
// ricerca in oggetto letterale
if (({ foo : 1, bar : 1 })[type]) { ... }
Concetti chiave
- Il codice deve essere suddiviso in unità funzionali – moduli, servizi, ecc. E devono evitare la tentazione di avere tutto in un unico blocco $(document).ready(). Questo concetto è noto come incapsulamento.
- Non ripetere il codice. Identificare pezzi simili e utilizzare tecniche di eredità.
- Sebbene la natura di jQuery, non tutte le applicazioni JavaScript si trovano nel DOM e non tutte le parti di funzionalità hanno la necessità di avere un rappresentanzio nel DOM.
- Le unità di funzionalità devono avere un giunto flessibile (in inglese loosely coupled) – una unità di funzionalità dovrebbe essere in grado di esistere da sé e la comunicazione con altre unità dovrebbe avvenire attraverso un sistema di messaggi come eventi personalizzati o pub/sub. E per quanto possibile, tenere lontana la comunicazione diretta le unità funzionali.
Il concetto di giunto (o articolazione) flessibile può essere particolarmente problematico per gli sviluppatori che fanno la loro prima incursione in applicazioni complesse, quindi se siete di partenza, cercate di essere consapevoli di questo concetto.
L'Oggetto Letterale
10.1: Un oggetto letterale
var myFeature = {
myProperty : 'ciao',
myMethod : function() {
console.log(myFeature.myProperty);
},
init : function(settings) {
myFeature.settings = settings;
},
readSettings : function() {
console.log(myFeature.settings);
}
};
myFeature.myProperty; // 'ciao'
myFeature.myMethod(); // registra 'ciao'
myFeature.init({ foo : 'bar' });
myFeature.readSettings(); // registra { foo : 'bar' }
Come è possibile applicare questo schema nel codice jQuery? Per esempio, nel codice seguente scritto nello stile tradizionale di jQuery:
// Facendo clic su un item della lista
// si carica un certo contenuto.
// Utilizzando l'ID dell'item e a sua
// volta nasconde gli altri item fratelli.
$(document).ready(function() {
$('#myFeature li')
.append('<div/>')
.click(function() {
var $this = $(this);
var $div = $this.find('div');
$div.load('foo.php?item=' +
$this.attr('id'),
function() {
$div.show();
$this.siblings()
.find('div').hide();
}
);
});
});
10.2: Utilizzare un oggetto letterale per una funzionalità jQuery
var myFeature = {
init : function(settings) {
myFeature.config = {
$items : $('#myFeature li'),
$container : $('<div class="container"></div>'),
urlBase : '/foo.php?item='
};
// permete di sovrascrivere
// la configurazione predefinita
$.extend(myFeature.config, settings);
myFeature.setup();
},
setup : function() {
myFeature.config.$items
.each(myFeature.createContainer)
.click(myFeature.showItem);
},
createContainer : function() {
var $i = $(this),
$c = myFeature.config.$container.clone()
.appendTo($i);
$i.data('container', $c);
},
buildUrl : function() {
return myFeature.config.urlBase +
myFeature.$currentItem.attr('id');
},
showItem : function() {
var myFeature.$currentItem = $(this);
myFeature.getContent(myFeature.showContent);
},
getContent : function(callback) {
var url = myFeature.buildUrl();
myFeature.$currentItem
.data('container').load(url, callback);
},
showContent : function() {
myFeature.$currentItem
.data('container').show();
myFeature.hideContent();
},
hideContent : function() {
myFeature.$currentItem.siblings()
.each(function() {
$(this).data('container').hide();
});
}
};
$(document).ready(myFeature.init);
I vantaggi sono:
- Separazione di ogni funzionalità in piccoli metodi. In futuro, se si desidera cambiare la modo di visualizzare il contenuto, ci sarà chiaro dove farlo. Nel codice originale, questo passaggio è molto più difficile da individuare.
- Si rimuovono gli usi di funzioni anonime.
- Le opzioni di configurazione ssi sono spostate in una posizione centrale.
- Si eliminano le limitazioni che hanno catene di metodi, rendendo il codice più facile da rifattorare, mescolare e riordinare.
Grazie alle sue caratteristiche, l’uso di oggetti letterali consente un netto miglioramento per lunghi tratti di codice inseriti in un blocco $(document).ready(). Tuttavia, no sono più avanzati che avere diverse dichiarazioni di funzioni all’interno di un blocco $(document).ready().
10.3: Il modello modulare
var feature =(function() {
// variabili e funzioni private
var privateThing = 'segreto',
publicThing = 'non segreto',
changePrivateThing = function() {
privateThing = 'super segreto';
},
sayPrivateThing = function() {
console.log(privateThing);
changePrivateThing();
};
// API pubblica
return {
publicThing : publicThing,
sayPrivateThing : sayPrivateThing
}
})();
feature.publicThing; // registra 'non segreto'
feature.sayPrivateThing();
// registra 'segreto' e cambia il valore
// di privateThing a 'super segreto'
Il modello è potente perché permette di avere variabili e funzioni private, esponendo una API limitata che consente la retituzione di proprietà e metodi di un oggetto.
Di seguito è riportato una revisione dell’esempio visto prima, con le stesse caratteristiche, ma esponendo un unico metodo pubblico del modulo, showItemByIndex().
10.4: Utilizzare il modello modulare per una funzionalità jQuery
$(document).ready(function() {
var feature = (function() {
var $items = $('#myFeature li'),
$container = $('<div class="container"></div>'),
$currentItem,
urlBase = '/foo.php?item=',
createContainer = function() {
var $i = $(this),
$c = $container.clone().appendTo($i);
$i.data('container', $c);
},
buildUrl = function() {
return urlBase + $currentItem.attr('id');
},
showItem = function() {
var $currentItem = $(this);
getContent(showContent);
},
showItemByIndex = function(idx) {
$.proxy(showItem, $items.get(idx));
},
getContent = function(callback) {
$currentItem.data('container').load(buildUrl(), callback);
},
showContent = function() {
$currentItem.data('container').show();
hideContent();
},
hideContent = function() {
$currentItem.siblings()
.each(function() {
$(this).data('container').hide();
});
};
$items
.each(createContainer)
.click(showItem);
return { showItemByIndex : showItemByIndex };
})();
feature.showItemByIndex(0);
});
Quando un progetto raggiunge una certa dimensione, cominicia a diventare difficile la gestione dei moduli in un’applicazione, giacché è necessario sapere ordinarli in modo corretto, e cominciare a combinarli in un unico file per ottenere il minor numero di richieste. È anche possibile che si voglia caricare codice “al volo” dopo il caricamento della pagina.
RequireJS è uno strumento per la gestione delle dipendenze creato da James Burke, che aiuta a gestire i moduli, caricarli nel giusto ordine e combinarli con facilità, senza dover apportare alcuna modifica. A sua volta, fornisce un modo semplice per caricare il codice una volta caricata la pagina, permettendo di minimizzare il tempo di download.
RequireJS possiede un sistema modulare, ma non c’è bisogno di seguirlo per ottenere i sui benefici. Il formato modulare di RequireJS permette la scrittura del codice incapsulato, incorporazione dell’internazionalizzazione (i18n) ai pacchetti (per consentirne l’uso in diverse lingue) e anche l’uso dei servizi JSONP come dipendenze.
Ottenere RequireJS
È anche utile scaricare un esempio di progetto jQuery che utilizza RequireJS.
10.5: Usare RequireJS: un semplice esempio
<!DOCTYPE html>
<html>
<head>
<title>jQuery+RequireJS pagina di esempio</title>
<script src="scripts/require-jquery.js"></script>
<script>require(["app"]);</script>
</head>
<body>
<h1>jQuery+RequireJS pagina di esempio</h1>
</body>
</html>
<script>require(["scripts/app.js"]);</script>
10.6: Un semplice file JavaScript con dipendenze
require(["jquery.alpha", "jquery.beta"], function() {
// le estensioni jquery.alpha.js y jquery.beta.js
// sono state caricate.
$(function() {
$('body').alpha().beta();
});
});
Se il modulo non ha dipendenze, solo si deve specificare il il nome come primo argomento di require.def(). Il secondo argomento è un oggetto letterale che definisce le proprietà del modulo. Per esempio:
10.7: Definizione di un modulo RequireJS che non ha dipendenze
require.def("my/simpleshirt",
{
color: "black",
size: "unisize"
}
);
Se il modulo ha dipendenze, si può specificare nel secondo argomento di require.def() attraverso un array e poi passare una funzione come terzo argomento. Questa funzione sarà chiamata per definire il modulo una volta caricate tutte le dipendenze. Questa funzione riceve i valori restituiti dalle dipendenze come argomento (nello stesso ordine in cui sono dichiarate nell’array), allora la stessa deve restituire un oggetto che definisca il modulo.
10.8: Definizione di un modulo RequireJS con dipendenze
require.def("my/shirt",
["my/cart", "my/inventory"],
function(cart, inventory) {
// restituisce un oggetto che definisce
// il modulo "my/shirt".
return {
color: "blue",
size: "large"
addToCart: function() {
inventory.decrement(this);
cart.add(this);
}
}
}
);
my/cart.js
my/inventory.js
my/shirt.js
I moduli non devono per forza restituire un oggetto, qualsiasi tipo di valore è permesso.
10.9: Definizione di un modulo RequireJS che restituisce una funzione
require.def("my/title",
["my/dependency1", "my/dependency2"],
function(dep1, dep2) {
// restituisce una funzione per definire "my/title".
// Questo restituisce o imposta
// il titolo della finestra
return function(title) {
return title ? (window.title = title) : window.title;
}
}
);
requirejs/ (utilizzayo per eseguire gli strumenti)
webapp/app.html
webapp/scripts/app.js
webapp/scripts/require-jquery.js
webapp/scripts/jquery.alpha.js
webapp/scripts/jquery.beta.js
10.10: File di configurazione per gli strumenti di ottimizzazione di RequireJS
{
appDir: "../",
baseUrl: "scripts/",
dir: "../../webapp-build",
// Commentare la riga seguente se volete
// minimizzare il codice dal compilatore
// in modalità "semplice"
optimize: "none",
modules: [
{
name: "app"
}
]
}
Per avviare l’elaborazione dei files, aprire una finestra di comando, andare alla cartella webapp/scripts ed eseguire:
# per sistemi non-Windows
../../requirejs/build/build.sh app.build.js
# per sistemi Windows
..\..\requirejs\build\build.bat app.build.js
var myPortlet = Portlet({
title : 'Curry',
source : 'data/html/curry.html',
initialState : 'open' // or 'closed'
});
myPortlet.$element.appendTo('body');
myPortlet.open(); // forza per aprire
myPortlet.close(); // forza per chiudere
myPortlet.toggle(); // alterna gli stati
// tra aperto e chiuso
myPortlet.refresh(); // aggiorna il contenuto
myPortlet.destroy(); // rimuove il porlet dalla pagina
myPortlet.setSource('data/html/onions.html');
// cambia il codice
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):
<div class="room" id="kitchen">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
Senza l’utilizzo di eventi personalizzati, è possibile scrivere la routine come segue:
$('.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');
}
});
$('.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');
});
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:
<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>
$('.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');
}
});
- 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:
$(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' ]);

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
<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>
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
populateremovecollapseexpand
$.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');
}
};
$('#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 ]);
});
});
});
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:
$('form').submit(function(e) {
e.preventDefault();
var term = $('#search_term').val();
$('#twitter').trigger('getResults', [ term ]);
});
$('#get_trends').click(function() {
$('#twitter').trigger('getTrends');
});
$.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 ]);
}
});
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.
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

Ancora nessun commento