Advanced Facelets programming

Create custom logic tags and EL functions

If you think internationalization is hard, think again! In this article, Richard Hightower follows up his immensely popular introduction to Facelets with more advanced ways to bridge the gap between Java Server Faces (JSF) and EL. Follow along as Rick shows you how to internationalize your Web pages easily, add custom logic tags to a composition component, and incorporate metaprogramming into your Facelets development.

Richard Hightower (rhightower@arc-mind.com), Developer, ArcMind Inc.

Rick Hightower serves as chief technology officer for ArcMind Inc, a training company the specializes in JSF, Spring and Hibernate. He is coauthor of the popular book Java Tools for Extreme Programming, about applying extreme programming to J2EE development, and coauthor of Professional Struts.



09 May 2006

Also available in Chinese Russian Japanese

Facelets seems to really scratch an itch for the JSF development community. I've gotten a lot of positive feedback from my first article about Facelets, and I'm looking forward to showing you more fun stuff you can do with Facelets in this article. Kudos to Jacob Hookom and everyone else involved in the development of Facelets!

In my last article, I introduced the concept behind Facelets and showed you how to create and manipulate its HTML-style templates and reusable composition components. In this article, I build on that discussion, using many of the same examples and components I introduced then. For starters, I show you a pain-free way to do Internationalization using a Facelets expression language (EL) function. Next, I show you how to create reasonable defaults and custom logic tags. Finally, I show you how to do lightweight metaprogramming in Facelets.

Because so many of the examples in this article build on the ones from my previous article, I strongly suggest you read that article first. You might also want to download the example code for this article and install Facelets (and Tomahawk) before going further.

Internationalization made easy

Internationalization doesn't have to hurt when you're working with Facelets. For the first exercise, I show you how to extend the field composition component from the last article to work with dates, Booleans, and all manner of Java types that should be displayed as text. I also show you how to use reasonable defaults to internationalize the label for the field.

Recall from last time that the field composition component uses the field name as its default label if the label is not passed, as shown here:

...
<!--  The label is optional. Use fieldName as label if label missing. -->
    <c:if test="${empty label}">
        <c:set var="label" value="${fieldName}" />
    </c:if>

Say you would like to change that. Instead of loading the fieldName as its default label, you want the field composition component to look up the fieldName in the resource bundle associated with Faces. For this, you create a tag library that defines tags and EL functions. First, you create an EL function that looks up a fieldName in the resource bundle. If it can't find the label in the resource bundle, it tries to generate a fieldName based on the camel case of the given fieldName. Listing 1 shows an example usage:

Listing 1. Example usage of the EL function
<html xmlns="http://www.w3.org/1999/xhtml"
      ...
      xmlns:arc="http://www.arc-mind.com/jsf/core"
      xmlns:t="http://myfaces.apache.org/tomahawk">


<!--  The label is optional. Generate it if it is missing. -->
    <c:if test="${empty label}">
        <c:set var="label"
               value="${arc:getFieldLabel(fieldName,namespace)}" />
    </c:if>

Creating an EL function

In Listing 1, I the EL function arc:getFieldLabel(fieldName,namespace) to look up the fieldName in the resource bundle associated with Faces. The getFieldLabel() method actually looks up the label in the resource bundle. Here are the steps to create an EL function:

  1. Create a TagLibrary class.
  2. Create a Java utility class.
  3. Register the getFieldLabel() method.
  4. Register the TagLibrary class.
  5. Register the facelets-taglib descriptor file.

The following sections explain these steps one by one and show you how to use your new EL function.

Step 1. Create a TagLibrary class

EL functions and logic tags are defined in a Facelets TagLibrary class. To create a TagLibrary, class you need to subclass AbstractTagLibrary, as shown in Listing 2:

Listing 2. Subclassing AbstractTagLibrary
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.tag.AbstractTagLibrary;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;


/**
 * JsfCoreLibrary is an example for IBM developerWorks (c).
 * @author Rick Hightower from ArcMind Inc. http://www.arc-mind.com
 */
public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Namespace used to import this library in Facelets pages  */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

    /**  Current instance of library. */
    public static final JsfCoreLibrary INSTANCE = new JsfCoreLibrary();
    ...
    ...

Once the tag library is defined, you can add EL functions and logic tags to it.

Step 2. Create a Java utility class

Next you need to create a Java utility class that has a static method called getFieldLabel(). This utility method has no special tie to Facelets; it's just a regular Java static method, as shown in Listing 3:

Listing 3. The Java utility class with getFieldLabel()
package com.arcmind.jsfquickstart.tags;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.faces.context.FacesContext;

/**
 * Functions to aid developing JSF applications.
 */
public final class JsfFunctions {
	/**
	 * Stops creation of a new JsfFunctions object.
	 */
	private JsfFunctions() {
	}

	/**
	 * Get the field label.
	 *
	 * @param fieldName
	 *            fieldName
	 * @param formId
	 *            form id
	 * @return Message from the Message Source.
	 */
	public static String getFieldLabel(final String fieldName,
			final String formId) {

		Locale locale = FacesContext.getCurrentInstance().getViewRoot()
				.getLocale();
		String bundleName = FacesContext.getCurrentInstance().getApplication()
				.getMessageBundle();

		ResourceBundle bundle = ResourceBundle
                                        .getBundle(bundleName, locale, getClassLoader());

		/** Look for formId.fieldName, e.g., EmployeeForm.firstName. */

		String label = null;
		try {
			label = bundle.getString(formId + fieldName);
			return label;
		} catch (MissingResourceException e) {
			// do nothing on purpose.
		}

		try {
			/** Look for just fieldName, e.g., firstName. */
			label = bundle.getString(fieldName);
		} catch (MissingResourceException e) {
			/**
			 * Convert fieldName, e.g., firstName automatically becomes First
			 * Name.
			 */
			label = generateLabelValue(fieldName);
		}

		return label;

	}

	private static ClassLoader getClassLoader() {
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();
		if (classLoader == null) {
			return JsfFunctions.class.getClassLoader();
		}
		return classLoader;
	}

	/**
	 * Generate the field. Transforms firstName into First Name. This allows
	 * reasonable defaults for labels.
	 *
	 * @param fieldName
	 *            fieldName
	 *
	 * @return generated label name.
	 */
	public static String generateLabelValue(final String fieldName) {
		StringBuffer buffer = new StringBuffer(fieldName.length() * 2);
		char[] chars = fieldName.toCharArray();

		/* Change firstName to First Name. */
		for (int index = 0; index < chars.length; index++) {
			char cchar = chars[index];

			/* Make the first character uppercase. */
			if (index == 0) {
				cchar = Character.toUpperCase(cchar);
				buffer.append(cchar);

				continue;
			}

			/* Look for an uppercase character, if found add a space. */
			if (Character.isUpperCase(cchar)) {
				buffer.append(' ');
				buffer.append(cchar);

				continue;
			}

			buffer.append(cchar);
		}

		return buffer.toString();
	}
}

The getFieldLabel() method shown in Listing 3 looks up the field in the resource bundle associated with JSF. If it cannot find the field, it splits and capitalizes the camel-case string, such that a "firstName" field name becomes "First Name." This gives you a reasonable default, which you can still set up in the resource bundle if you want to override it.

Step 3. Register the getFieldLabel method

In the AbstractTagLibrary class shown in Listing 2 (JsfCoreLibrary), you need to register the getFieldLabel() method with the base class so that it is available as an EL function. I do this in Listing 4:

Listing 4. Registering JsfCoreLibrary.java as an EL function
    public JsfCoreLibrary() {
        super(NAMESPACE);

        try {
            Method[] methods = JsfFunctions.class.getMethods();

            for (int i = 0; i < methods.length; i++) {
                if (Modifier.isStatic(methods[i].getModifiers())) {
                    this.addFunction(methods[i].getName(), methods[i]);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

As you can see, Listing 4 just iterates through all the methods on the JsfFunction class using reflection. It then adds all JsfFunction's static methods using the inherited method addFunction().

Step 4. Register the TagLibrary class

You've now defined the EL function and the TagLibrary class that contains it. Next, you need to register your new TagLibrary class so that it can be found by Facelets. You do this by declaring a new library class in a new facelets-taglib file called jsf-core.taglib.xml, as shown in Listing 5:

Listing 5. jsf-core.taglib.xml
<?xml version="1.0"?>

<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "facelet-taglib_1_0.dtd">

<facelet-taglib>
	<library-class>
           com.arcmind.jsfquickstart.tags.JsfCoreLibrary
      </library-class>
</facelet-taglib>

Step 5. Register the facelets-taglib descriptor file

For Facelets to find your tag library, you need to register it in the WEB-INF/web.xml file, as shown in Listing 6:

Listing 6. Registering the new taglib with Facelets
	<context-param>
		<param-name>facelets.LIBRARIES</param-name>
		<param-value>
			/WEB-INF/facelets/tags/arcmind.taglib.xml;
			/WEB-INF/facelets/tags/jsf-core.taglib.xml
		</param-value>
	</context-param>

Notice that I added jsf-core.taglib.xml to the semicolon-delimited list passed to the init-param facelets.LIBRARIES. Composition components go in their own taglib file. EL functions and Facelets logic tags can go in the same taglib file.


Using the EL function in a composition component

Now that you've defined the EL function, placed it in the Facelets taglib descriptor file, and registered the taglib descriptor file in web.xml, you can start to use the tag. In the field composition component, you can use the arc:getFieldLabel to look up the label in the resource bundle, as shown in Listing 7:

Listing 7. Using the EL function
<!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:ui="http://java.sun.com/jsf/facelets"  
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:f="http://java.sun.com/jsf/core" 
      xmlns:arc="http://www.arc-mind.com/jsf/core"   
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"  
      xmlns:t="http://myfaces.apache.org/tomahawk">

  THIS TEXT WILL BE REMOVED
  <ui:composition>

    <c:if test="${empty label}">
        <c:set var="label"
               value="${arc:getFieldLabel(fieldName,namespace)}" />
    </c:if>
       ...

Notice I imported that "arc" library by specifying its namespace in the xmlns:arc="http://www.arc-mind.com/jsf/core" HTML element. The namespace has to match the namespace declared in the TagLibrary class (JsfCoreLibrary in this case) as shown here:

public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Namespace used to import this library in Facelets pages  */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

This might seem like a lot of work, even for just five steps. However, once you've done this basic setup, you can easily add more logic tags and EL functions without going through the same hassle every time. You'll have reasonable defaults for all your field label names. And, if your application only supports your native tongue and location, then for most cases, you won't have to add entries in the resource bundle. You'll only have to add entries in the resource bundle if you want a special label or if you start to use your application for other languages and/or locations.

Facelets solves a pet peeve of mine about internationalization: writing a lot of special code for something I may never need. Now it's free! Next, let's see what happens if you decide you want to change the field tag so that it generates Booleans, dates, and text components automatically.


Creating custom logic tags

First, you want to add some metaprogramming capabilities to the field.jsp tag. For this, you create Facelets logic tags that tell you if a particular value binding type is a Boolean (isBoolean), some form of text (isText), or a date (isDate). Then you use the tags to render the appropriate component.

Listing 8 shows the usage for these tags (field.xhtml):

Listing 8. field.xhtml
<!--  Initialize the value binding -->
<arc:setValueBinding var="vb" valueBinding="#{entity[fieldName]}"/>

<!--  If the value binding is a string, display an inputText field. -->
<arc:isText id="vb">
    <h:inputText id="#{fieldId}"  value="#{entity[fieldName]}" 
                 required="${required}" styleClass="fieldInput">
          <ui:insert />
    </h:inputText>
</arc:isText>

<!--  If the value binding is a boolean, display a  
      selectBooleanCheckbox field. -->
<arc:isBoolean id="vb">
         <h:selectBooleanCheckbox id="#{fieldId}"
                  value="#{entity[fieldName]}" required="${required}"/>
</arc:isBoolean>

<!--  If the value binding is a date, display a t:inputDate field. -->
<arc:isDate id="vb">
	<t:calendar id="#{fieldId}" renderPopupButtonAsImage="true"
			        renderAsPopup="true" 
                          value="#{entity[fieldName]}" 
                          required="${required}"
			        styleClass="fieldInput" >
                <ui:insert />
      </t:calendar>
	
</arc:isDate>

...

The field.xhtml code in Listing 8 renders an inputText if the value binding is some sort of text. It renders a selectBooleanCheckbox if the value binding type is a Boolean. Finally, it renders a Tomahawk calendar component if it is a date (see "About Tomahawk").

About Tomahawk

To complete this example, I installed Tomahawk support into the application. Tomahawk is an open source JSF component project that is part of MyFaces, which is part of Apache. This required downloading the appropriate JAR file and registering a Tomahawk Facelets taglib, as well as installing JavaScript generation support in the web.xml. See Resources to download and install MyFaces Tomahawk.

Next, you create a value binding to get the type that is being bound to a particular component. The value binding has information about the type of value the component is bound to. If you've used JSF, then you're already familiar with value bindings; for example, #{Employee.firstName} is a value binding tag that likely equates to a string type while the #{Employee.age} tag likely equates to an integer type. In the next section, you learn how to create a value binding tag using Facelets.

Create a value binding tag

There are four steps to creating a tag that retrieves a value binding in Facelets:

  1. Create a class that subclasses the TagHandler.
  2. Register the attributes in the constructor.
  3. Override the apply method.
  4. Register the tag in the JsfCoreLibrary class.

The following sections explain each of the steps to build a value-binding tag and then show you how to use it.

Step 1. Create a class that subclasses TagHandler

You can use a Facelets TagHandler to inject the logic to decide whether components in the body of a tag are added to the component tree. Remember that the endgame of a Facelets template is to create a JSF component tree.

Listing 9 shows how the SetValueBindingHandler tag subclasses TagHandler. The SetValueBindingHandler just defines a variable and puts the variable in the Facelets scope so your logic tags can reuse the variable.

Listing 9. SetValueBindingHandler subclasses TagHandler
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;


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


/**
 * Maps value binding and type so other tags can access it.
 */
public final class SetValueBindingHandler extends TagHandler {

Step 2. Register the attributes in the constructor

When you write Facelets tags, it is important to realize that the attributes are defined by the tag itself and not in an XML file. The constructor of SetValueBindingHandler defines and registers two attributes (var and valueBinding) in the constructor, as shown in Listing 10:

Listing 10. Register the attributes in the constructor
/**
 * Maps value binding and type so other tags can access it.
 */
public final class SetValueBindingHandler extends TagHandler {
    /**   The name of the new variable that this tag defines. */
    private final TagAttribute var;

    /**   The actual value binding expression. */
    private final TagAttribute valueBinding;

    /**
     * Constructor. Set up the attributes for this tag.
     *
     * @param config TagConfig
     */
    public SetValueBindingHandler(final TagConfig config) {
        super(config);
        /* Define var and valueBinding attributes. */
        this.var = this.getRequiredAttribute("var");
        this.valueBinding = this.getRequiredAttribute("valueBinding");
    }

Step 3. Override the apply method

Next, you override the apply method in SetValueBindingHandler. Typically, you override the apply method to programmatically decide if the components defined in the body of the tag are added to the component tree. However, SetValueBindingHandler just defines a variable (whatever var is set to; for example, "vb") to the value binding that was passed. Listing 11 shows the code to override the apply method:

Listing 11. Override the apply method
/**
 * Maps value binding and type so other tags can access it.
 */
public final class SetValueBindingHandler extends TagHandler {
...
...

    /**
     * Apply.
     *
     * @param faceletsContext faceletsContext
     * @param parent parent
     *
     * @throws IOException IOException
     */
    public void apply(final FaceletContext faceletsContext, final UIComponent parent) {
    	/* Create the ValueExpression from the valueBinding attribute. */
        ValueExpression valueExpression =
            this.valueBinding.getValueExpression(faceletsContext, Object.class);

        /* Get the name of the new value. */
        String tvar = this.var.getValue(faceletsContext);
        Class type = valueExpression.getType(faceletsContext);

        /* Put the value binding into the FaceletsContext where
         * we can retrieve it from other components.
         */
        faceletsContext.setAttribute(tvar, valueExpression);

        /* Cache the type so we don't have to look it
         * up in each tag. */
        faceletsContext.setAttribute(tvar + "Type", type);
    }

Listing 11 defines two variables: var and var + "Type". Next, you simply put the variables into the faceletsContext (see my comments in Listing 11 for more details).

Step 4. Register the tag in the JsfCoreLibrary class

You can use the same JsfCoreLibrary for this example as for the previous one. A TagLibrary can contain both EL functions and logic tags. So, for the final step of this exercise, you register the SetValueBindingHandler tag in the JsfCoreLibrary by calling the superclass addTagHandler, as shown in Listing 12:

Listing 12. Register the tag in the JsfCoreLibrary
/**
 * JsfCoreLibrary is an example for IBM developerWorks (c).
 * @author Rick Hightower from ArcMind Inc. http://www.arc-mind.com
 */
public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Namespace used to import this library in Facelets pages  */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

    /**  Current instance of library. */
    public static final JsfCoreLibrary INSTANCE = new JsfCoreLibrary();

    /**
     * Creates a new JsfCoreLibrary object.
     *
     */
    public JsfCoreLibrary() {
        super(NAMESPACE);

        this.addTagHandler("setValueBinding", SetValueBindingHandler.class);
        ...
    }

The nice thing about Facelets is that if you compare developing tags in Facelets to writing equivalent tags in JSP, it takes a lot less XML. You can easily develop many more tags and register them in the JsfCoreLibray. Notice that the name of the tag is setValueBinding.


Using the custom logic tag

Now that you've defined the tag, setValueBinding, you can start using it, as shown in Listing 13:

Listing 13. Using the setValueBinding tag
<html xmlns="http://www.w3.org/1999/xhtml"  
      xmlns:ui="http://java.sun.com/jsf/facelets"  
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:f="http://java.sun.com/jsf/core" 
      xmlns:arc="http://www.arc-mind.com/jsf/core"   
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"  
      xmlns:t="http://myfaces.apache.org/tomahawk">

  THIS TEXT WILL BE REMOVED
  <ui:composition>
  ...
    <!--  Initialize the value binding -->
    <arc:setValueBinding var="vb" 
                         valueBinding="#{entity[fieldName]}" />

You don't have to import anything or set up anything in web.xml. Once you define a tag library (like "arc"), you can add tags and functions to it without updating any XML files. Yes, you can escape XMHell!


Metaprogramming with Facelets

For a last little bit of Facelets fun (for this article), you'll create a tag that allows you to add components defined in the body if the value binding is of a certain type.

The magical tag, IsTypeHandler(), selectively decides if a component gets added to the component tree. The tags calls this.nextHandler.apply(faceletsContext, aParent) if the components defined in the body should be added to the component tree. The isTypeHandler() calls the abstract method isType() to determine if it should call this.nextHandler.apply(faceletsContext, aParent) and add the components defined in the body of tag handler to the component tree. Listing 14 shows the base class for all of this:

Listing 14. Base class for handling types
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

import java.io.IOException;

import javax.faces.component.UIComponent;


/**
 * Is the current field a boolean.
 */
public abstract class IsTypeHandler extends TagHandler {
    /**   */
    private final TagAttribute id;

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsTypeHandler(final TagConfig config) {
        super(config);
        this.id = this.getRequiredAttribute("id");
    }

    /**
     * Is the current field a boolean.
     *
     * @param faceletsContext ctx
     * @param aParent parent
     *
     * @throws IOException IOException
     */
    public void apply(final FaceletContext faceletsContext, final UIComponent aParent)
        throws IOException {
        /* Get the name of the value binding. */
        String tid = this.id.getValue(faceletsContext);
        Class type = 
          (Class) faceletsContext.getVariableMapper().resolveVariable(tid + "Type")
                                                .getValue(faceletsContext);

        /* If the type is a boolean, process the body of the tag.
         */
        if (isType(type)) {
            this.nextHandler.apply(faceletsContext, aParent);
        }
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is the correct type.
     */
    protected abstract boolean isType(Class type);
}

Next, you add a tag per type that you want to handle, as I do in Listing 15:

Listing 15. Handling specific types
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.tag.TagConfig;


/**
 * Is the current field a boolean.
 */
public final class IsBooleanHandler extends IsTypeHandler {

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsBooleanHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is a boolean.
     */
    protected boolean isType(final Class type) {
        /* If the type is a boolean, process the body of the tag.
         */
        if (type == Boolean.class) {
            return true;
        } else if (type.isPrimitive() && "boolean".equals(type.getName())) {
            return true;
        }

        return false;
    }
}

package com.arcmind.jsfquickstart.tags;

import java.util.Date;

import com.sun.facelets.tag.TagConfig;

/**
 * Is the current field a date.
 */
public final class IsDateHandler extends IsTypeHandler {

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsDateHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is a boolean.
     */
    protected boolean isType(final Class type) {
        /* If the type is a string, process the body of the tag.
         */
        if (type == Date.class) {
            return true;
        }
        return false;
    }
}

package com.arcmind.jsfquickstart.tags;

import java.math.BigDecimal;
import java.math.BigInteger;

import com.sun.facelets.tag.TagConfig;

/**
 * Is the current field a string.
 */
public final class IsTextHandler extends IsTypeHandler {

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsTextHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is a boolean.
     */
    protected boolean isType(final Class type) {
        /* If the type is a string, process the body of the tag.
         */
        if (type == String.class
        		|| type == Integer.class
        		|| type == BigDecimal.class
        		|| type == BigInteger.class
        		|| type == Character.class
        		|| type == Long.class
        		|| type == Short.class
        		|| type == Byte.class
        		|| type == Float.class
        		|| type == Double.class
        		|| (type.isPrimitive() && !type.getName().equals("boolean"))
        		) {
            return true;
        } else {
        	return false;
        }
    }
}

Listing 15 just uses the type of the value binding to decide if the components defined in the tag's bodies get added to the component tree. Each tag overrides the isType() method. Currently, I'm using just the type to determine if the body components get added to the tree, but you could easily use annotations or other forms of metadata if you wanted.

Naturally, your task isn't complete until you register all of the above tags with the JsfCoreLibrary, as shown in Listing 16:

Listing 16. Adding the type handling tags to the TagLibrary
public final class JsfCoreLibrary extends AbstractTagLibrary {
   ...
    public JsfCoreLibrary() {
        ...

        this.addTagHandler("isBoolean", IsBooleanHandler.class);
        this.addTagHandler("isText", IsTextHandler.class);
        this.addTagHandler("isDate", IsDateHandler.class);
        ...

Using type-based tags

Now you have three more tags in your taglibrary: isBoolean, isText, and isDate(). You can use these tags in your field tag as shown in Listing 17:

Listing 17. Final field.xhtml
<!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:ui="http://java.sun.com/jsf/facelets" 
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:f="http://java.sun.com/jsf/core" 
      xmlns:arc="http://www.arc-mind.com/jsf/core" 
      xmlns:c="http://java.sun.com/jstl/core"
	xmlns:fn="http://java.sun.com/jsp/jstl/functions"   
      xmlns:t="http://myfaces.apache.org/tomahawk">

	THIS TEXT WILL BE REMOVED
  <ui:composition>

    <!--  The label is optional. Generate it if it is missing. -->
    <c:if test="${empty label}">
		<c:set var="label" 
                   value="${arc:getFieldLabel(fieldName,namespace)}" />
    </c:if>

    <!-- The required attribute is optional, 
         initialize it to true if not found. -->
    <c:if test="${empty required}">
        <c:set var="required" value="true" />
    </c:if>


    <h:panelGroup>
        <h:outputLabel id="${fieldName}Label" 
                       value="${label}" for="#{fieldName}" />
    </h:panelGroup>

    <!--  Initialize the value binding -->
    <arc:setValueBinding var="vb" 
                         valueBinding="#{entity[fieldName]}" />

    <!--  If the value binding is a string, 
          display an inputText field. -->
    <arc:isText id="vb">

        <h:inputText id="#{fieldName}" 
                     value="#{entity[fieldName]}" 
                     required="${required}" styleClass="fieldInput">
            <ui:insert />
        </h:inputText>
    </arc:isText>

    <!--  If the value binding is a boolean, display a
           selectBooleanCheckbox field. -->
    <arc:isBoolean id="vb">
        <h:selectBooleanCheckbox  id="#{fieldName}" 
                                  value="#{entity[fieldName]}" 
                                  required="${required}" />
    </arc:isBoolean>

    <!--  If the value binding is a date, 
          display a t:inputDate field. -->
    <arc:isDate id="vb">
        <t:calendar id="#{fieldName}" renderPopupButtonAsImage="true" 
                   renderAsPopup="true" value="#{entity[fieldName]}" 
                   required="${required}" styleClass="fieldInput">
              <ui:insert />
        </t:calendar>
    </arc:isDate>

    <!--  Display any error message that are found -->
    <h:message id="${fieldName}Message" 
               style="color: red; text-decoration: overline" 
               for="#{fieldName}" />

</ui:composition>
	THIS TEXT WILL BE REMOVED AS WELL

</html>

Your field tag now displays an inputText field if the value binding is of type Integer, String, BigDecimal, Character, Long, Short, Byte, Float, Double, short, int, char, etc. The field tag displays a check box if the value binding type is a boolean or a Boolean. And it displays a Tomahawk calendar component if the value binding is of type java.util.Date -- which, frankly, is very cool.


In conclusion

In this article, I've shown you some fun and fairly powerful applications of Facelets. The Facelets EL function takes a lot of the pain out of internationalization, and custom logic tags allow you to extend Facelets' built-in logic tags. Along the way, you've seen how easy it was to extend the simple examples from my previous article with more advanced functionality. I showed you how to update the field composition component to work with dates (Tomahawk calendar), Booleans (check boxes), and all manner of Java types that should be displayed as text. I also introduced you to reasonable defaults, which I used to internationalize the label for the field component. Keep an eye out for more fun with Facelets in the coming months!


Download

DescriptionNameSize
Source codej-facelets2.zip12KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=110976
ArticleTitle=Advanced Facelets programming
publish-date=05092006