JSF

Content:

  1. Introduction
  2. What it is
  3. Philsophy
  4. Features
    1. Basic
    2. I18N
    3. Validation
    4. Master page
    5. Inverted facelets
  5. External components
    1. Apache Tomahawk
    2. Apache Trinidad
    3. JBoss RichFaces
  6. Custom components
    1. Custom component in Java
    2. Composite component (custom component in XHTML)

Introduction:

JSF (JavaServer Faces) is modern Java EE web frontend technology.

It is a rather complex technology that is not easy to master.

Current trend in web is towards doing UI in "HTML 5" (HTML + CSS + JavaScript) and having a backend expose RESTful web services, so the interest for a JSF is not in fashion.

Non the less it seems relevant for Java EE developers to know the basics of JSF.

Note that I am not a JSF expert myself, so consider the below an appetizer and bear with me if something is not done per current best practice for JSF usage.

What it is:

Old Java EE web applications used servlets and JSP pages - often using a MVC framework like Struts. For info on how to use JSP and servlets see here and here. For more info on how to use Struts see here.

JSF has supplemented that model since Java EE 5 (2006).

JSF is a component based web framework.

The key parts of a JSF application are:

JSF implementation
Implementation of JSF standard providing basic framework
JSF components
Components to be usef by View
View
Markup referring to JSF components and backing beans (V in MVC)
Backing bean
Java bean provding data and actions (M and C in MVC)

JSF versions:

Version Release
1.0 and 1.1 2004
1.2 2006
2.0 2009
2.1 2010
2.2 2013
2.3 2017
3.0 2020
4.0 2022

In version 3.0 JSF is an abbreviation for Jakarta Server Faces.

From version 4.0 JSF is just JF (Jakarta Faces).

The examples shown here will be based on JSF 2.2.

JSF supports multiple view technologies:

JSP
Easing the migration from older model. Default in JSF 1.x but deperecated in JSF 2.x.
Facelets
New XHTML based technology. Default in JSF 2.x.

Note that a lot of newer functionality is only available in facelets.

The examples shown here will show both JSP and Facelet unless only Facelet works in which case JSP is obviously not shown.

JSF is a specifictaion with multiple implementations.

Two widely used implementations are:

The examples showns here are tested with Apache MyFaces.

Philosophy:

It is important to understand that JSF components are defined by their behavior not by their implementation.

Specifically JSF does not manadate whether to generate HTML server side or to generate JavaScript server side and let the that JavaScript generate HTML client side.

JSF is also in best Java/JCP tradition extremely modular. In its core JSF is just a model for the processing of requests and some API's.

There are multiple implementations. Implementations are not required to generate the same HTML+CSS+JS for a given standard component.

The use of third party component libraries are common. See later sections.

There can be multiple view technologies. The standard are JSP and facelets, but experimental view technologies using XUL and Java have been created.

Managed beans and JSF components can be written in any languages generating Java byte code obviously Java but also Scala, Kotlin and Groovy.

So multiple JSF implementations, multiple component libraries, multiple view technologies and multiple programming languages for backing bean code. Pick what you like.

Features:

All the talk about view and components can seem a bit abstract, so let us see some examples to show how it works.

First note that JSF web applications have two main config files. Standard content are like below.

WEB-INF/web.xml

<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">
    <!-- setup JSF -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--  use .jsf extension (foobar.jsf will use foobar.jsp or foobar.xhtml as view) -->
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
    <!-- instruct JSF to store state server side not client side -->
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>server</param-value>
    </context-param>
    <!-- enable dependency injection (necessary Tomcat, not needed JBoss) -->
    <resource-env-ref>
        <resource-env-ref-name>BeanManager</resource-env-ref-name>
        <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
    </resource-env-ref>
</web-app>

WEB-INF/faces-config.xml

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
</faces-config>

For some of the examples following additional content will be added.

Basic:

Here is an example of some basic web functionality.

The example shows:

Components used include:

h:head
HTML <head>
h:body
HTML <body>
f:view
area handled by JSF
f:facet
bundle area
h:panelGrid and h:panelGroup
manage layout of screen
h:outputText
simple text
h:form
HTML <form>
h:inputText
HTML <input type="text" ...>
h:selectOneMenu and h:selectItems
HTML <select> and <option>
h:commandButton
HTML <input type="submit" ...>
h:dataTable and h:column
HTML <table>, <tr>, <th> and <td>
f:ajax (Facelet only)
AJAX update of part of screen

JSP page:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<head>
<title>Standard demo</title>
</head>
<body>
<f:view>
<h1>Standard demo</h1>
<h:panelGrid columns="2">
<h:panelGroup style="background-color: beige" layout="block">
<h2>Links:</h2>
<a href="standard1.jsf">JSP</a>
<br>
<a href="standard2.jsf">Facelet</a>
</h:panelGroup>
<h:panelGroup layout="block">
<h2>Form:</h2>
<h:form>
F1: <h:inputText value="#{testStandard.f1}"/>
<br>
F2: <h:inputText value="#{testStandard.f2}"/>
<br>
F3: <h:selectOneMenu value="#{testStandard.f3}"><f:selectItems value="#{testStandard.options}" var="opt" itemValue="#{opt}"/></h:selectOneMenu>
<br>
<h:commandButton value="Save" action="#{testStandard.save}"/>
</h:form>
<h2>Table:</h2>
<h:dataTable value="#{testStandard.data}" var="o" border="1">
<h:column><f:facet name="header"><h:outputText value="F1"/></f:facet><h:outputText value="#{o.f1}"/></h:column>
<h:column><f:facet name="header"><h:outputText value="F2"/></f:facet><h:outputText value="#{o.f2}"/></h:column>
</h:dataTable>
<h2>AJAX:</h2>
<span style="color: gray">(AJAX not supported in JSP)</span>
</h:panelGroup>
</h:panelGrid>
The end.
</f:view>
</body>
</html>

For intro to JSP see section 2 to 7 here.

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Standard demo</title>
</h:head>
<h:body>
<f:view>
<h:panelGrid columns="2">
<f:facet name="header">
<h1>Standard demo</h1>
</f:facet>
<h:panelGroup style="background-color: beige" layout="block">
<h2>Links:</h2>
<h:link outcome="standard1.jsf" value="JSP"/>
<br/>
<h:link outcome="standard2.jsf" value="Facelet"/>
</h:panelGroup>
<h:panelGroup layout="block">
<h2>Form:</h2>
<h:form>
F1: <h:inputText value="#{testStandard.f1}"/>
<br/>
F2: <h:inputText value="#{testStandard.f2}"/>
<br/>
F3: <h:selectOneMenu value="#{testStandard.f3}"><f:selectItems value="#{testStandard.options}" var="opt" itemValue="#{opt}"/></h:selectOneMenu>
<br/>
<h:commandButton value="Save" action="#{testStandard.save}"/>
</h:form>
<h2>Table:</h2>
<h:dataTable value="#{testStandard.data}" var="o" border="1">
<h:column><f:facet name="header">F1</f:facet>#{o.f1}</h:column>
<h:column><f:facet name="header">F2</f:facet>#{o.f2}</h:column>
</h:dataTable>
<h2>AJAX:</h2>
<h:form>
Input: <h:inputText id="in_s" value="#{testStandard.s}"/>
<br/>
<h:commandButton value="Save">
<f:ajax execute="in_s" render="out_s"/>
</h:commandButton>
<br/>
You entered: <h:outputText id="out_s" value="#{testStandard.s}"/>.
</h:form>
</h:panelGroup>
<f:facet name="footer">
The end.
</f:facet>
</h:panelGrid>
</f:view>
</h:body>
</html>

Backing bean:

package test.standard;

import java.util.ArrayList;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class TestStandard {
    // form fields
    private Integer f1;
    private String f2;
    private String f3;
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
    // form action
    public void save() {
    }
    // form options list
    public List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("A");
        res.add("B");
        res.add("C");
        return res;
    }
    // data table
    public static class T1 {
        private int f1;
        private String f2;
        public T1() {
            this(0, "");
        }
        public T1(int f1, String f2) {
            this.f1 = f1;
            this.f2 = f2;
        }
        public int getF1() {
            return f1;
        }
        public void setF1(int f1) {
            this.f1 = f1;
        }
        public String getF2() {
            return f2;
        }
        public void setF2(String f2) {
            this.f2 = f2;
        }
    }
    public List<T1> getData() {
        List<T1> res = new ArrayList<T1>();
        res.add(new T1(1,"A"));
        res.add(new T1(2,"BB"));
        res.add(new T1(3,"CCC"));
        res.add(new T1(4,"DDDD"));
        res.add(new T1(5,"EEEEE"));
        return res;
    }
    // AJAX field
    private String s;
    public String getS() {
        return s;
    }
    public void setS(String s) {
        this.s = s;
    }
    /*
    // ensure session exist
    @PostConstruct
    void initialiseSession() {
        FacesContext.getCurrentInstance().getExternalContext().getSession(true);
    }
    */
}

View refer to properties as #{camelcaseclassname.propertyname} and action methods as #{camelcaseclassname.methodname}.

@ManagedBean make the class usable as a backing bean.

@RequestScope, @SessionScope and @ApplicationScope determine scope (life) of the instances of the backing bean.

Screen shot:

JSF standard demo

I18N:

Internationalization is an important feature of any web framework and of course JSF supports that.

JSF I18N support is done via standard Java message bundle mechanism.

JSP page:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<head>
<title>Internationalization</title>
</head>
<body>
<f:view>
<h1><h:outputText value="#{msg['title']}"/></h1>
<h2><h:outputText value="#{msg['subtitle']}"/>:</h2>
<h:form>
<h:commandButton value="#{msg['english']}" action="#{testInt.english}"/>
<h:commandButton value="#{msg['danish']}" action="#{testInt.danish}"/>
<br>
<h:outputText value="#{msg['text1']}"/>
<br>
<h:outputText value="#{msg['text2']}"/>
</h:form>
</f:view>
</body>
</html>

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Internationalization</title>
</h:head>
<h:body>
<f:view>
<h1><h:outputText value="#{msg['title']}"/></h1>
<h2><h:outputText value="#{msg['subtitle']}"/>:</h2>
<h:form>
<h:commandButton value="#{msg['english']}" action="#{testInt.english}"/>
<h:commandButton value="#{msg['danish']}" action="#{testInt.danish}"/>
<br/>
<h:outputText value="#{msg['text1']}"/>
<br/>
<h:outputText value="#{msg['text2']}"/>
</h:form>
</f:view>
</h:body>
</html>

Backing bean:

package test.standard;

import java.util.Locale;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;

@ManagedBean
@RequestScoped
public class TestInt {
    // internationalization
    public void english() {
        FacesContext.getCurrentInstance().getViewRoot().setLocale(Locale.US);
    }
    public void danish() {
        FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("da", "DK"));
    }
}

Modified WEB-INF/faces.config.xml:

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
    ...
    <application>
        <locale-config>
            <default-locale>en_US</default-locale>
            <supported-locale>en_US</supported-locale>
            <supported-locale>da_DK</supported-locale>
        </locale-config>
        <resource-bundle>
            <base-name>msg</base-name>
            <var>msg</var>
        </resource-bundle>
    </application>
    ...
</faces-config>

msg_en_US.properties:

title = Internationalization
subtitle : Text
english = English
danish = Danish
text1 = This is a line
text2 = This is another line

msg_da_DK.properties:

title = Internationalisering
subtitle : Tekst
english = Engelsk
danish = Dansk
text1 = Dette er en linie
text2 = Dette er en anden linie

Screen shots:

JSF internationalization english JSF internationalization danish

Validation:

Another important feature of any web framework is easy user friendly input validation.

JSF does it using validation components including:

f:validateLongRange
validate integer input against valid range
f:validateLength
validate string input against valid range
validateRegex
validate string input against regex pattern
h:message
display validation errors

JSP page:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<head>
<title>Check demo</title>
</head>
<body>
<f:view>
<h1>Check demo</h1>
<h2>Form:</h2>
<h:form id="form">
F1: <h:inputText id="f1" value="#{testStandard.f1}" required="true" requiredMessage="F1 is required" validatorMessage="F1 is not in 1 to 100 range"><f:validateLongRange minimum="1" maximum="100"/></h:inputText><h:message for="f1" style="color:red"/>
<br>
F2: <h:inputText id="f2" value="#{testStandard.f2}" required="true" requiredMessage="F2 is required" validatorMessage="F2 is not a string with length in 1 to 30 range"><f:validateLength minimum="1" maximum="30"/></h:inputText><h:message for="f2" style="color:red"/>
<br>
F3: <h:inputText id="f3" value="#{testStandard.f3}" required="true" requiredMessage="F3 is required" validatorMessage="F3 is not in format nn-nn-nn"><f:validateRegex pattern="\d{2}-\d{2}-\d{2}"/></h:inputText><h:message for="f3" style="color:red"/>
<br>
<h:commandButton value="Save" action="#{testStandard.save}"/>
</h:form>
</f:view>
</body>
</html>

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Check demo</title>
</h:head>
<h:body>
<f:view>
<h1>Check demo</h1>
<h2>Form:</h2>
<h:form id="form">
F1: <h:inputText id="f1" value="#{testStandard.f1}" required="true" requiredMessage="F1 is required" validatorMessage="F1 is not in 1 to 100 range"><f:validateLongRange minimum="1" maximum="100"/></h:inputText><h:message for="f1" style="color:red"/>
<br/>
F2: <h:inputText id="f2" value="#{testStandard.f2}" required="true" requiredMessage="F2 is required" validatorMessage="F2 is not a string with length in 1 to 30 range"><f:validateLength minimum="1" maximum="30"/></h:inputText><h:message for="f2" style="color:red"/>
<br/>
F3: <h:inputText id="f3" value="#{testStandard.f3}" required="true" requiredMessage="F3 is required" validatorMessage="F3 is not in format nn-nn-nn"><f:validateRegex pattern="\d{2}-\d{2}-\d{2}"/></h:inputText><h:message for="f3" style="color:red"/>
<br/>
<h:commandButton value="Save" action="#{testStandard.save}"/>
</h:form>
</f:view>
</h:body>
</html>

Screen shots:

JSF validation OK JSF validation error

Master page:

A common problem for large web applications is to maintain a consistent design across many pages.

To facilitate that JSF like some other popular web technologies has a master page concept.

Note that this is a facelet only feature - it is not available for JSP pages.

A master page looks like:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
...
<!-- placeholder for named content that will be provided by actual page -->
<ui:insert name="somename">some default content</ui:insert>
...
<!-- include another file with facelet code -->
<ui:include src="somefacelet.xhtml"/>
...
</html>

And an actual page look like:

<!-- everything above here is ignored -->
<ui:composition template="nameofmasterpagefacelet.xhtml">
<ui:define name="somename">
some content
</ui:define>
</ui:composition>
<!-- everything below here is ignored -->

Master page facelet layout.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title><ui:insert name="title">Default title</ui:insert></title>
</h:head>
<h:body>
<h1><ui:insert name="title">Default title</ui:insert></h1>
<div>
<ui:insert name="content"/>
</div>
<div>
<ui:include src="footer.xhtml"/>
</div>
</h:body>
</html>

Include facelet footer.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
</head>
<body>
<ui:composition>
<p style="background-color: lightgray">
The end.
</p>
</ui:composition>
</body>
</html>

Actual page facelet master1.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
</head>
<body>
<ui:composition template="layout.xhtml">
<ui:define name="title">Page 1</ui:define>
<ui:define name="content">
<h2>Content:</h2>
<p>
This is page #1.
</p>
</ui:define>
</ui:composition>
</body>
</html>

Screen shot:

JSF master page

Inverted facelets:

Facelets has an interesting little feature that I call inverted facelet.

Basically a tag:

<h:somejsfcomponent .../>

can also be written as:

<somehtmltag jsfc="h:somejsfcomponent" .../>

And there is a special JSF component ui:remove:

<somehtmltag jsfc="ui:remove" .../>

which causes JSF to completely discard that HTML tag.

The point of this little trick is that it is possible to write facelets as almost valid XHTML. This allow facelets to be viewable in tools that can display XHTML.

The feature is intended to allow visual web designers to edit facelets.

I would never let a web designer that know XHTML but not JSF edit a facelet. Cute concept but the risk of bad edits being made is just too great.

The facelet shown up under basic functionality can be rewritten as:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<head jsfc="h:head">
<title>Standard demo</title>
</head>
<body jsfc="h:body">
<div jsfc="f:view">
<div jsfc="h:panelGrid" columns="2">
<div jsfc="f:facet" name="header">
<h1>Standard demo</h1>
</div>
<div jsfc="h:panelGroup" style="background-color: beige" layout="block">
<h2>Links:</h2>
<a jsfc="h:link" outcome="standard1.jsf" value="JSP"><span jsfc="ui:remove">JSP</span></a>
<br/>
<a jsfc="h:link" outcome="standard2.jsf" value="Facelet"><span jsfc="ui:remove">Facelet</span></a>
</div>
<div jsfc="h:panelGroup" layout="block">
<h2>Form:</h2>
<form hsfc="h:form">
F1: <input type="text" id="f1" jsfc="h:inputText" value="#{testStandard.f1}"/>
<br/>
F2: <input type="text" id="f2" jsfc="h:inputText" value="#{testStandard.f2}"/>
<br/>
F3: <select id="f3" jsfc="h:selectOneMenu" value="#{testStandard.f3}"><option jsfc="f:selectItems" value="#{testStandard.options}" var="opt" itemValue="#{opt}"><span jsfc="ui:remove">Dummy</span></option></select>
<br/>
<input type="submit" jsfc="h:commandButton" value="Save" action="#{testStandard.save}"/>
</form>
<h2>Table:</h2>
<table jsfc="h:dataTable" value="#{testStandard.data}" var="o" border="1">
<tr>
<th jsfc="h:column"><span jsfc="f:facet" name="header">F1</span>#{o.f1}</th>
<th jsfc="h:column"><span jsfc="f:facet" name="header">F2</span>#{o.f2}</th>
</tr>
<tr jsfc="ui:remove">
<td>data</td>
<td>data</td>
</tr>
<tr jsfc="ui:remove">
<td>data</td>
<td>data</td>
</tr>
<tr jsfc="ui:remove">
<td>data</td>
<td>data</td>
</tr>
</table>
<h2>AJAX:</h2>
<form jsfc="h:form">
Input: <input type="text" jsfc="h:inputText" id="in_s" value="#{testStandard.s}"/>
<br/>
<input type="submit" jsfc="h:commandButton" value="Save">
<f:ajax execute="in_s" render="out_s"/>
</input>
<br/>
You entered: <h:outputText id="out_s" value="#{testStandard.s}"/>.
</form>
</div>
<div jsfc="f:facet" name="footer">
The end.
</div>
</div>
</div>
</body>
</html>

When displayed actually running on the server it looks exactly like the original. The big difference is if the facelets are opened directly in the browser.

Original:

JSF original version

Inverted version:

JSF inverted version

External components:

One is not restricted to the JSF components defined in the JSF standard. There are plenty of third party JSF components available.

Well known open source and commercial JSF component libraries include:

This obviously raises a few questions:

  1. Should you stick to standard components or use extra component library?
  2. If you decide to use an extra component library then which one should it be?

My suggestion is:

  1. For simple needs like basic admin web GUI then stick to standard components, but for anything that need to appeal to a larger use base use an extra component library to improve the user experience.
  2. If you have an Oracle ADF background go for Apache Trinidad. If you need tigth integration with custom client side JavaScript go for JBoss RichFaces (and ignore the EOL status). For everybody else go for Apache Tomahawk.

The reason I am not recommending PrimeFaces is that I have never tried it myself. But it is worth noting that it has a very good reputation, so you may want to consider it.

Apache Tomahawk:

Tomahawk is an Apache project provding additional JSF components. It is related but independent of Apache MyFaces project.

To use Tomahawk the following changes must be made:

Modified WEB-INF/web.xml:

<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">
    ...
    <filter>
        <filter-name>Tomahawk Filter</filter-name>
        <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Tomahawk Filter</filter-name>
        <url-pattern>*.jsf</url-pattern>
    </filter-mapping>
    ...
</web-app>

For a complete list of Tomahawk components see Tomahawk web site.

Components used in example include:

t:panelLayout
manage layout of screen
t:popup (Facelet only)
mouse over popup text
t:selectOneCountry, t:selectOneLanguage and t:inputDate (best in Facelet)
input of country, language and date
t:panelTabbedPane and t:panelTab (best in Facelet)
tabs
t:tree2 (Facelet only)
expandable/collapsable tree

(Yes - using Tomahawk practically mandates using facelets)

JSP page:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="t" uri="http://myfaces.apache.org/tomahawk" %>
<html>
<head>
<title>Tomahawk demo</title>
</head>
<body>
<f:view>
<t:panelLayout headerStyle="background-color: lightgray"
               navigationStyle="background-color: beige"
               bodyStyle="background-color: white"
               footerStyle="background-color: lightgray">
<f:facet name="header">
<t:div>
<h1>Tomahawk demo</h1>
</t:div>
</f:facet>
<f:facet name="navigation">
<t:div>
<h2>Links:</h2>
<a href="tomahawk1.jsf">JSP</a>
<br>
<a href="tomahawk2.jsf">Facelet</a>
</t:div>
</f:facet>
<f:facet name="body">
<t:div>
<h2>Text effects:</h2>
This is some text. <span style="color: gray">(Popup not supported in JSP)</span>
<h2>Form input:</h2>
<h:form>
Country: <t:selectOneCountry value="#{testTomahawk.country}"/>
<br>
Language: <t:selectOneLanguage value="#{testTomahawk.language}"/>
<br>
Date: <t:inputDate type="date" popupCalendar="false" value="#{testTomahawk.date}"/> <span style="color: gray">(Popup calendar not supported in JSP)</span>
<br>
<h:commandButton value="Save"/>
</h:form>
<h2>Tabs:</h2>
<t:panelTabbedPane selectedIndex="0">
<t:panelTab label="Tab A">
This is A. And here is some more text to fill out the tab.
</t:panelTab>
<t:panelTab label="Tab B">
This is B. And here is some more text to fill out the tab.
</t:panelTab>
<t:panelTab label="Tab C">
This is C. And here is some more text to fill out the tab.
</t:panelTab>
</t:panelTabbedPane>
<span style="color: gray">(Tabs look bad in JSP)</span>
<h2>Tree:</h2>
<span style="color: gray">(Tree not supported in JSP)</span>
</t:div>
</f:facet>
<f:facet name="footer">
<t:div>
The end.
</t:div>
</f:facet>
</t:panelLayout>
</f:view>
</body>
</html>

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:t="http://myfaces.apache.org/tomahawk"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Tomahawk demo</title>
</h:head>
<h:body>
<f:view>
<t:panelLayout headerStyle="background-color: lightgray"
               navigationStyle="background-color: beige"
               bodyStyle="background-color: white"
               footerStyle="background-color: lightgray">
<f:facet name="header">
<h1>Tomahawk demo</h1>
</f:facet>
<f:facet name="navigation">
<h2>Links:</h2>
<h:link outcome="tomahawk1.jsf" value="JSP"/>
<br/>
<h:link outcome="tomahawk2.jsf" value="Facelet"/>
</f:facet>
<f:facet name="body">
<h2>Text effects:</h2>
<t:popup displayAtDistanceX="20" displayAtDistanceY="-20">
<h:outputText value="This is some text."/>
<f:facet name="popup"><h:outputText value="Hi there" style="background-color: yellow"/></f:facet>
</t:popup>
<h2>Form input:</h2>
<h:form>
Country: <t:selectOneCountry value="#{testTomahawk.country}"/>
<br/>
Language: <t:selectOneLanguage value="#{testTomahawk.language}"/>
<br/>
Date: <t:inputDate type="date" popupCalendar="true" value="#{testTomahawk.date}"/>
<br/>
<h:commandButton value="Save"/>
</h:form>
<h2>Tabs:</h2>
<t:panelTabbedPane selectedIndex="0">
<t:panelTab label="Tab A">
This is A. And here is some more text to fill out the tab.
</t:panelTab>
<t:panelTab label="Tab B">
This is B. And here is some more text to fill out the tab.
</t:panelTab>
<t:panelTab label="Tab C">
This is C. And here is some more text to fill out the tab.
</t:panelTab>
</t:panelTabbedPane>
<h2>Tree:</h2>
<t:tree2 value="#{testTomahawk.tree}" showRootNode="true" var="node">
<f:facet name="Category"><h:outputText value="#{node.description}"/></f:facet>
<f:facet name="Link"><a href="#{node.identifier}"><h:outputText value="#{node.description}"/></a></f:facet>
</t:tree2>
</f:facet>
<f:facet name="footer">
The end.
</f:facet>
</t:panelLayout>
</f:view>
</h:body>
</html>

Backing bean:

package test.tomahawk;

import java.util.Date;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import org.apache.myfaces.custom.tree2.TreeModel;
import org.apache.myfaces.custom.tree2.TreeModelBase;
import org.apache.myfaces.custom.tree2.TreeNode;
import org.apache.myfaces.custom.tree2.TreeNodeBase;


@ManagedBean
@RequestScoped
public class TestTomahawk {
    // special form field types
    private String country = "DK";
    private String language = "da";
    private Date date = new Date();
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getLanguage() {
        return language;
    }
    public void setLanguage(String language) {
        this.language = language;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    // tree
    @SuppressWarnings("unchecked")
    public TreeModel getTree() {
        TreeNode search = new TreeNodeBase("Category", "Search", false);
        search.getChildren().add(new TreeNodeBase("Link", "Google", "http://www.google.com/", true));
        search.getChildren().add(new TreeNodeBase("Link", "Bing", "http://www.bing.com/", true));
        TreeNode social = new TreeNodeBase("Category", "Social", false);
        social.getChildren().add(new TreeNodeBase("Link", "Facebook", "http://www.facebook.com/", true));
        social.getChildren().add(new TreeNodeBase("Link", "Twitter", "http://www.twitter.com/", true));
        social.getChildren().add(new TreeNodeBase("Link", "LinkedIn", "http://www.linkedin.com/", true));
        TreeNode inet = new TreeNodeBase("Category", "Internet", false);
        inet.getChildren().add(search);
        inet.getChildren().add(social);
        return new TreeModelBase(inet);
    }
}

Screen shots:

JSF Tomahawk demo JSF Tomahawk demo JSF Tomahawk demo JSF Tomahawk demo

Apache Trinidad:

Tomahawk is an Apache project provding additional JSF components. It is related but independent of Apache MyFaces project.

The code is based on a contribution from Oracle (ADF).

To use Tomahawk the following changes must be made:

Modified WEB-INF/web.xml:

<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">
    ...
    <filter>
        <filter-name>Trinidad Filter</filter-name>
        <filter-class>org.apache.myfaces.trinidad.webapp.TrinidadFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Trinidad Filter</filter-name>
        <url-pattern>*.jsf</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>Resources Servlet</servlet-name>
        <servlet-class>org.apache.myfaces.trinidad.webapp.ResourceServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Resources Servlet</servlet-name>
        <url-pattern>/adf/*</url-pattern>
    </servlet-mapping>
    ...
</web-app>

Modified WEB-INF/faces-config.xml:

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
    ...
    <application>
        <default-render-kit-id>org.apache.myfaces.trinidad.core</default-render-kit-id>
    </application>
    ...
</faces-config>

For a complete list of Trinidad components see Trinidad web site.

Components used in example include:

tr:panelBorderLayout (Facelet only)
manage layout of screen
tr:inputDate
input of date
tr:inputColor (Facelet only)
input color
tr:tree (Facelet only)
expandable/collapsable tree
tr:chart
various chart types

(Yes - using Trinidad practically mandates using facelets)

JSP page:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="tr" uri="http://myfaces.apache.org/trinidad" %>
<%@ taglib prefix="trh" uri="http://myfaces.apache.org/trinidad/html" %>
<html>
<head>
<title>Trinidad demo</title>
</head>
<body>
<f:view>
<h1>Trinidad demo</h1>
<h2>Links:</h2>
<a href="trinidad1.jsf">JSP</a>
<br>
<a href="trinidad2.jsf">Facelet</a>
<br>
<span style="color: gray">(Panel border layout does not work in JSP)</span>
<h2>Form input:</h2>
<h:form>
Date: <tr:inputDate chooseId="d" value="#{testTrinidad.date}"/><tr:chooseDate id="d"/>
<br>
Color: <span style="color: gray">(Input color not supported in JSP)</span>
<br>
<h:commandButton value="Save"/>
</h:form>
<h2>Tree:</h2>
<span style="color: gray">(Tree not supported in JSP)</span>
<h2>Chart:</h2>
<tr:chart value="#{testTrinidad.chart}" type="verticalBar" perspective="false" inlineStyle="width:200px; height:200px;"/>
<tr:chart value="#{testTrinidad.chart}" type="pie" perspective="false" inlineStyle="width:200px; height:200px;"/>
<h:outputText value="The end."/>
<br>
<span style="color: gray">(Panel border layout does not work in JSP)</span>
</f:view>
</body>
</html>

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:tr="http://myfaces.apache.org/trinidad"
      xmlns:trh="http://myfaces.apache.org/trinidad/html"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Trinidad demo</title>
</h:head>
<h:body>
<f:view>
<tr:panelBorderLayout>
<f:facet name="top">
<div  style="background-color: lightgray">
<h1>Trinidad demo</h1>
</div>
</f:facet>
<f:facet name="left">
<div style="background-color: beige">
<h2>Links:</h2>
<h:link outcome="trinidad1.jsf" value="JSP"/>
<br/>
<h:link outcome="trinidad2.jsf" value="Facelet"/>
</div>
</f:facet>
<f:facet name="right">
<div>
<h2>Form input:</h2>
<h:form>
Date: <tr:inputDate chooseId="d" value="#{testTrinidad.date}"/><tr:chooseDate id="d"/>
<br/>
Color: <tr:inputColor chooseId="c" value="#{testTrinidad.color}"><tr:convertColor patterns="#RRGGBB"/></tr:inputColor><tr:chooseColor id="c"/>
<br/>
<h:commandButton value="Save"/>
</h:form>
<h2>Tree:</h2>
<h:form>
<tr:tree value="#{testTrinidad.tree}" var="node">
<f:facet name="nodeStamp">
<h:outputText value="#{node.name}" rendered="#{not node.leaf}"/>
<tr:goLink destination="#{node.link}" text="#{node.name}" rendered="#{node.leaf}"/>
</f:facet>
</tr:tree>
</h:form>
<h2>Chart:</h2>
<tr:chart value="#{testTrinidad.chart}" type="verticalBar" perspective="false" inlineStyle="width:200px; height:200px;"/>
<tr:chart value="#{testTrinidad.chart}" type="pie" perspective="false" inlineStyle="width:200px; height:200px;"/>
</div>
</f:facet>
<f:facet name="bottom">
<div style="background-color: lightgray">
<h:outputText value="The end."/>
</div>
</f:facet>
</tr:panelBorderLayout>
</f:view>
</h:body>
</html>

Backing bean:

package test.trinidad;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import org.apache.myfaces.trinidad.model.ChartModel;
import org.apache.myfaces.trinidad.model.ChildPropertyTreeModel;
import org.apache.myfaces.trinidad.model.TreeModel;

@ManagedBean
@RequestScoped
public class TestTrinidad {
    // special form field types
    private Date date = new Date();
    private String color = "#0000FF";
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    // tree
    public static class Link {
        private String name;
        private String link;
        private List<Link> children = new ArrayList<Link>();
        public Link() {
            this("", "");
        }
        public Link(String name, String link) {
            this.name = name;
            this.link = link;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getLink() {
            return link;
        }
        public void setLink(String link) {
            this.link = link;
        }
        public boolean isLeaf() {
            return link.length() > 0;
        }
        public List<Link> getChildren() {
            return children;
        }
    }
    public TreeModel getTree() {
        Link search = new Link("Search", "");
        search.getChildren().add(new Link("Google", "http://www.google.com/"));
        search.getChildren().add(new Link("Bing", "http://www.bing.com/"));
        Link social = new Link("Social", "");
        social.getChildren().add(new Link("Facebook", "http://www.facebook.com/"));
        social.getChildren().add(new Link("Twitter", "http://www.twitter.com/"));
        social.getChildren().add(new Link("LinkedIn", "http://www.linkedin.com/"));
        Link inet = new Link("Internet", "");
        inet.getChildren().add(search);
        inet.getChildren().add(social);
        return new ChildPropertyTreeModel(inet, "children");
    }
    // chart
    public static class MyChartModel extends ChartModel {
        private String label;
        private String[] serLabel;
        private List<Double> data;
        public MyChartModel(String label, String[] serLabel, List<Double> data) {
            this.label = label;
            this.serLabel = serLabel;
            this.data = data;
        }
        @Override
        public List<String> getGroupLabels() {
            return Arrays.asList(label);
        }
        @Override
        public List<String> getSeriesLabels() {
            return Arrays.asList(serLabel);
        }
        @Override
        public List<List<Double>> getYValues() {
            return Arrays.asList(data);
        }
    }
    public ChartModel getChart() {
        return new MyChartModel("Test", new String[] { "A", "B", "C", "D", "E" }, Arrays.asList(1.0, 4.0, 9.0, 16.0, 25.0));
    }
}

Screen shots:

JSF Trinidad demo JSF Trinidad demo

JBoss RichFaces:

RichFaces is a JBoss project provding additional JSF components.

RichFaces really consist of two parts: RichFaces and A4J (Ajax4jsf).

RichFaces was officially declared EOL in 2016. But it is still a pretty good library.

RichFaces and A4J are very JavaScript integratable. They have lots of hooks for integrating with client side JavaScript.

RichFaces and A4J are facelets only - no JSP support.

To use RichFaces/A4J the following changes must be made:

For a complete list of RichFaces/A4J components see RichFaces web site.

Components used in example include:

rich:tooltip
mouse over popup text
rich:progressBar
progress bar based on data from server
rich:inputNumberSlider and rich:inputNumberSpinner
input of integer via graphical changee
rich:autocomplete
input of string with autocomplete
rich:editor
input of text via WYSIWYG editor
rich:calendar
input of date
rich:tabPanel and rich:tab
tabs
rich:tree and rich:treeNode
expandable/collapsable tree
rich:toolbar, rich:dropDownMenu and rich:menuItem
menu bar
rich:chart and rich:chartSeries
various chart types
a4j:commandButton and a4j:outputPanel
AJAX update of part of screen

RichFaces facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:a4j="http://richfaces.org/a4j"
      xmlns:rich="http://richfaces.org/rich"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>RichFaces demo</title>
</h:head>
<h:body>
<f:view>
<h1>RichFaces demo</h1>
<h2>Links:</h2>
<a href="richfaces2.jsf">Facelet</a>
<h2>Text effects:</h2>
<rich:panel header="Demo">
This is some text.<rich:tooltip><p style="background-color: yellow">Hi there</p></rich:tooltip>
</rich:panel>
<rich:progressBar value="#{testRichFaces.progress}" minValue="0" maxValue="100" label="#{testRichFaces.progress}% complete"/>
<h2>Form input:</h2>
<h:form>
F1: <rich:inputNumberSlider minValue="0" maxValue="100" value="#{testRichFaces.f1}"/>
<br/>
F1: <rich:inputNumberSpinner minValue="0" maxValue="100" value="#{testRichFaces.f1}"/>
<br/>
F2: <rich:autocomplete value="#{testRichFaces.f2}" autocompleteList="#{testRichFaces.list}" mode="client"/>
<br/>
Text: <rich:editor value="#{testRichFaces.text}" width="600" height="100"/>
<br/>
Date: <rich:calendar value="#{testRichFaces.date}"/>
<br/>
<h:commandButton value="Save"/>
</h:form>
<h2>Tabs:</h2>
<h:form>
<rich:tabPanel>
<rich:tab header="Tab A">
This is A. And here is some more text to fill out the tab.
</rich:tab>
<rich:tab header="Tab B">
This is B. And here is some more text to fill out the tab.
</rich:tab>
<rich:tab header="Tab C">
This is C. And here is some more text to fill out the tab.
</rich:tab>
</rich:tabPanel>
</h:form>
<h2>Tree:</h2>
<rich:tree value="#{testRichFaces.tree}" var="node" nodeType="#{node.type}" toggleType="client">
<rich:treeNode type="Category">
<h:outputText value="${node.name}"/>
</rich:treeNode>
<rich:treeNode type="Link">
<a href="#{node.link}"><h:outputText value="#{node.name}"/></a>
</rich:treeNode>
</rich:tree>
<h2>Menu:</h2>
<rich:toolbar>
<rich:dropDownMenu label="Search">
<rich:menuItem label="Google" mode="client" onclick="window.location.href='http://www.google.com/'"/>
<rich:menuItem label="Bing" mode="client" onclick="window.location.href='http://www.bing.com/'"/>
</rich:dropDownMenu>
<rich:dropDownMenu label="Social">
<rich:menuItem label="Facebook" mode="client" onclick="window.location.href='http://www.facebook.com/'"/>
<rich:menuItem label="Twitter" mode="client" onclick="window.location.href='http://www.twitter.com/'"/>
<rich:menuItem label="LinkedIn" mode="client" onclick="window.location.href='http://www.linkedin.com/'"/>
</rich:dropDownMenu>
</rich:toolbar>
<h2>Chart:</h2>
<rich:chart title="Bar demo" style="width: 300px">
<rich:chartSeries data="#{testRichFaces.barChart}"/>
</rich:chart>
<rich:chart title="Line demo" style="width: 300px">
<rich:chartSeries data="#{testRichFaces.lineChart}"/>
</rich:chart>
<rich:chart title="Pie demo" style="width: 300px">
<rich:chartSeries data="#{testRichFaces.pieChart}"/>
</rich:chart>
</f:view>
</h:body>
</html>

A4J facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:a4j="http://richfaces.org/a4j"
      xmlns:rich="http://richfaces.org/rich"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>A4J demo</title>
</h:head>
<h:body>
<f:view>
<h1>A4J demo</h1>
<h2>Links:</h2>
<a href="a4j2.jsf">Facelet</a>
<h2>AJAX:</h2>
<h:form>
<a4j:commandButton value="Update all" execute="@form" render="@form" immediate="true"/>
<a4j:commandButton value="Update both" execute="op" render="op" immediate="true"/>
<a4j:commandButton value="Update last only" execute="ot" render="ot" immediate="true"/>
<br/>
<a4j:outputPanel id="op">
Time: <h:outputText value="#{testRichFaces.time}"/>.
<br/>
Time: <h:outputText id="ot" value="#{testRichFaces.time}"/>.
</a4j:outputPanel>
</h:form>
</f:view>
</h:body>
</html>

Backing bean:

package test.richfaces;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import org.richfaces.model.ChartDataModel;
import org.richfaces.model.StringChartDataModel;
import org.richfaces.model.TreeNode;
import org.richfaces.model.TreeNodeImpl;

@ManagedBean
@RequestScoped
public class TestRichFaces {
    // progress bar
    private static int p = 0;
    public int getProgress() {
        p = (p + 1) % 101;
        return p;
    }
    // form fields
    private Integer f1;
    private String f2;
    public Integer getF1() {
        return f1;
    }
    public void setF1(Integer f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    // special form field types
    private Date date;
    private String text;
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    // auto complete list
    public List<String> getList() {
        List<String> res = new ArrayList<String>();
        res.add("A1");
        res.add("A2");
        res.add("A3");
        res.add("B1");
        res.add("B2");
        res.add("B3");
        res.add("C1");
        res.add("C2");
        res.add("C3");
        return res;
    }
    // AJAX fields
    public String getTime() {
        return (new Date()).toString();
    }
    // tree
    public static class Link extends TreeNodeImpl {
        private String name;
        private String link;
        public Link() {
            super();
        }
        public Link(String name, String link) {
            super(!link.equals(""));
            this.name = name;
            this.link = link;
        }
        public String getType() {
            return link.equals("") ? "Category" : "Link";
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getLink() {
            return link;
        }
        public void setLink(String link) {
            this.link = link;
        }
    }
    public TreeNode getTree() {
        Link search = new Link("Search", "");
        search.addChild(0, new Link("Google", "http://www.google.com/"));
        search.addChild(1, new Link("Bing", "http://www.bing.com/"));
        Link social = new Link("Social", "");
        social.addChild(0, new Link("Facebook", "http://www.facebook.com/"));
        social.addChild(1, new Link("Twitter", "http://www.twitter.com/"));
        social.addChild(2, new Link("LinkedIn", "http://www.linkedin.com/"));
        Link inet = new Link("Internet", "");
        inet.addChild(0, search);
        inet.addChild(1, social);
        return inet;
    }
    // chart
    public ChartDataModel<String,Number> getBarChart() {
        StringChartDataModel chart = new StringChartDataModel(ChartDataModel.ChartType.bar);
        chart.put("A", 1.0);
        chart.put("B", 4.0);
        chart.put("C", 9.0);
        chart.put("D", 16.0);
        chart.put("E", 25.0);
        return chart;
    }
    public ChartDataModel<String,Number> getLineChart() {
        StringChartDataModel chart = new StringChartDataModel(ChartDataModel.ChartType.line);
        chart.put("A", 1.0);
        chart.put("B", 4.0);
        chart.put("C", 9.0);
        chart.put("D", 16.0);
        chart.put("E", 25.0);
        return chart;
    }
    public ChartDataModel<String,Number> getPieChart() {
        StringChartDataModel chart = new StringChartDataModel(ChartDataModel.ChartType.pie);
        chart.put("A", 1.0);
        chart.put("B", 4.0);
        chart.put("C", 9.0);
        chart.put("D", 16.0);
        chart.put("E", 25.0);
        return chart;
    }
}

Screen shots:

JSF RichFaces demo JSF RichFaces demo
JSF RichFaces demo JSF RichFaces demo
JSF A4J demo JSF A4J demo JSF A4J demo

Custom components:

There are actually two ways to create your own JSF components. The first is to write the component in Java. The second is to create the component from existing components using the composite feature of JSF.

My recommendation is: create a composite component if possible - it is much simpler, and if you have to write one in Java then only provide facelets support no JSP support - JSP support can be tricky.

Custom component in Java:

There is no magic in the external component libraries. Obviously one can create ones own component library.

And doing it for facelets is not too difficult.

New component:

package test.custom;

import java.io.IOException;
import java.util.List;

import javax.faces.component.FacesComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import test.custom.TestCustom.DataPair;

@FacesComponent(createTag=true, tagName="calctable", namespace="http://local/jsf/custom")
public class CalcTableComponent extends UIComponentBase {
    @Override
    public String getFamily() {
        return "local";
    }
    // properties
    private String hdr1;
    private String hdr2;
    private List<DataPair> data;
    public String getHdr1() {
        return hdr1;
    }
    public void setHdr1(String hdr1) {
        this.hdr1 = hdr1;
    }
    public String getHdr2() {
        return hdr2;
    }
    public void setHdr2(String hdr2) {
        this.hdr2 = hdr2;
    }
    public List<DataPair> getData() {
        return data;
    }
    public void setData(List<DataPair> data) {
        this.data = data;
    }
    // render
    @SuppressWarnings("unchecked")
    private <T> T fix(T curr, String name) {
        Object o = getStateHelper().eval(name);
        if(o == null) {
            return curr;
        } else {
            return (T)o;
        }
    }
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        // fix EL value expressions for facelets
        hdr1 = fix(hdr1, "hdr1");
        hdr2 = fix(hdr2, "hdr2");
        data = fix(data, "data");
        // output
        ResponseWriter rw = context.getResponseWriter();
        rw.startElement("h2", this);
        rw.write(hdr2);
        rw.endElement("h2");
        rw.startElement("table", this);
        rw.writeAttribute("border", "1", null);
        rw.startElement("tr", this);
        rw.startElement("th", this);
        rw.write(hdr1);
        rw.endElement("th");
        rw.startElement("th", this);
        rw.write(hdr2);
        rw.endElement("th");
        rw.endElement("tr");
        for(int i = 0; i < data.size(); i++) {
            rw.startElement("tr", this);
            rw.startElement("td", this);
            rw.write(data.get(i).getX());
            rw.endElement("td");
            rw.startElement("td", this);
            rw.write(data.get(i).getFx());
            rw.endElement("td");
            rw.endElement("tr");
        }
        rw.endElement("table");
    }
}

The @FacesComponent annotation makes the class a known component with the spcified tag name and namespace.

The tags attributes are just plain Java properties.

And one just override a render method to output the desired HTML.

Maybe not super elegant, but actually easier than a JSP tag library.

Facelet:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:cust="http://local/jsf/custom"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Custom component</title>
</h:head>
<h:body>
<h1>Custom component</h1>
<f:view>
<cust:calctable hdr1="n" hdr2="n*n" data="#{testCustom.data2}"/>
<br/>
<cust:calctable hdr1="n" hdr2="n*n*n" data="#{testCustom.data3}"/>
</f:view>
</h:body>
</html>

Backing bean:

package test.custom;

import java.util.ArrayList;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class TestCustom {
    public class DataPair {
        private String x;
        private String fx;
        public DataPair(String x, String fx) {
            this.x = x;
            this.fx = fx;
        }
        public String getX() {
            return x;
        }
        public String getFx() {
            return fx;
        }
    }
    public List<DataPair> getData2() {
        List<DataPair> res = new ArrayList<>();
        for(int i = 1; i <= 10; i++) {
            res.add(new DataPair(Integer.toString(i), Integer.toString(i * i)));
        }
        return res;
    }
    public List<DataPair> getData3() {
        List<DataPair> res = new ArrayList<>();
        for(int i = 1; i <= 10; i++) {
            res.add(new DataPair(Integer.toString(i), Integer.toString(i * i * i)));
        }
        return res;
    }
}

Adding JSP support requires more work.

A JSP tag class:

package test.custom;

import java.util.List;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.webapp.UIComponentELTag;

import test.custom.TestCustom.DataPair;

public class CalcTableTag extends UIComponentELTag {
    // properties
    private String hdr1;
    private String hdr2;
    private List<DataPair> data;
    public String getHdr1() {
        return hdr1;
    }
    public void setHdr1(String hdr1) {
        this.hdr1 = hdr1;
    }
    public String getHdr2() {
        return hdr2;
    }
    public void setHdr2(String hdr2) {
        this.hdr2 = hdr2;
    }
    public List<DataPair> getData() {
        return data;
    }
    public void setData(List<DataPair> data) {
        this.data = data;
    }
    // fix EL value expressions for JSP
    public void setHdr1(Object hdr1) {
        if(hdr1 instanceof String) {
            setHdr1((String)hdr1);
        } else if(data instanceof ValueExpression) {
            ValueExpression ve = (ValueExpression)hdr1;
            setHdr1((String)ve.getValue(getELContext()));
        } 
    }
    public void setHdr2(Object hdr2) {
        if(hdr2 instanceof String) {
            setHdr2((String)hdr2);
        } else if(data instanceof ValueExpression) {
            ValueExpression ve = (ValueExpression)hdr2;
            setHdr2((String)ve.getValue(getELContext()));
        }
    }
    @SuppressWarnings("unchecked")
    public void setData(Object data) {
        if(data instanceof List) {
            setData((List<DataPair>)data);
        } else if(data instanceof ValueExpression) {
            ValueExpression ve = (ValueExpression)data;
            setData((List<DataPair>)ve.getValue(getELContext()));
        }
    }
    // delegate
    @Override
    public String getComponentType() {
        return "CalcTableComponent";
    }
    @Override
    public String getRendererType() {
        return null;
    }
    @Override
    public void setProperties(UIComponent component) {
        super.setProperties(component);
        CalcTableComponent real = (CalcTableComponent)component;
        real.setHdr1(hdr1);
        real.setHdr2(hdr2);
        real.setData(data);
    }
}

For more info about custom JSP taglibs and TLD files see here.

A tag library descriptor here WEB-INF/cust.tld:

<taglib 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/web-jsptaglibrary_2_1.xsd"
        version="2.1">
    <tlib-version>1.0</tlib-version>
    <jsp-version>2.0</jsp-version>
    <short-name>Custom tags</short-name>
    <uri>http://local/jsf/custom</uri>
    <display-name>Custom tags</display-name>
    <tag>
        <name>calctable</name>
        <tag-class>test.custom.CalcTableTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>hdr1</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>hdr2</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>data</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.util.List</type>
            <deferred-value>true</deferred-value>
        </attribute>
    </tag>
</taglib>

And adding the component to WEB-INF/faces-config.xml so that the tag classes getComponentType method can refer to its name:

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
              http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
    ...
    <component>
        <component-type>CalcTableComponent</component-type>
        <component-class>test.custom.CalcTableComponent</component-class>
        <property>
            <property-name>hdr1</property-name>
            <property-type>java.lang.String</property-type>
        </property>
        <property>
            <property-name>hdr2</property-name>
            <property-type>java.lang.String</property-type>
        </property>
        <property>
            <property-name>data</property-name>
            <property-type>java.util.List</property-type>
        </property>
    </component>
    ...
</faces-config>

Now we can create the JSP page:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="cust" uri="http://local/jsf/custom" %>
<html>
<head>
<title>Custom component</title>
</head>
<body>
<h1>Custom component</h1>
<f:view>
<cust:calctable hdr1="n" hdr2="n*n" data="${testCustom.data2}"/>
<br>
<cust:calctable hdr1="n" hdr2="n*n*n" data="${testCustom.data3}"/>
</f:view>
</body>
</html>

Note that it uses ${} not #{}. I was not able to get #{} to work properly.

I recommend not supporting JSP pages for custom tags. Too much trouble.

Composite component:

Facelets has a nice feature to create new components called composite components from other components.

And it is super easy.

A resources/foo/bar.xhtml with content:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="x"/>
<composite:attribute name="y"/>
</composite:interface>
<composite:implementation>
<!-- normal facelet code referring to attributes #{cc.attrs.x} and #{cc.attrs.x} -->
</composite:implementation>
</html>

becomes tag bar in namespace http://java.sun.com/jsf/composite/foo.

Now we can do calctable as a composit tag in resources/custom/calctable.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="hdr1"/>
<composite:attribute name="hdr2"/>
<composite:attribute name="data"/>
</composite:interface>
<composite:implementation>
<h2>#{cc.attrs.hdr2}</h2>
<h:dataTable value="#{cc.attrs.data}" var="o" border="1">
<h:column><f:facet name="header">#{cc.attrs.hdr1}</f:facet>#{o.x}</h:column>
<h:column><f:facet name="header">#{cc.attrs.hdr2}</f:facet>#{o.fx}</h:column>
</h:dataTable>
</composite:implementation>
</html>

And use it in a Facelet similar to the custom tag example:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:cust="http://java.sun.com/jsf/composite/custom"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Composite component</title>
</h:head>
<h:body>
<h1>Composite component</h1>
<f:view>
<cust:calctable hdr1="n" hdr2="n*n" data="#{testCustom.data2}"/>
<br/>
<cust:calctable hdr1="n" hdr2="n*n*n" data="#{testCustom.data3}"/>
</f:view>
</h:body>
</html>

It can't be much easier than that.

Article history:

Version Date Description
1.0 January 1st 2020 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj