jsf 2: best practices und hidden features
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.TRANSCRIPT
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