[JSF Ext] oder [JSF Extensions|JSF Ext] (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: * Vereinfachte Fehlerbehandlung * Dynamisches Laden von AJAX-Facelets * Facelet-Scopes mit komfortabler Load-/Unload-Funktion * Einfach nutzbare Event-Erzeugung und -Empfang !!!Inhalt [{TableOfContents title='Page contents' numbered='true'}] !!!Overview The main features of the [JSF-Ext] (formerly JSF Desktop) are: * __Events:__ Event sources and registration. Java beans and components can produce events, other beans and components can consume them. This includes rendering and updating components, even using AJAX requests. * __Messages:__ Clean message handling. Exceptions are converted to faces messages, using normal or AJAX requests. An event is raised, thus you can update your message components. * __Dynamic Includes:__ Clean component tree and small network traffic. Facelets can dynamically are loaded from XHTML sources when needed through AJAX requests. No more overloaded component trees, no waiting for loading all the megabytes to your browser. You can include as much popups, tabs and other elements in your page without slowing it down. Everything is just loaded when needed. And much better, its also can be unloaded when finished. Both DOM and component tree stays clean. * __Custom Scopes:__ Use custom scopes within your loaded facelets, they are cleaned up when finished. No more manual clearing of data from popups. Nice memory footprint and save data. * __Integration:__ [JSF-Ext] works perfectly together with Richfaces, Primefaces and other component libraries. !!!Kompatibilität Demo unserer [Desktop-Plattform|Intersult Plattform] auf der Basis von [JSF] v2, die auch auf [GAE|Google Application Engine] lauffähig ist: Damit setzen Sie komplexe, interaktive [Applications|Application] im Handumdrehen auf. !!!Konfiguration In der pom.xml wird angegeben: {{{ <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" }}} !!Annotations und InjectionProvider [JSF Ext] ist darauf ausgelegt, ohne größere Konfiguration benutzt werden zu können. Viele Features können Out-of-the-Box verwendet werden, sobald sich das jsf-ext.jar in der Web-Application in /WEB-INF/lib befindet. Bei einigen Features und Konstellationen ist es von Vorteil, bestimmte Konfigurationen vorzunehmen. Wie bereits in [JSF2] erwähnt, werden in einigen Servlet-Containern die Annotations nicht verarbeitet. [JSF Ext] registriert einen sogenannten InjectionProvider, was im Normalfall transparent funktioniert. 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> }}} !!!Tags Das [JSF Ext] enthält eine Reine von Tags. !!Insert Tag The Insert-Tag can insert UIComponent objects into the component tree. This is usefull when creating XHTML-Components with namespace http://java.sun.com/jsf/composite/... 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>}}} !!Behavior-Tag Eine Neuerung bei [JSF2] ist die Einführung von Behaviors, also das Generieren von Java-Script-Snippets aus Java heraus. Ein Behavior kann jeder Komponente hinzugefügt werden, die ClientBehaviorHolder implementiert. 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 }}} !!AJAX-Tag Der AJAX-Tag von [JSF Ext] erlaubt das Abschicken eines anderen Formulars. Der Tag <f:ajax> kann nur das aktuelle Formular abschicken, innerhalb der Action-Source in der er sich befindet. Manchmal ist es sinnvoll andere Targets abzuschicken, dafür stellt [JSF Ext] den Tag <e:ajax> zur Verfügung: {{{ <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> }}} !!DIV-Tag In JSF gibt es eine Reihe von Möglichkeiten, einen DIV- oder SPAN-Tag zu erzeugen, zum Beispiel durch <h:panelGroup>. Dabei handelt es sich jedoch um einfache Komponenten, die keine ClientBehaviorHolder sind, und daher kein <f:ajax>, <e:bahavior> und andere Behaviors unterstützen. Der Tag <e:div> ist ein vollwertiger ClientBehaviorHolder: {{{ <e:div id="mouse-active" tabindex="0" onkeypress="alert((event || window.event).keyCode);"> <ext:mouse-focus id="mouse-focus"/> <h1>TEST</h1> </e:div> }}} Damit wird das Erstellen von einfachen Komponenten mit wenig HTML-Code und flachen Komponenten-Bäumen deutlich vereinfacht. !!!Tag-Support [JSF-Ext] bietet eine Reihe von Features, die den Bau von eigenen Tags unterstützen. !!Tag-Hierarchie Beim Bau von Composite-Tags möchte man in einigen Fällen auf den darüberliegenden Tag zugreifen. __Hintergrund:__ Die EL-Expression #{component.parent.parent.parent} (dazwischen zwei UINamingContainer und UIPanel die Facelets einfügt) würde normaler Weise den Parent-Tag liefert, evaluiert jedoch zu null. Zum einen ist zu dieser Zeit die Tag-Hierarchie noch nicht aufgebaut, zum anderen wird der UINamingContainer aus der Facelet-Hierarchie herausgenommen, da der Tag durch einen speziellen Mechanismus gerendert wird. __Lösung:__ Zugriff über die ext-Variable #{ext.tag.composite.parent}. Folgendes Beispiel zeigt eine Composite-Component für ein Mouse-Behavior: {{{ <?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" 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:c="http://java.sun.com/jsp/jstl/core" 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:interface> <cc:implementation> <e:behavior id="mouseover" event="mouseover" for="#{ext.tag.composite.parent}" script="focus();"/> <e:behavior id="mouseout" event="mouseout" for="#{ext.tag.composite.parent}" script="blur();"/> </cc:implementation> </html> }}} Diese ist bereits Bestandteil des JSF-Ext unter <ext:mouse-focus>. !!!Events Events sind eine Erweiterung des Action- und Update-Systems von JSF. Events können von der Java- und XHTML-Seite erzeugt und konsumiert werden. ||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. !!Java Ein Event wird erzeugt, indem die Instanz von Event geholt wird. Dies kann durch @ManagedProperty("#{event}") geschehen oder durch die statische Methode Event.instance(). Dabei ist zu beachten, dass keine Session scoped Bean in einen Application Context injected wird. 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. !!XHTML Events können auch in XHTML verarbeitet werden. Zum Beispiel der vorgefertigte Event com.intersult.messages, der bei vorhandenen Faces-Messages ausgelöst wird: {{{ <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. !!Raise-Component Events können hervorragend dazu benutzt werden, um AJAX-Submits und Rerendering voneinander zu trennen. Events werden dabei durch Components erzeugt und von anderen Components konsumiert. So braucht die erzeugende Component zur Erstellungszeit nicht wissen, welche Components rerendered werden: {{{ <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. !!Hinweise Events sind momentan Session-basiert, das heißt es existieren keine Application übergreifenden Events. !!!Scopes Scopes sind Bereiche, innerhalb derer Variablen zugreifbar sind und es kann ein Facelet zugeordnet sein. !!Einfacher Scope Der Scope beginnt mit einem Scope-Tag: {{{ <e:scope id="scope-id"> <h:outputText value="#{scope.id}"/> </e:scope> }}} !!Scope mit Facelet Die Scopes sind eines der mächtigsten Features im [JSF Ext]. Ein Scope wird zunächst im XHTML definiert: {{{ <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. !!Scope laden Zum passenden Zeitpunkt soll der Scope natürlich geladen werden, das heißt das mit viewId verwiesene Facelet wird tatsächlich in den Component-Tree eingefügt und per AJAX an den Client übertragen. Dies geschieht mit: {{{ <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. !!Java-Code Wird innerhalb des XHTML eine Java-Action aus einem Scope aufgerufen, so ist der Scope zugreifbar durch Scopes.getScope() 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. !!Verschachtelte Scopes Der Code demonstriert das Iterieren und Schachteln von Scopes: {{{ <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.