In questo articolo introdurremo Apache Wicket, un web framework orientato alla programmazione per componenti, che permette di realizzare applicazioni web con una interfaccia accattivante e decisamente "desktop oriented". Come vedremo infatti sia l'approccio che i pattern utilizzati rimandano molto più a Swing, che non ad altri framework web, come GWT o per -certi versi- JSF.
Nel mondo java la scelta di un framework da utilizzare per lo sviluppo web è come sappiamo piuttosto ampia e comprende tecnologie molto variegate, da Struts o Spring fino a JSF: per lo più viene utilizzato il pattern MVC e un approccio orientato al riutilizzo di componenti. Negli ultimi tempi però stanno ergendo anche per i linguaggi JVM degli approcci maggiormente basati sul principio "Convention Over Configuration", divenuto celebre in particolare per l'adozione di web framework quali Ruby on Rails (Ruby), simfony (Php) o Grails e Play! per il mondo java e JVM: di questo ultimo gruppo fa parte anche Wicket.
Il progetto Wicket è un progetto Apache dotato di una buona comunità di sviluppatori e che presenta una lista di caratteristiche molto interessanti, tra le quali ad esempio:
- reale separazione dei contenuti tra View e Model/Controller
- sicurezza
- riusabilità di componenti
Una menzione va anche al fatto che Wicket supporta Ajax in maniera del tutto trasparente, cioè ajax viene utilizzato solo se il supporto per javascript è abilitato.
Download ed installazione di Wicket
Per i nostri esempi useremo MyEclipse for Spring, ma ovviamente la procedura descritta sarà quasi indentica per un Eclipse standard.
La prima cosa da fare sarà andare sul sito di wicket per effettuare il download:
http://wicket.apache.org/start/download.html
Una volta effettuato il download del file zip scelto (nel nostro caso l'ultima versione), apriamo l'archivio e selezioniamo tutti i jar contenuti (le librerie che ci serviranno nei nostri esempi), e li estraiamo in una cartella di riferimento:
Fatto questo apriamo eclipse e selezioniamo dal menù Window/Preferences/user libraries
e clicchiamo sul tasto new
:
chiamiamo la libreria WicketLib
e clicchiamo su add external jars
:
quindi andiamo a selezionare tutti i jars nella cartella WicketLib
, verranno aggiunti alla libreria:
Installazione del plugin Wicket per Eclipse (qwickie)
Il passo successivo consiste nell'installare il plugin qwickie per Eclipse, che ci permetterà di lavorare con Wicket in maniera più pratica.
Apriamo il menu help/install new software
e inseriamo l'url seguente: http://qwickie.googlecode.com/svn/trunk/qwickie.updatesite/, quindi selezioniamo il plugin e procediamo.
Creazione del primo progetto "WicketBase"
Creiamo finalmente il nostro primo progetto con il nome WicketBase, e possiamo tranquillamente crearlo come web dynamic project. Possiamo ovviamente anche importare il java project WicketBase
allegato alla guida (per far funzionare il tutto dovrete configurare nel build path il riferimento alla user library WicketLib
prima creata):
La struttura è semplice, c'è un file html che risiede sotto il package com.wicket.web
ed una classe java con lo stesso identico nome: è infatti obbligatorio in wicket avere una pagina html ed una classe java con lo stesso nome se vogliamo "gestirne" il contenuto, vedremo più avanti cosa significa.
la prima pagina HelloPage.html
Apriamo a questo punto la pagina HelloPage.html
:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
xml:lang="en" lang="en">
<body>
<h1>
Hello
</h1>
<div>
<span wicket:id="subject">[content]</span>
</div>
</body>
</html>
Dove, aldilà della definizione dei namespace iniziale, che serve per validare il contenuto della pagina,
la struttura della pagina html è particolarmente semplice, ma è interessante notare l'utilizzo dell'attributo particolare wicket:id=”subject”
, verrà utilizzato lato server dal framework per collegare ad esso i contenuti java.
La parte che abbiamo qui lasciato identificata con [content]
rappresenta per noi un segnaposto di ciò che il browser mostrerà normalmente all'utente:
Apache Wicket a differenza di altri framework (ad esempio GWT) è un framework server-side: questo significa che ricrea l'alberatura DOM dei componenti lato server, in aggiunta a quella presente lato client.
In generale Wicket identifica ciascun componente con un proprio id, tramite un attributo gestito dal framework e riconoscibile per il particolare prefisso wicket
. Quindi è del tutto normale scrivere ad esempio:
<body>
<form id="form1" wicket:id="form1">
<input id="nomeTf" wicket:id="nomeTf"/>
<input type="submit"/>
</form>
</body>
E generalmente si tende ad utilizzare per comodità lo stesso nome sia per l'id dell'elemento html, che per l'id dell'attributo wicket.
La classe HelloPage
Apriamo ora la classe java associata alla pagina HelloPage.html
:
package com.wicketWeb;
import java.io.Serializable;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
public class HelloPage extends WebPage implements Serializable {
private static final long serialVersionUID = 1L;
public HelloPage() {
Label s = new Label("subject", "John");
add(s);
}
}
La classe HelloPage
estende WebPage
(una delle classi di Apache Wicket), ed implementa l'interfaccia placeholder java.io.Serializable
(poi vedremo perchè).
La classe si comporta come se dovesse costruire i componenti che in HelloPage.html
hanno un wicket:id
associato quindi nel costruttore standard aggiunge una label con id "subject
" e valore (o Modello, come vedremo tra poco) “john
”.
Ricapitolando graficamente:
Completiamo il tutto aggiungendo una classe MyApp.java
, che sarà la classe iniziale che la nostra applicazione Wicket utilizzerà per l'avvio:
public class MyApp extends WebApplication {
@Override
public Class getHomePage() {
return HelloPage.class;
}
}
Nel metodo getHomePage()
verrà restituita HelloPage.class
, a indicare che per la prima pagina dovrà essere istanziato un oggetto di quel tipo, e visualizzata la pagina HomePage.html
utilizzando l'omonima classe HomePage.java
Il deployment descriptor web.xml
Definiamo infine il il deployment descriptor web.xml
:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<filter>
<filter-name>WicketFilter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.wicketWeb.MyApp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>WicketFilter</filter-name>
<url-pattern>/app/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
che specifica anche che la prima classe da caricare da Wicket (l'entry point dell'applicazione web) è MyApp.java
:
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.wicketWeb.MyApp</param-value>
</init-param>
si nota inoltre che per richiamare il motore di Wicket dobbiamo specificare l'url in modo che contenga /app/*
cioè /app/
con qualsiasi contenuto, come avviene normalmente in qualsiasi altro web framework.
Possiamo a questo punto effettuare il deploy e lanciare Tomcat in myeclipse:
Nella pagina html non abbiamo inserito nessun codice di scripting: Wicket richiede di associare a ciascun componente da gestire lato server un wicket:id
e nient'altro, quindi chi si dedica al layout delle pagine non troverà codice di scripting legato a java, e lato server chi si occupa dello sviluppo potrà gestire i componenti html da java.
Nella prossima parte cercheremo di introdurre il concetto di Modello.
Il Modello.
Procedendo con il nostro semplice esempio introduttivo per Apache Wicket, cerchiamo di capire il concetto di Model.
Ad una variabile con identificatore wicket:id
può essere associato un Model, che rappresenta i dati: modificando il contenuto del Model viene aggiornato il valore della variabile lato View.
Proviamo a fare anche qui un piccolo esempio, estendendo il precedente. Riscriviamo la classe HelloPage
in questo modo:
package com.wicketWeb;
import java.io.Serializable;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.Model;
public class HelloPage extends WebPage implements Serializable {
private static final long serialVersionUID = 1L;
private String labelValue = "John";
public HelloPage() {
final Model mymodel = new Model(labelValue);
final Label s = new Label("subject", mymodel);
add(s);
}
}
L'applicazione funziona allo stesso modo, e quello che abbiamo fatto è associare all'elemento Label
un contenitore dati chiamato Model
.
Aggiungiamo un link alla pagina HelloPage.html
:
Hello
[content]
cambia testo
...che ovviamente va aggiunto alla classe HelloPage
:
package com.wicketWeb;
import java.io.Serializable;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.Model;
public class HelloPage extends WebPage implements Serializable {
private String labelValue = "John";
public HelloPage() {
final Model m = new Model(labelValue); // 1
final Label s = new Label("subject", m); // 2
Link mylink = new Link("link") { // 3
@Override
public void onClick() {
m.setObject("ok pressed!"); // 4
}};
add(mylink); // 5
add(s);
}
}
Analisi del codice.
Vediamo di analizzare per punti il codice scritto, facendo riferimento ai numeri lasciati nei commenti.
Nel punto 1 e 2 le due variabili utilizzate dalla inner class devono necessariamente essere dichiarate come final
(ricordiamoci che siamo in presenza di una applicazione web, e deve essere garantita l'immutabilità dei valori potenzialmente condivisi tra varie request).
Nel punto 3 abbiamo creato un wicket link: l'id passato al costructor (cioè link
) è uguale al wicket:id
del nostro link:
cambia testo
Abbiamo utilizzato una classe anonima (inner class) per definire il metodo onClick()
(punto 4), e la sua implementazione. Il metodo verrà chiamato non appena errà generato l'evento relativo al click sul link, quello che il link farà concretamente è gestire l'evento eseguendo il codice del metodo che cambia il valore del Model
alla nuova stringa “ok pressed
”.
Nel punto 5 il link va aggiunto alla pagina (prima creato poi aggiunto).
Se eguiamo nuovamente l'applicazione:
E' interessante notare che prima di tutto il concetto di Post-Back non esiste in Apche Wicket, e che il link non è contenuto in un form affinchè venga inviata richiesta al server: insomma qualcosa che si avvicina molto al concetto di “binding dinamico”.
E' anche da notare che bisogna scrivere molto più codice rispetto ad una normale applicazione web ma si è ripagati laddove utilizzando altri framework si può avere a volte la necessità di intervenire con codice di scripting nella View in maniera proporzionale alla complessità dell'applicazione.
L'interfaccia Serializable e le sessioni in Apache Wicket
Eliminando nell'esempio l'utilizzo dell'interfaccia Serializable ed il corrispettivo membro serialVersionUID:
public class HelloPage extends WebPage {
// ELIMINIAMO -> implements Serializable {
private String labelValue = "John";
// ELIMINIAMO -> private static final long serialVersionUID = 1L;
...
l'applicazione funzionerà allo stesso modo ma la console mostrerà un messaggio di errore:
Questo apparentemente perchè è stato prima definito un identificatore per la serializzazione, e poi eliminato. In realtà se non avessimo implementato l'interfaccia Serializable fin dall'inizio, Wicket comunque avrebbe generato un errore nella console. Wicket gestisce la sessione e lo stato dei componenti coinvolti in essa in maniera del tutto trasparente all'utente e per farlo ha necessità di identificare la sessione (e con i essa i dati serializzati dai componenti relativi ad essa) tra le diverse richieste dell'utente.
Il tutto deriva come è facile supporre dalla natura stateless del protocollo HTTP.
Per capire meglio quanto detto lanciamo l'applicazione poi copiamo l'url seguente:
e incolliamolo nella barra degli indirizzi del browser:
É facile notare che Wicket ha appeso all'url il token jsessionid
per identificare la sessione (utilizzando la tecnica dell'url rewriting).
Il token serve a Wicket poiché la sessione può essere salvata su disco (o passata tra diversi cluster ad esempio) e quindi insieme ad essa anche le classi della nostra web application: questo il motivo per il quale devono essere serializzabili.
Wicket ed Ajax
Vediamo un ultimo interessante esempio concentrandoci sul supporto ad ajax in Apache Wicket, modifichiamo solo la classe java in questo modo:
package com.wicketWeb;
// imports ...
public class HelloPage extends WebPage implements Serializable {
private String labelValue = "John";
private static final long serialVersionUID = 1L;
public HelloPage() {
final Model m = new Model(labelValue);
final Label s = new Label("subject", m);
AjaxLink mylink = new AjaxLink("link") { // 1
@Override
public void onClick(AjaxRequestTarget target) { // 2
// TODO Auto-generated method stub
m.setObject("ok pressed!");
target.add(s); // 3
}};
// quando usi ajax il componente ajax-oriented deve avere associato un id lato client
// in modo che sia recuperabile via DOM, il wicket:id non coincide con l'id DOM ,
// spesso si usa lo stesso nome id x evitare omonimie
s.setOutputMarkupId(true); // 4
add(s);
add(mylink); // 5
}
}
Abbiamo utilizzato un AjaxLink
invece che un link standard (punto 1).
Il metodo onclick()
riceve un oggetto AjaxRequestTarget
(punto 2), che si comporta come un listener: bisogna in sostanza aggiungere a questo componenti i componenti che vogliamo aggiornare via ajax, nel nostro caso il componente label
(punto 3).
Al punto 4 viene specificato che alla label deve essere associato un id se non esiste già: ajax viene utilizzato via javascript lato client ed aggiorna la label sulla pagina html mostrata all'utente (HelloPage.html
). É quindi necessario individuare il componente nella gerarchia DOM, attraverso un id, in questo caso non il wicket:id ma l'id DOM utilizzabile lato client.
Da notare che un interessante corollario a questo comportamento è che con Wicket possiamo raggruppare più componenti (ad esempio usando un div
) ed aggiornarli via ajax in maniera molto semplice, ragionando cioè per widget html.
Effettuando il deploy dell'applicazione:
la console mostra che siamo in modalità sviluppo. Questa modalità potrà essere cambiata in production mode
in seguito, ma cosa ancora più interessante è la comparsa di un ajax monitor/debug.
Se proviamo a cliccare sul link wicket ajax debugger
, la finestra mostrerà il contenuto prodotto dalla chiamata ajax:
Leggendo attentamente su questa console, possiamo capire quale componente ajax è stato aggiornato e il markup id ('subject 3') che gli è stato associato da wicket con la chiamata s.setOutputMarkupId(true)
.
Se cliccate col tasto destro sulla pagina del browser e visualizzate il sorgente html della pagina noterete la stessa cosa:
.... // sorgente pagina
Hello
John
cambia testo
Considerazioni finali
C'è da dire che si sente un po' la mancanza di un editor visuale, che potrebbe facilitare molto gli sviluppatori aumentandone la produttività.
In ogni caso Apache Wicket è un ottimo framework, che non fa della rapidità il suo punto di forza ma riesce ad articolare lo sviluppo di una applicazione web come se stessimo creando una applicazione swing per il desktop.