JSF Facelift

JSF Facelift: Få vil kunne glemme vores Pretty Faces oplæg på gå-hjem-mødet i marts, hvor Jacob Avlund og Jesper Tejlgaard gennemgik JSF 2.0 med sædvanlig skarphed og sorte solbriller.Vores dynamiske duo har nu forfattet en artikel på baggrund af oplægget i marts, hvor de gennemgår Facelets, Composite Components, annotationer, Ajax-requests, nye events og request-parametre, rigt illustreret med konkrete kodeeksempler. Jacob og Jesper har begge stor erfaring med JSF og tilsvarende frameworks og kommer i artiklen med deres ærlige vurdering af JSF 2.0 som framework til webudvikling.
JSF har længe været kritiseret for ikke at yde web-udviklerne den nødvendige hjælp i deres opgaveløsning. Resultatet af manglerne er en myriade af open source-frameworks, der alle løser de samme praktiske problemstillinger på forskellig vis. Med JSF 314, specifikationen af JSF 2.0, har Roger Kitain og Ed Burns forsøgt at imødekomme kritikerne og inkorporere den praktiske erfaring oparbejdet i open source verdenen. Spørgsmålet er så, om JSF 2.0 gør det nemmere at være web-udvikler?

At komme hele vejen omkring JSR 314 og/eller alle kritikpunkter, vil kræve mere plads, end denne artikel tillader. Vi fokuserer i stedet på nogle enkelte hovedændringer.

Facelets 2.0 og composite components

Facelets er efterhånden et af de mest benyttede open source JSF-frameworks. Det løser en række uhensigtsmæssigheder og tilføjer ekstra funktionalitet, som smidiggører udviklingen af JSF-applikationerne. En feature som templating er jo nærmest uundværlig i et JSF-projekt. Kitain og Burns har taget konsekvensen af dette og med JSR 314 inkluderet Facelets.

Facelets er dog ikke alene blevet inkluderet. Komponentudvikling er blevet simplificeret kraftigt i forhold til tidligere versioner af JSF, hvor man skulle skrive en Java-komponentklasse, Java tag handler, tag library descriptor (.tld) og registrere komponenter i faces-config.xml. Skulle man lave lidt mere kompliceret funktionalitet, krævede det oftest også indgående kendskab til JSF’s request lifecycle. Vores erfaring har indtil videre været, at det kan blive dyrt for et projekt.

Med Facelets 2.0 har man gjort det muligt at droppe Java-koden med de store mængder tilhørende XML-konfigurationer fuldstændigt. Vi vil med et eksempel illustrere hvordan det gøres.

Vi skal i gang med at lave et nyt website. Det har forholdvis mange formularer der skal udfyldes. Disse består altid af:

  • En tabelstruktur med tre kolonner – label, input felt og eventuel fejlbesked hvis udfyldt forkert.
  • En Submit-knap til at indsende indholdet.

En af vores formularsider ser således ud:

<html 
    xmlns_ui="http://java.sun.com/jsf/facelets"
    xmlns_h="http://java.sun.com/jsf/html"
    xmlns_f="http://java.sun.com/jsf/core"
    xmlns_lb="http://java.sun.com/jsf/composite/components/lundogbendsen">
  <f:view>
  ... andet kode ...

     <lb:inputForm submitLabel="Create" submitAction="#{person.create}">
        <lb:inputText label="Your name" value="#{person.name}" />
        <lb:inputText label="Your email" value="#{person.email}">
           <f:validateRegex pattern=".*@.*" for="input" />
        </lb:inputText>
        <lb:inputText label="Your age" value="#{person.age}">
           <f:validateLongRange minimum="0" maximum="140" for="input" />
        </lb:inputText>
     </lb:inputForm>

     ... andet kode ...
  </f:view>
</html>

Figur 1: Formularside, som gør brug af inputForm og inputText

Den opmærksomme læser har allerede lagt mærke til namespacet “lb” og de to nye børn i klassen <lb:inputForm> og <lb:inputText>. Begge er custom made. Sidstnævnte komponent repræsenterer et inputfelt med tilhørende label og fejlbesked. Førstnævnte repræsenterer den tabelstruktur som indeholder inputfelterne.
Som i resten af Java EE 6 platformen benytter JSF 2.0 sig i høj grad af convention over configuration. Efter dette princip er komponenterne implementeret i Facelets view-filerne, henholdsvis inputForm.xhtml og inputText.xhtml, og lokaliseret i web-applikationens folder ‘/components/lundogbendsen’ defineret ved namespacet.

Lad os se på implementationen af <lb:inputText>:

<html 
      xmlns_h="http://java.sun.com/jsf/html"
      xmlns_composite="http://java.sun.com/jsf/composite">
  <body>
    <composite:interface>
       <composite:attribute name="label" required="true" />
       <composite:attribute name="value" required="true" />
       <composite:editableValueHolder name="input" />
    </composite:interface>

    <composite:implementation>
       <tr>
          <td><h:outputLabel value="#{cc.attrs.label}:" id="label"/></td>
          <td><h:inputText id="input" value="#{cc.attrs.value}"
                 converterMessage="Could not convert #{cc.attrs.label}"
             validatorMessage="#{cc.attrs.label} is not valid"
             requiredMessage="#{cc.attrs.label} is required"
                 required="true"/></td>
          <td><h:message for="input" /></td>
       </tr>
    </composite:implementation>
  </body>
</html>

Figur 2: inputText.xhtml

Custom components består af et interface (<composite:interface>), som definerer komponentens konfigurerbare egenskaber, samt implementeringen af komponenten. I ovenstående eksempel ses, at label og value defineres i interfacet som krævede komponentattributter. En vigtig pointe her er, at komponentattributter både kan tage en værdi (vores brug af label) eller et EL-udtryk (vores brug af value). Derudover definerer <composite:editableValueHolder/> muligheden for at tilknytte validators til inputfeltet. Dette benyttes i siden for både email og age.

Komponenten inputForm gør brug af en, efter vores mening, meget vigtig mulighed for custom components. I figur 1 ses, at <lb:inputForm> har børn i form af <lb:inputText/>. Netop det at lave custom components, som kunne have børn, var tidligere en arbejdskrævende opgave, som skulle løses ved Java-klasser, tag library descriptors og faces-config.xml konfiguration . Vi skal se hvordan det nu kan gøres langt mere produktivt og intuitivt.

<html 
    xmlns_h="http://java.sun.com/jsf/html"
    xmlns_composite="http://java.sun.com/jsf/composite">
  <body>
    <composite:interface>
       <composite:attribute name="submitLabel" required="true"/>
       <composite:attribute name="submitAction" required="true"
                  method-signature="java.lang.String action()"/>
    </composite:interface>

    <composite:implementation>
       <h:form>
          <table width="#{cc.attrs.width}">
             <composite:insertChildren />
          </table>
          <h:commandButton value="#{cc.attrs.submitLabel}"
                           action="#{cc.attrs.submitAction}"
       </f:form>
    </composite:implementation>
  </body>
</html>

Figur 3: inputForm.xhtml

Hemmeligheden er at finde i komponentens implementering. Brugen af tagget <composite:insertChildren> definerer, at inputForm-komponenten kan tage en eller flere komponenter som børn. Denne mulighed er forfatterne ikke tidligere stødt på i JSF 1.x udvikling med de open source-frameworks vi har afprøvet. Featuren er stærk og vi er overbeviste om dens værdi for JSF udviklere. Det er desuden værd at notere sig, at komponenten også implementerer submitknappen, men at både knappens tekst og actionmetode konfigureres ved brug af komponenten.

Ovenstående eksempel er så fortænkt, at det er lige til en skolebog. Det illustrerer imidlertid fint hvor nemt, det er blevet at udvikle forholdsvis komplicerede komponenter med JSF 2.0. De to komponenter er helt og holdent skrevet i Facelets view-filer ved at benytte eksisterende JSF-tags. Væk er Java-klasser, registrering i faces-config.xml og den super verbose TLD-fil. Det skal indrømmes, at composite components også kunne konstrueres med JSF 1.X og Facelets 1.X, men udtrykskraften er stadig større i JSF 2.0 composite components. Blandt andet er det for første gang muligt at tilføje børn til en composite component. Det her kan kun ende med en produktivitetsforbedring i JSF udviklingen! Become a DRY (Don’t Repeat Yourself) developer!

Inkluderingen af Facelets i JSF 2.0-specifikationen er det eneste rigtige træk. Hovedaktørerne selv har været så overbeviste, at mange af de nye features kun understøttes, hvis Facelets bruges som view declaration language (VDL). Good old JSP kan stadig benyttes, men understøtter ikke alle JSF 2.0 spec’ens features. Har man eksempelvis behov for at lave Ajax- og/eller GET requests, som vi skal se på længere nede, så skal man skrive Facelets view files (typisk .xhtml).

Annotationer

JSF 2.0 mindsker, ligesom resten af Java EE 6-platformen, konfigurationsfilernes XML-helvede ved convention over configuration, samt indførsel af annotationer. Det er nu muligt at konfigurere managed beans, scopes, validators og converters ved brug af annotationer. Tager vi den managed bean (“person”), som var brugt til ovenstående side, kan den nu defineres på følgende facon:

package dk.lundogbendsen;

import java.io.Serializable;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean(name = "person")
@RequestScoped
public class Person implements Serializable {

   private static final long serialVersionUID = 1L;

   private String name;
   private String email;
   private Integer age;

   ... getter and setter methods...
}

Ajax

Siden JSF så dagens lys, er der sket ting og sager på web-fronten i den store vide verden – og én af de absolut mest dominerende nye trends er Ajax, der ved hjælp af client-side kode gør det muligt at lave asynkrone – og mere responsive – web-applikationer. JSF har traditionelt været ret fattigt på client-side kode som JavaScript, og Ajax-understøttelse har indtil nu ikke været en del af frameworket. Imidlertid har JSF’s komponentmodel gjort dette vakuum ideelt for tredjeparts framework-udviklere, idet de har kunnet levere komponenter baseret på JSF som indkapslede al den ”ajaxificerede” client-side kode, som server-side programmører helst holdt sig langt væk fra. Der går tretten på dusinet af disse frameworks, nogle af de mere kendte er RichFaces, IceFaces, Trinidad og PrimeFaces.

Imidlertid gør denne diversitet også området ret fragmenteret, og JSF 2.0 forsøger at råde bod på dette ved at implementere rudimentær Ajax-understøttelse. Der er ikke, som i mange af de eksisterende frameworks, tale om at komponenterne i sig selv er blevet decideret ajaxificerede men derimod, at de nu understøttes af et nyt tag (eller en ny JavaScript-funktion, hvis man insisterer på at arbejde mere low-level). Tagget, <f:ajax>, kan knyttes til en eksisterende komponent som f.eks. en commandButton, og denne komponent vil så fungere via Ajax-semantik i stedet for browserens normale synkrone request-response lifecycle.
Følgende eksempel viser to forskellige anvendelser af <f:ajax>:

<h:head>
</h:head>
<h:body>
  <h:dataTable var="lotteryNumber" value="#{lotteryNumbers.numbers}"
    id="numbers">
    <h:column>
      <f:facet name="header">
        Lottery numbers
      </f:facet>
    #{lotteryNumber}
    </h:column>
  </h:dataTable>
  <h:form>
    <h:commandButton value="Draw" action="#{lotteryNumbers.drawNumber}">
      <f:ajax execute="@this" render="@all" />
    </h:commandButton>
    <h:commandButton value="Reset">
      <f:ajax render="@all" listener="#{lotteryNumbers.reset}" />
    </h:commandButton>
  </h:form>
</h:body>

Ajax-taggene, indlejret under de to commandButtons, fortæller JSF, at disse knapper skal benyttes i et Ajax-kald i stedet for det normale roundtrip. Attributten ”execute” i det øverste tag fortæller JSF, hvilken komponent der skal eksekveres – i dette tilfælde @this, hvilket referer til den commandButton tagget er indlejret under. Det betyder, at dennes action (lotteryNumber.drawNumber) bliver kaldt, og modellen dermed opdateret. Attributten ”render” fortæller derimod JSF, hvilken del af siden der skal hentes forfra via Ajax, når eksekveringen er fundet sted – i dette tilfælde @all, hvilket betyder hele siden. Andre mulige værdier for execute og render er @form (den form tagget befinder sig i) og @none (ingenting). Et alternativ til execute er listener, som det andet tag benytter – dette refererer til en metode, som har void som returtype og tager en AjaxBehaviorEvent som parameter, altså lidt i stil med ActionListeners.

Mere specifikt sker der det, at den traditionelle JSF-livscyklus med de 6 faser (Restore View, Apply Request Values, Process Validators, Update Model, Invoke Application og Render Response) er blevet delt op, så execute-attributten via Ajax beder komponenten løbe igennem de fem første faser, og render-attributten beder komponenten om at køre den sidste.

Den opmærksomme læser vil i øvrigt bemærke, at der er kommet to andre nye tags til – <h:head> og <h:body> – der naturligt nok svarer til HTML-taggene med samme navn. Idet <f:ajax> er baseret på JavaScript, er <h:head> påkrævet for at bruge dette tag, da det loader den nødvendige client-side funktionalitet.

GET-requests og events

Med enkelte undtagelser har alt det HTTP, som JSF hidtil har benyttet sig af, været af metoden POST. GET-requests har i det store hele været komplet og aldeles negligeret, hvilket var en smule problematisk f.eks. i forbindelse med browserens frem- og tilbage-knapper og bookmarks (at indsætte såkaldte redirects i faces-config har gjort det muligt at omgå  problemerne, men en sådan løsning afstedkommer imidlertid andre udfordringer). I JSF 2.0 har man endelig taget konsekvensen og leveret en mere fornuftig understøttelse af GET.

Et klassisk eksempel er noget så simpelt, som at arbejde med querystring-parametre i requestet. Det gøres i JSF 2.0 på følgende måde:

<f:metadata>
  <f:viewParam name="extra" value="#{lotteryNumbers.extra}" />
</f:metadata>

Med ovenstående vil vi nu kunne lave RESTful url’er til JSF-sider, fx “http://www.javaee6.dk/lottery.jsf?extra=21.”

Vi har jo i JSF 1.X været vant til, at JSF opdaterer vores model for os via requestet, og det er såmænd også hvad der sker i ovenstående. Tagget <f:metadata> definerer noget information om siden, der ikke er en del af selve sidens output som sådan. Det indlejrede tag <f:viewParam> definerer, at GET-parametret med navn ”extra” (tillægstal til lotteriet…) skal indlæses og lægges ned i property’en extra på vores managed bean lotteryNumbers. Dette sker vel at mærke inden siden overhovedet loades – metadata-tagget vil typisk placeres så tidligt på siden som muligt (f.eks. lige under XML namespace-deklarationerne, som typisk sker i tagget <html>).

Med <f:metadata> har vi nu også en løsning på en anden klassisk problemstilling, som JSF-udviklere længe har døjet med: muligheden for at afvikle kode før en JSF-side loades og renderes. Dette gøres ved hjælp af en ny, strammere event-model. Bemærk følgende tilføjelse:

<f:metadata>
  <f:viewParam name="extra" value="#{lotteryNumbers.extra}" />
  <f:event type="preRenderView" listener="#{lotteryNumbers.checkExtras}" />
</f:metadata>

JSF 2.0 understøtter en række specifikke events, som man kan knytte listeners til – lidt ligesom klassiske phaselisteners, men mere elegant. I dette tilfælde har vi føjet en listener til ”preRenderView”-eventen, altså lige inden komponenttræet renderes. Det er et helt åbenlyst sted at placere kode, som skal køres før siden vises – og evt. inddrage den modelændring, som vi netop lavede ved at injecte GET-parametret (”extra”) i vores model. Andre typer events man kan bruge inkluderer preRenderComponent, postAddToView, preValidate, postValidate osv.

Konklusion

Det er helt åbenlyst, at JSF’s skabere har kigget dybt og grundigt på mange af de problemstillinger, som JSF-udviklere til daglig sidder overfor, og søgt at løse disse på fornuftig vis. I det store hele er mange af de nyheder, JSF 2.0 byder på, standardiseringer af eksisterende løsninger (Facelets, Ajax, etc.). Men at de netop inkluderer best practice gennem en lang årrække, berettiger JSF’s fortsatte eksistens på Java EE-platformen. JSF 2.0 imødekommer en masse af vores personlige kritikpunkter ved JSF 1.X, og vi glæder os til at se JSF 2.0 i produktionsløsninger.