JSF Ext
This is version . It is not the current version, and thus it cannot be edited.
Back to current version   Restore this version

JSF Ext oder JSF Extensions ist nun die dritte Auflage unserer beliebten Erweiterungen für JSF. Nach den Versionen Intersult Taglib für JSF 1.2, JSF Desktop für JSF 2.0 ist nun JSF Ext für JSF 2.0 bis 2.2 verfügbar. JSF Ext ist eine Erweiterung von JSF 2 (2.0, 2.1, 2.2) um einige grundlegende Features, die man für saubere, funktionale AJAX-Applikationen braucht.

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:

Inhalt#

Overview#

The main features of the JSF Ext (formerly JSF Desktop) are:

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:

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#

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.

NameJavaParameterBeschreibung
javax.faces.post_constructEvent.EVENT_POST_CONSTRUCTObject managedBeanDer Event wird nach dem Erzeugen einer managed Bean durch das JSF-System erzeugt.
javax.faces.pre_destroyEvent.EVENT_PRE_DESTROYDer Event wird vor dem Entfernen einer managed Bean erzeugt.
javax.faces.eventEvent.EVENT_EVENTString event, Object... argumentsDieser Meta-Event wird bei jedem Event erzeugt, sodass allgemeine Event-Handler geschrieben werden können. Dies ist mit Vorsicht zu verwenden.
javax.faces.messagesMessageHandler.EVENT-Der Event wird erzeugt, wenn Faces Messages vorliegen.
login.beforeLogin.EVENT_LOGIN_BEFORE-Bevor ein Benutzer eingeloggt wird.
login.successLogin.EVENT_LOGIN_SUCCESS-Nachdem ein Benutzer erfolgreich eingeloggt wurde. Der Benutzer ist über Login.instance().getUser() abrufbar.
login.failLogin.EVENT_LOGIN_FAIL-Nachdem ein Login fehlgeschlagen ist.
login.afterLogin.EVENT_LOGIN_AFTER-Nach dem Login-Prozess, unabhängig ob er erfolgreich oder fehlgeschlagen ist.
login.logoutLogin.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:

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#

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.

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):
  1. Im pom.xml die jsf-ext eintragen, wie in JSF Ext beschrieben.
  1. 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:

NameJave-TypBeschreibung
valuebyteEnthält den Inhalt der Datei. Bei Bedarf wird noch eine Stream-Lösung geliefert.
filenameStringEnthält den Filenamen der Datei.
mimeTypeEnthä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.

Rechliches#

JSF ist eine Wort- und Bildmarke, auf die keinerlei Anspruch erhoben wird. Die Nennung dieser Marke dient ausschließlich der Referenz und Zuordnung dieses Produkts.