Le novità dell'ultima release di ZK

Introduzione al pattern MVVMdi

Poco più di due anni fa, avevamo visto su queste pagine come ZK fosse un framework semplice e potente. Con esso, gli sviluppatori sono messi in grado di scrivere delle vere e proprie applicazioni basate su Ajax, senza grande sforzo. Avevamo proposto questo framework come 'emergente' e 'alternativo' alle JSF. Da allora a oggi, ZK è stato utilizzato da più di un milione di utenti. In Italia, in particolare, ZK ha avuto un'ottima crescita: sempre più aziende scelgono ZK come framework preferito per i propri siti web.

Da quando MokaByte ha pubblicato i due articoli su ZK nei numeri di ottobre e novembre 2009 [1], molte persone mi hanno scritto per avere consigli pratici su questo strumento: se è necessario fare dei corsi di formazione, o se se valga  la pena investirci su.

In generale le persone che mi hanno scritto sono professionisti disillusi dalla tecnologia JSF, desiderosi di non investire troppo tempo (e soldi) su tecnologie web complesse (e quindi preoccupati di "proteggere il proprio investimento") e che comunque sentono l'esigenza di non rimanere ancorati a vecchi framework come Wicket e Struts.

Scegliere ZK come proprio strumento di sviluppo per siti web è la naturale conseguenza alla mancanza di tecnologie pronte all'uso della stessa elevata qualità. Oggi continuiamo con la trattazione di ZK fornendo una descrizione delle sue principali evoluzioni.

Nuove funzionalità in ZK6

La versione 6 di ZK presenta miglioramenti che sono al tempo stesso piuttosto numerosi e anche molto significativi. Le migliorie apportate in questa nuova versione sono principalmente le seguenti:

  • uso dei generics: questo comporterà l'eliminazione di inutili type casting nel codice;
  • un nuovo databinder che permette di scrivere controller completamente indipendenti dalla vista (ZUL file) dando una notevole flessibilità;
  • il pattern MVVM che permette un disaccoppiamento completo tra la vista (file ZUL) e i controller, che diventano dei semplici POJO completamente slegati dalle pagine web;
  • selettori dei componenti con annotazioni in stile jQuery e CSS;
  • template avanzati: è possibile scrivere dei macro componenti nelle pagine ZUL senza scrivere alcuna riga di codice Java.

Nei paragrafi successivi cominciamo a vedere in dettaglio alcune di queste novità.

Il nuovo binder

Il nuovo binder di ZK consiste in un meccanismo automatico di aggiornamento e caricamento di dati in una pagina ZUL, di esecuzione di comandi, di conversioni e di validazioni. Tutte queste operazioni vengono eseguite automaticamente mantenendo sempre aggiornati i dati nelle pagine web con i dati letti dal modello sottostante.

Il nuovo binder prende origine dalla versione precedente e da numerosi feedback della comunità di sviluppatori sparsa per il mondo: il risultato è uno strumento flessibile e ricco di funzionalità.

Possiamo catalogare le annotazioni introdotte nelle categorie di seguito riportate.

Identificazione e inizializzazione del controller

@id(...): viene usata per identificare un view model o una form;

@init(...): viene usata per inizializzare un view model o una form.

Binding del modello con la vista

@load(...): serve per caricare delle variabili dal modello alla pagina web;

@save(...): serve per salvare i dati inseriti (o cambiati) dall'utente nelle pagine web iniettandoli nel modello sottostante;

@bind(...): equivale ad annotare un widget sia con @load(...) che con @save(...);

@command(...): serve per associare un evento che si verifica sulle pagine web con un metodo del controller.

Convertitori e validatori di dati

@converter(...): permette di associare un convertitore a un tipo di dato;

@validator(...): permette di validare un dato sulla pagina web visualizzando automaticamente i messaggi di errore.

Esempi di binding

Il binding di ZK supporta le espressioni EL per accedere a proprietà, mappe e per scrivere dei semplici predicati di valutazione. Per esempio, supponiamo di voler iniettare nel widget image il nome dell'immagine da caricare


In questo esempio carichiamo boy o girl a seconda del valore trovato nel campo gender di Person. Da notare che l'instanza di Person, legata a questa pagina, la troviamo dentro un oggetto chiamato vm; "vm" sta per ViewModel: per ora possiamo vederlo come una specie di Controller, ma parlerò in seguito del ViewModel e del relativo pattern MVVM.

Il caricamento di dati nel widget può essere fatto non solo all'atto della creazione della pagina, ma può essere fatto anche a seguito di eventi che sono stati scatenati dall'utente. Per esempio supponiamo di voler visualizzare una label dopo aver eseguito il comando update:


...

Come si vede, il comando viene identificato con l'annotazione @Command che prende come parametro il nome dell'azione (update).

L'annotazione @load(...) permette di caricare il valore dal modello vm.myLabel dopo che l'evento di update è stato consumato dal comando associato.

Viceversa, per salvare un valore immesso da GUI nel modello occorrerà usare l'annotazione @save(...) come segue:


In questo esempio il Binder salverà il valore immesso dall'utente dentro il ViewModel vm, e precisamente dentro la proprietà firstname di person.

Se invece il textbox deve essere sempre sincronizzato con il valore del modello, basterà scrivere:


che equivale a scrivere assieme: @load(...) @save(...).

Esempi di convertitori e validatori

Supponiamo di voler visualizzare una data in un formato personalizzato mediante un nostro convertitore. Con il nuovo binding è veramente semplice: sul file ZUL basterà indicare il nome del convertitore:

format=‘yyyy/MM/dd')"/>

Il nome del convertitore, ancora una volta, viene risolto dal Binder che lo cerca tra le proprietà del ViewModel (il Binder cercherà la proprietà dateformatConverter presente nel ViewModel). Il convertitore non è altro che una classe che implementa org.zkoss.bind.Converter:

public class DateFormatConverter implements Converter {
 
       public Object coerceToUi(Object val, Component comp, BindContext ctx) {
             final String format = (String) ctx.getConverterArg("format");
             return new SimpleDateFormat(format).format(date);
       }
      
       public Object coerceToBean(Object val, Component comp, BindContext ctx) {
             final String format = (String) ctx.getConverterArg("format");
             try {
                    return new SimpleDateFormat(format).parse(date);
             } catch (ParseException e) {
                    throw UiException.Aide.wrap(e);
             }
       }
}

La classe DateFormatConverter implementa due metodi: coerceToUi(...) e coerceToBean(...). CoerceToUi(...) serve per tradurre dall'oggeto del modello (Date) nell'oggetto della vista (String), mentre coerceToBean(...) costruisce l'oggetto del modello (Date) a partire da una stringa.

Per quanto riguarda i validatori, il meccanismo è del tutto analogo: si utilizza l'annotazione @validator e lo si lega a una classe. Implementiamo un validatore di date:

public class CreationDateValidator implements Validator{
       private Messages messages;
       ...
       public void validate(ValidationContext ctx) {
             Date creation = (Date)ctx.getProperty().getValue();
             if(creation==null){
                    ctx.setInvalid();
                    messages.put("creationDate", "must not null ");
             }else{
                    messages.remove("creationDate");
             }
             ctx.getBindContext().getBinder().notifyChange(messages, "creationDate");
       }
}

Questa classe vàlida una data passata dal contesto di ZK; se ci sono degli errori, li aggiunge nella coda dei messaggi Messages. A questo punto basterà richiamare il validatore nel widget grafico in questo modo:

             @save(vm.creationDate,before=‘saveOrder')
              @validator(vm.creationDateValidator)"/>

A questo punto per comprendere appieno il funzionamento del Binder è necessario illustrare come vengono creati e gestiti i controller vm che permettono di legare il modello alla vista, cioè che cosa è un ViewModel e quindi come è definito il pattern MVVM.

Il pattern MVVM

ll pattern Model - View - ViewModel (MVVM) è una variante del pattern MVC (Model - View - Controller), differente anche dal MVP (Model - View - Presentation) che permette una chiara separazione tra la parte di vista (presentazione) e la parte modello.

Nel pattern MVVM gioca un ruolo importante il "controller", che qui viene chiamato ViewModel, cioè "modello della vista", il che sta a indicare che la parte controller rappresenta insieme sia il modello che le azioni che vengono utilizzate dalle pagine web, senza però che il controller stesso dipenda in alcun modo dalla vista.

In questo pattern abbiamo come attori il Model, la View, e il ViewModel. Vediamo brevemente di seguito questi elementi costitutivi.

Model

Il Model è la parte dei dati che dobbiamo elaborare e visualizzare nelle pagine web; in pratica è la parte più vicina al database.

View

La View è l'insieme delle pagine ZUL che vengono visualizzate sul browser.

ViewModel

Il ViewModel è il modello della vista. In pratica è un POJO che racchiude in se':

  • i comandi che la vista può richiamare;
  • i dati che la vista può visualizzare o modificare;
  • i convertitori e i validatori che la vista può usare per convertire o validare dei dati complessi.

La parte importante di questo pattern è che il ViewModel è un POJO del tutto indipendente dalla pagina ZUL che dovrà utilizzarlo. Non solo: più pagine ZUL possono usare lo stesso ViewModel.

Vantaggi del MVVM per lo sviluppo

In questa maniera è possibile minimizzare il tempo di sviluppo del sito web e ottimizzare gli sforzi del team: il team degli sviluppatori Java si occuperà del Model e del ViewModel (quindi della logica del sito web); mentre i grafici si occuperanno delle pagine web in maniera del tutto indipendente gli uni dagli altri.

Un ulteriore vantaggio è la testabilità dei ViewModel: essendo dei POJO possono esser testati in maniera semplice senza dover utilizzare complicati strumenti di test di interfacce grafiche.

Come funziona MVVM in breve

È stato possibile realizzare questo nuovo pattern solamente tramite gli strumenti di binding che permettono di mantenere sincronizzate la parte ViewModel con la parte View in maniera automatica e trasparente per gli sviluppatori: ecco perchè il pattern MVVM si basa completamente sul nuovo binding ed ecco perche' il nuovo binding è stato pensato principalmente per realizzare questo paradigma di programmazione.

In pratica i valori da visualizzare nella vista vengono "spinti" automaticamente dal Binder verso la vista, mentre è sempre il Binder che inietta nel ViewModel i valori modificati nella vista e richiama i metodi di elaborazione degli eventi (@Comand).

Quindi il ViewModel è materialmente differente dal Controller del tradizionale MVC: in questo caso non è il Controller che "spinge" il proprio stato nelle pagine web, ma è il Binder che si interpone tra i due e permette di spingere lo stato del ViewModel verso la vista, e viceversa dalla vista aggiorna il ViewModel; il tutto rendendoli l'uno indipendente dall'altro.

Enfatizzando questo meccanismo, possiamo dire che un altro modo in cui si potrebbe chiamare questo pattern è MVB, cioè Model - View - Binder.

Dato che gli attori di questo pattern sono principalmente il modello, la vista, e il binder (che automaticamente mette in comunicazione vista e modello) a voler essere proprio precisi bisognerebbe chiamarlo Model - Binder -View, poiche' il Binder si comporta come un mediatore tra vista e modello. Al di là di questa precisazione, speriamo che il concetto sia chiaro ai lettori.

La potenza del nuovo Binder

Senza il nuovo binding tutto questo non sarebbe stato possibile: nella figura 1 si vede cosa accade nell'implementazione del pattern MVP (Model - View - Presentation, una variante dell'MVC) nella vecchia versione di ZK.

 

 

Figura 1 - Questo è ciò che accadeva nel passato, nella vecchia versione di ZK, con l'implementazione del pattern MVP.

 

Come si vede, tramite il pattern MVP è il Presentation Layer che direttamente informa la vista che il proprio stato è cambiato (passo 4), mentre al passo 1 è direttamente la vista che richiama un comando del Presentation Layer.

Al contrario, la nuova versione di ZK implementa il pattern MVVM (figura 2). In MVVM, i passi 1 (lancio di un evento dalla GUI) e 7 (aggiornamento della GUI a seguito di un cambiamento del modello) vengono intercettati dal Binder e non vengono direttamente passati dal/al ViewModel, mentre il passo 5 (avviso del cambiamento del proprio stato interno del ViewModel) viene passato solamente al Binder che provvederà ad aggiornare una o più pagine web interessate.

 

 

Figura 2 - Nella nuova versione di ZK; è implementato il pattern MVVM, in cui il ruolo del nuovo Binder è cruciale.

 

Hello World!

Vediamo ora il classico esempio HelloWorld! di MVVM, in modo tale da rendere tutto più chiaro. Trovate i sorgenti nell'allegato hello-zk.zip scaricabile dal menu di destra. Il progetto HelloWorld! è formato da:

  • View: una pagina ZUL che contiene una label (con un messaggio di saluto) e un bottone che richiede dal modello di visualizzare il saluto;
  • ViewModel: si occupa di eseguire il comando di visualizzazione del saluto e memorizzare nel proprio stato interno il saluto richiesto;
  • Binder: le annotazioni del Binder legano le due parti, ossia il file ZUL con il ViewModel.

Ecco la pagina ZUL che contiene il messaggio con il pulsante di richiesta di visualizzazione del saluto:

        viewModel="@id(‘vm') @init(‘org.mokabyte.mvvm.HelloViewModel')">
       
       

Come si vede abbiamo usato il Binder per:

  1. definire l'@id() del ViewModel da legare alla pagina, che abbiamo chiamato vm (ViewModel); ecco quindi il significato di vm che avevamo visto negli esempi precedenti sul binding;
  2. inizializzare @init(..) la window con il nostro ViewModel HelloViewModel: per tutta la pagina verrà referenziato con l' @id vm;
  3. caricare la label con vm.message, cioè con la proprietà message di HelloViewModel;
  4. richiedere di eseguire il comando showHello di HelloViewModel ogniqualvolta si preme il tasto Show.

La classe HelloViewModel è mostrata di seguito

public HelloViewModel {
       private String message;
       @Init
       public void init(BindContext ctx) {
             message = "";
       }
       public String getMessage() {
             return message;
       }
      
       @Command @NotifyChange("message")
       public void showHello() {
             message = "Hello World!";
       }
}

Come si vede abbiamo definito:

  1. un metodo init() annotato con @Init(..) che indica il valore iniziale del messaggio di saluto;
  2. un metodo getMessage() usato dal Binder per accedere alla proprietà message; è il @load(vm.message) che avevamo visto nel file ZUL;
  3. un comando showHello() (@Command) che, inoltre, notifica (@NotifyChange) al Binder il cambio di valore della proprietà message (è il passo 5 della figura 2, quando il ViewModel notifica al Binder che una proprietà è stata cambiata dalla logica del Modello).

In pratica il Binder aggiornerà tutti i riferimenti nei widget dei file ZUL che contengono le annotazioni @load(vm.message) o anche @bind(vm.message). Come si vede, HelloViewModel sta solamente notificando che è avvenuto un cambiamento al proprio interno, ma non sa nulla a chi questa modifica verrà notificata; del resto, tutto ciò, non deve interessargli: in questa maniera abbiamo realizzato un completo disaccoppiamento tra la vista (ZUL file) e tutta la logica Java che fa muovere il sito Web.

Ecco che il nuovo "controller" HelloViewModel può essere modificato in maniera del tutto autonoma rispetto alle modifiche che i grafici faranno sulla pagina ZUL; questo vantaggio è importante quando si lavora su un sito web (poter dare libertà di modifiche sia al team Java che ai grafici).

Una nota di cautela su Binding e MVVM

Il nuovo binding di ZK e il pattern MVVM sono veramente interessanti; c'è da fare però una considerazione. Una unica pecca di questo meccanismo così flessibile e semplice riguarda la necessità di dover sempre annotare i metodi che cambiano i valori del modello con @NotifyChange(...). Questo può portare a dover annotare anche i metodi setter degli entity bean qualora li usassimo direttamente all'interno del ViewModel.

Per esempio, se a seguito di un comando vengono modificati molti campi di uno o più entity bean, allora dovremo taggare i setter del singolo o dei vari entity bean con @NotifyChange, altrimenti le modifiche apportate non verranno caricate. Quello che si può fare per evitare di annotare gli entity beans è di annotare invece direttamente il comando @Command del ViewModel con @NotifyChange specificando assieme tutti i campi che si stanno modificando. In questo modo il nostro modello di entity bean rimane pulito, però l'elenco dei campi potrebbe essere molto lungo e potrebbe anche cambiare durante l'evoluzione del sito web.

Il punto importante è che gli entity beans (che fanno parte della logica del DB) non dovrebbero dipendere da nient'altro che dalla tecnolgia degli EJB (e al limite dall'ORM scelto); invece con il meccanismo del @NotifyChange gli entity bean potrebbero anche dipendere dalla tecnologia Web.

Selettori di componenti

Il SelectorComposer permette di selezionare dei componenti ZK tramite una sintassi simile a quella usata da jQuery e CSS3.

Prendiamo ad esempio una form complessa che deve essere compilata dall'utente. Per esempio, prendiamo una form che contiene i dati da inserire in una agenda telefonica:

  • cognome;
  • nome;
  • numero di telefono;
  • ....

 

 

Figura 3 - Campi di una form complessa.

 

La form presentata all'utente due pulsanti: uno di invio dei dati e un altro di pulitura dei widget grafici. Come possiamo implementare il comando Clear in modo tale che pulisca tutti i campi contemporaneamente? Utilizzando la vecchia release di ZK avremmo dovuto fare nel seguente modo:

public class SomeFormController extends GenericForwardComposer {
      
       Textbox usenameTb;
       Textbox passwordTb;
       Textbox retypepwTb;
       // ...
       // ...
       Textbox memoTb;
      
       public void onClick$clearBtn(Event event) {
             usenameTb.setValue("");
             passwordTb.setValue("");
             retypepwTb.setValue("");
             // ...
             // ...
             memoTb.setValue("");
       }
      
}

Dovevamo scrivere un Controller derivando da GenericForwardComposer, dichiarare tutti i widget della form e scrivere il "comando" onClick$clearBtn(...) che "a mano" ripulisce tutti i widget. Certamente è una strategia che funziona, ma evidentemente porta a dei problemi di manutenibilità oltre che a pericolosi "copia & incolla".

Con la nuova versione di ZK, definiamo invece un ViewModel che deriva da SelectorComposer.

SelectorComposer è del tutto simile a GenericForwardComposer, tuttavia permette di iniettare (nel controller) i widget della pagina tramite l'annotazione @Wire(...) e tramite selettori in stile jQuery/CSS3.

Ecco come utilizzeremo il selettore per il nostro esempio:

@Wire("textbox, intbox, decimalbox, datebox")
List inputs;

In questo modo selezioniamo tutti i widget di tipo textbox, intbox, decimalbox e datebox. La lista inputs contiene tutti oggetti di tipo InputElement che sono dei "wrapper" ai widget reali. Una volta definita la lista di tutti i widget che vogliamo pulire, definiamo il comando onClear() che ascolta (@Listen) l'evento onClick del bottone Clear:

@Listen("onClick = button[label=‘Clear']")
public void onClear(MouseEvent event) {
    for(InputElement i : inputs)   i.setText("");
}

In questo modo abbiamo ottenuto la funzionalità richiesta con molte meno righe di codice e in modo estremamente più pulito. Ecco il ViewModel completo:

public class FormController extends SelectorComposer {
       @Wire("textbox, intbox, decimalbox, datebox")
       List inputs;
      
       @Listen("onClick = button[label=‘Clear']")
       public void onClear(MouseEvent event) {
             for(InputElement i : inputs)    i.setText("");
       }
}

La selezione dei componenti tramite annotazione @Wire può avvenire anche in modalità differenti. Ecco degli esempi di come richiamare i widget della grafica nel proprio controller:

  • @Wire("button"): inietta qualsiasi componente di tipo button;
  • @Wire("#myButton"): inietta qualsiasi componente con id = myButton;
  • @Wire("button=#myButton"): inietta qualsiasi bottone con id = myButton;
  • @Wire("button[label=‘Submit']"): inietta qualsiasi bottone la cui proprietà label è uguale a Submit.

Template Avanzati

Prima di ZK6, per implementare delle funzionalità avanzate, era necessario scrivere codice Java, cioè dei renderer personalizzati. Oggi invece è possibile utilizzare solamente codice ZUL accedendo al modello direttamente dal template. Per esempio, supponiamo di voler leggere una lista di dati e di metterli in una griglia; con il templating di ZK6 basterà scrivere il seguente codice ZUL:

       
             
             
       
       
             
                    
                    
             

Dove la lista di fruits è di tipo ListModel che contiene gli oggetti del modello (che sono di tipo Fruit). Fruit deve contenere almeno getter per le proprietà name e cost (getName() e getCost()). La lista verrà passata al modello del template (model) e per ogni elemento della lista (${each}) visualizzeremo i campi di Fruit.

Conclusioni

Le nuove funzionalità di ZK6 fanno fare al framework un enorme passo avanti: le migliorie apportate permettono di utilizzare uno stile di programmazione moderno ed efficiente. Il binding automatico e il pattern MVVM formano assieme un unico strumento più flessibile e semplice del vecchio pattern MVC/MVP poiche' permettono di legare le pagine web alla logica Java solamente tramite annotazioni garantendo un'alta manutenibilità.

In definitiva ho una risposta per la domanda più importante che mi è stata posta su ZK: "è conveniente investire soldi e tempo su questa tecnologia?" La mia risposta è di nuovo affermativa, fermo restando che ci sono indubbiamente delle alternative valide, come PrimeFaces, Vaadin, SmartGWT, jQuery e così via. Ma a mio modesto giudizio, ZK rimane uno dei migliori framework in assoluto perche' è uno strumento maturo, intuitivo, ben progettato, con una comunità di sviluppatori e utenti molto vasta, con plugin open Source scritti da molti "contributori" volontari che lo complementano in alcune parti; e infine coniuga flessibilità con ottime performance lato server e client, e presenta una ottima curva di apprendimento.

Riferimenti

[1] Gli articoli di MokaByte su ZK

http://www2.mokabyte.it/cms/article.run?articleId=RSD-89N-1MJ-RN7_7f000001_18359738_bce062e1

 

http://www2.mokabyte.it/cms/article.run?articleId=24O-7SB-COK-R48_7f000001_18359738_47dff45a

 

http://www2.mokabyte.it/cms/article.run?articleId=FSH-9JS-VVS-5OU_7f000001_26000063_4f8b8af8

 

[2] Una introduzione al pattern MVVM

http://books.zkoss.org/wiki/Small%20Talks/2011/November/Hello%20ZK%20MVVM

 

[3] Esempi di utilizzo delle nuove funzionalità di ZK6

http://books.zkoss.org/wiki/Small_Talks/2011/November/MVVM_in_ZK_6_-_Design_your_first_MVVM_page

 

[4] Un esempio di come implementare un CRUD con ZK6

http://books.zkoss.org/wiki/Small_Talks/2011/November/MVVM_in_ZK_6_-_Design_CRUD_page_by_MVVM_pattern

 

[5] Confronto tra MVC e MVVM

http://books.zkoss.org/wiki/Small_Talks/2011/December/MVVM_in_ZK6:in_Contrast_to_MVC

 

[6] Un'ulteriore descrizione del pattern MVVM non fatta da Potix

http://java.dzone.com/articles/test-driving-mvvm-pattern-zk-0

 

[7] Un esempio di integrazione di Spring con MVVM

http://books.zkoss.org/wiki/Small_Talks/2012/January/MVVM_in_ZK6:_Work_with_Spring

 

[8] Advancing Templating in ZK6 http://books.zkoss.org/wiki/Small_Talks/2011/July/Envisage_ZK_6.0:_Rendering_List_and_Tree_Model_with_Templates

 

 

Condividi

Pubblicato nel numero
171 marzo 2012
Michele Mazzei si è laureato in Scienze dell’Informazione nell’ormai lontano 1998. Si occupa di progettazione e scrittura di software in Java/Java EE e in C/C++ sul mondo Linux. Lavora a Roma in ambito spaziale maturando esperienze in ambito OGC, GIS, Map Server, Payload Data Ground Segment (PDGS). Si interessa di…
Ti potrebbe interessare anche