Corso JavaScript (parte 3)
A cura di Gabriele Favrin
Articolo pubblicato su Amiga Life 110 (aprile 2000)
Nota bene: l'articolo proposto in questa pagina è
proprietà dell'autore e non può essere distribuito o
riutilizzato in alcun modo o forma senza il suo consenso scritto.
Le informazioni qui riportate risalgono alla data di pubblicazione
dell'articolo e non è detto che siano ancora valide o
che rispecchino lo stato attuale dei programmi, le posizioni delle persone
interpellate o quelle degli autori stessi.
Avviso
Questo corso descrive JavaScript 1.1. Sebbene le informazioni proposte siano valide come punto di partenza per la conoscenza del linguaggio, suggerisco agli interessati di seguire guide aggiornate alla versione più recente di JavaScript che offre funzionalità avanzate come la gestione di dati XML esterni e la completa manipolazione dell'aspetto del documento.
Introduzione
Nella scorsa puntata abbiamo parlato degli oggetti e della loro
creazione e manipolazione al fine di controllare l'ambiente JavaScript
e di riflesso il documento a video. Ricordiamo che oltre agli oggetti
citati nel corso e negli esempi aggiuntivi, ne esistono parecchi altri,
descritti nelle specifiche ufficiali JavaScript.
Sempre nella scorsa puntata abbiamo visto come gli script, oltre che
eseguiti automaticamente durante il caricamento del documento, possono
essere interfacciati al browser e controllati in base alle azioni
dell'utente.
Esiste però una terza strada per eseguire uno script: l'uso
del metodo "setTimeout" dell'oggetto "window" (la finestra su cui
è visualizzato il documento). Tale metodo si invoca fornendo
come argomenti l'espressione JavaScript da eseguire (generalmente una
funzione) e l'intervallo di tempo, espresso in millisecondi, trascorso
il quale eseguirla. E' tramite questa soluzione che sono realizzate le
diffuse scritte scorrevoli o i banner pubblicitari a rotazione. Vediamo
un esempio, ricordando, ancora una volta, che i numeri di linea sono
presenti solo come riferimento.
10 <html><head><script type="text/javascript" language="JavaScript"><!-- 11 var mode=1; 12 function swap() 13 { 14 if (mode == 1) 15 document.logo.src='logo_eal_color.gif'; 16 else 17 document.logo.src='logo_eal_bw.gif'; 18 19 mode=-mode-0; t=setTimeout('swap()', 500); return true; 20 } 21 // --></script></head> 22 <body onLoad="t=setTimeout('swap()', 500)"> 23 <img name="logo" src="logo_eal_bw.gif" border="0"><br><br> 24 <form><input type="button" value="clearTimeout(t)" onClick="clearTimeout(t)"></form> 25 </body></html>
L'esempio non è utilizzabile su Voyager 3 e su IBworse 2.1. Il primo al momento non supporta "setTimeOut()", mentre il secondo, pur supportando il metodo, non gestisce correttamente lo scambio delle immagini.
Lo script in sé è composto da una semplice funzione di scambio fra le due versioni di un'immagine. Notare l'utilizzo di una singola operazione per cambiare il valore della variabile "mode" da 1 a -1 e viceversa ad ogni esecuzione della funzione.
Al momento del caricamento viene definito un timeout (linea 22) cui si
assegna la variabile "t". Tale assegnazione non è
obbligatoria, a meno che, come in questo caso, non si voglia poter
annullare il timeout impostato utilizzando il metodo "clearTimeout()"
(linea 24).
Tornando alla funzione "swap()" si noterà, nella linea 19,
che il timeout viene reimpostato. Ciò è
necessario in quanto tale metodo opera una sola volta, allo scadere
dell'intervallo di tempo prestabilito (in questo caso 500 millisecondi,
ovvero mezzo secondo).
Anche questa volta l'esempio proposto consente di aggiungere solo una
piccola funzionalità alle nostre pagine. Ma JavaScript offre
molto di più. Può essere utilizzato per
realizzare script complessi, come giochi interattivi, punti di partenza
per controllare server remoti tramite moduli, sin anche a interi
listini o piccoli database. Per farlo è necessario aggirare
alcuni limiti intrinseci del linguaggio, ed è su questo che
si focalizzerà la terza parte del corso.
Ci si chiederà perchè ricorrere a JavaScript,
dovendo aggirare le incompatibilità delle diverse
implementazioni ed i pesanti limiti interni, quando ci si potrebbe
servire di script CGI sul server. Le risposte sono molteplici.
Innanzitutto non è sempre detto che il server su cui si
trovano le nostre pagine supporti certe funzionalità, poi
è bene ricordare che JavaScript, operando sul browser
dell'utente, garantisce tempi di risposta nettamente inferiori rispetto
a quelli di uno script residente su un server remoto, cui collegarsi ad
ogni azione. Infine, le conoscenze necessarie per operare su sistemi
Unix o NT sono senz'altro maggiori di quelle richieste per sviluppare
uno script sul proprio Amiga.
Il funzionamento di un programma "complesso" (almeno rispetto agli
esempi visti sinora) tipico del web è generalmente diviso in
4 fasi:
- Inizializzazione e caricamento dei dati necessari al funzionamento.
- Presentazione dei dati.
- Attesa delle direttive dell'utente.
- Elaborazione delle richieste utente e ritorno al punto 2.
Tralasciando il caricamento dei dati, argomento che tratteremo in
seguito, affrontiamo ora un problema solo in apparenza insormontabile.
Come detto all'inizio di questo corso, gli script JS esistono
esclusivamente all'interno del documento che li ospita. Quando si
cambia pagina, l'intero contesto JavaScript (funzioni, variabili,
eventi, timeout, ecc) viene azzerato.
Come è possibile, dunque, visualizzare dei nuovi dati a
video (l'utilizzo di singoli requester generati con "alert"
è improponibile), quando, così facendo,
svanirebbero le funzioni che compongono il programma che li ha generati?
La soluzione c'è e proviene direttamente dai creatori di
JavaScript.
I frame
I frame, introdotti da Netscape e successivamente ufficializzati a
standard di rete (HTML 3.2), consentono di visualizzare documenti
multipli all'interno di una singola finestra. Se è vero che
uno script può accedere, per ragioni di sicurezza, solo alla
gerarchia degli oggetti presenti nella finestra in cui si trova,
è invece possibile gestire oggetti, metodi,
proprietà, nonchè variabili e funzioni collocate
nelle frame che compongono un frameset (l'insieme di frame presenti in
un documento).
La soluzione, quindi, consiste nell'inserire il proprio script
all'interno della pagina in cui è definito il frameset
(d'ora in poi documento superiore). E' anche possibile usare
"document.write()" per creare il frameset da JavaScript. Sarebbe
comunque opportuno utilizzare questa soluzione solo qualora il supporto
di JavaScript risulti necessario per la successiva consultazione del
documento, per non escludere chi si serve di browser incompatibili con
JS o che non lo ha attivato. Vediamo ora un semplice esempio, diviso
tre parti. Quanto segue non è utilizzabile con Voyager 3 in
quanto tale browser ancora non supporta i vari metodi di riferimento ai
frame da JavaScript. Miglior compatibilità con IBrowse 2.1,
che riconosce le variabili presenti in frame differenti da quello dello
script ma non le funzioni.
Parte 1:
<html> <head> <script type="text/javascript" language="JavaScript"><!-- var test='1 2 3'; function un1(testo) { alert(testo); return true; } // --></script> <frameset cols="50%,*"> <frame src="listato2a.html" name="left"> <frame src="listato2b.html" name="right"> </frameset> </head> </html>
Questo primo documento definisce il frameset. Si noterà che ad ogni frame viene assegnato un nome, utilizzabile poi da JavaScript (o da HTML, con l'attributo "target" dell'elemento "a") per riferirsi al frame e quindi a tutta la gerarchia di oggetti ad esso collegati. Funzioni ed eventuali variabili definite qui saranno accessibili dagli altri frame usando il suffisso "top." che indica, all'interno di un frameset, il documento superiore (quello nel quale il frameset stesso è stato definito.
Parte 2:
<html> <script type="text/javascript" language="JavaScript"><!-- var test='4 5 6'; document.writeln('Variabile test in questo frame: '+test); // --></script> <form> <input type="button" value="top.fun1()" onClick="top.fun1('Funzione invocata dal frame sinistro')"> </form> </html>
Questo è il frame sinistro. Si noti la definizione di una variabile, successivamente accessibile in due modi: dal documento nel quale è stata creata, tramite normale riferimento e da un altro frame, tramite "top.nomeFrame.variabile", in questo caso "top.left.test". E' anche presente un piccolo modulo dal quale si richiama una funzione posta nel documento superiore.
Parte 3:
<html> <script type="text/javascript" language="JavaScript"><!-- document.writeln('Variabile top.test: '+top.test); // --></script> </html>
Il terzo script, posto nel frame destro, si limita a visualizzare la
variabile "test" definita nel documento superiore.
Il tutto, che a un primo sguardo potrà apparire complesso,
risulta invece piuttosto semplice una volta compresi i vari meccanismi
con cui riferirsi ai singoli frame, anche perché,
trattandosi sempre di oggetti, è possibile definire un nome
nuovo con il quale riferirsi ad uno specifico documento ed ai relativi
metodi e proprietà (le variabili definite in uno script non
sono che proprietà del documento nel quale lo script
è posto).
Analogamente a immagini e moduli, anche per i frame esiste un array,
tramite il quale è possibile conoscere nome, URL, documento
superiore e varie altre informazioni relative a ognuno dei frame
presenti nel documento, nonché accedervi direttamente,
qualora non sia possibile (o conveniente, ad esempio in un ciclo) farlo
per nome.
Operando con i frame è possibile usare appieno i metodi
"open()", "write()" e "close()" dell'oggetto document (da non
confondere con "open()" e "close()" di "window") per modificare il
contenuto di uno o più frame. L'oggetto "document"
è infatti una proprietà dell'oggetto "frame"
(attenzione a riferirsi al frame corretto, quindi!). JavaScript1.2
mette a disposizione anche l'ultile metodo "document.clear()" per
azzerare il documento corrente.
Un esempio, tenendo come riferimento il frameset precedente:
left.document.open("text/html", "replace"); left.document.writeln('<html>Contenuto ridefinito via JavaScript</html>'); left.document.close();
Queste linee cancellano il documento presente nel frame "left" e ne
creano uno nuovo. I parametri opzionali usati in "open()" consentono di
specificare il tipo MIME del documento (oppure direttamente il plugin
col quale visualizzare i dati) e se il nuovo documento deve sostituire
quello corrente nella cronologia (history) del browser. Attualmente
questi parametri non sono supportati da AWeb.
Può anche rendersi necessaria la sostituzione del contenuto
di un frame con una pagina presente in rete. Per farlo si ricorre
all'oggetto "location", contenente informazioni sull'URL corrente e
modificabile con "left.location=nuovo_url
" ossia, ad esempio,
"left.location='http://www.cnn.com'
".
Con "location" sono utilizzabili sia URL assoluti, cioè
comprensivi di indirizzo e path, sia relativi. Tuttavia,
poiché non è detto che l'URL corrente del frame
(sulla base del quale verrà elaborato l'URL relativo) sia lo
stesso definito all'inizio (ad esempio se il frame è stato
rigenerato tramite JavaScript) è meglio servirsi di URL
assoluti. Per non rendere il proprio script dipendente dal sito su cui
si trova, è possibile ricorrere ad alcune istruzioni per
ricavare il path corrente e utilizzarlo come base:
var loc_href=document.URL; var loc_hrefslash=loc_href.lastIndexOf('/'); var base_href=loc_href.substring(0, (loc_hrefslash > 7 ? loc_hrefslash : loc_href.length))+'/';
A questo punto con "left.location=base_href+'nuovo.html'
"
sarà possibile caricare il documento "nuovo.html" senza
rischio di errori. Il ricorso a passaggi intermedi è
necessario per aggirare un bug di MSIE che si manifesta utilizzando il
metodo "lastIndexOf()" sulle proprietà di "location". Il
meccanismo proposto non funziona con IBrowse 2.1, in quanto proprio
"lastIndexOf()" non è ancora supportato.
Tramite i frame, e la conseguente possibilità di gestire
più documenti alla volta, abbiamo risolto il problema della
visualizzazione dati. Basterà infatti deputare un frame a
questo scopo e gestire gli input dell'utente dagli altri. Resta aperta
la questione di come caricare dinamicamente le informazioni, ad esempio
per un quiz interattivo o un piccolo database. La soluzione comunque
è a portata di mano ed è una conseguenza diretta
dell'utilizzo dei frame.
Caricamento dinamico dei dati
Sebbene, come spiegato nella prima puntata di questo corso, JavaScript
non sia in grado di caricare file e quindi dati aggiuntivi, esiste una
soluzione semplice quanto efficace per realizzare qualcosa di analogo.
Parlando di frame abbiamo visto come sia possibile definire un frameset
e poi, mantenendo tutte le funzioni create al suo interno, modificare
una o più frame caricandovi nuovi documenti.
Ora, cosa succederebbe se all'interno dei nuovi documenti inserissimo
uno script in cui vengono definite delle variabili (ad esempio usando
un array)? Otterremmo, senza troppa fatica, il caricamento
dinamico di dati non contenuti nel programma. Ma vediamo un semplice
esempio, immaginando di avere uno script che visualizza dei proverbi
caricati da diverse pagine. Creato il frameset (composto dai frame di
nome "up" e "down"), associamo ad un bottone, tramite "onClick()", la
seguente funzione:
function caricaNuovo() { up.location=base_href+'data'+(counter++)+'.html'; return true; }
La funzione provvede a caricare nel frame superiore un documento HTML di nome "dataNN.html" (dove NN è un numero progressivo), così composto:
<html> <head> <script type="text/javascript" language="JavaScript"><!-- function nuoviDati() { var top.proverbio[num_proverbio++]="Meglio un uovo oggi..."; var top.proverbio[num_proverbio++]="Chi lo dice..."; } // --></script> </head> </html>
In questo modo il problema del caricamento è risolto. Se ne
presenta però un altro: quando invocare la funzione che
definisce i nuovi dati?
A questo punto va aperta una parentesi sul comportamento dei browser
durante l'esecuzione di uno script che effettua la modifica di un frame
o, più in generale, di un documento a video. Alcuni
programmi operano in maniera asincrona, ovvero caricano o modificano il
documento man mano che incontrano le relative istruzioni, mentre altri
lavorano in sincrono ossia prima interpretano le istruzioni e poi, solo
al termine dello script corrente (che può essere una singola
funzione, magari associata a un gestore di evento) mostrano a video i
risultati. Il comportamento varia non solo da browser a browser (AWeb,
ad esempio, appartiene alla seconda categoria) ma anche da versione a
versione dello stesso programma (alcune versioni di Netscape per Mac
operano in sincrono, altre in asincrono).
La disomogeneità di funzionamento è tale da non
consentire di fare affidamento sulle modalità di esecuzione,
costringendo quindi ad un diverso approccio al problema di comunicare
allo script principale l'avvenuto caricamento del nuovo documento e la
conseguente disponibilità delle relative funzioni e/o dati.
La soluzione consiste nell'utilizzo degli eventi, in particolare di
"onLoad()". Vediamo come, inserendo nell'esempio precedente la seguente
linea:
<body onLoad="top.mostraProverbi()" onError="alert('Errore durante il caricamento')">
Con questa aggiunta risolviamo anche il problema di comunicare
all'utente eventuali errori occorsi durante il caricamento.
Il meccanismo è presto detto: quando ne abbisogna, lo script
principale, ridefinisce l'URL di un frame di servizio (che
può anche essere invisibile all'utente) e termina. Alla fine
del caricamento della nuova pagina viene eseguita una funzione dello
script principale (in questo caso "mostraProverbi()" che a sua volta
invoca la funzione di definizione dati presente nel documento appena
caricato (nel caso specifico "nuoviDati()").
Tale funzione, per altro, è libera di definire le nuove
variabili sia nel contesto del frame corrente (andranno quindi perse al
momento di un nuovo caricamento) sia, come nell'esempio, in quello
dello script principale utilizzando il suffisso "top." (che, vale la
pena ripeterlo, punta alla finestra contenente il documento che
definisce il frameset).
Una dimostrazione di questo meccanismo si trova sul CD allegato, ed
è adeguatamente commentato per consentire di comprendere
appieno una tecnica che apre la strada ad applicazioni altrimenti
impossibili in JavaScript.
Così come in certi casi si può dover acquisire
nuovi dati durante il funzionamento, capita di avere la
necessità di salvare informazioni. In questo senso le
possibilità di JavaScript sono molto ridotte, ma qualcosa,
in determinate circostanze, si può fare.
I cookie
Anche i cookie, come i frame, sono stati introdotti da Netscape e
successivamente adottati come standard ufficiale di rete. Si tratta di
particolari intestazioni HTTP composte generalmente da nome del cookie,
relativo valore, dominio/path di appartenenza ed eventuale data di
scadenza Il browser immagazina i cookie e, quando torna sullo stesso
indirizzo (o dominio, a seconda dei casi), comunica al server le
informazioni precedentemente ricevute. Esistono comunque diverse
limitazioni: possono esistere al massimo 3000 cookie per browser, di 4k
di lunghezza l'uno (nome e valore) e solo 20 per ogni server. Inoltre
l'accettazione dei cookie è a discrezione dell'utente che
è libero di rifiutarli tutti o selettivamente.
I cookie non rappresentano un rischio per la sicurezza in quanto
vengono memorizzati su specifici file sotto il controllo del browser e
non del server remoto. Esiste tuttavia un rischio privacy legato alle
informazioni memorizzate (alcuni siti, soprattutto commerciali, li
usano per tenere traccia delle attività dell'utente sul
proprio servizio).
JavaScript consente di creare e consultare cookie in maniera abbastanza
semplice, utilizzando la proprietà "cookie" dell'oggetto
"document". Prima di procedere è opportuno attivare
l'accettazione dei cookie (magari selettiva, così da vedere
cosa viene salvato).
Cominciamo con un semplice esempio:
document.cookie='rivista='+escape('EAL');
In questo caso viene definito un cookie di nome "rivista" con valore
"EAL". Notare l'uso della funzione "escape" per convertire
eventuali caratteri non alfanumerici nel formato MIME in cui devono
essere espressi gli header HTTP (% seguito dal valore esadecimale del
carattere). Sebbene non sia questo il caso, è utile
abituarsi ad usare "escape" per non incorrere in spiacevoli sorprese al
momento di creare programmi più complessi.
Il cookie non ha data di scadenza e quindi andrà perso alla
chiusura del browser. Per renderlo permanente è necessario
impostare una data, operazione un pò articolata ma comunque
resa più semplice dalla disponibilità
dell'oggetto "date" e dei relativi metodi. Per definire una data nel
formato standard richiesto dai cookie, si procede come segue:
var oggi=new Date(); var scadenza=new Date(); scadenza.setTime(oggi.getTime()+(1000*60*60*24*365));
La prima linea assegna alla variabile di servizio "oggi" una nuova
istanza dell'oggetto "date" (che, così invocato, contiene la
data corrente). La seconda linea effettua la stessa operazione per la
variabile che rappresenterà la scadenza del cookie e che ora
diviene, anch'essa, un oggetto di tipo "date".
Nella terza linea viene invocato il metodo "setTime()" la cui funzione
è impostare la data in un oggetto "date" utilizzando come
riferimento il numero di millisecondi trascorsi dalla mezzanotte del
1/1/1970. L'argomento fornito è la data che si vuole
ottenere, nel caso specifico la corrente più un
anno. Per calcolarla si utilizza "getTime()" sull'oggetto
"oggi" e si somma il risultato al valore 1000*60*60*24*365, ovvero il
numero di millisecondi presenti in un anno.
A questo punto, la definizione del cookie diverrà:
document.cookie='rivista='+escape('EAL')+'; expire='+scadenza.toGMTString();
Notare l'uso del metodo "toGMTString()" per inserire nel cookie la data
di scadenza nel classico formato GMT.
Fin qui la scrittura, solo in apparenza complessa (invece ci
è stata utile per conoscere meglio l'oggetto "date"). La
lettura è decisamente più semplice. Quando il
nostro script viene eseguito, il browser ha già assegnato al
documento gli eventuali cookie precedentemente memorizzati. Per
leggerli è quindi sufficiente andare a consultare
"document.cookie".
Purtroppo, sebbene a un documento possano essere associati
più cookie, da JavaScript questi risultano tutti parte della
proprietà "document.cookie", a differenza di quanto accade
con altri elementi, come link, immagini e frame, accessibili tramite
array. Pertanto, per isolare una singola coppia nome/valore,
è necessario ricorrere alle varie funzionalità
dell'oggetto "string". La documentazione ufficiale JS ci viene in aiuto
fornendo una comoda funzione che, invocata con il nome di un cookie
come argomento, ne restituisce il valore. Una versione commentata in
italiano è disponibile sul CD allegato, assieme ad altri
esempi relativi alla gestione dei cookie.
Ringraziamenti e note
Si ringraziano AmiTrix development per AWeb e Vapor (nella persona di Luca Danelon) per Voyager3. Grazie ad Angelo Verdone e Daniele Franza per la "prova su strada" del corso.
Ricordiamo che, per ragioni di spazio, gli esempi proposti non contengono tutti gli elementi HTML il cui uso è normalmente obbligatorio all'interno di un documento.