如何通过导航菜单ajax刷新动态包含内容?(JSF SPA) [英] How to ajax-refresh dynamic include content by navigation menu? (JSF SPA)

查看:23
本文介绍了如何通过导航菜单ajax刷新动态包含内容?(JSF SPA)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚在学习 JSF 2 多亏了这个网站,我在这么短的时间内学到了很多东西.

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

没有发布我的代码,因为我不确定 Facelets 是否可以做到这一点.除了 Facelets 之外,还有其他更适合我的要求的方法吗?

解决方案

一个简单的方法是以下视图:

<h1>标题</h1></h:panelGroup><h:panelGroup id="menu" layout="block"><h:形式><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></f:ajax></h:form></h:panelGroup><h:panelGroup id="content" layout="block"><ui:include src="/WEB-INF/includes/#{bean.page}.xhtml"/></h:panelGroup>

用这个豆子:

@ManagedBean@ViewScoped公共类 Bean 实现了 Serializable {私人字符串页面;@PostConstruct公共无效初始化(){page = "include1";//默认包含.}//+getter+setter.}

在这个例子中,实际的包含模板是/WEB中的include1.xhtmlinclude2.xhtmlinclude3.xhtml-INF/includes 文件夹(文件夹和位置完全由您选择;文件只是放在 /WEB-INF 中以便通过猜测浏览器中的 URL 防止直接访问地址栏).

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

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

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

缺点是视图会变得相对较大,并且所有关联的托管 bean 可能会被不必要地初始化,即使它们不会按照呈现的条件使用.

至于元素的定位,这只是应用正确的 CSS 的问题.这超出了 JSF 的范围:) 至少,<h:panelGroup layout="block"> 渲染了一个

,所以应该足够好了.

最后但并非最不重要的是,这种 SPA(单页应用程序)方法对 SEO 不友好.所有页面都不能被搜索机器人索引,也不能被最终用户添加书签,您可能需要在客户端摆弄 HTML5 历史记录并提供服务器端回退.此外,对于带有表单的页面,将在所有页面中重用完全相同的视图范围 bean 实例,从而在您导航回之前访问过的页面时导致不直观的范围行为.我建议使用模板方法,而不是按照本答案的第二部分所述:如何使用 JSF 2.0 Facelets 在 XHTML 中包含另一个 XHTML? 另见 如何在 JSF 中导航?如何使 URL 反映当前页面(而不是前一个页面).

I'm just learning JSF 2 thanks to this site I had learned a lot in such a short time.

My question is regarding how to implement a common layout to all my JSF 2 pages and have only the content part of the page refresh not the whole page whenever I click a link/menu from a different panel. I am using the Facelets approach it does what I want except that each time I click a link from a panel (e.g. menu items from left panel) the whole page is refreshed. What I am looking for is a way to refresh only the content part of my page. To illustrate this below is my target pagelayout.

Did not post my code because I'm not sure if Facelets can do this . Are there other approach more suited for my requirement other than Facelets?

解决方案

A straightforward approach would be the following view:

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

With this bean:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

     private String page;

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

     // +getter+setter.
 }

In this example, the actual include templates are include1.xhtml, include2.xhtml and include3.xhtml in /WEB-INF/includes folder (folder and location is fully free to your choice; the files are just placed in /WEB-INF in order to prevent direct access by guessing the URL in browser's address bar).

This approach works in all MyFaces versions, but requires a minimum of Mojarra 2.3.0. In case you're using a Mojarra version older than 2.3.0, then this all fails when the <ui:include> page in turn contains a <h:form>. Any postback will fail because it is totally missing the view state. You can solve this by upgrading to minimally Mojarra 2.3.0 or with a script found in this answer h:commandButton/h:commandLink does not work on first click, works only on second click, or if you're already using JSF utility library OmniFaces, use its FixViewState script. Or, if you're already using PrimeFaces and exclusively use <p:xxx> ajax, then it's already transparently taken into account.

Also, make sure that you're using minimally Mojarra 2.1.18 as older versions will fail in keeping the view scoped bean alive, causing the wrong include being used during postback. If you can't upgrade, then you'd need to fall back to the below (relatively clumsy) approach of conditionally rendering the view instead of conditionally building the view:

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

The disadvantage is that the view would become relatively large and that all associated managed beans may be unnecessarily initialized even though when they would not be used as per the rendered condition.

As to positioning of the elements, that's just a matter of applying the right CSS. That's beyond the scope of JSF :) At least, <h:panelGroup layout="block"> renders a <div>, so that should be good enough.

Last but not least, this SPA (Single Page Application) approach is not SEO friendly. All the pages are not indexable by searchbots nor bookmarkable by endusers, you may need to fiddle around with HTML5 history in client and provide a server side fallback. Moreover, in case of pages with forms, the very same view scoped bean instance would be reused across all pages, resulting in unintuitive scoping behavior when you navigate back to a previously visited page. I'd suggest to go with templating approach instead as outlined in 2nd part of this answer: How to include another XHTML in XHTML using JSF 2.0 Facelets? See also How to navigate in JSF? How to make URL reflect current page (and not previous one).

这篇关于如何通过导航菜单ajax刷新动态包含内容?(JSF SPA)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆