Last week, a co-worker of mine at Oracle was attempting to use a forEach
tag to produce components in a loop and developed issues. Having some experience trying to deal with for each I started looking into the problem and found a disturbing problem.
The JSTL <c:forEach/>
tag was written to make JSP development easier, but was never intended to be used with Java Server Faces. In JSP 2.1 some enhancements were made to improve the usage of non-JSF JSP tags intermingled with JSF by introducing deferred expressions. The idea behind the deferred expressions was to be able to create expressions that would normally have to be evaluated at JSP tag execution and allow them to be executed with JSF components instead. The problem is that the implementation of deferred expressions is flawed for the <c:forEach/>
loop except for basic usage only.
Use Case
For the illustration of the problem with the <c:forEach/>
I will simplify the use case. The are two main problems with the tag both of which involve the changing of the list bound to the items
attribute. For the first, consider the following requirements:
- For each loop required, JSF stamped iteration components cannot be used (like JSP tags that include a page)
- Ajax used to partially render the page
- Id's of components must be tied to the item in the loop to allow actions
Problem 1 - static EL
A simple example can be used to show the problem:
<h:form>
<c:forEach var="item" items="#{bean.items}">
<h:commandButton
id="button_${var.key}"
value="Remove #{var.key}"
actionListener="#{bean.remove}" />
</c:forEach>
</h:form>
Take for example a list of "A", "B", "C" and "D". The buttons render as:
ID | Text |
button_A | Remove A |
button_B | Remove B |
button_C | Remove C |
button_D | Remove D |
Now should the user click "Remove A", the managed bean removes the value from the items
list. The result is:
ID | Text |
button_B | Remove C |
button_C | Remove D |
button_D | Remove |
Notice the mismatch of ID and text? Notice that the last button's #{var.key}
evaluated to null?
To understand the problem, it is best to describe a step by step process of how the code works:
- The for each tag is executed and the first iteration (count 0) is begun
- The tag creates an
IndexedValueExpression
. This sub-class ofValueExpression
.- The expression to get the items is stored (
#{bean.items}
) - The index of the for each iteration is stored (
0
)
- The expression to get the items is stored (
- This
IndexedValueExpression
is then added to theVariableMapper
as thevar
- item. - The body of the tag is executed
- The command button tag is instructed to create or find a component with ID
button_A
. - The command button component is created with all its attributes.
- When the component is created, the
ValueExpression
instances are created - Each
ValueExpression
is created with a reference to theFunctionMapper
andVariableMapper
- When the component is created, the
- The for each tag proceeds with its execution and processes indexes 1-3
When the action executes and removes A, the following occurs during the render phase. First, the for each tag looks for the button for B and finds it. Then the loop repeats. At the end of processing the tags, the code realizes that button_A
was never referenced, and is therefore removed from its parent, the form.
The problem is that once the component is created, the ValueExpression
instances are created, and are never re-created. This is because the expressions with their mappers are serialized and restored as part of the component state. This means that button_B
which was found for the first item after item A was removed still has its original ValueExpression
for the value
attribute. This expression has the variable mapper with the indexed value expression for the var
which has index 1.
This means that if components are used with IDs that tie to the items, it is possible to have the value expressions pointing to the wrong index in the for each loop once changes are made to the items list.
Problem 2 - Component State
One workaround to problem one is to use dynamically created IDs. This will violate our above requirement of needing to match the component to the item for advanced PPR (AJAX) replacement, but it is a consideration.
Lets use the above use case with the IDs removed:
<h:form>
<c:forEach var="item" items="#{bean.items}">
<h:commandButton
value="Remove #{var.key}"
actionListener="#{bean.remove}" />
</c:forEach>
</h:form>
This renders as:
ID | Text |
j_id_id2 | Remove A |
j_id_id2j_id_1 | Remove B |
j_id_id2j_id_2 | Remove C |
j_id_id2j_id_3 | Remove D |
When A is removed, since the item key is not used in the ID, the above problem is not witnessed:
ID | Text |
j_id_id2 | Remove B |
j_id_id2j_id_1 | Remove C |
j_id_id2j_id_2 | Remove D |
What you will notice is that item B which used component with ID j_id_id2j_id_1
is now rendered by component with ID j_id_id2
. Unlike above, this component has the correct index stored for it, but its state may break the view. Now command buttons are not a great example of this, but many components have state that is more evident, like tree node expansion. What this will mean to the user is that component state that belongs with item B is now associated with item C.
How could this be addressed?
The fundamental problem is that the implementation of the for each loop tag assumes that the location of the components that are created are independent of the items used to produce them. The ValueExpression
instances are created for components with hard coded indexes and there is no way that I know of to update the index to the correct value later.
One fix is to allow the for each loop to use keys for the item instead of indexes. The obvious problem with this idea is that it would involve a significant API enhancement to be able to specify a key for an object and the use of a Map
instead of a List
or array to be able to lookup the value for the key. Something like:
<h:form>
<c:forEach var="item" keys="#{items.keys} items="#{bean.items}">
<h:commandButton
value="Remove #{var.key}"
actionListener="#{bean.remove}" />
</c:forEach>
</h:form>
Where keys would be a List
or array of serializable objects for keys in the items which would be represented as a Map
.
Summary
As I have always recommended on the Apache MyFaces and Facelets mailing lists the best solution is to never use JSTL tags for JSF development. Unfortunately there are some pieces of functionality which is hard to replicate using components. Perhaps JSF 2.0 will allow things like component controlled sub-page inclusion.