Migrare da RichFaces a PrimeFaces? Si-può-fareeee!

Stando a quanto mostra Google Trends, l’interesse della comunità di sviluppatori JSF si è spostato esponenzialmente da RichFaces a PrimeFaces tanto da superare il rivale intorno al settembre del 2011.
CoseNonJaviste si è già avvicinata a PrimeFaces con un tutorial che mostra le potenzialità del framework, che si mostra molto curato, potente ed accattivante rispetto alla concorrenza. Lasciare la vecchia strada per la nuova si sa, può essere pericoloso, perché soprattutto inizialmente si perde di produttività, con il rischio di trovarsi a non riuscire a fare cose che prima si era capaci in poco tempo. La curiosità però è più forte della prudenza, e ne è valsa veramente la pena.

Qualcosa è cambiato

Dallo showcase, PrimeFaces si dimostra in forma smagliante, veloce e responsivo (non sembra nemmeno JSF 😉 ). Con tutta quella miriade di componenti già pronti e ben fatti, l’entusiasmo monta! Se però si pensa di usare PrimeFaces in ambito professionale ed abbandonare RichFaces, voglio solo guadagnarci e non perderci niente. A mio avviso, un framework si dimostra robusto e potente nella misura in cui si riesce ad girare intorno ai “casi d’uso” per cui è stato pensato, senza creare troppi problemi. In pratica, vorrei la libertà di:

  • ottenere facilmente gli id client dei tag JSF da dare in pasto a jQuery
  • inviare oggetti in formato JSON al client
  • chiamare un metodo di un backing bean da JavaScript
  • controllare l’esito delle validazioni dopo una chiamata ajax

Tutte cose che RichFaces mi permetteva di fare. E adesso?

RichFunction vs Widget

PrimeFaces ha un approccio piuttosto diverso rispetto a RichFaces nella gestione dei suoi componenti lato client, soprattutto nel modo in cui vi si accede tramite JavaScript. In RichFaces infatti hanno grande importanza alcune funzioni lato server usate spesso nelle Expression Language delle pagine JSF:

  • rich:clientId('id-server'): traduce l’id server di un tag JSF passato come argomento in un id client
  • rich:element('id-server'): viene tradotto in document.getElementById('id-client'), dove l’id server passato alla funzione RichFaces viene tradotto automaticamente in id client. E’ una scorciatoia per document.getElementById(#{rich:clientId('id-server')})
  • rich:component('id-server'): permette di accedere alle API JavaScript di un componente RichFaces. Sul client viene tradotto in document.getElementById('id-client').component, che ovviamente è una scorciatoia per #{rich:element('id-server')}.component
  • rich:jQuery('id-server'): da RichFaces 4, è disponibile questa ottima funzione per ottenere in un attimo un oggetto jQuery su un elemento HTML, conoscendo l’id server del corrispettivo tag JSF. E’ una scorciatoia per jQuery(‘#id-client’), dove di solito l’id client contiene i due punti (:), che vanno “escapati”… ma di questo parliamo tra poco!

Come si può vedere già in questi ultimi due casi, l’uso di queste funzioni è molto potente, ma porta ad una sintassi ibrida e poco leggibile. Capita infatti spesso nelle pagine di dover scrivere righe e righe di JavaScript miste a funzioni di questo tipo, perdendo chiarezza nel codice e soprattutto la possibilità di portare il JavaScript in un file esterno alla pagina, perché le EL non verrebbero interpretate. Quando poi l’id client che abbiamo ottenuto lo vogliamo dare in pasto a jQuery, le cose si complicano ancora.

La naming convention di JSF infatti “cozza” un po’ con quella di jQuery.
Prendiamo per esempio questo frammento di codice:

<h:form id="myForm">
   <h:inputText id="myInput"/>
</h:form>

Sul browser verrà “compilato” come segue:

<form id="myForm">
   <input type="text" id="myForm:myInput" />
</form>

Ok, un occhio furbo direbbe: ma se metto l’attributo prependId="false" alla form il problema non si pone! In questo caso è vero, ma non possiamo essere sempre così fortunati 😉

Come possiamo vedere l’id del campo di input è diventato myForm:myInput. Quei due punti maledetti in jQuery vengono interpretati come meta-classi, che di solito identificano lo stato di un componente (per esempio :checked, :disabled e così via). Per far digerire questo id a jQuery, va “escapato” e va aggiunto il prefisso giusto (#), ottenendo così #myForm\\:myInput… insomma la cosa si fa laboriosa, non a caso è stata aggiunta la nuova funzione rich:jQuery!! Per chi usa RichFaces 3 e vuole usare jQuery, il problema è molto sentito…

Con grande e piacevole sorpresa, anche PrimeFaces integra jQuery, ma in modo molto più estremo! Quasi tutti i componenti PrimeFaces infatti, oltre che essere UIComponent JSF, sono anche PrimeFaces Widget: questa caratteristica fa si che questi componenti espongano l’attributo widgetVar che possiamo immaginare come un binding lato client! Che significa?

<h:form id="myForm">
   <p:inputText id="myInput" widgetVar="myWidget"/>
</h:form>

Che myWidget ce lo ritroviamo nel DOM come oggetto JavaScript contenente informazioni come:

  • id client del componente
  • id client del componente “escapato” e “prefissato” per jQuery
  • oggetto jQuery del componente (!!!)
  • accesso alle API JavaScript del componente

Con questo approccio non c’è più bisogno di ricorrere a funzioni che mischiano linguaggio client (JavaScript) con linguaggio server (Expression Language)… almeno finché non abbiamo bisogno dell’id di un componente che non è un Widget. Anche PrimeFaces espone le sue funzioni Facelets:

  • #{p:component('id-server')}: restituisce l’id client di un qualsiasi tag JSF. Possiamo usare poi
    $(PrimeFaces.escapeClientId('#{p:component("id-server")}'))
    

    se vogliamo l’oggetto jQuery associato al componente HTML.

  • #{p:widgetVar(p:component("id-server"))}: restituisce l’oggetto Widget client associato all’id server (in PR 4, nel 3 non c’è bisogno del p:component)

data vs RequestContext

Ogni componente RichFaces (mutuato in realtà dal progetto Ajax4JSF) che ha un comportamento ajax, ha un attributo molto particolare chiamato data che, legato ad un qualsiasi attributo di un backing bean, serializza il valore dell’attributo stesso in un oggetto JSON accessibile dal client durante l’evento oncomplete di una chiamata ajax proprio con una variabile chiamata data. Un esempio, anche se stupido, è meglio di mille parole:

<h:form>
   <a4j:commandButton  
          action="#{facesTestsBackingBean.loadServerData}" 
          value="Load Data" 
          data="#{facesTestsBackingBean.account}" 
          oncomplete="alert(event.data.userName + ' ' + event.data.password)"/>
</h:form>

dove

@Named
@RequestScoped
public class FacesTestsBackingBean implements Serializable {

   private Account account;

   public void loadServerData() {
      account = new Account("mario", "rossi");
   }
	
   public Account getAccount() {
      return account;
   }
}

public class Account implements Serializable{

   private String userName;	
   private String password;

   public Account(String userName, String password) {
      super();
      this.userName = userName;
      this.password = password;
   }
//Getters e Setters
}

Questo codice funziona solo con RichFaces 4, dove la funzione callback chiamata sull’oncomplete ha come argomento l’evento generato dal bottone. data è un attributo di questo evento e, come promesso, contiene il nostro oggetto serializzato.
RichFaces 3 invece non gestisce direttamente l’evento nella callback, ma passa data come terzo argomento della funzione.
In entrambe le versioni comunque, è possibile mandare in serializzazione POJO, collezioni o mappe Java.

PrimeFaces invece ha una gestione totalmente diversa dell’interazione server-client, ma permette di ottenere quasi lo stesso risultato. L’interazione è gestita da un RequestContext, attraverso il quale sostanzialmente è possibile gestire una mappa stringa-oggetto che viene inviata al client come terzo argomento della funzione di callback oncomplete con il nome di args: questa variabile JavaScript sarà speculare alla mappa Java che abbiamo creato sul server. Vediamo come si trasforma l’esempio precedente:

public void loadServerData() {
   RequestContext requestContext = RequestContext.getCurrentInstance();
   requestContext.addCallbackParam("account", new Account("mario", "rossi"));
}

<h:form>
   <p:commandButton  
          action="#{facesTestsBackingBean.loadServerData}" 
          value="Load Data"
          oncomplete="alert(args.account.userName + ' ' + args.account.password)"/>
</h:form>

PrimeFaces utilizza anche la variabile args per notificare il client sullo stato della validazione. Infatti, se la validazione della form non è andata a buon fine, la variabile args.validationFailed assume valore true.
Normalmente invece con RichFaces, l’unico modo per ottenere la stessa informazione è controllare direttamente il contesto JSF tramite EL (#{facesContext.validationFailed}) e mischiare nuovamente linguaggio client con linguaggio server per prendere decisioni in base al suo valore.

Pro e contro di entrambi gli approcci? In RichFaces si è costretti a legare allo stato del backing bean gli oggetto che vogliamo serializzare, mentre con PrimeFaces abbiamo visto che è tutto gestito attraverso la mappa del RequestContext. D’altro canto però la serializzazione di RichFaces è molto più potente di quella PrimeFaces, perché supporta anche le collezioni Java. Dando un’occhiata al codice di PrimeFaces, sembra che dipenda dalla classe PrimePartialResponseWriter. Per personalizzarlo, date un’occhiata al post su JSF e JSON (ispirato proprio da Prime Faces…)

jsFunction vs remoteCommand

Forse questo è l’unico caso di perfetta parità… o quasi.
Il tag jsFunction è uno strumento potentissimo di RichFaces per poter effettuare chiamate ajax dal nostro codice JavaScript verso un metodo del backing bean che sta dietro alla pagina corrente. Anche PrimeFaces ha uno strumento analogo, chiamato remoteCommand. Entrambi ci danno la possibilità di controllare il ciclo di vita della chiamata ajax tramite funzioni JavaScript di callback legate a certi eventi. Nonostante i nomi differenti, gli eventi sono gli stessi:

Evento jsFunction remoteCommand
Prima della chiamata onbegin onstart
Al termine della chiamata, prima dell’aggiornamento del DOM onbeforedomupdate onsucess, onerror
Al termine della chiamata, dopo l’aggiornamento del DOM oncomplete oncomplete

Gli eventi sono praticamente gli stessi: PrimeFaces in più gestisce separatamente il caso in cui la chiamata HTTP ritorni un codice di errore. Questa può rivelarsi molto utile in innumerevoli casi.
Oltre a questi, remoteCommand ha un attributo interessante chiamato partialSubmit: quando impostato a true, esegue il submit non dell’intera form in cui si trova il tag, ma solo degli id specificati dall’attributo process (che è il corrispettivo dell’attributo execute di JSF2 e RichFaces 4)
Fin qua sembra che le differenze siano irrilevanti, ma una cosa manca a remoteCommand, ovvero la possibilità di chiamare la funzione JavaScript generata dal tag passando degli argomenti, che vengono iniettati nel backging bean. In RichFaces si ottiene questo risultato grazie al tag a4j:param. Per ottenere lo stesso effetto con PrimeFaces, bisogna ricorrere a PrimeFaces Extensions e al tag pe:remoteCommandParameter.

Conclusioni

A mio avviso queste sono le cose più interessanti che si possono incontrare se si intende migrare framework, e sono sicuramente quelle meno documentate. Non ha molto senso confrontare i tag di entrambi i framework uno ad uno, ma nell’uso dei componenti di tutti i giorni ci sono comunque altre differenze sostanziali. Quelle più evidenti sono:

rich:message vs p:message

I rich:message/s sono molto comodi perché in caso di errori di validazione si refreshano automaticamente al termine di una chiamata ajax. I p:message/s invece non hanno questa caratteristica: sono quindi molto più simili agli h:message/s, ma in più hanno lo stile css coerente con il tema correntemente in uso. Per ottenere lo stesso comportamento dei parenti di RichFaces, è necessario “wrapparli” dentro un p:outputPanel con l’attributo autoUpdate="true".

p:outputLabel e p:inputText

Quanto ho visto questi componenti per la prima volta mi sono chiesto che bisogno ci fosse di personalizzare tag JSF così semplici. Un esempio è meglio di mille parole. Normalmente in JSF con RichFaces, un elemento di una form è di questo tipo:

<h:outputLabel for="text1" value="Text1"/>
<h:inputText value="#{testBackingBean.text1}" label="Text1" id="text1" required="true"/>
<rich:message for="text1" id="text1-message" />

Che viene renderizzato in questo modo:

Elemento form RichFaces
Elemento form RichFaces in errore

Di RichFaces in realtà c’è solo il rich:message. Gli altri tag JSF prendono automaticamente lo style del tema di default di RichFaces.
Con PrimeFaces, questo frammento di codice potrebbe diventare:

<p:outputPanel autoUpdate="true">
   <p:outputLabel for="text1" value="Text1"/>
   <p:inputText value="#{testBackingBean.text1}" widgetVar="input1" id="text1" required="true"/>
   <p:message for="text1" />
</p:outputPanel>

con una sostanziale differenza di stile e di comportamento:

Elemento form PrimeFaces
Elemento form PrimeFaces in errore

A differenza di RichFaces, solo i componenti PrimeFaces ricevono lo stile del framework, scoraggiando di fatto l’utente ad utilizzare i normali tag JSF se vuole mantenere una veste grafica (e funzionale a questo punto) coerente. In caso di errori di validazione infatti, la label e il campo di input vengono evidenziati di rosso. Una cosa curiosa sull’outputLabel: l’asterisco accanto al testo compare perché il campo di input a cui è riferito è obbligatorio!
Non saprei alla fine quale dei due approcci sia meglio, de gustibus…
A conti fatti, condivido l’entusiasmo della comunità JSF verso PrimeFaces, soprattutto per la sua integrazione stretta con jQuery. Ci sarebbe molto altro da dire ma credo che questo post sia diventato anche troppo lungo… Se qualcuno avesse notato altre differenze eclatanti da condividere questo è il posto giusto!

Per approfondimenti consiglio

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+