JSF Facelets: templates

Facelets were introduced in JSF 2.0 to resolve common inefficiencies in JSF when creating large-scale sites. Two most useful additions provided by facelets were the ability to reuse the code through templating and to create custom composite components conveniently.

In this article I would like to describe how we can use facelets to create templates and reuse them to create multiple web pages.

Creating template

Templates encourage reuse of existing layouts and greatly reduce the potential cost necessary to modify some common parts of the web site. With templates the layout needs to be changed only in one place and the changes will be visible on multiple pages.

A template in facelets is a normal XHTML document which contains additional facelets tags. These tags are defined in namespace http://xmlns.jcp.org/jsf/facelets and by convention use prefix ui.

The most important one ui:insert is a kind of place-holder which marks the parts of the template that can be redefined by the clients of this template. Each such ui:insert tag should contain unique name so that clients can easily refer to it and can optionally have default contents to use when clients don’t want to redefine it.

The template in our example (/templates/main.xhtml) looks like this:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
        <title><ui:insert name="title" /></title>
        <h:outputStylesheet library="default" name="css/styles.css" />
    </h:head>
    <h:body>
        <div class="header">
            <ui:insert name="header">
                <ui:include src="/sections/default-header.xhtml" />
            </ui:insert>
        </div>
        <hr/>

        <div class="data">
            <ui:insert name="data" />
        </div>

        <hr/>
        <div class="footer">
            <ui:insert name="footer">
                <ui:include src="/sections/default-footer.xhtml" />
            </ui:insert>
        </div>
    </h:body>
</html>

In this example the template defines 4 place-holders: title, header, data and footer. The header and the footer have default contents defined and this contents will be included from specified XHTML files using ui:include tags. The title and the data will be by default empty.

Header (/sections/default-header.xhtml) looks like this:

<?xml version='1.0' encoding='UTF-8' ?>
<ui:composition 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">
    <h1>
        <h:outputText value="#{msgs.nothingViewing}" />
    </h1>
</ui:composition>

and the footer (/sections/default-footer.xhtml) like this:

<?xml version='1.0' encoding='UTF-8' ?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:h="http://xmlns.jcp.org/jsf/html"
                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:graphicImage library="default" name="images/logo.png" />
    <h:outputLink value="http://en.wikipedia.org/wiki/List_of_Presidents_of_the_United_States">Check on Wikipedia</h:outputLink>
</ui:composition>

We use new tag ui:composition without any attribute (especially template attribute which will be mentioned later) to represent a sequence of elements that can be put somewhere in XHTML document. Additionally, this tag allows us to create well-formed XML document and to define XML namespaces. Note also that we are not using html, head or body tags because it might result in invalid HTML document with multiple nested html tags.

Using the template

Now once we have the template we can use it to create a simple application with two screens. First screen shows a list of few USA presidents and allows to switch to the other screen containing details about selected president.

To create a concrete web page (the client of the template) we put ui:composition element at the root of the document with attribute template pointing to the template which we want to use. Inside this element we can refer to ui:insert tags from template and replace their default contents by the contents defined inside ui:define tag.

The code for the first screen /index.xhtml is like this:

<?xml version='1.0' encoding='UTF-8' ?>
<ui:composition template="/templates/main.xhtml"
             xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
             xmlns:h="http://xmlns.jcp.org/jsf/html"
             xmlns:f="http://xmlns.jcp.org/jsf/core">
    
    <ui:define name="title">
        #{msgs.presidents}
    </ui:define>
    
    <ui:define name="data" >
        <h:form>
            <h:panelGrid columns="1">
                <h:selectOneListbox value="#{presidents.selectedIndex}">
                    <f:selectItems value="#{presidents.allItems}" />
                </h:selectOneListbox>
                <h:commandButton value="#{msgs.details}" action="details" />
            </h:panelGrid>
        </h:form>
    </ui:define>
</ui:composition>

In this case the header and footer are not redefined so they will have their default contents as defined in the template.

Replacing parts of template

The seconds screen of the application is quite similar to the first one but it additionally replaces the default contents of the header, which was specified in the template, with its own contents.

<?xml version='1.0' encoding='UTF-8' ?>
<ui:composition template="/templates/main.xhtml"
             xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
             xmlns:h="http://xmlns.jcp.org/jsf/html">
    
    <ui:define name="title">
        #{msgs.presidents}: #{presidents.name}
    </ui:define>
    
    <ui:define name="header">
        <ui:param name="presidentName" value="#{presidents.name}" />
        <ui:include src="/sections/selected-header.xhtml" />
    </ui:define>

    <ui:define name="data" >
        <h:form>
            <h:panelGrid columns="1">
                <h:outputText style="text-align: center; font-size: x-large" value="#{presidents.name}" />
                #{presidents.description}
                <h:commandButton value="#{msgs.back}" action="index" />
            </h:panelGrid>
        </h:form>
    </ui:define>
</ui:composition>

The default contents of the header from the template will be discarded and the new contents of the header will be included from /sections/selected-header.xhtml file:

<?xml version='1.0' encoding='UTF-8' ?>
<ui:composition 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">
    <h1>
        <h:outputFormat value="#{msgs.currentlyViewing}">
            <f:param value="#{presidentName}" />
        </h:outputFormat>
    </h1>
</ui:composition>

Passing parameters

If you take a close look, you will notice that the included header uses parameter presidentName. This parameter is extracted from managed bean and passed to the included header using ui:param tag before the actual ui:include tag. This is the way how you can make the templates parametrizable.

It is also possible to specify ui:param elements as children of ui:include element to limit their scope.

Conclusion

Facelets are a great tool for creating and reusing templates. If used correctly, they speed up development, reduce duplication and reduce the cost of future modifications.

The source code for example is available at GitHub.

Advertisement

About Robert Piasecki

Husband and father, Java software developer, Linux and open-source fan.
This entry was posted in Java, Java EE, JSF and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.