LEZIONE VI - Oggetti "avanzati" di JS (parte B)



Nella prima parte abbiamo visto come creare degli oggetti personali che possono racchiudere funzioni, ovvero una nuova tecnica di programmazione avanzata.

A fine pagina ci sono i link per scaricare l'esempio spiegato in questa lezione e per vedere l'esempio in azione direttamente online.

Quello che vedremo di seguito ci permetterà i raggruppare funzioni generali e crearci i nostri pacchetti che poi utilizzeremo per qualsiasi realizzazione vogliamo fare.
Creandoci dei "moduli" generali, abbiamo anche bisogno di tenerli ordinati per poter poi utilizzare quello che a noi serve. Uno dei modi migliori per fare questo è utilizzare dei gruppi di moduli in base alla loro destinazione, ad esempio io uso (e lo vedrete negli esempi) dei gruppi ben distinti che sono:

- System per tuti i moduli generici che implementano degli elementi di sistema
- Utility per tutti i moduli che non hanno importanza "vitale" ma sono utili a certe funzionalità
- Widget sono i programmi non generici, ad esempio la chat

Per definire questi gruppi di oggetti, dobbiamo utilizzare una funzione denominata "namespace" che genera i gruppi che poi utilizzeremo.
Questa funzione non ha nulla da spiegare, vedrete che non fa altro che creare "oggetti di oggetti". E' importante che sia posta sempre come prima istruzione di qualsiasi script:

var myFunc = {};

myFunc.namespace = function() {
    var a = arguments, o = null, i, j, d;
    for ( i = 0; i < a.length; ++i ) {
        d = a[i].split(".");
        o = myFunc;

        for ( j = (d[0] == "myFunc") ? 1 : 0; j < d.length; ++j ) {
            o[d[j]] = o[d[j]] || {};
            o = o[d[j]];
        }
    }

    return o;
};

myFunc.namespace("System", "Utility", "Widget");

Dopo questo forse è più chiaro quello che era stato scritto nella parte precedente di questa lezione, ovvero myFunc.Widget.Chat oppure myFunc.Utility.Connect.asyncRequest(...).
Per entrare in dettaglio:

- myFunc e' il nome che assegno al mio gruppo principale di funzioni. Per esempio le YUI iniziano tutte con YAHOO; quindi e' un modo per identificare gli oggetti propri da quelli di altri sviluppatori
- il secondo valore e' il gruppo che abbiamo creato sopra con la funzione namespace
- il terzo valore e' il nome del nostro oggetto
- tutto quello che viene inserito dopo il terzo valore sono funzioni o valori intrinsici al nostro oggetto

Avendo fatto un po' più chiarezza su quello scritto anche nell parte 1 della lezione, vediamo in dettaglio come realizzare una delle Utility più importanti e che ci permettono di utilizzare AJAX: la connessione remota.
Il codice che vedremo è stato realizzato proprio per permetteci di operare con connessioni asincrone.

Abbiamo già visto nelle lezioni precedenti come funziona il modulo XMLHttpRequest e come implementarlo nelle nostre pagine con un paio di esempi concreti.
Nella parte 1 della lezione ho riscritto il codice della chat come oggetto "Widget" per racchiuderlo in un oggetto unico che possiamo utilizzare ovunque.
Nel codice scritto in certi punti vengono effettuate le connessioni remote: per prelevare i dati da visualizzare, per inserire il messaggio nella chat. Queste connessioni avvenivano richiamando un modulo "Utility" denominato myFunc.Utility.Connect e in particolare la sua funzione asyncRequest().

Ora vedrò di scrivervi il codice di questo oggetto spiegandovi qual'è la logica in modo che poi possiate anche voi cominciare a realizzare i vostri oggetti.
Nel nostro "namespace" abbiamo definito i gruppi di oggetti che useremo, quindi il nostro ogetto e' stato chiamato "Connect" e dichiarato come segue:

myFunc.Utility.Connect = {
...
}

La prima parte del nostro oggetto serve per specificare delle variabili che verranno utilizzate nelle varie funzioni; è buona norma dichiararle sempre in testa all'oggetto, almeno si sa che quando si cerca una variabile si trova li! occhiolino
Il dichiarare delle variabili nell'oggetto, comporta che queste siano globali per tutte le funzioni del nostro oggetto. Non è necessario dichiarare variabili per utilizzarle nell'oggetto, ma solo se queste devono avere un valore prefissato iniziale:

    transId : 0,
    poll : {},
    pollInterval : 50,
    msXml : [
        'MSXML2.XMLHTTP.3.0',
        'MSXML2.XMLHTTP',
        'Microsoft.XMLHTTP'
    ],
    defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',

Se siete degli osservatori noterete che le variabili msXml e defaultPostHeader contengono dei valori che abbiamo visto negli esempi scorsi. La prima è un array che contiene i tipi di connessioni ActiveX per il modulo XMLHttpRequest di Internet Explorer, la seconda e' l'header standard per le connessioni POST.
Per completare la serie, diciamo che transId è un contatore che si incrementa di 1 ogni volta che viene generata una connessione verso il server... poll è un oggetto che conterrà le nostre transazioni verso il server... pollInterval è il tempo in millisecondi per cui il nostro programma deve verificare se ci sono risposte dal server.

La parte successiva che analiziamo è l funzione che viene richiamata per effettuare la connessione

asyncRequest : function (method, url, callback, postData) {
    var obj = this.init();
    if ( ! obj ) {
        alert("Impossibile effettuare una connessione");
        return null;
    } else {
        obj.conn.open(method, url, true);
        
        if ( method == 'POST' ) {
            obj.conn.setRequestHeader('Content-Type', this.defaultPostHeader);
        }
        
        this.handleReadyState(obj, callback);
        obj.conn.send(postData || null);

        return obj;
    }
},

Questa funzione, come si può vedere, accetta 4 parametri che rispettivamente indicano:

- metodo di connessione GET o POST
- indirizzo della pagina sul server da richiamare
- funzione da richiamare una volta ricevuta la risposta dal server
- nel caso il metodo sia POST, i parametri da passare al server (es. "id=34&testo=ciao&...")

Perchè si è usata una funzione "callback"? Dato che questo oggetto è generico e può esere usato con qualsiasi altro nostro programma, non è corretto inserire al suo interno le procedure che deve esguire una volta ricevuti i dati dal server. Ogni programma ha le sue procedure, quindi deve essere il programma stesso che gestisce i dati ricevuti dalla nostra funzione di comunicazione.

Addentrandoci nelle righe di codice, vediamo che la prima cosa che fa è richiamare una funzione propria dell'oggetto init() che non fa altro che inizializzare la nostra connessione creando un oggetto indipendente da altre connessioni (la funzione è presente nello ZIP a fondo pagina).

Continuando con il nostro codice, la seconda istruzione verifica che l'inizializzazione della connessione restituisca l'oggetto, in caso contrario è avvenuto qualcosa che non ci permette di connetterci. Passiamo direttamente al caso positivo, ovvero l'avvenuta connessione; viene richiamata la nostra funzione che invia la richiesta al server obj.conn.open(method, url, true); dove il terzo parametro indica "connessione asincrona".
Se la nostra connessione usa il metodo POST verranno inviati anche gli header appropriati, in caso contrario no.

La funzione successiva this.handleReadyState(obj, callback); è molto importante e ci permette di gestire l'asincronicità delle connessioni:

handleReadyState : function(obj, callback) {
    var oConn = this;

    this.poll[obj.tId] = window.setInterval(
        function () {
            if ( obj.conn && obj.conn.readyState === 4 ) {
                window.clearInterval(oConn.poll[obj.tId]);
                delete oConn.poll[obj.tId];

                oConn.handleTransResponse(obj, callback);
            }
        }
    ,this.pollInterval);
},

Prima di tutto creo una varibile che contiene il nostro oggetto globale poi inizio con la verifica vera e propria della risposta del server; con this.poll[obj.tId] = window.setInterval( praticamente viene creata una funzione richiamata ogni ,this.pollInterval); millisecondi e appunto inserita nel nostro oggetto poll[id connessione]. In questo modo ogni connessione effettuata verso il server è memorizzata in un suo oggetto cosicché quando il server risponde, sappiamo che quella risposta è di un determinato oggetto.
Tutto questo perchè avendo una connessione asincrona, il programma non aspetta di avere una risposta prima di effettuare una seconda connessione (o terza, o quarta, ...) e quindi sarebbe impossibile sapere cosa fare quando il server risponde (potrebbe essere che il server risponda prima alla seconda richiesta che alla prima). Con gli oggetti, invece, abbiamo sempre un riferimento e i dati che ci servono quando il server risponde, potendo associare tale risposta ad un oggetto specifico che contiene il nostro "callback".

La condizione presente nella funzione obj.conn && obj.conn.readyState === 4 verifica solo che la connessione sia valida e che il responso del server sia 4 (completato). Fino a che non c'è questa condizione l'oggetto "poll" rimane in attesa di una risposta.
Una volta ottenuta la nostra riposta, liberiamo un po' di risorse di sistema eliminando l'intervallo di verifica per l'oggetto "poll" e poi eliminando l'oggetto stesso; per ultimo richiamiamo la funzione oConn.handleTransResponse(obj, callback); che si preoccupa di prendere i dati ricevuti dal server e richiamare le varie funzioni interne in caso di errore, o in caso affermativo la funzione "callback".
Capire il funzionamento di quest'ultima funzione è molto semplice e vi invito a leggere il codice direttamente nello ZIP a fondo pagina.

Le parti più importanti del nostro oggetto "Connection" sono state spiegate, se poi leggendo il odice completo avete dei dubbi, potete sempre lasciare un messaggio nel forum.

L'esempio associato a questa parte della lezione 6 è sempre la nostra famosa chat vista nella lezione 4. Ho solamente variato i colori del CSS, mentre la funzionalità rimane invariata, ma scritta interamente con il nuovo codice.
Come sempre dai link qui sotto potete avere sia il codice che l'esempio on-line:

Guarda l'esempio on-line della chat

Scarica lo ZIP dell'esempio

Per utilizzare l'esempio in locale bisogna avere installato e funzionante: web server (Apache o simili), motore PHP, database MySQL.
N.B. la creazione del database per questo esempio è specificato nella lezione 4.

Alla prossima lezione!