I have found some strange behaviour when updating nested c:forEach. Apparently value of var is not correct on inner c:forEach.
The following example declares a simple Child Class, a Parent Class which includes a list of Child, and a managed bean (ViewScoped). This bean initializes a Parent (P0) with no children and a Parent (P1) with 3 children. The method extractFirst() simply get the first child available of P1 and add it to P0.
The index.html prints all the information using 2 c:forEatch nested tags. After submit, extractFirst() is executed and screen is updated, but, the result is not what I expected.
I get the same result with ajax and non-ajax requests with both standard h:commandButton and Primefaces p:commandButton.
First Scren
- parent.id: P0
- parent.id: P1
- child.id: C0 - childIndex.current.id: (C0)
- child.id: C1 - childIndex.current.id: (C1)
Second Scren (After submit)
- parent.id: P0
- child.id: C1 - childIndex.current.id: (C0) //Expected C0 but I get C1
- parent.id: P1
- child.id: - childIndex.current.id: (C1) //Expected C1 but I get ¿null?
Environment: Java8, JEE7, Wildfly 10.0.1
Code example (Full code at https://github.com/yerayrodriguez/nestedForeachProblem):
Child Class
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
public Child(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Parent Class
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private List<Child> children = new ArrayList<>();
public Parent(String id) {
this.id = id;
}
public String getId() {
return id;
}
public List<Child> getChildren() {
return children;
}
}
Managed Bean
@Named
@ViewScoped
public class TestManager implements Serializable {
private static final long serialVersionUID = 1L;
private List<Parent> root = new ArrayList<>();
public List<Parent> getRoot() {
return root;
}
@PostConstruct
public void init() {
// Parent 0 with no children
Parent parent0 = new Parent("P0");
root.add(parent0);
// Parent 1 with 2 children
Parent parent1 = new Parent("P1");
parent1.getChildren().add(new Child("C0"));
parent1.getChildren().add(new Child("C1"));
root.add(parent1);
}
public String extractFirst() {
Parent P0 = root.get(0);
Parent P1 = root.get(1);
if (!P0.getChildren().isEmpty()) {
return null;
}
// Get first child of P1
Child removedChild = P1.getChildren().remove(0);
System.out.println("Removed child from P1: " + removedChild.getId()); // OK
System.out.println("Is removed child id equals 'C0': " + removedChild.getId().equals("C0")); // OK
// Add this child to P0
P0.getChildren().add(removedChild);
Child firstP0Child = P0.getChildren().get(0);
System.out.println("Frist P0 Child: " + firstP0Child.getId()); // OK
System.out.println("Is first P0 child id equals 'C0': " + firstP0Child.getId().equals("C0")); // OK
return null;
}
}
index.html
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<h:head />
<h:body>
<h:form id="myForm">
<h:commandButton action="#{testManager.extractFirst()}" value="NON AJAX" />
<h:commandButton value="AJAX" action="#{testManager.extractFirst()}">
<f:ajax render="myForm" />
</h:commandButton>
<p:commandButton action="#{testManager.extractFirst()}" value="PF NON AJAX" ajax="false" />
<p:commandButton action="#{testManager.extractFirst()}" value="PF AJAX" update="myForm" />
<ul>
<c:forEach var="parent" items="#{testManager.root}">
<li>parent.id: #{parent.id}</li>
<ul>
<c:forEach var="child" items="#{parent.children}" varStatus="childIndex">
<li>child.id: #{child.id} - childIndex.current.id: (#{childIndex.current.id})</li>
</c:forEach>
</ul>
</c:forEach>
</ul>
</h:form>
</h:body>
</html>