Composite Input Components in JSF

Composite components are a great feature of JSF 2.0. The canonical example is a login component with fields for the username and password:

<mylib:login name="#{user.name}"
      password="#{user.password}"
      loginAction="#{user.login}"/>

This has been well explained elsewhere. But here is what always baffled me. I want to have a composite date component, with three menus for day, month, and year.

But I want it to have a single value of type java.util.Date, so I can use it like this:

<mylib:date value="#{user.hireDate}"/>

and not

<mylib:date day="#{user.hireDay}" month="#{user.hireMonth}" year="#{user.hireYear}"/>

Why do I care?

I asked around and people told me that this couldn't be done with composite components—I'd have to write an actual custom component.

But, as I discovered, it is not so. With a small dose of knowledge of the JSF lifecycle, and the poorly documented technique of backing components, this is actually pretty easy. Here goes.

When you make a composite component, it is normally turned into a UINamingContainer that contains the child components in the implementation. But you can also force a different component to be used, provided

The easiest way of using your own component is to make a class whose name is libraryName.compositeComponentName, such as mylib.date. (It's a bit weird to have a lowercase class name, but that's the price to pay for “convention over configuration”.)

package mylib;
public class date extends UIInput implements NamingContainer { 
    public String getFamily() { return "javax.faces.NamingContainer"; }
    ...
}

Note that I extend UIInput and not UINamingContainer. In the world of JSF, UIInput is an “editable value holder”, a class that holds a value of an arbitrary type (not necessarily numbers or strings), and to which you can attach validators.

The JSF lifecycle starts out like this:

For a composite component, the submitted value is a combination of the submitted values of the children. You could combine them by putting them into a map, but I simply say that the submitted value is the composite component:

public class date extends UIInput implements NamingContainer { 
    ...
    public Object getSubmittedValue() { return this; }
    ...
}

(If you don't override this method, the submitted value is null, and that gets into a murky corner of processing that you want to avoid.)

The conversion from a bunch of values to a date happens in getConvertedValue:

public class date extends UIInput implements NamingContainer { 
    ...
    protected Object getConvertedValue(FacesContext context, Object newSubmittedValue) {
        UIInput dayComponent = (UIInput) findComponent("day");
        UIInput monthComponent = (UIInput) findComponent("month");
        UIInput yearComponent = (UIInput) findComponent("year");
        int day = (Integer) dayComponent.getValue();
        int month = (Integer) monthComponent.getValue();
        int year = (Integer) yearComponent.getValue();
        if (isValidDate(day, month, year)) // helper method that checks for month lengths, leap years
           return new Date(year - 1900, month - 1, day);
        else 
           throw new ConverterException(new FacesMessage(...));
    }
    ...
}

This is very similar to the usual conversion action, except that I combine the values from multiple child components. (I attached a javax.faces.Integer converter to each of the children so I don't have to convert the submitted strings to integers myself.)

That takes care of processing the input. On the rendering side, I just populate the children before rendering them:

public class date extends UIInput implements NamingContainer { 
    ...
    public void encodeBegin(FacesContext context) throws IOException {
        Date date = (Date) getValue();
        UIInput dayComponent = (UIInput) findComponent("day");
        UIInput monthComponent = (UIInput) findComponent("month");
        UIInput yearComponent = (UIInput) findComponent("year");
        dayComponent.setValue(date.getDate());
        monthComponent.setValue(date.getMonth() + 1);
        yearComponent.setValue(date.getYear() + 1900);
        super.encodeBegin(context);
    }
}

That's all. The same recipe works for any composite component that collects input for a complex data type.

Here is the code of a sample application that works out of the box in GlassFish 3 (but not in Tomcat). Note that the sample application uses the composite component as an input for java.util.Date. It works with bean validation without any effort on the developer's part.

The moral of this is: