问题描述
假设我想在我的应用程序中导航,并包括不同的facelet页面动态。我有这样一个commandLink:
< H:commandLink值=链接行动=#{navigation.goTo('someTest')}>
< F:AJAX渲染=:内容/>
< / H:commandLink>
这是我包括facelet中:
< H:格式ID =内容>
<用户界面:包括SRC =#{navigation.includePath}/>
< /小时:形式GT;
导航类:
公共类导航{
私人字符串的viewName;
公共无效后藤(字符串的viewName){
this.viewName =的viewName;
}
公共字符串getIncludePath(){
返回与resolvePath(的viewName);
}
}
我看到类似的例子,但是这并不当然工作。由于 UI:包括
是一个taghandler时,包括发生长时间我的导航监听器被调用之前。旧的facelet是包含,而不是新的。到目前为止,我得到它。
现在的头痛部分:如何动态包括facelet中,基于一个ActionListener?我想包括的facelet在preRender事件,RENDER_RESPONSE前的PhaseListener。这两个工作,但在事件监听器,我不能包括facelet中包含的其他preRender事件,并在PhaseListener在我得到重复ID在附带的facelet一些点击后。然而,检查组件树告诉我,没有重复的成分都没有。也许这两种想法都是不以善可言。
我需要一个解决方案,在该页面的 UI:包括
,或其中包括facelet中的Java类,不必知道的网页,这将被收录,也没有确切的路径。有没有人之前解决这个问题?我该怎么办呢?
我使用JSF 2.1和Mojarra 2.1.15
所有你需要重现该问题是这个bean:
@Named
公共类部分实现Serializable {
私有静态最后长的serialVersionUID = 1L;
私人最终名单,其中,字符串>值=新的ArrayList<字符串>();
公共部分(){
values.add(测试);
}
公共无效setInclude(字符串包括){
}
公开名单<字符串> GetValues方法(){
返回值;
}
}
这在你的索引文件:
< H:头>
< H:outputScript库=javax.facesNAME =jsf.js/>
< / H:头>
< H:身体GT;
< H:格式ID =topform>
< H:panelGroup中ID =容器>
<我:包括SRC =/ test.xhtml/>
< /小时:panelGroup中>
< /小时:形式GT;
< / H:身体GT;
和这text.xhtml
< UI:重复值=#{some.values}VAR =VAL>
< H:commandLink值=#{} VAL行动=#{some.setInclude(VAL)}>
< F:AJAX渲染=:topform:集装箱/>
< / H:commandLink>
< / UI:重复>
这足以产生这样的错误:
javax.faces.FacesException:不能添加相同的组件两次:topform:j_id-549384541_7e08d92c
有关 OmniFaces ,我也曾经尝试过通过创建一个<○:包括>
作为的代替的它做了 FaceletContext#includeFacelet()
的<一个href="http://docs.oracle.com/javaee/6/api/javax/faces/component/UIComponent.html#en$c$cChildren%28javax.faces.context.FacesContext%29"相对=nofollow> 连接codeChildren()
方法。这样的权利包括的facelet期间被记住恢复视图阶段和所包含的组件树只改变在渲染阶段,这正是我们想要实现这个结构。
下面是一个基本的开球例如:
@FacesComponent(com.example.Include)
公共类包括扩展UIComponentBase {
@覆盖
公共字符串getFamily(){
返回com.example.Include;
}
@覆盖
公共布尔getRendersChildren(){
返回true;
}
@覆盖
公共无效连接codeChildren(FacesContext的上下文)抛出IOException异常{
的getChildren()清()。
((FaceletContext)context.getAttributes()得到(FaceletContext.FACELET_CONTEXT_KEY)。)includeFacelet(这一点,getSrc())。
super.en codeChildren(上下文);
}
公共字符串getSrc(){
回报(字符串)getStateHelper()的eval(SRC)。
}
公共无效setSrc(字符串SRC){
。getStateHelper()把(src用户,SRC);
}
}
这是注册于 .taglib.xml
如下:
&LT;标签&GT;
&LT;标签名称&gt;&包括LT; /标签名称&gt;
&其中;成分&gt;
&lt;成分型&GT; com.example.Include&LT; /组件式&GT;
&LT; /成分&gt;
&LT;属性&GT;
&LT;名称&gt;&SRC LT; /名称&gt;
&LT;要求&GT;真&LT; /要求和GT;
&LT;类型&GT; java.lang.String中&LT; /类型&GT;
&LT; /属性&GT;
&LT; /标签&GT;
这正常工作有以下看法:
&LT; H:outputScript名=fixViewState.js/&GT;
&LT; H:形式GT;
&LT; UI:重复值=#{includeBean.includes}VAR =包括&GT;
&LT; H:的commandButton值=包含#{包括}行动=#{includeBean.setInclude(包括)}&GT;
&LT; F:AJAX渲染=:包括/&GT;
&LT; / H:的commandButton&GT;
&LT; / UI:重复&GT;
&LT; /小时:形式GT;
&LT; H:panelGroup中的id =包括&GT;
&LT;我:包括SRC =#{includeBean.include} .xhtml/&GT;
&LT; /小时:panelGroup中&GT;
和以下支持bean:
@ManagedBean
@ViewScoped
公共类IncludeBean实现Serializable {
私人列表&LT;字符串&GT;包括= Arrays.asList(include1,措施包括1,include3);
私人字符串包括= includes.get(0);
私人列表&LT;字符串&GT; getIncludes(){
返回包括;
}
公共无效setInclude(字符串包括){
返回this.include =包括:
}
公共字符串getInclude(){
返回包括;
}
}
(本例中预计,包括文件 include1.xhtml
, include2.xhtml
和 include3.xhtml
在相同的基础文件夹作为主文件)的
在 fixViewState.js
可以在这个回答中找到:JSF的commandButton工程第二次点击。这个脚本是强制性的,为了修复 JSF问题790 由此视图状态迷路时,有多个ajax的形式而更新了对方的父母。
另外请注意,这样,每个包含文件可以有自己的&LT; H:形式GT;
在必要的时候,所以你不一定需要把它周围的包括:
此方法工作正常,在Mojarra,即使回发请求,从形式到来里面包括,但是它没有硬盘的MyFaces的初始请求已经在以下异常:
显示java.lang.NullPointerException
在org.apache.myfaces.view.facelets.impl.FaceletCompositionContextImpl.generateUniqueId(FaceletCompositionContextImpl.java:910)
在org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.generateUniqueId(DefaultFaceletContext.java:321)
在org.apache.myfaces.view.facelets.compiler.UIInstructionHandler.apply(UIInstructionHandler.java:87)
在javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:49)
在org.apache.myfaces.view.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:158)
在org.apache.myfaces.view.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:57)
在org.apache.myfaces.view.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:48)
在org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:394)
在org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:448)
在org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:426)
在org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:244)
在com.example.Include.en codeChildren(Include.java:54)
MyFaces的基本发布的视图生成时结束期间的facelet背景下,使其无法在视图渲染时间,导致NPE的,因为内部状态有几个调零输出特性。这是但是,可以添加的,而不是在渲染时间的facelet文件各个组件。我并没有真正有过调查,如果这是我的错,或MyFaces的故障的时间。这也是为什么它没有结束在OmniFaces呢。
如果您使用的是Mojarra无论如何,随意使用它。然而,我强烈建议用同样的页面上的所有可能的用例测试它彻底。 Mojarra有一些状态保存相关的怪癖其中的也许的使用此结构时失败。
Suppose I want to navigate in my application, and include different facelet pages dynamically. I have a commandLink like this:
<h:commandLink value="Link" action="#{navigation.goTo('someTest')}">
<f:ajax render=":content" />
</h:commandLink>
And this is where I include the facelet:
<h:form id="content">
<ui:include src="#{navigation.includePath}" />
</h:form>
The Navigation class:
public class Navigation {
private String viewName;
public void goTo(String viewName) {
this.viewName = viewName;
}
public String getIncludePath() {
return resolvePath(viewName);
}
}
I have seen similar examples, but this doesn't work of course. As ui:include
is a taghandler, the include happens long before my navigation listener is invoked. The old facelet is included, instead of the new. So far I get it.
Now to the headache part: How can I dynamically include a facelet, based on an actionListener? I tried to include the facelet in a preRender event, and a phaseListener before RENDER_RESPONSE. Both work, but in the event listener I can't include a facelet which contains an other preRender event, and in the phaseListener I get duplicate Id's after some clicks in the included facelet. However, inspecting the component tree tells me, there are no duplicate components at all. Maybe these two ideas were not to good at all..
I need a solution, where the page with the ui:include
, or the Java class which includes the facelet, doesn't have to know the pages, which will be included, nor the exact path. Did anybody solve this problem before? How can I do it?
I am using JSF 2.1 and Mojarra 2.1.15
All you need to reproduce the Problem is this 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;
}
}
This in your index file:
<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>
And this in 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>
That's enough to produce an error like this:
javax.faces.FacesException: Cannot add the same component twice: topform:j_id-549384541_7e08d92c
For OmniFaces, I've also ever experimented with this by creating an <o:include>
as UIComponent
instead of a TagHandler
which does a FaceletContext#includeFacelet()
in the encodeChildren()
method. This way the right included facelet is remembered during restore view phase and the included component tree only changes during render response phase, which is exactly what we want to achieve this construct.
Here's a basic kickoff example:
@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);
}
}
Which is registered in .taglib.xml
as follows:
<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>
This works fine with the following view:
<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>
And the following backing 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;
}
}
(this example expects include files include1.xhtml
, include2.xhtml
and include3.xhtml
in the same base folder as the main file)
The fixViewState.js
can be found in this answer: JSF commandButton works on second click. This script is mandatory in order to fix JSF issue 790 whereby the view state get lost when there are multiple ajax forms which update each other's parent.
Also note that this way each include file can have its own <h:form>
when necessary, so you don't necessarily need to put it around the include.
This approach works fine in Mojarra, even with postback requests coming from forms inside the include, however it fails hard in MyFaces with the following exception during initial request already:
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 basically releases the Facelet context during end of view build time, making it unavailable during view render time, resulting in NPEs because the internal state has several nulled-out properties. It's however possible to add individual components instead of a Facelet file during render time. I didn't really have had the time to investigate if this is my fault or MyFaces' fault. That's also why it didn't end up in OmniFaces yet.
If you're using Mojarra anyway, feel free to use it. I however strongly recommend to test it thoroughly with all possible use cases on the very same page. Mojarra has some state saving related quirks which might fail when using this construct.
这篇关于动态的AJAX导航以&lt;用户界面:包括&GT;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!