JSF ui:repeat 被 ui:include 包含在 h:panelGroup 中并带有条件渲染...满口
原来的问题在下面,但我想出了一个更简单的例子来演示这个问题,并认为它应该放在顶部。
无论如何,看起来 ui:repeat
标记是在检查父元素是否实际渲染之前进行处理的。要重新创建此内容,请使用以下 Facelet (minimalTest.xhtml):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:repeat> inside <h:panelGroup rendered="false"></title>
</h:head>
<h:body>
<h:form>
<h1>Testing</h1>
<h:panelGroup rendered="false">
<span>#{minimalTestBean.alsoThrowsException}</span>
<ul>
<ui:repeat value="#{minimalTestBean.throwsException}" var="item">
<li>#{item}</li>
</ui:repeat>
</ul>
</h:panelGroup>
</h:form>
</h:body>
</html>
使用此 bean (MinimalTestBean.java):
package com.lucastheisen.beans;
import java.io.Serializable;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class MinimalTestBean implements Serializable {
private static final long serialVersionUID = 9045030165653014015L;
public String getAlsoThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called either" );
}
public List<String> getThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called" );
}
}
从此示例中您可以看到包含 ui 的
静态设置为 h:panelGroup
: Repeatrendered=false
,我认为这意味着 h:panelGroup
内的任何 EL 表达式都不会被执行。 EL 表达式只调用抛出 RuntimeException 的 getter。然而,ui:repeat
实际上正在为其列表调用 getter,从而导致异常,即使它一开始就不应该被渲染。如果您注释掉 ui:repeat
元素,则不会抛出异常(即使其他 EL 表达式保留在 h:panelGroup
中),正如我所期望的那样。
阅读 stackoverflow 上的其他问题让我相信这可能与经常提到的 先有鸡还是先有蛋问题,但我不确定到底为什么,也不知道该怎么办。我想将 PARTIAL_STATE_SAVING 设置为 false 可能会有所帮助,但希望避免内存影响。
---- 原始问题 ----
基本上,我有一个页面,使用
包裹 < 有条件地呈现部分。 ui:include src="pageXXX.xhtml" />
(根据此 答案)。问题是,如果其中一个 pageXXX.xhtml 内部有
,即使包含
,它似乎也会被处理。 code> 有 rendered=false
。这是一个问题,因为我的某些部分依赖于应在它们之前访问的其他部分的初始化。为什么包含的 pageXXX.xhtml 会被处理?
这是一个令人痛苦的错误,并且非常难以归结为一个小示例,但这是我可以构建的演示该问题的最小案例。首先是一个基本页面:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:include></title>
</h:head>
<h:body>
<h:form>
<h1>#{testBean.title}</h1>
<h:panelGroup rendered="#{testBean.modeOne}">
<ui:include src="modeOne.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{testBean.modeTwo}">
<ui:include src="modeTwo.xhtml" />
</h:panelGroup>
</h:form>
</h:body>
</html>
正如您所看到的,该页面将根据 testBean bean 中的值有条件地包含 modeOne 页面或 modeTwo 页面。然后你有 modeOne (默认):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<span>Okay, I'm ready. Take me to </span>
<h:commandLink action="#{testBean.setModeTwo}">mode two.</h:commandLink>
</ui:composition>
</html>
在我的现实世界应用程序中,它是一个设置 modeTwo 所需内容的页面。设置完成后,此页面上的操作将引导您进入 modeTwo:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<div>Here is your list:</div>
<ui:repeat value="#{testBeanToo.list}" var="item">
<div>#{item}</div>
</ui:repeat>
</ui:composition>
</html>
modeTwo 页面基本上在 ui:repeat 中显示 modeOne 页面的详细信息,因为实际信息位于集合中。主要托管 bean (TestBean):
package test.lucastheisen.beans;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private Mode mode;
@ManagedProperty( value="#{testBeanToo}" )
private TestBeanToo testBeanToo;
public TestBean() {
System.out.println( "constructing TestBean" );
setModeOne();
}
public String getTitle() {
System.out.println( "\ttb.getTitle()" );
return mode.getTitle();
}
public boolean isModeOne() {
return mode == Mode.One;
}
public boolean isModeTwo() {
return mode == Mode.Two;
}
public void setModeOne() {
this.mode = Mode.One;
}
public void setModeTwo() {
testBeanToo.getReadyCauseHereICome();
this.mode = Mode.Two;
}
public void setTestBeanToo( TestBeanToo testBeanToo ) {
this.testBeanToo = testBeanToo;
}
private enum Mode {
One("Mode One"),
Two("Mode Two");
private String title;
private Mode( String title ) {
this.title = title;
}
public String getTitle() {
return title;
}
}
}
是所有主要数据的 bean,TestBeanToo bean 将用于详细信息:
package test.lucastheisen.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBeanToo implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private ObjectWithList objectWithList = null;
public TestBeanToo() {
System.out.println( "constructing TestBeanToo" );
}
public String getTitle() {
System.out.println( "\ttb2.getTitle()" );
return "Test Too";
}
public List<String> getList() {
System.out.println( "\ttb2.getList()" );
return objectWithList.getList();
}
public void getReadyCauseHereICome() {
System.out.println( "\ttb2.getList()" );
objectWithList = new ObjectWithList();
}
public class ObjectWithList {
private List<String> list;
public ObjectWithList() {
list = new ArrayList<String>();
list.add( "List item 1" );
list.add( "List item 2" );
}
public List<String> getList() {
return list;
}
}
}
Original question is below, but as I have come up with a more minimal example to demonstrate this problem, and figured it should go at the top.
Anyway, it appears that ui:repeat
tags are processed before checking to see if parent elements are actually rendered. To recreate this, here is the facelet (minimalTest.xhtml):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:repeat> inside <h:panelGroup rendered="false"></title>
</h:head>
<h:body>
<h:form>
<h1>Testing</h1>
<h:panelGroup rendered="false">
<span>#{minimalTestBean.alsoThrowsException}</span>
<ul>
<ui:repeat value="#{minimalTestBean.throwsException}" var="item">
<li>#{item}</li>
</ui:repeat>
</ul>
</h:panelGroup>
</h:form>
</h:body>
</html>
With using this bean (MinimalTestBean.java):
package com.lucastheisen.beans;
import java.io.Serializable;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class MinimalTestBean implements Serializable {
private static final long serialVersionUID = 9045030165653014015L;
public String getAlsoThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called either" );
}
public List<String> getThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called" );
}
}
From this example you can see that the h:panelGroup
that contains the ui:repeat
is statically set to rendered=false
which I would assume would mean that none of the EL expressions inside of that h:panelGroup
would get executed. The EL expressions just call getters which throw a RuntimeException. However, the ui:repeat
is actually calling the getter for its list thus causing the exception even though it should not be getting rendered in the first place. If you comment out the ui:repeat
element, no exceptions get thrown (even though the other EL expression remains in the h:panelGroup
) as I would expect.
Reading other questions here on stackoverflow leads me to believe that is likely related to the oft-referred-to chicken/egg issue, but I am not sure exactly why, nor what to do about it. I imagine setting the PARTIAL_STATE_SAVING to false might help, but would like to avoid the memory implications.
---- ORIGINAL QUESTION ----
Basically, I have a page that conditionally renders sections using <h:panelGroup rendered="#{modeXXX}">
wrapped around <ui:include src="pageXXX.xhtml" />
(per this answer). The problem is that if one of the pageXXX.xhtml has a <ui:repeat>
inside of it, it seems to get processed even when the containing <h:panelGroup>
has rendered=false
. This is a problem because some of my sections rely on having been initialized by other sections that should be visited before them. Why is the included pageXXX.xhtml getting processed?
This is a painful bug and incredibly hard to boil down to a small example, but here is the most minimal case I could build that demonstrates the issue. First a base page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:include></title>
</h:head>
<h:body>
<h:form>
<h1>#{testBean.title}</h1>
<h:panelGroup rendered="#{testBean.modeOne}">
<ui:include src="modeOne.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{testBean.modeTwo}">
<ui:include src="modeTwo.xhtml" />
</h:panelGroup>
</h:form>
</h:body>
</html>
As you can see this page will conditionally include either the modeOne page or the modeTwo page based upon the value in the testBean bean. Then you have modeOne (the default):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<span>Okay, I'm ready. Take me to </span>
<h:commandLink action="#{testBean.setModeTwo}">mode two.</h:commandLink>
</ui:composition>
</html>
Which in my real world app would be a page that sets up things needed by modeTwo. Once set up, an action on this page will direct you to modeTwo:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<div>Here is your list:</div>
<ui:repeat value="#{testBeanToo.list}" var="item">
<div>#{item}</div>
</ui:repeat>
</ui:composition>
</html>
The modeTwo page basically presents a details for the modeOne page in a ui:repeat as the actual information is in a collection. The main managed bean (TestBean):
package test.lucastheisen.beans;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private Mode mode;
@ManagedProperty( value="#{testBeanToo}" )
private TestBeanToo testBeanToo;
public TestBean() {
System.out.println( "constructing TestBean" );
setModeOne();
}
public String getTitle() {
System.out.println( "\ttb.getTitle()" );
return mode.getTitle();
}
public boolean isModeOne() {
return mode == Mode.One;
}
public boolean isModeTwo() {
return mode == Mode.Two;
}
public void setModeOne() {
this.mode = Mode.One;
}
public void setModeTwo() {
testBeanToo.getReadyCauseHereICome();
this.mode = Mode.Two;
}
public void setTestBeanToo( TestBeanToo testBeanToo ) {
this.testBeanToo = testBeanToo;
}
private enum Mode {
One("Mode One"),
Two("Mode Two");
private String title;
private Mode( String title ) {
this.title = title;
}
public String getTitle() {
return title;
}
}
}
Is the bean for all the main data, and the TestBeanToo bean would be for the details:
package test.lucastheisen.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBeanToo implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private ObjectWithList objectWithList = null;
public TestBeanToo() {
System.out.println( "constructing TestBeanToo" );
}
public String getTitle() {
System.out.println( "\ttb2.getTitle()" );
return "Test Too";
}
public List<String> getList() {
System.out.println( "\ttb2.getList()" );
return objectWithList.getList();
}
public void getReadyCauseHereICome() {
System.out.println( "\ttb2.getList()" );
objectWithList = new ObjectWithList();
}
public class ObjectWithList {
private List<String> list;
public ObjectWithList() {
list = new ArrayList<String>();
list.add( "List item 1" );
list.add( "List item 2" );
}
public List<String> getList() {
return list;
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在渲染视图时,
不会检查自身的rendered
属性(实际上没有)及其父级。考虑使用 Tomahawk 的
代替。<ui:repeat>
does not check therendered
attribute of itself (it has actually none) and its parents when the view is to be rendered. Consider using Tomahawk's<t:dataList>
instead.