In this recipe, we will explore RichFaces CDK for creating JSF custom components. For those who are not familiar, CDK stands for Component Development Kit—a sub-project of RichFaces that allows you to easily create rich components with built-in Ajax support.
During this recipe, we will develop a custom component that will render a text field for inserting a phone number of type xxxx-xxxxx-x
. We will render the phone number as a string, of the form xxxx-xxxxx-x
, but we will store it as a string of type xxxxxxxxxx
—this task will be accomplished by a custom converter.
The HTML prototype of our custom component will be:
<div title="Phone field:"> <input name="phoneField" value="0000-00000-0" /> <img src="phone.gif" /> </div>
RichFaces CDK requires Maven, therefore we should have Maven 2.2.1 or higher installed in our system. This is available at http://maven.apache.org/download.html or under the libraries in the code bundle for this book in the /JSF_libs/Apache Maven 2.2.1
folder.
After download, you have to set the CLASSPATH to that of the /bin
directory of the Maven distribution. For example:
SET PATH = "C:PacktJSFKitapache-maven-2.2.1in"
Going forward, you should configure Maven for RichFaces CDK. For this, open the /conf/settings.xml
file for editing and add this code to the profiles section (after the last <profile>
tag):
... <profile> <id>cdk</id> <repositories> <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/1</url> <layout>legacy</layout> </repository> <repository> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> <updatePolicy>never</updatePolicy> </snapshots> <id>repository.jboss.com</id> <name>Jboss Repository for Maven</name> <url>http://repository.jboss.com/maven2/</url> <layout>default</layout> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>maven.jboss.org</id> <name>JBoss Repository for Maven Snapshots</name> <url>http://snapshots.jboss.org/maven2/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </pluginRepository> <pluginRepository> RichFaces CDKusing<releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> <updatePolicy>never</updatePolicy> </snapshots> <id>repository.jboss.com</id> <name>Jboss Repository for Maven</name> <url>http://repository.jboss.com/maven2/ </url> <layout>default</layout> </pluginRepository> </pluginRepositories> </profile> ...
Now activate the new profile by adding the following after the profiles section:
... <activeProfiles> <activeProfile>cdk</activeProfile> </activeProfiles> ...
Now, everything is set for using RichFaces CDK. Next, you should manually create a folder where the components will be stored (we named it, /JSFComponentFolder_CDK
), and a file named pom.xml
(in this folder) with the following content:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jsf.custom.component</groupId> <artifactId>jsfComponent</artifactId> <url>http://packt.cdk.org</url> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.0.0-RC</version> </dependency> <dependency> <groupId>javax.faces</groupId> <artifactId>jsf-impl</artifactId> <version>2.0.0-RC</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>el-impl</groupId> <artifactId>el-impl</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.richfaces.ui</groupId> <artifactId>richfaces-ui</artifactId> <version>3.3.3.BETA1</version> </dependency> </dependencies> </project>
Some of the pom.xml
elements are:
We have developed a JSF application to test our custom component with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library. The custom component JAR can be found with the book code bundle under the /JSF_libs/phoneNumberComponent—JSF 2.0
. In addition, we have used RichFaces 3.3.3.BETA1, which provides support for JSF 2.0. You can download this distribution from http://www.jboss.org/richfaces. The RichFaces libraries (including necessary dependencies) are in the book code bundle, under the /JSF_libs/RichFaces—JSF 2.0
folder.
OK, at this point zero, we have everything ready to start developing our first JSF custom component using RichFaces CDK.
First, we need to create a project for the component itself. In the library directory, /JSFComponentFolder_CDK
, which you just created, launch the following command in MS-DOS command prompt (see the following screenshot):
You should get a BUILD SUCCESSFUL message, and a folder named /phoneNumberComponent
with the following structure:
Next, we have to extend the predefined structure with the following directories:
srcmainconfig
esources
: This will contain the resource-config.xml
file for the resources registrationsrcmainjavajsfcustomcomponent
enderkit
: This will contain the render classsrcmain
esourcesjsfcustomcomponent
enderkithtmlcss
: This will store CSS filessrcmain
esourcesjsfcustomcomponent
enderkithtmlimages
: This will store imagessrcmain emplatesjsfcustomcomponent
: This will contain JSP templates-srcmainjavajsfcustomcomponentcomponentconverter
: It will contain the custom converterAssuming that you have accomplished this boring task, let's move forward and add maven-compiler-plugin
to the plugins
section in the /phoneNumberComponent/pom.xml
file:
… <plugin> <artifactId>maven-compiler-plugin</artifactId> <inherited>true</inherited> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> …
Next step consists in generating a skeleton for our component. For this, navigate from the Command Prompt into the /phoneNumberComponent
folder and execute the command from the screenshot:
After the BUILD SUCCESSFUL message, three files will have been generated:
phoneNumberComponent.xml
UIPhoneNumberComponent.java
htmlPhoneNumberComponent.jspx
At this point we start the real development stage. We begin with the component class. By default, this class extends the UIComponentBase
class, but we can modify it to extend the UIInput
class, since we have a simple input component:
package jsf.custom.component.component; import javax.faces.component.UIInput; public abstract class UIPhoneNumberComponent extends UIInput { public static final String COMPONENT_TYPE = "jsf.custom.component.PhoneNumberComponent"; public static final String COMPONENT_FAMILY = "jsf.custom.component.PhoneNumberComponent"; }
Now, we can modify the template for generating the renderer class. Open the JSP-like template, generated earlier, and modify it accordingly to our component:
<?xml version="1.0" encoding="UTF-8"?> <f:root xmlns:f="http://ajax4jsf.org/cdk/template" xmlns:c=" http://java.sun.com/jsf/core" xmlns:ui=" http://ajax4jsf.org/cdk/ui" xmlns:h=" http://ajax4jsf.org/cdk/h" xmlns:u=" http://ajax4jsf.org/cdk/u" xmlns:x=" http://ajax4jsf.org/cdk/x" xmlns:jsp=" http://ajax4jsf.org/cdk/jsp" class="jsf.custom.component.renderkit.html. PhoneNumberComponentRenderer" baseclass="jsf.custom.component.renderkit. PhoneNumberComponentRendererBase" component="jsf.custom.component.component.UIPhoneNumberComponent"> <f:clientid var="clientId"/> <f:resource name="/jsf/custom/component/renderkit/html/images/phone.gif" var="icon" /> <div id="#{clientId}" x:passThruWithExclusions="value,name,type,id"> <input id="#{clientId}" name="#{clientId}" type="text" value="#{this:getValueAsString(context, component)}"/> <jsp:scriptlet> <![CDATA[if(component.getFacet("icon")!=null && component.getFacet("icon").isRendered()) {]]> </jsp:scriptlet> <u:insertFacet name="icon" /> <jsp:scriptlet> <![CDATA[ }else{ ]]> </jsp:scriptlet> <img src="#{icon}"/> <jsp:scriptlet> <![CDATA[ } ]]> </jsp:scriptlet> </div> </f:root>
Next, we develop the renderer class. This class should be stored in srcmainjavajsfcustomcomponent
enderkit
and it looks as shown next:
package jsf.custom.component.renderkit; import java.io.IOException; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import org.ajax4jsf.renderkit.HeaderResourcesRendererBase; import jsf.custom.component.component.UIPhoneNumberComponent; import jsf.custom.component.component.converter.PhoneConverter; public abstract class PhoneNumberComponentRendererBase extends HeaderResourcesRendererBase { public void decode(FacesContext ctx, UIComponent ui_comp){ ExternalContext externalContext = ctx.getExternalContext(); Map paramsOnRequest = externalContext.getRequestParameterMap(); UIPhoneNumberComponent uiPhoneNumberComponent = (UIPhoneNumberComponent)ui_comp; String clientId = uiPhoneNumberComponent.getClientId(ctx); String submittedValue = (String)paramsOnRequest.get(clientId); if (submittedValue != null) { uiPhoneNumberComponent.setSubmittedValue(submittedValue); } } public String getValueAsString(FacesContext ctx, UIComponent ui_comp) throws IOException { UIPhoneNumberComponent uiPhoneNumberComponent = (UIPhoneNumberComponent)ui_comp; String valueString = (String)uiPhoneNumberComponent.getSubmittedValue(); if (valueString == null) { Object value = uiPhoneNumberComponent.getValue(); if (value != null) { Converter converter = getConverter(ctx, uiPhoneNumberComponent); valueString = converter.getAsString(ctx, ui_comp, value); } } return valueString; } public Object getConvertedValue(FacesContext ctx, UIComponent ui_comp, Object submittedValue) throws ConverterException{ UIPhoneNumberComponent uiPhoneNumberComponent = (UIPhoneNumberComponent)ui_comp; Converter converter = getConverter(ctx, uiPhoneNumberComponent); String valueString = (String)submittedValue; return converter.getAsObject(ctx, ui_comp, valueString); } private Converter getConverter(FacesContext ctx, UIPhoneNumberComponent uiPhoneNumberComponent){ Converter converter = uiPhoneNumberComponent.getConverter(); if (converter == null) { PhoneConverter phoneConverter = new PhoneConverter(); converter=phoneConverter; } return converter; } }
This class has several methods that are very important for rendering the component. First, we have the decode
method, which is responsible for reading values from request parameters and storing the submitted value locally on the component. Next, we have the getConverter
method, responsible for returning an instance of our converter (if none was indicated). The last two methods, getValueAsString
and getConvertedValue
, are responsible for rendering the value back to the view, and converting the submitted value.
The final class that we will develop is our custom converter. This is a simple converter that transforms the string xxxx-xxxxx-x
to xxxxxxxxxx
and vice versa:
package jsf.custom.component.component.converter; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.convert.FacesConverter; @FacesConverter(value = "PhoneConverter") public class PhoneConverter implements Converter { public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) { if (arg0 == null) { throw new NullPointerException("context"); } if (arg1 == null) { throw new NullPointerException("component"); } String s = String.valueOf(arg2); if (s.length() != 10) { return "0000-00000-0"; } String phoneNumber = s.substring(0,4) + "-" + s.substring(4,9) + "-" + s.substring(9,10); return phoneNumber; } public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) { if (arg0 == null) { throw new NullPointerException("context"); } if (arg1 == null) { throw new NullPointerException("component"); } try { String start = arg2.substring(0,4); String midle = arg2.substring(5,10); String end = arg2.substring(11,12); String phoneNumber = start + midle + end; return phoneNumber; } catch (Exception e) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Parser error! - Cannot convert this phone number from xxxx-xxxxx-x to xxxxxxxxxx!", "Cannot convert this phone number from xxxx-xxxxx-x to xxxxxxxxxx!"); throw new ConverterException(message); } } }
Now, speaking about resources, we need an image named phone.gif
(accordingly in our component and template). This should be placed in the srcmain
esourcesjsfcustomcomponent
enderkithtmlimages
folder, and should be configured in the resource-config.xml
file, as shown next (in this file you should configured many other resources, such as CSS files):
… <resource> <name>jsf/custom/component/renderkit/html/images/phone.gif</name> <path>jsf/custom/component/renderkit/html/images/phone.gif</path> </resource> …
We must finish one more step before compiling our component. We must specify the component properties in the phoneNumberComponent.xml
file as shown next (place this in the <component>
tag):
… <property> <name>value</name> <classname>java.lang.Object</classname> <description>Component value</description> </property> <property> <name>title</name> <classname>java.lang.String</classname> <description>Component title</description> <defaultvalue>"Phone number"</defaultvalue> </property> <property> <name>name</name> <classname>java.lang.String</classname> <description>Component name</description> </property> …
Now is the big moment! From the /phoneNumberComponent
folder, execute the following command:
mvn clean install
If you get a BUILD SUCCESSFUL message, then a new folder target was created under the /phoneNumberComponent
folder. In this folder, you should find a JAR file named phoneNumberComponent-1.0-SNAPSHOT.jar
. This is our component!
Next, we can develop a simple JSF project and test it (don't forget to add this new component and RichFaces 3.3.3.BETA1 into the project classpath). Since you should have enough experience for this, the following snippet is only a variant of the JSF test page:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <!-- RichFaces tag library declaration --> <%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%> <%@ taglib uri="http://richfaces.org/rich" prefix="rich"%> <!-- Custom component tag library declaration --> <%@ taglib uri="http://packt.cdk.org/phoneNumberComponent" prefix="e"%> <html> <head> <title>Using the phoneNumberComponent custom component</title> </head> <body> <f:view> <h:form id="phoneFormID"> <e:phoneNumberComponent id="phoneID" title="Enter phone number as xxxx-xxxxx-x" value="#{bean.phone}" /> <h:message showSummary="true" showDetail="false" for="phoneID" style="color: red; text-decoration:overline" /> <br /> <h:commandButton id="btnID" value="Submit Phone" /> </h:form> </f:view> </body> </html>
The following is output screenshot:
In addition if you want to add a custom validator (let's name it PhoneValidator.java
) you can follow the given steps:
srcmainjavajsfcustomcomponentcomponentvalidator
, and save in it the validator sourcepackage jsf.custom.component.component; import javax.faces.component.UIInput; import jsf.custom.component.component.validator.PhoneValidator; public abstract class UIPhoneNumberComponent extends UIInput { public static final String COMPONENT_TYPE = "jsf.custom.component.PhoneNumberComponent"; public static final String COMPONENT_FAMILY = "jsf.custom.component.PhoneNumberComponent"; public UIPhoneNumberComponent() { PhoneValidator phoneValidator=new PhoneValidator(); addValidator(phoneValidator); } }
It's done!
More details about custom components with RichFaces CDK can be found at the address http://docs.jboss.org/richfaces/latest_3_3_X/en/cdkguide/html/.