Das Produkt JSF Ext enthält eine ganze Reihe von JSF-Tags. Diese werden auf dieser Seite beschrieben.
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>
In vielen Fällen hab man bereits Behavior-Components zu einem Tag hinzugefügt. Würde man dennoch das entsprechende Attribut verwenden (z.B. onclick bei commandButton), kann es zu Problemen kommen, wie die Reihenfolge der Snippets oder das "return false;"-Anhängsel.
Lösung bringt der Behavior-Tag von JSF Ext. Typisch ist zum Beispiel die Verwendung zusammen mit dem AJAX-Tag:
<h:commandButton value="X"> <f:ajax/> <e:unload/> <e:behavior script="#{rich:component('popup-panel')}.hide(event);"/> </h:commandButton>
Die Behavior-Snippets werden in der entsprechenden Reihenfolge produziert:
jsf.util.chain(this,event,'mojarra.ab(this,event,\'action\',0,0)','RichFaces.$(\'popup:form:popup-panel\').hide(event);');return false
<h: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 |
<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.
<h:panelGrid> <f:ajax event="dblclick" action="#{bean.action}"/> </h:panelGrid>
<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>
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.
<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.
<h:panelGroup> <e:attribute name="oncontextmenu"> <f:ajax listener="#{bean.render}" render=":some-menu"/> </e:attribute> </h:panelGroup>
<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>
<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.
<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>
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.
<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.
<?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.
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.
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.
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.
<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"/>
<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>
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>
Die alte Implementierung über Render-Attribute führt zu folgenden Verbesserungswünschen:
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.
Hier kann der Tag <e:async> verwendet werden, dieser lädt seinen Inhalt erst nach dem Rendern der Seite per AJAX nach:
<script type="text/javascript"> jsf.ajaxQueue = 10; </script> <c:forEach begin="1" end="20" var="index"> <e:div style="height: 20px; background-color: #e0e0e0; margin-bottom: 3px;"> <e:async> <h:outputText value="Async Content #{index}, sleep = #{async.sleep}"/> </e:async> </e:div> </c:forEach>
Und eine Klasse, die den Request verzögert:
@Component @Scope(WebApplicationContext.SCOPE_REQUEST) public class Async { private static Random random = new Random(); public int getSleep() throws InterruptedException { int sleep = random.nextInt(3000); Thread.sleep(sleep); return sleep; } }
Der Tag <e:socket> nutzt Web-Sockets um Browser-Push durchzuführen. Dies ist eine sehr effiziente Implementierung mit geringer Verzögerung zwischen dem Versenden der Nachricht und dem Eintreffen im Browser, sowie Netzwerk und Ressourcen schonend. Dies ist möglich, wenn der Web-Server den Web-Standard 3.0 beherrscht (z.B. ab Tomcat 7). Sind Web-Sockets nicht verfügbar aufgrund eines Web-Servers kleiner als 3.0, weil der Browser dies nicht beherrscht oder ein dazwischen liegender Proxy es nicht unterstützt, wird die Comet-Engine verwendet, die Long-Polling durchführt.
Zur Verwendung wird einfach das Atmosphere-Framework in die pom.xml konfiguriert:
<dependency> <groupId>org.atmosphere</groupId> <artifactId>atmosphere-runtime</artifactId> <version>2.0.0.RC3</version> </dependency>
Hintergrund: Es wird das Atmosphere-Framework verwendet, das mit allen gängigen Application-Servern und Browsern zusammen arbeitet.
Die Anwendung des Sockets sieht zum Beispiel so aus:
<e:socket channel="/test"> <f:ajax render=":push-form:chat"/> </e:socket>
Auf der Java-Seite wird der Socket dann so angesprochen:
PushContext.instance().push("/test");
Hinweis: Die Push-Funktionalität befindet sich momentan in der Entwicklung. In Zukunft werden weitere Funktionalitäten hinzukommen.
Wird ein Load-Balancer verwendet, sind die Web-Sockets zu beachten. Bei Apache werden folgende Proxy-Einträge vor der eigentlichen Applikation gebraucht:
ProxyPass /<context-path>/faces/javax.faces.resource/<channel> ws://<ziel>/<context-path>/faces/javax.faces.resource/<channel> ProxyPassReverse /<context-path>/faces/javax.faces.resource/<channel> ws://<ziel>/<context-path>/faces/javax.faces.resource/<channel>
Erklärung: Die Web-Socket-Protokolle WS werden vor den eigentlichen Proxy-Einträgen für die Application abgefangen und mit dem WS-Protokoll umgeleitet. In Apache 2.4 steht dafür das Modul mod_proxy_wstunnel.so zur Verfügung.