header image
Home arrow Computing arrow Software Development arrow Writing Custom Components in JSF + Facelets
Writing Custom Components in JSF + Facelets Print E-mail

One of the misleading parts for me was that available articles treating "custom JSF component creation" around are targetting JSF components used within JSP pages. If you're using Facelets (a JSP independent view-handler for JSF - see http://facelets.dev.java.net) things are slightly different. At least it seems to be easier to create custom components with Facelets than doing it with JSP.

Hopefully to provide some help, I'll shortly describe the steps necessary for me to build my first
custom JSF component within a Facelets web-app:

1. Tell Facelets to load your custom taglibrary. Add something like this to your web.xml file:  

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

The xml file above is treated as tag-library for the facelets framework similar as we know it from JSP (the tld files). You can add all your custom tags/components into this file. Basically it should look like this:

2. Create a facelets-taglib for your custom tags:

<?xml version="1.0" encoding="UTF-8"?>
<!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://org.zapto.hanzz/mytags</namespace>
    <tag>
        <tag-name>rdolink</tag-name>
        <component>
            <component-type>hanzz.RdoLinkComponent</component-type>
        </component>
    </tag>
    <tag>
        <tag-name>rdocoll</tag-name>
        <component>
            <component-type>hanzz.RdoCollComponent</component-type>
        </component>
    </tag>
</facelet-taglib>

3. Publish your custom components to JSF in faces-config.xml. Add something like this:

    <component>
        <component-type>hanzz.RdoLinkComponent</component-type>
        <component-class>org.zapto.hanzz.jsf.RdoLinkComponent</component-class>
    </component>
     <component>
        <component-type>hanzz.RdoCollComponent</component-type>
        <component-class>org.zapto.hanzz.jsf.RdoCollComponent</component-class>
    </component>
    
As you can see - the <component-type> element in faces-config.xml maps the facelet-tag's <component-type> element as visible above. This is the way how facelets can map a custom tag within a XML file to a JSF component and JSF can map the component type to a specific implementation - the Java Class.

4. Write the corresponding Java Class(es).
This is where it gets specific: Depending on the requirements of your custom component the class should be derived from a JSF base component. In my example I had to write a selectOneRadio that was able to render the radio-buttons label with a http-link to a descriptive text. Therefore my custom component was derived from UISelectOne:

public class RdoLinkComponent extends UISelectOne {
    /** Creates a new instance of RdoLinkComponent */
    public RdoLinkComponent() {
        super();
    }
    public void encodeBegin(FacesContext context) throws IOException {
        final ResponseWriter writer = context.getResponseWriter();
        final String clientId = getClientId(context);
        
        RdoCollComponent childColl = null;
        for (UIComponent comp : getChildren()) {
            if(comp instanceof RdoCollComponent) {
                childColl = (RdoCollComponent) comp;
            }
        }      
        if((childColl==null)||(childColl.getValue()==null)) {
            writer.writeText("Keine Produkte verfügbar.",null);
        } else {
            Collection<Product> prods = (Collection<Product>)childColl.getValue();
            for(Product prod:prods) {
                encodeRadio(writer,prod,clientId);  
            }
        }
    }
    
    private void encodeRadio(ResponseWriter writer, PlcProduct prod, String clientId) throws IOException {
        writer.startElement("input",null);
        writer.writeAttribute("type","radio",null);
        writer.writeAttribute("name", clientId, "clientId");
        writer.writeAttribute("value", prod.getKey(), "value");
        writer.endElement("input");
        writer.startElement("a",null);
        writer.writeAttribute("href",prod.getDescUrl(),null);
        writer.writeAttribute("target","_blank",null);
        writer.writeText(prod.getLabel(),null);        
        writer.endElement("a");
    }

    public void encodeEnd(FacesContext context) throws IOException {
    }

    public void decode(FacesContext context) {
        Map requestMap = context.getExternalContext().getRequestParameterMap();
        final String clientId = getClientId(context);

        String string_submit_val = ((String) requestMap.get(clientId));
        setSubmittedValue(string_submit_val);
        setValid(true);  
    }
    
    public void encodeChildren(FacesContext context) throws IOException {
    }
    protected void validateValue(FacesContext context, Object value) {
        setValid(true);
    }

The encodeXXX-methods are used to render the component into HTML, the decode method is used to read the components selected value of the submitted form. I won't explain that in more detail here - look at the methods in the example above, it should be straightforward to understand. For more information about encoding and decoding refer to the JSF docs.

You can see that I've used a child component to assign the radio-groups select items but the
rendering of the html- input tags is done right here in the parent class, so I created only a dummy implementation for the child RdoColl-Component:

public class RdoCollComponent extends UIOutput {
    /** Creates a new instance of RdoCollComponent */
    public RdoCollComponent() {
    }
    public void encodeBegin(FacesContext context) throws IOException {   
    }
    public void encodeEnd(FacesContext context) throws IOException {
    }
    public void encodeChildren(FacesContext context) throws IOException {
    }
    public void decode(FacesContext context) {
    }
}

The above RdoColl-Component is used to "hold" the collection of possible options (i.e. the SelectItems) for the RdoLink-Component above. Implementing all of the above empty methods ensures that the child component does not produce any HTML output. I'm not sure if this is the state-of-the-art way to do this, basically it works fine in this case. I could not use the standard f:selectItems here because more than only a label and a value attribute is needed -  the URL for the link to the description of the product.

5. Now we have written our custom components and publicized them to facelets/jsf, we simply can add it on the required .xhtml page like this:

- add the namespace as defined in your facelet taglib and a prefix:
 xmlns:hanzz="http://org.zapto.hanzz/mytags"
 
- use your tags:
 <hanzz:rdolink value="#{orderBean.selectedProduct}"   >
       <hanzz:rdocoll value="#{orderBean.availableProducts}"/>
 </hanzz:rdolink>



Last Updated ( Mar 20, 2007 at 03:38 PM )
Zwei Dinge sind unendlich:
Das Universum
und die menschliche Dummheit.
Aber bei dem Universum
bin ich mir noch nicht ganz sicher.

Albert Einstein