My blog has moved!

You should automatically be redirected in 6 seconds. If not, visit
http://blogs.i2m.dk/allan
and update your bookmarks.

Tuesday 12 February 2008

Creating a custom JSF message component

The other day I was tasked with creating feedback messages for a JSF-based application as JavaScript alert dialogues. JSF already got a tag for displaying messages attached to components (<h:message /> and <h:messages />), however they are limited to render the text using a given cascading style sheet class for each level of severity.

This entry will show how to build a custom JSF message tag for rendering the messages as a JavaScript alert.

There are many excellent tutorials and blog entries out there describing JSF component development much better than I am capable of. So, I'll just focus on what is needed to create this specific component. My instructions are based on NetBeans 6.0, but they don't including anything that is not possible to do in other IDEs, so you should be able to translate the instructions into your own IDE.

We will keep the tag simple with one attribute, for, which will specify for which component the messages should be displayed. The signature of the tag will therefore be: <jc:alertMessage for="myForm" />.

1. Fire up NetBeans

2. Create a new Java Class Library Project (I've called mine jsf-components)

Before we continue let me briefly explain what we need:


META-INF/jsf-components.tld

Tag library descriptor declaring your tag and defining its attributes

META-INF/faces-config.xml

Declares the tag in the faces application

components/AlertMessage.java

Class representing the user interface component with properties for each attribute supported by the tag. It's recommended to sub-class one of the existing components to avoid doing all the work yourself.

components/AlertMessageRenderer.java

Class for rendering the content of the tag. This is the class where we will actual construct the HTML/Javascript code that is outputted to the browser.

components/AlertMessageTag.java

Class representing the actual tag.




Now that we know what to do - lets get on with it.

3. Create the components package in the Source Packages


3a) Right click Source Packages
3b) Select 'New'
3c) Select 'Other'
3d) Select 'Java'
3e) Select 'Java Package'
3f) Click 'Next'
3g) Enter components as the package name
3h) Click 'Finish'

4. Create the tag library descriptor
4a) Right click Source Packages
4b) Select 'New'
4c) Select 'Other'
4d) Select 'Web'
4e) Select 'Tag Library Descriptor'
4f) Click 'Next'
4g) Enter the following information:


  • TLD name: jsf-components

  • URI: http://jsf-components

  • Prefix: jc


4h) Click 'Finish'

5. Add the javaee.jar from the GlassFish project (%GLASSFISH%/lib) to the library dependencies of the project.

6. Create the Java class for the UI component, AlertMessage subclassing the UIMessage component.


package components;

import javax.faces.component.UIMessage;

public class AlertMessage extends UIMessage {

private static final String MSG_COMPONENT_TYPE = "components.alertmessage";
private static final String MSG_COMPONENT_FAMILY = "javax.faces.Message";

@Override
public String getFamily() {
return MSG_COMPONENT_FAMILY;
}

@Override
public String getRendererType() {
return MSG_COMPONENT_TYPE;
}

@Override
public String getFor() {
return super.getFor();
}

@Override
public void setFor(String forParam) {
super.setFor(forParam);
}
}


7. Create the Java class for the tag, AlertMessageTag subclassing the UIComponentELTag tag.


package components;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.webapp.UIComponentELTag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;

public class AlertMessageTag extends UIComponentELTag {

private ValueExpression forParam;

private static final String MSG_COMPONENT_TYPE = "components.alertmessage";

private static final String MSG_RENDERER_TYPE = "components.alertmessage";

@Override
public String getComponentType() {
return MSG_COMPONENT_TYPE;
}

@Override
public String getRendererType() {
return MSG_RENDERER_TYPE;
}

@Override
protected void setProperties(UIComponent component) {
super.setProperties(component);
AlertMessage em = (AlertMessage) component;
em.setValueExpression("for", forParam);
}

@Override
public int doEndTag() throws JspException {
return super.doEndTag();
}

@Override
public int doStartTag() throws JspException {
return super.doStartTag();
}

@Override
public void setPageContext(PageContext ctx) {
super.setPageContext(ctx);
}

@Override
public void setParent(Tag tag) {
super.setParent(tag);
}

public void setFor(ValueExpression forParam) {
this.forParam = forParam;
}
}


9. Create the Java class for rendering the tag, AlertMessageRenderer subclassing the JSF Renderer.


package components;

import java.io.IOException;
import java.util.Iterator;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
import org.apache.commons.lang.StringEscapeUtils;

public class AlertMessageRenderer extends Renderer {

@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
AlertMessage alertMessage = (AlertMessage) component;

// Locate the component for which to display messages
UIComponent forComponent = alertMessage.findComponent(alertMessage.getFor());

// If the component could not be found end processing
if (forComponent == null) {
return;
}

// Iterate through messages for the component
Iterator iter = context.getMessages(forComponent.getClientId(context));
if (iter.hasNext()) {
ResponseWriter writer = context.getResponseWriter();

// Start the script tag
writer.startElement("script", alertMessage);
writer.writeAttribute("type", "text/javascript", null);

// Construct one big string of all messages
StringBuffer message = new StringBuffer();
while (iter.hasNext()) {
FacesMessage msg = (FacesMessage) iter.next();
if (message.length() > 0) {
// Separate each message with the JavaScript escape code
// for newline
message.append("\n");
}

message.append(msg.getSummary());
}

// Escape the constructed string to be outputable as a JavaScript
String displayMessage = StringEscapeUtils.escapeJavaScript(message.toString());

// Output the javascript code for displaying the alert dialogue
String jsAlert = "alert('" + displayMessage + "');";
writer.writeText(jsAlert.toCharArray(), 0, jsAlert.length());

// End the script tag
writer.endElement("script");
}
}

@Override
public void decode(FacesContext ctx, UIComponent component) {
if (ctx == null || component == null) {
throw new NullPointerException();
}
}
}


Notice that I'm using StringEscapeUtils.escapeJavaScript(String) from the Apache Commons Lang library to ensure the correct output of the JavaScript code.

10. Update the tag library descriptor to reflect the created component:


<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>jc</short-name>
<uri>http://jsf-components</uri>
<tag>
<name>AlertMessage</name>
<tag-class>components.AlertMessageTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>
The ID of the component whose attached FacesMessage object
(if present) should be diplayed by this component.
</description>
<name>for</name>
<required>true</required>
<deferred-value/>
</attribute>
</tag>
</taglib>


11. Create the faces configuration (META-INF/faces-config.xml) for the component:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2" 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-facesconfig_1_2.xsd">
<component>
<component-type>components.alertmessage</component-type>
<component-class>components.AlertMessage</component-class>
</component>

<render-kit>
<renderer>
<description>Renderer for the alert message component.</description>
<component-family>javax.faces.Message</component-family>
<renderer-type>components.alertmessage</renderer-type>
<renderer-class>components.AlertMessageRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>


12. Build the class library

All you need to do now is to place the jsf-components.jar into your web application, declare the tag library (<%@taglib prefix="jc" uri="http://jsf-components" %>) on the pages where you want to use the tag (<jc:alertMessage for="componentId" />). Example:


<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@taglib prefix="jc" uri="http://jsf-components" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Example of AlertMessage component</title>
</head>
<body>
<f:view>
<h:form>
<jc:AlertMessage for="numberField" />
<h:inputText id="numberField">
<f:validateLength minimum="5" maximum="10" />
<f:validateLongRange minimum="10000" maximum="222222" />
</h:inputText>
<h:commandButton value="Submit" action="reload" />
</h:form>
</f:view>
</body>
</html>





UPDATE (30. July 2008): Facelets Support

You can use the component with Facelets by providing a Facelets Tag Library descriptor. In this example I name the tag library descriptor mycomponents.taglib.xml and place in the WEB-INF/ directory.

The contents of the descriptor is:


<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">

<facelet-taglib>
<namespace>http://jsf-components</namespace>
<tag>
<tag-name>alertMessage</tag-name>
<component>
<component-type>components.alertmessage</component-type>
<renderer-type>components.alertmessage</renderer-type>
</component<
</tag<
</facelet-taglib<


Next you need to reference the descriptor in web.xml using the facelets.LIBRARIES context parameter:


<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/mycomponents.taglib.xml</param-value>
</context-param>


Remember that if you have more than one Facelets Tag Library Descriptor, to separate them using semicolons (;)

23 comments:

Anonymous said...

Nice writeup, will sure come in handy. Thanks for the explanation.

Anonymous said...

Good work man!!

suggest a excellent JSF create component tutorial, please.

And post the link of the apache library.

Mirla Braga said...

I like you post. It very good.
But, when I implementations there is a error:

org.apache.jasper.JasperException: Unable to convert string "idlogin" to class "javax.el.ValueExpression" for attribute "for": Property Editor not registered with the PropertyEditorManager

Do you know why???

I don't know to do.

Thanks

Allan Lykke Christensen said...

Hi Mirla,

Thanks for visiting my blog.

Which version of JSF are you using?

Kind regards,
Allan

Mirla Braga said...

Hello Allan

I am using the version JSF 1.2
that come wiht JBoss 4.2.2

Thanks

ferJON said...

Hi,

This is a nice tutorial on creating custom JSF component.

I would just like to ask how will I implement the messages version of this...

Thanks, and more power!!!

Anonymous said...

Hi,

Thanks for writing this up. I was getting some errors about not being able to find a NamedObject from this line:

private static final String MSG_COMPONENT_TYPE = "components.extendedmessage";

So I changed it to "components.alertmessage" because I figured there was not a ExtendedMessage object anywhere.

Do you know what happened. It works find with the above changes

Allan Lykke Christensen said...

Thanks for pointing out the problem with the component type. It was a mistake on my side when I transferred my code to the blog. I will fix the code in the entry right away.

Anonymous said...

Hi,

How would I use the same with Facelets ? What enteries are needed in taglib.xml . I am not sure which will come as component or handler class ?
Please let me know.

Allan Lykke Christensen said...

To use any custom component with Facelets you need to provide a tag library descriptor specifically for Facelets. The tag library descriptor contains the namespace and component and render type for each of your components in the library. Once the descriptor is complete you must reference it using the facelets.LIBRARIES context parameter in web.xml.

I've added the instructions to the end of the blog.

Enjoy.

Anonymous said...

This is very nice post about creating custom component, it is for H:message so I followed this same meterial and build a custome h:messages by modifying some parts of the code. So if anybody is looking for h:messages custom component then visit this link
http://www.jaininaveen.com/?page_id=23

Anonymous said...

Thanks. Nice. But I followed your instructions, got error:

org.apache.jasper.JasperException: Unable to convert string "xyz" to class "javax.el.ValueExpression" for attribute "for": Property Editor not registered with the PropertyEditorManager

I am using jsf ri 1.2 and jboss 5.0.1 GA. Thanks for help.
Dave

Allan Lykke Christensen said...

Hi Dave,

It's a bit hard to see what might have gone wrong. Did you change any of the code, or did you copy it verbatim?

Sso said...
This comment has been removed by the author.
Sso said...
This comment has been removed by the author.
Allan Lykke Christensen said...

Hello is,

jsf-components is the product of the blog entry. I haven't provided a pre-compiled output of the code. If you create the code as described in the blog, you'll end up with jsf-components.jar.

Hope that helps.

Regards,
Allan

Sso said...

THINK YOU VERY MUCH Allan Lykke Christensen

I Create all the classes the tld file ...
but i used eclipse IDE
where can i find the jar ...
please really help in my project think you very much i delete my post cause ...i have asked for facelet
but i still used the
classic tag definition
thinks for you again

Allan Lykke Christensen said...

Hi is,

No need to delete your posts - someone else might have the same issues as yourself.

I've decided to create a project for my JSF utility library on Kenai. I'll post the URL once I've finished uploaded the library (20 -30 mins).

Regards,
Allan

Sso said...

OK ok ...that's fine
i undrestand ..
...you mean to create this project alone ...
i'm really sorry ..i think that was to implement this classe into my jsf project ...

OK i think i will do it now arllight
sorry i'm just a beginner
i have only one month in JEE world :p

have a good night and think for the help

Merci beaucoup Monsieur

Sso said...

Oh really ...I'm waiting for so :)
that will really great
to post it

Sso said...

After your permission
i have create it
and i post it there
http://www.4shared.com/file/103214665/2f019e99/jsf-components.html

but i'm still testing if everything gonna fine

and waiting for your post

Allan Lykke Christensen said...

Hi is,

I created a project for my JSF utilities at Kenai: http://kenai.com/projects/i2m-jsf

You can download the first version of the utilities from here:

http://kenai.com/projects/i2m-jsf/downloads/directory/Version_1.0Simply add i2m-jsf-1.0.jar to your WEB-INF/lib directory and you are ready to go.

Sso said...

Hi
Allan
i would like to think you very much