jsf - Wrong var value after updating nested c:forEach
问题描述
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>
解决方案
就像评论中提到的和几个引用的链接
“你不能为每个更新一个”
该视图是构建一次,并且由于您return null
在操作方法结束时,您有效地停留在完全相同的视图实例上,而无需实际重建它。但是由于您确实操作了一些支持数据,在您看来您可能得到了错误的数据,但实际上您最终处于某种未定义的状态(至少这是我的印象,它甚至可能在 JSF 实现和/或版本之间有所不同,甚至可能JSTL 实现,关于 var 和 varStatus 的东西,甚至可能是一些视图树的东西)。
即使您在方法的末尾返回一个空字符串,结果也不是您希望的那样。虽然结果(至少在我目前手头的 wildfly 10 中)也不是我所期望的。根据从 JSF 操作返回 null 和 "" 之间的区别,我希望重新创建 bean,最终结果是页面看起来与您开始时相同。JSTL 中的 EL 很可能以某种方式“缓存”,即使在这种情况下,结果也是未定义的,从而证实了在处理属于 JSTL 生成内容的后端数据时的“未定义”行为。
例如,当您使用 5 个元素到 P1 并将第 3 个元素从 P1 移动到 P0 动作时会发生什么(当同时将第 3 个元素的 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"
导致页面被重建,但不幸的是(但正如预期的那样)bean 的最终结果为“无操作”。这可以通过给 bean 一个比视图更长的范围来解决。我个人只有@SessionScope
手头,但 DeltaSpike 的@ViewAccessScope
范围更短,更好的“管理”选择,如何选择正确的 bean 范围?,我经常使用它。
所以建议仍然是(总是但有时可能隐藏或未明确制定):
当数据已用于创建视图(树、重复)等时,请勿操作支持 JSTL 标记的数据,除非支持数据的 bean 的范围大于
@ViewScoped
并且完成后重定向获取 (PRG) 以重建风景。
免责声明 我的“未定义状态”假设可能有问题。关于所经历的行为可能有明确的解释,我只是在我调查它的短时间内没有找到它,我也没有动力去深入挖掘。由于这样做的“正确方法”希望更清楚。
推荐阅读
- python - Tensorflow:使用 MySQL 数据库懒惰地检索每批数据集
- google-bigquery - Bigquery + Firebase 分析:选择昨天的表
- kotlin - 如何在 Kotlin 中创建可变的字母列表?
- r - 如何从 shinyapps.io 下载带有多字节字符图的 Rmarkdown 报告
- python - 每个值相对于列的平均值/百分位数,并使用 pandas 内置函数将结果保存在新列中
- python - 值错误:;检查输入时出错:预期 flatten_input 具有 3 个维度,但得到的数组具有形状 (940682, 2)
- python - `机器人框架中的测试用例超时,测试用例变量而不是在***变量***部分中定义的变量`
- java - 如何防止键盘打开时编辑文本缩小?
- sql - 当列在sql中具有空值时,将日期列从varchar转换为日期类型
- django - Django 中用户定义的 postgres jsonb 查询的安全问题