36

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

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

在此处输入图像描述 没有发布我的代码,因为我不确定 Facelets 是否可以做到这一点。除了 Facelets 之外,还有其他更适合我要求的方法吗?

4

3 回答 3

67

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

<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.xhtmlin/WEB-INF/includes文件夹(文件夹和位置完全由您选择;这些文件只是放置在其中/WEB-INF防止通过猜测浏览器地址栏中的 URL 来直接访问)。

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

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

...
<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">呈现 a <div>,所以应该足够好。

最后但同样重要的是,这种 SPA(单页应用程序)方法对 SEO 不友好。所有页面都不能被搜索机器人索引,也不能被最终用户收藏,您可能需要在客户端中摆弄 HTML5 历史记录并提供服务器端后备。此外,在带有表单的页面的情况下,相同的视图范围 bean 实例将在所有页面中重用,当您导航回以前访问的页面时,会导致不直观的范围行为。我建议改用模板方法,如本答案第二部分所述:如何使用 JSF 2.0 Facelets 在 XHTML 中包含另一个 XHTML?另请参阅如何在 JSF 中导航?如何使 URL 反映当前页面(而不是上一个页面)

于 2011-08-18T20:47:54.257 回答
2

如果您只想刷新页面的一部分,则只有两种方法可以走(对于一般的网络,而不仅仅是 JSF)。您必须使用框架或 Ajax。JSF 2 原生支持 ajax,查看 f:ajax 标记以仅更新 1 个组件而无需重新加载整个页面。

于 2011-08-18T14:14:01.520 回答
-1

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

于 2011-08-18T19:01:20.960 回答