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>
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.
<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.
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" 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.