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.
Übersicht der Features:
Folgende Inhalte befinden sich auf eigenen Wiki-Seiten:
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"
Damit setzen Sie komplexe, interaktive Applications im Handumdrehen auf.
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>
<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.
In diesem Fall kann der Faces Filter vor den betroffenen Filtern gesetzt werden. Die anderen Filter zwischen dem Faces Filter und dem Faces Servlet können dann auf den Faces Context zugreifen:
<filter> <filter-name>Faces Filter</filter-name> <filter-class>com.intersult.jsf.FacesFilter</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>Faces Filter</filter-name> <url-pattern>/faces/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>
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.
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.
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 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");
Meist arbeitet man soweit es geht mit relativen Ausdrucken und wechselt notfalls zu absoluten. Um die allgemeine Verwendbarkeit von absoluten Ausdrücken in Composite Components zu gewährleisten, beginnt man diese etwa mit :#{cc.clientId}:input-Text. Das funktioniert leider nicht immer, insbesondere wenn Tabellen, For-Tags oder andere dynamische Elemente darüber liegen.
Dazu führt JSF Ext die Syntax ".." ein, die man bereits von relativen Pfadangaben im Dateisystem kennt. Damit ist es nun möglich innerhalb einer Composite Component einen oder einige Components nach oben zu gehen und von dort ab etwas zu selektieren. Also die Kombination von relativen Angaben und Erreichbarkeit der Components:
<e:ajax render="..:#{cc.id}:panel"/>
Hinweis: Jede Angabe von ".." geht eine Naming-Container nach oben. Components die kein Naming Container sind, werden dabei übersprungen, ebenso wie beim ursprünglichen Absteigen im Component Tree mit x:y:z.
<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.
<faces-config> <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> </application> </faces-config>
<import resource="classpath:/META-INF/extContext.xml"/>
Hinweis: JSF Ext registriert zusätzlich den ViewScope von JSF.
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.
@Component @Scope(Scopes.CUSTOM_SCOPE) public class Popup { ... }
Oder im View-Scope:
@Component @Scope(Scopes.VIEW_SCOPE) public class Popup { ... }
<bean class="com.intersult.jsf.spring.EventBeanProcessor"/>
@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.
<?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.
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.
<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.
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: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.
<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.
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. |
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.
DocumentWriter writer = new DocumentWriter(context.getResponseWriter(), "application/pdf"); context.setResponseWriter(writer);
Hinweis: Für die Integration in den JSF Lifecycle sind noch einige andere Dinge zu beachten. Dies zu erklären wäre zu umfangreich für diese Dokumentation, dies kann in der Dokumentation für JSF nachgelesen werden.
Dies kann nützlich sein, wenn der Document-Tree weiter verarbeitet werden soll, zum Beispiel zum Generieren anderer Ansichten.
Hinweis: Für PDF steht das PDF Renderkit zur Verfügung. Andere Browser- und Download-Ansichten sollten generell in der Form eines Renderkit zur Verfügung gestellt werden. Für weitere Unterstützung beraten wir Sie gerne.
Hintergrund: Ohne Redirect-After-Submit wird nach der Navigation auf eine andere Seite in der Browser-URL-Zeile die vorhergehende Seite angezeigt. Des Weiteren wird bei einem Seiten-Request die für den Benutzer oft unverständliche Meldung angezeigt, ob die Seite nochmal abgeschickt werden soll. Ziel des Redirect-After-Submit-Pattern ist beides zu vermeiden.
Redirect-After-Submit wird aktiv, wenn man JSF Ext einbindet und web.xml 3.0 verwendet. Alle Page-Submits mit <h:commandButton>, <h:commandLink> etc. werden mit einem Redirect abgeschlossen.
Ergebnis: Nach der Navigation wird die aktuelle URL im Browser angezeigt. Refresh der Seite im Browser kann ohne Rückfrage durchgeführt werden.
Hinweis: Bei einem Redirect geht üblicher Weise der Zustand der Seite verloren. JSF Ext nutzt den sogenannten Flash-Scope von JSF, um dieses Hindernis zu überwinden und auch Faces-Messages über den Redirect zu retten. Daher ist web.xml 3.0 und Tomcat 7 für das saubere Arbeiten erforderlich.
Bei web.xml 2.x und Tomcat 6 kann ein Fallback-Mechanismus verwendet werden, der die View-Id als GET-Parameter an die URL hängt. Dies ist zwar problemlos möglich, jedoch können dann keine "sauberen" URLs mehr produziert werden. Dieses Verhalten kann mit einem Konfigurations-Parameter in der web.xml aktiviert werden:
<context-param> <param-name>javax.faces.REDIRECT_AFTER_SUBMIT</param-name> <param-value>true</param-value> </context-param>
Dabei existieren zwei Konfigurationsparameter:
Parameter | Bedeutung |
---|---|
javax.faces.IGNORE_EXCEPTIONS | Eine durch Kommata separierte Liste von voll qualifizierten Exception-Klassen-Namen, die ignoriert werden sollen. Diese tauchen nicht als Fehler auf. Damit sollte sehr vorsichtig umgegangen werden, weil es zu versteckten Fehlfunktionen in der Applikation führen kann, die später sehr schwer zu finden sein können. |
javax.faces.PASS_EXCEPTIONS | Diese Exceptions werden an den nächsten Exception-Handler durchgelassen. Dies ist für Exceptions sinnvoll, für die man einen eigenen Exception-Handler schreiben möchte oder eine Dritt-Software hat, die diese handeln möchte. |
Dieser erlaubt das Definieren von Exception-Navigationen in der faces-config.xml:
<navigation-rule> <navigation-case> <from-outcome>org.springframework.security.access.AccessDeniedException</from-outcome> <to-view-id>/security/login.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <navigation-case> <from-outcome>java.lang.Throwable</from-outcome> <to-view-id>/error.xhtml</to-view-id> </navigation-case> </navigation-rule>
Erklärung: Die erste Navigationsregel leitet alle Fälle von AccessDeniedException auf die View /security/login.xhtml um. Die zweite Regel für Throwable leitet alle Exceptions, die nicht von vorherigen Regeln erfasst wurden auf die View /error.xhtml um.
Die aufgetretene Exception wird dabei in einer Bean namens pageError erfasst:
Property oder Methode | Bedeutung |
---|---|
exception | Hier ist die aufgetretene Instanz von java.lang.Throwable enthalten. Dadurch können genauere Informationen über den Fehler angezeigt werden. |
viewId | Hier ist die View-Id gespeichert, bei Bedarf kann diese ebenfalls angezeigt werden. |
init() | Diese Methode kann vor dem Aufbau der Error-Page aufgerufen werden und check ob ein Fehler vorliegt. |
back() | Navigiert zurück auf die Seite auf der der Fehler aufgetreten war, damit der Benutzer weiter arbeiten kann. |