This blogpost is about a problem which about 80% of all user problems regarding JSF Ajax in the myfaces mailinglist revolve around. Namely how do I handle the jsf ajax in a multiple form scenario.
JSF Ajax and multiple forms, a standard case, which should be easy right.
Let's have a small look at an example:
As we see here, two forms each updating a component in itself via ajax.
Now what happens if we submit the forms alternating.
After a while we run into a ViewRoot cannot be found Exception on the server side.
We did everything right, why do we face this issue?
The answer lies in a bug in the JSF Ajax protocol, more precisely the way the ViewState is processed.
Lets have a look at an Ajax response:
Here we see the root cause of the problem. There is a parameter defining the ViewState with the identifier javax.faces.ViewState, however it is not clear where it belongs to.
Practically a viewstate must be attached to a form. So the issuing form definitely must receive it. However what about the other forms?
And here is the root cause of the error. Only the issuing form is updated and the ViewState which is dependend on the viewroot not the form in the second form is not updated.
This image shows exactly what happens:
As you can see only one form is update the second form now has a viewstate which is not the current one and at one point is dropped from the ViewState history, a classical concurrency issue.
So the solution would be to update all forms in the html document, right?
Theoretically yes, but there is one API which prevents this simple solution. Portlets.
In a portlet environment you have multiple viewroots all belonging to different jsf session instances on the server.
The next logic solution would be to update all jsf elements under ViewRoot.
Again, a good idea, but the protocol prevents it. On the pure client side we do not have any marker or indicator, which tells the ajax code where the current ViewRoot begins.
To ease this protocol problem an extension to the jsf spec was added under 2.1 which eases this problem. According to the spec you have to add the second form manually as render target. Something which both implementations follow. Here is a snippet which shows the solution:
Here we can see, we have added the second form as render target.
Now both forms will be updated and the viewstate always will be up to date.
This solution while being spec compliant is not satisfactory. Sometimes you dont know if a certain form is still present after the ajax case. Is there still a way to update all forms in the page or at least to take the form id out of the equation?
Unfortunately not within the bounds of the specification. However, being aware of this issue, I have added in Apache MyFaces additionally to the spec behavior two other ways to resolve the issue.
First, you don't have to define the form as render target, but you can define any element within a form as render target and the form will be updated.
Secondly, the probably best solution. A configuration parameter which forces MyFaces to update all forms in a page.
With following code snippet you can enable this mechanism:
Once you have added this snippet of javascript, myfaces will be in no portlet mode and will enforce an update all forms with the viewstates policy.
Note: While this method wont break the code if you switch to mojarra, you will not get the benefits, because Mojarra does not yet have a similar solution to the issue.
Keine Kommentare:
Kommentar veröffentlichen