JSF 2.0 Test Drive Part 1: Developing a Facelet Composite Component

 

As part of my work on JSR 314 (JSF 2.0) I've been reviewing the specification, Javadocs, and tag library docs for accuracy. One way I do this is to make examples that test drive what the documentation says JSF 2.0 will do.

To that end, here's Part 1 of my JSF 2.0 Test Drive... Developing a Facelet Composite Component

Composite Component Tag

  <testcc:inputColor />

Description

An Ajaxified JSF 2.0 Facelet Composite Component that lets you choose an RGB color.

Downloadable Source Code

The Maven 2 project source code is licensed under the MPL 1.1 and is available at the edoras framework SVN repo:

http://svn.portletfaces.org/svn/portletfaces/legacy/examples/trunk/webapps/org.edorasframework.example.facelet.cc.jsf.2.0/

Screen Shot

 

JSF 2.0 Terminology

In order to understand some of the terms used in the example, let's talk about some new JSF 2.0 Facelet terminology found in the documentation:

Composite Component: Refers to an XHTML file that contains a piece of reusable markup that is encapsulated by the ui:component tag
Composite Component Author: Refers to the person that develops the composite component
Composite Component Tag: Refers to a tag like <testcc:inputColor /> that lets folks create instances of the composite component
Using Page: Refers to the Facelet XHTML f:view that contains the composite component tag
Page Author: Refers to the person that that creates an instance of the composite component on a using page.

Composite Component Source: inputColor.xhtml

Here's the markup for the component itself. Note how JSF 2.0 now has a nice ui:interface section for defining the usage contract for the page author to abide by, and a ui:implementation section for hiding the inner workings of the component itself (the reusable markup). Also note that I didn't have to write any Javascript to perform Ajax updates in the DOM -- JSF 2.0 has that built-in with the f:ajax tag. The render attribute of f:ajax specifies a space-delimited list of "ids" that are to be re-rendered after the form is submitted via XmlHttpRequest().

<ui:component xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:cc="http://java.sun.com/jsf/composite"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.w3.org/1999/xhtml http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd">

  <cc:interface>
    <cc:attribute name="value" required="true" type="org.edorasframework.example.Color">
      <cc:attribute name="red" required="true" />
      <cc:attribute name="green" required="true" />
      <cc:attribute name="blue" required="true" />
    </cc:attribute>
    <cc:actionSource name="colorPalette" targets="redSelector greenSelector blueSelector" />
    <cc:editableValueHolder name="colorFields" targets="redInputText greenInputText blueInputText" />
    <cc:facet name="header" required="true" />
  </cc:interface>

  <cc:implementation>
    <cc:renderFacet name="header" />
    <f:ajax render="preview kids">
      <h:panelGrid columns="2">
        <h:outputLabel value="R:" />
        <h:inputText id="redInputText" value="#{cc.attrs.value.red}">
          <f:validateLongRange minimum="0" maximum="255" />
        </h:inputText>
        <h:outputLabel value="G:" />
        <h:inputText id="greenInputText" value="#{cc.attrs.value.green}">
          <f:validateLongRange minimum="0" maximum="255" />
        </h:inputText>
        <h:outputLabel value="B:" />
        <h:inputText id="blueInputText" value="#{cc.attrs.value.blue}">
          <f:validateLongRange minimum="0" maximum="255" />
        </h:inputText>
      </h:panelGrid>
    </f:ajax>
    <h:outputText value="Color Preview: " />
    <c:set value="#{cc.attrs.value.red}" var="red" />
    <c:set value="#{cc.attrs.value.green}" var="green" />
    <c:set value="#{cc.attrs.value.blue}" var="blue" />
    <c:set value="#{red},#{green},#{blue}" var="rgb" />
    <h:outputText id="preview" value=" " style="border: 1px solid; padding: 1px 10px; background-color: rgb(#{rgb});" />
    <f:ajax render="redInputText greenInputText blueInputText preview kids">
      <h:panelGrid border="1" columns="3">
        <f:facet name="header">
          <h:outputText value="Color Palette" />
        </f:facet>
        <h:commandLink id="redSelector" value="Red">
          <f:setPropertyActionListener target="#{cc.attrs.value.red}" value="255" />
          <f:setPropertyActionListener target="#{cc.attrs.value.green}" value="0" />
          <f:setPropertyActionListener target="#{cc.attrs.value.blue}" value="0" />
        </h:commandLink>
        <h:commandLink id="greenSelector" value="Green">
          <f:setPropertyActionListener target="#{cc.attrs.value.red}" value="0" />
          <f:setPropertyActionListener target="#{cc.attrs.value.green}" value="255" />
          <f:setPropertyActionListener target="#{cc.attrs.value.blue}" value="0" />
        </h:commandLink>
        <h:commandLink id="blueSelector" value="Blue">
          <f:setPropertyActionListener target="#{cc.attrs.value.red}" value="0" />
          <f:setPropertyActionListener target="#{cc.attrs.value.green}" value="0" />
          <f:setPropertyActionListener target="#{cc.attrs.value.blue}" value="255" />
        </h:commandLink>
      </h:panelGrid>
    </f:ajax>
    <br />
    <h:panelGroup id="kids">
      <cc:insertChildren />
    </h:panelGroup>
  </cc:implementation>

</ui:component>

Using Page Source: usingPage.xhtml

Here's what the using page looks like, and how it creates an instance of the composite component by using a composite component tag. Note that with JSF 2.0, you can attach action-listeners and value-change-listeners to composite components. Additionally, you can specify children inside the composite component tag, provided that the composite component author uses the ui:insertChildren tag to place the children somewhere in the reusable markup. The testcc:inputColor tag shown below inserts an h:panelGrid as a child, which shows the "You Selected..." part to the user.

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html" xmlns:testcc="http://java.sun.com/jsf/composite/testcc"
  xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.w3.org/1999/xhtml http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd">
  <h:head>
    <title>Using Page</title>
  </h:head>
  <h:body>
    <h:form>
      <h:messages />
      <testcc:inputColor value="#{modelManagedBean.color}">

        <f:facet name="header">
          <h:outputText value="Please choose a color" />
        </f:facet>

        <f:actionListener for="colorPalette" type="org.edorasframework.example.ColorActionListener" />

        <f:valueChangeListener for="colorFields" type="org.edorasframework.example.ColorValueChangeListener" />

        <!--
        The following h:panelGrid will be used by the cc:insertChildren tag in the
        cc:implementation section of the  testcc:inputColor composite component.
        -->
        <h:panelGrid columns="2">
          <f:facet name="header">
            <h:outputText value="You Selected: " />
          </f:facet>
          <h:outputLabel value="Red:" />
          <h:outputText value="#{modelManagedBean.color.red}" />
          <h:outputLabel value="Green:" />
          <h:outputText value="#{modelManagedBean.color.green}" />
          <h:outputLabel value="Blue:" />
          <h:outputText value="#{modelManagedBean.color.blue}" />
        </h:panelGrid>

      </testcc:inputColor>
      <br />
      <h:commandButton value="Submit" />
    </h:form>
  </h:body>
</f:view>

Composite Component Value POJO: Color.java

Here is the POJO that is used to supply a value to the composite component:

package org.edorasframework.example;

import java.io.Serializable;

/**
 * This class provides a way of representing a color as an RGB triplet.
 *
 * @author  "Neil Griffin"
 */
public class Color implements Serializable {

  private static final long serialVersionUID = -3810147232196826614L;

  private int red;
  private int green;
  private int blue;

  public int getBlue() {
    return blue;
  }

  public int getGreen() {
    return green;
  }

  public int getRed() {
    return red;
  }

  public void setBlue(int blue) {
    this.blue = blue;
  }

  public void setGreen(int green) {
    this.green = green;
  }

  public void setRed(int red) {
    this.red = red;
  }

}

The remainer of the files (action listener, value change listener, model managed bean) are all available from the SVN repos.

Anyways, hope you enjoyed seeing JSF 2.0 in action! Good stuff ahead!

Blogs
Hi Neil
thank you for this but i tried a jsf 2 portlet
with portletfaces-bridge-2.0.0-BETA2
im using netbeans 6.9.1 , jsf 2 , liferay 6.0.5 CE
the portlet has been deployed but when i add it
to a page it throws an exception

java.lang.UnsupportedOperationException

here is my project
http://www.pageraz.com/wp-content/uploads/PF_TEST.zip

and by the way the svn repository url that you adeed is not exist anymore can you please relocate it

thankx
We recently fixed some UnsupportedOperationException problems in the trunk, which will be made available in BETA3.

For now, you can use a BETA3-SNAPSHOT by building the JARs manually from the trunks in SVN:

http://svn.portletfaces.org/svn/portletfaces/bridge/org.portletfaces.bridge.api/trunk/
http://svn.portletfaces.org/svn/portletfaces/bridge/org.portletfaces.bridge.impl/trunk/
http://svn.portletfaces.org/svn/portletfaces/bridge/org.portletfaces.bridge/trunk/

That might make the UnsupportedOperationException problem go away.
Hello Neil
thank you for reply
i built the portletfaces beta3-snapshot and replace it
in my project and it throws new exception
The FacesServlet cannot have a url-pattern of /*. Please define a different url-pattern.

the stack trace
----------------
javax.faces.FacesException: The FacesServlet cannot have a url-pattern of /*. Please define a different url-pattern.
at com.sun.faces.application.view.JspViewHandlingStrategy.executePageToBuildView(JspViewHandlingStrategy.java:305)
at com.sun.faces.application.view.JspViewHandlingStrategy.buildView(JspViewHandlingStrategy.java:130)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:106)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at org.portletfaces.bridge.BridgeImpl.doFacesRequest(BridgeImpl.java:241)
at org.portletfaces.bridge.GenericFacesPortlet.doView(GenericFacesPortlet.java:194)
at javax.portlet.GenericPortlet.doDispatch(GenericPortlet.java:328)
at javax.portlet.GenericPortlet.render(GenericPortlet.java:233)
---------------------------------

these are my files

web.xml
-------------------------
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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_2_5.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</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>
</web-app>
-----------------------------
portlet.xml
-------------------------------
<?xml version='1.0' encoding='UTF-8' ?>
<portlet-app xmlns='http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd' version='2.0'>
<portlet>
<description>PF_TEST</description>
<portlet-name>PF_TEST</portlet-name>
<display-name>PF_TEST</display-name>
<portlet-class>org.portletfaces.bridge.GenericFacesPortlet</portlet-class>
<init-param>
<name>javax.portlet.faces.defaultViewId.view</name>
<value>start.xhtml</value>
</init-param>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
</supports>
<portlet-info>
<title>PF_TEST</title>
<short-title>PF_TEST</short-title>
</portlet-info>
</portlet>
</portlet-app>
------------------------------------------

start.xhtml
------------------------------
<?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://java.sun.com/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
------------------------------

i hope i can find a solution

thankx
I would recommend that you start with the JSF 2.0 example portlet source and make modifications to it:

http://svn.portletfaces.org/svn/portletfaces/portlets/examples/jsf-2.0-portlet/trunk/
Hello Neil
my example worked fine i don't know where the problem was
but now i have a jsf 2 portlet and it works in liferay
i haven't tried all the functions like the ajax request and
the navigation
i will ensure that worked than i will put my portlet here
so anyone can use it

thank you Neil
I noticed it behaves like this if you don't have a faces-config.xml. Even an empty one will do it.

Cheers,

Eduard