Thursday, April 9, 2009

JSF Event Re-Phasing

JSF has a standard mechanism for controling the lifecycle phase that certain events are broadcast in. For ActionSource implementors, such as UICommand, and the ActionEvent(s) they create and queue, and EditableValueHolder implementors, such as UIInput, and the ValueChangeEvent(s) they create and queue, they use their immediate property to determine the phase to be broadcast in. For ActionSource, when immediate="false" (default), its ActionEvent uses the INVOKE_APPLICATION phase, and when immediate="true", it uses the APPLY_REQUEST_VALUES phase. For EditableValueHolder, when immediate="false" (default), its ValueChangeEvent uses the PROCESS_VALIDATIONS phase, and when immediate="true", it uses the APPLY_REQUEST_VALUES phase.

This primarily allows for Cancel buttons. In a form with input components that have validation settings, command components can be set to have
immediate="true", so that they may bypass validation, and accomplish a non-form task, such as navigating to another page. In some cases, input components may wish to convey their information to the server when a Cancel button is clicked. As so applications may set immediate="true" on input components as well.

But how does one make a ValueChangeEvent be broadcast in INVOKE_APPLICATION phase, so that the valueChangeListener will be fired after UPDATE_MODEL phase, when the input values are now all actually in the bean? One needs a way to make that event be broadcast later than there is an API to specify. In practice, applications use event re-queuing. In the valueChangeListener, the event phase is examined, and set to the later phase, and then the event is re-queued, and the listener returns. When it is invoked again, in the properly desired phase, then the real logic is executed. It works, but is tedious to do in every listener. But most of all, it's non-declarative. When reading a view definition page, one can look for immediate and know which phase the event will broadcast in. But one has to know to look in the bean code to know if re-queuing is happening. It would be more ideal if there was a component or a tag that would accomplish this.

In steps the ice:setEventPhase component. It can take any event type, as specified by class name, not just the standard JSF events, and change them to any phase. It operates on the events of child components, that is any component that is within the ice:setEventPhase component.

It works because when a component queues an event, the event bubbles up through its ancestors until it reaches the UIViewRoot. This allows containers like dataTable to wrap events inside other event objects which contain the row index. When child component events bubble up through the ice:setEventPhase, it then modifies their broadcast phase. Quite straightforward.

The prototypical use case is when one input component changes the value(s) of other input component(s) in its valueChangeListener. What goes wrong is that the ValueChangeEvent is broadcast either in APPLY_REQUEST_VALUES or PROCESS_VALIDATIONS phases. Both of which happen before UPDATE_MODEL phase. So when the one component's valueChangeListener modifies the bean values of the other input components, those input components later get their bean values overwritten by their submitted values, which are basically their old values. With ice:setEventPhase, you can change the ValueChangeEvent to broadcast in INVOKE_APPLICATION phase, so that the valueChangeListener gets the last say in the bean property values.


http://wiki.icefaces.org/display/ICE/ICE+Components+Reference
http://res.icesoft.org/docs/latest/tld/ice/setEventPhase.html