giovedì 15 settembre 2016

Una Web-app single-page full javascript (ExtJs,Node,Nginx)

Negli ultimi anni ho progettato diverse applicazioni web utilizzando diverse tecnologie e nel corso del tempo le architetture utilizzate si sono evolute fino ad arrivare ad una webapp full-stack in javascript, con l'utilizzo di ExtJs, Node.js e Nginx


Architettura multilayer


Nel 2006 , quando ho iniziato con le prime note sulla tecnologia Ajax l'architettura di una webapp 2.0 era relativamente semplice: si utilizzavano chiamate ajax all'interno di una webapp scritta in php, asp, jsp o uno dei linguaggi web che andavano per la maggiore. Nel corso del tempo li ho utilizzati tutti, o quasi, trovando piu' facile sviluppare in php rispetto ad asp o jsp.

Man mano cominciavano a diventare noti i primi framework ajax: prototype, jquery,mootools che rendevano disponibili un set di librerie javascript che facilitavano lo sviluppo della logica di gestione dell'interazione della pagina con l'utente e standardizzavano le chiamate ajax al backend.

Parlando di webapp aziendali, in cui si doveva interagire con i dati presenti su un database, mi veniva quasi naturale cercare un modo per separare il client con la sua interfaccia grafica (GUI) da tutto cio' che riguardava l'interfaccia verso il DB. In piu' cercavo qualcosa che mi aiutasse a creare GUI professionali direttamente in javascript.

Il primo framework che ho utilizzato per lo sviluppo della GUI fu Activewidgets: in questo modo potevao separare il client scritto completamente in javascript dal backend che potevo scrivere indifferentemente con un linguaggio dinamico come php,jsp, asp. Solo che ora il backend si occupava di implementare unicamente la business logic di lettura/scrittura dei dati rispetto al DB sottostante.
Il client era composto di diversi file javascript quante erano le "viste" della GUI, ed aveva un suo motore javascript per gestire la logica di generazione dinamica degli elementi della GUI attraverso le librerie di ActiveWidgets, le interazioni con l'utente e le chiamate ajax verso il backend.

Il backend era progettato con una architettura tale per cui in un unica pagina dinamica, il connettore, si gestiva un proxy di chiamate RPC in arrivo dal client. Il connettore puo' essere una pagina jsp che sfrutta la persistenza dei javabeans, oppure un modulo HttpHandler scritto in C#, o un set di moduli php.

Il mio intento principale era quello di separare il piu' possibile il client dal backend , su una architettura a due o tre livelli. Ad esempio se si ha un DB Oracle si puo' sfruttare una business logic scritta in pl/sql richiamata dai moduli intermedi scritti in java/c# del connettore che agisce solo come layer di trasporto dei dati da e verso il client, magari incapsulati in messaggi in formato JSON.

In questo modo persone diverse potevano interagire nello sviluppo della webapp, rendendo anche piu' modulare il tutto anche rispetto agli skill richiesti. Cambiando il connettore iniziale poi si poteva collegare il client a qualsiasi tecnologia di backend il cliente avesse a disposizione, mantenendo allo stesso tempo le medesime librerie per il client.

Su questo modello di architettura ho iniziato ad utilizzare come framework javascript ExtJS (dalla versione 4.2 in poi)  migliorando molto le funzionalità che si possono implementare nel client. In pratica le webapp ora sono delle applicazioni su singola pagina che non hanno nulla da invidiare ai programmi che sviluppavo in C/C++ con architettura client/server.

ExtJs nel tempo ha reso disponibili e stabili tecniche avanzate per effettuare chiamate di tipo RPC, introducendo la tecnologia Ext.Direct. In pratica si tratta di un protocollo che permette di scrivere le chiamate alle funzioni remote scritte sul backend direttamente sul client. Basta che sia configurato ed installato un connettore adatto al linguaggio utilizzato con cui il motore di ExtJs mappa i metodi disponibili sul server di backend  scritti seguendo le convenzioni opportune in modo tale che la chiamata al metodo javascript venga reindirizzata alla funzione remota sul server.

Questa e' l'architettura che piu' o meno ho utilizzato finora.

Parallelamente nel tempo ho visto venire alla ribalta molti altri framework javascript, piu' o meno incentrati sulle funzionalità di creazione delle GUI.

Bootstrap, Meteor, Backbone.js, Angular.js, Ember.js e sicuramente qualcuno me lo sono dimenticato

Dovendo gestire webapp aziendali ho sempre trovato ExtJs ottimo, anche perche' secondo me ha una delle migliori Grid disponibili. Veloce, flessibile, e piena di funzionalità, ed i clienti di solito quando chiedono un componente di questo tipo tendono a pensare a funzionalità che nemmeno la griglia di excel possiede...

C'e' un aspetto che pero' ho sempre cercato di migliorare in una architettura di questo tipo: come minimo occorre poter gestire skill di javascript per il client, jsp/php/c# per il backend sull'application server e TSQL/plsql per la business logic sul DB. E senza considerare le varianti possibili sui vari layer. Ma se per TSQL/plsql non ci sono molte alternative in ambito enterprise (in altri ambiti si possono usare NoSqlDB javascript)

Full Stack Javascript


A questo punto ho pensato alla possibilità di utilizzare una architettura che utilizzasse solo javascript, dal client alla logica di business lato server.
Node.js sarebbe perfetto per utilizzare javascript anche sul server ed in piu' ha dei plus tra cui:
  • molto efficiente grazie alla struttura event-based
  • utilizzando Express e' semplicissimo scrivere un server HTTP
  • ci sono moduli per connettersi ai principali DB, tra cui SqlServer (effettivamente provato e non serve compilare i sorgenti),MySql ed Oracle
In questo modo si puo' creare un server HTTP che risponde alle chiamate effettuate dal client ExtJs, sia mantenendo call di tipo RPC sia modificando le chiamate in architettura REST.

Resta un ultimo problema da risolvere: come si puo' integrare il client javascript che utilizza le librerie ExtJs che deve essere servito come pagine statiche nel web server istanziato dal processo generato con Node.js

Node e' ottimizzato per gestire i contenuti dinamici, ma servire contenuti statici pone tutta una serie di problematiche e limitazioni, non ultimo il fatto che il loop degli eventi rimane bloccato finche' si completa il trasferimento di files di dimensioni considerevoli.

Nginx usato come reverse proxy


La soluzione che ho trovato e' quella di usare Nginx come webserver per i contenuti statici, e come reverse-proxy per utilizzare il server creato con Node per le chiamate REST che il client effettua per recuperare i dati dal backend.
In pratica Nginx serve i files statici iniziali (html piu' i vari engine javascript che compongono il client) poi le interazioni con l'utente che generano le call ajax vengono reindirizzate da nginx sul server Node che opera su un processo distinto. Occorre che i servizi nginx e node stiano in ascolto su due porte diverse e che le call ajax REST abbiano un entry point comune, ad esempio il percorso "http://nomesito/api/" .
Il tutto viene gestito , supponendo che node sia in ascolto sulla porta 3000, con una direttiva di configurazione che per nginx puo' essere come questa

location ~ ^/api|other1|other2 {
    proxy_pass   http://127.0.0.1:3000;
}
mentre per i contenuti statici, supponendo che tutte le librerie del client siano referenziate nel file index.html si puo' usare questa direttiva

location / {
    root html;
    index index.html index.htm;
}

martedì 22 luglio 2008

DOM DocumentFragments

Questo Post prende spunto dall'articolo di John Resig che tratta i DOM DocumentFragments.

I DOM DocumentFragments sono dei contenitori lightweight (letteralmente "peso-leggero" cioe' semplificati,leggeri)che possono contenere dei nodi del Document Object Model (DOM).

Fanno parte delle specifiche ufficiali "DOM 1", quindi sono supportati nei browser recenti (a partire ad esempio da IE6).

Ma perche' possono diventare utilissimi ad uno sviluppatore di web applications?

Osservando meglio le specifiche si nota che:

"Furthermore, various operations -- such as inserting nodes as children of another Node -- may take DocumentFragment objects as arguments; this results in all the child nodes of the DocumentFragment being moved to the child list of this node"

ovvero cio' significa che se prendiamo un set di nodi del DOM e lo aggiungiamo ad un frammento (fragment) allora possiamo aggiungere il frammento al documento, invece di aggiungere ogni nodo singolarmente.

Nel far questo si ottiene una migliore performance complessiva, avendo a disposizione anche il metodo cloneNode nell'oggetto DocumentFragments. Questo perche' e' meno costoso aggiungere i nodi al frammento, essendo un oggetto molto piu' piccolo dell'intero DOM che invece ad ogni inserimento deve essere "risistemato".

Supponiamo di avere un blocco di nodi DOM che vogliamo inserire in un semplice documento:

var elems = [
document.createElement("hr"),
text( document.createElement("b"), "Links:" ),
document.createTextNode(" "),
text( document.createElement("a"), "Link A" ),
document.createTextNode(" | "),
text( document.createElement("a"), "Link B" ),
document.createTextNode(" | "),
text( document.createElement("a"), "Link C" )
];


function text(node, txt){
node.appendChild( document.createTextNode(txt) );
return node;
}


Se vogliamo aggiungere questi nodi nel nostro documento probabilmente lo faremmo nel modo tradizionale: con un ciclo che scorre i nodi, li clona individualmente (cosicche' possiamo continuare ad aggiungerli attraverso tutto il documento):

var div = document.getElementsByTagName("div");


for ( var i = 0; i < div.length; i++ ) {
for ( var e = 0; e < elems.length; e++ ) {
div[i].appendChild( elems[e].cloneNode(true) );
}
}

DocumentFragments Append

Invece quando consideriamo l'uso dell'oggetto DocumentFragments possiamo subito vedere una diversa struttura. Per iniziare aggiungiamo tutti i nostri nodi nel frammento stesso, costruito usando il metodo createDocumentFragment.

Poi il punto interessante arriva quando e' ora di inserire effettivamente i nodi dentro il documento: dobbiamo solo chiamare appendChild e cloneNode una volta sola per tutti i nodi!

var div = document.getElementsByTagName("div");


var fragment = document.createDocumentFragment();
for ( var e = 0; e < elems.length; e++ ) {
fragment.appendChild( elems[e] );
}

for ( var i = 0; i < div.length; i++ ) {
div[i].appendChild( fragment.cloneNode(true) );
}
Facendo qualche misurazione su una pagina di demo si ottiengono risultati analoghi a questi:

Browser      Normal (ms) Fragment (ms)
Firefox 3.0.1      90                47
Safari 3.1.2        156               44
Opera 9.51        208               95
IE 6                      401             140
IE 7                     230                61
IE 8b1                 120               40


giovedì 20 marzo 2008

Le closure in Javascript

(Il seguente articolo e' estratto dal tutorial Javascript avanzato in corso di pubblicazione su www.ajaxcity.it, come parte III degli aspetti avanzati delle funzioni)

IL CONTESTO DI ESECUZIONE

Il contesto di esecuzione e' un concetto astratto usato nel documento di specifica del linguaggio ECMAScript (la versione standardizzata di javascript).
Non viene detto nulla in questo documento di specifica su come debba essere implementato questo concetto nella pratica.

Tutto il codice javascript viene eseguito in un contesto di esecuzione. Il codice globale, ovvero quello eseguito inline, normalmente come file JS o come pagina HTML, viene eseguito in un contesto di esecuzione globale, ed ogni chiamata a funzione avra' il proprio contesto di esecuzione associato.

Il codice eseguito con la funzione eval avra' un contesto di esecuzione distinto, ma poiche' eval di solito non viene utilizzato normalmente, se non nella formattazione JSON, per ora non viene trattata.

Il contesto di esecuzione di una funzione

Quando una funzione javascript viene invocata [con l'uso di "()" dopo il reference al nome della funzione], si inizializza un nuovo contesto di esecuzione; se viene chiamata una nuova funzione, o la stessa ricorsivamente, si crea un nuovo contesto di esecuzione e il programma in esecuzione entra in questo contesto per tutta la durata della chiamata a funzione; ritornando poi al contesto di origine quando la funzione chiamata restituisce il controllo al chiamante.

In questo modo il codice javascript in esecuzione forma una pila di contesti di esecuzione.

Creazione di un contesto di esecuzione per una chiamata a funzione

Quando viene creato un nuovo contesto di esecuzione, avvengono una serie di azioni in un ordine definito.

Per prima cosa, nel contesto di esecuzione di una funzione, viene creato un pseudo-oggetto "Activation", che ha proprieta' accessibili con un nome proprio ma non e' un oggetto normale perche' non ha un prototipo e non puo' essere referenziato direttamente usando codice javascript.

Il passo successivo e' la creazione dell'oggetto arguments, gia' incontrato nel nostro tutorial, con tutte le proprieta' collegate viste in precedenza.
Una proprieta' dell'oggetto Activation viene create col nome arguments e si assegna ad essa un reference all'oggetto arguments.

Activation.arguments --> Function.arguments

Come passo successivo si ha l'assegnazione di uno scope, o ambito, al contesto di esecuzione. Lo scope, o ambito, o ambiente, consiste in una lista (o elenco concatenato - catena - visto che si parla spesso di scope chain) di oggetti.

Ogni oggetto Function, ovvero ogni funzione (che in ultima istanza e' un oggetto in javascript) ha una proprieta' interna [[scope]] che anch'essa consiste di una lista di oggetti.

Lo scope assegnato al contesto di esecuzione di una chiamata a funzione e' composto dalla lista referenziata dalla proprieta' [[scope]] dell'oggetto funzione corrispondente con l'oggetto Activation aggiunto in testa a questa lista concatenata.

Riassumento un po' si ha quindi l'oggetto Function, ovvero la funzione stessa, con la sua proprieta' interna [[scope]] che referenzia una lista concatenata, una struttura dati esterna alla funzione, con in testa l'oggetto Activation della funzione, ovvero con in testa un reference all'oggetto Action della nostra funzione.

Quindi viene inizializzata l'instanziamento delle variabili, ovvero ad ogni parametro formale presente per la funzione viene associata una proprieta' dell'oggetto globale Variable-Activation (nella specifica questo oggetto viene nominato come Variable ma in definitiva si tratta dell'oggetto Activation creato in precedenza).

Se gli argomenti della chiamata a funzione corrispondono a questi parametri allora i valori di questi argomenti vengono assegnati alle proprieta' dell'oggetto Variable, senno' gli viene assegnato un valore "undefined".

Per le funzioni interne (ovvero le funzioni dichiarate dentro la nostra funzione appena chiamata) si creano oggetti funzione assegnati alle proprieta' dell'oggetto Variable con nomi che corrispondono ai nomi usati nelle dichiarazioni delle funzioni interne.

L'ultimo passo e' quello di creare le proprieta' dell'oggetto Variable che corrispondono a tutte le variabili dichiarate all'interno della nostra funzione, mettendo inizialmente un valore "undefined" fino a quando non avviene la valutazione delle espressioni di assegnamento durante l'esecuzione vera e propria del codice della nostra funzione.

E' proprio il fatto che l'oggetto Activation, con la sua proprieta' arguments, e l'oggetto Variable con tutte le sue proprieta' che corrispondeono alle variabili locali della funzione, siano lo stesso oggetto, che permette all'identificatore arguments di essere usato come una variabile locale, ovvero richiamandolo come [Function.]arguments all'interno del corpo della funzione.

Infine, si assegna un valore da poter utilizzare con la keyword this.
In pratica viene assegnato o l'oggetto proprietario della funzione, oppure l'oggetto globale se viene assegnato (internamente) un valore 'null'.

Il contesto di esecuzione globale invece ha alcune leggeri differenze poiche' non possiede argomenti cosicche' non necessita di un oggetto Activation definito che lo referenzi.

Il contesto globale non ha bisogno di uno scope, e la sua scope chain, cioe' la lista associata, consiste solo di un oggetto, l'oggetto globale primitivo.
Questo oggetto globale primitivo viene usato come oggetto Variable, cosa per cui le funzioni dichiarate globalmente diventano proprieta' dell'oggetto globale, come fossero variabili dichiarate globalmente.

Il contesto globale utilizza anche un reference all'oggetto globale per l'oggetto this.

Si vede quindi che tutto si riduce ad un oggetto, globale o no, con una propria lista (o scope chain) ed un oggetto Activation/Variable che contiene nelle sue proprieta' tutti i componenti mappati dell'oggetto iniziale.

LA SCOPE CHAIN O LISTA DEGLI AMBITI DI VISIBILITA' DELLE VARIABILI

La lista degli ambiti di visibilita' delle variabili per una chiamata a funzione, o catena di scope, viene costruita aggiungendo l'oggetto Activation/Variable in testa alla lista tenuta nella proprieta' [[scope]] della nostra funzione.

Cerchiamo quindi di capire come viene definita questa proprieta' interna [[scope]].

Nella specifica standardizzata del linguaggio javascript (ECMAScript) le funzioni sono oggetti. Vengono creati o durante le istanziazioni delle variabili dalle dichiarazioni delle funzioni (quando si usa la parola chiave function), o durante il processo di valutazione delle espressioni funzionali (quando si usano le funzioni anonime var fn=function()) oppure invocando il construttore Function.

Gli oggetti creati usando il costruttore Function hanno sempre una proprieta' [[scope]] che referenzia una lista di scope che contiene solo l'oggetto globale "{}".

Se creiamo un oggetto funzione con gli altri due metodi avremo invece una lista di scope del contesto di esecuzione in cui viene creato assegnata alla loro proprieta' interna [[scope]].

Se il contesto di esecuzione e' quello globale, ovvero se definiamo normalmente una funzione in un file JS eseguito all'interno della nostra pagina web, anch'essa conterra' solo l'oggetto globale.

Se definiamo una funzione utilizzando una espressione funzionale del tipo

var myFunction = function(formalParameter){
//function body code
}


viene creata inizialmente una proprieta' con il nome della variabile myFunction, senza creare l'oggetto per la funzione. Siamo pero' sempre nel contesto di esecuzione globale.
E quando si valuta l'espressione si crea l'oggetto Function e viene passato alla proprieta' della variabile un riferimento a questo oggetto.
Comunque l'oggetto viene creato sempre nel contesto di esecuzione globale.

Le dichiarazioni e le espressioni di funzioni annidate (inner functions) dentro altre funzioni fanno si' che l'oggetto risultante venga creato dentro il contesto di esecuzione di una funzione, cosi' che si abbiano liste di scope piu' elaborate.

Consideriamo il codice seguente in cui definiamo una funzione con una dichiarazione di funzione annidata al suo interno, e che poi esegue la funzione esterna:

function funzioneEsterna(par){
function funzioneInterna(){
//corpo della funzione interna
}
//resto delle istruzioni della funzione esterna
}
funzioneEsterna(5);


L'oggetto corrispondente alla dichiarazione della funzione esterna {funzioneEsterna} viene creato durante il setup delle variabili nel contesto di esecuzione globale cosi' che la proprieta' [[scope]] contiene la lista di scope composta dal solo oggetto globale.

Quando il codice globale esegue la chiamata alla funzione esterna viene creato un nuovo contesto di esecuzione per quella chiamata a funzione insieme ad un oggetto Variable/Activation insieme ad esso.
L'ambito di visibilita' delle variabili di questo nuovo contesto di esecuzione diventa la catena composta dal nuovo oggetto Activation seguito dalla lista referenziata dalla proprieta' [[scope]] dell'oggetto {funzioneEsterna}, ovvero solo l'oggetto globale {}.

Il setup dell'oggetto Variable per questo nuovo contesto di esecuzione si riflette nella creazione di un oggetto funzione che corrisponde alla definizione della funzione interna e la proprieta' [[scope]] di questo oggetto funzione viene referenziata con lo scope del contesto di esecuzione in cui viene creato, ovvero la lista di scope della funzione esterna, cioe' una lista con l'oggetto Activation seguito dall'oggetto globale {}.

Riassumento quindi generalmente quando si definisce una funzione solitamente viene creato un oggetto con scope globale, cioe' con una lista di scope associata alla proprieta' [[scope]] di quell'oggetto che contiene l'oggetto globale come unico termine della lista.

Risoluzione degli identificatori

Tutto il complesso discorso appena evidenziato ci serve per capire il meccanismo di risoluzione degli identificatori che avviene in Javascript.

Gli identificatori vengono risolti contro (against) la scope chain, o lista degli ambiti di visibilita' delle variabili.

La risoluzione degli identificatori inizia con il primo oggetto nella lista degli scope.
Si verifica se questo primo oggetto possiede una proprieta' con nome uguale all'identificatore.

La lista di scope e' una lista di oggetti, e gli oggetti possiedono una propria lista di prototipi, come abbiamo visto in una lezione precedente, perche' un oggetto puo' derivare da un altro oggetto come suo prototipo, e cosi' via fino al prototipo dell'oggetto globale.

Per cui questo controllo delle proprieta' dell'oggetto contenuto nella lista degli scope significa ripercorrere tutta la catena dei prototipi di quell'oggetto (se ne possiede una). Se non viene trovato un valore corrispondente nelle proprieta' del primo oggetto si passa a controllare all'oggetto successivo nella lista degli scope, se ci sono altri oggetti in essa.

Tutto cio' si ripete fino a quando viene trovata una proprieta' di qualche oggetto nella lista degli scope con un nome che corrisponde all'identificatore o fino a quando si esaurisce la scope chain.

L'oggetto globale e' sempre alla fine della lista degli scope.

Poiche' i contesti di esecuzione associati con le chiamate a funzione avranno sempre l'oggetto Activation in cima alla lista degli scope, gli identificatori locali utilizzati nel corpo della funzione verrano controllati sempre per primi per vedere se corrispondono con i parametri formali della funzione, con i nomi delle funzioni interne o con le variabili locali. Costoro verranno risolti come proprieta' nominali dell'oggetto Activation/Variable.

LE CLOSURE IN JAVASCRIPT

Proviamo, alla luce del discorso appena concluso nel capitolo precedente, un esempio di codice come il seguente:

var myObject = function() {...} ();


Vediamo che abbiamo creato una variabile chiamata myObject e gli stiamo associando una funzione anonima che non possiede argimenti formali.

La novita' rispetto a quanto visto finora e' la presenza delle due parentesi tonde () appena dopo le parentesi graffe che nelle nostre intenzioni racchiudono il corpo della funzione.

Queste parentesi tonde finali indicano a javascript di eseguire la funzione immediatamente dopo la creazione dell'oggetto Function associato durante la fase di parsing della definizione della funzione stessa.

Cio' significa che javascript non associa piu' alla variabile myObject un reference all'oggetto Function, come visto prima, ma associera' il risultato della funzione, o piu' propriamente cio' che viene restituito dalla funzione al termine della sua esecuzione.

Se la nostra funzione fosse una semplice funzione con all'interno solo variabili locali ed espressioni di calcolo che utilizzano le variabili locali, il valore ritornato sara' presumibilmente un singolo valore che verra' quindi associato alla variabile myObject, facendo si' che il contesto di esecuzione si esaurisca con l'esecuzione della funzione e permettendo al meccanismo di garbage collection (GC) di javascript di riciclare il contenuto dell'oggetto Activation/Variable creato durante la chiamata a funzione e l'oggetto Function stesso visto che si tratta di una funzione anonima che non puo' piu' essere richiamata altrove.

Il meccanismo di Garbage Collection di Javascript

In javascript, come in Java, si ha un meccanismo automatico di Garbage Collection, ovvero di raccolta della spazzatura.

Per i linguaggi di programmazione la spazzatura e' tutto l'insieme di oggetti che una volta esaurita la loro esistenza una volta compiuto il compito per cui sono stati creati, e che quindi occupano memoria.

Una funzione anonima una volta eseguita non puo' piu' essere richiamata, non avendo un nome. Quindi l'oggetto Function, e l'oggetto Activation creato durante la sua esecuzione, devono essere smaltiti per liberare la memoria occupata.

Piu' generalmente un oggetto che non puo' piu' essere referenziato diventa disponibile per il suo smaltimento e distruzione.

Il meccanismo di Garbage Collection si occupa di ricercare periodicamente gli oggetti non piu' referenziabili da smaltire e della loro effettiva distruzione con conseguente liberazione della memoria occupata.

Creazione di una Closure

Se pero' la nostra funzione anonima possedesse una o piu' funzioni interne facenti parte dell'espressione di return della nostra funzione principale, alla variabile myObject verrebbe ora associato un oggetto contenente le funzioni interne restituite col return:

var myObject = function() {
var privateVar = 'Questa variabile è privata!';
var privateFunction = function() {
alert('Questa funzione è privata!');
}
return {
showPrivateVar : function () {
alert(privateVar);
},
modifyPrivateVar : function(val) {
privateVar = val;
},
executePrivateFunc : function() {
privateFunction();
}
}
}();


In questo caso la nostra funzione anonima "esterna" possiede localmente una variabile privateVar pura ed una variabile privateFunction a cui viene associata una funzione interna anonima - diremo quindi piu' semplicemente che possiede la funzione interna privateFunction tanto ora abbiamo capito il giro che viene fatto.

Inoltre nella sezione retituita all'esterno dalla sua esecuzione troviamo, nella notazione ad oggetti di javascript, tre ulteriori funzioni (chiamate comunemente "accessors" o modificatori) che operano sulla variabile e sulla funzione interna locali.

Se la funzione esterna ritornasse solo un valore come risultato, non potremmo piu' accedere a privateVar o a privateFunction una volta eseguita la funzione esterna.

Cioe' il contesto di esecuzione si esaurirebbe con la funzione, e l'oggetto Activation/Variable verrebbe dereferenziato completamente appartenendo allo scope interno alla funzione con lista composta dall'oggetto Activation e dall'oggetto globale {}.

Invece la variabile myObject ha uno scope globale con una lista composta solo dall'oggetto globale {}.

Invece quando vengono restituite delle funzioni interne all'esterno della funzione esterna, con l'istruzione return come nel caso riportato con il codice javascript, l'oggetto ritornato (che consta di tre funzioni) referenzia lo scope interno alla funzione esterna, perche' le tre funzioni interne hanno una scope chain composta giustamente dall'oggetto Activation/Variable che referenzia gli elementi locali del corpo della funzione esterna, e dall'oggetto globale {}.

Questo oggetto quindi viene associato alla variabile con scope globale myObject.

Ecco creata una closure (o "chiusura") dello scope interno alla funzione eseguita sullo scope globale della variabile myObject.

In questo modo, poiche' la variabile myObject puo' referenziare ancora l'oggetto Activation attraverso uno dei tre metodi "acquisiti", questo oggetto non puo' piu' essere eliminato dal meccanismo di GC della memoria.

In pratica la funzione esterna una volta eseguita non esiste piu'. Esiste solo il risultato della funzione. Pero' le sue variabili e funzioni private esistono ancora e possono essere accedute/modificate attraverso le funzioni di accesso/modifica acquisite dalla variabile globale myObject:

Quando ora chiamiamo
myObject.showPrivateVar()
si ottiene un alert box con il messaggio 'Questa variabile è privata!';

Allo stesso modo se chiamiamo
myObject.modifyPrivateVar('nuovo valore')
otteniamo la modifica del valore della variabile privata (ed e' l'unico modo con cui questa variabile privata puo' essere modificata, solo se viene fornito all'esterno un modificatore).
Se quindi adesso richiamiamo
myObject.executePrivateFunc()
otteniamo un alert box con il messaggio 'nuovo valore'.

Questo modo di creare una closure permette la creazione di namespace e l'incapsulamento dei metodi e delle proprieta' locali degli Oggetti in javascript.

myObject agisce come namespace, e le tre funzioni di accesso/modifica nascondono le variabili e i metodi privati della funzione esterna che ora puo' essere vista come Oggetto/Classe che esporta verso l'esterno tre metodi pubblici.

Ci sono molti altri modi per creare una closure.

In pratica ogni volta che viene restituita una funzione interna al di fuori di una funzione esterna si forma una chiusura dello scope interno sullo scope esterno. Se si ha quindi un reference associato ad un elemento globale, come nel nostro caso la variabile myObject, oppure quando si associa un eventhandler ad un evento di un elemento del Document Object Model della nostra pagina web, l'oggetto Activation dello scope locale sopravvive alla terminazione della funzione esterna.

Per approfondimenti ulteriori su altri modi di creare closure vi rimando all'articolo di Claudio Cicali (http://stacktrace.it/articoli/2007/12/javascript-closures/), oppure al 'must-read' di Richard Cornford (http://www.jibbering.com/faq/faq_notes/closures.html) da cui ho tratto spunto per questo capitolo sulle closure soprattutto sul discorso preliminare dell'execution context e sulla struttura della scope chain.

mercoledì 19 marzo 2008

Collaborazione con AjaxCity.it

Da circa un mese sto collaborando con il portale AjaxCity.it sia fornendo supporto al forum sulle questioni che riguardano la progettazione di web applications ajax, sia collaborando alla stesura di un Tutorial Javascript avanzato.
Mi sono accorto infatti, leggendo le domande fatte sul forum di AjaxCity che molti sviluppatori intraprendono la strada dell'approccio ajax senza le necessarie basi di Javascript per inquadrare gli aspetti piu' importanti coinvolti in questo approccio di progettazione.

Non mi riferisco quindi alla conoscenza base del linguaggio, ma a quegli aspetti avanzati che comunque assumono una maggiore rilevanza rispetto all'utilizzo tradizionale di javascript per la sola validazione di un form in una pagina web.

Spesso ci si imbatte in concetti tipo i timers, le closure, i prototipi, o gli event-handlers che non sempre fanno parte del bagaglio di conoscenze javascript necessarie per sfruttare a fondo le potenzialita' dell'approccio ajax.

Quindi vi rimando al portale di AjaxCity per quanto riguarda il Tutorial Javascript avanzato, mentre parallelamente cerchero' di portare avanti qui il Tutorial Ajax.

Buona lettura ;)

lunedì 4 febbraio 2008

Ajax: da dove inizio?

Che cosa e' Ajax?

Ecco la prima domanda che ci viene in mente quando leggiamo per la prima volta qualche articolo sulla programmazione web che ci parla di questo nuovo Ajax!Magari siamo abituati a fare le nostre pagine web dinamiche in php, in jsp oppure in asp.
E leggiamo che se le facessimo in Ajax potremmo anche fare quell'applicazione ultra-avanzata che non abbiamo idea come siano riusciti a farla cosi' veloce e cosi'...nuova rispetto alle nostre pagine web!

Vi siete magari chiesti come hanno fatto a fare le mappe di Google, Gmail oppure il sito di Flickr, o se siete piu' curiosi come hanno potuto creare una
pagina che sembra un foglio excel, e soprattutto funziona come un foglio excel, con anche il copia-incolla!!!!

Sicuramente si trovano migliaia di siti ormai che ci spiegano cosa e' Ajax, che non e' un linguaggio vero e proprio, che e' un acronimo e da quali parole e' formato.

Ok, d'accordo, ma in pratica? Come si inizia con Ajax???

Ecco dove casca l'asino....come si dice in questi casi.
Qui su questa domanda ci sono molte opinioni in proposito, non tutte concordanti tra loro.
Chi vi suggerisce un framework, chi un altro.
Chi vi spiega come si usa l'oggetto XMLHttpRequest, chi vi dice "no attenzione, su IE6 devi usare l'ActiveX!".
Chi vi racconta l'uso di una libreria integrata con php, che di un'altra integrata con jsp oppure l'utilizzo di AJAX.NET.

Supponiamo pero' di essere nei panni dello sviluppatore di pagine web "old style" medio, che conosce abbastanza bene l'html, sa usare pure i css e un po' di javascript per validare i campi di un form. E che conosce php, oppure jsp, oppure asp, oppure ASP.NET ma non vuole legarsi necessariamente a nessuno di essi.

Beh il primo punto di partenza per iniziare con Ajax e' migliorare la nostra conoscenza con Javascript!

Mentre prima scrivevamo 10 righe di javascript nelle nostre pagine web, ora ci tocchera' scriverne almeno 20 volte tanto! E dovremo pure essere capaci a manipolare il Document Object Model della nostra pagina web.

Non sai cosa e' il D.O.M.? Prima di proseguire oltre leggi bene
qui ,qui e qui.

E dopo Javascript? Mi serve altro oppure posso gia' iniziare con Ajax?

In teoria e' sufficiente, pero' come diceva una famosa pubblicita' la potenza e' nulla senza controllo. Javascript ci fornisce la potenza necessaria. Ma sapere cosa e' ajax nel profondo ci fornisce il controllo che ci manca.
Mi serve sapere qualcosa di piu' su come usare javascript per avere una pagina Ajax. Ovvero capire in cosa e' diversa una pagina ajax rispetto ad una pagina web "normale", e perche' mai una pagina ajax e' "meglio" di quella normale.

Ajax e' ne' piu' ne' meno che la capacita' modificare la mostra pagina potendo richiedere i nuovi dati dinamicamente al webserver senza usare il metodo submit() con il tag


<form action="destinazione.php" method="POSTGET"></form>

In piu' abbiamo la possibilita' di fare questa richiesta in modo a-sincrono. Ma su quest'aspetto torneremo piu' tardi.

Veniamo innanzitutto al tag form:

quando abbiamo bisogno di modificare dinamicamente la nostra pagina web con nuovi contenuti solitamente che facciamo? Richiamiamo la nostra pagina dinamica passando dei parametri per recuperare una versione alternativadella stessa pagina in cui qualche dato e' cambiato.
Se stiamo recuperando la lista dei CAP di una citta' in un combobox nella nostra pagina web, e digitiamo "Milano", ricarichiamo la nostra pagina con l'elenco giusto passando ad esempio come parametro "destinazione.php?city=milano". Fin qui nulla di sconvolgente.

Ma se potessimo chiedere al server di sarci solo la lista dei CAP senza tutto il resto? In fondo a noi serve quello, non tuttala pagina. Poi magari attacchiamo la lista dei CAP alla combobox che compare nella nostra pagina sul browser.
Con il tag form non possiamo, perche' il metodo submit() richiede tutta la pagina ricaricandola dal server. E facendo questo la vecchia pagina visualizzata sul browser viene sovrascritta completamente.

Per far cio' abbiamo a disposizione ora sui browser di ultima generazione (IE7, Firefox2, Opera9, Safari3) un metodo alternativo:il metodo send() presente in un oggetto javascript chiamato XMLHttpRequest.

Ma possiamo usarlo subito oppure occorre fare qualche operazione preliminare? Dobbiamo caricare qualcosa prima?

No, non dobbiamo caricare nulla, a meno di non usare IE6. In quel caso non si ha a disposizione quell'oggetto internamente nel browser,ma occorre caricare un oggetto ActiveX esterno. Vedremo dopo come si fa. Proseguiamo supponendo di averlo gia' a disposizione.

Quindi come si usa ora quel metodo send()?

Ecco, occorre un minimo di preparazione per poterlo usare. Perche' occorre sapere come dirgli cosa vogliamo caricare e capirecome utilizzare i risultati che ci verranno restituiti dal server una volta elaborata la nostra richiesta della lista dei CAP.

E qui interviene il javascript, poiche' l'unico modo per utilizzare il metodo send() di quell'oggetto e' tramite javascript:

Metteremo tutto il codice javascript in un file esterno alla nostra pagina web.
Dobbiamo anche entrare un un preciso ordine di idee: dobbiamo sempre pensare che il codice javascript viene eseguito, ed ha il suo ambito di efficacia, solo sul client. Quindi dobbiamo sempre ricordarci che chi effettua le richieste verso il web server remotoe' il browser su cui stiamo mostrando la nostra pagina web.

Quindi mentre prima quando programmavamo una pagina dinamica in php consideravamo tutto dal punto di vista del server su cui viene eseguito il codice php, ora ribaltiamo il nostro punto di osservazione poiche' il codice javascript viene eseguito sul client.

Vediamo il codice che ci serve per usarlo:


// se il browser e' != IE6 il typeof di XMLHttpRequest e' diverso da 'undefined'
// in questo modo si puo' capire se stiamo usando IE6 oppure no
// Con IE6 ridefiniamo la funzione XMLHttpRequest
// cosi' da caricare l'ActiveX alternativo per usare le chiamate Ajax
// anche quando chiameremo la funzione XMLHttpRequest()
if (typeof(XMLHttpRequest) == 'undefined')
var XMLHttpRequest = function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}

// Questa e' la funzione principale per effettuare le chiamate ajax.
// occorre passare l'url che si vuole richiedere e la funzione di
// callback da chiamare quando si ricevono i risultati dal webserver
function sendRequest(url, callBack) {

// inizializziamo l'oggetto base delle chiamate ajax
// in questo modo, con l'if di sopra, abbiamo o la chiamata diretta
// alla funzione che carica l'oggetto XMLHttpRequest nativo
// oppure la chiamata alla funzione ridefinita che carica l'ActiveX
var req = new XMLHttpRequest();

// costruiamo l'eventhandler che collega la funzione di callback
// sull'evento onreadystatechange attivato dall'oggetto XMLHttpRequest
// ogni volta che cambia il suo stato interno a fronte della richiesta ajax
req.onreadystatechange = function(){

// readyState==4 quando la richiesta e' stata elaborata e si riceve il risultato dal server
if (req.readyState == 4 )){

// status==200 quando la risposta dal server e' stata ricevuta correttamente
if(req.status == 200){

// chiamo la funzione di callback passando l'oggetto XMLHttpRequest come riferimento
// per manipolare il risultato ricevuto memorizzato nelle proprieta'
// req.responseText oppure req.responseXML
callBack(req);
}else{
alert("errore");
}
}
}

// inizializziamo il canale con il server passando il tipo di richiesta (POST o GET)
// l'url o indirizzo del server cui fare la richiesta
// e true o false se vogliamo fare una richiesta asincrona o sincrona
req.open("POST", url, true);

// effettuiamo l'attivazione della richiesta remota
req.send();
}

Innanzitutto dobbiamo caricare l'oggetto associando una sua nuova istanza alla variabile req.

In questo modo viene caricata l'ActiveX nel caso in cui si sta usando IE6 dentro il primo blocco if(){}.

NOTA: Se l'oggetto e' incluso nel browser, il tipo associato e' diverso da 'undefined'. Quindi testando il typeof() sappiamo che l'oggetto non e' interno al browser.

Per cui in quel caso "ritorniamo", ovvero carichiamo, l'oggetto ActiveX alternativo.

Non controlliamo nemmeno se il caricamento va bene, perche' o l'oggetto e' interno oppure e' un ActiveX.Se va male l'ActiveX vuol dire che il nostro browser ha dei problemi gravi e non funziona correttamente, e ce ne saremmo gia' accorti.

Il metodo send() ha bisogno di sapere se la richiesta viene fatta in GET oppure in POST, come col tag form.Poi deve sapere l'url, ovvero quale risorsa richiedere al webserver - come nell'attributo action di prima. Infine serve dirgli se fare la richiesta in modo asincrona, percio' diciamo "true", cioe' "vero" per significare si.Tutti queste informazioni vengono fatte "aprendo" l'oggetto XMLHttpRequest con il metodo open();

Ok. Adesso dobbiamo capire come intercettare i dati quando ci vengono inviati dal server.
Mentre prima con form non dovevamo preoccuparci di fare nulla, poiche' ci pensava il browser a ricaricare la pagina ricevuta dal server, adesso dobbiamo essere noi ad intercettare i dati restituiti e a "piazzarli" al loro posto. Per capire quando e se i dati sono arrivati, ci vengono in soccorso due stati associati all'oggetto req in collegamento all'evento onreadystatechange:

  • l'evento notifica le varie fasi di ricezione dei dati remoti variando il valore di uno stato interno. Ci basta sapere a questo punto che i dati sono stati ricevuti dal server quando readyState==4.
  • a quel punto controlliamo l'attributo status che indica il codice ritornato dal server, che altri non e' che il noto codice di ritorno del protocollo HTTP. Ci basta sapere che se i dati sono ricevuti corretti il codice e' 200. Cio' vuol dire che in tutti gli altri casi i dati o sono stati ricevuti con un codice di errore oppure non sono ancorastati ricevuti. Per questo motivo mostriamo un alert di errore se sono stati ricevuti con stato !=200.


In teoria bisognerebbe gestire anche il caso in cui non si arrivi ad un readyState=4, ma per ora non ce ne occupiamo per semplicita'.

L'evento onreadystatechange non siamo noi che lo gestiamo, ma viene gestito in modo asincrono direttamente dal browser.Cioe' il nostro browser in un thread parallelo controlla il passaggio degli stati collegati a questo evento, facendolo scattareper avvisare , per cosi' dire, l'"utente" della pagina che ha fatto la richiesta che tale richiesta sta venendo processata in parallelo.

Cio' significa che la nostra pagina web non viene bloccata finche' non si ricevono tutti i dati, ma l'utente puo' continuare ad interagire con essa. Mentre prima con form tutto si bloccava finche' la pagina non veniva ricaricata completamente. Adesso che sappiamo come capire se i dati sono arrivati, come li sistemiamo al loro posto?Con l'uso di una funzione di callback!. Una funzione di callback è una funzione specializzata che viene passata come parametro a un'altra funzione (che invece è generica). Questo permette alla funzione generica di compiere un lavoro specifico attraverso la callback.

Nel nostro caso la funzione generica e' sendRequest(), mentre quella specializzata e' callBack(req).

In questo modo tramite la funzione di callback abbiamo un riferimento all'oggetto req ed ai dati ricevuti tramite la variabile req.responseText, potendo anche specificare di volta in volta una funzione specializzata diversa a seconda della richiesta asincrona che vogliamo compiere.

Supponiamo ad esempio di dover recuperare prima la lista delle citta' e poi la lista dei CAP.Se usassimo direttamente il collegamento req.responseText dentro la funzione generica per smistare i dati nel giusto contenitore dovremmo indicare sia la combobox delle citta' che quella dei CAP introducendo qualche parametro checi permetta di distinguere l'uno o l'altro nei due casi.

Invece se usiamo due funzioni di callback diverse (collegaCity e collegaCap) basta indicare nella chiamata quale callback usare - sendRequest(url, collegaCity) e sendRequest(url, collegaCap).
Dentro la funzione di callback poi collegheremo il testo ricevuto all'elemento (o agli elementi) del DOM opportuni:


supponiamo di avere un combobox vuoto con ID="cap", e di ricevere i seguenti dati come stringa HTML:


<option value ="20100" selected="selected">20100</option>
<option value ="20101">20101</option>
<option value ="20102">20102</option>
<option value ="20103">20103</option>


...e cosi' via. In questo caso collegheremo i dati al combobox in questo modo, sfruttando le funzioni di manipolazione del DOM:


//questa e' la funzione di callback per inserire i dati ricevuti
// in un combobox html <select id="cap"></select>
// Passiamo come parametro l'oggetto XMLHttpRequest inizializzato con la
// variabile req nella chiamata alla funzione
// sendRequest('destinazione.php?city=milano', collegaCap); che abbiamo usato per richiedere la lista dei CAP
// al web server remoto in modo asincrono
function collegaCap(req){

// req.responseText contiene la stringa ritornata dal webserver con la lista dei CAP
var dati=req.responseText;

// ora dobbiamo recuperare un riferimento al combobox usando il metodo document.getElementById()
var combo=document.getElementById('cap');

// inseriamo la stringa html ricevuta dal server come contenuto html interno al tag
// <select></select>
// il browser in questo modo aggiorna la pagina automaticamente
// mostrando il contenuto del nostro combobox con la lista dei CAP
combo.innerHTML=dati;
}

L'ultima domanda che ci poniamo e': come creiamo i dati?

Beh questo e' semplicemente programmazione lato server con un linguaggio dinamico, come facevamo prima.

Cioe' creiamo una pseudo-pagina web che agisca solo come fornitore dati, ovvero che quando viene chiamata restituisca i dati in formato html magari prelevandoli da un database oppure da un webservice.

Ecco fatto!Questo e' Ajax! Semplice, no?
Ma e' tutto qui? Si...in un certo senso.

E' come dire che abbiamo visto come costruire il mattoncino base dei Lego con cui costruire strutture megacomplesse!!!!!

Qual'e' il vantaggio di tutto cio'?

Beh se pensiamo che magari il nostro combobox e' dentro ad una pagina con centinaia di campi diversi che possono essere popolati dinamicamente "al volo" immaginiamo che poter avere a disposizione un meccanismo asincrono di popolamento dinamico "on demand" senza dover ognivolta ricaricare tutto quanto possa essere un modo molto efficiente di gestire la nostra pagina.E soprattutto molto piu' veloce, poiche' dover caricare solo pochi dati per volta piuttosto che centinai di KB per tutta la pagina puo' portare ad un risparmio di tempo considerevole ogni volta.


Questa e' solo una introduzione di base su come iniziare praticamente con Ajax.Resta inteso che occorre approfondire tutto quello che si e' detto qui con il tutorial Ajax vero e proprio, perche' si tratta solo di una introduzione che ha il solo scopo di incuriosirvi e di farvi capire cosa sia Ajax.

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 += ""}
}