如何通过导航菜单ajax-refresh动态包含内容? (JSF SPA)

时间:2011-08-18 14:03:12

标签: jsf single-page-application facelets uiinclude ajax-update

我正在学习JSF 2,感谢我在这么短的时间内学到了很多东西。

我的问题是关于如何为我的所有JSF 2页面实现一个公共布局,并且每当我从其他面板单击链接/菜单时,只有页面的内容部分不刷新整个页面。我正在使用Facelets方法,它做了我想做的事情,除了每次点击面板中的链接(例如左面板的菜单项)时,整个页面都会刷新。我正在寻找的是一种只刷新页面内容部分的方法。为了说明这一点,我的目标是pagelayout。

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

3 个答案:

答案 0 :(得分:61)

一种直截了当的方法是以下观点:

<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.
 }

在此示例中,include1.xhtml文件夹中的实际包含模板为include2.xhtmlinclude3.xhtml/WEB-INF/includes(文件夹和位置完全免费供您选择;文件为只需通过猜测浏览器地址栏中的网址,将/WEB-INF放入prevent direct access

此方法适用于所有MyFaces版本,但至少需要Mojarra 2.3.0。如果您使用的是早于2.3.0的Mojarra版本,那么当<ui:include>页面依次包含<h:form>时,这一切都会失败。任何回发都将失败,因为它完全缺少视图状态。您可以通过升级到最低限度的Mojarra 2.3.0或使用此答案h:commandButton/h:commandLink does not work on first click, works only on second click中的脚本来解决此问题,或者如果您已经在使用JSF实用程序库OmniFaces,请使用其FixViewState script。或者,如果您已经使用PrimeFaces并专门使用<p:xxx> ajax,那么它已经被透明地考虑在内了。

此外,请确保您使用最低限度的Mojarra 2.1.18,因为旧版本将无法使视图范围bean保持活动状态,从而导致在回发期间使用错误的包含。如果你无法升级,那么你需要回到下面(相对笨拙)的方法来有条件地渲染视图而不是有条件地构建视图:

...
<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可能会被不必要地初始化,即使它们不会按照渲染条件使用它们。

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

最后但并非最不重要的是,此SPA(单页应用程序)方法不适合SEO。所有页面都不能被搜索机器人编入索引,也不能被最终用户收藏,您可能需要在客户端中摆弄HTML5历史记录并提供服务器端备份。此外,对于带有表单的页面,同一个视图范围的bean实例将在所有页面中重用,导致在您导航回以前访问过的页面时出现不直观的作用域行为。我建议采用模板化方法,如本答案第2部分所述:How to include another XHTML in XHTML using JSF 2.0 Facelets?另见How to navigate in JSF? How to make URL reflect current page (and not previous one)

答案 1 :(得分:2)

如果您只想刷新页面的一部分,那么只有两种方法(对于Web而言,不仅仅是JSF)。你必须使用框架或Ajax。 JSF 2原生支持ajax,检查f:ajax标记只更新1个组件而不重新加载整个页面。

答案 2 :(得分:-1)

Netbeans提供了一个向导,使用JSF以最小的努力创建建议的布局。因此,最好的方法是查看Facelets Template Wizard并查看生成的源。