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:
- Vereinfachte Fehlerbehandlung
- Dynamisches Laden von AJAX-Facelets
- Facelet-Scopes mit komfortabler Load-/Unload-Funktion
- Einfach nutzbare Event-Erzeugung und -Empfang
Inhalt#
Page contents
- 1 Inhalt
- 2 Overview
- 3 Kompatibilität
- 4 Konfiguration
- 4.1 Annotations und InjectionProvider
- 5 Tags
- 5.1 Insert Tag
- 5.2 Behavior-Tag
- 5.3 AJAX-Tag
- 5.3.1 Sources
- 5.3.2 Action Source
- 5.3.3 Client-Behaviors
- 5.4 DIV-Tag
- 5.5 Attribute-Tag
- 5.5.1 Attribute-Behaviors
- 5.5.2 Zusammengesetzte Attribute
- 5.6 Reference-Tag
- 5.7 New-Tag
- 6 Tag-Support
- 6.1 Composite Behaviors
- 6.2 Forms und AJAX
- 7 Events
- 7.1 Hintergrund
- 7.2 Java
- 7.3 FacesMessages
- 7.3.1 FacesMessages
- 7.3.2 Beispiel Input-Wrapper
- 7.4 Component FacesMessages
- 7.5 Raise-Component
- 7.6 Hinweise
- 8 Scopes
- 8.1 Einfacher Scope
- 8.2 Scope mit Facelet
- 8.3 Scope laden
- 8.4 Scopes entladen
- 8.5 Java-Code
- 8.6 Spring
- 8.7 Verschachtelte Scopes
- 9 Spring Framework Integration
- 9.1 Scopes
- 9.1.1 Beispiel
- 9.2 Events
- 9.2.1 Beispiel
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 auf der Basis von JSF v2, die auch auf GAE lauffähig ist:Damit setzen Sie komplexe, interaktive Applications im Handumdrehen auf.
Konfiguration#
Die Anwendung befindet sich im Intersult Maven Repository:In der pom.xml wird angegeben:
<dependency> <groupId>com.intersult</groupId> <artifactId>jsf-ext</artifactId> <version>2.1.11-SNAPSHOT</version> </dependency> <repository> <id>intersult-repo</id> <name>Intersult Repository</name> <url>http://intersult.com/public/maven</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.event.EventInjectionProvider</param-value> </context-param>
Tags#
Das JSF Ext enthält eine Reine von Tags.Insert Tag#
Der Insert Tag kann Components in den Component Tree einfügen, anhand einer EL-Expression. Dies ist unter anderem nützlich, wenn man Composite Components baut. Also Components mit dem Namespace http://java.sun.com/jsf/composite/...In der Composite Component gibt es nur den Tag <cc:insertChildren>, damit hat man keine detailierte Kontrolle, die Children werden alle an derselben Stelle eingefügt. Möchte man diese zum Beispiel durch ein SPAN-Tag wrappen, braucht man den Insert Tag.
Lösung: Man iteriert durch die Children mittels <c:forEach> und fügt diese an einer beliebigen Stelle mittel <e:insert> ein.
<?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:c="http://java.sun.com/jsp/jstl/core" 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" default="center"/> </cc:interface> <cc:implementation> <div style="margin-top: 5px; text-align: #{cc.attrs.align};"> <c:forEach items="#{cc.children}" var="child"> <h:panelGroup style="padding: 5px 5px 0 0;" rendered="#{child.rendered}"> <e:insert component="#{child}"/> </h:panelGroup> </c:forEach> </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.Sources#
Manchmal ist es sinnvoll andere Sources 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"> <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.
Action Source#
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>
Client-Behaviors#
Der Tag <e:ajax> ist nicht nur ein ClientBehavior sonder auch selbst wieder ein ClientBehaviorHolder. Er unterstützt die vier AJAX-Events begin, complete, success und error, wobei das Default-Event success ist:<h:graphicImage id="edit" value="edit.png"> <e:ajax event="click"> <f:setPropertyActionListener value="#{element}" target="#{editController.element}"/> <rich:componentControl target=":some-popup" operation="show"/> </e:ajax> </h:commandButton>
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.
Attribute-Tag#
Bei manchen Tags fehlen Attribute, wie zum Beispiel placeholder oder oncontextmenu. JSF-Tags reichen die Attribute nicht durch, daher besteht keine andere Möglichkeit diese Attribute hinzuzufügen. In den meisten Fällen können solche Attribute durch das Attribute-Tag trotzdem hinzugefügt werden:<h:inputText id="name" value="#{bean.name}"> <e:attribute name="placeholder" value="Name eingeben..."/> </h:inputText>
Attribute-Behaviors#
Der Attribute-Tag ist ein ClientBehaviorHolder, es können also Behaviors darunter gehängt werden wie <e:behavior> oder sogar <e:ajax>. Dadurch ist es möglich, Behaviors für Attribute zu festzulegen, die eigentlich nicht dafür gedacht sind:<h:panelGroup> <e:attribute name="oncontextmenu"> <f:ajax listener="#{bean.render}" render=":some-menu"/> </e:attribute> </h:panelGroup>
Zusammengesetzte Attribute#
Gelegentlich möchte man Attribute zusammen setzen, zum Beispiel um Styles in Composite-Tags zu rendern. Dies kann durch Kombination eines Attribute-Tags und mehreren Behavior-Tags erreicht werden:<e:div id="#{cc.id}" styleClass="button-bar"> <e:attribute name="style" renderEmpty="false"> <e:behavior script="text-align: #{cc.attrs.align};" rendered="#{!empty cc.attrs.align}"/> <e:behavior script="background-color: #{cc.attrs.color};" rendered="#{!empty cc.attrs.color}"/> </e:attribute> ... </e:div>
Reference-Tag#
Vorwort: Generell ist der Tag <e:scope> vorzuziehen, da er ausgereifter ist und mehr Funktionalität bietet. In einzelnen Fällen kann dennoch der Tag <e:reference> von vorteilhaft sein.Der Reference-Tag schafft Zugriff auf den ELContext einer andere Stelle im Komponenten-Baum. Über eine festgelegte Variable kann auf Elemente aus einem beliebigen Kontext zugegriffen werden. Einschließlich iterierte Kontexte, wie sie von <ui:repeat> oder Tabellen erzeugt werden.
In JSF werden oft Components verwendet, die durch Iteration mit <ui:repeat> oder auf andere Art vervielfältigt werden, auch Tables, Trees und ähnliche Komponenten geben wiederholten HTML-Code aus.
Gerade bei vervielfältigtem Code möchte man diesen schlank bauen, da dies für die Effizienz der Web-Seite ausschlaggebend ist. Kontext-Menüs, Popups und andere Elemente, die nur einmal für ein Element geöffnet werden brauchen, sollten also aus der Iteration herausgenommen werden. Doch früher oder später muss auf das Element wieder zugegriffen werden.
In einfachen, wenig zeitkritischen Fällen behilft man sich mit einem AXAJ-Submit. Zum Beispiel:
<h:form id="form"> <ui:repeat id="repeat" value="#{fn:split('1,2,3,4,5', ',')}" var="index"> <h:commandButton id="button" value="Button #{index}"> <f:setPropertyActionListener target="#{bean.selected}" value="#{item}"> <e:ajax onsuccess="showContextMenu();"/> </h:commandButton> </c:forEach> </h:form>
Anmerkung: In JSF 2.1.11 ist für den Tag <ui:repeat> kein Attribut "id" in der Taglib eingetragen. Da es sich um eine UIComponent handelt, kann die Id trotzdem angegeben werden und wird auch korrekt verarbeitet. Damit erreicht man, dass die Child Components der Iteration definierte Namen bekommen.
Allerdings wird dazwischen ein AJAX-Aufruf abgeschickt. Wenn in einem Popup oder Kontextmenu das Element betreffende Dinge neu gerendered werden sollen, sicher der richtige Weg. Bei einfacheren Komponenten oft hinderlich und vom Benutzer als zu "hackig" empfunden. Da kann der Reference-Tag Abhilfe schaffen:
<h:form id="form"> <c:forEach begin="1" end="5" var="index"> <h:commandButton id="button-#{index}" value="Button #{index}"> <e:setReference target=":form:ref"/> <e:behavior script="showContextMenu();" disableDefault="true"/> </h:commandButton> </c:forEach> <e:reference id="ref" var="ref"> <h:commandButton value="Show" action="#{test.actionText(ref.index)}"> <f:ajax/> </h:commandButton> </e:reference> </h:form>
Erklärung: Der Tag <e:setReference> ist ein ClientBehavior, also JavaScript für den Tag <e:reference>. Beim Klicken eines der iterierten Command-Buttons wird die Referenz des Tags <e:reference> gesetzt, ohne einen Request zum Server abzuschicken. Beim Klicken des unteren Command-Buttons "Show" wird mit "ref.index" auf den ELContext des referenzierten Buttons zugegriffen, also die Variable "index" die zum Iterieren verwendet wurde.
New-Tag#
Der Tag <e:new> kann an jeder Stelle verwendet werden, an der auch <f:param> verwendet wird:<e:load scopeId="test-scope"> <e:new name="bean" type="com.intersult.test.Bean"/> </e:load>
Es ist auch möglich, Properties der neuen Bean Werte zuzuweisen:
<e:new name="bean" type="com.intersult.test.Bean"> <f:param name="param1" value="#{some-expression}/> </e:new>
Hinweis: Es ist zu erwähnen, dass das Instantiieren von Beans durch den New-Tag möglicher Weise nicht die beste Praxis ist. Die erste Wahl beim Erzeugen von Beans sollte das Verwenden von Scope- und Factory-Annotations sein, entweder über JSF mit @ManagedBean oder über Spring mit @Component. An zweiter Wahl steht das Erzeugen im Java-Code, zum Beispiel mit Lazy-Initialization Pattern "if (bean == null) bean = new Bean();". <e:new> macht vor allem da Sinn, wo eine neue leere Bean erzeugt werden soll (z.B. Create Popup) und wo mehrere Parameter durch eine Bean übergeben werden sollen.
Tag-Support#
JSF Ext bietet eine Reihe von Features, die den Bau von eigenen Tags unterstützen.Composite Tags sind ein mächtiges und zugleich einfach zu handhabendes Instrument zum Erstellen eigener Taglibs. Durch einige zusätzliche Instrumente werden Composite Tags noch vielseitiger. Damit kann vielfach auf die Entwicklung von nativen Tags verzichtet werden, inklusive aufwändiger Compile, Redeploy und Restart-Zyklen. Durch die erweiterten Möglichkeiten steigt gleichzeitig der Anreiz zu Composite Tags.
Composite Behaviors#
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 liefern, 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 jsf, also #{jsf.tag.composite.parent.component}. 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="#{jsf.tag.composite.parent.component}" script="focus();"/> <e:behavior id="mouseout" event="mouseout" for="#{jsf.tag.composite.parent.component}" script="blur();"/> </cc:implementation> </html>
Dieser Tag ist bereits Bestandteil des JSF-Ext unter <ext:mouse-focus>. Er kann zusammen mit jedem ClientBehaviorHolder, wie dem <e:div> angewendet werden.
Forms und AJAX#
Wird eine Komponente durch AJAX neu gerendered, die eine FORM enthält, geht der View-State der FORM verloren. In einigen Fällen kann dies verhindert werden, in dem die Form explizit im Render-Attribut eines AJAX-Tags angegeben wird. Im Zusammenhang mit Include-Anweisungen, wie auch den Scopes ist dies nur bedingt möglich.JSF Ext löst dieses Problem durch zusätzlichen Java-Script Code. Durch das Einbinden der JAR-Bibliothek ist das Problem transparent gelöst. Der enthaltene Java-Script Code wird immer dann eingebunden, wenn auch der JSF-AJAX-Code eingebunden wird. Er fügt den ViewState in enthaltenenen FORM-Tags ein, sodass keine zusätzlichen Render-Anweisungen benötigt werden.
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 |
---|---|---|---|
javax.faces.post_construct | Event.EVENT_POST_CONSTRUCT | Object managedBean | Der Event wird nach dem Erzeugen einer managed Bean durch das JSF-System erzeugt. |
javax.faces.pre_destroy | Event.EVENT_PRE_DESTROY | Der Event wird vor dem Entfernen einer managed Bean erzeugt. | |
javax.faces.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. |
javax.faces.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. |
Hintergrund#
In JSF werden oft Render-Attribute direkt in AJAX- und anderen Anweisungen angegeben. Da diese lose gekoppelt sind, wird im günstigen Fall eine Fehlermeldung entstehen, dass die entsprechende Id nicht mehr im Component Tree vorhanden ist. Der nachträgliche Einbau zusätzlich zu rendernder Components ist nicht vorgesehen, diese müssen explizit am Render-Attribut aller betreffenden Anweisungen hinzugefügt und gepflegt werden.Aus diesem Grund führt JSF Ext die (Render-)Events ein. Events können zwar aus der View durch den Tag <e:raise> erzeugt werden, sinnvoll ist meist jedoch die Erzeugung durch die raise-Methode auf der Java-Seite. Dadurch bleibt die lose Kopplung von View und Controller erhalten. Die können Controller können Ereignisse auslösen, auf die sich Teile der View registrieren um neu gerendert zu werden.
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.
FacesMessages#
Events können auch im XHTML verarbeitet werden, indem bestimmte Bereiche neu gerendert werden.FacesMessages#
Zum Beispiel der vorgefertigte Event javax.faces.messages, der bei vorhandenen Faces-Messages ausgelöst wird:<h:messages id="messages" globalOnly="true"> <e:render event="javax.faces.messages"/> </h:message>
Ergebnis: Die Faces-Messages werden gerendered, ohne dass bei jedem AJAX-Tag ein gesondertes Rendered-Attribut angegeben werden muss.
Beispiel Input-Wrapper#
Situation: In einer Anwendung kommen zumeist verschiedene Eingabeelemente wie <h:inputText>, <h:selectOneMenu>, <h:inputSecret>, <h:selectBooleanCheckbox> sowie selbst gebaute Tags zur Verwendung. Für die Applikation besteht gewöhnlich ein einheitliches Layout mit AJAX- oder Client-Side Validierung, FacesMessages und weiteren Elementen.Lösung bisher: In vielen Projekten wird für jedes Input-Element ein Composite-Tag gebaut, in dem der Code für AJAX, Validierung, Messages und so weiter wiederholt wird.
Mit JSF Ext kann ein generischer Wrapper für Input-Elements gebaut werden:
<?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:c="http://java.sun.com/jsp/jstl/core" 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" xmlns:p="http://primefaces.org/ui" > <cc:interface> </cc:interface> <cc:implementation> <e:insert component="#{cc.children[0]}"> <f:ajax event="change"/> </e:insert> <h:message id="#{cc.children[0].id}-message" for=":#{cc.children[0].clientId}"> <e:render event=":#{cc.children[0].clientId}"/> </h:message> </cc:implementation> </html>
Die Anwendung dieses Composite-Tags ist dann wie folgt:
<h:form id="form"> <app:input id="text"> <h:inputText id="text" value="#{test.text}"/> </app:input> <h:commandButton value="Submit" action="#{bean.action}"> <f:ajax/> </h:commandButton>
Erklärung: Die Composite-Component wrappt das Input-Element, welches innerhalb des Composite Tags durch <e:insert> eingefügt wird. Der Zugriff erfolgt dabei durch die EL-Expression cc.children[0].
Component FacesMessages#
JSF Ext generiert auch Events für Component FacesMessages, die entstehen beim Konvertieren, Validieren oder manuell in Java-Code. Oft wird unnötiger Weise das komplette Form neu gerendert, um die Component FacesMessages anzuzeigen. Mit JSF Ext können gezielt einzelne Messages gerendert werden, wenn diese auftreten. Der Event-Name entspricht dabei der Client-Id der aufgetretenen Component FacesMessage:<e:render event=":form:some-text"/>
Der Event tritt in folgenden Fällen auf:
- Nur wenn die Component vom AJAX-Execute betroffen ist
- Wenn eine FacesMessage für die Component zum ersten Mal vorhanden ist
- Wenn eine FacesMessage für die Component zum zweiten oder öfteren Mal vorhanden ist
- Wenn keine FacesMessage für die Component verschwunden ist
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:render 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. Die Variablen sind über EL-Expressions durch #{scope.<...>} zugreifbar. Insbesondere können sie durch den Load-Tag mit <f:param> übergeben werden. So entfallen iterierte Popups, Kontext-Menüs und andere Detail-Views.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 render=":popup:form"/> <e:load scopeId="popup"> <f:param name="someParam" value="someValue"/> <f:param name="someExpression" value="#{bean.someValue}"/> </e:load> </h:commandButton>
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" > <rich:popupPanel id="popup" show="true" autosized="true" modal="false"> <f:facet name="header"> <h:outputText value="Loaded Popup"/> </f:facet> <f:facet name="controls"> <h:form id="controls-form"> <h:commandButton value="X" immediate="true"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup')}.hide(event);"/> </h:commandButton> </h:form> </f:facet> <h:form id="form"> <h:panelGrid columns="2"> <h:outputLabel for="popupText" value="Popup Text"/> <h:outputText id="popupText" value="#{bean.popupText}"/> <h:outputLabel for="someParam" value="Some Param"/> <h:outputText id="someParam" value="#{scope.someParam}"/> <h:outputLabel for="someExpression" value="Some Expression"/> <h:inputText id="someExpression" value="#{scope.someExpression}"/> </h:panelGrid> <ext:buttons> <h:commandButton id="save" value="Save" action="#{bean.save}"> <f:ajax execute="@form"/> <e:unload/> <e:behavior script="#{rich:component('popup')}.hide(event);"/> </h:commandButton> <h:commandButton id="cancel" value="Cancel" immediate="true"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup')}.hide(event);"/> </h:commandButton> </ext:buttons> </h:form> </rich:popupPanel> </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 Unload-Tag um einen commandButton, der mit AJAX-Tag erweitert wurde. Im Gegensatz zum Load-Tag braucht beim Unload-Tag nicht zwingend die Scope-Id angegeben werden. Für den Fall dass der Unload-Tag innerhalb eines Scopes auftritt, wird automatisch dieser Scope verwendet. Damit vereinfachen sich unter anderem Close- und Save-Buttons.
Scopes entladen#
Neben dem Tag <e:unload> können Scopes auch mit #{scopes.unload(id)} entladen werden und innerhalb des Scopes selbst mit #{scope.unload}. Dies ist praktisch, um zum Beispiel ohne Formular einen Close-Butten auf einem Popup zu platzieren:<p:dialog id="dialog" header="#{messages['rule.edit']}" visible="true"> <f:ajax event="close" listener="#{scope.unload}"/> <h1>Content</h1> </p:dialog>
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.
Spring#
Der Scope kann auch zusammen mit Spring-Scopes verwendet werden, dabei ist die Konfiguration weiter unten in diesem Dokument zu beachten.@Component @Scope(Scopes.SCOPE_NAME) public class SomeBean { ... }
Erklärung: Die Beans werden also bei Bedarf instantiiert und im JSF-Scope abgelegt. Dadurch kann ein Dialog- oder Unterdialog abgearbeitet werden. Am Ende des Workflow wird der Scope samt Inhalt wieder abgeräumt und der Garbage-Collection zugeführt. Dies ist sehr effizient implementiert, die Anwendung profitiert durch Verwenden von <e:load>, <e:unload> und <e:scope> von einer spürbaren Vereinfachung.
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"> <f: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"> <f: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.
Spring Framework Integration#
JSF Ext arbeitet mit dem Spring Framework zusammen. Für die Spring-JSF-Integration ist es sinnvoll zunächst den EL-Resolver von Spring zu registrieren:<faces-config> <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> </application> </faces-config>
Scopes#
Wenn man nicht nur JSF Managed Beans im Custom Scope von JSF Ext haben möchte, kann dieser auch in der applicationContext.xml registriert werden:<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="view"> <bean class="com.intersult.jsf.spring.ViewScope"/> </entry> <entry key="scope"> <bean class="com.intersult.jsf.spring.CustomScope"/> </entry> </map> </property> </bean>
Hinweis: JSF Ext enthält zusätzlich noch einen Scope, mit dem der ViewScope von JSF registriert werden kann.
Um Werte aus Scopes in Beans zu injizieren, können die Scopes
<bean name="scopes" class="com.intersult.jsf.Scopes" factory-method="instance" scope="session" lazy-init="true"/> <bean name="scope" factory-bean="scopes" factory-method="getScope" scope="prototype" lazy-init="true"/>
Beispiel#
Zum Beispiel wäre dann folgende Konstruktion möglich:Beim Laden des Scopes:
<e:load scopeId="rule-remove"> <f:param name="rule" value="#{rule}"/> </e:load>
Innerhalb des Scopes:
<h:commandButton value="#{messages['remove']}" action="#{ruleEditController.remove}"> <f:ajax/> <e:unload/> </h:commandButton>
Der Controller dazu:
@Component @Scope(WebApplicationContext.SCOPE_REQUEST) public class RuleEditController { @Value("#{scope.rule}") private Rule rule; @Transactional public void remove() { ... } }
Erklärung: Damit ist es möglich, eine Entity- oder andere Modell-Bean innerhalb eines Scopes zu bearbeiten, dessen Lebenszyklus an ein AJAX-Popup gekoppelt ist. Es ist keine weitere Java-Bean erforderlich, die in einem Scope abgelegt werden muss. Alle parameterisierten Beans werden beim Beenden (Unload) des Scopes freigegeben.
Hinweis: Der Controller ist mit der Annotation @Scope(WebApplicationContext.SCOPE_REQUEST), damit die Injection im richtigen Scope erfolgt. Der Scope kann sich mit jedem Request ändern.
Events#
Um die Annotation @Listener auch in Spring-Beans verwenden zu können, kann man den EventBeanProcessor in Spring registrieren:<bean class="com.intersult.jsf.spring.EventBeanProcessor"/>
Beispiel#
Der Listener kann verwendet werden, um Inhalte von Ergebnislisten bei bedarf neu zu generieren:@Component @Scope(WebApplicationContext.SCOPE_REQUEST) public class QueryController { @Transactional @Listener(Rule.EVENT_CHANGED) public void execute() { ... }
Hinweis: Es ist darauf zu achten, dass betreffende Spring-Beans mit einem entsprechenden Scope versehen werden, wie zum Beispiel @Scope(WebApplicationContext.SCOPE_REQUEST). Die Events können nicht während der Startphase der Applikation gebunden werden, da hier JSF noch nicht initialisiert ist.