如何通过导航菜单实现JSF SPA中的动态包含内容的ajax刷新?

36

我正在学习JSF 2,感谢这个网站,在短时间内我学到了很多。

我的问题是如何在所有JSF 2页面中实现一个通用布局,并且只有页面内容部分在我从不同的面板点击链接/菜单时刷新,而不是整个页面。我使用Facelets方法可以实现我想要的效果,但每次我从面板(例如左侧面板的菜单项)点击链接时,整个页面都会刷新。我要寻找的是一种方式来仅刷新页面的内容部分。为了说明这一点,下面是我目标页面布局。

enter image description here 没有发布我的代码,因为我不确定Facelets能否做到这一点。除了Facelets之外,还有其他更适合我的要求的方法吗?

3个回答

66
一个直接的方法是使用以下视图:
<h:panelGroup id="header" layout="block">
    <h1>Header</h1>
</h:panelGroup>
<h:panelGroup id="menu" layout="block">
    <h:form>
        <f:ajax render=":content">
            <ul>
                <li><h:commandLink value="include1" action="#{bean.setPage('include1')}" /></li>            
                <li><h:commandLink value="include2" action="#{bean.setPage('include2')}" /></li>            
                <li><h:commandLink value="include3" action="#{bean.setPage('include3')}" /></li>            
            </ul>
        </f:ajax>
    </h:form>
</h:panelGroup>
<h:panelGroup id="content" layout="block">
    <ui:include src="/WEB-INF/includes/#{bean.page}.xhtml" />
</h:panelGroup>

使用这个 bean:
@ManagedBean
@ViewScoped
public class Bean implements Serializable {

     private String page;

     @PostConstruct
     public void init() {
         page = "include1"; //  Default include.
     }

     // +getter+setter.
 }

在这个例子中,实际的包含模板是位于/WEB-INF/includes文件夹中的include1.xhtmlinclude2.xhtmlinclude3.xhtml(文件夹和位置完全由您自己选择;文件只是放置在/WEB-INF中为了防止通过猜测浏览器地址栏中的URL直接访问)。
此方法适用于所有MyFaces 2.x版本,但在Mojarra中需要至少2.3.x。如果您使用的是早于2.3.0的Mojarra版本,则当<ui:include>页面反过来包含<h:form>时,所有内容都会失败。任何回发都会失败,因为它完全缺少视图状态。您可以通过升级到至少Mojarra 2.3.0或使用此答案中找到的脚本h: commandButton / h: commandLink第一次单击不起作用,仅在第二次单击时起作用来解决此问题。或者,如果您已经使用PrimeFaces并且专门使用<p:xxx> ajax,则它已经自动考虑到了。

此外,请确保您使用的是至少Mojarra 2.1.18版本,因为旧版本无法保持视图范围bean的活动状态,导致在postback期间使用错误的包含。如果您无法升级,则需要退回到下面(相对笨拙)的条件渲染视图而不是条件构建视图的方法:

...
<h:panelGroup id="content" layout="block">
    <ui:fragment rendered="#{bean.page eq 'include1'}">
        <ui:include src="include1.xhtml" />
    </ui:fragment>
    <ui:fragment rendered="#{bean.page eq 'include2'}">
        <ui:include src="include2.xhtml" />
    </ui:fragment>
    <ui:fragment rendered="#{bean.page eq 'include3'}">
        <ui:include src="include3.xhtml" />
    </ui:fragment>
</h:panelGroup>

缺点是视图会变得相对较大,即使根据渲染条件不使用所有相关的托管bean,也可能会不必要地初始化它们。有关<ui:include src="#{...}"><x:someComponent rendered="#{...}">的深入解释,请参见JSTL in JSF2 Facelets... makes sense?

至于元素的定位,那只是应用正确的CSS的问题。这超出了JSF的范围 :) 至少,<h:panelGroup layout="block">呈现一个<div>,所以应该足够好了。

最后,这种单页应用程序(SPA)方法不利于SEO。所有页面都无法被搜索引擎机器人索引或被终端用户添加书签,您可能需要在客户端调整HTML5历史记录并提供服务器端回退。此外,在具有表单的页面中,相同的视图范围bean实例将在所有页面中重复使用,导致在返回以前访问过的页面时出现不直观的作用域行为。我建议采用模板方法,如本答案的第二部分所述:如何在JSF 2.0 Facelets中包含另一个XHTML?另请参阅如何在JSF中导航?如何使URL反映当前页面(而不是上一页)?

正是我所需要的!!!而且实现干净易懂。幸运的是,我计划使用它的应用程序更多地是企业局域网应用程序,我的用户不需要它具有SEO友好性、可搜索性(如果“可搜索性”是一个词的话):)和可书签化,这就像为我的要求量身定制一样。非常感谢你。 - royjavelosa
你好,非常感谢BalusC提供的出色答案。我在这个模板中有一个提交表单,类似于include1,我想通过一个命令按钮跳转到include2,但需要点击两次。有没有办法来纠正这个问题? - Sima
@BulusC:我该如何在Primeface4(<p:commandButton)中使用fixviewstate.js呢?谢谢。 - Sima
@Sima:它可以自动识别PrimeFaces(特别是jQuery)。 - BalusC
@BalusC:如果我使用Primefaces的<p:panelMenu>而不是一个简单的<ul>菜单,那么我需要在<p:menuitem>中设置什么属性来呈现内容? - Alvaro Pedraza
显示剩余7条评论

2
如果您只想刷新页面的一部分,通常有两种方法(不仅仅是针对JSF而言)。您可以使用frames或Ajax。JSF 2原生支持ajax,请查看f:ajax标签,以更新单个组件而无需重新加载整个页面。

1
就我所经历的来看,包含表单的包含内容使用这种方法并不是很有效。 - BalusC
可能需要尝试将整个部分包装在表单中,或者将表单包含在ajax化的部分中。 - GBa
我更多地是指使用<ui:include src =“#{bean.dynamicinclude}”/>语法。由于某种原因,在回发的恢复视图期间,src的解析方式与呈现响应期间不同,这反过来导致找不到表单,因此没有进行任何处理。但这也可能只是Mojarra的一个错误。我不确定。 - BalusC
有趣的是,我尝试了MyFaces 2.1.1,但它在渲染响应期间就无法显示所需的动态包含。 - BalusC
为了让ui:include src="..."像facelets 1.1.x一样工作,您需要将web配置参数org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS设置为true。有关详细信息,请参见MYFACES-3271 - lu4242
我已经测试过了(实际上这是一个常见问题),你需要将org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS和org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS_PRESERVE_STATE都设置为true,这样内容就会完全保存和恢复,并且验证将按预期工作。 - lu4242

0

Netbeans提供了一个向导,使用JSF可以轻松创建建议的布局。因此,开始的最佳方式是查看Facelets模板向导并查看生成的源代码。


那么ajax的加载/刷新部分怎么样?Netbeans也会生成那部分吗?我觉得不会。 - BalusC
是的,你说得对。Netbeans不会生成那部分代码。但它有一个很好的向导来定义布局。Ajax部分显然必须手动完成。有许多方法可以做到这一点,从使用“rendered”属性到使用ui:include src="..."。如果您在页面上有多个表单,并且需要保持“同步”状态,则可以在myfaces上使用此hack:<script type="text/javascript"> window.myfaces = window.myfaces || {}; myfaces.config = myfaces.config || {}; myfaces.config.no_portlet_env = true; </script> 请参见MYFACES-3159 - lu4242

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接