JSF features several code reuse mechanisms for XHTML views. Here are those without need of additional Java code, ordered by complexity, simplest first.
-
Including
Done via elementui:include
withsrc="includes/file.xhtml"
attribute, and optional nestedui:param
that supports#{}
bean-properties, but not methods. CAUTION: When including relatively from a sub-directory, and the included file also includes relatively, that second include will fail! -
Templating
The includer can override elements, marked byui:insert
, throughui:define
, and can passui:param
parameters, but also no support for methods. It is possible to inject complete HTML fragments into the included template. Useful for unified look and layout. Namespace prefix "ui:",insert
,define
,composition
,decorate
, .... -
Composite Component
Advanced JSF 2 component concept. Combines a number of existing components with markup to form new components. Namespace prefix "cc:" or "composite:",interface
,attribute
,implementation
, .... Result is an XML-element with an arbitrary name and namespace belowhttp://xmlns.jcp.org/jsf/composite/
. Makes a XHTML view reusable for different Java controllers, because the parameter attributes can be evaluated with all kinds of#{}
expressions, even methods.
The differences are not that big. Inclusion does not provide method parameter passing, but it can be parameterized with Java properties. Templating doesn't provide methods either, but enables inserting whole XHTML fragments into the included file. Composite component finally allows method parameter passing, and its final appearance is a completely new XML element type, driven by attributes.
Setup
Please refer to
my recent Blog
for a JSF 2.3 setup with Eclipse, there you find the Maven pom.xml
,
Servlet web.xml
, JSF faces-config.xml
, CDI beans.xml
and ApplicationFacesConfig.java
.
I found out that you can omit the Eclipse configuration when you do following:
- In a file explorer, create a simple Maven project directory structure (not a "dynamic web project" archetype!)
- Add
pom.xml
with JSF dependencies in its root directory - Run Maven build from commandline:
mvn package
- Import the project into Eclipse as "Maven project" (not as "Java project"!)
Example Index
1. Including
This example renders dates in a special way. Here is a screenshot of the user interface:
Project source files and directories:
includer.xhtml
(master view)
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:f="http://xmlns.jcp.org/jsf/core" > <h:head> <title>JSF Include Example</title> </h:head> <h:body> <h2>JSF <ui:include> Example with <ui:param></h2> <span> Once the <ui:include src="includes/date-box.xhtml"> <ui:param name="label" value="Creation Date"/> <ui:param name="value" value="#{dateHolder.creationDate}"/> <ui:param name="pattern" value="d.M.yyyy, HH:mm"/> </ui:include> </span> <span> mattered, but now it's just the <ui:include src="includes/date-box.xhtml"> <ui:param name="label" value="Latest Update"/> <ui:param name="value" value="#{dateHolder.lastUpdate}"/> <ui:param name="borderColor" value="red"/> </ui:include> .... </span> </h:body> </html>
date-box.xhtml
(the included XHTML)
<!DOCTYPE html> <!-- Formatted date with titled border. --> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" > <h:body> <ui:param name="borderColor" value="#{empty borderColor ? 'black' : borderColor}"/> <ui:param name="pattern" value="#{empty pattern ? 'yyyy-MM-dd HH:mm' : pattern}"/> <fieldset style=" display: inline-block; border-radius: 0.6em; border-color: #{borderColor}; "> <legend style="color: #{borderColor};">#{label}</legend> <h:outputText value="#{value}"> <f:convertDateTime pattern="#{pattern}"/> </h:outputText> </fieldset> </h:body> </html>
DateHolder.java
(Java bean that provides dates, used by the master view)
package fri.jsf; import java.io.Serializable; import java.time.Instant; import java.util.Date; import javax.faces.view.ViewScoped; import javax.inject.Named; @Named @ViewScoped public class DateHolder implements Serializable { public Date getCreationDate() { return Date.from(Instant.EPOCH); } public Date getLastUpdate() { return new Date(); // now } }
You don't need setters here, these date properties are read-only.
Explanations
Inclusion can happen everywhere, in this case among inline elements.
The parametrization is easy to understand, although restricted to Java bean properties and plain text.
As you see we can pass a date into date-box.xhml
, and it gets formatted there,
so parametrization is not restricted to strings.
Have a eye on the parameters in includer.xhml
and their re-definitions inside date-box.xhml
.
That's the way to establish default values.
You can use the same parameter-names on both sides!
We make the first date-box use another date-format pattern
than the second.
We insert the borderColor
parameter into CSS code to set a red color.
2. Templating
This example lays out a scrolling web page with a fixed header, the header having one or two lines. The first header line is divided into left, center and right part. Here is a screenshot with a one-line header:
File structure:
templater.xhtml
(master view)
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:f="http://xmlns.jcp.org/jsf/core" > <h:head> <title>JSF Template Example</title> </h:head> <h:body> <!-- Everything outside <ui:composition> will be ignored! --> <ui:composition template="templates/sticky-header-page.xhtml"> <ui:param name="headerBackColor" value="orange"/> <ui:define name="headerTextLeft"> <!-- Explicitly erase left side default text --> </ui:define> <ui:define name="headerTextCenter"> Pirate Adventures </ui:define> <!-- ui:define name="headerSecondLine"> Second Line </ui:define --> <ui:define name="contentText"> <ui:include src="includes/long-text.html"/> </ui:define> </ui:composition> <!-- To not ignore everything outside <ui:composition>, use <ui:decorate> --> </h:body> </html>
sticky-header-page.xhtml
(the template)
<!DOCTYPE html> <!-- A fixed header and a page scrolling underneath. The header has a left/center/right part, and an optional second line. --> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html"> <h:body> <!-- The fixed page header --> <div style=" position: fixed; top: 0; left: 0; right: 0; text-align: center; padding: 0.5em; background-color: #{empty headerBackColor ? 'lightGray' : headerBackColor}; "> <div> <span style="float: left;"> <ui:insert name="headerTextLeft"> [Author] </ui:insert> </span> <span><b> <ui:insert name="headerTextCenter"> [Title] </ui:insert> </b></span> <span style="float: right;"> <ui:insert name="headerTextRight"> [Date] </ui:insert> </span> </div> <div> <ui:insert name="headerSecondLine"> <ui:param name="noSecondLine" value="true" /> </ui:insert> </div> </div> <div style="clear: both;"></div> <!-- The scrolling page content --> <div style="padding-top: #{noSecondLine ? '2em' : '3em'};"> <ui:insert name="contentText"/> </div> </h:body> </html>
long-text.html
(long example text for scrollbars, goes to contentText
)
<!DOCTYPE html> <html> <body> Lorem ipsum dolor sit amet, consectetur adipiscing elit. ...... </body> </html>
Copy the complete nonsense text from lipsum.com to make the page scroll.
Explanations
Parameterization is the same as with inclusions.
Here we have headerBackColor
as plain <ui:param>,
which sets an orange background color to the header.
The difference between parameters and insertions is that the latter can receive entire HTML fragments.
Essential is that any name
attribute in a <define>
of the master page
matches a name
attribute in an <insert>
of the template.
Such insertion points are headerTextLeft
, headerTextCenter
,
headerTextRight
, headerSecondLine
, and contentText
.
Mind that not all insertion points are used,
and that the insertion points sometimes define defaults and sometimes not.
In case they define defaults, you can erase those explicitly by passing empty,
as done with headerTextLeft
(see screnshot, left text is not present).
In case no define was done, the insert's default will be visible when present,
like in headerTextRight
.
On the other hand the headerSecondLine
insertion point does not have a default and
will not be present when no define was done for it.
This opens a dependency -
the template's layout depends on the presence of headerSecondLine
,
because the padding-top of the contentText
must be set correctly to not obscure the scrolling text underneath.
The nested <ui:param> noSecondLine
will be true
when no define for headerSecondLine
was done.
In that case the padding-top goes to 3em, else it will be 2em.
Setting such a <ui:param> is a trick to find out whether a define was done or not,
as the whole headerSecondLine
insertion point would be removed when being replaced by a define.
Try commenting in the out-commented headerSecondLine
in
templater.xhtml
master file, and check that the padding-top automatically grows!
The contentText
gets replaced by a HTML file loaded through <ui:include>.
Such can be mixed in at any time.
Please mind that, when you have a file containing <ui:composition>, JSF would ignore anything outside that tag. If you don't like that, use <ui:decorate> instead.
3. Composite Component
Example is a text field with a label and a submit-button, reused to enter either a name or an e-mail (both have their own button). Whatever was entered by the user gets stored in the backing bean, and is rendered in the list at bottom. The screenshot shows a state when two names and one e-mail were entered:
Source files:
composer.xhtml
(master view)
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:cc="http://xmlns.jcp.org/jsf/composite" xmlns:tl="http://xmlns.jcp.org/jsf/composite/textline" > <h:head> <title>JSF Composite Example</title> </h:head> <h:body> <h2>JSF <composite> Example</h2> <tl:textlineSubmit label="Name" text="#{personTextlinesController.name}" buttonLabel="Register" buttonAction="#{personTextlinesController.submitName}" /> <tl:textlineSubmit label="E-Mail" text="#{personTextlinesController.email}" buttonLabel="Send" buttonAction="#{personTextlinesController.submitEmail}" /> <h:dataTable value="#{personTextlinesController.summary}" var="item"> <h:column>#{item}</h:column> </h:dataTable> </h:body> </html>
textlineSubmit.xhtml
(the component)
<!DOCTYPE html> <!-- A label with a input text field and a command-button. --> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:composite="http://xmlns.jcp.org/jsf/composite" xmlns:h="http://xmlns.jcp.org/jsf/html"> <composite:interface> <composite:attribute name="label" default="Input"/> <composite:attribute name="text"/> <composite:attribute name="buttonLabel" default="Submit"/> <composite:attribute name="buttonAction" method-signature="java.lang.String method()" required="true"/> </composite:interface> <composite:implementation> <h:form> <h:panelGrid columns="3"> #{cc.attrs.label}: <h:inputText value="#{cc.attrs.text}"/> <h:commandButton value="#{cc.attrs.buttonLabel}" action="#{cc.attrs.buttonAction}"/> </h:panelGrid> </h:form> </composite:implementation> </html>
PersonTextlinesController.java
(Java controller, used by the master view)
package fri.jsf; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.faces.view.ViewScoped; import javax.inject.Named; @Named @ViewScoped public class PersonTextlinesController implements Serializable { private String name; private String email; private List<String> summary = new ArrayList<>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public String submitName() { summary.add("name="+name); return ""; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String submitEmail() { summary.add("email="+email); return ""; } public List<String> getSummary() { return summary; } }
Explanations
The composer.xhtml
master page uses two instances of the component,
parameterized through attributes of the component tag, which are
label
, text
, buttonLabel
and buttonAction
.
The labels are plain texts, text
is a Javabean property,
buttonAction
is a Java bean method.
This makes composite component special.
So, here parameterization happens through attributes of the XML-element that represents the component.
We can set the buttons of the two component instances to different callbacks of the backing Java bean. The Java bean returning "" from these callbacks makes JSF stay on the same page. The list on bottom mirrors the history of inputs. Mind that this page suffers from the double-submit problem, refreshing the browser page submits a value.
The texlineSubmit.xhtml
component is built like a function.
It has a signature-like interface section, and body-like implementation section.
Mind that the
interface attributes
allow definition of default values.
Inside the implementation, you refer via cc.attrs.xxx
to interface-attribute xxx
, this prefix is hardcoded in JSF.
The component's element-tag <texlineSubmit>
is the file basename of the component (texlineSubmit.xhtml
),
and that file must reside in
src/main/webapp/resources/textline/
when the namespace should be
http://xmlns.jcp.org/jsf/composite/textline
, like it is referenced in
head of composer.xhtml
.
The PersonTextlinesController
bean is @ViewScoped
to not lose
the list of inputs.
Every bean-property also needs an action-callback for that property.
In real life we would use different controllers for each instance of a composite component.
Conclusion
It's a little bit hard to know what to use when. The composite for sure is the most reusable component, but it is also the most complex. Still there are more options, e.g. Java tag handlers. JSF is big.
Keine Kommentare:
Kommentar veröffentlichen