Visualizzazione post con etichetta closure. Mostra tutti i post
Visualizzazione post con etichetta closure. Mostra tutti i post

mercoledì, gennaio 14, 2015

Biblioteca: visualizziamo la lista dei libri

Ora che l'architettura della nostra applicazione è ben delineata, risulta molto semplice aggiungere le funzionalità rimanenti decise in fase di analisi

Con l'approccio modulare visto fino ad ora, basta aggiungere due ulteriori moduli per
  • la gestione delle rotte (richieste) relative alle gestione dei libri (lista, ricerca,rimozione ...) in routes/libri.js
  • implementazione logica di business relativa alla gestione dei libri in servizi/serviziLibri.js
Il nostro diagramma degli oggetti si modifica in questo modo


Il menu dinamico in funzione del profilo dell'utente che ha effettuato o meno l'accesso, lo modifichiamo in questo modo
 if (!session.utente)  
      ul(class='nav nav-pils')  
           li(role='presentation')  
                a(href='/utente/login') Login  
           li(role='presentation')  
                a(href='/utente/registrazione') Registrati!  
 else  
      ul(class='nav nav-pils')  
           li(role='presentation')  
                a(href='/utente/logout') Logout  
                li(role='presentation')  
                     a(href='/libri') Lista Libri  
                li(role='presentation')  
                     a(href='/libri/cerca') Cerca Libro  
           if (session.utente.profilo === 'admin')  
                li(role='presentation')  
                     a(href='/libri/nuovo') Nuovo Libro  
Nel caso di utente che accede con il profilo semplice, attiviamo nel menu le rotte (link di richiesta)
  • /libri per visualizzare la lista dei libri
  • /libri/cerca per visualizzare un form di ricerca libro
Nel caso di utente che accede con il profilo admin, viene attivata anche la rotta
  • /libri/nuovo per visualizzare un form di inserimento di un nuovo libro
Il modulo app viene modificanto con l'aggiunta di un nuovo router legato all'indirizzo /libri
 var libri = require('./routes/libri');  
 ...  
 app.use('/libri', libri);  
Il modulo libri fornisce, per ora, la gestione della rotta radice
 var express = require('express');  
 var router = express.Router();  
 var serviziLibri = require('../servizi/serviziLibri');  
   
 router.route('/')  
      .get(function(request,response) {  
           var libri = serviziLibri.listaLibri();  
           response.render('listalibri', {libri : libri});  
      });  
   
 module.exports = router;  
e quindi la lista dei libri sarà visualizzata a seguito della richiesta all'indirizzo /libri utilizzando la nuova vista presente in /views/listalibri.jade
 extends ./layout.jade  
   
 block titolo  
      title Lista Libri  
   
 block testata  
      h1.text-info Lista Libri!  
   
 block contenuti-principali  
      if (libri)  
           table(class='table table-striped table-bordered')  
                thead  
                tr  
                     th Codice  
                     th Titolo  
                     th Autore  
                each libro in libri  
                     tr  
                          td #{libro.codiceisbn}  
                          td #{libro.titolo}  
                          td #{libro.autore}  
Il modulo serviziLibri sarà così implementato, sempre utilizzando la tecnica delle funzioni definite ed eseguite immediatamente
 module.exports = (function(){  
      var libri = [  
           {  
                titolo : 'Il signore degli anelli',  
                codiceisbn: 'DRF00223Z',  
                autore: 'John Ronald Reuel Tolkjen'   
           },  
           {  
                titolo : 'Guerra e pace',  
                codiceisbn: 'FTGR44E3',  
                autore: 'Lev Tolstoj'   
           },  
           {  
                titolo : 'Javascript the good parts',  
                codiceisbn: 'ZZ3423DR665',  
                autore: 'Douglas Crockford'   
           }  
      ];  
   
      var listaLibri = function() {  
           return libri;  
      };  
   
      return {  
           listaLibri : listaLibri  
      };  
 })();  
Come si può osservare, continuiamo ancora ad utilizzare una base dati in memoria, tra qualche post sostituiremo il codice presente il questo modulo con quello relativo alla interazione con una base dati.

Nel prossimo post, vedremo come implementare la ricerca, ma ormai dovrebbe essere chiaro, spero, come sia strutturato il codice.

Su https://whispering-peak-1284.herokuapp.com/  potete testare le funzionalità appena implementate.

Come al solito, ho aggiornato su github il progetto installato su heroku, in modo che possiate avere sempre tutto il codice a disposizione, in questo post è spiegato come utilizzare github.

Dopo aver fatto il pull del progetto, ricordatevi di eseguire, se non lo avete mai fatto, npm install nella cartella di progetto;

Se volete eseguire il programma in locale digitate node app.js e puntate il browser all'indirizzo http://localhost:5000.

Alla prox
Ivan

lunedì, dicembre 15, 2014

Stream e pipe (con una p ...)

Con questo post, vorrei approfondirei con voi i concetti di stream e pipe presenti nella piattaforma node.js

Uno stream non è altro che un flusso di dati dinamico: può essere una sorgente di dati, una destinazione di dati o entrambi.

Una pipe è un condotto che unisce una sorgente dati ad una destinazione dati, preoccupandosi di adattare le eventuali differenti velocità di elaborazione dati delle sorgenti e delle destinazioni.


Il concetto di Stream è un'astrazione: i dati veri e propri possono risiedere in un file così come provenire da un server, l'utilizzo degli stream e pipe è identico indipendentemente dal tipo di sorgente/destinzaione dati.

In pratica uno stream è un emettitore di eventi, che implementa particolati metodi: a seconda dei particolari metodi implementati si parla di Readables Stream, Writable Streams e Duplex

Readable Streams
Uno stream di lettura ci permette di leggere dati da una sorgente. La sorgente puo' essere qualsiasi cosa: un file sul file system, un buffer in memoria o ancora un altro stream.
Per leggere da uno stream basta mettersi in ascolto dell'evento data emesso dai stream di tipo readable.
 var fs = require('fs');  
 var streamLettura = fs.createReadStream('file.txt');  
 var data = '';  
 
 streamLettura.setEncoding('utf-8');
 
 streamLettura.on('data', function(chunk) {  
   data+=chunk;  
 });  
    
 streamLettura.on('end', function() {  
   console.log(data);  
 });  
In questo caso, la sorgente dati è costituito da un file.

Inizialmente lo stream è in uno stato statico ma non appena attacchiamo una funzione di callback all'evento data i dati cominciano a fluire fino a che terminano e lo stream emette l'evento end che possiamo intercettare con un'altra funzione di callback.

Se questo codice vi ricorda la soluzione a questo esercizio, siete sulla buona strada per capire la flessibilità di questo meccanismo: indipendentemente dal tipo di sorgente dati, la gestione dei dati viene sempre fatta allo stesso modo; l'oggetto response, in questo caso, è uno stream readable la cui sorgente dati è un socket di rete.

Di default, i dati letti da uno stream sono un oggetto di tipo Buffer, impostando il corretto encoding (ad es. utf-8), i dati vengono passati alla funzione di callback in formato stringa.

Writable Streams
Uno stream di scrittura ci permette si scrivere dati su una destinazione. Supponiamo di voler copiare un contenuto di un file in un altro file
 var fs = require('fs');  
 var readableStream = fs.createReadStream('file.txt');  
 var writableStream = fs.createWriteStream('file2.txt');  
    
 readableStream.setEncoding('utf8');  
    
 readableStream.on('data', function(chunk) {  
   writableStream.write(chunk);  
 });  
Semplice e intutitivo.

Il metodo write ritorna true o false a seconda dell'effettiva elaborazione dei dati in scrittura, se ritorna false, significa che dati aggiuntivi vengono memorizzati in memoria fino a che lo stream di scrittura non diventa di nuovo pronto: uno stream di scrittura emette l'evento drain per segnalare che è di nuovo pronto ad accettare dati senza bufferizzarli.

Se abbiamo dunque uno stream di lettura veloce ed uno stream di scrittura lento, la soluzione vista sopra potrebbe non essere efficiente perchè i dati emessi dalla sorgente verrebbero parcheggiati in un buffer in memoria, rallentando l'elaborazione dei dati; per rendere efficiente una elaborazione di questo tipo si utilizzano le pipe

Pipe
Vedetelo come un condotto che unisce sorgente e destinazione, che si preoccupa di far arrivare i dati a destinazione quando è effetivamente pronta a riceverli: anche in questo caso l'utilizzo è semplice e intuitivo
 var fs = require('fs');  
 var readableStream = fs.createReadStream('file1.txt');  
 var writableStream = fs.createWriteStream('file2.txt');  
    
 readableStream.pipe(writableStream);  
L'esempio copia i dati di file1.txt in file file2.txt, creandolo se non esiste.

Duplex
Sono stream che si comportano sia some sorgente che destinazione di dati: pensate ad esempio ad un socket di connessione TCP.
Immaginate un servizio in ascolto su una porta che gestisce un socket che è fullduplex: può ricevere dati e mandare dati sul socket. Sembra comlesso vero? Vediamo come implementare un server TCP di questo tipo con node.js
 var net = require('net');  
 var server = net.createServer(function(socket) {  
    console.log('server connesso');  
    socket.on('end', function() {  
       console.log('server disconnected');  
    });  
    socket.write('Benvenuto dal server eco ;))\r\n');  
    socket.on('data', function(data) {  
       console.log('ricevuti ' + data);
    });  
    socket.pipe(socket);  
 });  

 server.listen(8000, function() {  
    console.log('server in esecuzione su porta 8000');  
 });  
   

Per implementare un server TCP abbiamo bisogno del modulo net.

Creiamo un server con net.createServer() fornendo la funzione di callback che verrà eseguita ogni volta che un client si connette al nostro server.

Con il metodo listen mettiamo in ascolto il servizio sulla porta specificata come argomento.

La funzione di callback riceve un oggetto di tipo stream duplex su cui il server puo' leggere e/o scrivere i dati, secondo il protocollo che si vuole mettere in piedi.

In questo caso, per ogni connessione effettuata da un client, il server saluta e aspetta dati dal client che vengono immediatamente ritornati al client stesso tramite l'operazione di pipe effettuata da sorgente (socket) e destinazione (sempre socket!).

Per testare l'applicazione, eseguite il programma con node programma.js, il server si metterà in ascolto sulla porta 8000, per terminare il server digitare ctrl + c nella console.


Aprite un'altra console e digitate telnet localhost 8000, sulla console apparirà il saluto del server, se provate a digitare un qualsiasi carattere, vi verrà immediatamente rispedito indietro dal server che traccerà sulla sua console i dati elaborati, grazie alla gestione dell'evento data sul socket sorgente.

Nel prossimo post vedremo come implementare un server http, in pochi passi saremo in grado di implementare complesse applicazioni web con javascript ;)

Ho aggiornato su github il progetto, effettuate un git pull per aggiornare i sorgenti che avete in locale, come mostrato in questo post

Come al solito, per dubbi e/o domande: commentate!

Alla prox.
Ivan

giovedì, dicembre 04, 2014

Esercitazioni su node.js: esercizio 7

Testo dell'esercizio
Giusto per ribadire il concetto di stream che genera eventi asincroni da gestire, scrivere un programma, come il precedente,  per effettuare una richiesta HTTP di tipo GET all'indirizzo http://cspnet.it, questa volta però, visualizzare tutti i dati della risposta solo quando la richiesta è terminata.
Come ulteriore vincolo, l'unica variabile globale, (a livello di intero programma, esterna a qualsiasi funzione) deve essere solo la variabile var http = require('http');

Suggerimenti
Ricordate che l'evento data viene generato piu' volte, al termine della visualizzazione della risposta (che sarà ovviamente la pagina html home del sito di csp), provate infatti a stampare il numero di volte che è stato emesso l'evento data.
Per il vincolo richiesto, non scordate le IIF

Buon lavoro!

Una possibile soluzione
 var http = require('http'),  
   i = 0;  
   
 (function() {  
      var totalData = "",  
          numeroChunk = 0;  
   
          http.get('http://cspnet.it', function (response) {  
             response.setEncoding('utf8');  
             response.on('data', function(data) {  
                totalData += data;  
                numeroChunk++;  
             });  
            response.on('error', console.error);  
            response.on('end', function() {  
                 console.log("FINITO: " +   
                        totalData +   
                       " NUMERO CHUNK " +   
                            numeroChunk);  
            });  
          });  
 })();  
Il prossimo post verterà sul potente concetto di stream in node.js.
L'oggetto response vedetelo come un flusso di dati che viene alimentato dal server e letto dal client quando i dati sono disponibili.

Per motivi di efficienza, i dati non sono mandati in un unico blocco, l'evento 'data' dello stream response viene emesso più volte ed ogni volta viene eseguita la funzione di callback che accoda i dati ricevuti nella variabile totalData e incrementa il contatore numeroChunk.

Quando non ci sono più dati, lo stream emette l'evento end, che ci permette di definire una funzione di callback che stampa a video i dati finali.

Ho aggiornato su github il progetto, effettuate un git pull per aggiornare i sorgenti che avete in locale, come mostrato in questo post

Come al solito, per dubbi e/o domande: commentate!

Alla prox.
Ivan


martedì, dicembre 02, 2014

Esercitazioni su node.js: esercizio 6

Ora i duri cominciano a giocare.

Testo dell'esercizio
Scrivere un programma per effettuare una richiesta HTTP di tipo GET ad un URL passato al programma come primo parametro dalla linea di comando.
Scrivere sulla console il contenuto testuale della rispota HTTP.

Suggerimenti
Per questo esercizio utilizzerete il modulo http, in particolare il metodo get(), presente nella distribuzione di base: la documentazione la potete trovare a questo link

La funzione di callback è di questo tipo function callback(response) ... dove response è un oggetto che rappresenta uno Stream in node.js: prossimamente approfondiremo il concetto di Stream, per ora ci basti sapere che uno Stream è un flusso di dati che puo' emettere particolari eventi gestibili da nostre funzioni di callback.
Gli eventi piu' importanti da gestire sono "data" , "error" e "end".
Ad esempio response.on("data", function(data) {}) ci permette di processare parzialmente i dati della risposta, non appena disponibili, in maniera asincrona: l'oggetto response puo' emettere più eventi "data" a seconda della dimensione della risposta effettiva del server.

L'oggetto response ha anche un metodo setEncoding(...) che ci permette di trattare il parametro data, ricevuto dalla funzione di callback degli eventi emessi dall'oggetto responsedirettamente come una stringa nella codifica scelta (usate 'utf-8'). 

Buon Lavoro!

Una possibile soluzione
Più semplice del previsto, grazie ai moduli a disposizione della piattaforma node.js
 var http = require('http');  
   
 http.get(process.argv[2], function (response) {  
  response.setEncoding('utf8');  
  response.on('data', console.log);  
  response.on('error', console.log);
  response.on('end', function() {console.log("FINITO ... RICHIESTA CONCLUSA");});
 });  
Invochiamo il metodo get del modulo http passando come parametro la funzione di callback che gestisce la risposta.

La risposta è un oggetto che rappresenta uno stream di dati che emette eventi che hanno un nome: data,error, end.

L'evento data viene emesso ogni volta che c'è un blocco di dati disponibile per l'elaborazione, (ricordate che ora stiamo programmando in maniera asincrona).

L'evento error viene emesso ogni qual volta c'è un errore nella comunicazione con il server.
L'evento end viene emesso quando la richiesta è conclusa.

Notate che il secondo parametro del metodo on, nel caso di evento data ed error, deve essere una funzione che accetterà come parametri i dati effettivi ottenuti dal server: possiamo direttamente passare la funzione console.log che si occuperà di stampare a video i dati o i messaggi di errore.

Ho aggiornato su github il progetto, effettuate un git pull per aggiornare i sorgenti che avete in locale, come mostrato in questo post

Come al solito, per dubbi e/o domande: commentate!

Alla prox.
Ivan



Impariamo a definire i nostri ravioli .. ehm ... moduli in node.js

Node.js è stato disegnato all'insegno della modularità.
Abbiamo già notato come esista una pletora di moduli utilizzabili da un programma node.js:
basta utilizzare l'istruzione require('nomemodulo') per utilizzare le funzionalità esportate dal modulo.

La piattaforma provvede una serie di moduli inclusi nella distribuzione di base, abbiamo visto ad esempio l'utilizzo del modulo fs o del modulo path

In questo post impariamo a definire i nostri moduli da utilizzare nei nostri programmi, per cercare di programmare utilizzando ravioli e non spaghetti

Una volta definito un modulo, è anche possibile pubblicarlo sul repository ufficiale dei moduli gestiti da npm.

Date un'occhiata alla lista ufficiale di moduli per node disponibili attualmente.

Se vi piace un modulo, basterà eseguire in una shell il comando npm  <nomemodulo> nella cartella radice del progetto che state sviluppando, per poterlo utilizzare con una istruzione di require('nomemodulo') dal vostro codice.

Ad esempio, supponete di aver bisogno della funzionalità di trasformazione dei vostri oggetti json in formato xml: basterà utilizzare il modulo di terze parti che si chiama xml registrato sul repository ufficiale.

Vi posizionate nella cartella del vostro progetto e digitate npm install xml: verrà creata una directory node_modules, all'interno del vostro progettoche contiene i sorgenti del modulo, se un modulo dipende da altri moduli, questi verranno a loro volta scaricati in locale, tutto in maniera semplice e trasparente.

Ogni modulo ufficiale ha una pagina di documentazione come questa in cui ci sono le informazioni su come utilizzarlo.

In questo caso basta scrivere del codice di questo tipo
 var xml = require('xml');  
   
 var xmlString = xml({libri :   
      [  
           {  
                libro: [  
                     {titolo : 'Il signore degli anelli'},   
                     {autore : 'Tolkien'}  
                ],  
           },  
           {  
                libro: [  
                     {titolo: 'Guerra e pace'},  
                     {autore: 'Tolstoj'}  
                ]  
           }  
      ]  
 }, true);  
   
 console.log(xmlString);  
Per ottenere sulla console
 <libri>  
   <libro>  
     <titolo>Il signore degli anelli</titolo>  
     <autore>Tolkien</autore>  
   </libro>  
   <libro>  
     <titolo>Guerra e pace</titolo>  
     <autore>Tolstoj</autore>  
   </libro>  
 </libri>  
I singoli moduli devono essere definiti in un file, il cui nome sarà il nome del modulo, all'interno del file si deve utilizzare una istruzione del tipo module.exports = {}: assegniamo alla proprietà export dell'oggetto globale module un qualsiasi oggetto che definiremo noi, magari una iife per incapsulare dettagli implementativi mediante il meccanismo delle closure.

Facciamo un esempio.
Immaginiamo di implementare un modulo biblioteca che ci permetta di salvare in memoria dei libri e di ottenere la lista attuale dei libri.

Il programma principale utilizza il modulo biblioteca
 var biblioteca = require('./biblioteca');  
   
 biblioteca.inserisciLibro({  
      titolo : "Il signore degli anelli",  
      autore : "Tolkien"  
 });  
   
 biblioteca.inserisciLibro({  
      titolo : "Guerra e pace",  
      autore : "Tolstoj"  
 });  
   
 biblioteca.elaboraLibri(function(error, libri) {  
      if (error)  
           console.log(error.messaggio);  
      else {  
           libri.forEach(function(libro) {  
                console.log("Titolo: " +   
                          libro.titolo +   
                          "- Autore: " +   
                          libro.autore);  
           });  
      }  
 });  
Il modulo biblioteca dovrà essere implementato in un file dal nome biblioteca.js presente nella stessa cartella del file programma.js che rappresenta il programma principale.
 module.exports = (function() {  
      var libri = [],  
          inserisciLibro = function(libro) {  
             libri.push(libro);  
          },  
          elaboraLibri = function(callback) {  
             if (libri.length === 0)  
                callback({messaggio: 'biblioteca vuota'}, null);  
             else  
                callback(null, libri);       
          };  
          
          return {  
             inserisciLibro : inserisciLibro,  
             elaboraLibri : elaboraLibri  
          };  
 })();  
Notate la prima istruzione che assegna a module.exports il risultato di una funzione che viene eseguita immediatamente, tale risultato è un oggetto che espone solo i metodi inserisciLibro ed elaboraLibri.

Notate anche l'utilizzo di una funzione di callback definita in programma.js e passata come argomento a eleboraLibri, tale funzione di callback verrà eseguita solo se non ci sono condizioni di errore (in questo caso rappresentate da una biblioteca vuota).

Ho aggiornato su github il progetto, effettuate un git pull per aggiornare i sorgenti che avete in locale, come mostrato questo post.
Ricordatevi, per il progetto esempio-xml, di posizionarvi nella cartella radice ed eseguire npm install xml per includere il modulo xml nel progetto.

Se avete dubbi e/o domande, commentate!

Alla prox.
Ivan


sabato, novembre 22, 2014

"this" questo sconosciuto

Uno dei problemi principali che un programmatore java affronta nello studiare javascript, è capire a cosa punta il benedetto reference this.

In java è semplice, ho una classe con proprietà e metodi, istanzio un oggetto di quella classe, this rappresenta l'oggetto istanziato su cui si invocano i metodi.

In javascript non è così ovvio.

Nel contesto globale, this rappresenta l'oggetto globale window


 <script>  
   var f = function() {  
          console.log(this);  
        };  
      f();  
 </script>  

In una funzione costruttore, che deve essere invocata con l'operatore new, this rappresenta l'oggetto che verrà creato e restituito al termine dell'esecuzione della funzione stessa.

 var Auto = function(colore, cilindrata) {  
           this.colore = colore;  
           this.cilindrata = cilindrata;  
          };  
   
 var ferrari = new Auto("rossa", 6000);  

Ma provate a dimenticarvi dell'operatore new, inaspettatamente this ritorna a puntare a window.

Se dichiarate un oggetto con una proprietà di tipo funzione, tale funzione viene chiamata metodo dell'oggetto e this punta all'oggetto stesso.

 var auto = {  
    colore : "rosso",  
    cilindrata: 1500,  
    motore: "spento",  
    accendi: function() {  
             this.motore = "acceso";  
           },  
    spegni: function() {  
            this.motore = "spento";  
          }  
 };  

Provate ad omettere this nei corpi delle funzioni, vi ritroverete con una nuova proprietà motore nell'oggetto globale window, senza alcun tipo di errore in fase di esecuzione.

Fin qui, tutto sommato ce la caviamo, date un'occhiata al prossimo esempio e provate a capire a cosa punta this

 var esempio = {  
     nome: "ivan",  
     metodo: function() {  
              function inner() {  
               alert(this.nome);  
              }  
             alert(this.nome);  
             inner();  
            }  
 };  

Metodo è una proprietà dell'oggetto esempio, di tipo funzione, quindi this punta all'oggetto stesso, l'alert della funzione esterna stampa "ivan".

Nella funzione metodo, è definita una funzione interna (inner function), sarebbe molto naturale credere che il this della inner function punti allo stesso oggetto esempio, ma non è così.

Le inner function non ricevono il valore this dall'outer function.

Invocando inner(), l'alert della inner function stampa "undefined", perchè this punta a window nella inner function.

Per ovviare a questo problema, sfruttando il concetto di closure, l'esempio precedente lo riscriviamo in questo modo:

 var esempio = {  
     nome: "ivan",  
     metodo: function() {  
              var that = this;  
              function inner() {  
               alert(that.nome);  
              }  
             alert(this.nome);  
             inner();  
            }  
 };  

Memorizziamo il valore this in una varibile locale that, poichè le inner function hanno accesso alle varibili locali delle outer tramite il meccanismo delle closure, questa volta anche il secondo alert stamperà, come voluto, il valore "ivan".

Spero che ora, non abbiate piu' dubbi nell'affrontare i vari this e that :)

Alla prox.
Ivan


martedì, novembre 18, 2014

Un pò di pratica

Non c'è niente di meglio che un pò di pratica per capire i concetti esposti fino ad ora.

Per l'occasione ho creato un progetto su GitHub, dove metterò di volta in volta tutto il materiale necessario per eseguire gli esempi che proporrò.

Git è un sistema di condivisione sorgenti distribuito.

Ognuno di voi potrà installare sulla sua macchina il repository dei sorgenti eseguendo un semplice comando da una shell, a patto di installare git per il proprio sistema operativo.

Se non usate ancora git, vi consiglio di provarlo, poi non potrete farne a meno.

Scaricatelo e installatelo seguendo questo link e scegliendo il pacchetto d'installazione in base al sistema operativo utilizzato.


  • Aggiungete al path di sistema la cartella bin presente nel percorso d'installazione
  • Create una cartella vuota che conterrà tutti gli esercizi che vi proporrò.
  • Aprite una shell nuova e posizionatevi nella cartella appena creata.
  • Eseguite il comando:
    git clone https://github.com/ivansaracino/passionejavascript.git


Il comando precedente copierà nella vostra cartella la cartella passionejavascript

Quando aggiornerò il repository remoto, vi basterà invocare il comando

  • git pull

dalla cartella radice passionejavascript.

Nella cartella passionejavascript troverete la cartella modulerevealedpattern contenente la soluzione al seguente esercizio:

Creare una pagina web con due bottoni, start e stop.
Appena caricata la pagina, il bottone start è abilitato, il bottone stop è disabilitato.
Quando premete il bottone start, dovrà partire un contatore, che si aggiornerà ogni secondo; a questo punto il bottone start diventa disabilitato e il bottone stop abilitato.
Quando premete il bottone stop, il contatore si arresterà e così via.

L'esempio non è particolarmente utile ma mostra l'utilizzo del module revealed pattern,  un design pattern in javascript, che ha lo scopo di esporre sullo scope global un unico oggetto, sfruttando il concetto di closure e IIFE che abbiamo visto in questo post

Per eseguire la soluzione vi basta aprire il file index.html in un browser.

Notate come nello scope globale ci sia solo la variabile modulo  che espone le funzioni start() e stop() legate ai click dei rispettivi bottoni presenti nella pagina html.

Tutti i dettagli implementativi sono nascosti nella IIFE.

Se avete dubbi o problemi, non fate i timidi: chiedete commentando :))

Alla prox
Ivan

sabato, novembre 15, 2014

Spaghetti o Ravioli?

Siamo ora pronti a comprendere il codice presentato nel primo post, lo riscrivo qui per comodità

  var modulo = (function() {  
                   var f1 = function() {  
               
                            },  
                       f2 = function() {  
                 
                            },  
                       f3 = function() {  
                 
                            };  
               
                       return {  
                         f1 : f1,  
                         f2 : f2,
                         f3 : f3
                       };   
                })();  
   

Cosa contiene la variabile modulo? Contiene il risultato della esecuzione immediata della funzione dichiarata a destra, tale funzione ritorna il seguente oggetto


 {  
   f1 : function() {},  
   f2 : function() {}  
 }  
   
   

e quindi window.modulo espone le uniche proprietà f1 e f2 che in questo caso sono funzioni.

Le inner function, cioe le funzioni dichiarate all'interno di un'altra funzione, hanno l'importante proprietà di poter accedere alle variabili locali e i parametri dell'outer function anche quando l'outer function ha terminato la sua esecuzione.

Questa caratteristica, chiamata closure, insieme al concetto di  IIFE: Immediate Invocation Function Expression,  acronimo pomposo per descrivere una funzione che viene invocata appena subito dopo la sua dichiarazione, permette di creare degli oggetti che espongono solo le funzioni che vogliamo, nascondendo i dettagli implementativi; ciò ci permette di modularizzare il nostro codice, passando da un codice a spaghetti a un codice a ravioli :)

Facciamo ancora un esempio:

 var biblioteca = (function(nome) {  
        
      var libri = [],  
       
           nomeBiblioteca = nome,  
          
           inserisciLibro = function(libro) {  
               libri.push(libro);  
           },  
          
           getNomeBiblioteca = function() {  
               return nomeBiblioteca;  
           },  
          
           numeroLibri = function() {  
               return libri.length;  
           };  
   
          return {  
            insert : inserisciLibro,  
            getName : getNomeBiblioteca,  
            length : numeroLibri  
          };  
   
 })('civica di milano');  


 biblioteca.insert({titolo:'guerra e pace'});
 console.log(biblioteca.getName());
 console.log(biblioteca.length());

biblioteca è un oggetto, ritornato dalla funzione che viene immediatamente eseguita, espone dunque solo i tre metodi insert, getName, e length.

La variabile locale libri non è accessibile dall'esterno, essendo una variabile locale di una funzione,

I metodi dell'oggetto ritornato, possono ancora accedere alle variabili locali della funzione esterna, anche se la funzione esterna ha già finito la sua esecuzione: notate bene che in questo caso la eseguiamo immediatamente passandole il parametro 'civica di milano'.

Le linee di codice successive, memorizzano il libro dal titolo 'guerra e pace' nell'array libri, che esiste ancora nonostante la funzione precedente ha finito la sua esecuzione, stampano sulla console rispetivamente 'civica di milano' e 1.

Spero che ora non abbiate più dubbi sul potente concetto di closure, che ci permette di definire oggetti complessi nascondendo la loro complessita a chi deve utilizzarli.

Cominciate a utilizzare le IIFE e il vostro codice passerà da un groviglio di spaghetti a dei ravioli gustabili  uno ad uno :).

Alla prox.
Ivan

venerdì, novembre 14, 2014

Json e dintorni

Prima di affrontare il concetto chiave di closure, vediamo dunque di definire correttamente quello di oggetto in javascript.

Javascript non è tipizzato, le variabili dichiarate con la parola chiave var possono contenere numeri,stringhe,booleani (true o false), null e undefined.
I numeri, le stringhe e i booleani sono a tutti gli effetti degli oggetti, visto che hanno anche loro dei metodi, anche se sono oggetti immutabili.

Un oggetto è un contenitore di proprietà, dove  le proprietà, separate dalla virgola, hanno un nome ed un valore.
Una proprietà puo essere rappresentata da qualsiasi stringa, compresa la stringa vuota.
Il valore di una proprietà può essere un qualsiasi valore eccetto undefined

Gli oggetti sono utili per contenere e organizzare i dati, gli oggetti possono contenere altri oggetti e così possono rappresentare facilmente strutture ad albero o graphi.

Il modo standard di rappresentare gli oggetti in javascript è costituito dalle parentesi graffe aperte e chiuse che circondano zero o più coppie nome/valore.

 var oggetto_vuoto = {};  
   
 var dipendente = {  
   nome: "ivan",  
   cognome:"saracino"  
 };  
   
 var volo = {  
   compagnia: "alitalia",  
   numero: 345,  
   partenza: {  
    citta: "roma",  
    orario: "14/11/2014 13:30"  
   },  
   arrivo: {  
    citta: "milano",  
    orario: "14/11/2014 15:00"     
   }  
 };  

Ultimamente si usa il formato JSON (Javascript Standard Object Notation) per lo scambio dati tra compenenti distribuiti: il formato JSON nasce proprio dal linguaggio Javascript.

Per recuperare un valore da una proprietà di un oggetto

   
 // metodo standand di navigazione oggetti  
 volo.partenza.citta // roma  
   
 // metodo alternativo che mette in luce la natura di collezione associativa 
 // di coppie nome/valore  
 dipendente["nome"] // ivan  
   
 //proprieta noon presenti su oggetto  
 dipendente.nonesisto // undefuned  
   
 //tentativo di accedere a proprietà di proprietà inesistenti  
 dipendente.nonesisto.proprieta // eccezione a run time  

Per scrivere le proprietà di un oggetto

 // aggiornamento di una proprietà esistente  
 dipendente.nome = "ciccio";  
   
 // se una proprietà non esiste, viene creata  
 // dinamicamente!  
 dipendente.età = 43  

Le variabili che contengono gli oggetti sono dei puntatori agli oggetti in memoria.

Quando passiamo un oggetto come parametro, il passaggio del parametro è per copia  ( qui sono in disaccordo col buon Douglas Crockford che dice che il passaggio è by reference) come dimostrato dal seguente frammento di codice:

 var x = {p : 1};  
   
 var y = x;  
   
 y.p = 2;  
   
 x.p vale 2, stiamo modificando lo stesso oggetto   
 tramite lo stesso puntatore  
   
   
 definiamo una funzione che modifica la proprietà  
 di un parametro  
   
 var f = function(par) {par.p = 3};  
   
 invochiamo la funzione passando l'oggetto x  
   
 f(x);  
   
 x.p vale 3, nella funzione usiamo la copia   
 del puntatore x per modificare lo stesso oggetto in memoria  
   
 cerchiamo di cambiare l'oggetto puntato da par  
   
 var f = function(par) {par = {p:4}};  
   
 invochiamo nuovamente la funzione passando l'oggetto x  
   
 f(x);  
   
 x.p vale ancora 3, par è una copia del puntatore x,   
 copia che muore quando finisce la funzione f,  
 x punta all'oggetto originale  

In effetti il passaggio dei parametri è identico al linguaggio Java.

Ci sono altre considerazioni da fare sugli oggetti, ma credo sia opportuno dedicarci un post apposito.

Alla prox.
Ivan

E finalmente trovai gli oggetti

Nel primo post abbiamo visto come creare un modulo javascript rappresentato dalla variabile globale modulo che altro non è che un oggetto che espone le funzioni f1, f1, f3.

Qual è il vantaggio?

Invece di avere 3,4,5, ...N oggetti globali ne abbiamo uno solo e non si rischia, col tempo, di avere collisioni tra i nomi delle funzioni  (succede più spesso di quanto non si creda) generando errori difficili da risolvere.

Se avete letto con attenzione, dovrebbe risultare chiaro che, in javascript, le funzioni sono oggetti: possono essere memorizzate in varibile e possono essere passate come argomenti di altri funzioni.

Quando il browser carica una pagina html e incontra un nodo <script></script>, esegue immediatamente il codice presente in esso, il contesto di esecuzione dell'interprete javascript presenta un oggetto globale window  a cui vengono aggiunte dinamicamente le varie variabili definite nel nostro codice.

Riprendendo l'esempio del  primo post , l'oggetto window, dopo l'esecuzione della prima versione dell'esempio, presenta le nuove proprietà f1, f2,f3 il cui valori sono oggetti di tipo funzione.

Per provare l'affermazione precedente basta aprire una console javascript (F12 su qualsiasi browser moderno) e digitare window.f1, window.f2 o window.f3: la console mostrerà il valore delle tre proprietà f1,f2,f3 di window

Ecco il risultato prodotto dalla console:


Ovviamente per eseguire le funzioni, scriveremmo window.f1(); window.f2(); o window.f3(); oppure f1(); f2(); o f3() oppure this.f1(); this.f2(); this.f3(); il che significa che this = window nel contesto globale di esecuzione.

Nel secondo esempio,invece, verrà creata una unica proprietà window.modulo che questa volta è un oggetto che espone le tre funzioni per eseguire le quali scriveremo window.modulo.f1(); window.modulo.f2(); o window.modulo.f3();

Non ci stupirà allora che:

 var f1 = function() {  
                alert('f1');  
      };  
   
 Equivalente in tutto e per tutto a   
   
 function f1() {  
      alert('f1');  
 }  

La prima dichiarazione mette in risalto il concetto che le funzioni sono oggetti.

Avevo gli oggetti che cercavo sotto gli occhi e non lo sapevo :))

Alla prox.
Ivan


giovedì, novembre 13, 2014

All'inizio fu il caos ...

Non so quale sia stato il vostro primo approccio con il linguaggio javascript, io ho cominciato ad usarlo mettendo mano ad una vecchia applicazione web scritta in asp con un po' di controlli di validazione di un form.

Tanto per rendere l'idea:
 <html>  
      <head>  
           <script>  
                function f1() {  
   
                } 
   
                function f2() {  
   
                }
   
                function f3() {  
   
                } 
           </script>  
      </head>  
      <body>  
        <!-- eventi sul dom che scatenano le varie funzioni -->  
      </body>  
 </html>  
Nel mio caso le funzioni erano "solo" una ventina e già  faticavo a seguire il flusso di esecuzione del codice, impiegavo tanto tempo a fare le modifiche piu' banali.

Capite che non ci misi molto ad odiare javascript, soprattutto perchè abituato al rigore di un linguaggio ad oggetti tipizzato e compilato come Java.

Mi sentivo spaesato. Io che cercavo sempre di strutturare il codice in Java cercando di evitare codice a spaghetti, non sopportavo l'accozzaglia di funzioni e variabili globali sparse nella varie pagine html senza un minimo di struttura e di incapsulamento dei dati.

Insomma evitavo lavori di front end come la peste, ma con l'avvento del web2 e 3 e 4, a (proposito : a che punto siamo?) non potevo più nascondermi: dovevo prendere di petto il linguaggio e domarlo.

Avevo letto da qualche parte che Javascript  era comunque un linguaggio ad oggetti, ma io gli oggetti non li vedevo! Ma come si fa ad avere un oggetto senza una classe? Queste erano le mie perplessità, finchè, cercando sul web, non mi sono imbattuto in lui: Douglas Crockford e ho cominciato a scoprire le meraviglie del linguaggio.

Grazie a Douglas, l'esempio in alto ho cominciato a scriverlo così:
  var modulo = (function() {  
                   var f1 = function() {  
               
                            },  
                       f2 = function() {  
                 
                            },  
                       f3 = function() {  
                 
                            };  
               
                       return {  
                         f1 : f1,  
                         f2 : f2,
                         f3 : f3
                       };   
                })();  
   
ottenendo una unica variabile globale che incapsula le funzionalità del modulo, nascoste all'esterno come se fossero private :))

Per capire a fondo il precedente codice, bisogna padroneggiare i concetti di closure, IIFE, inner function che cercherò di illustrare nei prossimi post :))
Alla prox
Ivan