Sunday, July 11, 2010

What's wrong with the JSF 2 ui:repeat

The single little thing that's been able to hold my attention repeatedly over the past few weeks in JSF 2 has been the "ui:repeat" tag.

To begin with, it's quite simple. It's so very simple that it's authors probably find it too trivial to explain what it does. This is all you get in its tag library documentation:



Ok. So it's an alternative to c:forEach, or h:dataTable, right? Maybe they just created this tag for some political reasons(if there're two more tags in the same spec and the same impl for the same purpose, you do get the doubt), but that's fine with me as long as it works.

"As long as it works".. hmm... yes.. and that's the tricky part. It seems to be that there are a number of issues with ui:repeat. Most importantly, when you have nested ui:repeats, things start behaving real crazy.

OK. So what if it has bugs? It's an alternative to c:forEach and h:dataTable, right? Let's use c:forEach instead.

And so, when you start using c:forEach, you realize that it's a totally different tag, with a totally different purpose.

Ok. So ditch that c:forEach. Let's see what the bugs with ui:repeat and apply patches ourself.

The first thing - ui:repeat doesn't recognize model updates.
The fix is quite simple. In the process method of UIRepeat, just add a simple check (as suggested by the proposed patch on the bug report):

if (PhaseId.RENDER_RESPONSE.equals(phase) && !hasErrorMessages(faces)) {
if(isNestedInIterator()){
this.childState = null;
}
}


And the second thing - ui:repeat botches up input fields - checkboxes, textboxes and all sorts of EditableValueHolders in general. That has also been reported.
The fix is pretty simple, though if you are like me, new to JSF, and stuck with it, with no other way out, you'd need to hit your head against the code for anywhere between three to five days before you figure it out.

In the populate method of the SavedState inner class, just change this one line:
this.value = evh.getValue();
to
this.value = evh.getLocalValue();




The root cause of this second bug is an API design flaw -
The JSF ValueHolder interface has two methods getValue and setValue, but interestingly, they are not complementary. That is, if you use the setValue method and push in some value, you may not get that same value out of the getValue.
Why? Because that's how it has been designed.

Defies conventions and common-sense expectations? Certainly does. Bad API? Definitely.

The method ought to be named setLocalValue, not setValue.

I wonder how these seemingly simple bugs in code and documentation seem to be around for years in such a high quality, high visibility software library.

2 comments:

  1. Thanks for the write-up. I guess part of the problem is that Facelets was not part of JSF 1.x and at some point upstream went pretty silent. And since it had to be packaged anyway, people started making fixes and packaging their own modified versions of Facelets with their applications...

    Daniel

    ReplyDelete
  2. Thank you very much for this blogpost!
    Jaro

    ReplyDelete