The information in this blog post has been superseded. Please see Safely manipulating the component tree with JSF 2, revisitedThe ProblemA common problem encountered by writers of dynamic JSF 1.x components (like
Metawidget) was
"at what point in the lifecycle can I safely manipulate the component tree"?
For a page's initital GET request, there weren't many lifecycle methods you could hook into - you basically had
encodeBegin. For subsequent POST-backs there were a slew of hooks, including
decode,
processValidators and
processUpdates, but none were at exactly the right place in the lifecycle to do safe component tree manipulation. What would be 'exactly the right place'? Namely:
- the component tree should be fully realized (ie. all parents and children should be set)
- the component tree should not have been serialized yet
Because there was no 'official' place, writers of dynamic JSF 1.x components had to resort to all kinds of tricks. As
Ken Paulson wrote "...people use constructors, bindings, action methods... people create their own renderer which dynamically adds children each request". To which
Jacob Hookum opined "what's actually needed is post component tree creation hooks, providing the ability to then modify the component tree".
The SaviourEnter JSF 2. From the spec
"System Events are introduced in version 2 of the specification and represent specific points in time for a JSF application". They are like the old PhaseEvents, but much more fine-grained. For the purposes of this blog, the SystemEvent we are interested in is PostAddToViewEvent.
To use it, first
subscribe to the event in your UIComponent's constructor...
public class UITestComponent
extends UIComponentBase
implements SystemEventListener {
public UITestComponent() {
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.getViewRoot().subscribeToViewEvent(PostAddToViewEvent.class, this);
}
...then, when it gets fired, you can safely manipulate the component tree ...
public void processEvent( SystemEvent event ) throws AbortProcessingException {
FacesContext ctx = FacesContext.getCurrentInstance();
UIComponent dynamicallyGenerated = ctx.getApplication().createComponent( "javax.faces.HtmlInputText" );
getChildren().add( dynamicallyGenerated );
}
The GotchasWhile working through this solution, there were a number of gotchas I encountered. Huge thanks to
Ryan Lubke for holding my hand through them:
First, you should use
subscribeToViewEvent, not
subscribeToEvent. The latter will mean the event subscription itself get serialized into the component tree, so you'll keep doubling up subscribers every time the page refreshes.
Second, you must hook into
UIViewRoot's PostAddToViewEvent, not UITestComponent's. This is because, at the time UITestComponent's PostAddToViewEvent is fired, its children will not be initialized yet.
Third, you cannot use the
@ListenerFor annotation to hook into SystemEvents from a UIComponent. This is because UIComponent implements ComponentSystemEventListener, and
@ListenerFor is designed to ignore the SystemEventListener interface for any class that implements ComponentSystemEventListener.
The UglyThere is, unfortunately, still a problem for certain types of dynamic component.
If your component, in its
processEvent method, dynamically creates not just simple HtmlInputText components but nested components that are themselves dynamic, we have a problem. This is because the 'official' way to safely manipulate the component tree is through SystemEvents, but
there is no official way to safely manipulate the SystemEvent list.
In the current release of the
JSF Reference Implementation (2.0.1), attempting to dynamically create a component that in turn tries to subscribe to a SystemEvent will get you a ConcurrentModificationException. In future releases, there will not be an exception but the newly added SystemEvent will simply not fire, so your newly added dynamic component will never get an opportunity to populate itself.
This new issue is
being tracked by the JSF RI team. Until it is resolved, JSF 2
can safely create dynamic components (a big step forward), but it
cannot safely create dynamic components that themselves create dynamic components.
Suggestions welcome!