Monday, October 25, 2010

Safely manipulating the component tree with JSF 2, revisited

I've been working with both the Apache MyFaces and the Oracle Mojarra teams on improving JSF 2 support in Metawidget.

Metawidget is a more dynamic component than most, and exercises JSF 2 in a way few component libraries do. In particular, it stresses the relationship between dynamically modifying the component tree, partial state saving (new in JSF 2), and firing nested SystemEvents (new in JSF 2). As such, all 3 teams (MyFaces, Mojarra, Metawidget) have uncovered bugs in our implementations.

I'm delighted to say it looks like all these will be resolved in time for MyFaces 2.0.3 and Mojarra 2.2. For those interested in the 'correct' implementation of a dynamic JSF 2 component, as agreed by all teams, I've put together a little Acid Test that tests your JSF implementation for full compliance. In the process, it demonstrates the 'right' way to implement a dynamic JSF 2 component using SystemEvents. Which is:

public class UIAddComponent
   extends UIComponentBase implements SystemEventListener {

   public UIAddComponent() {

      FacesContext context = FacesContext.getCurrentInstance();
      UIViewRoot root = context.getViewRoot();

      root.subscribeToViewEvent( PreRenderViewEvent.class, this );
   }

   public boolean isListenerForSource( Object source ) {

      return ( source instanceof UIViewRoot );
   }

   public void processEvent( SystemEvent event )
      throws AbortProcessingException {

      if ( !FacesContext.getCurrentInstance().isValidationFailed() ) {
      
         // Safely manipulate component tree here
      }
   }
}

My thanks to all teams for working so hard on this issue!

UPDATE 1: looks like this approach may be making it into the JSF spec: http://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1007

UPDATE 2: for AJAX requests, you also need to test partialViewContext.isAjaxRequest (see comments below)

17 comments:

lior said...

Hi Richard,

I am trying dynamically put components into the component tree. A figured using UIStableComponent from your Acid Test could be a good starting point. The problem is, I need to get an attribute of the component's tag: . I tried using getAttributes and this.getValueExpression but neither seems to find the attribute.

Richard said...

Lion,

Could you send some code to support@metawidget.org? Failing that, some pointers:

1. Are you using JSP (not Facelets)? In which case the 'tag' will be in a separate class altogether (one that extends javax.faces.webapp.UIComponentTag) so you need to look for your attribute there

2. If your component has a getter/setter for the attribute you are specifying, it will end up there.

Hope that helps,

Richard.

lior said...

Hi Richard,

Using JSF2 (Mojarra 2.1), I've played around with your example and managed to bind a bean to the value attribute of an input field created in code. So far so good.

However, I run in to problems once I try to use this in a more complex scenario. I have a list of beans, each representing a complex property that should render itself into a form. I found that forEach is to early (for the pattern you suggest) and repeat is to late for binding to the values.

Could you elaborate on how to put the pattern into use in such a scenario?

Thanks,

Lior

Richard said...

I haven't tried running this inside a c:forEach, so that might be different. Could you put together some code and send it to support@metawidget.org?

Richard.

Radha said...

Hi,

Thank you for providing this Acid test example. I have been trying to dynamically create dataTables with JSF and I am finding it really difficult.

I modified the code for UIAddComponent to the following:


public void processEvent( SystemEvent event )
throws AbortProcessingException {

if ( !FacesContext.getCurrentInstance().isPostback() ) {

HtmlDataTable table = new HtmlDataTable();

JsfUtility.setValueExpression(table, "value", "#{company1.programmers}");
table.setVar("programmer");
getChildren().add(table);

HtmlColumn column = new HtmlColumn();
table.getChildren().add(column);
HtmlOutputText text = new HtmlOutputText();
JsfUtility.setValueExpression(text, "value", "#{programmer.firstName}");
column.getChildren().add(text);

column = new HtmlColumn();
table.getChildren().add(column);
text = new HtmlOutputText();
JsfUtility.setValueExpression(text, "value", "#{programmer.lastName}");
column.getChildren().add(text);
}
}


This works with MyFaces but doesn't with Mojarra 2.1. Mojarra recreates identical empty columns when the table is being populated and then throws an error when there is an id clash due to the identical columns.

Sincerely,
Rory.

Richard said...

Radha,

Yes, this is actually one of the Acid Tests (test 6 in EventTest.v8.zip).

The Mojarra guys are making steady progress and have recently fixed this particular part of the Acid Test. Please try your code with...

http://java.net/jira/secure/attachment/45087/1826.zip

And let them know whether it works at...

http://java.net/jira/browse/JAVASERVERFACES-1826

Also, vote for this issue! :)

Regards,

Richard.

Arjan said...

Interesting find!

I additionally found that in JSF 1.2 or JSF 2.0 with partial state saving disabled, one is able to add any number of components dynamically to the tree in a backing bean's setter method for the binding property of a tag on a Facelet.

E.g. on a Facelet:



Bean:

public void setBind(UIComponent bind) {

UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
// Components can be inserted here
root.getChildren().add(...);
}

This however does not work in JSF 2.0 (Mojarra 2.03 & 2.1.1) with partial state saving enabled. The component is added, and even renders, but does not survive a postback.

One would also expect that a (global) phase listener would be able to add components to the tree in BEFORE RENDER_RESPONSE. This however does not work. It seems logical that the tree would be fully restored AFTER RESTORE_VIEW, but for some reason it's actually only fully restored somewhere between BEFORE RENDER_RESPONSE and PreRenderViewEvent.

I created a global phase listener that just prints out the phase and the state of the view root; whether it's not null and if it has children. I then requested a simple page consisting of a JSF form with a button in it. The form was bound to a backing bean as shown above. Additionally, the global phase listener registered itself for the PreRenderViewEvent.

This is the result on Mojarra 2.1.1:

18:56:22,910 INFO [javax.faces.event.PhaseListener] before RESTORE_VIEW 1
18:56:22,910 INFO [javax.faces.event.PhaseListener] Viewroot null
18:56:22,911 INFO [javax.faces.event.PhaseListener] after RESTORE_VIEW 1
18:56:22,911 INFO [javax.faces.event.PhaseListener] Viewroot not null
18:56:22,911 INFO [javax.faces.event.PhaseListener] Viewroot has no children
18:56:22,911 INFO [javax.faces.event.PhaseListener] before RENDER_RESPONSE 6
18:56:22,911 INFO [javax.faces.event.PhaseListener] Viewroot not null
18:56:22,911 INFO [javax.faces.event.PhaseListener] Viewroot has no children
18:56:22,912 INFO [com.test.BackingBean] setBind for backing bean called.
18:56:22,912 INFO [javax.faces.event.PhaseListener] Viewroot not null
18:56:22,912 INFO [javax.faces.event.PhaseListener] Viewroot has children
18:56:22,912 INFO [javax.faces.event.PhaseListener] PreRenderViewEvent fired.
18:56:22,912 INFO [javax.faces.event.PhaseListener] Viewroot not null
18:56:22,913 INFO [javax.faces.event.PhaseListener] Viewroot has children
18:56:22,914 INFO [javax.faces.event.PhaseListener] after RENDER_RESPONSE 6
18:56:22,914 INFO [javax.faces.event.PhaseListener] Viewroot not null
18:56:22,914 INFO [javax.faces.event.PhaseListener] Viewroot has children

As you mentioned, components added in PreRenderViewEvent render and survive a postback, but in the case of Mojarra only in 2.1.1. In the often used Mojarra 2.0.3 this also does not work, but as mentioned: when partial state saving is disabled components can be added in the setBind method.

FInally, components added AFTER RENDER_RESPONSE do not survive a postback.

Ludovic Pénet said...

Thank you for this great post.

I was using the "encodeBegin" way, and it was breaking a few things, among which handling of @ResourceDependency.

With your method, everything is fine and the code is much cleaner.

I hope it will make its way in the standard spec.

Ludovic Pénet said...

Well, in fact, it does not work as expected when the control is conditionnaly rendered and not initially visible (using rendered=#{test...}" attribute).

Did you find a solution also working in this case ?

In this case, it is a postback, so

FacesContext.getCurrentInstance().isPostback() is true, but no PreRenderViewEvent has been dispatched with FacesContext.getCurrentInstance().isPostback() == false

Richard Kennard said...

I'm not completely clear from your comment what you're doing. But it sounds very much like you're running up against this bug:

https://java.net/jira/browse/JAVASERVERFACES-2089

Could you maybe simplify your use case? Instead of using #{test} maybe just use #{true} and #{false} to see whether that works? If it does, it's an EL issue (as described in the bug report)

Ludovic Pénet said...

Thank you for your interest in my question.

I use primefaces. I have a layout with a picker on the left (west in primefaces terminology) and some data on the picked item in the center.

In the center, I have code like :





my:dataTable is a control derived from PrimeFaces datatable.
It dynamically adds column according to the type of selected item.

It does so on PreRenderViewEvent.

Initially, no item is selected and the datatable is not rendered.

When an item is selected, an update of the outputPanel is performed. As the picker is a primefaces dataTable, it is on primefaces event.

As my components are not composite, it does not seem to me that it is related to the composite component bug you pointed.

In fact, I avoid composite components and most often use tag components or custom components because of bugs such as the one you pointed.

For the time being, I started to "fix" this bug by adding the components on the first PreRenderView event. I track that this initialisation has been performed to avoid buggy multiple initialisation (with just a simple boolean value).

But I have then a bug with data binding in display or edition components I dynamically add : all the EL expression resolve to null. Everything gets fine on a full reload.

I hope it is clearer... Thanks again for your attention.

Andreas Zschorn said...

Hi Richard,
can you provide me some explanation or hints, why it is only safe to manipulate the component tree if it is not a postback.

if ( !FacesContext.getCurrentInstance().isPostback() ) {

// Safely manipulate component tree here
}

I could not find a explanation in the spec. I know it does not work with jsf-impl-2.1.7-jbossorg-2.jar. In subsequent i always get the "Unable to find state for component with clientId".
So it seems the component state is not saved correctly, but I could not understand what is the reasoning behind it.
It would be great if you can give me some explanations. If i see it correct, this means, I can't manipulate the component tree in an ajax request, which does not sound logical for me.
Thanks for your insight
Andreas

Richard Kennard said...

Ludovic,

I'm afraid your description is not completely clear. Could you please put together a minimal, sample project and send it to support@metawidget.org? Then I can take a look.

Regards,

Richard.

P.S. The bug I pointed you to applies to any dynamically created component, not just composite components. But you're right: it may not be relevant in your case.

Richard Kennard said...

Andreas,

Apologies: my mistake. It is fine to manipulate on postback. Normally you don't *want* to, because you want to create your dynamic components the first time the page is rendered, and not recreate them every time a button is clicked.

But you can if you want.

More importantly you don't want to recreate them on validation failure, because you'll lose the invalid values that are stored in the components (but cannot be written back to the model). So you should probably just check for 'isValidationFailed'. I have updated the blog to reflect this.

Regards,

Richard.

P.S. Note that 'postback' in JSF is less onerous than regular HTML post. It's not considered a 'postback' if one JSF page posts to a different JSF page. It's only a 'postback' if you post back to yourself. However you're right this will affect AJAX.

Ludovic Pénet said...

Richard, thanks again for your attention.

Initializing my dynamic control even in a postback works perfectly fine for me. I found that another strange behaviour was in fact pbkac.

Now, I have some problems when I dynamically instanciate ValueExpression-s in PreRenderViewEvent handler, but this is another topic. :-)

Anonymous said...

It still not enough good in many cases:
if(!FacesContext.getCurrentInstance().isPostback())

The probleme sometimes your component isn't rendered when postback false (only after an ajax event when postback is true).

(Please excuse my poor English)

Richard Kennard said...

You are correct. This blog entry is a little outdated.

Please see the latest code for UIMetawidget (https://raw.githubusercontent.com/metawidget/metawidget/364d4eae550b78a0f759647e4ad9bf14fc473a0c/modules/java/faces/core/src/main/java/org/metawidget/faces/component/UIMetawidget.java)

Instead of .isPostback(), it uses:

* context.isValidationFailed() to determine if a validation error
* partialViewContext.isAjaxRequest() to dermine if an AJAX request