mercoledì 23 gennaio 2008

Capitolo 6 - Costruzione di una Interfaccia Client-Server

Introduzione

Vediamo ora come possiamo implementare un modulo che possa agire da interfaccia di collegamento tra la porzione del Controllore da far eseguire sul Client e la porzione residente sul WebServer che agisce da trade-union con un eventuale Database Server.

In teoria vorremmo poter avere a disposizione un modulo di interfaccia che possa rendersi indipendente il piu' possibile dalla particolare tecnologia con cui potremmo voler costruire la porzione di Controllore residente sul webserver.

Di modo che possa andar bene sia utilizzando ASP.NET/C# su un server IIS che JSP o PHP su un server Apache.

Vedremo quindi come sara' possibile adottare una architettura comune a tutte le tecnologie, specificando anche le peculiarita' necessarie nel caso si usi c# oppure jsp.


Il nostro modulo di interfaccia deve necessariamente avere una porzione lato client che dovra' intergarsi con il Controllore locale scritto in Javascript, ed una porzione lato server che deve integrarsi con la tecnologia server utilizzata.

Lato client si avra' sostanzialmente una sezione di API javascript aggiuntive da aggiungere al controller della nostra web application. Lato server si avra' una pseudo-pagina web dinamica nella tecnologia utilizzata che agira' come wrapper per la libreria-controller lato server della nostra web application.

Lato client

Come abbiamo visto nel capitolo 4 utilizzeremo il metodo net.ContentLoader per effettuare le richieste asincrone verso il webserver.

Il comando base sara' nella forma:

var loaderx=new net.ContentLoader('proxy.jsp?var1=value1&par2=value2’,handler,null,"POST",'');

ove si avra' una pagina dinamica (qui in jsp come esempio) cui passare eventualmente i parametri necessari da elaborare per produrre i dati da inviare in risposta a questa richiesta che verranno gestiti dalla funzione handler() del controller lato client.

Certo e' che e' molto scomodo utilizzare ogni volta la forma base per effettuare le varie richieste che dovremo effettuare verso il server.

Vediamo ora come migliorare il tutto costruendo intorno alla chiamate base una interfaccia di programmazione, o API.

Costruzione di una API lato client

Volendo strutturare un po' il nostro controller lato server possiamo raggruppare tutte le funzioni che ci possono servire in gruppi omogenei, ad esempio raggruppandoli per servizio lato client gestito.

Generalmente si suppone di strutturare l'interfaccia grafica in sezioni tra loro distinte ancorche' correlate, magari corrispondenti alle varie voci che possono presentarsi su un menu' introduttivo. In questo modo possiamo raggruppare i metodi implementati sul controller lato server per classi o servizi di appartenenza.

Grazie a cio' potremo suddividere l'implementazione del controller stesso in moduli separati legati tra loro dalla porzione dell'interfaccia che agira' come smistatore delle chiamate ricevute ai vari moduli.

Definiamo quindi il concetto di classe del nostro controller lato server come raggruppamento delle funzioni implementate per un determinato servizio. Quando faremo una richiesta al server quindi speficicheremo la classe di appartenenza e la funzione effettivamente richiamata.

In questo modo potremo utilizzare un comando simile, in una forma piu' comunemente utilizzata:

executeRequest(‘className’,’functionName’,’parameterList’,returnHandler);

Rimane da ricostruire di volta in volta la parte di querystring relativa al passaggio dei parametri.

Per rendere piu' agevole questo passo utilizziamo un meccanismo di push dei parametri in una struttura buffer costruita con un Array: creaimo un array in cui depositare i vari parametri che poi verranno recuperati dal costruttore della executeRequest che automaticamente generera' la querystring da passare al metodo net.ContentLoader.

var myp=new pars();
function pars(){
this.data=new Array();
}
pars.prototype.init=function(){
this.data.length=0;
}
pars.prototype.addPar=function(obj){
if(obj==undefined){
obj="";
}
this.data.push(obj);
};
pars.prototype.add3Par=function(){
this.addPar();
this.addPar();
this.addPar();
}
pars.prototype.remPar=function(p){
var len=this.data.length;
for(i=0;i<len;i++){
if(this.data[i]==p){
break;
}
}
var len1=len-1;
for(j=i;j<len1;j++){
this.data[j]=this.data[j+1];
}
this.data.length=this.data.length-1;
}
pars.prototype.remAll=function(){
this.data.length=0;
}
pars.prototype.shows=function(){
var output="";
var len=this.data.length;
for(i=0;i<len;i++){
output+=this.data[i].toString();
}
return output;
}

//common vars
lib='./proxy.jsp',bcNm="?class=",bcFx="&function="

function executeRequest(){
var a=executeRequest.arguments;
var clx=a[0]; //class name
var func=a[1]; //function name
var handler=a[2]; //return handler
var debug=a[3]; //debug truefalse
var sync=a[4]; //sync request truefalse
var vars="";
var len=myp.data.length;
var output="";
for(i=0;i<len;i++){
vars+="&value"+(i+1)+"="+myp.data[i].toString();
output+=myp.data[i].toString();
}
window.status=output;
var loaderx=new net.ContentLoader(lib+strParamsx,handler,null,"POST",'','','','',sync);
}


Cosi' per effettuare una richiesta remota occorre inizializzare il buffer dei parametri, aggiungere i parametri , ed effettuare la chiamata alla nostra funzione:


try{
myp.init();
myp.addPar("101");
exQuery("MacroDPDV","getCampi",retHandler);
}catch(err){
errmsgxxx('Preparing getNPWIP',err.message);
}

function retHandler(){
var d=this.req.responseText;
try{
alert(d);
}catch(err){
errmsg('Function retHandler',d+";"+err.message);
}
};

Interfaccia proxy lato server
Vediamo quali requisiti occorre soddisfare rispetto alla creazione e all'utilizzo del file dinamico di interfaccia da costruire sul webserver utilizzando una delle tecnologie a disposizione - JSP o ASP.NET/C#.
Mostriamo quindi un esempio di codice per queste due tecnologie nell'ottica di poterci interfacciare poi ad un insieme di librerie che si possano sviluppare a livello di Business Logic residente sul webserver per il collegamento eventuale con un Database server.
In sostanza vogliamo poter utilizzare per la tecnologia JSP un insieme di JavaBeans, ovvero una libreria scritta in Java, mentre per la tecnologia ASP.NET vorremmo poter collegarci ad una libreria web .NET scritta in C#.
Proxy proxy.jsp

<%@ page language="java" import="java.sql.*, java.util.*, pie.beans.fouryp.FourYPCapexVO"%>
<jsp:useBean id="setPropertyTomcat" scope="session" class="jsputilityservlet.SetPropertyServlet" />
<jsp:useBean id="db" scope="session" class="pie.db.DBConn" />
<jsp:useBean id="ExplGameOpPrj" scope="session" class="pie.beans.pop.ExplGameOpPrj" />
<jsp:useBean id="ExplActDAO" scope="session" class="pie.beans.action.ExplActDAO" />

<%

String beanName = request.getParameter("class");
String beanMethod = request.getParameter("function");
String value = request.getParameter("value1");
String value2 = request.getParameter("value2");
String value3 = request.getParameter("value3");
String result="";
int res=0;

final String newline = System.getProperty("line.separator");

try
{

if(beanName.equals("JSON")){
if(beanMethod.equals("getTechPrjNameList")){
System.out.println("JSON list of Projects Names");
result = ExplActDAO.getPrjNameListJSON(nav.getUser_id_nbr(),nav.getConnection());
out.println(result);
}
}
else if(beanName.equals("actionFilters")){
if(beanMethod.equals("setRefDate")){
ExplGameOpPrj.setExpl_ref_date(value);
}else
if(beanMethod.equals("setEndDateTo")){
ExplGameOpPrj.setAct_end_date_to(value);
}

}
else if(beanName.equals("Budget"))
{

}
}
catch(Exception e)
{
out.println("Errore:"+e.getMessage());
}

%>


In questo modo e' sufficiente includere i JavaBeans e le classi necessarie appartenenti alla libreria (magari compattata in un file jar) e definire le associazioni servizio/funzione/parametri passate dal client con le operazioni richieste ai Business Components del server e i parametri aggiuntivi necessari, eventualmente ricostruendo un messaggio o un export di output HTML o in formato JSON da restituire al client come risposta, nel modo classico per le pagine JSP.

Proxy proxy.ashx

Vediamo ora nel caso volessimo utilizzare la tecnologia .NET lato server.
Innanzitutto vorrei puntualizzare una cosa: lo scopo e' quello di poter utilizzare una libreria centralizzata lato server, che nel caso .NET si traduce nella compilazione di una DLL di classi C# piu' che nella creazione di pagine dinamiche .aspx, anche perche' sarebbe complicato riutilizzarla direttamente con una modalita' simile a quella vista per le librerie Java importabili come JavaBeans.

Occorre innanzitutto utilizzare una pagina dinamica del tipo .ashx , ovvero dobbiamo usare un ASP.NET Web Handler File che permette di richiamare un HTTP Handler.

Un HTTP Handler non è altro che una classe .NET che implementa l'interfaccia IHttpHandler (reperibile sotto il namespace System.Web). Tale interfaccia espone un metodo utile a processare la richiesta HTTP in entrata (il metodo ProcessRequest) e una proprietà, di tipo boolean, utile a decidere se l'istanza dell'HTTP Handler corrente può essere utilizzata o meno da altre richieste (la proprietà IsReusable). Chiaramente, è proprio attraverso l'implementazione di questa interfaccia che noi possiamo sviluppare gli handler utili a svolgere le funzionalità custom di cui abbiamo bisogno.

Il nostro file proxy.ashx contiene solo l'indicazione dell' HTTP Handler:


<%@ WebHandler Language="C#" class="Library.Dispatch"  codebehind="Dispatch.cs" %>

In questo modo si richiama il cotruttore della classe Dispatch compilata nel file Library.dll In questo modo poi la classe Dispatch instrada le associazioni servizio/funzione richieste passando i parametri necessari alle varie funzioni implementate in classi separate per rispecchiare i vari servizi implementati sul client.


using System;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Oracle.DataAccess.Client;

namespace Library
{

public class Dispatch : IHttpHandler, IRequiresSessionState
{
internal OracleConnection conn=new OracleConnection();
internal OracleCommand cmd=new OracleCommand();
internal string connString="";

private HttpContext ctx;
public OracleConnection connTmp;
Library.LoginHandler lh;
Library.postLogin pl;
Library.Servizio1 s1;
Library.Servizio1 s2;

public Dispatch()
{
conn.ConnectionString="";
}
public void ProcessRequest(HttpContext ctx)
{

try
{
if(ctx.Request["class"]=="login")
{
lh=new LoginHandler();
lh.ProcessRequest(ctx);
}
else if(ctx.Request["class"]=="postLogin")
{
pl=new postLogin();
pl.ProcessRequest(ctx);
}
else if(ctx.Request["class"]=="Servizio1")
{
s1=new PerseoStogit.PozziCampione();
if(ctx.Request["function"]=="Funzione1")
{
s1.Funzione1();
}
else if(ctx.Request["function"]=="Funzione2")
{
s1.Funzione2();
}
}
else if(ctx.Request["class"]=="Servizio2")
{
s2=new PerseoStogit.userServices();
if(ctx.Request["function"]=="Funzione1")
{
s2.Funzione1();
}
if(ctx.Request["function"]=="Funzione2")
{
s2.Funzione2();
}
}
else
{
ctx.Response.Write("Non e' stata trovata una corrispondenza con la classe e la funzione chiamate: "+ctx.Request.QueryString.ToString());
}
}
catch(Exception err)
{ctx.Response.Write("Invalid Operation - call system administrator"+err.Message.ToString());}
}
public bool IsReusable { get { return false; } }
}
}


In questo caso e' obbligatorio implementare in ogni classe della libreria i metodi ProcessRequest(HttpContext ctx) e IsReusable specificando che la classe implementa le interfacce IHttpHandler e IRequiresSessionState.
Sono comunque dettagli inerenti alla tecnologia .NET e rimandiamo alla documentazione dedicata per gli approfondimenti necessari. Poi ogni classe c# implementera' le funzionalita' richieste restituendo il risultato al client utilizzando il metodo ctx.Response.Write(...) associato al contesto http gestito dall' HTTP Handler.

Riassunto

In questo modo, seguendo questa architettura, e' possibile interfacciare una libreria di Business Components lato server scritta pressoche' in qualsiasi linguaggio web dinamico (magari in PHP anche se non so se sia possibile costruire un modulo unificato come puo' essere un unico file jar per i JavaBeans o un unica dll compilata per l' http handler .net) oltre a quelli illustrati a patto di costruire una pagina proxy opportuna, potendo utilizzare le stesse API client di interfaccia.

Capitolo 5 - Il ruolo del web server

Introduzione

Vediamo ora alcuni aspetti che interessano principalmente il web server all’interno delle nostre web applications. Partiamo con l’analisi del ruolo del web server nelle applicazioni precedentemente implementate in assenza dell’approccio Ajax, arrivando ad analizzare il ruolo nel web 2.0 che comunque viene mantenuto dal parte del web server.

Utilizzo del web server

Nel ciclo di vita delle nostre applicazioni Ajax, il web server deve adempiere a due ruoli principali, e quesi sono tra loro ben distinti.
Primo, deve caricare l’applicazione sul browser del client. Possiamo quindi assumere che la trasmissione iniziale delle librerie e dei file che compongono lo scheletro della nostra applicazione sia una fase abbastanza statica. Cioe’ scriviamo la nostra applicazione come un insieme di file .html, .css e .js che anche un web server elementare puo’ gestire.
Secondo , deve dialogare con il client, recuperando i dati eventalmente da un database server e rifornire il client con questi dati a fronte di una richiesta pervenuta da esso.
Poiche’ abbiamo a disposizione solo l’ HTTP come unico meccanismo di trasporto disponibile, solo il client puo’ effettuare delle richieste, mentre il server puo’ solo rispondere a tali richieste.

Progettazione lato server

In una applicazione web convenzionale la progettazione lato server tende a diventare piuttosto complessa, dovendo controllare e monitorare il flusso delle operazioni effettuate dall’utente attraverso l’applicazione, e mantenere uno stato consistente con esso.
Solitamente l’applicazione e’ progettata in un linguaggio particolare, tipo JSP o ASP.NET+C#, che possono a loro volta essere conseguenza di una determinata architettura utilizzata, o di un sistema operativo sottostante. Ad esempio, se optiamo per un application server su hardware Linux, non possiamo adoperare un web server IIS collegato al framework .NET, e probabilmente opteremo per l’utilizzo o di Tomcat + JSP oppure di Apache + PHP come infrastruttura lato server.
Non parliamo qui dei framework tipo JSF (Java Server Faces) o STRUTS, primo perche’ non li ho mai utilizzati, quindi non li conosco se non superficialmente, secondo perche’ non modificano di molto il nostro discorso.


Architetture ad N livelli


Un concetto solitamente utilizzato nelle applicazioni distribuite e’ quello di livello. Un livello spesso rappresenta un particolare insieme di responsabilita’ per una applicazione, ma descrive anche un sottosistema che puo’ essere isolato fisicamente su una particolare macchina o processo.

I primi sistemi distribuiti erano costituiti da un livello client e da un livello server. Il client era solitamente un programma desktop che utilizzava una libreria per il socket di rete per comunicare con il server. Ad esempio i form progettati con Oracle Developer 2000 erano form desktop che si connettevano al database server Oracle per recuperare i dati e per gestire la logica lato server con procedure pl/sql.

Allo stesso modo le prime applicazioni web consistevano di una applicazione sul browser che dialogava con il web server, che si occupava solo di inviare i files sulla rete prelevandoli dal proprio filesystem.

A mano a mano che le applicazioni diventavano piu’ complesse e cominciavano a richiedere l’accesso alle basi dati, il modello a due livelli e’ stato applicato al solo web server, arrivando cosi’ al modello a tre livelli comunemente utilizzato, in cui il web server si frappone tra il client e la base dati.

Raffinamenti successivi hanno portato alla separazione ulteriore tra livello di presentazione e regole di business, sia come processi separati, sia come progettazione modulare all’interno di un unico processo.

Le moderne applicazioni web hanno tipicamente due liveli principali. Il livello di business modellizza il dominio del business e dialoga direttamente con il database. Il livello di presentazione prende i dati dal livello di business e li presenta all’utente. Il browser agisce come client muto.

L’introduzione dell’approccio Ajax puo’ essere considerato come lo sviluppo di un livello client ulteriore, che separa le responsibilita’ del tradizionale livello di presentazione della gestione del flusso di lavoro e della sessione utente tra il web server ed il client.


Il ruolo del livello di presentazione lato server puo’ essere ulteriormente ridotto, e il controllo del workflow puo’ essere trasportato sul nuovo livello del client, scritto in Javascript ed eseguito all’interno del browser.



Manutenzione lato client e lato server dei domini di business


In una applicazione Ajax dobbiamo ancora modellizzare il dominio del business sul server, vicino alla base dati e ad altre risorse vitali centralizzate. Comunque, per dare al codice lato client la sufficiente intelligenza e reattivita’, dobbiamo mantenere un modello parziale del dominio di business nel browser. Cio’ genera il problema di manterere sincronnizzati questi due modelli tra di loro.
In realta’ e’ un problema che era gia’ noto sugli application server J2EE, per cui si utilizzavano degli oggetti dedicati al trasferimento dei dati tra i livelli, formati da semplici oggetti JAVA di intercomunicazione.
Nelle nostre applicazioni queste comunicazioni tra livelli si riducono a leggere dati dal server e a scrivere dati sul server. Abbiamo gia’ affontato le metodologie per leggere dati dal web server. E i formati di trasporto dati che possiamo utilizzare. Sappiamo anche gia’ come poter inviare dati al web server. Vedremo successivamente come scrivere dati sulla base dati, ovvero sul livello piu’ lontano rispetto al client.



Web server senza alcun framework

Il modo piu’ semplice di avere un framework lato webserver e’ quello di non averne affatto.
Come abbiamo visto finora, definiamo una pagina master che conterra’ le librerie Javascript, i fogli di stile, e tutte le altre risorse, compreso l’ajax engine. Per fornire i dati a questa pagina dobbiamo semplicemente sostituire o popolare i vari layer con i flussi di dati di nostra scelta.

La controindicazione principale di questo approccio in una applicazione web classica e’ data dal fatto che i collegamenti tra i vari documenti sono sparpagliati nei documenti stessi. Cioe’ il ruolo del Controllore del modello MVC (Model View Controller) non e’ definito con chiarezza in un unico posto.
Se occorre ridefinire il flusso di lavoro occorrera’ di certo modificare i collegamenti in piu’ posizioni su piu’ documenti.

Usando invece i frameworks come Apache Struts si usa la variante Model2, per cui si centralizza il ruolo del Controllore in un unico servizio o pagina che diventa responsabile del routing delle richieste verso i servizi di back-end e della presentazione delle Viste corrette.

Il problema e’ dato dal fatto che questi frameworks non gestiscono il rilascio del client in fase di startup.


Utilizzo di un framework lato server basato su Componenti


Widgets per le web applicatioins

I frameworks basati sui componenti aiutano ad innalzare il livello di astrazione per la progettazione delle interfacce grafiche per il web, fornendo un toolkit di componenti lato server (o anche lato client, come vedremo in seguito) le cui Application Programming Interfaces (o API) assomigliano a quelle usate per i widget set di una GUI desktop.
Quando i widget web vengono visualizzati generano automaticamente un flusso di istruzioni HTML e un gestore Javascript che fornisce la funzionalita’ equivalente a quelli desktop all’interno del browser, liberando il progettista da un mare di codifica di base.

Molti frameworks di questo tipo descrivono l’interazione con l’utente usando analogie con il mondo desktop. Cioe’ un componente Button avra’ un event handler per l’evento click, e cosi’ via.

I frameworks piu’ intelligenti gestiscono il processing degli eventi sul server in modo nascosto, mentre quelli piu’ grossolani attiveranno una chiamata al server ad ogni evento attivato.

I frameworks piu’ noti sono i Form Windows per IIS/.NET e le Java Server Faces sotto Tomcat.

Oggigiorno questi framework hanno integrate funzionalita’ di tipo Ajax, strettamente legate comunque al framework stesso, come ad esempio la libreria AJAX.NET.


Conclusione

Abbiamo analizzato piu’ da vicino gli elementi architetturali che possono costituire la nostra web application. Questi elementi sono indipendenti dall’approccio che utilizzeremo in ultima istanza per costruire la nostra applicazione seguendo l’approccio Ajax. Seguiremo comunque lo schema a piu’ livelli di separazione, con la presenza di un ajax engine lato client appoggiato al browser web, un application server intermedio con un framework lato server che possa agire come Controller distribuito solo per il web server in congiunzione con il Controller dato dall’ajax engine sul client, un database server, ed un framework basato sui componenti, pero’ lato client e strettamente legato all’ajax engine vero e proprio.

In realta’ il Controller e’ composto da due moduli. Uno lato client ed uno lato server. Questa struttura permette di poter riutilizzare la parte client del Controller, e di utilizzare la parte server usando la stessa architettura su piattaforme diverse, cambiando solo il modo di implementazione che deve adattarsi alla piattaforma utilizzata.


Il Controller lato web server e’ dipendente dalla piattaforma server usata (.NET , JSP o PHP ad esempio), mentre la porzione di Controller collegata all’ajax engine sul client e’ grossomodo indipendente e riutilizzabile per qualunque piattaforma.

Come framework lato client basato sui componenti, ovvero come widgets per il web, vedremo l’utilizzo sia della libreria di ActiveWidgets v2.5.1 , sia di quella Ext.js 2.0.

martedì 22 gennaio 2008

Capitolo 4 - Miglioriamo l’ AjaxCore

Introduzione



Utilizzando la prima versione del nostro AjaxCore si presentano alcuni piccoli problemi.
Uno degli aspetti piu’ complicati da gestire, sotto certe condizioni, e’ la sincronizzazione tra ricezione dei dati richiesti al server e l’event handler collegato che si deve occupare della distribuzione dei dati ricevuti nei vari contenitori o layer presenti nella nostra Graphical User Interface.
A seconda del tipo di operazione che vogliamo poter effettuare siamo costretti ad inserire una opzione specifica nell’event handler nelle funzioni loadHTMLDoc o loadHTMLPost.
Oppure dobbiamo ampliare il numero di funzioni disponibili come abbiamo fatto per la funzione getBeanResult e per il wrapper setValToDB , e le sottovarianti che da essa posso derivare.

In aggiunta a questo aspetto, quando abbiamo la necessita’ di effettuare piu’ di una operazione in cascata a partire dalla ricezione dei dati richiesti, dovremmo inserire delle chiamate indirette ad altre funzioni javascript (anonime oppure no) in modo statico, cioe’ senza avere la possibilita’, per ora, di gestirle di volta in volta a seconda dei casi.

Facciamo un esempio.

Abbiamo sulla nostra interfaccia grafica un form che mostra l’elenco dei dipendenti con nome, cognome, email ed altri dati collegati.

Supponiamo di voler aggiornare l’elenco dei dipendenti a fronte dell’ immissione di un nuovo record. Dopo che il dato viene inviato all’Application Server e memorizzato sul database, abbiamo la necessita’ di effettuare un refresh della tabella mostrata sul client con il nuovo record appena inserito.

Di certo dobbiamo gestire l’eccezione dovuta ad un errore nel salvataggio sul database del nuovo dato. Quindi il refresh della nostra tabella necessariamente deve seguire il salvataggio del dato sul server. Deve seguire strettamente quell’evento. E dobbiamo anche pero’ sapere che il salvataggio ha avuto esito positivo. E dobbiamo saperlo prima di effettuare il refresh.

Abbiamo percio’ il seguente diagramma temporale:


Il periodo che intercorre tra l’invio al server della richiesta di aggiornamento dei dati e’ non definito. Ovvero , trattandosi di una transizione asincrona, abbiamo il controllo solo dell’evento che viene scatenato quando riceviamo un messaggio da parte del server che ci avverte dell’avvenuto aggiornamento. Ma non sappiamo a priori il momento esatto in cui avverra’. Dobbiamo quindi avere a disposizione un meccanismo per poter lanciare la seconda eventuale richiesta in cascata alla ricezione del messaggio di esito positivo. Cioe’ se l’esito dell’aggiornamento e’ negativo non effettuiamo la seconda richiesta.

Un modo per far cio’ e’ personalizzare una chiamata ad un metodo all’interno dell’event handler loadHTMLPost tramite un parametro.
E inserire nel metodo un test sul messaggio ritornato dal server dopo la prima transazione.

Cio’ e’ difficilmente gestibile nell’ unico event handler statico oppure usando dei cloni di questo event handler. Ci occorre un event handler anonimo personalizzabile di volta in volta.

La libreria Net – il Metodo ContentLoader

Come per la libreria json.js ci viene in aiuto una libreria OpenSource. Vediamo il listato seguente:



Questa libreria implementa un Oggetto “net” che al suo interno ha il metodo ContentLoader() che si occupa dell’interfacciamento con il server remoto in modo asincrono.


  • Il primo parametro - url - referenzia l’url che vogliamo recuperare sull’ application server. Obbligatorio.

  • Il secondo parametro - onload - referenzia l’event handler che vogliamo attivare sull’evento onreadystatechange dell’oggetto XMLHttpRequest. Obbligatorio.

  • Il terzo - onerror - referenzia un event handler in caso di errore (error handler). E’ un parametro opzionale, in quanto in assenza del parametro viene referenziato un error handler di default.

  • Il quarto parametro - method - indica il metodo di trasmissione da applicare [GET – POST]. Il default e’ impostato su GET. Generalmente e’ preferito l’uso del metodo POST.

  • Il quinto parametro - contentType - ci aiuta a gestire l’interpretazione della querystring passata in abbinamento al method. Se vogliamo passare coppie di chiavi-valori come dati usando il metodo POST dobbiamo usare il contentType application/x-www-form-urlencoded. Di default e’ impostato su questa modalita’.

  • Gli ultimi due parametri riguardano la possibilita’ di fare interrogazioni usando il protocollo SOAP (per i web services) e la possibilita’ di gestire i privilegi su browsers della famiglia Mozilla. Noi non useremo questi parametri.

Javascript Prototypes

Questa libreria utilizza alcuni costrutti avanzati del linguaggio Javascript, tipo l’oggetto prototype e i metodi alternativi per definire una funzione. Diamo solo un breve accenno all’oggetto prototype. Come vediamo dal codice all’interno del metodo costruttore ContentLoader si istanziano attributi interni e metodi dell’oggetto, tra cui il metodo this.loadXMLDoc().

Pero’ nel costruttore si dichiara solo il metodo demandando successivamente la sua effettiva implementazione tramite il metodo prototype.

Quasto metodo permette di definire “a posteriori” un metodo di un oggetto, dopo che l’oggetto e’ stato costruito solo con le sue dichiarazioni.

In realta’ il metodo prototype fa molto di piu’. Esso permette di istanziare una proprieta’ o un metodo a posteriori a tutte le istanze di un oggetto.
Per chi non sia avvezzo alla programmazione ad oggetti, mentre una classe e’ la modellizzazione dell’oggetto con le sue proprieta’ e i suoi metodi, le istanze della classe sono gli oggetti veri e propri che si utilizzano all’interno del nostro codice.



Descrizione del nucleo del costruttore ContentLoader

In questo modo possiamo aggiungere tre metodi alle istanze del nostro oggetto. In realta’ si tratta di un metodo , di un event handler interno costruito su una funzione anonima e di un error handler , anch’esso interno al nostro oggetto.

Questa sezione di codice, interna al primo metodo si occupa del caricamento dell’oggetto XMLHttpRequest, restituendo un riferimento req se il caricamento va a buon fine.


Successivamente se il riferimento req e’ definito, cioe’ se if(this.req)==true, si effettua il passaggio dei parametri passati ai vari elementi dell’oggetto asincrono, l’apertura del collegamento con l’application server e il collegamento dell’ event handler di onreadystatechange all’eventhandler esterno passato col riferimento onload attraverso la chiamata loader.onReadyState.call(loader);

In realta’ quello che avviene e’ che il primo metodo attiva in modo indiretto l’event handler interno all’oggetto net tramite la funzione call(caller) .


Questo secondo metodo (l’handler) si occupa della gestione del codice associato all’evento onreadystatechange per attivare il metodo esterno in modo corretto solo quando la transazione ha segnalato il corretto codice di completamento avvenuto in modo positivo con il codice HTTP 200 (OK).


Se sul completamento della transazione non si ha codice 200 oppure 0, si richiama l’error handler. Se non ne e’ stato definito uno personalizzato viene chiamato quello di default, che visualizza un semplice alert con visualizzato il codice di errore HTTP e la relativa descrizione breve.

Il Lazy Function Definition Pattern e il problema del Warm-Up

Questo pattern, formalizzato da Peter Michaux nell’articolo sul sito http://peter.michaux.ca/article/3556 indica una soluzione efficiente per il problema del warm-up di un oggetto.
In sostanza:



var foo = function() {
var t = new Date();
foo = function(){
return t;
}
return foo(); // return t;
};

la prima volta che chiamiamo la funzione si istanzia l’oggetto Date( ), pero’ si sovrascrive la funzione stessa con un’altra che ritorna solo il reference all’oggetto appena istanziato.
Prima del termine della definizione della funzione esterna si ritorna la funzione sovrascritta, per restituire al chiamante l’oggetto istanziato.
In realta’ esiste una ulteriore ottimizzazione possibile: al posto di ritornare la chiamata alla funzione sovrascritta possiamo ritornare direttamente il reference interno all’oggetto, come facciamo all’interno della funzione sovrascritta:


return t;


in questo modo le volte successive che chiamiamo la funzione foo( ) in realta stiamo chiamando la funzione sovrascritta, ricevendo immediatamente il reference all’oggetto istanziato la prima volta.

Nel caso del nostro oggetto XMLHttpRequest non possiamo pero’ usare lo stesso oggetto per tutte le richieste, perche’ l’attributo this.req.responseText varia per ogni chiamata. Dobbiamo per forza istanziare un oggetto per ognuna di esse.

Aggiunta della gestione delle richieste Sincrone

A volte in casi molto particolari abbiamo la necessita’ di effettuare richieste verso il werver in modo assolutamente sincrono. Ovvero quando effettuiamo la richiesta al server vogliamo che il nostro Ajax Engine rimanga in attesa della risposta da parte del server in modo prioritario rispetto a qualunque altra operazione. Per far cio’ dobbiamo modificare leggermente la nostra libreria che finora suppone di dover effettuare sempre richieste asincrone, aggiungendo un parametro opzionale che indichi con un valore booleano “true” se la richiesta deve essere effettuata in modo sincrono.

Di seguito riporto la libreria cosi’ modificata evidenziando i punti su cui sono intervenuto.


var net=new Object();
net.READY_STATE_UNINITIALIZED=0;
net.READY_STATE_LOADING=1;
net.READY_STATE_LOADED=2;
net.READY_STATE_INTERACTIVE=3;
net.READY_STATE_COMPLETE=4;
var statusText = new Array();
statusText[100] = "Continue";
statusText[101] = "Switching Protocols";
statusText[200] = "OK";
statusText[201] = "Created";
statusText[202] = "Accepted";
statusText[203] = "Non-Authoritative Information";
statusText[204] = "No Content";
statusText[205] = "Reset Content";
statusText[206] = "Partial Content";
statusText[300] = "Multiple Choices";
statusText[301] = "Moved Permanently";
statusText[302] = "Found";
statusText[303] = "See Other";
statusText[304] = "Not Modified";
statusText[305] = "Use Proxy";
statusText[306] = "(unused, but reserved)";
statusText[307] = "Temporary Redirect";
statusText[400] = "Bad Request";
statusText[401] = "Unauthorized";
statusText[402] = "Payment Required";
statusText[403] = "Forbidden";
statusText[404] = "Not Found";
statusText[405] = "Method Not Allowed";
statusText[406] = "Not Acceptable";
statusText[407] = "Proxy Authentication Required";
statusText[408] = "Request Timeout";
statusText[409] = "Conflict";
statusText[410] = "Gone";
statusText[411] = "Length Required";
statusText[412] = "Precondition Failed";
statusText[413] = "Request Entity Too Large";
statusText[414] = "Request-URI Too Long";
statusText[415] = "Unsupported Media Type";
statusText[416] = "Requested Range Not Satisfiable";
statusText[417] = "Expectation Failed";
statusText[500] = "Internal Server Error";
statusText[501] = "Not Implemented";
statusText[502] = "Bad Gateway";
statusText[503] = "Service Unavailable";
statusText[504] = "Gateway Timeout";
statusText[505] = "HTTP Version Not Supported";
statusText[509] = "Bandwidth Limit Exceeded";
net.getXHR = function(){
var http;
try {
http = new XMLHttpRequest;
//alert("new http object");
net.getXHR = function() {
//alert("reload old http object");
return new XMLHttpRequest;
};
}
catch(e) {
//alert("ActiveX version");
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for (var i=0, len = msxml.length; i < len; ++i) {
try {
http = new ActiveXObject(msxml[i]);
net.getXHR = function() {
return new ActiveXObject(msxml[i]);
};
break;
}
catch(e) {}
}
}
//alert("deploy");
return http;
}
net.ContentLoader=function(url,onload,onerror,method,params,contentType,headers,secure,noasync){
this.req=null;
this.flag=null;
this.onload=onload;
this.onerror=(onerror) ? onerror : this.defaultError;
this.secure=secure;
this.loadXMLDoc(url,method,params,contentType,headers,noasync);
}
net.ContentLoader.prototype={
loadXMLDoc:function(url,method,params,contentType,headers,noasync){
if (!method){
method="GET";
}
if (!contentType && method=="POST"){
contentType='application/x-www-form-urlencoded';
}
this.req = net.getXHR();
if (this.req){
try{
try{
if (this.secure && netscape && netscape.security.PrivilegeManager.enablePrivilege)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
}
}catch (err){}
if(noasync){
this>.req.open(method,url,false);
}else{
this.req.open(method,url,true);
}
if (contentType){
this.req.setRequestHeader('Content-Type', contentType);
}
if (headers){
for (var h in headers){
this.req.setRequestHeader(h,headers[h]);
}
}
var loader=this;
this.req.onreadystatechange=function(){
loader.onReadyState.call(loader);
}
//this.req.setRequestHeader( 'If-Modified-Since', 'Thu, 06 Apr 2000 00:00:00 GMT' );
this.req.send(params);
}catch (err){
this.onerror.call(this);
}
}
},
onReadyState:function(){
var req=this.req;
var ready=req.readyState;
if (ready==net.READY_STATE_COMPLETE){
var httpStatus=req.status;
if (httpStatus==200 || httpStatus==0){
try{
if (this.secure && netscape && netscape.security.PrivilegeManager.enablePrivilege)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
}
}catch (err){}
this.onload.call(this);
}else{
this.onerror.call(this);
}
}
},
defaultError:function(){
alert("Critical Network Error!!! error fetching data!"
+"\n\nreadyState:"+this.req.readyState
+"\nstatus: "+statusText[this.req.status]
+"\nheaders: "+this.req.getAllResponseHeaders());
}
}

Riassunto

Grazie alla libreria Javascript appena analizzata abbiamo adesso a disposizione l’oggetto net e il metodo ContentLoader per gestire sia il caricamento dell’oggetto XMLHttpRequest sia l’utilizzo di un event handler collegato in modo assolutamente dinamico. Non dobbiamo cioe’ piu’ predefinire l’event handler all’interno del nostro AjaxCore. In piu’, come vderemo successivamente, abbiamo anche un semplice modo per effettuare chiamate multiple a cascata perfettamente sincronizzate tra di loro. In piu’ abbiamo anche a disposizione un metodo per personalizzare eventualmente un error handler. Abbiamo inoltre visto come ottimizzare il caricamento dell’oggetto XMLHttpRequest usando il Pattern LFD.

venerdì 18 gennaio 2008

Ottimizzazione del codice Javascript

Qualche post fa abbiamo visto come ottimizzare le dimensioni di un file Javascript utilizzando strumenti tipo packer. Si ottiene quindi un file "minified", - ovvero minimizzato - come si dice in gergo.

Questa tecnica aiuta a diminuire i tempi di caricamento nel passaggio tra webserver e client.

Se pero' il nostro codice e' stato scritto "con i piedi", ovvero se non e' un codice ottimizzato sulla velocita' di esecuzione, trattandosi di un linguaggio interpretato in runtime, la nostra webapplication risultera' poco reattiva e impieghera' magari molto tempo per eseguire le istruzioni piu' onerose.

Tutto cio' ha la sua causa principale nel motore di parsing incluso nel browser. Quello che alcuni chiamano virtual machine di esecuzione del codice javascript.

Ci sono motori di parsing piu' efficienti e meno efficienti. E siccome i vari browser disponibili utilizzano praticamente motori di parsing diversi cio' porta a dire che ci sono browser piu' veloci e browser meno veloci ad eseguire una web application Ajax.

Nell'ambiente degli sviluppatori di applicazioni web c'e' la convinzione che il browser piu' lento di tutti sia Internet Explorer, mentre Firefox sia uno dei piu' veloci. E Safari sia il piu' veloce tra tutti.

In realta' la mia personale opinione e' che molto dipende dal codice javascript. I vari browser attuali di ultima generazione, tranne casi particolari, hanno ognuno i propri punti deboli. Ma sono tutti piu' o meno equivalenti. Tranne Internet Explorer 6 e 7 che hanno una grandissima inefficienza nella concatenazione e nel trattamento delle stringhe!

Come si puo' vedere in questa pagina link, sono stati effettuate delle misurazioni sui vari browser per catalogarne i punti deboli e i punti di forza di ognuno.

Con il benchmark utilizzato il browser piu' veloce risulta essere Opera9.5, ed il piu' lento IE7 (IE6 e' ancora piu' lento ma non e' stato considerato essendo della generazione precedente).

Si vede quindi che con un codice standard javascript usato come benchmark ogni browser ha delle debolezze relative. IE ha una debolezza assoluta eclatante.

Quindi ha senso in questi browser ottimizzare gli ambiti meno efficienti, per poter limare quanto piu' possibile ove il parser e' meno efficiente.

Prediamo ora in considerazione Internet Explorer (poiche' e' il browser piu' lento ed anche quello piu' diffuso) e vediamo alcune tecniche per migliorare le prestazioni di javascript.


Utilizziamo le variabili locali

Il parser quando esegue il nostro codice costruisce una "scope chain", ovvero una catena degli ambiti di validita' (o scope) delle variabili e delle funzioni/oggetti.

Una variabile definita dentro una funzione ha un ambito di validita' locale alla funzione. Fuori da quella funzione quella variabile non esiste, a meno che non ci sia una variabile definita estarnamente, che pero' e' effettivamente un oggetto diverso anche se avesse lo stesso nome della variabile definita internamente alla funzione utilizzando "var".

Potendoci essere funzioni annidate l'una dentro l'altra e' chiaro che viene definita una catena di scope per stabilire gli ambiti di validita' dei vari oggetti.

Ad esempio:

var a="variabile1";

function funzioneA(){
var b="variabile2";
var output=b+a;
return output;
}

alert(funzioneA());


Questa funzione utilizza una variabile locale - b - ed una variabile globale - a - per costruire una variabile di output locale con la concatenazione delle due stringhe iniziali. Il motore di parsing di IE risolve le variabili dallo scope piu' specifico a quello meno specifico (o piu' globale, se volete) quando deve eseguire le funzioni sulle variabili. Cosi' risulta dispendioso concatenare una variabile globale. Risulta piu' efficiente definire un reference locale che agisca da cache rispetto alla variabile globale (meglio eliminare le variabili globali del tutto!!!):

var a="variabile1";
function funzioneA(){
var b="variabile2";
var c=b; //meglio addirittura var a="variabile1"; localmente
var output=b+c;
return output;
}

alert(funzioneA());


Ottimizzazione del limite superiore dei cicli for()

Un altro aspetto ottimizzabile, che coinvolge questa volta i clicli for() in relazione agli array e' il seguente:

'var a=new Array();
....popoliamo l'array con N numeri...
//visualizziamo la somma di tutti i numeri contenuti
var output=0;
for(var idx=0;idx < a.length; idx++){ output += a[idx]; }

E' poco efficiente perche' ad ogni ciclo si valuta di nuovo la lunghezza dell'array!!!!
quindi si puo' ottimizzare in questo modo mettendo in cache locale la dimensione dell'array:


var len=a.length:

for(var i=0;i < len; i++} output+=a[i];
}





Ottimizzazione dei riferimenti impliciti

Supponiamo di avere una funzione che costruisce dinamicamente la nostra User Interface a blocchi - Titolo,Body e Footer - dentro un contenitore "baseElement" identificato dal suo ID:

function BuildUI(){
var baseElement = document.getElementById('target');
baseElement.innerHTML = ''; //Clear out the previous
baseElement.innerHTML += BuildTitle();
baseElement.innerHTML += BuildBody();
baseElement
.innerHTML += BuildFooter();
}


In questo modo il nostro elemento target viene aggiornato piu' volte. Quando utilizziamo innerHTML il browser ricostruisce "al volo" il DOM e aggiorna il contenuto dell'elemento a video.
Quindi abbiamo una ripetizione di aggiornamenti.

Ottimizziamo il tutto effettuando un unica riscrittura del contenuto html del nostro elemento di base:

function BuildUI(){
var elementText=BuildTitle()+BuildBody()+BuildFooter();
document.getElementById('target').innerHTML = elementText;
}

Ottimizzazione dei rimandi impliciti ridondanti

Prendiamo in esame il codice seguente:

function CalculateSum(){
var addendoSinistra = document.body.all.addendoSinistra.value;
var addendoDestra = document.body.all.addendoDestra.value;
document.body.all.result.value = addendoSinistra + addendoDestra;
}

In questo modo andiamo a riconsiderare due volte la navigazione di tutti gli elementi del body del documento, per estrarre i due addendi della somma; Abbiamo quindi una ridondanza andando a referenziare di nuovo la collezione globale degli elementi della pagina.

Possiamo invece ottimizzare il tutto costruendo una prima collezione locale di elementi da utilizzare successivamente per estrare i valori degli addendi:

function CalculateSum(){
var collezione = document.body.all; //Cache this
var addendoSinistra= collezione.addendoSinistra.value;
var addendoDestra = collezione.addendoDestra.value;
collezione.result.value= addendoSinistra + addendoDestra;
}


Ricerca dei Simboli delle funzioni Javascript

Supponiamo di avere un ciclo for al cui interno richiamiamo una funzione JS:
'
for(var index=0;index < length;index++){ Work(myCollection[index]);
}
'
In questo modo il parser deve ricercare ad ogni passo per risolvere il puntatore-simbolo interno associato alla funzione richiamata all'interno della "scope chain".

Possiamo ottimizzare il codice creando una cache locale della funzione esterna. In questo modo il parser usera' la copia locale del puntatore-simbolo senza dover ripercorrere tutta la "scope chain":
'
var funcWork = Work;

for(var index = 0;index < length; index++){ funcWork(myCollection[index]);
}
'

Allo stesso modo questo discorso vale per le funzioni del DOM interno ad IE:
'
var parentElement=document.getElementById('target');
var length = myCollection.getItemCount();

for(var index = 0; index.length; index++){
parentElement.appendChild(myCollection[iterate]);
}
'

diventa:
'
var funcAppendChild = document.getElementById('target').appendChild;
var length = myCollection.getItemCount();

for(var index = 0; index.length; index++){
funcAppendChild(myCollection[iterate]);
}
'
Concatenazione di stringhe

Veniamo ora alle dolenti note da cui abbiamo iniziato il nostro discorso: la concatenazione delle stringhe sotto Internet Explorer utilizzando l'operatore di concatenazione "+=" su oggetti di tipo stringa.

Benche' il parser javascript sia stato migliorato tantissimo nella gestione della concatenazione delle stringhe nel passaggio dalla versione 6 alla versione 7 del browser, rimane ancora l'operazione piu' lenta in assoluto rispetto a qualunque altro browser.

L'inefficenza di questa operazione e' tale che si puo' avere un rallentamento tangibile della nostra web application quando debba eseguire la concatenazione di molte stringhe (diciamo almeno una decina).

Come possiamo ottimizzare questa operazione?

Usando il metodo join('') dell'oggetto Array in cui metteremo le stringhe da concatenare:

var smallerStrings = new Array();
var destLargeString="";

smallerStrings.push(""stringa1");
smallerStrings.push(""stringa2");
smallerStrings.push(""stringa3");
smallerStrings.push(""stringa4");
...
smallerStrings.push(""stringaN");

var destLargeString = smallerStrings.join('');

In questo modo sfruttiamo un metodo interno al browser piu' efficiente, migliorando abbastanza il punto piu' critico del browser IE.

Riassunto

In questo post ho voluto mostrare alcune delle tecniche piu' conosciute di ottimizzazione del codice javascript sotto Internet Explorer che utilizzo piu' frequentemente e che effettivamente danno una buona ottimizzazione dei punti critici in questo browser. In realta' ci sono anche altre tecniche avanzate, tipo i cicli do-while inversi autoestinguenti e l' "unfolding" parziale dei cicli for valide in generale ma che non e' detto che diano dappertutto dei reali vantaggi.

Ritengo che solitamente sia sufficiente questo primo ciclo di ottimizzazione del codice, perche' se non si avesse comunque una web application reattiva probabilmente il problema e' piu' generale a livello dell'architettura complessiva, e non ristretto alla velocita' di parsing del nostro codice javascript.

Come ultimo consiglio suggerisco comunque di misurare i tempi di esecuzione dei punti che vogliamo ottimizzare per verificare direttamente quanto tempo si risparmia, e per non perdere magari troppo tempo a rivisitare il nostro codice per guadagnarci solo pochi millisecondi.

Per chi avesse voglia di approfondire ulteriormente l'argomento puo' seguire questa presentazione fatta dal gruppo di sviluppo di Internet Explorer

mercoledì 12 dicembre 2007

Tutorial Ajax - Capitolo 3

Introduzione




Il nostro Ajax Engine ci permette di recuperare i dati dal web server sotto forma di codice html preformattato, oppure come risultato dell’elaborazione di pagine dinamiche scritte magari in JSP oppure in ASP.NET.

Quando si sviluppano web applications complesse ci si ritrova a dover gestire probabilmente qualche decina di interfacce grafiche diverse, collegate ad una pagina principale a cui l’utente accede previa autenticazione e sceglie quale servizio utilizzare tra l’insieme di servizi disponibili all’interno dell’applicazione.

A questo punto chi progetta l’applicazione puo’ in teoria decidere di creare un unico documento il cui contenuto puo’ venire azzerato e reinizializzato per ogni diverso servizio senza ricaricare la pagina che agisce ora solo come contenitore esterno.



Facciamo un semplice esempio per illustrare questo scenario.


Questa e’ una pagina html. Molto semplice e nemmeno il massimo dal punto di vista stilistico. E sicuramente chiunque sarebbe in grado di farla meglio. Pero’ e’ una pagina funzionante. Carica un file contenente le istruzioni di formattazione CSS ed il nostro ajax engine prima di istanziare tre contenitori – i layer- e richiamare la funzione initPage() contenuta nell’engine.

Se la funzione di inizializzazione non eseguisse nessuna operazione al suo interno, questa pagina sarebbe un bel documento vuoto agli occhi dell’utente che vuole visualizzarlo sul proprio browser.



Se pero’ riempissimo di volta in volta i tre contenitori con il contenuto necessario , dislocandoli in posti diversi grazie al file CSS?



Potremmo ad esempio creare un piccolo form all’interno del primo layer:




Se in un file remoto form.html contenesse solo il codice iniziale che vediamo sopra, e lo inserissimo nel primo layer sfruttando il metodo innerHTML, sul nostro browser apparirebbero un campo di inserimento testo, una lista a tendina ed un bottone. Ovvero l’utente vedrebbe gli oggetti che costituiscono il nostro form.

Immagino che a questo punto la strada che voglio seguire si intraveda chiaramente. Con un set di documenti html preformattati, come si trattasse delle tessere di un puzzle, possiamo comporre interfacce grafiche complesse in modo arbitrario, con effetti interessanti ed inediti finora dal punto di vista dell’interattiviita’.

Nel nostro esempio, una volta inserito il nome della citta’ possiamo intercettare l’evento che indica il cambiamento del testo visualizzato per precaricare i codici postali associati in modo trasparente. Solo quando avremo digitato una citta’ riconosciuta caricheremo - pressoche’ in tempo reale e senza che l’utente se ne accorga - solo i suoi CAP corrispondenti nella lista:





La giungla dei fornitori dati

Utilizzando l’impostazione appena vista si puo’ gia’ progettare una web application aziendale su un’unica pagina web. Se pero’ si devono organizzare piu’ servizi e si devono inserire piu’ oggetti nella nostra interfaccia grafica i cui dati (o stati) cambiano a seconda dell’interazione che si ha con l’utente, si puo’ arrivare a dover creare innumerevoli fornitori dati, ovvero documenti html preformattati o pagine dinamiche che restituiscano la porzione di codice html da inserire negli elementi sulla GUI.

Per migliorare un poco la situazione si possono accorpare piu’ oggetti in un fornitore dati.

Se cioe’ avessimo molti oggetti in un form e ognuno di essi deve essere inizializzato con i suoi dati, allora creeremo una pagina dinamica unica che dara’ come risultato il form con gli elementi valorizzati, da collegare al contenitore/layer opportuno. E non avere un fornitore per l’inizializzazione di ogni oggetto.




Ora pero’ dobbiamo dare uno sguardo a come formattare i dati restituiti dai nostri fornitori dati oltre all’html, perche’ spesso avremo la necessita’ di richiedere al web server solo un dataset senza l’html di contorno. Il formato XML
Se ci soffermiamo su AJAX inteso solamente come acronimo, notiamo che l’ XML assume un ruolo rilevante essendo indicato come formato di trasporto dei dati. In realta’ e’ scorretto pensare che l’ XML sia intrinsecamente legato ad Ajax inteso come approccio di progettazione web. Ma comunque puo’ esere utilizzato come formato di output per le richieste Ajax.

XML sta per eXtensible Markup Language, ovvero Linguaggio a Markup estesi.
Si tratta quindi di un linguaggio a markup di tipo generico che puo’ essere utilizzato per descrivere praticamente qualsiasi cosa. L’XML differisce dagli altri linguaggio a markup come l’SGML e l’ HTML per il fatto che non e’ vincolato ad un vocabolario di markup o tag.

Percio’ l’autore di un documento XML e’ libero di utilizzare qualunque termine voglia come marcatore per i tag. L’unica prescrizione e’ che il documento segua alcune regole semplici che pero’ non sono collegate al vocabolario dei tag.

Un esempio di XML






La prima linea di codice e’ una dichiarazione XML. Questa indica la versione dell’XML che si sta utilizzando e la codifica di carattere del documento. Tutto quello che segue deve essere contenuto in tags. Ciascun set di tag denota un elemento del documento. Il nome di un elemento e’ contenuto nel tag di apertura e in quello di chiusura. Il valore di un elemento e’ contenuto nel testo tra il tag di apertura e quello di chiusura. E’ importante notare come l’ XML non fa nulla. Il linguaggio fu creato solo come modalita’ di immagazzinamento dati, non per manipolarli. In quest’ottica per manipolare i dati contenuti occorre un linguaggio di programmazione, come il JavaScript, per estrarre le varie informazioni dal file XML e porli nella pagina web.



L’XML in azione


Se avessimo una pagina web con l’elenco dei dipendenti in un ufficio, memorizzato in un file XML, dobbiamo poter recuperare i vari dati dei dipendenti leggendo il file XML, facendo il parsing dei vari tag e gererando gl iopportuni tag HTML per visualizzare questi dati nella pagina web finale, presumibilmente all’interno di qualche contenitore identificato da uno o piu’ layer





I dati di ritorno, richiamati dal nostro componente XHR, verranno gestiti da un codice javascript simile a questo sull’evento onreadystatechange:



Questi dati in formato XML possono essere “percorsi” usando gli stessi metodi forniti dal Document Object Model utilizzati per “navigare” all’interno di un documento HTML.
e si conosce la struttura dell’ XML ricevuto dal server si puo’ recuperare l’informazione desiderata usando i metodi getElementsByTagName e le proprieta’ come nodeValue.

Estrazione dei dati dall’ XML

Sapendo che nel nostro documento XML e’ presente un elemento , si recupera una collezione dei vari elementi con


Sapendo che di tali elementi ce n’e’ uno solo, sono interessato solo al primo elemento di questa collezione:







Questo elemento contiene quindi il nodo desiderato. Il nodo e’ il primo elemento figlio di questo elemento:






Adesso posso prendere il valore di questo nodo testuale usando la sua proprieta’:





ed assegnamo il valore alla variabile javascript:





In questo modo possiamo recuperare anche le informazioni relative al website e alla email:





Usando I metodi del DOM come createElement, createTextNode e appendChild, possiamo ricostruire una porzione di HTML per contenere le informazioni estratte prima:








A questo punto devo inserirei nuovi markup all’interno del layer nel documento:



var details = document.getElementById("details");






Innanzitutto devo assicurarmi che il contenitore sia vuoto, senno’ svuotarlo:



while(details.hasChildNodes()){
details.removeChild(detaild.lastChild); }



poi inserisco il contenuto:



details.appendChild(header);
details.appendChild(link);



Vantaggi dell’ XML

E’ un formato molto versatile per i dati. Si puo’ definire il proprio vocabolario di tag personalizzandolo per i nostri dati. Questa flessibilita’ ha favorito la larga diffusione del formato XML, che nel tempo e’ diventato uno dei modi piu’ usati per scambiare dati nelle applicazioni web. Inoltre per fare il parsing vero e proprio riutilizziamo i metodi propri del nostro Document Object Model senza la necessita’ di usare un altro linguaggio oltre al Javascript.

Svantaggi dell’ XML



Occorre smpre usare il corretto header. E’ necessario che ad ogni tag di apertura corrisponda il suo tag di chiusura con il corretto annidamento degli elementi. E’ quindi facilecommettere un errore.
Utilizzare il DOM per distribuire il contenuto parserizzato puo’ essere una operazione non cosi’ breve per cui sono necessarie molte istruzioni.Ogni nodo deve essere creato ad attaccato al documento. Uno ad uno. Quindi per una applicazione con un file XML grande il codice puo’ diventare presto troppo complesso da gestire con facilita’. Si potrebbero usare a questo proposito le XSLT, o eXtensible Stylesheet Language Transformations, che effettuano un mapping tra XML ed XHTML.
Il problema e’ che non tutti i browser supportano questa funzione avanzata.



Il formato JSON

Abbiamo detto in precedenza che per formattare i dati richiesti al web server possiamo utilizzare un documento html statico o dinamico, oppure un documento xml, o recuperare i dati richiesti secondo il formato JavaScript Object Notation.

In molti ritengono che il formato XML sia il miglior formato per l’interscambio di dati. Come visto prima e’ un formato testuale per cui si puo’ assicurare l’interazione tra sistemi diversi attraverso la difinizione dei DTDs oppure degli Schemi XML. Questi sono dei meccanismi usati comunemente quando si devono trasmettere dati tra sistemi disomogenei o tra piattaforme diverse.

In questo modo si rende il formato dei dati indipendente dalle tecnologie utilizzate per il loro trattamento. Il prezzo da pagare e’ la necessita’ di recuerare dall’XML la strutturazione gerarchica interna a questi dati nel linguaggio proprio dei vari sistemi da cui devono essere elaborati , ed eventualmente ritrasformare i nuovi dati risultanti qualora debbano essere trasmessi ad altri sistemi.

Nel nostro caso dovremo convertire i dati ricevuti in formato XML in oggetti JavaScript.

Quando usiamo il formato diretto html, in abbinamento al metodo innerHTML, questa conversione avviene attraverso il parsing del codice effettuato dal browser e la conseguente creazione degli oggetti JavaScript all’interno del Document Object Model.

Utilizzando l’ XML dobbiamo effettuare esplicitamente il parsing del codice e la distribuzione nel DOM attraverso la valorizzazione degli attrbuti dei nodi interessati oppure la creazione di nuovi Nodi collegati a nodi preesistenti. Cio’ avviene utilizzando i metodi di trattamento forniti dal DOM stesso.

Personalmente non ho mai, se non in casi assolutamente necessari, utilizzato questa impostazione per trattare i dati ricevuti. Ritengo che porti a dover scrivere codice javascript troppo lungo - cosa di per se’ poco efficiente per un linguaggio interpretato come il javascript - e nemmeno tanto intuitivo per le strutture da utilizzare in questo caso specifico (navigazione della gerarchia del DOM, Xpath, espressioni regolari).

Da questo punto di vista la situazione ottimale sarebbe quella di avere a disposizione i dati in un formato che possa essre utilizzato direttamente dal JavaScript, html preformattato a parte.

Questo formato e’ chiamato JSON (http://json.org/) e consiste in un dataset in notazione letterale ad oggetti javascript.


Proviamo a convertire un dataset XML in JSON:








Convertito in notazione JSON diventa:





Il benieficio di questo formato e’ che e’ gia’ in un formato che JavaScript puo’ comprendere, e tutto cio’ che serve fare e’ convertire questo codice in oggetti da mostrare usando il metodo eval() alla stringa:


var data = eval( '(' + x.responseText + ')' );


Cio’ restituisce il contenuto come oggetti che possiamo manipolare attraverso l’uso della notazione array-associativa o di proprieta’ degli oggetti.





Per i fornitori dati nel nostro server l’utilizzo del formato JSON al posto del formato XML e’ decisamente piu’ efficiente e veloce (nei miei test si ha una velocizzazione fino a dieci volte). Comunque dobbiamo stare attenti ad una cosa: l’utilizzo del metodo eval() puo’ essere pericoloso perche’ esso esegue qualsiasi codice javascript, e non solo steinghe JSON. Possiamo aggirare questo problema utilizzando , al posto del metodo direttamente, una funzione di parsing che ci assicuri che solo i dati vengano convertiti in oggetti, mentre il codice potenzialmente pericoloso non venga eseguito.

Esiste una libreria open-source liberamente scaricabile all’indirizzo
http://www.json.org/js.html che contiene il parser per i dati JSON. Nella nostra web application sara’ sufficiente in cludere tale libreria nella pagina principale, insieme all’ajax engine.






Vantaggi del formato JSON


La notazione JSON e’ leggera. Mentre ogni valore in XML richiede un tag di apertura ed uno di chiusura, JSON richiede solo un nome da scrivere una volta sola.
Diversamente dall’ XML, JSON non ha bisogno di essere trasmesso dal web server con uno specifico header “content-type” ma viene trasmesso come testo semplice.


Svantaggi del formato JSON


La sintassi richiesta dal format JSON e’ molto precisa. Un apice al posto sbagliato o una parentesi mancante possono cambiare significato a tutta la stringa JSON. Occorre anche gestire con le sequenze di escape i caratteri speciali come i singoli e i doppi apici. Una stringa JSON complessa puo’ diventare assai poco leggibile a differenza di un dataset XML la cui lettura e’ agevolata dalla presenza dei tag iniziali e finali. In piu’ occorre fare la valutazione della stringa JSON in modo sicuro occorre ricordarsi di processarla con la funzione di parsing opportuna che esegue solo le proprieta’ contenute nella stringa ignorando qualsiasi funzione o metodo in essa presente.


Riassunto


In questo capitolo abbiamo visto alcuni aspetti collegati all’utilizzo del nostro ajax engine all’interno di una web application. Innanzitutto abbiamo visto l’approccio che, a fonte di una pagina html che agisce da puro contenitore, utilizza chiamate multiple ai fornitori (di dati e struttura grafica) per costruire sia l’interfaccia grafica (utilizzando fornitori HTML statici oppure dinamici), sia per popolarne gli elementi.
Poi abbiamo visto come accorpare la costruzione della GUI in un'unica chiamata remota - o in poche chiamate al server, delegando poi ai fornitori dati veri e propri il compito di popolare gli elementi costruiti. In seguito abbiamo visto come utilizzare il formato XML oppure il formato JSON per caricare e distribuire sul client i dati richiesti al server.