如何通过导航菜单 ajax-refresh 动态包含内容? (JSF SPA)
Posted
技术标签:
【中文标题】如何通过导航菜单 ajax-refresh 动态包含内容? (JSF SPA)【英文标题】:How to ajax-refresh dynamic include content by navigation menu? (JSF SPA) 【发布时间】:2011-10-29 20:03:35 【问题描述】:我刚刚学习 JSF 2,感谢这个网站,我在这么短的时间内学到了很多东西。
我的问题是关于如何为我的所有 JSF 2 页面实现通用布局,并且每当我从不同的面板单击链接/菜单时,只刷新页面的内容部分而不是整个页面。我正在使用 Facelets 方法,它可以执行我想要的操作,除了每次单击面板中的链接(例如左侧面板中的菜单项)时,整个页面都会刷新。我正在寻找的是一种仅刷新页面内容部分的方法。为了说明这一点,下面是我的目标页面布局。
没有发布我的代码,因为我不确定 Facelets 是否可以做到这一点。除了 Facelets 之外,还有其他方法更适合我的要求吗?
【问题讨论】:
【参考方案1】:如果你只想刷新页面的一部分,只有两种方法可以去(对于一般的 web,而不仅仅是 JSF)。您必须使用框架或 Ajax。 JSF 2 原生支持 ajax,查看 f:ajax 标记以仅更新 1 个组件而无需重新加载整个页面。
【讨论】:
就我目前的经验而言,这在包含包含表单的内容时效果不佳。 true...可能还需要将整个部分包装在一个表单中,或者将表单包含在 ajaxified 部分中 我更多地指的是使用<ui:include src="#bean.dynamicinclude" />
语法。由于某种原因,在回发的恢复视图期间解析 src 的方式与在渲染响应期间解析的方式不同,这反过来又导致找不到表单,因此没有处理任何内容。但这也可能只是一个 Mojarra 错误。我不知道。然而。
有趣的是,我尝试了 MyFaces 2.1.1,它只是在渲染响应期间没有显示所需的动态包含。
要使工作 ui:include src="..." 与 facelets 1.1.x 一样,您必须将 web 配置参数 org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS 设置为 true。详情请见MYFACES-3271。【参考方案2】:
Netbeans 提供了一个向导,可以使用 JSF 轻松创建建议的布局。因此,最好的开始方法是查看Facelets Template Wizard 并查看生成的源代码。
【讨论】:
ajax 加载/刷新部分怎么样? Netbeans 是否也生成该部分?我不这么认为。 是的,你是对的。Netbeans 不会生成该部分。但它有一个很好的向导来定义布局。 ajax 部分显然必须手动完成。有很多方法可以做到这一点,从使用“rendered”属性到使用 ui:include src="..."。如果您在一个页面上有多个表单并且您需要保持“同步”状态,您可以在 myfaces 上使用此 hack: 见MYFACES-3159【参考方案3】:一个简单的方法是以下视图:
<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>
用这个豆子:
@ManagedBean
@ViewScoped
public class Bean implements Serializable
private String page;
@PostConstruct
public void init()
page = "include1"; // Default include.
// +getter+setter.
在这个例子中,实际的包含模板是/WEB-INF/includes
文件夹中的include1.xhtml
、include2.xhtml
和include3.xhtml
(文件夹和位置完全由您选择;文件只是按顺序放置在/WEB-INF
中通过猜测浏览器地址栏中的 URL 到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 处于活动状态,从而导致在回发期间使用错误的 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">
呈现了一个 <div>
,所以应该足够好了。
最后但同样重要的是,这种 SPA(单页应用程序)方法对 SEO 不友好。所有页面都不能被搜索机器人索引,也不能被最终用户收藏,您可能需要在客户端中摆弄 HTML5 历史记录并提供服务器端后备。此外,对于带有表单的页面,相同的视图范围 bean 实例将在所有页面中重用,导致当您导航回先前访问的页面时,范围界定行为不直观。我建议使用模板方法,而不是如本答案第二部分所述: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)。
【讨论】:
正是我要找的东西!!!加上实现干净且易于理解。对我来说幸运的是,我计划使用的应用程序更像是一个企业 LAN 应用程序,而且我的用户不需要它对 SEO 友好、可搜索(如果 searchbotable 曾经是一个词):) 和可收藏的,这就是为什么它喜欢为我量身定制的要求。非常感谢。 嗨,感谢 BalusC 的精彩回答。我在这个模板中有一个像 include1 这样的提交表单,我想通过命令按钮转到 include2,但它需要单击两次。有没有办法纠正这个问题? @BulusC:如何将 fixviewstate.js 用于 primeface4(<p:panelMenu>
而不是一个简单的 <ul>
菜单,我需要在 <p:menuitem>
中设置什么属性来呈现内容?跨度>
以上是关于如何通过导航菜单 ajax-refresh 动态包含内容? (JSF SPA)的主要内容,如果未能解决你的问题,请参考以下文章