更新嵌套后 var 值错误 c:forEach
Wrong var value after updating nested c:forEach
我在更新嵌套 c:forEach 时发现了一些奇怪的行为。显然 var 的值在内部 c:forEach 上不正确。
下面的例子声明了一个简单的ChildClass,一个ParentClass 其中包括 Child 的列表和一个托管 bean (ViewScoped)。这个 bean 初始化一个没有 children 的 Parent (P0) 和一个有 3 children 的 Parent (P1)。 extractFirst() 方法简单地获取 P1 的第一个 child 可用并将其添加到 P0。
index.html 使用 2 c:forEatch 嵌套标签打印所有信息。提交后,执行 extractFirst() 并更新屏幕,但是,结果不是我所期望的。
我在 ajax 和 non-ajax 标准 h:commandButton 和 Primefaces p:commandButton 请求中得到相同的结果。
第一屏
- parent.id: P0
- parent.id: P1
- child.id: C0 - childIndex.current.id: (C0)
- child.id: C1 - childIndex.current.id: (C1)
第二个屏幕(提交后)
- 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?
环境:Java8、JEE7、Wildfly 10.0.1
代码示例(完整代码位于 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;
}
}
托管 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>
就像评论中提到的和几个引用的链接
"you cannot update a for each"
视图只构建一次,由于您 return null
在操作方法的末尾,您实际上 stay on the excact same view INSTANCE without actually rebuilding it。但是由于您 确实 操作了一些支持数据,在您看来您似乎得到了 错误的 数据,但实际上您最终处于某种未定义的状态(在至少这是我的印象,它甚至可能在 JSF 实现 and/or 版本之间甚至可能在 JSTL 实现之间有所不同,关于 var 和 varStatus 的东西,甚至可能是一些视图树的东西)。
即使您 return 在方法末尾添加一个空字符串,结果也不会如您所愿。尽管结果(至少在我目前手头的 wildfly 10 中)也不像我预期的那样。根据 Difference between returning null and "" from a JSF action,我希望重新创建 bean,最终结果是页面看起来与您开始时一样。 JSTL 中的 EL 很可能在某种程度上是 'cached',即使在这种情况下,结果也是未定义的,这证实了在处理属于 JSTL 生成的内容的后端数据时的 'undefined' 行为。
参见例如当你在 P1 中使用 5 个元素并将第三个元素从 P1 移动到 P0 时会发生什么(当同时更改第三个元素的 id 时,你会看到更显着的事情,例如附加'-moved'到它(-m 在我的代码和屏幕截图中)
Child removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
甚至做两次
Child removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
因此,正如您所看到的,不仅 var
是错误的,而且 varStatus
也是错误的。只是搬家后没注意到 'C0'.
现在如何重建视图?返回 "index?faces-redirect=true"
导致页面被重建,但不幸的是(但正如预期的那样)最终结果为 'no-op' 的 bean 也是如此。这可以通过为 bean 提供比视图更长的作用域来解决。我个人手头只有 @SessionScope
,但 DeltaSpike @ViewAccessScope
的范围更短,'managed' 的选择更好,How to choose the right bean scope?,我经常使用它。
所以建议仍然是(一直是,但有时可能隐藏或未明确制定):
Don't manipulate data backing JSTL tags when the data has been used to create the view (tree, repeats) etc unless the scope of the bean backing the data is longer than @ViewScoped
and a Post-Redirect-Get (PRG) is done to rebuild the view.
免责声明
我的 'undefined state' 假设可能有问题。可能对所经历的行为有明确的解释,我只是在短时间内没有找到它,也没有动力去深入挖掘。由于 'right way' 这样做希望更清楚。
我在更新嵌套 c:forEach 时发现了一些奇怪的行为。显然 var 的值在内部 c:forEach 上不正确。
下面的例子声明了一个简单的ChildClass,一个ParentClass 其中包括 Child 的列表和一个托管 bean (ViewScoped)。这个 bean 初始化一个没有 children 的 Parent (P0) 和一个有 3 children 的 Parent (P1)。 extractFirst() 方法简单地获取 P1 的第一个 child 可用并将其添加到 P0。
index.html 使用 2 c:forEatch 嵌套标签打印所有信息。提交后,执行 extractFirst() 并更新屏幕,但是,结果不是我所期望的。
我在 ajax 和 non-ajax 标准 h:commandButton 和 Primefaces p:commandButton 请求中得到相同的结果。
第一屏
- parent.id: P0
- parent.id: P1
- child.id: C0 - childIndex.current.id: (C0)
- child.id: C1 - childIndex.current.id: (C1)
第二个屏幕(提交后)
- 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?
环境:Java8、JEE7、Wildfly 10.0.1
代码示例(完整代码位于 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;
}
}
托管 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>
就像评论中提到的和几个引用的链接
"you cannot update a for each"
视图只构建一次,由于您 return null
在操作方法的末尾,您实际上 stay on the excact same view INSTANCE without actually rebuilding it。但是由于您 确实 操作了一些支持数据,在您看来您似乎得到了 错误的 数据,但实际上您最终处于某种未定义的状态(在至少这是我的印象,它甚至可能在 JSF 实现 and/or 版本之间甚至可能在 JSTL 实现之间有所不同,关于 var 和 varStatus 的东西,甚至可能是一些视图树的东西)。
即使您 return 在方法末尾添加一个空字符串,结果也不会如您所愿。尽管结果(至少在我目前手头的 wildfly 10 中)也不像我预期的那样。根据 Difference between returning null and "" from a JSF action,我希望重新创建 bean,最终结果是页面看起来与您开始时一样。 JSTL 中的 EL 很可能在某种程度上是 'cached',即使在这种情况下,结果也是未定义的,这证实了在处理属于 JSTL 生成的内容的后端数据时的 'undefined' 行为。
参见例如当你在 P1 中使用 5 个元素并将第三个元素从 P1 移动到 P0 时会发生什么(当同时更改第三个元素的 id 时,你会看到更显着的事情,例如附加'-moved'到它(-m 在我的代码和屏幕截图中)
Child removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
甚至做两次
Child removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
因此,正如您所看到的,不仅 var
是错误的,而且 varStatus
也是错误的。只是搬家后没注意到 'C0'.
现在如何重建视图?返回 "index?faces-redirect=true"
导致页面被重建,但不幸的是(但正如预期的那样)最终结果为 'no-op' 的 bean 也是如此。这可以通过为 bean 提供比视图更长的作用域来解决。我个人手头只有 @SessionScope
,但 DeltaSpike @ViewAccessScope
的范围更短,'managed' 的选择更好,How to choose the right bean scope?,我经常使用它。
所以建议仍然是(一直是,但有时可能隐藏或未明确制定):
Don't manipulate data backing JSTL tags when the data has been used to create the view (tree, repeats) etc unless the scope of the bean backing the data is longer than
@ViewScoped
and a Post-Redirect-Get (PRG) is done to rebuild the view.
免责声明 我的 'undefined state' 假设可能有问题。可能对所经历的行为有明确的解释,我只是在短时间内没有找到它,也没有动力去深入挖掘。由于 'right way' 这样做希望更清楚。