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:

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.

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.