JSF 2: Best Practices und Hidden Features

Preview:

DESCRIPTION

Speaker: Lars Röwekamp W-JAX 2012 Eine Webanwendung auf Basis von JSF 2 zu bauen ist nicht immer nur lustig. Ein ungewohntes Programmiermodell, scheinbare Spezifikationslücken und eine Menge Ungereimtheiten. Wer soll da noch durchsteigen? Die Session zeigt anhand eines praktischen Beispiels essenzielle Best Practices und Hidden Features von JSF. Zusätzlich werden einige wirklich nützliche JSF Libraries und Frameworks vorgestellt.

Citation preview

Lars Röwekamp | open knowledge GmbH | @mobileLarson

Hidden Features

JSF 2

Best

Pra

ctic

es

Don‘t ask me, RTFS!

„Come on, that‘s way to much.“*

*JSF 2 Spec: > 500P

Bookmarking

Validation

Ajax

ComponentsBehavior

Stuff

Ajax

It‘s sooo easy!

Ajax

2.) „Was soll gerendert werden?“

1.) „Was soll ausgeführt werden?“

Ajax> Asynchrones Request/Response Handling> Partitial Execute/Rendering via Lifecycle> JSF Component Tree > Ajax Request Status Handling> Ajax Request Error Handling

> Easy to use: f:ajax-Tag > Total control: jsf.ajax.request( )

Ajax<!-- Das AJAX TAG --><h:form ...> <h:commandButton value=“Update“> <f:ajax execute=“@form“ render=“updateMe“ /> </h:commandButton>

<h:outputTextField value=“#{aBeansValue}“ id=“updateMe“ /></h:form>

Ajax<f:ajax execute = „wen ausführen?“ render = „wen updaten?“ event = „auf was reagieren?“ listener = „wen interessiert‘s noch?“ onevent = „zusätzliche JavaScript Callback-Funktion“ onerror = „zusätzliche JavaScript Callback-Fehler-Funktion“ />

Ajax<!-- Das AJAX TAG (Master / Detail) --><h:selectOneMenu id="master" value="#{demo.master}"> <f:selectItems value="#{demo.masterItems}"/> <f:ajax render="detail" listener="#{demo.masterChanged}"/></h:selectOneMenu>

<h:selectOneMenu id="detail" value="#{demo.detail}"> <f:selectItems value="#{demo.detailItems}"/></h:selectOneMenu>

Ajax<f:ajax execute = „wen ausführen?“ render = „wen updaten?“ event = „auf was reagieren?“ listener = „wen interessiert‘s?“ onevent = „zusätzliche JavaScript Callback-Funktion“ onerror = „zusätzliche JavaScript Callback-Fehler-Funktion“ />

Ajax<f:ajax execute = @this render = @none event = action (Button/Link) valueChange (sonst) listener = @none onevent = status: begin, complete, success source: triggering DOM event responseCode, responseText/XML/>

Ajax<!-- Die JavaScript API (Master/Detail) --><h:selectOneMenu id="master" value="#{demo.master}" valueChangeListener="#{demo.masterChanged}" onChange="jsf.ajax.request( this, event, {render: detail});"/>

<h:selectOneMenu id="detail" value="#{demo.detail}"> <f:selectItems value="#{demo.detailItems}"/></h:selectOneMenu>

Ajax<!-- Die JavaScript API (Master/Detail) --><h:selectOneMenu id="master" value="#{demo.master}" valueChangeListener="#{demo.masterChanged}" onChange="jsf.ajax.request( this, event, {render: detail});"/>

<h:selectOneMenu id="detail" value="#{demo.detail}"> <f:selectItems value="#{demo.detailItems}"/></h:selectOneMenu>

Source Option(s)Event

AjaxPitfalls> inner/outer Component IDs> Ajax in Custom Components> Ajax Status Feedback

„Want some cool stuff?“

Ajax<!-- axaj response manipulation via PartitialResponseWriter (PrimeFaces style)--><partial-response> <changes> <update id="abc">...</update> <update id="xyz“>...</update> <extension ln="primefaces" type="args"> {"loggedIn":false} </extension> </changes></partial-response>

http://www.primefaces.org/showcase/ui/dialogLogin.jsf

Ajax<!-- axaj response handling inside JSF view (PrimeFaces style)--><script type="text/javascript">    function handleLoginRequest(xhr, status, args) {      if(args.validationFailed || !args.loggedIn) {        jQuery('#dialog') .effect("shake", { times:3 }, 100);      } else {        dlg.hide();        jQuery('#loginLink').fadeOut();      }    }  </script>  

Ajax// PartitialResponseWriter (PrimeFaces style)

@Overridepublic void endDocument() throws IOException {  Map<String, String> attributes = new HashMap<String, String>();  attributes.put("ln", "primefaces");  attributes.put("type", "args");  startExtension(attributes);  write("{"loggedIn":false}");  endExtension();  super.endDocument();}

Behavior

Behavior> Idee: Komponente um clientseitige Funktionalität erweitern, die vom Autor ursprünglich nicht vorgesehen war.

> Mittel: JSF Behavior API zur Erweiterung beliebiger Komponenten um Client-side Scripting

Behavior<!-- Behavior in Action --><h:form ...> <h:commandButton value=“Update“> <f:ajax execute=“@form“ render=“updateMe“ /> </h:commandButton>

<h:outputTextField value=“#{aValue}“ id=“updateMe“ /></h:form>

Standard Behavior

Behavior> Client-side Validation> Client-side Logging> DOM & Style Manipulation> Animationen & visuelle Effekte> Alerts & Confirmation Dialoge> Lazy Data Fetching> Integration mit 3rd Party Libraries> ...

BehaviorMain Player

> ClientBehavior a.k.a. Script Generator: zuständig für Generierung von passendem Skript

> ClientBehaviorHolder a.k.a. Vermittler: zuständig für das Wiring zwischen Komponente, Event und ClientBehavior

Behavior<!-- BEHAVIOR in action -->

// chain of behaviors <h:commandButton value=“Update“>

// 1. ask user for permission <mystuff:confirm event=“click“ />

// 2. if YES send AJAX call <f:ajax event=“click“ render=“updateMe“ />

</h:commandButton>

„My“ Behavior

Standard Behavior

Behaviorpackage de.openknowledge.example.behavior; @FacesBehavior(xyz.behavior.Confirm)public class ConfirmBehavior

extends ClientBehaviorBase {

@Override public String getScript(

ClientBehaviorContext behaviorContext) { return “return confirm(‘Are your sure?‘)“;

}}

„My“ Behavior

Behavior<?xml version='1.0' encoding='UTF-8'?><facelet-taglib xmlns="..." version="2.0"> <namespace>http://xyz.de/mystuff</namespace> <tag> <tag-name>confirm</tag-name> <behavior> <behavior-id> xyz.behavior.Confirm </behavior-id> </behavior> </tag></facelet-taglib>

Facelets TagLib

in WEB-INF o. META-INF

Server-side Action?

Behavior<!-- BEHAVIOR with server-side action -->

// input field with behavior <h:inputText value=“#{someValue}“>

// inject jsf.ajax.request/show suggestions <foo:suggest suggestions=“#{serverSuggestions}“ />

</h:inputText>Server-side Action

Behavior// BEHAVIOR with server-side action

// participate in request decodingpublic void decode(FacesContext context, UIComponent uiComponent) {

// create suggestion list via // behavior directly or via service or ... ...}

Server-side Action

Component

It‘s so easy, again!

Components<html xmlns=“http://www.w3.org/1999/xhtml“ xmlns:composite=“.../jsf/composite“ > <!--INTERFACE --> <composite:interface> ... </composite:interface>

<!--IMPLEMENTATION --> <composite:implementation> ... </composite:implementation></html>

Komponente liegt unter: ./resources/comp/util/myComp.xhtml

Comp Interface

Comp Implementation

Components<html xmlns=“http://www.w3.org/1999/xhtml“ ... xmnls:util= “http://java.sun.com/jsf/composite/ comp/util“ > ... <util:myComp ... /> ...

</html>

Komponente liegt unter: ./resources/comp/util/myComp.xhtml

Comp in Action

Components> XHTML plus> Interface & Implementation> Convention over Configuration> Convention over Code

> Pitfall „couldn‘t find ID“> Pitfall „couldn‘t find ACTION“> Pitfall „couldn‘t add VALIDATOR “> Pitfall „couldn‘t add CHILDS“> Pitfall „couldn‘t add I18N“

Components> XHTML plus> Interface & Implementation> Convention over Configuration> Convention over Code

> Pitfall „couldn‘t find ID“> Pitfall „couldn‘t find ACTION“> Pitfall „couldn‘t add VALIDATOR “> Pitfall „couldn‘t add CHILDS“> Pitfall „couldn‘t add I18N“

Components

Components

Components<html xmlns= ... > <!--INTERFACE --> <composite:interface> <composite:attribute name=“user“ /> <composite:attribute name=“userLabel“ /> <composite:attribute name=“pwdLabel“ /> <composite:attribute name=“loginBtnLabel“/> <composite:attribute name=“action“ method-signature=“java.lang.String action()“/> </composite:interface>

...

</html>

Components<html xmlns= ... > <!--INTERFACE --> <composite:interface> <composite:attribute name=“user“ /> <composite:attribute name=“userLabel“ /> <composite:attribute name=“pwdLabel“ /> <composite:attribute name=“loginBtnLabel“/> <composite:attribute name=“action“ method-signature=“java.lang.String action()“/> </composite:interface>

...

</html>Reserved Qualifier!

Components<html xmlns= ... > <!--INTERFACE --> <composite:interface> <composite:attribute name=“user“ /> <composite:attribute name=“userLabel“ /> <composite:attribute name=“pwdLabel“ /> <composite:attribute name=“loginBtnLabel“/> <composite:attribute name=“loginAction“ method-signature=“java.lang.String action()“/> </composite:interface>

...

</html>

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> <h:panelGrid columns=“2“> #{cc.attrs.namePrompt} <h:inputText id=“name“ value=“#{cc.attrs.user.name}“/> ... </h:panelGrid> <h:commandButton id=“loginBtn“ value=“#{cc.attrs.loginBtnLabel}“ action=“#{cc.attrs.loginAction}“ /> </h:form> <p>Die Super-Login-Komponente von open knowledge</p> </composite:implementation/></html>

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> <h:panelGrid columns=“2“> #{cc.attrs.namePrompt} <h:inputText id=“name“ value=“#{cc.attrs.user.name}“/> ... </h:panelGrid> <h:commandButton id=“loginBtn“ value=“#{cc.attrs.loginBtnLabel}“ action=“#{cc.attrs.loginAction}“ /> </h:form> <p>Die Super-Login-Komponente von open knowledge</p> </composite:implementation/></html>

Dependency to User

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> <h:panelGrid columns=“2“> #{cc.attrs.namePrompt} <h:inputText id=“name“ value=“#{cc.attrs.user.name}“/> ... </h:panelGrid> <h:commandButton id=“loginBtn“ value=“#{cc.attrs.loginBtnLabel}“ action=“#{cc.attrs.loginAction}“ /> </h:form> <p>Die Super-Login-Komponente von open knowledge</p> </composite:implementation/></html>

Dependency to User

I18N

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> <h:panelGrid columns=“2“> #{cc.attrs.namePrompt} <h:inputText id=“name“ value=“#{cc.attrs.user.name}“/> ... </h:panelGrid> <h:commandButton id=“loginBtn“ value=“#{cc.attrs.loginBtnLabel}“ action=“#{cc.attrs.loginAction}“ /> </h:form> <p>Die Super-Login-Komponente von open knowledge</p> </composite:implementation/></html>

Dependency to User

I18N

id=“form:name“

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> <h:panelGrid columns=“2“> #{cc.attrs.namePrompt} <h:inputText id=“name“ value=“#{cc.attrs.user.name}“/> ... </h:panelGrid> <h:commandButton id=“loginBtn“ value=“#{cc.attrs.loginBtnLabel}“ action=“#{cc.attrs.loginAction}“ /> </h:form> <p>Die Super-Login-Komponente von open knowledge</p> </composite:implementation/></html>

Dependency to User

I18N

id=“form:name“

Child Tags, Facets,...?

Components<html xmlns= ... > <!--INTERFACE --> <composite:interface> <composite:attribute name=“user“ /> <composite:attribute name=“userName“ /> <composite:attribute name=“userPwd“ /> ...

</composite:interface>

...

</html>

Dependency to User

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> ... </h:form>

<p>Die Super-Login-Komponente von open knowledge</p> <p>#{cc.resourceBundleMap.myFooterText}</p> </composite:implementation/></html>

I18N

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <h:form id=“form“> ... </h:form>

<p>Die Super-Login-Komponente von open knowledge</p> <p>#{cc.resourceBundleMap.myFooterText}</p> </composite:implementation/></html>

I18N

Components<html xmlns= ... > <!--INTERFACE --> <composite:interface>

<composite:attribute name=“userName“ /><composite:attribute name=“userPwd“ />

<composite:editableValueHolder name=“userName“ targets=“form:name“/> <composite:editableValueHolder name=“userPwd“ targets=“form:pwd“/> <composite:editableValueHolder name=“allFields“ targets=“form:name form:pwd“/> <composite:actionSource name=“loginButton“ targets=“form:loginBtn“/> ... </composite:interface> ...</html>

Expose Components

Components<html xmlns= ... > ... <!--IMPLEMENTATION --> <composite:implementation> <composite:renderFacet name=“header“ /> <h:form id=“form“> ... </h:form> <composite:insertChildren/> </composite:implementation/></html>

Child Tags

Facets

Bookmark

BookmarksJSF erlaubt „Bookmarking“, aber ...

> URLs scheinen hinterher zu hängen> URLs sind unschön> URLs implizieren i.d.R. einen State

Bookmarks

Bookmarks

Bookmarks

BookmarksJSF 1.x „Bookmarking“

> <h:outputLink> für GET> PhaseListener zur Manipulation> redirect um aus Post ein Get zu machen

BookmarksJSF 1.x „Bookmarking“

> <h:outputLink> für GET> PhaseListener zur Manipulation> redirect um aus Post ein Get zu machen

JSF 2.x „Bookmarking“

> <h:link> oder <h:button> für GET> <f:viewParam> zum Setzen von Params> <f:event type=“preRenderView“ zum Laden von benötigten Daten

Bookmarks

Bookmarks

<!-- ! Bookmarkable link for user, e.g.: ! <a href=“/context/user.xhtml?id=1234“>..</a>!-->!<h:link value=“Details von #{user.name}“! outcome="user">! <f:param name="id" value="#{user.id}" />!</h:link>!

Step 1: Create URL

userList.xhtml

Bookmarks<!-- ! Use bookmarkable link for user, e.g.: ! <a href=“/context/user.xhtml?id=1234“>..</a>!-->!<f:metadata>! <f:viewParam name="id" ! value="#{userManager.userId}" />! <f:event type="preRenderView" ! listener="#{userManager.loadUser}" />!</f:metadata>!<h:head>...</h:head>!<h:body>! ... <!-- display user details -->!</h:body>!

Step 2: Use URL

user.xhtml

Bookmarks<!-- ! Use bookmarkable link for user, e.g.: ! <a href=“/context/user.xhtml?id=1234“>..</a>!-->!<f:metadata>! <f:viewParam name="id" ! value="#{userManager.userId}" />! <f:viewAction action="#{userManager.loadUser}" />!!</f:metadata>!<h:head>...</h:head>!<h:body>! ... <!-- display user details -->!</h:body>!

Step 2: Use URL

ab JSF 2.2!

user.xhtml

Geht da noch mehr?

JSF 2.x URIs

> Ugly www.demo.de/faces/showCust?id=54

> Nice www.demo.de/customer/meier/hans

Bookmarks

JSF 2.x URIs

> WTH? www.demo.de/list?type=cust&f=a&t=d

> Ahh, alles klarwww.demo.de/customerlist/from/a/to/d

Bookmarks

JSF meets

PrettyFaces

Pretty Faces - URL Rewriting

> localhost:8080/faces/start.xhtml> localhost:8080/start

<url-mapping id="start“> <pattern value="/start" /> <view-id>/faces/start.xhtml</view-id></url-mapping>

Bookmarks

Pretty Faces - URL Rewriting

> .../faces/cust/d.xhtml?c=mobileLarson > .../customer/mobileLarson

<url-mapping id=“customerDetails“> <pattern value=“/customer/ #{name : custBean.username}" /> <view-id>/faces/cust/d.xhtml</view-id></url-mapping>

Bookmarks

Pretty Faces - URL Rewriting

> .../customer/mobileLarson

<url-mapping id=“customerDetails“> <pattern> ... </pattern> <view-id> ... </view-id> <action>#{custBean.loadCust}</action></url-mapping>

Bookmarks

Validation

Ready to use!

Validation

> Component Validation - check> Cross-Layer Validierung - kind of check> Cross-Component Validation - what?

Validation

> Component Validation - check> Cross-Layer Validierung - kind of check> Cross-Component Validation - what?

ValidationCross-Component Validation

> Validierung über mehrere Komponenten

> username != password> password == password repeat> password Validation wie in Klasse XYZ

ValidationCross-Component Validation

> Alternative A: externe Validation Lib, z.B. Apache MyFaces ExtVal

> Alternative B: Self-Made Validator inkl. Zugriff auf Komponente(n)

> Alternative C: JSF System Events, d.h. pre/postValidate Callbacks

Stuff

Stuff<!-- classic “debugging“ via ui:remove --><html><h:head /><h:body><h:form>   Text to display.   <ui:remove>Text to remove</ui:remove>   Text to display.</h:form></body></html>

Stuff<!-- classic “debugging“ via ui:debug --><html ... ><h:head> ... </h:head><body> ... <ui:debug hotkey="0" rendered= "#{initParam['javax.faces.PROJECT_STAGE'] eq 'Development'}" />

</body></html>

Stuff<!-- classic “debugging“ via ui:debug --><html ... ><h:head> ... </h:head><body> ... <ui:debug hotkey="0" rendered= "#{initParam['javax.faces.PROJECT_STAGE'] eq 'Development'}" />

</body></html>

Lars Röwekamp | CIO New Technologies | @mobileLarson

Hidden Features

Thankyou

Best

Pra

ctic

es

Recommended