Thursday, August 02, 2007

A sad story about UIInput

Some questions are asked much more often than others. Let me tell you the story about one particular detail of JSF component life cycle. The reason why I decided that is is deserve to be in my blog is simple – I've being asked so many times about it, that it become ridiculous.

If you have no relation to Java Server Faces at all, than perhaps, you better skip entire story. But if you do have some JSF knowledge, you are welcome to continue!

So let me tell the story.

Imagine the situation when you have a page with some input. It can be any variation of input – text input, select or check-box, it is irrelevant. Let say it is plain text input and you need do the following:

  1. User can enter value to the input and it will be processed as usual.

  2. In addition to that user can select some pre-defined value. Or in any other way to assign some particular value to the input and than, be able to see this new value on screen. After this user can press "enter" or by any other mean trigger normal processing.

It looks very simple, but when you dig a bit further, you will discover more nasty details.

If you want to skip directly to the working example – here is the link.

A simple solution is obvious – you can create action that will update model value. The only complication is to put immediate="true" so you action will be fired regardless of validation errors. You expect that after your action fires, the input will display new value. Nope!

The problem is the UIInput has complex internal structure. It keep internal values for the reason to preserve user input if there are some validation errors. For example if you have a credit card number that allow only numeric inputs, and you type "abc", than you expect to see some polite error message (like "invalid input, you stupid bastard!") - and your "abc" stay intact. The picture even more complex, because we have two internal values – "submitted" that hold exact user input and "local" that holds already converted and validated value before it will be pushed to the model. So, whenever component see non-null submitted value during rendering – it will use it; otherwise if there is non-null local value – it will use it; finally if no local or submitted values exists – component will use value from the model (it will evaluate value binding expression). That's it!

Hopefully now you can see the solution for our little nasty problem: all you need to do is to make sure that no submitted value exists during rendering.

The most obvious way to achieve that – just to reset submitted value in your action code. The other possible approach – to use Richfaces component a4j:region that can limit set of processed submitted values. Both cases are demonstrated here.

My best wishes!