Introduzione

Nell'articolo [MOKAWEB2I] è stato introdotto il Web 2.0, parlando degli aspetti tecnologici e sociali, fornendo una visione introduttiva delle tecnologie relative al nuovo modo di intendere il Web. Nello scorso articolo [MOKAWEB2II] si è parlato di Wiki, lo strumento che maggiormente ha contribuito alla diffusione del Web 2.0. In questo articolo si inizierà a parlare specificamente delle tecnologie Web 2.0; l'argomento trattato sarà Ajax.

AJAX

AJAX è l'acronimo di Asynchronous JavaScript and XML:

Lo scopo principale di AJAX è di rendere i siti più usabili, interattivi e funzionali. Il termine AJAX non si riferisce a un'unica tecnologia, ma raggruppa un insieme di tecniche e tecnologie:

  • HTML e CSS per il markup e lo stile
  • DHTML per il contenuto dinamico
  • DOM (Document Object Model)
  • XMLHttpRequest, l'oggetto per l'interscambio asincrono dei dati tra browser e web server
  • XML come formato dati

Le applicazioni tradizionali Web basano il loro funzionamento su un protocollo Request-Response. L'utente effettua una Request verso il server a fronte di una specifica azione (click su un link, su un bottone, su un'immagine, etc.), il Server riceve i dati di un Form (via GET o POST), effettua una elaborazione dei dati, e invia al Client una nuova pagina Web che andrà a sostituire (o a ricaricare) la pagina precedente. Questo meccanismo è di tipo prettamente sincrono, vale a dire che il Client, per proseguire la sua elaborazione, deve aspettare che il Server abbia concluso l'elaborazione e preparato la pagina HTML di output.

AJAX introduce un'indirezione tra il Client e il Server per rendere la comunicazione più interattiva e performante. In sostanza AJAX disaccoppia la comunicazione tra Client e Server rendendo possibile una comunicazione tipo asincrono, vale a dire che il Client non interagisce direttamente con il Server ma lo fa invece con l'engine AJAX, che si occupa di dialogare con il Server. Il Client preleva i dati da un eventuale oggetto Form e li passa allo strato AJAX. Nell'interfacciarsi con l'engine AJAX, il Client specifica la funzione di callback associata a un determinato evento, al verificarsi del quale verrà richiamata dall'engine AJAX. In questo modo, l'utente non dovrà più attendere che la pagina venga ricaricata, ma semplicemente la vedrà cambiare davanti ai sui occhi contestualmente ai dati che sta immettendo nella Form.

Figura 1 - Confronto di applicazioni web tradizionali e applicazioni basate su AJAX
Figura 1 - Confronto di applicazioni web tradizionali e applicazioni basate su AJAX

L'idea alla base delle tecnologie AJAX è di sfruttare un particolare oggetto: l'XMLHttpRequest che è incluso nei browser (un tempo era disponibile solo come ActiveX in Internet Explorer). L'oggetto XMLHttpRequest consente di gestire il flusso dei dati tra il Client e il Server indirizzando la risposta del server su uno o più elementi della pagina stessa senza doverla ricaricare. Esemplificando quanto detto, si pensi di avere una pagina Web che deve gestire il controllo della validità sintattica dell'indirizzo email.

Figura 2 - Caso d'uso d'esempio
Figura 2 - Caso d'uso d'esempio

Con le tecnologie Web tradizionali si esegue una Post a una Servlet con l'indirizzo e-mail da controllare.

<FORM method="GET" ACTION="<%=request.getContextPath()%>/CheckEmailServlet">
  <tr>
    <td align="left">Email:</td>
    <td><input type="text" name="email" value="" size="16" onChange=checkEmail(this.form)></td>
    <td><div><jsp:getProperty name="viewBean" property="message"/></div></td>
  </tr>
  <tr>
    <td align="left">Press to submit:</td>
    <td><input type="submit" name="CONFERMA" value="CONFERMA"></td>
    <td></td>
  </tr>
  <tr>
    ...
  </tr>
</FORM>
public class CheckEmailServlet extends HttpServlet {
  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String nextPage = "";
    String emailParam = request.getParameter("email");
    boolean retValue = MailChecker.isEmailValid(emailParam);

    if (retValue) {
      nextPage = "/success.jsp";
    } else {
      ErrorViewBean errorViewBean = new ErrorViewBean("Email NON valida!");
      request.setAttribute("errore", errorViewBean);
      nextPage = "/emailForm.jsp";
    }

    // configurazione della Response HTTP
    response.setContentType("text/html");
    response.setHeader("Pragma", "No-cache");
    response.setDateHeader("Expires", 0);
    response.setHeader("Cache-control", "no-cache");

    // Dispatch della Next View
    RequestDispatcher rd = this.getServletContext().getRequestDispatcher(nextPage);
    rd.forward(request, response);
  }
}
public class NewCheckEmailServlet extends HttpServlet {
  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
    // configurazione della Response HTTP
    response.setContentType("text/xml");
    response.setHeader("Cache-Control", "no-cache");

    String emailParam = request.getParameter("email");
    boolean retValue = MailChecker.isEmailValid(emailParam);

    if (retValue) {
      response.getWriter().write("<valid>true</valid>");
    } else {
      response.getWriter().write("<valid>false</valid>");
    }
  }
}
<html>
  ...
  <script type="text/javascript">
function validate() {
  // Lettura del parametro email della FORM
  var email = document.getElementById("email");

  // URL del server da richiamare per la validazione
  var url = "NewCheckEmailServlet?email=" + encodeURIComponent(email.value);

  // Funzione che verra' richiamata al ritorno
  var callback = validateCallback;

  // Istanza XMLHttp
  var xmlhttp = false;

  // Se Mozilla, Firefox, Opera, Safari, ...
  if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
  // Se il browser e' Internet Explorer
  } else if (window.ActiveXObject) {
    try {
      // Versione 5.5 o inferiore
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      ...
      try {
        // Versione 5.5 o superiore
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) {
        ...
      }
    }
  }

  if (!xmlhttp) {
    alert("ERRORE: AJAX #NON# supportato dal tuo browser!");
    return false;
  }

  // Apro la connessione verso il server in modo asincrono
  xmlhttp.open('GET', url, true);
  alert("Validazione di " + email + " con URL:" + url + "... ");

  // Il valore di ritorno (la risposta della Servlet) viene intercettato da questa funzione
  xmlhttp.onreadystatechange = function () {
    // 4 significa che l'elaborazione e' stata completata
    if (xmlhttp.readyState == 4) {
      // Codice di ritorno del server 200 vuol dire OK
      if (xmlhttp.status == 200) {
        // Lettura valore di ritorno in formato XML
        if (callback) {
          // Esecuzione della funzione preposta
          callback(xmlhttp.responseXML);
        }
      } else {
        alert("ERRORE: Codice di ritorno=" + xmlhttp.status);
      }
    }
  }

  // Codifica dei campi secondo il MIME type corretto
  xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

  // Invio dei parametri
  xmlhttp.send('email=' + encodeURIComponent(email));
}
  </script>

Nel codice sopra riportato la funzione di callback che verrà invocata è function() e l'evento a cui è registrata è onreadystatechange, dell'oggetto XMLHTTPRequest:

xmlhttp.onreadystatechange = function () {
  ...
}

In tale funzione è presente il controllo sullo stato della connessione (xmlhttp.status); nel caso quest'ultimo corrisponda al valore 200 (comunicazione avvenuta con successo), viene richiamata la seguente funzione di validazione:

function validateCallback(responseXML) {
  var msg = responseXML.getElementsByTagName("valid")[0].firstChild.nodeValue;
  var mdiv = document.getElementById("message");

  if (msg == "false") {
    // email non valido
    mdiv.innerHTML = "Email NON valida!";
  } else {
    // email valida
    mdiv.innerHTML = "Email corretta";
  }
}
Figura 3 - Il traffico di rete dell'esempio proposto, sviluppato con Ajax
Figura 3 - Il traffico di rete dell'esempio proposto, sviluppato con Ajax

Nell'esempio riportato è stata implementata in modo esplicito la gestione della comunicazione Client/Server attraverso l'utilizzo di JavaScript e dell'oggetto XMLHttpRequest. L'utilizzo di JavaScript sulla pagina Web comporta una più difficile fase di debugging / test in fase di sviluppo, di monitoring e di manutenzione.

Esistono prodotti open source che permettono di nascondere i dettagli implementativi javascript agevolando quindi lo sviluppo. Lo stesso esempio verrà ora sviluppato mediante l'utilizzo di due differenti strumenti open source: DOJO e Google Web Toolkit. Il primo esempio utilizzerà le librerie JavaScript di DOJO, mentre il secondo usufruirà delle funzionalità del toolkit Google. In entrambi gli esempi, lato Server, verrà utilizzata sempre la stessa Servlet (NewCheckEmailServlet) precedentemente spiegata.

Figura 4 - Uno sguardo d'insieme all'esempio proposto: JavaScript, Libreria e Toolkit
Figura 4 - Uno sguardo d'insieme all'esempio proposto: JavaScript, Libreria e Toolkit

DOJO

Dojo [DOJO] è un toolkit Open Source DHTML scritto in JavaScript che permette la creazione di funzionalità dinamiche nelle pagine web e in ogni altro ambiente che supporti JavaScript. DOJO organizza le sue funzionalità su un sistema di package. Quando si costruiscono script con DOJO, si possono includere le sole API necessarie al proprio sviluppo.

L'utilizzo di DOJO semplifica quindi l'interfaccia con l'oggetto XMLHTTPRequest mediante apposite librerie (analogamente a quanto visto in [MOKAJAX_II] con la libreria Sarissa). Innanzi tutto è necessario importare la libreira JavaScript dojo.js all'interno della pagina.

<script type="text/javascript" src="dojo/dojo.js"></script>

Dopodichè possono essere specificati i moduli o package di riferimento: operazione, questa, non obbligatoria ma utile per "incrementare le perfomance di caricamento".

<script type="text/javascript">
  dojo.require("dojo.io.*");
  dojo.require("dojo.event.*");
</script>
function validate() {
  dojo.io.bind({
    method: 'get',
    url: 'NewCheckEmailServlet',
    content: {email:dojo.byId('email').value},
    load: validateCallback,
    error: handleError,
    mimetype: 'text/xml'
  });
}
<html>
  <head>
    <title>Wrapper HTML for Check Email</title>
    <meta name='gwt:module' content='it.kok.hello.Hello'>
  </head>
  <body>
    <script language="javascript" src="gwt.js"></script>
    <iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
    <h1>Moka Check Email</h1>
    <table align=center>
      <tr>
        <td id="slot1"></td>
        <td id="slot2"></td>
        <td id="slot3"></td>
      </tr>
    </table>
  </body>
</html>
public class Hello implements EntryPoint {
  // Metodo entry point
  public void onModuleLoad() {
    final Label label = new Label();
    final TextBox textBox = new TextBox();
    final Button button = new Button("Check Email");

    button.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (textBox.getText().equals("")) {
          label.setText("Introdurre Indirizzo e-mail!");
        } else {
          label.setText("");
          String url = "http://localhost:8080/sampleAjaxEmailWebApp/CheckEmailAjaxServlet?email="
            + textBox.getText();
          try {
            HTTPRequest.asyncGet(url, new ResponseTextHandler() {
              public void onCompletion(String responseText) {
                // Nella realtà, questo testo sarebbe sotto
                // forma di response RPC. Ma questa tecnica
                // è comunque ottima per test ed esempi!
                validateCallback(responseText);
              }

              private void validateCallback(String responseText) {
                Document customerDom = XMLParser.parse(responseText);
                Element customerElement = customerDom.getDocumentElement();
                String resultValue = getElementTextValue(customerDom, "valid");
                if (resultValue.equals("false")) {
                  // email non valido
                  label.setText("E-Mail NON VALIDA!!!");
                } else if (resultValue.equals("true")) {
                  // email valida
                  label.setText("E-Mail CORRETTA!!!");
                } else {
                  // email valida
                  label.setText(resultValue);
                }
              }

              private String getElementTextValue(Document parent, String elementTag) {
                NodeList list = parent.getElementsByTagName(elementTag);
                Node node = list.item(0);
                if (node != null) {
                  Node child = node.getFirstChild();
                  return child.getNodeValue();
                } else {
                  return parent.toString();
                }
              }
            });
          } catch(Exception e) {
            label.setText("Connection to : "+ url +" Refused!!!");
            e.printStackTrace();
          }
        }
      }
    });

    RootPanel.get("slot1").add(textBox);
    RootPanel.get("slot2").add(button);
    RootPanel.get("slot3").add(label);
  }
}

Il client invoca la Servlet mediante la classe HTTPRequest per poi effettuare il parsing della risposta mediante la classe XMLParser. In modo analogo a come si farebbe con una GUI AWT, vengono definiti gli oggetti grafici (in questo caso appartenenti alle librerie GWT) con relativa gestione degli eventi. Stesso discorso vale per l'oggetto HTTPRequest (che in GWT corrisponde a un wrapper dell'oggetto XMLHTTPRequest) alla cui chiamata asyncGet viene passato l'handler ResponseTextHandler(), il cui metodo astratto associato all'evento onCompletion viene ridefinito con l'invocazione della function di callback validateCallback(). In ultimo si procede al posizionamento di tali oggetti grafici. Tale operazione viene attuata associando gli oggetti grafici con gli ID definiti all'interno della pagina HTML, per poi passare all'elaborazione del compilatore per la generazione del layout finale.