JSF Ext wurde weiter vereinfacht, sodass eine hohe Geschwindigkeit der Web-Seiten erreicht werden kann. JSF Ext wurde optimiert, sodass höchst mögliche Kompatibilität mit anderen Produkten sichergestellt ist. Sollten Sie Fragen, Bugs oder Feature-Requests haben, so nutzen sie bitte die Issue List.
Einige der Features:
- Dynamik: Echtes dynamisches Laden von Teilen großer Views
- Scopes: Component-Scopes mit komfortabler Load-/Unload-Funktion
- Messages: Vereinfachte Fehlerbehandlung
- Events: Einfach nutzbare Event-Erzeugung und automatisches Rendering
- Kompatibilität: Arbeitet zusammen mit Primefaces, Richfaces, Spring etc.
- Fertigstellung/Fixes: Multipart-Requests (Submit/AJAX), View State Fix, Compatibility, AJAX Insert und Delete etc.
Inhalt#
Page contents
- 1 Inhalt
- 2 Overview
- 3 Konfiguration
- 3.1 Kompatibilität
- 3.2 Annotations und InjectionProvider
- 3.3 Redirects
- 3.4 AJAX-Queue
- 4 Tags
- 4.1 Insert Tag
- 4.2 Behavior-Tag
- 4.3 Evaluate Tag
- 4.4 AJAX-Tag
- 4.4.1 Sources
- 4.4.2 Action Source
- 4.4.3 Client-Behaviors
- 4.5 DIV-Tag
- 4.6 Attribute-Tag
- 4.6.1 Attribute-Behaviors
- 4.6.2 Zusammengesetzte Attribute
- 4.6.3 Part-Attribute
- 4.6.4 Style-Attribute
- 4.7 Reference-Tag
- 4.8 New-Tag
- 4.9 Set-Tag
- 4.10 Label
- 4.11 Busy Pointer
- 4.12 Mouse Visibility
- 4.13 Move-Listener
- 4.14 Init-Tag
- 4.15 Otherwise-Tag
- 4.16 Render-Tag
- 4.17 Async-Tag
- 5 Tag-Support
- 5.1 Composite Behaviors
- 5.2 Forms und AJAX
- 5.3 Der ExtResponseWriter
- 6 Events
- 6.1 Hintergrund
- 6.2 Java
- 6.3 FacesMessages
- 6.3.1 FacesMessages
- 6.3.2 Beispiel Input-Wrapper
- 6.4 Component FacesMessages
- 6.5 Raise-Component
- 6.6 Hinweise
- 7 Scopes
- 7.1 Freien Scope erzeugen
- 7.2 Direkter Scope
- 7.3 Scope mit Facelet-View
- 7.4 Scope laden
- 7.5 Scopes entladen
- 7.6 Java-Code
- 7.7 Spring
- 7.8 Verschachtelte Scopes
- 7.9 Component Injection
- 7.10 Recovering
- 7.11 Scope Behaviors
- 7.12 Onunload Tag
- 7.13 Scope Hierarchie
- 7.13.1 Cascading
- 7.13.2 Programmatisches Cascading
- 7.14 Debugging
- 8 Internationalisierung
- 9 Spring Framework
- 9.1 Scopes
- 9.1.1 Beispiel
- 9.1.2 Ablegen einer Spring-Bean im Scope
- 9.2 Events
- 9.2.1 Beispiel
- 10 Primefaces
- 10.1 Primefaces Buttons
- 11 Validieren
- 11.1 Unique-Validator
- 11.2 Input-Wrapper
- 12 Converter
- 12.1 SelectOneMenu und SelectItem
- 12.2 String-Constructor
- 12.3 Language und Country
- 12.4 Display-Language und -Country
- 13 Files und Resources
- 13.1 File Upload
- 13.2 Resource Provider
- 14 Rechliches
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.
Konfiguration#
Die Anwendung befindet sich im Intersult Maven Repository:In der pom.xml wird angegeben:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ... <dependencies> <dependency> <groupId>com.intersult</groupId> <artifactId>jsf-ext</artifactId> <version>2.2-SNAPSHOT</version> </dependency> ... </dependencies> <repositories> <repository> <id>intersult-repo</id> <name>Intersult Repository</name> <url>http://repository.intersult.com/repository</url> </repository> ... </repositories> </project>
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"
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.
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>
Redirects#
Standardmäßig schicken Command-Tags wie <h:commandButton> das Formular ab und rendern unter umständen eine neue ViewId. Dabei entstehen zwei Probleme:- Im Browser wird bei einem View-Wechsel die vorherige View-Id angezeigt, anstatt der neuen View-Id.
- Ein Refresh der Seite durch den Browser führt zu Komplikationen, die Submit-Parameter müssen nochmals abgeschickt werden. Die meisten Browser legen dabei ein für den Benutzer wenig verständliches Verhalten an den Tag, Popups öffnen sich mit merkwürdigen Fragen.
Die Lösung besten in einem Redirect nach dem Submit, der häufig Page für Page, Action für Action in das Projekt reingezogen wird. Die Nachteile seitens Wartbarkeit, Konsistenz und Veränderbarkeit liegen auf der Hand. Eine saubere Lösung besteht darin, einen entsprechenden Navigation-Handler zu benutzen:
<navigation-handler>com.intersult.jsf.util.RedirectNavigationHandler</navigation-handler>
Hinweis: Der Redirect-Navigation-Handler ist per Default deaktiviert, da dies einen erheblichen Eingriff in den Page-Flow und damit das Standard-Verhalten von JSF darstellt. Das Feature kann bei Bedarf durch die oben gezeigte Zeile einfach selbst in die eigene faces-config.xml eingefügt werden.
AJAX-Queue#
Üblicher Weise werden AJAX-Aufrufe serialisiert. Generell macht dies Sinn, da es sonst zu Problemen mit dem View-State kommt. Aktiviert wird das Parallelisieren von AJAX-Aufrufen durch das Setzen der JavaScript-Variable "jsf.ajaxQueue":<script type="text/javascript"> jsf.ajaxQueue = 2; </script>
Warnung: Aus gutem Grund werden die AJAX-Requests üblicher Weise serialisiert. Beim Verwenden paralleler AJAX-Requests besteht die Gefahr, einen veralteten View-State an den Server zu senden. Parallele Requests werden nur korrekt arbeiten, wenn ausgeschlossen wird, dass ein Formular mehrere Abfragen gleichzeitig abschickt.
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
Evaluate Tag#
Wenn mit Actions und Java-Script gearbeitet wird, kann eine Ausführung im Erfolgsfall des Form Submit sinnvoll sein. Dies kann durch den Evaluate-Tag erreicht werden, der einen ActionListener darstellt und entsprechendes Script ausführen kann.<h:commandButton value="Submit"> <f:ajax/> <e:evaluate script="alert('Success');"/> </h:commandButton>
Zusätzlich ist der Evaluate-Tag ein ClientBahaviorHolder, sodass Behavior-Tags verwendet werden können, um das Script zu erzeugen.
Unterschied zwischen Behavior- und Evaluate-Tag:
Behavior | Evaluate | |
---|---|---|
Zeitpunkt der Ausführung | Direkt durch den Browser-Event | Durch den AJAX-Request an den Browser gesendet |
Bedingungen für die Ausführung | Keine Server-Bedingungen möglich, nur Java-Script if-Statements | Das Script wird nur nach erfolgreichen Submit ausgeführt, also wenn die Validation erfolgreich war |
Anwendungszweck | Steuerung des Browserverhaltens, Ein- und Ausblenden, Fading, dynamische Browserelemente, Client-Behavior | Abwickeln von Server-States, Popups öffnen und Schließen, Submits bestätigen |
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>
Des Weiteren unterstützt der Attribut-Tag folgende Attribute:
Name | Beschreibung |
---|---|
finalizer | Zeichen für den Abschluss eines Behaviors. Default ist, dieses Zeichen aufgrund einer Reihe von Heuristiken zu erraten. Dazu zählen Behaviors und Attribut-Namen wie z.B. "style" mit den Finalizern ";" und "". Jeder untergeordnete Behavior-, Part- und anderer Tag wird mit diesem Zeichen abgeschlossen. |
delimiter | Zeichen für die Abtrennung untergeordneter Behavior-Tags usw. Im Gegensatz zum Finalizer wird dieses Zeichen nur zwischen den Behaviors eingefügt, nicht jedoch am Ende. Default ist ein einzelnes Whitespace. |
Hinweis: Durch dieses Feature können Strings bequem zusammengesetzt werden, die mit Attributen "rendered" versehen sind. Untergeordnete Behaviors, wie das Part-Tag, können die Werte nochmal für einzelne Strings überschreiben.
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>
Part-Attribute#
Zusammengesetzt Attribute können bequem durch <e:part> erzeugt werden:<h:commandButton> <e:attribute name="value"> <e:part value="This"/> <e:part value="is"/> <e:part value="a"/> <e:part value="composed"/> <e:part value="value"/> </e:attribute> </h:commandButton>
Hinweis: Der Part-Tag kann hilfreich sein, wenn bedingte styleClass-Attribute auftreten. Entsprechende Teile können mit dem rendered-Attribut gesteuert werden, anstatt unübersichtliche EL-Expressions zu konstruieren.
Style-Attribute#
Da Style-Attribute besonders häufig auftreten und zusammengesetzt werden, gibt es dafür einen eigenen Tag:<e:div> <e:attribute name="style"> <e:style name="text-align" value="#{valueEdit.permission.field.type.align}"/> <e:style name="width" value="100%"/> <e:style name="margin-left" value="10px"/> </e:attribute> </e:div>
Reference-Tag#
Achtung: Der Reference-Tag wird nicht mehr unterstützt.Begründung: Der Reference-Tag wird seit längerem durch Scopes ersetzt. Diese sind wesentlich besser in JSF integriert, brauchen weniger Ressourcen und sind flexibler in der Anwendung.
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.
Set-Tag#
Eine gängige Praxis ist das Setzen von Java-Properties mittels <f:setPropertyActionListener>. Bei Composite Components können ebenfalls als ActionSource agieren:<?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:e="http://java.sun.com/jsf/ext" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:cc="http://java.sun.com/jsf/composite" > <cc:interface> <cc:attribute name="label"/> <cc:attribute name="action" targets="link" method-signature="void action()"/> <cc:actionSource name="action" targets="link" default="true"/> </cc:interface> <cc:implementation> <h:commandLink id="link" <h:outputText value="#{cc.attrs.label}"/> </h:commandLink> </cc:implementation> </html>
Dieser Tag kann wie folgt verwendet werden:
<app:myLink label="Some Link"> <e:set value="Some Value" target="#{bean.value}"/> </app:myLink>
Beim Verwenden von <f:setPropertyActionListener> müsste man explizit mit einem Attribut for="action" die Action spezifizieren. Der Tag <e:set> macht dies automatisch, bei häufigem Verwenden sieht der kurze Name einfach ausdrucksvoller und aufgeräumter aus.
Label#
JSF Ext liefert einen Tag <e:outputLabel> mit, der auf Required-Attribute mit einem unterschiedlichen Style reagiert. Zusätzlich berücksichtigt werden die Annotations @NotNull und @NotEmpty auf den durch EL-Expressions gebundenen Bean-Klassen.Die Verwendung ist identisch mit <h:outputLabel>:
<e:outputLabel for="username:username" value="#{messages['user.username']}"/> <ext:input id="username"> <h:inputText id="username" value="#{userRegister.user.username}"/> </ext:input>
Dabei kommen die beiden CSS-Style-Klassen label-required und label-not-required zum Einsatz, die mit "font-weight: bold;" und "color: #a0a0a0;" vorbelegt sind. Diese können durch ein Benutzer-Stylesheet überschrieben werden.
Der <e:outputLabel> unterstützt das Attribut disabled, mit dem der Label als optional angezeigt werden kann, unabhängig von der tatsächlichen Required-Eigenschaft. Dies ist nützlich, wenn die zugehörige Input-Component ebenfalls Disabled wird.
Busy Pointer#
Die Integration von AJAX in die Anwendung findet vom Browser zunächst transparent statt. Der Benutzer hat keinen Hinweis, dass gerade eine Server-Aktion erfolgt. Der Vorteil ist, dass der Benutzer mit dem Browser normal weiter arbeiten kann. In vielen Fällen ist dies auch möglich, da Operationen unabhängig voneinander ausgeführt werden können. Der Nachteil ist, dass der Benutzer keinen Hinweis erhält, dass die beabsichtigte Operation angelaufen ist.In den Anwendungen werden daher unterschiedlichste Elemente in die Seite eingebaut, von rotierenden, blinkenden durch durchlaufenden Bildern bis zu Seiten abdunkelnden und sperrenden Elementen. Das übliche Verfahren von Anwendungen die Verarbeitung einer Operation zu zeigen, ist den Mauszeiger als Busy Pointer darzustellen.
Also wieso nicht auch im Browser bei JSF-Anwendungen den Busy Pointer aktivieren. Der Benutzer wird nicht abgelenkt durch zappelnde Bilder, es wird nichts gesperrt, da die Seite ja weiterhin funktional ist:
<ext:busyPointer/>
Optional kann das Attribut cursor angeben werde, das per default auf "wait" gesetzt ist.
Mouse Visibility#
Auf Seiten die sehr viele Elemente enthalten kann es die Übersicht verbessern, wenn Elemente erst bei Mouse-Over angezeigt werden. Durch reine CSS-Styles ist das meist nicht zu realisieren, weil das Mouse-Over-Element ein Parent-Element des anzuzeigenden Elements ist. Lösungen mit A-Tags, die man häufig findet, führen zu Nebenwirkungen wie Anchor-Links und ungewollten GET-Requests.Eine Lösung ist der Tag <ext:mouse-visibility>:
<e:div> <h:outputText id="project" value="#{processDetails.process.project.name}"/> <ext:mouse-visibility for=":process-form:image"/> <h:graphicImage id="image" name="edit.gif" library="images/bitcons"> <e:ajax event="click"> <e:load scopeId=":projectEdit"> <f:param name="project" value="#{project}"/> </e:load> </e:ajax> </h:graphicImage> </e:div>
Darüber hinaus gibt es noch die Tags <ext:mouse-display> mit welcher der Display-Style eines Elements gesteuert werden kann. Besonders schick ist auch der <ext:mouse-fade>, mit der das Element ein- und ausgefadet wird.
Move-Listener#
Auf modernen Web-Seiten wird mehr und mehr mit Popups und Elementen gearbeitet, die mit der Maus verschoben werden können. Der Move-Listener ist ein Event-Generator, mit dem solche Eingriffe verarbeitet werden können:<e:moveListener listener="#{dialog.moveListener}"/>
JSF Ext enthält eine Custom-Scoped Bean "dialog", in der das Ergebnis gespeichert werden kann. Im obigen Beispiel geschieht dies durch listener="#{dialog.moveListener}". Ein entsprechendes Popup kann dann durch position="#{dialog.position}" wieder positioniert werden.
Um einem Primefaces-Dialog einen Listener hinzuzufügen, verwendet man:
<e:moveListener listener="#{dialog.moveListener}" targetNode="document.getElementById('#{cc.clientId}:dialog').childNodes[0]" height="element.childNodes[1].offsetHeight - 16" width="element.childNodes[1].offsetWidth"/>
Init-Tag#
In einigen Situationen ist es hilfreich, wenn eine Bean an einer bestimmten Stelle im Component-Tree initialisiert werden kann. So kann beispielsweise ein HTTP-GET-Parameter verarbeitet werden oder ein Scope initialisiert. Dazu gibt es den Tag <e:init>:<e:init action="#{testBean.init}"/>
Hinweis: Die Action-Methode kann mehrfach aufgerufen werden, wenn Redirects durchgeführt werden.
Der Tag kann innerhalb eines Scopes verwendet werden, um den Scope zu initialisieren, falls kein Laden mit <e:load> erfolgt ist.
<e:scope id="test" load="true"> <e:init action="#{testBean.init}"/> ... </e:scope>
public class TestBean { @ScopeValue private String test; public void init() { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); String test = externalContext.getRequestParameterMap().get("test"); if (test != null) this.test = test; } ... }
Hinweis: Der Load-Tag ist dabei weiterhin möglich.
Der Init-Tag ist eine ActionSource und kann damit auch ActionListener als Unterobjekte haben:
<e:init> <e:set value="#{value}" target="#{target}"/> </e:init>
Otherwise-Tag#
Da ist mit dem <c:choose>, <c:when> und <c:otherwise> Konstrukt in JSF Probleme gibt, enthält JSF Ext eine neue Implementierung von Otherweise als Component <e:otherwise>. Diese Component ist einfacher in der Nutzung:- Als Basis der Choose-Liste kann eine beliebige Component verwendet werden
- Child-Components werden mit rendered-Attributen versehen
- Der Otherwise-Tag wird hinzugefügt und rendered dann, wenn keine der anderen Components gerendert wurde
Das Ganze sieht dann so aus:
<e:div> <h:selectBooleanCheckbox id="boolean" value="#{bean.value}" rendered="#{bean.type == 'boolean'}"/> ... <e:otherwise> <h:inputText id="text" value="#{bean.value}"/> </e:otherwise> </e:div>
Render-Tag#
Der Tag <e:render> ist ein nützlicher Tag für dynamische Web-Seiten, in denen Teile automatisch refreshed werden. Bisher gibt man im Ajax-Tag eine Liste von Elementen an, die neu gerendered werden sollen.Die alte Implementierung über Render-Attribute führt zu folgenden Verbesserungswünschen:
- Dynamik: Zum Zeitpunkt des Ausführens der Render-Anweisung ist unklar, welche Bereiche überhaupt neu gerendered werden brauchen. Die Zielbereiche können über das Render-Attribut, Scopes und andere Mechanismen dynamisch ein- und ausgeblendet werden. Man möchte die Veränderung einer Struktur melden können, unabhängig von den zur Laufzeit tatsächlich abhängigen Elementen.
- Weiterentwicklung: Nach Schreiben der Render-Anweisung wird die Anwendung weiterentwickelt. Zielbereiche zum Rendern können hinzukommen oder wegfallen, dies führt zu permanenter Pflege einer wachsenden Zahl von Render-Attributen in der Anwendung. Gewünscht ist, dass fertiger Code nicht permanent wieder angefasst werden braucht.
- Modularisierung: Bei Modulen, die durch JAR-Dateien ins Projekt kommen, kann auf die XHTML-Seiten kein Einfluss genommen werden. Dort soll ebenfalls ein Rendering möglich sein.
Die Lösung sind Events und der Render-Tag. Typischer Weise werden Events durch den Code ausgelöst, wie etwa beim Login:
Event.instance().raise(EVENT_LOGIN, authentication.getPrincipal());
Dadurch werden Bereiche neu gerendered:
<h:form id="process-form" enctype="multipart/form-data" styleClass="ui-widget" style="margin-left: 10px;"> <e:render event="intersult.subflow.Authenticator.login"/> <e:render event="intersult.subflow.Process.change"/> <e:render event="intersult.subflow.Process.select"/> <e:div rendered="#{!empty processDetails.process}"> ... </e:div> </form>
Hinweis: Es können nur Bereiche neu gerendered werden, die bereits gerendered wurden. Dadurch können Render-Tags dadurch problemlos in Scopes und anderen dynamischen Berechen verwendet werden. Es ist völlig verträglich, wenn ein Abschnitt mit einem Render-Tag selbst nicht gerendered wurde, dieser wird dann einfach ignoriert.
Tipp: Soll ein Bereich durch ein Render-Tag ein- und ausgeblendet werden, platziert man den Render-Tag in die übergeordnete Component. So wird die Region auf jeden Fall gerendered, unabhängig vom Render-Zustand (Render-Attribut, <c:if> etc.) der innen liegenden Sektion.
Async-Tag#
Manchmal kann es von Vorteil sein, wenn der wichtigste Teil einer Seite sofort zur Verfügung steht. Andere Teile können dann nachgeladen werden. Dies trifft insbesondere zu, wenn eine langsame Verbindung besteht oder Teile der Seite mehr Zeit auf dem Server in Anspruch nehmen, etwa bei komplexen Datenbankanfragen oder Berechnungen.Hier kann der Tag <e:async> verwendet werden, dieser lädt seinen Inhalt erst nach dem Rendern der Seite per AJAX nach:
<p:panel header="HTML Content"> <h1>HTML Content</h1> </p:panel> <e:async> <p:panel header="Async Content"> <h1>Async Content</h1> </p:panel> </e:async>
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.
Der ExtResponseWriter#
JSF-Behaviors bestehen aus einer Klasse die von Behavior oder ClientBehavior abgeleitet sind. Im Wesentlichen ist eine Methode getScript enthalten, mit der ein Fragmet des entsprechenden event-Attribut des entsprechenden HTML-Tags gerendert wird, also zum Beispiel onclick="...". Bei komplexeren Behaviors braucht man zusätzlichen HTML-Code, der kann an dieser Stelle nicht geschrieben werden. Ursache ist dass der HTML-Writer sich gerade innerhalb des geöffneten Tags befindet und startElement ungültiges HTML erzeugen würde.Der ExtResponseWriter von JSF Ext stellt eine statische Methode getEndElementWriter(ResponseWriter responseWriter) bzw. getEndElementWriter() zur Verfügung. Damit bekommt man einen ResponseWriter, mit dem geschrieben werden kann. Der HTML-Code wir dann effektiv vor dem Schließen des End-Tags ausgegeben:
ResponseWriter writer = ExtResponseWriter.getEndElementWriter(); writer.startElement("div", component); writer.writeAttribute("id", component.getClientId(behaviorContext.getFacesContext()), null); writer.writeAttribute("style", "display: none;", null); writer.endElement("div");
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:<e:render event="javax.faces.messages" target="messages"/> <h:messages id="messages" globalOnly="true"/>
Ergebnis: Die Faces-Messages werden gerendered, ohne dass bei jedem AJAX-Tag ein gesondertes Rendered-Attribut angegeben werden muss.
Hinweis: Hier wird der Render-Tag nicht im Messages-Tag verschachtelt, weil der Messages-Tag das Rendern von Child-Tags ausdrücklich verhindert.
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 for=":#{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. Um eine Komponenten durch eine Faces-Message rendern zu lassen, kann das for-Attribut verwendet werden:<e:render for=":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
Hinweis: Das For-Attribut löst nur die Client-Id auf, das heißt es können relative Id's benutzt werden oder die @-Aliase. Letztlich entspricht es einem Event mit der vollen ClientId der Component, also <e:render for="text"/> hat denselben Effekt wie <e:render event=":form:panel:text"/>. Das Verwender des For-Attributs erleichtert allerdings den Bau der Komponenten und vermeidet Fehler, indem die Id validiert wird.
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#
Jeder der mit Web-Seiten zu tun hat, insbesonder mit JSF, hat schon mit viel Enthusiasmus viele Features, Popups und Steuerelemente in eine Seite eingebaut. Hinterher war die Seite dann so langsam, dass sie eigentlich nicht mehr benutzbar war, hohe CPU-Last und Speicherverbrauch auf dem Server verursacht hat.Scopes nehmen sich genau diesem Thema an, Komponenten können in einen Scope verpackt werden, und zwar komplett. Das heißt XHTML-Dateien, JSF-Komponentenbäume und Managed-Beans. Teile einer Seite können durch AJAX bei Bedarf geladen und auch wieder entladen werden. Damit kombinieren Sie komplexe Funktionalität auf einer Seite mit Leichtgewichtigkeit und Schnelligkeit. Zusätzlicher Vorteil: Komponenten die in Scopes liegen, können auf jeder Seite wieder verwendet werden - sogar mehrfach gleichzeitig.
Im Detail sind Scopes abgetrennte Bereiche auf einer JSF-Seite, mit eigenen Variablen und einer XHTML-View. Beim Laden können Parameter durch <f:param> übergeben werden, diese sind dann über EL-Expressions durch #{scope.<...>} und Bean-Injections @ScopeValue zugreifbar.
Grundsätzlich gibt es zwei verschiedene Arten von Scopes: Scopes mit dem Tag <e:scope> (gebundene Scopes) und Scopes ohne diesen Tag (freie Scopes).
Gebundener Scope | Freier Scope | |
---|---|---|
View-Id | Die View ist direkt über den Tag zugeordnet. | Die View wird erst beim erzeugen durch den Load-Tag festgelegt. |
Anzahl | Der Scope ist durch den Tag festgelegt und kann nur geladen (load) und entladen (unload) werden. | Der Scope kann beliebig oft durch einen Load-Tag instantiiert werden. Dadurch sind unabhängige und sich wiederholende Popups realisierbar. |
Scope-Id | Der Load-Tag referenziert den Scope direkt über seine Scope-Id. | Dem Scope kann über den Load-Tag eine Id zugewiesen werden, wiederholte Load-Actions greifen damit auf denselben Scope zu. Wird keine Scope-Id zugewiesen, erzeugt jeder Load-Tag einen neuen Scope. |
Unload | Der Scope kann sowohl von innen durch einen Unload-Tag entladen werden, als auch von außen durch Angabe der Scope-Id. | Der Scope kann nur entladen werden, wenn eine Scope-Id angegeben wurde. Ohne Scope-Id kann der Scope von innen, durch einen Unload-Tag ohne Scope-Id entladen werden. |
Position | Die View wird dort eingefügt, wo der Tag platziert wurde. Damit ist genau steuerbar, an welcher Stelle der HTML-Code gerendert wird. | Die View wird im HTML-Body eingefügt. Es ist nicht weiter steuerbar, an welcher Stelle die View gerendert wird. |
Anwendungszweck | Hauptsächlich Bildschirmelemente die an verschiedenen Positionen einer Applikation eingefügt werden sollen und dahinter liegende Scoped-Beans besitzen. | Vor allem Popups, die Details einer Information anzeigen und/oder mehrfach abgebildet werden sollen. |
Freien Scope erzeugen#
<h:commandButton value="Test"> <f:ajax/> <e:load viewId="/dialog/test.xhtml"/> </h:commandButton>
Erklärung: Der Command-Button läd einen neuen Scope mit der View /dialog/test.xhtml. Die View wird angezeigt und hat Zugriff auf Custom-Scoped Variablen.
Direkter Scope#
Ein direkter Scope beginnt mit einem Scope-Tag:<e:scope id="scope-id"> <h:outputText value="#{scope.id}"/> </e:scope>
Scope mit Facelet-View#
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 viewId="/dialog/test.xhtml"/>
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 viewId="/dialog/test.xhtml"/> </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 viewId="/dialog/test.xhtml"> <f:param name="someParam" value="someValue"/> <f:param name="someExpression" value="#{bean.someValue}"/> </e:load> </h:commandButton>
Wird ein Scope innerhalt eines bestehenden Scopes geladen, kann es notwendig sein innerhalb des Load-Tags auf den bestehenden Scope zuzugreifen. Dies kann über die Variable scope erfolgen, also zum Beispiel:
<e:load viewId="/dialog/user-password.xhtml"/> <f:param name="user" value="#{scope.user}"/> </e:load>
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:evaluate 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.
Der Load-Tag unterstützt eine Action, innerhalb derer bereits der neue Scope verfügbar ist.
<h:commandButton value="Create user"> <f:ajax/> <e:load viewId="/dialog/createUser.xhtml" action="#{userController.create}/> </h:commandButton>
Erklärung: Würde man die Action im <h:commandButton> aufrufen, wäre der Scope nicht verfügbar, damit könnten auf die Werte aus dem Scope nicht zugegriffen werden.
Hinweis: Falls die Tags innerhalb des Scopes Header-Resourcen installieren, werden diese Transparent nachgeladen. Das bedeutet zunächst eine schlanke Web-Seite, die schnell geladen werden kann. Einzelne CSS-Dateien, Scripts und Images werden erst bei Bedarf nachgeladen.
Für seltene Fälle unterstützt der Load-Tag auch eine Unload-Action. Die Managed-Beans können über eine @PreDestroy-Annotation feststellen, wenn der Scope beendet wird und dort den entsprechenden Code ausführen. Bei kleinen Scopes mächte man in einigen Fällen keinen eigenen Controller, sodass diese Methode verwendet werden kann.
<e:load viewId="/dialog/createUser.xhtml" action="#{scope.projectScope.addCascade(scope)}" unloadAction="#{scope.projectScope.removeCascade(scope)}"> <f:param name="projectScope" value="#{projectScope}"/> </e:load>
Erklärung: Dem Scope "projectScope" wird ein Fenster "Create User" aufgeschaltet. Mit der action wird eine Cascade installiert, die beim Schließen des Project Scope den neuen Create User Scope ebenfalls schließt. Die Unload Action entfernt diese Cascade wieder.
Ein Scope kann auch aus einem JAR geladen werden, wenn er in META-INF/resources liegt:
<e:load viewId="scopes.xhtml" library="debug"/>
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.
Component Injection#
Gelegentlich kann der Wunsch bestehen, einzelne Elemente in eine Scope-Komponente zu injecten. Dieses Design-Pattern kann durch die den Tag <e:insert> erreicht werden, auch im Zusammenhang mit <c:forEach> bzw. <e:for>.Im folgenden Beispiel wird ein Save-Button einem Dialog hinzugefügt:
<app:editResource value="#{processDetails.process.title}" rendered="#{facesContext.externalContext.isUserInRole('ROLE_MANAGER')}"> <h:form id="resource-form"> <ext:buttons> <h:commandButton value="#{messages['save']}" action="#{processDetails.save}"> <f:ajax/> <e:unload scopeId=":textList"/> </h:commandButton> </ext:buttons> </h:form> </app:editResource>
Im Tag <app:editResource>:
<e:load scopeId=":textList"> <f:param name="resource" value="#{cc.attrs.value}"/> <f:param name="children" value="#{cc.children}"/> </e:load>
Und schließlich im Scope:
<c:forEach items="#{scope.children}" var="child"> <h:panelGroup style="padding: 5px 5px 0 0;" rendered="#{child.rendered}"> <e:insert component="#{child}"/> </h:panelGroup> </c:forEach>
Erklärung: Die Composite-Children werden durch die EL-Expression cc.children als Scope-Variable übergeben und durch den Insert-Tag eingefügt. Natürlich kann der Zwischenschritt über die Composite-Component auch entfallen. In diesem Fall können die Children zum Beispiel auch mit der EL-Expression component.children übergeben werden.
Hinweis: Die so eingefügten Komponenten werden an ihrer Ursprungsstelle ausgeführt, was sich insbesondere bei Action-Methoden auswirken kann, wenn auf Variablen zugegriffen wird. Component-Injection sollte daher nur für einfache Elemente verwendet werden, da es schnell zu unsauberen Code führen kann.
Alternative: Als saubere Lösung für Scopes, die in unterschiedlichen Varianten auftreten, kann der Inhalt des Scopes in eine XHTML-Datei ausgelagert werden. Dann werden mehrere Scopes mit den entsprechenden Varianten aufgebaut.
Recovering#
In JSF führen Exceptions in der Regel zu einem Fehler auf einer Seite, die Seite kann nicht dargestellt werden. Oft wird auf eine Fehlerseite umgeleitet. Bei komplexen Seiten, etwa bei vielen Popups, ist dieses Verhalten nicht befriedigend, da erhebliche Daten verloren gehen können.Scopes unterstützen daher Recovering, Standardverhalten ist den betreffenden Scopes zu schließen, wenn darin eine Exception ausgelöst wurde. Zusätzlich enthält jeder Scope ein Attribut, an welches eine Method-Expression gebunden werden kann mit der Signatur boolean recover(java.lang.Throwable). Damit kann eine zusätzliche Methode angegeben werden, um ein Problem in diesem Scope zu lösen. Wurde das Problem gelöst, kann true zurückgegeben werden um den Standard-Recovery zu unterdrücken.
Scope Behaviors#
Die Scope-Komponente kennt folgende Behaviors:- onload: JavaScript Code der beim Laden des Scopes ausgeführt wird.
- onunload: JavaScript Code der beim Schließen des Scopes ausgeführt wird.
<e:scope id="test" recover="testBean.recover"> ... </e:scope>
Onunload Tag#
Manchmal ist es geschickter, Code für den Unload eines Scopes in den entsprechenden Components zu platzieren. Das Cleanup von DOM-Teilen bleibt also zusammen bei der betreffenden Komponente. Dazu gibt es den Tag <e:onunload>:<e:onunload script="cleanUpSomeElements();"/>
Scope Hierarchie#
Ein Scope kann direkt aus der Page geöffnet werden oder aus einem anderen Scope heraus. Im letzten Fall wird im neuen Scope der Parent Scope eingetragen und dem Parent Scope eine Cascade-Anweisung.Cascading#
Wird der Parent Scope geschlossen, werden automatisch alle daraus geöffneten Scopes mit geschlossen. Cascading kann unterbunden werden, wenn bei beim Load-Tag cascade="false" angegeben wird:<e:load viewId="/dialog/test.xhtml" cascade="false"/>
Programmatisches Cascading#
Scopes können auch manuell verlinkt werden, indem die Methode scope.addCascade(Scope scope) aufgerufen wird. Das Cascading kann durch scope.removeCascade(Scope scope) wieder entfernt werden. Dies ist dann interessant, wenn Popup-Fenster über einen Umweg auf andere aufgeschaltet werden.Die Scope-Liste gebraucht diesen Mechanismus:
<e:load scopeId="edit-#{element.id}" viewId="/dialog/object.xhtml" action="#{scope.scope.addCascade(scope)}" unloadAction="#{scope.scope.removeCascade(scope)}"> <f:param name="scope" value="#{element}"/> <f:param name="name" value="#{element.id}"/> <f:param name="object" value="#{element}"/> </e:load>
Debugging#
Wie in der Session oder anderen Scopes, können im Custom-Scope viele Werte liegen. Zu Debugging-Zwecken kann es interessant sein, in die Scopes zu sehen. Das Scope-Debugging ist mit Primefaces implementiert. Das heißt, wenn sie die Scope-Debug-Views verwenden wollen, brauchen sie Primefaces im Projekt.Der Einstieg erfolgt durch:
<e:load viewId="scopes.xhtml" library="debug"/>
Das sieht dann so aus:
Internationalisierung#
JSF Ext enthält einige Erweiterungen, die den Umgang mit Sprach- und Länderunterstützung erleichtern. Es sind SelectItems für Sprachen, Länder und Währungen vorhanden, damit können unter anderem Drop-Down-Menüs befüllt werden:<h:selectOneMenu id="country" value="#{bean.country}"> <f:selectItems value="#{locales.countrySelectItems}"/> </h:selectOneMenu> <h:selectOneMenu id="language" value="#{bean.language}"> <f:selectItems value="#{locales.languageSelectItems}"/> </h:selectOneMenu> <h:selectOneMenu id="currency" value="#{bean.currency}"> <f:selectItems value="#{locales.currencySelectItems}"/> </h:selectOneMenu>
Zusätzlich befindet sich noch ein Getter für localeSelectItems. Dies liefert Liste vollwertiger Locale-Elemente aus Locale.getAvailableLocales() in einer auf der Oberfläche darstellbaren Form.
Des Weiteren befindet sich ein Feld für eine User-Locale in der Session-Scoped Bean. Dadurch kann ein einfaches Auswählen der Locale erreicht werden:
<h:selectOneMenu id="language" value="#{locales.locale}" converter="intersult.String"> <f:selectItems value="#{locales.localeSelectItems}"/> </h:selectOneMenu>
Der Converter "intersult.String" konvertiert ein Locale-Element in einen Sprach-String und zurück, damit das Locale-Objekt mit dem selectOneMenu verwendet werden kann.
Spring Framework#
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:<import resource="classpath:/META-INF/extContext.xml"/>
Hinweis: JSF Ext registriert zusätzlich den ViewScope von JSF.
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.
Hinweis: Statt die ineffiziente Value-Injection über EL-Expression stellt die JSF Spring Integration eine direkte Injection von Scope-Werten durch die Annotation @ScopeValue zur Verfügung.
Ablegen einer Spring-Bean im Scope#
Spring Beans können auch im Custom-Scope abgelegt werden:@Component @Scope(Scopes.CUSTOM_SCOPE) public class Popup { ... }
Oder im View-Scope:
@Component @Scope(Scopes.VIEW_SCOPE) public class Popup { ... }
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.
Primefaces#
JSF Ext integriert sich mit einer Reihe von Frameworks und liefert dafür Tools, die teilweise transparent ablaufen.- Richfaces: Zusätzlich zu dem Form-Submit Problemen bei JSF selbst, treten zusätzliche Render-Probleme im Zusammenhang mit dem AJAX und Richfaces Popups auf. Diese werden durch das in JSF Ext enthaltene Javascript ajax.js transparent gelöst. Es können alls Popups und Elemente, die Forms enthalten neu gerendert werden, ohne dass die Submit-Funktionalität von Command-Buttons udn anderen Steuerelementen verloren geht.
- Spring: Wie bereits im Abschnitt über Scopes beschrieben, können auch Spring-Beans für Scopes und andere JSF Ext Elemente verwendet werden.
- Primefaces: Primefaces Dialogs <p:dialog> können zusammen mit <e:scope> verwendet werden, um ganz unabhängige Dialoge zu realisieren. Zusätzlich liefert JSF Ext eine Lösung zum Anpassen anderer Buttons an das Primefaces Layout.
Primefaces Buttons#
Beim Verwenden von Primefaces werden <h:inputCommand> und native HTML-Buttons nicht im Primefaces-Style dargestellt. Folgende Einträge korrigieren dies (zumindest für Primefaces 3.3):- Im pom.xml die jsf-ext eintragen, wie in JSF Ext beschrieben.
- Ins Page-Template folgenden Eingrag am Ende des Body-Tags vornehmen:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" ... <h:head id="head"> ... </h:head> <h:body id="body"> ... <h:outputStylesheet name="primefaces.css" library="css"/> <h:outputScript name="primefaces.js" library="ext-js"/> </h:body> </html>
Danach erscheinen alle <h:commandButton> und andere Buttons im Primefaces Style.
Validieren#
Die Validierung von Eingabewerten ist von der Idee her eine gute Sache, die Konfiguration ist allerdings nicht ganz so leicht. Spätestens wenn in den Faces-Messages die ClientId der betreffenden Komponenten beim Benutzer am Bildschirm erscheinen, hat man das Gefühl es fehlt noch etwas.Mit JSF Ext kann das Ganze so aussehen:
Die JSF-Validators werden direkt im Frontend angebracht, also in den XHTML-Dateien. Damit können Felder unterschiedlich validiert werden und Situations abhängige Logik implementiert werden. Zum Beispiel kann gesprüft werden, ob zwei Felder gleich sein müssen. Eine Sonderrolle spielt das Attribut required bei vielen UIInput-Elementen, mit dem eine Eingabe erzwungen werden kann.
Bean-Validators werden in Application Support behandelt.
Unique-Validator#
Mit dem Unique-Validator kann ein Property geprüft werden, ob es nur einmal in der Datenbank existiert. Der Unique-Validator braucht den Application Support als Abhängigkeit, da auf das Spring-Entity-Management zugegriffen werden muss.<h:outputLabel for="name" value="Name"/> <h:inputText id="name" value="#{userEdit.user.name}" required="true" validator="intersult.Unique"/>
Erklärung: Der Unique-Validator holt sich die Entity-Klasse und Property über die EL-Expression für das Attribut "value" und prüft ob dieser bereits in der Datenbank vorhanden ist. Falls die Bean bereits persistent ist, wird der betreffende Wert außgenommen, sodass die Bean auch mit demselben Wert für das Property geupdatet werden kann.
Input-Wrapper#
Der Input-Wrapper ist ein Composite-Tag der um ein Input-Element gelegt werden kann. Dadurch werden FacesMessages hinzugefügt und AJAX-Submit ausgeführt, wenn der Wert geändert wird. Das Layout von Eingabeelementen wird dabei vereinheitlicht.Dabei kann man wählen, welche Teile man benutzen möchte. Ganz bequem geht es mit dem Tag <ext:input>. Damit gewrappte Eingabe-Steuerelemente werden automatisch AJAX-Validiert und mit Faces Messages versehen.
<h:form id="some-form"> <h:outputLabel for="name" value="Name"/> <ext:input id="name"> <h:inputText id="name" value="#{userEdit.user.name}" required="true"/> </ext:input> </h:form>
Der Input-Wrapper-Tag ist eine Composite-Component, die man so verwenden kann wie sie ist. Oder man benutzt den Code als Vorlage für ein eigenes Projekt-Layout:
<?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="inputId" default=":#{cc.children[0].clientId}"/> <cc:attribute name="styleClass"/> <cc:attribute name="style"/> </cc:interface> <cc:implementation> <h:panelGrid cellpadding="0" cellspacing="0" style="#{cc.attrs.style}" styleClass="#{cc.attrs.styleClass}"> <e:insert component="#{cc.children[0]}"> <e:ajax event="change"/> </e:insert> <ext:message id="message" for="#{cc.attrs.inputId}"/> </h:panelGrid> </cc:implementation> </html>
Der entsprechende Messages-Tag:
<?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:attribute name="for"/> </cc:interface> <cc:implementation> <h:outputStylesheet name="faces-messages.css" library="css"/> <h:message id="message" for="#{cc.attrs.for}" infoClass="msg-info" warnClass="msg-warn" errorClass="msg-error"> <e:render event="#{cc.attrs.for}"/> </h:message> </cc:implementation> </html>
Die Styles befinden sich im JSF Ext und werden automatisch in den Header geladen.
Converter#
SelectOneMenu und SelectItem#
Das SelectOneMenu und andere Auswahlelemente können nicht direkt mit Objekten umgehen. Eine generische Lösung bietet der intersult.SelectItemConverter:<h:selectOneMenu id="start" value="#{workflowEdit.workflow.start}" converter="intersult.SelectItemConverter"> <f:selectItem itemLabel="#{messages['select']}" noSelectionOption="true"/> <f:selectItems value="#{transitionList.stateList}" var="state" itemLabel="#{state.name}" itemValue="#{state}"/> </h:selectOneMenu>
Erklärung: Der SelectItemConverter nummeriert die SelectItems durch, nutzt dann hashCode- und equals-Methoden um diese wiederzufinden. Bei den Elementen <f:selectItem> kann nun für itemValue die vollwertige Java-Bean angegeben werden, die auch beim Element <h:selectOneMenu> bei value verwendet wird.
Bemerkung: Da für den Browser jedes Element eine Nummer bekommt und nur diese an den Server zurückgeschickt wird, kann es beim zwischenzeitlichen Verändern der Liste zur Auswahl es falschen Elements kommen. Im Regelfall befindet sich die korrekte Liste noch im Component-Tree, sodass der Fehler nicht auftritt.
String-Constructor#
Der Converter intersult.String ist ein allgemeiner Converter für Objekte, die einen Constructor mit einem String-Argument besitzen und toString implementiert ist. Typischer Weise implementiert man einfache Datentypen auf diese Weise, dazu gehört auch Locale.Language und Country#
Die beiden Converter intersult.Language konvertiert ein Locale-Objekt nach dem Language-Anteil als String. Der Converter intersult.Country benutzt den Country-Anteil. Ein Converter für den kompletten Anteil ist nicht erforderlich, da hierfür der Converter intersult.String herangezogen werden kann.Display-Language und -Country#
Im Gegensatz zu Language- und Country-Converter konvertieren diese Converter einen Language- oder Country-String zu einem lesbaren String.Files und Resources#
File Upload#
File Upload und Multipart-Requests war schon immer ein wenig behandeltes Thema in JSF. In JSF 2.2 hat man zwar im Zusammenhang mit dem Servlet Standard 3 einen File Upload. Allerdings funktioniert dieser nur ab Tomcat 7 und nicht zusammen mit AJAX.Das JSF Ext ermöglicht Multipart-Requests und damit auch File Uploads mit Servlet Standard 2. Darüber hinaus ist auch ein Submit mit AJAX möglich. Folgendes Beispiel zeigt die Anwendung:
<h:form id="form" enctype="multipart/form-data"> <h:inputText id="text" value="#{fileUpload.text}"/> <e:inputFile id="file" value="#{fileUpload.file}" filename="#{fileUpload.filename}" mimeType="#{fileUpload.mimeType}"/> <h:commandButton value="Save" action="#{fileUpload.save}"> <f:ajax execute="@form"/> </h:commandButton> </h:form>
Der Tag <e:inputFile> hat folgende EL-Parameter:
Name | Jave-Typ | Beschreibung |
---|---|---|
value | byte | Enthält den Inhalt der Datei. Bei Bedarf wird noch eine Stream-Lösung geliefert. |
filename | String | Enthält den Filenamen der Datei. |
mimeType | Enthält den vom Browser gesendeten Mime-Type. |
Resource Provider#
Ab JSF 2 ist es zwar möglich Resource Handler von dem Interface ResourceHandler abzuleiten. Allerdings erfordert die Implementierung weitere Klassen und eine Vielzahl von Methoden, einen Faces Wrapper und das Registrieren im Faces Context.Eleganter geht es mit der Annotation @ResourceProvider, diese verwandelt die Methode einer Spring-Bean zu einem Resource Handler:
@ResourceProvider("file") public boolean handleResourceRequest(String resourceName) throws IOException { if (file == null) return false; FacesContext context = FacesContext.getCurrentInstance(); context.getExternalContext().getResponseOutputStream().write(file); context.getExternalContext().setResponseContentType(mimeType); context.getExternalContext().setResponseContentLength(file.length); context.responseComplete(); return true; }
Erklärung: Hier wirde explizit der Library-Name "file" angegeben, da die Methode nur auszugsweise gezeigt wird. Dieser Wert kann weggelassen werden, dann wird der Name der Spring-Bean verwendet. Dies vereinfacht auch das Wiederfinden eines Resource Providers.
Hinweis: Dieses Features verwendet zusätzlich Application Support und JSF Spring Integration. In der Regel wird es sich um ein Singleton (SCOPE_SINGLETON) handeln, also dem Default-Scope für eine Spring-Bean. Es können allerdings auch andere Scopes verwendet werden, wie Session Scope, beispielsweise um Nutzer spezifische Daten vorhalten zu können.