Home

Advertisement

Java Server Faces UISelect Components

The Java Server Faces (JSF) Reference Implementation (RI) ships with pretty spartan implementations of list components. These include the select, checkbox, and radio button components created by the following tags:


  • selectOneListbox

  • selectOneMenu

  • selectOneRadio

  • selectManyCheckbox

  • selectManyListbox

  • selectManyMenu



These components are special in that they allow a user to choose one or more values from a list of pre-defined options. These options are typically represented with nested selectItem, selectItems, and selectItemGroup tags. While seemingly straightforward, these tags are very finicky and often cause failures in the Process Validations phase that prevent action methods from being called.

Component and Value Binding


The selectOne and selectMany tags can either be bound to a value on the backing bean (via the value attribute) or to a UISelect component (via the binding component). If bound to a backing bean value, the value must be a List or an array. Sets are not supported.

A nested selectItems tag can only be bound to a value; they cannot be bound to a component. The value they are bound to must be a List of SelectItem or SelectGroup objects; or an array of SelectItems or SelectGroups. Sets are not supported.

A selectItem component is not bound to a single value. Instead, the itemLabel and itemValue attributes are specified using either string literals or Expression Language (EL) expressions to refer to bean variables. Note that if selectItem tags are chosen over a selectItems tag then the value type is limited to String.

SelectItem Values


SelectItems work best with String values. However, a SelectItem value can be any type, provided:


  • There is a Converter for the type

  • The selectItems tag is used and bound to a List or Array of SelectItem or SelectItemGroup objects



I have had very little luck with either of the following scenarios:


  • Using nested selectItem tags with string literals and an explicit converter attribute on the enclosing selectOne or selectMany tag

  • Using nested selectItem tags with string literals and an explicit converter tag

  • Using nested selectItem tags with EL expressions refering to object values in a backing bean

  • Using converters registered by ID



Validation


The selectOne and selectMany tags are unique in that they have an implicit validation step that occurs in the Process Validations phase: the value specified in the request must be contained in the list (or array) of SelectItem objects that is associated with the selectOne or selectMany tag. If the supplied request value is not, then a FacesMessage is queued on the componend with the summary text "Validation Error: Value is not valid" and the Process Validations phase jumps directly to the Render Response phase, bypassing the Invoke Application Phase.

This can be very tricky, especially if you are using a custom converter, because the validation logic uses the equals( ) method of the object on each element of the List (or array) of SelectItem objects to determine whether the object is contained in the collection. If the custom converter returns a new object instance, and the object instance does not override the equals( ) method, then this validation will always fail. The default implementation of equals( ) checks only for instance equality; two different instances of the same class, while logically equal, will not be considered equal unless there is an overridden equals( ) method.

Example: Selectable Domain Objects


Suppose there is a table called LIST_ITEM with the columns ID and LABEL. Through some ORM magic the values in this table can be read into a List of ListItem objects with the properties "id" and "label". We would like to render a drop-down list of SelectItems whose values are the ListItem IDs and whose labels are the ListItem labels. To do so, we have written a ListItemConverter and have registered it to the class ListItem. The getAsObject( ) method of the converter queries queries the database for a LIST_ITEM row with an ID that is equal to the supplied value. A new ListItem is created and populated with the values from the ResultSet, and the ListItem is returned from the Converter.

In this approach, each call to getAsObject produces a new instance. Therefore it is important to override the equals( ) method of ListItem like so:

public boolean equals( Object o )
{
    if( this == o ) return true;
    if( o == null ) return false;
    if( !(o instanceof ListItem) ) return false;
    ListItem that = (ListItem)o;
    return getID( ).equals( that.getId( ) ); // getLabel( ) could be used if unique
}


This will guarantee that the ListItem created by getAsObject will appear in the collection of SelectItems.

Fail-proof Approach


I have used the following design extensively and have had very good success with it:

  • Ensure there is a Converter implementation for the desired type registered by class

  • Bind the selectOne or selectMany tag to a List or object array of the desired type

  • Use a nested selectItems tag bound to a bean property that returns a List of SelectItems

  • In the backing bean property, set the SelectItem values to be objects of the desired type. Do not convert to a String. Set the label to be whatever String value is desired.

  • In the Class specification for the desired type, override the equals method to determine equality based on a unique key, preferrably a secondary key (business key).



Complete Example



ListItem.java
public class ListItem
{
    private long _id = -1;
    private String _label = null;

    public void setId( long id )
    {
        _id = id;
    }
    public long getId( )
    {
        return _id;
    }

    public void setLabel( String label )
    {
        _label = label;
    }
    public String getLabel( )
    {
        return _label;
    }

    public boolean equals( Object o )
    {
        if( this == o ) return true;
        if( o == null ) return false;
        if( !(o instanceof ListItem) ) return false;
        ListItem that = (ListItem) that;
        return getId( ) == that.getId( );
    }
}


ListItemConverter.java
public class ListItemConverter
implements Converter
{
    public Object getAsObject( FacesContext context, UIComponent component, String string )
    throws ConverterException
    {
        return new ListItemDao( ).loadById( Integer.parseInt( string ) );
    }

    public String getAsString( FacesContext context, UIComponent component, Object object )
    {
        return String.valueOf( ((ListItem)object).getId( ) );
    } 
}


BackingBean.java
public class BackingBean
{
    private ListItem[] _selectedListItems = null;

    public List getListItemSelectItems( )
    {
        List listItemSelectItems = new List( );
        List listItems = new ListItemDao( ).loadAll( );
        for( Iterator liIter = listItems.iterator( ); liIter.hasNext( ); )
        {
            ListItem li = (ListItem)liIter.next( );
            listItemSelectItems.add( new SelectItem( li, li.getLabeL( ) );
        } 
        return listItemSelectItems;
    }

    public void setSelectedListItems( ListItem[] selectedListItems )
    {
        _selectedListItems = selectedListItems;
    }
    public ListItem[] getSelectedListItems( )
    {
        return _selectedListItems;
    }
}


faces-config.xml
...
<converter>
    <converter-for-class>ListItem</converter-for-class>
    <converter-class>ListItemConverter</converter-class>
</converter>
...
<managed-bean>
    <managed-bean-name>backingBean</managed-bean-name>
    <managed-bean-class>BackingBean</managed-bean-class>
</managed-bean>
...


example.jsf
...
<h:selectOneMany value="#{backingBean.selectedListItems}">
    <f:selectItems value="#{backingBean.listItemSelectItems}"/>
</h:selectOneMany>
...
Tags:

Comments

(Anonymous)

SelectOneMenu details

hi,
thanks a tonne for this document. i was killing myself over the Validation error from a SelectOneMenu. The value was mapped to a long variable in my form and the SelectItems properties are Strings. Hence it was always giving the validation error. after i read your document i found the issue and it works fine.

thx again

ashok madhavan

(Anonymous)

Perfect!

This is by far the best explanation of the use of converters with these tags. Though I did discover some of this via rather painful trial and error, I wish I had come across this page earlier.

I'm sure the JSF user community will appreciate this contribution.

Thanks!

(Anonymous)

Thank you very much

As said by others, your explanation is the best, what I can find (after 5 days!!). Thank you very very very very very much (for each day spent :D )

(Anonymous)

Thank you for all the trouble

I tried for many hours to get this nut cracked and most articles on the web weren't much of a help. What I love most you say clearly which approaches don't work even if one should expect otherwise.
On the usages of lists, items and the conversions I think the JSF people should improve their toy rapidly. As well as on the question of supporting full-fledged methods in the expression language for value tags.

Thanks again. I know what you have been through to provide us with your knowledge.

(Anonymous)

Re: Thank you for all the trouble

I wish I had found this earlier. It's taken me all day and only after reading your article did I realise that f:selectItem will create String values, not Integer, so you can't use anything but Strings in your backing bean values, unless you create an arraylist of SelectItems

The answer is so obvious I'm ashamed it took me all day and I still only found it after you explained it here!

(Anonymous)

setting default selection for SelectOneRdio

For setting default selection for selectoneradio, I am setting the value attribute.
I am setting yesno in the constructor of the bean. Still I am not able to see the default selection on page startup. Please help

(Anonymous)

Re: setting default selection for SelectOneRdio

view.jsf has the following code..
[Error: Irreparable invalid markup ('<h:selectoneradio [...] faces-config.xml,>') in entry. Owner must fix manually. Raw contents below.]

view.jsf has the following code..
<h:selectOneRadio value="backingbean.radiobutton-value"...
...

In faces-config.xml,
<managed-bean>
...
<managed-property>
<property-name>radiobutton-value</property-name>
<value>male</value>
</managed-property>
</managed-bean>

(Anonymous)

thank you

i'm curious to see your solution working, just implementing it now, looks straight and handy, thank you very much.

btw: the complete example contains a little mistake:
"ListItem that = (ListItem) that;"

cheers
gun

(Anonymous)

Idetrorce

very interesting, but I don't agree with you
Idetrorce

(Anonymous)

Thanks!

Spent a lot of time googling and experimenting.
This post was a problem killer, just wish I had found it earlier!

(Anonymous)

Snx for you job!

Snx for you job!
It has very much helped me!

(Anonymous)

selectManyMenu how to

ok it works fine for
selectOneMenu but how do you do it for a
selectManyMenu

If anybody knows the answer you can send me an email to
icapidees@yahoo.fr

Thanks in advance,
icap

(Anonymous)

Hello

I'm new here, just wanted to say hello and introduce myself.

(Anonymous)

Great way of thinking

Excellent thinking greetings to you

I think you may also give your code more brilliance in the BackingBean class if you tried tomahawk and

anyway in my opinion that is a great way of thinking

(Anonymous)

You just saved me keester

Seriously. I've been trying to use a business model object as the itemValue for a selectitem for days now, just baffling myself, and this helped me out so much. I've not got it working with a converter. thanks!

(Anonymous)

related?

Jesse, I am looking for my nephew, and think it might be you. Please email me at wearecelts@aol.com Fran

January 2007

S M T W T F S
 123456
78910111213
14151617181920
21222324252627
28293031   
Powered by LiveJournal.com

Advertisement

Customize