venerdì 25 gennaio 2008

Capitolo 7 - Come creare una Interfaccia Grafica web

Premessa

Avendo visto nel capitolo precedente come collegare la porzione del Controller che deve gestire l'Interfaccia Grafica della nostra web application con la porzione che implementa i Business Components lato server, concentriamo ora la nostra attenzione sulla costruzione della GUI.

La Graphical User Interface e' probabilmente la parte della nostra web application piu' critica, poiche' e' cio' che l'utente finale utilizza direttamente e che quindi e' piu' soggetta a critiche e a richieste di modifica.

Se la nostra applicazione deve essere utilizzata da molti utenti prepariamoci al fatto che probabilmente la GUI non andra' bene a tutti gli utenti. Ci sara' sempre qualcuno che chiedera' un comportamento particolare personalizzato per qualche componente visuale. ;)
E prepariamoci anche al fatto che ci sara' sempre un utente che - non si sa mai come ci riescano - riuscira' a scovare il piu' piccolo difetto di funzionamento assolutamente impossibile da prevedere in anticipo.

Con GUI sto intendendo non solo i componenti visuali che vengono mostrati sulla finestra del nostro browser, le caselle di testo, i menu a tendina, le tabelle, le immagini e cosi' via, ma anche tutta la logica del controller sottostante che deve gestire l'interazione con l'utente, ovvero cosa debba succedere quando un utente clicca un bottone oppure seleziona una voce da un combobox.
Quindi tutta quella parte di codice javascript che nella nostra applicazione web definisce i vari eventhandler da collegare agli eventi gestiti dal browser in collegamento agli oggetti mostrati.

Progettazione web tradizionale

Nella creazione di interfacce grafiche web tradizionali si utilizzano gli oggetti visuali nel codice HTML collegando gli eventhandler agli attributi-eventi all'interno dei tag:


<table width="100%" cellspacing="0" cellpadding="0" border="0">  <!--HEADER DX Start -->
<tr>
<td>
<a href="#" target="_blank">
<img src="./image/info_out.gif"
onmouseout="javascript:cambiaImmagine(this);"
onmouseover="this.src='./image/info_on.gif'"
width="27"
height="27"
alt=""
border="0">

</a>
<img src="./image/faq_out.gif"
onmouseout="javascript:cambiaImmagine(this);"
onmouseover="this.src='./image/faq_On.gif'"
width="27"
height="28"
alt=""
border="0">

<A href="mailto:PERSEO_USER_SUPPORT@eni.it">
<img src="./image/mail_out.gif"
onmouseout="javascript:cambiaImmagine(this);"
onmouseover="this.src='./image/mailOn.gif'"
width="30"
height="28"
alt=""
border="0">

</a>
</td>
</tr>
</table> <!--HEADER DX END -->

Questo approccio per costruire una interfaccia grafica ha degli svantaggi evidenti:

  • Occorre definire gli oggetti usando il codice HTML
  • Occorre definire gli eventhandler usando codice Javascript
  • Occorre definire il layout degli oggetti usando i fogli di stile - CSS
  • Bisogna collegare gli eventhandler agli eventi all'interno dei tag HTML

Se poi si ha l'esigenza di modificare in runtime la forma di un oggetto, o il contenuto, o la disposizione nel layout della pagina, o il comportamento in risposta ad un evento, in reazione ad una richiesta asincrona al server, si possono seguire almeno due strade che pero' risultano alla lunga abbastanza tortuose se le modifiche non sono minime:

  • ricarico dal server la porzione di codice html che modifica l'oggetto preesistente avendo cura di mantenere i collegamenti con gli eventhandler richiamati al suo interno.
  • agisco con javascript sul Document Object Model per modificare l'oggetto riflesso nell'albero del DOM in memoria per modificare le caratteristiche visuali o comportamentali da variare

Svantaggi

Nella prima delle due opzioni si avra' una proliferazione sul server di snippet html per implementare le varie versioni modificate degli oggetti (o di gruppi di oggetti), mentre la seconda opzione comporta un appesantimento nel codice javascript con la gestione di numerose operazioni di manipolazione del DOM.

Vantaggi

La prima opzione sicuramente offre una elevata velocita' di esecuzione dovendo caricare dal server pochi bytes di codice html in sostituzione di quello repcedente, la seconda invece permette una gestione unificata e centralizzata all'interno del controller lato client.

A questo punto ci chiediamo se sia possibile avere una gestione unificata a livello del nostro controller javascript ma che non ci obblighi a scrivere troppo codice per modificare le caratteristiche dei nostri componenti visuali.

Soluzione

Ci vengono in soccorso percio' i Frameworks basati sui componenti lato client menzionati nel capitolo precedente: ExtJs ed ActiveWidgets. In questo modo potremo definire la nostra Interfaccia Grafica direttamente in Javascript, scavalcando quasi completamente il codice HTML, avendo in aggiunta a disposizione un set esteso di oggetti visuali con la possibilita' di poter scegliere diversi stili per il look-and-feel ed un insieme di API per la gestione di tutte le caratteistiche visuali e comportamentali.

Oltre agli oggetti classici solitamente questi framework forniscono anche oggetti visuali complessi preconfezionati, quali possono essere ad esempio i Treemenu, le Grid Editabili, i Canvas, le Window, gli Accordion e tanti altri.

Noi vedremo alcuni esempi di questi due framework, lasciando ai loro siti ufficiali una spiegazione approfondita ed esaustiva relativamente al loro utilizzo.

ActiveWidgets

Come riportato anche sul sito ufficiale, si tratta di una Libreria di Componenti Javascript per semplificare lo sviluppo di applicazioni web (e specialmente quelle Ajax) e renderle piu' produttive, fornendo un set di componenti visuali di uso comune ed un modello di programmazione semplice degli stessi.

ActiveWidgets e' indipendete dal web server e puo' funzionare in collegamento con qualsiasi ambiente server-side o con pagine html statiche. I componenti visuali vengono generati in runtime dalla libreria javascript che agisce da generatore di codice html direttamente sul client.

Durante il caricamento della pagina il browser esegue gli script javascript creando i componenti visuali, li configura, e quindi inserisce il codice html generato da ogni componente al posto giusto all'interno di una pagina html molto semplice che agisce da contenitore globale per il trasferimento degli script dal server sul client.

Da questo punto il codice html del componente viene generato e connesso al frammento della pagina html contenitore. Il componente quindi gestisce le interazioni con l'utente finale e aggiorna l'html in accordo con le variazioni dei dati , fornendo una semplice API per la manipolazione del contenuto, comportamento e stile visuale dei componenti.

Tutto cio' avviene lato client senza dover contattare il web server. In questo modello il server viene coinvolto solo per la gestione dei dati mentre l'interfaccia visuale e l'interazione con l'utente diventano responsabilita' del client.

Esempio 1

Vediamo ora alcuni esempi di utilizzo di questa libreria:

<script>
// create ActiveWidgets component
var obj = new AW.UI.Button;
// set the display text
obj.setControlText("Click me");
// assign click event handler
obj.onControlClicked = function(){
this.setControlText("Hello World!");
};
// add component to the page
document.write(obj);
</script>
In questo esempio si crea un componente "Button" con un testo iniziale "Click me" ed un eventhandler associato ad un evento interno del componente onControlClicked che fa cambiare il testo associato al bottone.

Per aggiungere effettivamente il componente alla pagina web si deve utilizzare il metodo window.write(obj) cui viene passato l'oggetto da renderizzare.

Esempio 2

Vediamo ora un secondo esempio, leggermente piu' complesso:


<script>
var myCells = [
["MSFT","Microsoft Corporation", "314,571.156"],
["ORCL", "Oracle Corporation", "62,615.266"]
];
var myHeaders = ["Ticker", "Company Name", "Market Cap."];
// create grid object
var obj = new AW.UI.Grid;
// assign cells and headers text
obj.setCellText(myCells);
obj.setHeaderText(myHeaders);
// set number of columns/rows
obj.setColumnCount(3);
obj.setRowCount(2);
// write grid to the page
document.write(obj);
</script>
In questo caso costruiamo una tabella-Grid con tre colonne, una riga di header e due righe di contenuto. Come possiamo vedere dal codice, a parte conoscere la sintassi esatta da utilizzare nel modello di programmazione fornito, la logica di costruzione degli elementi che compongono il nostro oggetto e' molto lineare nella sua semplicita'.

Esempio 3

<script>
var btn = new AW.UI.Button;
btn.setId("btn");
btn.setControlText("Change text");
btn.onClick = function(){
txt.setControlText("Hello World!");
};

var txt = new AW.UI.Input;
txt.setControlText("Input");
txt.setControlImage("search");

document.write(btn);
document.write(txt);
</script>
In questo esempio definiamo un bottone ed un textinput (casella di testo) con un testo di default "Input". Associamo anche all'evento onClick del bottone un eventhandler che reimposta il valore del testo mostrato nell'oggetto textinput. Poi colleghiamo i due componenti alla pagina html globale.

Come si puo' vedere si costruisce in modo molto veloce, intuitivo e semplice una GUI e il relativo Controllore lato client all'interno del codice Javascript che puo' anche essere messo in un file .js a se stante separandolo dal codice html del contenitore esterno globale.

Se dovessimo caricare dati da remoto con una richiesta ajax sarebbe sufficiente includere una chiamata al metodo executeRequest() come visto nei capitoli precedenti all'interno dell'eventhandler opportuno in risposta all'attivazione di una qualche interazione con l'utente.

Riassunto

Quanto mostrato intende essere un semplice spunto di utilizzo di questo framework, per mostrare le potenzialita' offerte allo sviluppatore di web applications ajax.
E' anche possibile racchiudere poi i metodi base forniti dalle API di ActiveWidgets in wrapper personalizzati per semplificare la costruzione dei vari oggetti , per integrarle magari con le nostre API viste in precedenza.
Si possono cosi' implementare interfacce grafiche complesse molto reattive che sicuramente possono incontrare il gradimento anche degli utenti piu' esigenti.

Come esempio riporto un esempio di codice che implementa un pannello con 4 tab con all'interno di ciascuno delle pseudo-griglie composte dinamicamente da textinput non modificabili, combobox e textinput modificabili recuperando i valori di inizializzazione dal database server effettuando le opportune chiamate verso il controllore lato server come visto nel capitolo precedente.

Si tratta di un servizio complesso utile per mostrare fino in fondo cosa si puo' fare usando questa libreria


var counter=0;
var actualStatus="";
bMenuOnClick = function(){window.close();};
bAlertOnClick = function(){alert('Prova:'+this.getId());};
bChangeMacroStatus = function(){confirmDialog('Warning','Vuoi cambiare lo stato di abilitazione della Macro? ',handleChangeStatus)};
findStatus = function(){
try{
myp.init();
myp.addPar("101");
exQuery("MacroDPDV","getActualStatus",dispActualStatus);
}catch(err){
errmsgxxx('Preparing getActualStatus',err.message);
}
function dispActualStatus(){
var d=this.req.responseText;
var j=d.parseJSON();
try{
if(controlReceivedData(dummyGrid,d,false,true,"No Record To Edit")){
lStatus.setControlText("Stato attuale della Macro: "+j.r[0].VALORE);
this.req=null;
}
}catch(err){
errmsgxxx('Function dispNPWIP',d+";"+err.message);
}
return "Stato attuale della Macro: "+actualStatus;
};
return "Stato attuale della Macro: "+actualStatus;
};
handleChangeStatus=function(btn){
if(btn=="yes"){
try{
myp.init();
exQuery("MacroDPDV","changeStatus",dispChangeStatus);
}catch(err){
errmsgxxx('Preparing changeStatus',err.message);
}
function dispChangeStatus(){
var d=this.req.responseText;
retmsg(d);
findStatus();
this.req=null;
};
}
};
var box =cOb("f","box");
var tabs =cOb("t","tabs");tabs.setItemText(["Valore DP/DV per Campo", "Parametri Globali", "Provenienza Dati per Campo", "Provenienza Dati per Pozzo"]);tabs.setItemCount(4);tabs.setSelectedItems([0]);output+=tabs;
var frame1 =cOb("f","frame1");
var frame2 =cOb("f","frame2");
var g_title =cOb("g","g_title",'',"PERSEO - Impostazione parametri MACRO DP/DV");
var bMenu =cOb("b","bMenu",'',"Close",'','',bMenuOnClick);
var bChange =cOb("b","bChange",'',"Change",'','',bChangeMacroStatus);
var lStatus =cOb("l","lStatus",'',findStatus);

dw(output);

var btna=cOb("b","btna",'',"btn1",'','',bAlertOnClick);
var btnb=cOb("b","btnb",'',"btn2",'','',bAlertOnClick);
var btnc=cOb("b","btnc",'',"btn3",'','',bAlertOnClick);

/****************************************************/
/* PRIMO TAB */
/****************************************************/

var dummyGrid =new AW.UI.Grid; dummyGrid.setId("dummyGrid");
var box2 =new AW.HTML.DIV; box2.setId("box2");
var bSave =new AW.UI.Button;bSave.setId("bSave");bSave.setControlText("Save");
var lCountry =new AW.UI.Label; lCountry.setId("lCountry");lCountry.setControlText("Country:");
var lCountryVal=new AW.UI.Label; lCountryVal.setId("lCountryVal");lCountryVal.setControlText("...loading value...");
var h1 =new AW.UI.Input; h1.setId("h1");h1.setControlText("CODICE CAMPO");h1.setStyle("background","lightblue");h1.setStyle("color","azure");h1.getContent('box/text').setAttribute('readonly',true);
var h2 =new AW.UI.Input; h2.setId("h2");h2.setStyle("background","lightblue");h2.setControlText("LAST UPDATE");h2.getContent('box/text').setAttribute('readonly',true);
var h3 =new AW.UI.Input; h3.setId("h3");h3.setStyle("background","lavender");h3.setControlText("DP_DV_CAMPO");h3.getContent('box/text').setAttribute('readonly',true);
var h4 =new AW.UI.Input; h4.setId("h4");h4.setStyle("background","lavender");h4.setControlText("NOTE");h4.getContent('box/text').setAttribute('readonly',true);
var h5 =new AW.UI.Input; h5.setId("h5");h5.setStyle("background","lavender");h5.setControlText("VALUE");h5.getContent('box/text').setAttribute('readonly',true);
var h6 =new AW.UI.Input; h6.setId("h6");h6.setStyle("background","lightblue");h6.setControlText("PREV VALUE");h6.getContent('box/text').setAttribute('readonly',true);
var h7 =new AW.UI.Input; h7.setId("h7");h7.setStyle("background","lightblue");h7.setControlText("");h7.getContent('box/text').setAttribute('readonly',true);
var result="ok";

var left=0;var myData=new Array();var globOut="";var bufferx="";var buffer="";var vdate="";bufferx=bSave+h1+h2+h3+h4;

var initPageT1=(function(){
buffer="";
try{
myp.init();
myp.addPar("101");
exQuery("MacroDPDV","getCampi",dispNP);
}catch(err){
errmsgxxx('Preparing getNPWIP',err.message);
}
function dispNP(){
var d=this.req.responseText;
try{
if(controlReceivedData(dummyGrid,d,false,true,"No Record To Edit")){
buildGrid(d);
this.req=null;
}
}catch(err){
errmsgxxx('Function dispNPWIP',d+";"+err.message);
}
};
});


var buildGrid=(function(res){
var d=res.parseJSON();
var top=0;
var output="";
var len=d.count;
//alert(len);
for(i=0;i<len;i++){
myData[i]=new Array();
myData[i][0]={idx:"col0"+i,valx:d.r[i].C_CAMPO};
}
lCountryVal.setControlText(d.r[0].C_CAMPO);
for(i=0;i<len;i++){
var el1=buildCol1(i,top,d,myData);
top+=19;
}
getId('ppp').innerHTML=buffer;
setTimeout('endwait()',1);
});
var buildCol1=(function(id,top,d,myData){
var dim=184;
myData[i][1]={idx:"col1"+id,valx:d.r[id].C_CAMPO};
var el=new AW.UI.Input;
el.setId("col1"+id);
el.setControlText(d.r[id].C_CAMPO+" - "+d.r[id].N_CMP);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("background","lavender");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
buffer+=el;
var el2=buildCol2(id,top,dim,d,myData);
return true;
});
var buildCol2=(function(id,top,dim,d,myData){
left+=dim-1;
var dim=150;
myData[i][2]={idx:"col2"+id,valx:d.r[id].DATAINS};
var el=new AW.UI.Input;
el.setId("col2"+id);
el.setControlText(d.r[id].DATAINS);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("background","lavender");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
buffer+=el;
var el4=buildCol4(id,top,dim-1,d,myData);

return true;
});
var buildCol4=(function(id,top,dim,d,myData){
left+=dim;
var dim=179;
myData[i][3]={idx:"col3"+id,valx:d.r[id].DP_DV_CAMPO};
var el=new AW.UI.Input;
el.setId("col3"+id);
el.setControlText(d.r[id].DP_DV_CAMPO);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',false);
el.onControlValidating = function(txt) {
if(isNaN(parseInt(txt)) parseInt(txt)<=0){
errmsgxxx('Validating','Value must be a number >=0');
el.setStyle("background","#ffbfbf");
result="ko";
return;
}else{
el.setStyle("background","#fff");
result="ok";
}
//alert(el.getId()+";"+txt);
insertVal(el.getId(),txt);
}
buffer+=el;
var el5=buildCol5(id,top,dim-1,d,myData);
return true;
});
var buildCol5=(function(id,top,dim,d,myData){
left+=dim;
var dim=321;
myData[i][4]={idx:"col4"+id,valx:d.r[id].NOTE};
var el=new AW.UI.Input;
el.setId("col4"+id);
el.setControlText(d.r[id].NOTE);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',false);
el.onControlValidating = function(txt) {
el.setStyle("background","#fff");
result="ok";
insertVal(el.getId(),txt);
}
buffer+=el;
left=0;
return true;
});
function initCmb(el,dim,subsid,dd,id){
var obj=el;
var cl="ew_uman";
var meth="getWResJSON";
var dim=dim;
var op1=false;
var dbg=false;
var sync=true;
myp.init();
myp.addPar(subsid);
var vars="";
function val(x){
return "&value"+x+"=";
};
for(xx=0;xx<myp.data.length;xx++){
vars+=val(xx+1)+myp.data[xx].toString();
}
var strParamsx=bcNm+"valGE"+bcFx+"getMastroJSON"+vars;
dbg==true ? alert(strParamsx) : null;
var loaderx=new net.ContentLoader(lib+strParamsx,handler,null,"POST",'','','','',sync);
function handler(){
try{
var dati=this.req.responseText;
var d=dati.parseJSON();
if(dbg){alert("debug: "+dati);}
if(d=="undefined" d==undefined){
cE(this.req.responseText,"dispose "+obj.getId());
}
}catch(err){
alert(err.message+" -- "+this.req.responseText);
var d=[1];
}
setCombo(el,d,dim);
if(dd.count==1){
setComboValue(obj,"first");
}else{
setComboText(obj,dd.r[id].MASTRO_NM);
}
obj.setControlDisabled(false);
};
}
function checkNull(txt){if(txt=="" txt ==undefined txt=="undefined"){return "null";}else{return txt;}}
function creaOutput(){globOut="";var l=myData.length;for(r=0;r<l;r++){globOut+=checkNull(myData[r][0].valx)+";";globOut+=checkNull(myData[r][3].valx)+";";globOut+=checkNull(myData[r][4].valx)+";";}alert(globOut);}
function insertVal(id,txt){myData[id.substr(4,id.length-4)][(parseInt(id.substr(3,1)))].valx=txt;}
function portaDatiSulDB(){if(result=="ok"){try{myp.init();myp.addPar(globOut);exQuery("valGE","saveUpdates",disp_portaDatiSulDB,true);}catch(err){errmsgxxx('Preparing portaDatiSulDB',err.message);}function disp_portaDatiSulDB(){try{var d=this.req.responseText;var j=d.parseJSON();retmsg(d);}catch(err){errmsgxxx('Function portaDatiSulDB',err.message);}}}else{errmsgxxx('Saving','Some negative values...cannot save');}}

/****************************************************/
/* SECONDO TAB */
/****************************************************/

var h21 = new AW.UI.Input; h21.setId("h21"); h21.setControlText("");
var h22 = new AW.UI.Input; h22.setId("h22"); h22.setControlText("");
var l21 = new AW.UI.Label; l21.setId("l21"); l21.setControlText("Durata Minima della zona di linearita'"); l21.setStyle("color","darkblue");
var l22 = new AW.UI.Label; l22.setId("l22"); l22.setControlText("Percentuale di soglia degli spike"); l22.setStyle("color","darkblue");
var l23 = new AW.UI.Label; l23.setId("l23"); l23.setControlText("h."); l23.setStyle("color","darkblue");
var l24 = new AW.UI.Label; l24.setId("l24"); l24.setControlText("%"); l24.setStyle("color","darkblue");

var buffery="";

buffery=bSave+l21+l22+l23+l24;

var initPageT2=(function(){
buffer="";
try{
myp.init();
myp.addPar("101");
exQuery("MacroDPDV","getGlobalPars",dispGlobalPars);
}catch(err){
errmsgxxx('Preparing getNPWIP',err.message);
}
function dispGlobalPars(){
var d=this.req.responseText;
var j=d.parseJSON();
try{
if(controlReceivedData(dummyGrid,d,false,true,"No Record To Edit")){
h21.setControlText(j.r[0].LINEARITY);
h22.setControlText(j.r[0].SPIKE);
this.req=null;
}
}catch(err){
errmsgxxx('Function dispNPWIP',d+";"+err.message);
}
};
});

/****************************************************/
/* TERZO TAB */
/****************************************************/

var h31a =new AW.UI.Input; h31a.setId("h31a");h31a.setControlText("CODICE e NOME CAMPO"); h31a.setStyle("background","lightblue");h31a.setStyle("color","azure"); h31a.getContent('box/text').setAttribute('readonly',true);
var h32a =new AW.UI.Input; h32a.setId("h32a");h32a.setControlText("NOTE"); h32a.setStyle("background","lightblue");h32a.getContent('box/text').setAttribute('readonly',true);
var h33a =new AW.UI.Input; h33a.setId("h33a");h33a.setControlText("SOURCE STHP"); h33a.setStyle("background","lavender"); h33a.getContent('box/text').setAttribute('readonly',true);
var h34a =new AW.UI.Input; h34a.setId("h34a");h34a.setControlText(""); h34a.setStyle("background","lavender"); h34a.getContent('box/text').setAttribute('readonly',true);

var bufferw="";

bufferw=bSave+h31a+h32a+h33a;//+h34;


var initPageT3a=(function(){
buffer="";
try{
myp.init();
myp.addPar("101");
exQuery("MacroDPDV","getFieldSthp",dispNPa);
}catch(err){
errmsgxxx('Preparing getFieldSthp',err.message);
}
function dispNPa(){
var d=this.req.responseText;
try{
if(controlReceivedData(dummyGrid,d,false,true,"No Record To Edit")){
buildGrid3a(d);
this.req=null;
}
}catch(err){
errmsgxxx('Function dispNPa',d+";"+err.message);
}
};
});
var buildGrid3a=(function(res){
var d=res.parseJSON();
var top=0;
var output="";
var len=d.count;
//alert(len);
for(i=0;i<len;i++){
myData[i]=new Array();
myData[i][0]={idx:"col0"+i,valx:d.r[i].C_CAMPO};
}
lCountryVal.setControlText(d.r[0].C_CAMPO);
for(i=0;i<len;i++){
var el1=buildCol13a(i,top,d,myData);
top+=19;
}
getId('ppp').innerHTML=buffer;
setTimeout('endwait()',1);
});
var buildCol13a=(function(id,top,d,myData){
var dim=284;
myData[i][1]={idx:"col1"+id,valx:d.r[id].C_CAMPO};
var el=new AW.UI.Input;
el.setId("col1"+id);
el.setControlText(d.r[id].C_CAMPO+" - "+d.r[id].N_CMP);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("background","lavender");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
buffer+=el;
var el2=buildCol33a(id,top,dim,d,myData);
return true;
});
var buildCol33a=(function(id,top,dim,d,myData){
left+=dim-1;
var dim=321;
myData[i][2]={idx:"col2"+id,valx:"null"};
var el=new AW.UI.Input;
el.setId("col2"+id);
el.setControlText(d.r[id].CMT);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("background","lavender");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
buffer+=el;
var el4=buildCol43a(id,top,dim-1,d,myData);

return true;
});
var buildCol43a=(function(id,top,dim,d,myData){
left+=dim;
var dim=228;
myData[i][3]={idx:"col3"+id,valx:d.r[id].STHP};
var el=new AW.UI.Combo;
el.setId("col3"+id);
el.setControlText(d.r[id].STHP);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
el.setItemText(["FIELD","WELL","MIXED"]);
el.setItemCount(3);
el.setItemValue(["F","W","X"]);
var popup= el.getPopupTemplate();
popup.setStyle("width",220);
popup.setStyle("height",17*6);
//cCountry.refresh();
setComboValue(el,d.r[id].STHP);
buffer+=el;
el.onControlValidating = function(txt) {
insertVal3a(el.getId(),el.getItemValue(el.getSelectedItems()));
}
var el5=buildCol53a(id,top,dim-1,d,myData);
return true;
});
var buildCol53a=(function(id,top,dim,d,myData){
left+=dim;
var dim=121;
myData[i][4]={idx:"col4"+id,valx:d.r[id].NOTE};
left=0;
return true;
});
function initCmb3a(el,dim,subsid,dd,id){
var obj=el;
var cl="MacroDPDV";
var meth="getWF";
var dim=dim;
var op1=false;
var dbg=false;
var sync=true;
myp.init();
myp.addPar(subsid);
var vars="";
function val(x){
return "&value"+x+"=";
};
for(xx=0;xx<myp.data.length;xx++){
vars+=val(xx+1)+myp.data[xx].toString();
}
var strParamsx=bcNm+"MacroDPDV"+bcFx+"getWF"+vars;
dbg==true ? alert(strParamsx) : null;
var loaderx=new net.ContentLoader(lib+strParamsx,handler,null,"POST",'','','','',sync);
function handler(){
try{
var dati=this.req.responseText;
var d=dati.parseJSON();
if(dbg){alert("debug: "+dati);}
if(d=="undefined" d==undefined){
cE(this.req.responseText,"dispose "+obj.getId());
}
}catch(err){
alert(err.message+" -- "+this.req.responseText);
var d=[1];
}
setCombo(el,d,dim);
if(dd.count==1){
setComboValue(obj,"first");
}else{
setComboText(obj,dd.r[id].STHP);
}
obj.setControlDisabled(false);
};
}
function insertVal3a(id,txt){myData[id.substr(4,id.length-4)][(parseInt(id.substr(3,1)))].valx=txt;}
function portaDatiSulDB3a(){if(result=="ok"){try{myp.init();myp.addPar(globOut);exQuery("valGE","saveUpdates",disp_portaDatiSulDB,true);}catch(err){errmsgxxx('Preparing portaDatiSulDB',err.message);}function disp_portaDatiSulDB(){try{var d=this.req.responseText;var j=d.parseJSON();retmsg(d);}catch(err){errmsgxxx('Function portaDatiSulDB',err.message);}}}else{errmsgxxx('Saving','Some negative values...cannot save');}}


/****************************************************/
/* QUARTO TAB */
/****************************************************/

var h31 =new AW.UI.Input; h31.setId("h31");h31.setControlText("CODICE e NOME CAMPO"); h31.setStyle("background","lightblue");h31.setStyle("color","azure"); h31.getContent('box/text').setAttribute('readonly',true);
var h32 =new AW.UI.Input; h32.setId("h32");h32.setControlText("CODICE e NOME POZZO"); h32.setStyle("background","lightblue");h32.getContent('box/text').setAttribute('readonly',true);
var h33 =new AW.UI.Input; h33.setId("h33");h33.setControlText("SOURCE STHP"); h33.setStyle("background","lavender"); h33.getContent('box/text').setAttribute('readonly',true);
var h34 =new AW.UI.Input; h34.setId("h34");h34.setControlText(""); h34.setStyle("background","lavender"); h34.getContent('box/text').setAttribute('readonly',true);

var bufferz="";

bufferz=bSave+h31+h32+h33;//+h34;


var initPageT3=(function(){
buffer="";
try{
myp.init();
myp.addPar("101");
exQuery("MacroDPDV","getSthp",dispNP);
}catch(err){
errmsgxxx('Preparing getNPWIP',err.message);
}
function dispNP(){
var d=this.req.responseText;
try{
if(controlReceivedData(dummyGrid,d,false,true,"No Record To Edit")){
buildGrid3(d);
this.req=null;
}
}catch(err){
errmsgxxx('Function dispNPWIP',d+";"+err.message);
}
};
});
var buildGrid3=(function(res){
var d=res.parseJSON();
var top=0;
var output="";
var len=d.count;
//alert(len);
for(i=0;i<len;i++){
myData[i]=new Array();
myData[i][0]={idx:"col0"+i,valx:d.r[i].C_POZZO};
}
lCountryVal.setControlText(d.r[0].C_CAMPO);
for(i=0;i<len;i++){
var el1=buildCol13(i,top,d,myData);
top+=19;
}
getId('ppp').innerHTML=buffer;
setTimeout('endwait()',1);
});
var buildCol13=(function(id,top,d,myData){
var dim=284;
myData[i][1]={idx:"col1"+id,valx:d.r[id].C_CAMPO};
var el=new AW.UI.Input;
el.setId("col1"+id);
el.setControlText(d.r[id].C_CAMPO+" - "+d.r[id].N_CMP);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("background","lavender");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
buffer+=el;
var el2=buildCol23(id,top,dim,d,myData);
return true;
});
var buildCol23=(function(id,top,dim,d,myData){
left+=dim-1;
var dim=321;
myData[i][2]={idx:"col2"+id,valx:d.r[id].C_POZZO};
var el=new AW.UI.Input;
el.setId("col2"+id);
el.setControlText(d.r[id].C_POZZO+" - "+d.r[id].N_POZZO);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("background","lavender");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
buffer+=el;
var el4=buildCol43(id,top,dim-1,d,myData);

return true;
});
var buildCol43=(function(id,top,dim,d,myData){
left+=dim;
var dim=228;
myData[i][3]={idx:"col3"+id,valx:d.r[id].STHP};
var el=new AW.UI.Combo;
el.setId("col3"+id);
el.setControlText(d.r[id].STHP);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',true);
el.setItemText(["FIELD","WELL"]);
el.setItemCount(3);
el.setItemValue(["F","W"]);
var popup= el.getPopupTemplate();
popup.setStyle("width",220);
popup.setStyle("height",17*6);
//cCountry.refresh();
setComboValue(el,d.r[id].STHP);
buffer+=el;
el.onControlValidating = function(txt) {
insertVal3(el.getId(),el.getItemValue(el.getSelectedItems()));
}
var el5=buildCol53(id,top,dim-1,d,myData);
return true;
});
var buildCol53=(function(id,top,dim,d,myData){
left+=dim;
var dim=121;
myData[i][4]={idx:"col4"+id,valx:d.r[id].NOTE};
/*
var el=new AW.UI.Input;
el.setId("col4"+id);
el.setControlText(d.r[id].NOTE);
el.setStyle("width",dim);
el.setStyle("height","20");
el.setStyle("top",top);
el.setStyle("left",left);
el.getContent('box/text').setAttribute('readonly',false);
el.onControlValidating = function(txt) {
el.setStyle("background","#fff");
result="ok";
insertVal3(el.getId(),txt);
}
buffer+=el;
*/

left=0;
return true;
});
function initCmb3(el,dim,subsid,dd,id){
var obj=el;
var cl="MacroDPDV";
var meth="getWF";
var dim=dim;
var op1=false;
var dbg=false;
var sync=true;
myp.init();
myp.addPar(subsid);
var vars="";
function val(x){
return "&value"+x+"=";
};
for(xx=0;xx<myp.data.length;xx++){
vars+=val(xx+1)+myp.data[xx].toString();
}
var strParamsx=bcNm+"MacroDPDV"+bcFx+"getWF"+vars;
dbg==true ? alert(strParamsx) : null;
var loaderx=new net.ContentLoader(lib+strParamsx,handler,null,"POST",'','','','',sync);
function handler(){
try{
var dati=this.req.responseText;
var d=dati.parseJSON();
if(dbg){alert("debug: "+dati);}
if(d=="undefined" d==undefined){
cE(this.req.responseText,"dispose "+obj.getId());
}
}catch(err){
alert(err.message+" -- "+this.req.responseText);
var d=[1];
}
setCombo(el,d,dim);
if(dd.count==1){
setComboValue(obj,"first");
}else{
setComboText(obj,dd.r[id].STHP);
}
obj.setControlDisabled(false);
};
}
function insertVal3(id,txt){myData[id.substr(4,id.length-4)][(parseInt(id.substr(3,1)))].valx=txt;}
function portaDatiSulDB3(){if(result=="ok"){try{myp.init();myp.addPar(globOut);exQuery("valGE","saveUpdates",disp_portaDatiSulDB,true);}catch(err){errmsgxxx('Preparing portaDatiSulDB',err.message);}function disp_portaDatiSulDB(){try{var d=this.req.responseText;var j=d.parseJSON();retmsg(d);}catch(err){errmsgxxx('Function portaDatiSulDB',err.message);}}}else{errmsgxxx('Saving','Some negative values...cannot save');}}


setTimeout('initPageT1()',2);
setTimeout('loadmsg(\"Initializing Grid Data\")',1);

var page1 = [bufferx];
var page2 = [buffery];
var page3 = [bufferw];
var page4 = [bufferz];
var pages = [page1, page2, page3, page4];

frame2.element().innerHTML = page1;

tabs.onCurrentItemChanged = function(i){
frame2.element().innerHTML = pages[i].join("");
if(i==0){
getId('ppp').innerHTML="";
bSave.onClick = function(){creaOutput();};//portaDatiSulDB();};
setTimeout('initPageT1()',2);
setTimeout('loadmsg(\"Initializing Grid Data\")',1);
}else if(i==1){
getId('ppp').innerHTML="";
bSave.onClick = function(){alert("secondo tab");};
getId('ppp').innerHTML=h21+h22;
setTimeout('initPageT2()',2);
}
else if(i==2){
getId('ppp').innerHTML="";
bSave.onClick = function(){creaOutput();};//portaDatiSulDB();};
setTimeout('initPageT3a()',2);
setTimeout('loadmsg(\"Initializing Grid Data\")',1);
}
else if(i==3){
getId('ppp').innerHTML="";
bSave.onClick = function(){creaOutput();};//portaDatiSulDB();};
setTimeout('initPageT3()',2);
setTimeout('loadmsg(\"Initializing Grid Data\")',1);
}
else{

}
if (frame2.$browser=="opera"){document.body.className += ""}
}

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.