Hier sind einige Informationen über JSF 2 gespeichert.
<cc:interface> <cc:attribute name="value"/> <cc:clientBehavior event="action" name="action" default="true" targets="button"/> </cc:interface> <cc:implementation> <h:commandButton id="button" value="#{cc.attrs.value}"> <cc:insertChildren/> </h:commandButton> </cc:implementation>
Mit dieser Konstruktion unterstützt der Composite-Tag das Client-Behavior "action". Soll umgekeht ein Composite-Tag gebaut werden, der selbst ein Client-Behavior zur Verfügung stellt, stellt JSF Ext weitere Möglichkeiten zur Verfügung.
<?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:clientBehavior event="action" name="action" targets="link" default="true"/> <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>
Erklärung: <cc:attribute name="action"> ermöglicht das optionale Verwenden einer MethodExpression als Attribut action. <cc:clientBehavior event="action"> ermöglicht das Verwenden von ClientBehavior, wie z.B. <f:ajax> auf dem Event action. <cc:actionSource name="action"> ermöglicht das Verwenden von ActionListener Components, wie z.B. <f:setPropertyActionListener>.
@FacesComponent("intersult.Test") public class TabPanel extends UINamingContainer { public String getTest() { return "Hello World!"; } }
und im XHTML instantiiert:
<?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 componentType="intersult.Test"> ... </cc:interface> <cc:implementation> <h:outputText value="#{cc.test}"/> ... </cc:implementation> </html>
Erklärung: Die Custom Composite Component wird über die Variable cc ebenso wie die gewöhnliche UINamingContainer referenziert. Dazu kommen Methoden und Properties, die in der abgeleiteten Klasse implementiert werden.
<?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:attribute name="value" default="0"/> </cc:interface> <cc:implementation> <div id="#{cc.clientId}"> <h:form id="header-form"> <table id="#{cc.clientId}:header" style="width: 100%;" cellpadding="0" cellspacing="0"> <tr> <c:forEach id="repeat" items="#{cc.children}" var="tab" varStatus="tabStatus"> <e:div element="td" styleClass="#{cc.attrs.value == tabStatus.index ? 'tab-active' : 'tab-inactive'}" style="padding-left: 5px;"> <e:ajax event="click" rendered="#{cc.attrs.value != tabStatus.index}" render=":#{cc.clientId}"> <e:set value="#{tabStatus.index}" target="#{cc.attrs.value}"/> </e:ajax> <h:outputText value="#{tab.attributes.header}"/> </e:div> </c:forEach> <td style="padding-right: 5px; width: 100%;"/> </tr> </table> </h:form> <e:div id="content"> <e:insert component="#{cc.children[cc.attrs.value]}"/> </e:div> </div> </cc:implementation> </html>
Erklärung: Der Set-Tag weißt den aktuellen Index (tabStatus.index) dem Attribut "value" zu (cc.attrs.value). Dieses Attribut wird verwendet, um den entsprechenden Tab-Content zu laden. Die Tabs selbst sind wiederum einfache Composite Tags mit einem Attribut "header".
<?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" > <cc:interface> <cc:attribute name="status" default="begin"/> </cc:interface> <cc:implementation> <script type="text/javascript"> jsf.ajax.addOnEvent(function(data) { document.getElementById('#{cc.clientId}:status').style.visibility = data.status == '#{cc.attrs.status}' ? 'visible' : 'hidden'; }); </script> <div id="#{cc.clientId}:status" style="visibility: hidden;"> <cc:insertChildren/> </div> </cc:implementation> </html>
Verwendung zum Beispiel wie folgt:
<test:ajaxStatus> <h:graphicImage value="/images/wait30trans.gif"/> </test:ajaxStatus>
pom.xml:
<dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>3.4-SNAPSHOT</version> </dependency> ... <repositories> <repository> <id>prime-repo</id> <name>Prime Technology Maven Repository</name> <url>http://repository.primefaces.org</url> <layout>default</layout> </repository> </repositories>
web.xml, es ist eine Web-App 3.0 erforderlich:
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> ... <servlet> <servlet-name>Push Servlet</servlet-name> <servlet-class>org.primefaces.push.PushServlet</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>Push Servlet</servlet-name> <url-pattern>/primepush/*</url-pattern> </servlet-mapping> ... </web-app>
Java:
public void someMethod() { PushContextFactory.getDefault().getPushContext().push("/test", null); }
Der Eintrag "/test" ist der sogenannte Channel über dem gepusht wird.
Im XHTML wird entweder ein AJAX-Tag verwendet:
<p:socket channel="/test" autoConnect="true"> <p:ajax event="message" update=":push-form:chat"/> </p:socket>
Oder wenn der Update manuell gemacht werden soll, kann auch Javascript verwendet werden:
<p:socket channel="/test" autoConnect="true" onMessage="someMethod"/>
Die Methode wird dann mit einem Parameter "data" aufgerufen, welches dem JSON-Serialisiertem Objekt aus dem Server-Push-Aufruf context.push("/test", data); entspricht.
Alternativ kann JSF Ext mit ins Projekt eingebunden werden, die Komponenten-Bibliothek enthält Javascript-Code, der das Problem transparent gelöst.
Eine weitere Ursache sind sich veränderte Client-Ids zwischen Rendering und Form Submit. Eine Lösung ist, den Naming-Containern und den Submit-Elementen Ids zu geben. Dies erleichtert generell das Debugging von Formularen, da die Ids oft in Fehlermeldungen angezeigt werden, die betreffenden Elemente werden so leicht gefunden.
<?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" > <cc:interface name="test"> <cc:attribute name="element" required="true"/> <cc:attribute name="rendered"/> </cc:interface> <cc:implementation> <c:if test="#{!rendered or rendered}"> <h:outputText value="<#{cc.attrs.element} id="#{cc.clientId}"" escape="false"/> <c:forEach items="#{cc.attributes}" var="attribute"> <h:outputText value=" #{attribute.key}="#{attribute.value}"" escape="false" rendered="#{!attribute.key.startsWith('javax.faces') and !attribute.key.startsWith('com.sun') and attribute.key != 'element' and attribute.key != 'rendered'}"/> </c:forEach> <h:outputText value=">" escape="false"/> <cc:insertChildren/> <h:outputText value="</#{cc.attrs.element}>" escape="false"/> </c:if> </cc:implementation> </html>
FacesContext.getCurrentInstance().isValidationFailed(); #{facesContext.validationFailed}
<context-param> <param-name>javax.faces.STATE_SAVING_METHOD</param-name> <param-value>server</param-value> </context-param> <context-param> <param-name>javax.faces.PROJECT_STAGE</param-name> <param-value>Development</param-value> </context-param> <context-param> <param-name>javax.faces.FACELETS_LIBRARIES</param-name> <param-value>/WEB-INF/test.taglib.xml</param-value> </context-param> <context-param> <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name> <param-value>true</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>
Der Eintrag javax.faces.FACELETS_SKIP_COMMENTS sorgt dafür, dass in XML-Kommentaren "<!-- Comment -->" keine EL-Expressions ausgewertet werden. Wird dieses Konfiguration nicht angegeben, kann es zu schwer auffindbaren Fehlern kommen.
<h:commandButton value="Test" action="#{test.action('param')}"/>
In der pom.xml die Libs referenzieren:
<dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>el-impl</artifactId> <version>2.2</version> </dependency> <repository> <id>maven.java.net</id> <name>Java.net Maven2 Repository</name> <url>http://download.java.net/maven/2</url> </repository>
Und in der web.xml die Factory festlegen:
<context-param> <param-name>com.sun.faces.expressionFactory</param-name> <param-value>com.sun.el.ExpressionFactoryImpl</param-value> </context-param>
In Google Application Engine klappt das nicht, dort kann nur JBoss EL verwendet werden.
<ui:debug hotkey="q"/>
Das Hotkey arbeitet in diesem Fall mit CTRL+SHIFT+Q. Falls das Hotkey nicht funktioniert, ist es wahrscheinlich durch etwas anderes abgefangen und man ersetzt es durch ein anderes.
%JAVA_HOME%/jre/lib/logging.properties
konfiguriert wird. Dort wird zunächst das Logging des java.util.logging.ConsoleHandler auf ALL gestellt, damit Logging-Events höher als INFO auch ausgegeben werden. Dann können Einträge hinzugefügt werden, wie zum Beispiel für javax.enterprise.resource.webcontainer.jsf.context, um Exceptions bei EL-Expressions auszugeben:
Den vorhandenen Eintrag auf ALL umstellen:
java.util.logging.ConsoleHandler.level = ALL
Und diesen neuen Eintrag am Ende der Datei hinzufügen:
javax.enterprise.resource.webcontainer.jsf.context.level=FINE
Eine Übersicht der möglichen Logging-Events befindet sich in der Enum com.sun.faces.util.FacesLogger
FacesContext.currentInstance().isProjectState(ProjectStage.Development)
Oder im Frontend:
#{facesContext.application.projectState == 'Development'}
<context-param> <param-name>com.sun.faces.injectionProvider</param-name> <param-value>com.sun.faces.vendor.WebContainerInjectionProvider</param-value> </context-param>
@Override public void onComponentCreated(FaceletContext context, UIComponent component, UIComponent parent) { parent.getChildren().add(component); }
Ein interessanter Artikel dazu.
FacesContextFactory factory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE); factory.getFacesContext(servletContext, request, response, lifecycle);
Außerhalb von Servlets braucht man den ServletContext, HttpServletRequest und HttpServletResponse, der nicht ganz einfach zu implementieren ist.
Das aktuelle Render Kit kann durch den Request-Parameter "javax.faces.RenderKitId" angesprochen werden, dieser befindet sich als Konstante unter ResponseStateManager.RENDER_KIT_ID_PARAM. Also zum Beispiel die URL:
http://localhost/test/faces/index.xhtml?javax.faces.RenderKitId=test.RenderKit
Dieser Parameter kann auch durch ein Hidden-Field übergeben werden, sodass zum Beispiel Tabellen-, PDF- und andere Downloads generisch für Web-Seiten entwickelt werden können.
Üblich sind <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> oder <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Zu lösen ist das durch einen Eintrag in der faces-config.xml:
<?xml version="1.0" encoding="UTF-8"?> <faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" > ... <faces-config-extension> <facelets-processing> <file-extension>.xhtml</file-extension> <process-as>xhtml</process-as> </facelets-processing> </faces-config-extension> ... </faces-config>
<h:commandLink value="#{fileUpload.filename}" action="#{fileUpload.download}" rendered="#{fileUpload.file != null}"/>
Die zugehörige Action-Methode:
public void download() throws IOException { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); externalContext.setResponseContentType(mimeType); externalContext.addResponseHeader("Content-Disposition", "attachment; filename=" + filename); externalContext.setResponseContentLength(file.length); OutputStream outputStream = externalContext.getResponseOutputStream(); outputStream.write(file); FacesContext.getCurrentInstance().responseComplete(); }
Erklärung: Der Download wird unmittelbar durch Anklicken des Links gestartet. Es wird kein kurzzeitiger Browser-Tab oder -Fenster geöffnet, die sich ohne sinnvollen Inhalt gleich wieder schließen, wie man das oft bei Web-Seiten sieht.
Als Variante kann der Browser bestimmte Mime-Types direkt anzeigen, wie z.B. GIF-, PNG-, JPG-Bilder, PDF-Dateien, je nach installierter Software noch mehr. Dies erreicht man durch hinzufügen von target="_blank" beim Link und setzen der Content-Disposition auf "inline":
<h:commandLink value="#{fileUpload.filename}" target="_blank" action="#{fileUpload.download}" rendered="#{fileUpload.file != null}"/>
public void download() throws IOException { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); externalContext.setResponseContentType(mimeType); externalContext.setResponseContentLength(file.length); OutputStream outputStream = externalContext.getResponseOutputStream(); outputStream.write(file); FacesContext.getCurrentInstance().responseComplete(); }