假设我想在我的应用程序中导航,并动态包含不同的facelet页面。我有一个像这样的commandLink:
<h:commandLink value="Link" action="#{navigation.goTo('someTest')}"> <f:ajax render=":content" /> </h:commandLink>
这是我包含小面的地方:
<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。到目前为止,我明白了。
ui:include
现在让我们头疼的是:如何基于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
对于OmniFaces,我也曾经通过在方法中创建<o:include>as UIComponent而不是a TagHandler进行过尝试。这样,在恢复视图阶段就可以记住正确的包含面,而包含组件树仅在渲染响应阶段发生变化,这正是我们想要实现的构造。FaceletContext#includeFacelet()encodeChildren()
<o:include>
UIComponent
TagHandler
FaceletContext#includeFacelet()
encodeChildren()
这是一个基本的启动示例:
@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如下:
.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; } }
(此示例期望include filesinclude1.xhtml,include2.xhtml并且include3.xhtml与主文件位于同一基本文件夹中)
include1.xhtml
include2.xhtml
include3.xhtml
本fixViewState.js可以在这个回答中找到:H:的commandButton / H:commandLink不首先点击工作,只能在第二次点击。为了修复JSF问题790,此脚本是必需的,从而在存在多个更新彼此父级的ajax表单时,视图状态会丢失。
fixViewState.js
还要注意,这种方式每个包含文件<h:form>在必要时都可以拥有自己的文件,因此您不必将其放在包含文件周围。
<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具有一些与状态保存有关的怪癖,这些怪癖在使用此构造时 可能会 失败。