Wednesday, April 23, 2008

JSF ValueChangeListeners

This is yet another post about JSF value change listeners and how they don't work as expected, intuitively, or logically. As I thought about it I couldn't come up with a real-life scenario when their behaviour would be justified or would make sense.

JSF Value Change Events are called after the Apply Request Values phase which updates the component tree with the request values from the client. For the framework developers (I assume) this is a logical place for it in the lifecycle because it's easy to know what values have changed.

Because they allow you to trigger logic that depends on changed values, the value change events are often used to update something in the model based on the change. For example, suppose you have a page with a country and state/province drop downs and you want to update the list of states/provinces when the user picks a country. So you've bound the values in the drop downs to some backing bean fields, and the selectItems to Lists in backing beans, and then attach a value change listener to the country drop down. In the listener method you put some logic to update the backing bean List of states/provinces. Now you expect the updated list to show up when the page is redisplayed.

But wait. Your listener method will get called after the Apply Request Values phase, at which point the component tree will have the new value but the backing bean still
won't. You can still get the new value out of the event (by calling event.getNewValue()), but that's not the worst of it. If you update the backing bean's list of states/provinces now it will still be overwritten when the lifecycle moves on to Update Model Values phase.

There are plenty of hacks to this problem, and none of them particularly graceful. They include: binding the component you want to update and setting it's submittedValue() - this can cause validation exceptions for combo boxes, and other problems for inputTexts; updating temporary variables that aren't value-bound and then updating the actual binding in other application logic; forwarding the lifecycle to the Render Response phase - not a good idea to circumvent the lifecycle, especially if using ICEfaces; using a phase listener and queuing functions to call at each phase; and, finally, moving the listener method to the appropriate phase. The last solution is the cleanest one I found so far (see References for source). I won't elaborate on the others - they are generally confusing and cause a trail of hacks to patch the holes created by them.

The solution is simple: you want the listener method to be called after the model values are updated? - so move the event to the Update Model Values phase. The value change event allows you to change its PhaseId to whatever you want and re-queue it to be called again. You can check for when the phase is reached and do your application logic using the backing bean values:

public void countryChangedVCE(ValueChangeEvent vce) {
else if(vce.getPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES)){
//Your application logic here:
//get the backing bean and update the state/province list based on the
//country value

Value Change Listeners - What you need to know

No comments: