JSF Ext oder JSF Extensions (vorher JSF Desktop) ist eine Erweiterung von JSF 2 (2.0, 2.1) um einige grundlegende Features, die man für saubere, funktionale AJAX-Applikationen braucht.
Einige der Features:
Damit setzen Sie komplexe, interaktive Applications im Handumdrehen auf.
<dependency> <groupId>com.intersult</groupId> <artifactId>jsf-ext</artifactId> <version>1.1-SNAPSHOT</version> </dependency> <repository> <id>intersult-repository</id> <name>Intersult Repository</name> <url>http://repository.intersult.com/repository</url> </repository>
JSF Ext includes namespaces e and ext. There are two namespaces, because JSF Ext contains both classic tags and on-the-fly facelet tags. In your XHTML-Pages, include the namespaces:
xmlns:e="http://java.sun.com/jsf/ext" xmlns:ext="http://java.sun.com/jsf/composite/ext"
Falls man feststellt, dass die Events im JSF Ext nicht arbeiten, Oberflächenelemente nicht aktualisiert werden und Messages nicht angezeigt werden, dann arbeiten vermutlich auch übliche JSF-Annotations @ManagedBean oder @SessionScoped nicht. Abhilfe schafft dann:
<context-param> <param-name>com.sun.faces.injectionProvider</param-name> <param-value>com.intersult.jsf_ext.event.EventInjectionProvider</param-value> </context-param>
Imagin a component which evenly distributes Command-Buttons in a form. You'de like to create a component (luckily this is already included in JSF Ext). The component should wrap each contained component in a SPAN-Tag with some padding.
Solution: You iterate through the children and write a span for each child. Into the span you insert the child itself. With normal JSF-Tags this is not possible, you need <i:insert>
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:cc="http://java.sun.com/jsf/composite" xmlns:e="http://java.sun.com/jsf/ext" xmlns:ext="http://java.sun.com/jsf/composite/ext" > <cc:interface> <cc:attribute name="align"/> </cc:interface> <cc:implementation> <div style="margin-top: 10px; text-align: #{empty cc.attrs.align ? 'center' : cc.attrs.align};"> <ui:repeat value="#{cc.children}" var="child"> <span style="margin-right: 5px;"> <i:insert component="#{child}"/> </span> </ui:repeat> </div> </cc:implementation> </html>
In vielen Fällen hab man bereits Behavior-Components zu einem Tag hinzugefügt. Würde man dennoch das entsprechende Attribut verwenden (z.B. onclick bei commandButton), kann es zu Problemen kommen, wie die Reihenfolge der Snippets oder das "return false;"-Anhängsel.
Lösung bringt der Behavior-Tag von JSF Ext. Typisch ist zum Beispiel die Verwendung zusammen mit dem AJAX-Tag:
<h:commandButton value="X"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup-panel')}.hide(event);"/> </h:commandButton>
Die Behavior-Snippets werden in der entsprechenden Reihenfolge produziert:
jsf.util.chain(this,event,'mojarra.ab(this,event,\'action\',0,0)','RichFaces.$(\'popup:form:popup-panel\').hide(event);');return false
<h:form id="test-form"> <h:panelGrid columns="3"> <h:outputText value="Name"/> <h:inputText id="name" value="#{bean.name}" required="true"/> <h:message for="name"/> </h:panelGrid> </h:form> <h:form id="other-form"> <h:commandButton id="tag-save" value="Tag Button" action="#{bean.save}"> <e:ajax source=":test-form" render=":test-form"/> </h:commandButton> </h:form>
Der Tag <e:ajax> ersetzt in diesem Fall den Tag <f:ajax>, er braucht nicht zusätzlich angegeben zu werden. Der Tag kann auch Actions abschicken, wenn er an einfachen ClientBehaviorHolder gehängt wird, weil er selbst eine ActionSource darstellt:
<h:panelGrid> <f:ajax event="dblclick" action="#{bean.action}"/> </h:panelGrid>
Name | Java | Parameter | Beschreibung |
---|---|---|---|
com.intersult.post_construct | Event.EVENT_POST_CONSTRUCT | Object managedBean | Der Event wird nach dem Erzeugen einer managed Bean durch das JSF-System erzeugt. |
com.intersult.pre_destroy | Event.EVENT_PRE_DESTROY | Der Event wird vor dem Entfernen einer managed Bean erzeugt. | |
com.intersult.event | Event.EVENT_EVENT | String event, Object... arguments | Dieser Meta-Event wird bei jedem Event erzeugt, sodass allgemeine Event-Handler geschrieben werden können. Dies ist mit Vorsicht zu verwenden. |
com.intersult.messages | MessageHandler.EVENT | - | Der Event wird erzeugt, wenn Faces Messages vorliegen. |
login.before | Login.EVENT_LOGIN_BEFORE | - | Bevor ein Benutzer eingeloggt wird. |
login.success | Login.EVENT_LOGIN_SUCCESS | - | Nachdem ein Benutzer erfolgreich eingeloggt wurde. Der Benutzer ist über Login.instance().getUser() abrufbar. |
login.fail | Login.EVENT_LOGIN_FAIL | - | Nachdem ein Login fehlgeschlagen ist. |
login.after | Login.EVENT_LOGIN_AFTER | - | Nach dem Login-Prozess, unabhängig ob er erfolgreich oder fehlgeschlagen ist. |
login.logout | Login.EVENT_LOGOUT | - | Nach dem Logout. |
Das Session basierte Event-Objekt enthält die Methode raise(String event, Object... arguments), also im einfachsten Fall:
Event.instance().raise("com.intersult.some-event");
Die Events werden in der Regel durch eine Annotation konsumiert:
@Listener("com.intersult.some-event") public void someEvent() { }
Es ist zu beachten, dass Events nur empfangen werden wenn eine Bean tatsächlich instantiiert ist.
<h:messages id="messages" globalOnly="true"> <e:update event="com.intersult.messages"/> </h:message>
Ergebnis: Die Faces-Messages werden gerendered, ohne dass bei jedem AJAX-Tag ein gesondertes Rendered-Attribut angegeben werden muss.
<h:commandButton value="Submit"> <f:ajax execute="@form"/> <e:raise name="com.intersult.test"/> </h:commandButton>
Hier ist zu beachten, dass ein AJAX-Tag zusätzlich verwendet wird. Bei vollen GET- oder POST-Requests machen Events keinen Sinn, da hier sowieso die komplette Seite gerendered werden würde.
Das Konsumieren kann wieder auf ähnliche Weise erfolgen:
<h:outputText id="output" value="#{bean.text}"> <e:update event="com.intersult.test"/> </h:outputText>
Es ist zu beachten, dass bei der Update-Component (hier outputText) eine Id angegeben wird, damit der AJAX-Handler das Rerendering zuordnen kann. Dies tritt beim Verwenden des render-Attributs nicht auf, da durch die Angabe einer Id ja eine vergeben wurde.
<e:scope id="scope-id"> <h:outputText value="#{scope.id}"/> </e:scope>
<e:scope id="popup" viewId="/popup.xhtml"/>
Diese Definition fügt einen Scope in den Component-Tree ein. Der Scope ist ein UINamingContainer, die Id wird also zu jeder enthaltenen Komponente als Prefix vorangestellt. Zum Beispiel erzeugt <h:inputText id="name" value="#{bean.name}"/> die Ausgabe von <input name="popup:name">.
Dieser Scope verweist auf die viewId "/popup.xhtml", damit ist ein Facelet innerhalb der Web-Applikation gemeint, vergleichbar zu <ui:include>. Das Facelet wird allerdings (zunächst) nicht geladen, im Komponentenbaum befindet sich ausschließlich die Scope Component.
<e:load scopeId="popup"/>
Das Load-Tag ist ein ActionListener, wird also unterhalb einer ActionSource eingefügt, wie zum Beispiel das <f:actionListener> oder <f:setPropertyActionListener>. Im Zusammenspiel mit AJAX kann nun der Scope dynamisch geladen werden:
<h:commandButton id="load-popup" value="Load Popup"> <f:ajax/> <e:load scopeId="popup"/> </h:commandButton>
Beim Laden des Scopes können zusätzlich Parameter übergeben werden:
<h:commandButton id="load-popup" value="Load Popup"> <f:ajax/> <e:load scopeId="popup" value="Load Popup"> <f:param name="someParam" value="someValue"/> <f:param name="someExpression" value="#{bean.someValue}"/> </e:load> </h:commandButton>
Das Expression-Binding ist momentan Read-Only, das heiße es kann nicht in Input-Tags verwendet werden. Das wird sich in zukünftigen Versionen erweitert werden.
Das geladene Popup-Facelet "/popup.xhtml" kann zum Beispiel so aussehen:
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:rich="http://richfaces.org/rich" xmlns:a4j="http://richfaces.org/a4j" xmlns:t="http://siemens.com/test" xmlns:app="http://java.sun.com/jsf/composite/app" xmlns:e="http://java.sun.com/jsf/ext" xmlns:ext="http://java.sun.com/jsf/composite/ext" > <h:form id="form"> <rich:popupPanel id="popup-panel" show="true" autosized="true" modal="false"> <f:facet name="header"> <h:outputText value="Loaded Popup"/> </f:facet> <f:facet name="controls"> <h:commandButton value="X"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup-panel')}.hide(event);"/> </h:commandButton> </f:facet> <h:outputText value="#{bean.popupText}"/> <ext:buttons> <h:commandButton id="save" value="Save" action="#{bean.save}"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup-panel')}.hide(event);"/> </h:commandButton> <h:commandButton id="cancel" value="Cancel"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup-panel')}.hide(event);"/> </h:commandButton> </ext:buttons> </rich:popupPanel> </h:form> </ui:composition>
Hier ist auch das Unload-Tag enthalten:
<h:commandButton value="X"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup-panel')}.hide(event);"/> </h:commandButton>
Es handelt sich wie beim Load-Tag um einen commandButton, der mit AJAX-Tag erweitert wurde. Unload ist wie Load ein ActionListener. Allerdings braucht hier keine Scope-Id angegeben werden, da wir uns ja innerhalb des Scopes befinden.
Ein Klasse im Custom-Scope kann so aussehen:
@ManagedBean @CustomScoped("#{scopes.scope}") public class FieldEdit implements Serializable { private static final long serialVersionUID = 1L; @ManagedProperty("#{scopes.scope.value}") private Field field; public Field getField() { if (field == null) field = new Field(); return field; } public void setField(Field field) { this.field = field; } public void save() { ProcessService.saveField(field); Event.instance().raise("workflow." + field.getWorkflow().getId() + ".fieldList.change"); Resource.addMessage( FacesMessage.SEVERITY_INFO, "field.save.success", field.getName(), field.getWorkflow().getName()); Scopes.instance().getScope().unload(); } }
Hier spart die save-Methode eine Menge Code, bei erfolgreicher Durchführung wird durch Scopes.instance().getScope().unload() der Scope beendet, die enthaltenen Daten freigegeben zur Garbage Collection sowie ein dahinerliegendes Popup geschlossen. Es sind keine weiteren XHTML-Elemente erforderlich, da der Render-Event dadurch ebenfalls erzeugt wird.
<c:forEach begin="1" end="3" var="index"> <e:scope id="scope-#{index}" load="true"> <e:param name="scopeVar" value="scope-value-#{index}"/> <div style="padding: 3px; margin: 3px; background-color: #e0e0e0; width: 400px;"> <h:outputText value="Inside: #{scope.id}, #{scope.scopeVar}"/> <e:scope id="nested" load="true"> <e:param name="innerVar" value="value-#{index}-#{scope.parent.scopeVar}"/> <h:outputText value="Nested: #{scope.innerVar}"/> </e:scope> </div> </e:scope> </c:forEach>
In der Praxis würde man eher Facelets laden mit dem Load-Tag und kein festes Attribut load="true" angeben.
Bei einigen Features und Konstellationen ist es von Vorteil, bestimmte Konfigurationen vorzunehmen.