Das Produkt JSF Ext enthält eine Reihe von JSF-Tags. Diese Seite beschriebt die Anwendung der Tags.
Ausnahmen:
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.
<e:script script="ext.util.flash('some-tag', '#80ff80');"/>
Möchte man zum Beispiel einen Composite-Tag schreiben, der am unteren Ende einer Page ein Behavior ausführt, kann das wie folgt geschehen:
<?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" > <cc:interface> <cc:clientBehavior name="behavior" event="behavior" targets="behavior" default="true"/> </cc:interface> <cc:implementation> <h:outputScript name="util.js" library="ext-js"/> <script id="#{cc.clientId}" type="text/javascript"> ext.util.addListener(window, 'scroll', function(event) { var body = event.target.body; var bottom = body.scrollTop + window.innerHeight; if (bottom == body.scrollHeight) { <e:outputBehavior id="behavior"/> } }); </script> </cc:implementation> </html>
Erklärung: Der Tag definiert das Behavior "behavior" als Behavior-Parameter. Das übergebene Behavior wird dann an der Stelle des <e:outputBehavior> im JavaScript-Block ausgegeben.
Hinweis: Dieser Tag ist als <ext:onscroll> bereits in JSF Ext enthalten.
Behavior | Evaluate | Script | |
---|---|---|---|
Zeitpunkt der Ausführung | Direkt durch den Browser-Event | Durch den AJAX-Request an den Browser gesendet | Durch das AJAX-Rendering der Component |
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 | Nur beim AJAX-Rendering, nicht beim Aufbau der Seite. |
Anwendungszweck | Steuerung des Browserverhaltens, Ein- und Ausblenden, Fading, dynamische Browserelemente, Client-Behavior | Abwickeln von Server-States, Popups öffnen und Schließen, Submits bestätigen | Spezielle AJAX- und Push-Features, Highlighting, Informationen. |
<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>
<h:graphicImage name="process.png" library="images/info"> <e:ajax event="click" action="/index.xhtml"> <f:param name="mode" value="AVAILABLE"/> </e:ajax> </h:graphicImage>
Achtung: Die im Value-Attribut verwendeten EL-Expressions werden bereits beim Rendern der View ausgewertet und können möglicher Weise veraltete, nicht gewünschte Werte enthalten. Verwenden sie diese Möglichkeit zur Übergabe von Parametern nur, wenn sie genau wissen, was sie tun.
Hinweis: In den meisten Fällen ist es angebraucht, die Werte mit <e:set> (bzw. <f:setPropertyActionListener>) zu setzen, weil die entsprechenden EL-Expressions erst beim Drücken des Knopfs ausgewertet werden.
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>
<e:load viewId="/dialogs/someDialog.xhtml"> <e:method-param name="action" value="#{test.action}"/> </e:load>
Damit können parameterisierte Dialoge erstellt werden, deren Action-Elemente bestimmte Methoden ausführen.
Erklärung: Der Aufruf findet dann über #{scope.action.invoke} statt. Die eigentliche Methode wird im EL-Scope als Value abgelegt und enthält die Methode "invoke", mit der die Methode dann aufgerufen werden kann.
Hinweis: Der Aufruf kann auch mit Parametern erfolgen, wenn #{scope.action.invoke(param)} verwendet wird.
Soll eine derart übergebene Method-Expression weitergereicht werden, kann dies als normaler <f:param> erfolgen:
<h:commandLink value="Nested"> <f:ajax/> <e:load scopeId="select" viewId="/dialog.xhtml"> <f:param name="action" value="#{scope.action}"/> </e:load> </h:commandLink>
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.
Darüber hinaus können die Klassen requiredStyleClass und notRequiredStyleClass auch manuell festgelegt werden, indem diese als Attribut angegeben werden.
<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>
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 Async-Tag unterstützt weiterhin die Attribute styleClass, style und das Facet "preview", mit dem ein Vorab-Content angezeigt werden kann:
<e:async> <f:facet name="preview"> <h:outputText value="Loading content..."/> </f:facet> </e:async>
Folgender XHTML-Code erzeugt eine Reihe von Knöpfen:
<h:form id="form"> <e:for id="for" value="#{table.list}" var="element"> <h:commandButton id="button" value="#{element.text}" action="#{element.action}"> <f:ajax/> </h:commandButton> </e:for> </h:form>
Das Attribut value kann dabei durch eine Collection, Map, Array, einzelnes Element oder einen Null-Wert versorgt werden.
Mit dem Attribut test können Elemente aus der Schleife gefiltert werden, entsprechend dem Syntax von <c:if test="...">.
Daher bietet JSF Ext einen Tag für das Rendern von vertikalen Text:
<e:vertical-text value="Hello World!" fontSize="16"/>
Attribut | Bedeutung |
---|---|
fontSize | Die Größe des Schriftsatz in Point (pt) |
width | Breite der gerenderten Grafik in Pixel |
height | Höhe der gerenderten Grafik in Pixel |
fontName | Name des Fonts |
color | Farbe der Schrift |
Eine einfachere Lösung für verteilte Anwendungen ist das Einbetten von Inhalten mit dem Tag <e:embed src="...">. Die Inhalte können vom gleichen Server, der gleichen Anwendung oder einer ganz anderen Quelle kommen. Da mit IFrames gearbeitet wird, ist nicht einmal JSF erforderlich oder die gleichen Komponentenbibliotheken.
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.
<h:commandButton value="Invoke Actions"> <f:ajax/> <e:action listener="#{bean.action1}"/> <e:action listener="#{bean.action2}"/> <e:action listener="#{bean.action3}"/> </h:commandButton>
Der Workaround besteht im Wrappen des Tags <e:actionContext> um die eigentliche Action-Source:
<p:treeTable id="table" value="#{cc.attrs.value}" var="node"> <p:column> <e:actionContext> <p:autoComplete ...> <f:attribute name="param" value="#{node.someValue}"/> </p:autoComplete> </e:actionContext> </p:column> </p:treeTable>
Erklärung: Bei der Primefaces Tree-Table wurde offenbar vergessen der Component-Push
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.
<ext:onscroll> <e:ajax action="#{dashboard.more}" render=":dashboard-form:transaction-list:list"/> </ext:onscroll>
Erklärung: Der Tag führt das Behavior <e:ajax/> aus, sobald ans untere Ende der Page gescrollt wird.
#{resource['<library>:<name>']}
zu erzeugen oder ganz zu Fuß mit
#{request.contextPath}/faces/javax.faces.resource/<name>?ln=<library>
Möchte man einen Link erzeugen vom Schema <a href="...">, bietet JSF Ext eine schönere Lösung:
<e:resourceLink name="<name>" library="<library"> <h:outputText value="Some link"/> </e:resourceLink>
Hinweis: Wie der normale <h:outputLink> unterstützt <h:resourceLink> ebenfalls Parameter vom Typ <f:param>. Des Weiteren ergänzt sich der Link gut mit Resource Provider
Der Severity-Tag ist eine Composite Component mit folgender Implementierung:
<h:panelGroup id="messages" style="#{cc.attrs.style}" styleClass="#{cc.attrs.styleClass}"> <e:render id="render" for=":#{cc.attrs.prefix}:*"/> <h:graphicImage name="info.png" library="images/message" rendered="#{e:severity(cc.attrs.prefix) == 'INFO'}"/> <h:graphicImage name="warn.png" library="images/message" rendered="#{e:severity(cc.attrs.prefix) == 'WARN'}"/> <h:graphicImage name="error.png" library="images/message" rendered="#{e:severity(cc.attrs.prefix) == 'ERROR'}"/> </h:panelGroup>
<h:form id="form"> <e:captcha id="captcha"/> <ext:message for=":form:captcha"/> ... </h:form>
Erklärung: Die Captcha Component rendert ein PNG-Image mit dem entsprechenden Text, sowie ein Eingabefeld, in dem der Text durch den Benutzer eingegeben wird. Die Component hat eine positive Validierung, wenn das Captcha korrekt ist, andernfalls schlägt die Validierung fehl und eine Faces Message wird eingefügt. Das Fehlschlagen der Validierung verhindert das Ausführen von Action Methoden, die an Command Buttons und ähnlichem hängen.
Um die Captcha Component zu verwenden, ist folgendes Maven Artifact in der pom.xml einzubinden:
<dependency> <groupId>com.octo.captcha</groupId> <artifactId>jcaptcha</artifactId> <version>1.0</version> <exclusions> <exclusion> <artifactId>servlet-api</artifactId> <groupId>javax.servlet</groupId> </exclusion> </exclusions> <scope>provided</scope> </dependency>
Hinweis: Das Artifact hat die servlet-api im Scope "compile" eingebunden. Um das Deployen der servlet-api.jar zu vermeiden, ist die Exclusion erforderlich. Andernfalls kann es zu Problemen beim Deployment kommen.
<h:inputTextarea id="comment" value="#{processDetails.comment}"> <e:charactersLeftBehavior event="keyup"/> </h:inputTextarea>
Erklärung: Die Anzahl möglicher Zeichen werden den Javax-Validation-Tags @Size und @Length oder dem LengthValidator entnommen. Die verbleibenden Zeichen werden dann im Text-Feld eingeblendet, die Eingabe wird auf die maximale Zeichenzahl begrenzt.