Problems with the tr:forEach
----------------------------

                 Key: TRINIDAD-1940
                 URL: https://issues.apache.org/jira/browse/TRINIDAD-1940
             Project: MyFaces Trinidad
          Issue Type: Bug
          Components: Components
    Affects Versions: 2.0.0.3-core
            Reporter: Andrew Robinson
            Assignee: Andrew Robinson


The tr:forEach tag has issues when trying to use component references and 
trying to keep the component state in sync with the items in a list. It also 
does not support Maps like the c:forEach tag does.

I seek to improve the forEach tag in Trinidad, and propose that JSF/JSTL does 
something similar so that user's can really use the for each tag without 
issues. Some of the for each problems are described in my blog:
http://drewdev.blogspot.com/2008/08/cforeach-with-jsf-could-ruin-your-day.html

I propose to address each of the issues with the JSP tag.

First, reliable component references:
<tr:forEach var="item" items="#{myBean.items}">
  <tr:panelGroupLayout id="pgl1" layout="horizontal">
    <tr:inputText id="it1" label="Enter value:" value="#{item.text}" 
autoSubmit="true" />
    <tr:outputText id="ot1" value="Value is: #{item.text}" 
partialTriggers="it1" />
  </tr:panelGroupLayout>
</tr:forEach>

The problem is that the author intended the output text component to partially 
updated by the sibling input text when it changed value, but that is not the 
result. Instead, the partial triggers is always "it1", but the generated input 
text components are "ot1", "ot1j_id_1" and "ot1j_id_2". The result is that the 
output text components all partially update only when the first input text is 
changed.

Another problem is that the indexed value expressions and the var status map 
that is put into the variable mapper by the for each loop is static.  Take the 
following example:

<tr:panelGroupLayout id="pgl1" layout="scroll">
  <tr:forEach var="item" items="#{myBean.items}" varStatus="vs">
    <tr:outputText id="ot1" value="This is the last item in the list: 
#{vs.last}. Item #{item.text}." />
  </tr:forEach>
</tr:panelGroupLayout>

Say during the first pass, the items in the list are A, B and C and the page 
would look like this:

This is the last item in the list: false. Item A.
This is the last item in the list: false. Item B.
This is the last item in the list: true. Item C.

Now, consider what the output would be if someone added an object D to the end 
of the list during invoke application:

This is the last item in the list: false. Item A.
This is the last item in the list: false. Item B.
This is the last item in the list: true. Item C.
This is the last item in the list: true. Item D.

Notice that both C and D think they are the last. The reason is that the 
UIComponentClassicTagBase will find the components generated for A, B and C 
during the findComponent call. It will only create a new component for D. When 
D is created, it will pick up the new variable status map created by the for 
each tag in its value expressions. Because when three earlier components were 
build, C was the last item, and the variable status map in their value 
expressions did not reflect any changes. D gets the correct values since it was 
just created.

-----------------

I propose to reduce any work required by page developers to implement the for 
each loop with re-ordering support (avoid having to use immediate EL in the ID 
attribute) and fix the issues above.

The proposal is that Trinidad Tag based components (those components created by 
UIXComponentELTag) will be able to have key-based IDs automatically generated 
as a result of being in a for each tag. So consider this example:

<tr:forEach var="item" items="#{bean.items}" varStatus="vs">
  <tr:inputText id="it1" value="#{item.value}" partialSubmit="true" />
  <tr:outputText id="ot1" value="Value is: #{item.value}"
    partialTriggers="it1_${vs.key}" />
</tr:forEach>

In this code, the IDs of the components would automatically pick up the item 
key. The item key would be stored on the var status. For List this key would 
simply be the index, for Map it would be the map key and CollectionModel would 
use the row key. UIXComponentELTag could check to see if a parent tag desires 
to alter the component IDs, which in this case, the for each would. With that 
set, the tags would alter the component IDs to append the key. For example, "_" 
+ key would be appended to each component ID (it1 would become it1_A if the key 
were "A").

The for each tag would set the key into the variable mapper so that, instead of 
indexes, the key would be used to evaluate the EL with the limitation that the 
key would be the index for List, in which the current behavior would be 
retained of the component state staying with the index rather than with the 
object.

Due to the fact that the JSP does not perform any component reordering, the 
org.apache.myfaces.trinidad.change.ReorderChildrenComponentChange component 
change class can be use to alter the component order and also notify the 
component change manager of the reordering. This would require users that wish 
to reorder the items in the list to ensure to both re-order the list as well as 
reorder the components by applying a component change.

Changes to UIXComponentELTag

Due to the fact that UIComponentClassicTagBase is a JSF class that we cannot 
modify, in order to fix the tr:forEach bugs, we need to work with what we can 
change. We will want to follow this up with proposed changes to the JSF and 
potentially the JSTL specifications to be able to make this type of 
functionality standard.

Since UIXComponentELTag extends UIComponentClassicTagBase, it can call 
protected methods to alter the ID of the tag. With the altering of the tag ID, 
the UIComponentClassicTagBase will produce different component IDs. In order to 
pull this off, there needs to be a defined contract between the for each tag 
and the Trinidad component tags. This contract could be done with static 
methods on UIXComponentELTag:

Proposed methods to be added to UIXComponentELTag
/**
 * Push a component ID suffix onto the page context to append to component IDs 
generated by {...@link UIXComponentELTag}.
 * This will append the suffix on all components up to a and including the text 
naming container.
 * @param pageContext the JSP page context
 * @param suffix the suffix to append to component IDs
 */
public static void pushComponentIdSuffix(PageContext pageContext, String suffix)
{
  ...
}

/**
 * Pop the last component ID suffix added to the page context.
 * @see #pushComponentIdSuffix(PageContext, String)
 * @param pageContext the JSP page context
 */
public static void popComponentIdSuffix(PageContext pageContext)
{
  ...
}

The ForEachTag would then call these methods before and after the body 
processing respectively. The idea would be that if a UIXComponentELTag created 
a naming container, it would clear any suffixes for children of the naming 
container. This is because the IDs would already be unique in the naming 
container, and there would be no reason to modify them, and it would also stop 
these changes from affecting any IDs inside of included pages. The push/pop 
method would be used to support a stack usage. This way, if nested for each 
tags are present, multiple suffixes would be added to ensure that the IDs in 
one for each tag would conflict with another's.

As this would be a documented behavior, users could leverage this API to ensure 
that their component references (like the partialTriggers attribute) point to 
the correct component.

Changes to ForEachTag
The for each tag, with this proposal, would need to be modified to call the new 
methods of UIXComponentELTag in doStartTag and doAfterBody functions.

The tag will need to change to provide dynamic value expressions for the var 
status and the var so that changes to the items will not break EL. This would 
involve using the key as a reference instead of an index for non list based 
items. The var status would need point to a component tree based attribute that 
could be updated in each request. For example, this would allow new items to be 
added, and the last attribute to correctly reflect that new state.

The for each tag would be change to support Map and CollectionModel as well, 
and adding the key as a valid attribute on the varStatus object.

Optionally skip this for List and array

If we did not implement the ID suffixing for List (and arrays), existing 
applications would not be affected. This is one consideration to have to ensure 
that backwards compatibility is maintained. Another alternative is to use a 
web.xml configuration parameter and only introduce the ID suffixing when the 
web XML parameter has enabled it for lists and arrays.


-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.

Reply via email to