forEach and Facelets - a bugfarm just waiting for harvest
- by Duncan Mills
An issue that I've encountered before and saw again today seems worthy of a little write-up. It's all to do with a subtle yet highly important difference in behaviour between JSF 2 running with JSP and running on Facelets (.jsf pages). The incident I saw today can be seen as a report on the ADF EMG bugzilla (Issue 53) and in a blog posting by Ulrich Gerkmann-Bartels who reported the issue to the EMG. Ulrich's issue nicely shows how tricky this particular gochya can be. On the surface, the problem is squarely the fault of MDS but underneath MDS is, in fact, innocent.
To summarize the problem in a simpler testcase than Ulrich's example, here's a simple fragment of code:
<af:forEach var="item" items="#{itemList.items}">
<af:commandLink id="cl1" text="#{item.label}" action="#{item.doAction}"
partialSubmit="true"/>
</af:forEach>
Looks innocent enough right? We see a bunch of links printed out, great.
The issue here though is the id attribute. Logically you can kind of see the problem. The forEach loop is creating (presumably) multiple instances of the commandLink, but only one id is specified - cl1. We know that IDs have to be unique within a JSF component tree, so that must be a bad thing? The problem is that JSF under JSP implements some hacks when the component tree is generated to transparently fix this problem for you. Behind the scenes it ensures that each instance really does have a unique id. Really nice of it to do so, thank you very much.
However, (you could see this coming), the same is not true when running with Facelets (this is under 11.1.2.n) in that case, what you put for the id is what you get, and JSF does not mess around in the background for you. So you end up with a component tree that contains duplicate ids which are only created at runtime. So subtle chaos can ensue. The symptoms are wide and varied, from something pretty obscure such as the combination Ulrich uncovered, to something as frustrating as your ActionListener just not being triggered. And yes I've wasted hours on just such an issue.
The Solution
Once you're aware of this one it's really simple to fix it, there are two options:
Remove the id attribute on components that will cause some kind of submission within the forEach loop altogether and let JSF do the right thing in generating them. Then you'll be assured of uniqueness.
Use the var attribute of the loop to generate a unique id for each child instance. for example in the above case: <af:commandLink id="cl1_#{item.index}" ... />.
So one to watch out for in your upgrades to JSF 2 and one perhaps, for your coding standards today to prepare you for.
For completeness, here's the reference to the underlying JSF issue that's at the heart of this: JAVASERVERFACES-1527