The Problem
A 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
The Saviour
Enter 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);
}
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 );
}
FacesContext ctx = FacesContext.getCurrentInstance();
UIComponent dynamicallyGenerated = ctx.getApplication().createComponent( "javax.faces.HtmlInputText" );
getChildren().add( dynamicallyGenerated );
}
The Gotchas
While 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 Ugly
There 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!
9 comments:
Hi Richard,
Great writeup.
I had anticipated the use for these events to be more focused on application developers (vs. component developers), but it's great to see the interesting ways they can be applied. If you are writing a custom component, you can often do the dynamic stuff you're describing during component creation. Although what's really missing from the spec is a real "factory" (see JSFT's factories) vs. the "binding" gimmick.
Application developers often want to do interesting things to the components after they've been created, for which this event should work very well.
One thing I'm curious about is how well JSF2's new stream-lined state-saving can keep up with extremely dynamic views. Have you done any testing to see what happens if the data driving your dynamic component creation changes from 1 request to the next? If it fails, there's a flag to revert back to the full state saving that was done in JSF 1.2 and prior.
Ken Paulsen
Ken,
Thanks for your comment.
Can you please elaborate on 'you can often do the dynamic stuff you're describing during component creation'? Do you mean in the constructor? Or following setParent? Or somewhere else? I'm looking for a reliable point in the lifecycle where getChildren() is fully realized.
Regards,
Richard.
We have been investigating the use of MetaWidget in a project we are starting. From your blog post I get mixed messages about the current stability of MetaWidget with JSF 2.0. Is this combination of technologies something that is advisable at this time?
-Justin
Thank you for your interest in Metawidget.
JSF 2.0 support is a top priority for Metawidget v1.0 (I need it for my clients too!).
As of v0.99, JSF 2.0 works but only with 'partial state saving' (a feature new to 2.0) turned off. I am working with the Mojarra guys to get a couple of bugs in Mojarra 2.0.3 resolved so that Metawidget can be solid on JSF 2.0.
Regards,
Richard.
As a workaround, you can add children components in the method setParent, but you have to add one more condition:
public void setParent (UIComponent parent) {
super.setParent (parent);
if (parent! = null & & getChildren (). IsEmpty ()) {
//Generate children
}
}
It seems that everything is working properly and I have not noticed any problems. Obviously this is not too elegant, but works with mojarra 2.0.3.
That may work for adding new children, but I don't think you'll be able to interrogate, reorder or delete existing children that way -because the component's children haven't been populated by that stage?
Still, I'm glad you've found something that'll work for your use-case.
Do you know any other workaround that allows nested components are generated, which works in JSF 2.0? Do the only what is left is to wait for majarra 2.0.4?
I think so. Please see...
http://kennardconsulting.blogspot.com/2010/10/safely-manipulating-component-tree-with.html
...and the 'Acid Test' code attached.
Richard.
Thanks for the hint with setParent, worked finally!
Post a Comment