假设我想在我的应用程序中导航,并动态包含不同的facelet页面。我有一个这样的commandLink:
<h:commandLink value="Link" action="#{navigation.goTo('someTest')}">
<f:ajax render=":content" />
</h:commandLink>
这就是我加入facelet的地方:
<h:form id="content">
<ui:include src="#{navigation.includePath}" />
</h:form>
导航类:
public class Navigation {
private String viewName;
public void goTo(String viewName) {
this.viewName = viewName;
}
public String getIncludePath() {
return resolvePath(viewName);
}
}
我见过类似的例子,但这当然不起作用。由于ui:include
是一个标记处理程序,因此包含会在我的导航侦听器调用之前发生。包含旧的facelet,而不是新的facelet。到目前为止,我明白了。
现在令人头疼的部分:如何基于actionListener动态包含facelet?我尝试将facelet包含在preRender事件中,并在RENDER_RESPONSE之前包含phaseListener。两者都有效,但在事件监听器中我不能包含一个包含其他preRender事件的facelet,而在phaseListener中,我在包含的facelet中点击一些后会得到重复的Id。但是,检查组件树告诉我,根本没有重复的组件。也许这两个想法根本不好......
我需要一个解决方案,其中包含ui:include
的页面或包含facelet的Java类不必知道将包含的页面,也不需要知道确切的路径。有没有人以前解决过这个问题?我该怎么办?
我正在使用JSF 2.1和Mojarra 2.1.15
你需要重现问题的是这个bean:
@Named
public class Some implements Serializable {
private static final long serialVersionUID = 1L;
private final List<String> values = new ArrayList<String>();
public Some() {
values.add("test");
}
public void setInclude(String include) {
}
public List<String> getValues() {
return values;
}
}
这在您的索引文件中:
<h:head>
<h:outputScript library="javax.faces" name="jsf.js" />
</h:head>
<h:body>
<h:form id="topform">
<h:panelGroup id="container">
<my:include src="/test.xhtml" />
</h:panelGroup>
</h:form>
</h:body>
这在text.xhtml
中<ui:repeat value="#{some.values}" var="val">
<h:commandLink value="#{val}" action="#{some.setInclude(val)}">
<f:ajax render=":topform:container" />
</h:commandLink>
</ui:repeat>
这足以产生这样的错误:
javax.faces.FacesException: Cannot add the same component twice: topform:j_id-549384541_7e08d92c
答案 0 :(得分:3)
对于OmniFaces,我也通过创建一个<o:include>
作为UIComponent
而不是一个TagHandler
进行了FaceletContext#includeFacelet()
的尝试。 encodeChildren()
方法。这样,在恢复视图阶段就会记住正确包含的facelet,并且包含的组件树仅在渲染响应阶段期间发生更改,这正是我们想要实现此构造的内容。
这是一个基本的启动示例:
@FacesComponent("com.example.Include")
public class Include extends UIComponentBase {
@Override
public String getFamily() {
return "com.example.Include";
}
@Override
public boolean getRendersChildren() {
return true;
}
@Override
public void encodeChildren(FacesContext context) throws IOException {
getChildren().clear();
((FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY)).includeFacelet(this, getSrc());
super.encodeChildren(context);
}
public String getSrc() {
return (String) getStateHelper().eval("src");
}
public void setSrc(String src) {
getStateHelper().put("src", src);
}
}
在.taglib.xml
中注册的内容如下:
<tag>
<tag-name>include</tag-name>
<component>
<component-type>com.example.Include</component-type>
</component>
<attribute>
<name>src</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
</tag>
这适用于以下视图:
<h:outputScript name="fixViewState.js" />
<h:form>
<ui:repeat value="#{includeBean.includes}" var="include">
<h:commandButton value="Include #{include}" action="#{includeBean.setInclude(include)}">
<f:ajax render=":include" />
</h:commandButton>
</ui:repeat>
</h:form>
<h:panelGroup id="include">
<my:include src="#{includeBean.include}.xhtml" />
</h:panelGroup>
以下支持bean:
@ManagedBean
@ViewScoped
public class IncludeBean implements Serializable {
private List<String> includes = Arrays.asList("include1", "include2", "include3");
private String include = includes.get(0);
private List<String> getIncludes() {
return includes;
}
public void setInclude(String include) {
return this.include = include;
}
public String getInclude() {
return include;
}
}
(此示例要求包含文件include1.xhtml
,include2.xhtml
和include3.xhtml
与主文件位于同一个基本文件夹中)
fixViewState.js
可在此答案中找到:h:commandButton/h:commandLink does not work on first click, works only on second click。此脚本是必需的,以便修复JSF issue 790,当有多个ajax表单更新彼此的父级时,视图状态会丢失。
另请注意,这样每个包含文件在必要时都可以拥有自己的<h:form>
,因此您不一定需要将它放在包含的位置。
这种方法在Mojarra中运行良好,即使回复请求来自include中的表单,但是在初始请求期间,MyFaces中的表单出现了以下异常:
java.lang.NullPointerException
at org.apache.myfaces.view.facelets.impl.FaceletCompositionContextImpl.generateUniqueId(FaceletCompositionContextImpl.java:910)
at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.generateUniqueId(DefaultFaceletContext.java:321)
at org.apache.myfaces.view.facelets.compiler.UIInstructionHandler.apply(UIInstructionHandler.java:87)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:49)
at org.apache.myfaces.view.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:158)
at org.apache.myfaces.view.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:57)
at org.apache.myfaces.view.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:48)
at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:394)
at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:448)
at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:426)
at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:244)
at com.example.Include.encodeChildren(Include.java:54)
MyFaces基本上在视图构建时间结束时释放Facelet上下文,使其在视图渲染时间内不可用,从而导致NPE,因为内部状态具有多个无效属性。但是,在渲染时可以添加单个组件而不是Facelet文件。我真的没有时间调查这是我的错还是MyFaces的错。这也是为什么它还没有在OmniFaces中结束的原因。
如果您正在使用Mojarra,请随意使用它。但是我强烈建议在同一页面上对所有可能的用例进行彻底测试。 Mojarra有一些与状态保存相关的怪癖,当使用这个构造时可能失败。