- 浏览: 11900 次
- 性别:
- 来自: 深圳
最新评论
JSF 的最大卖点在于它是一种基于组件的框架。这意味着您可以实现供其他人重用的组件。这种强大的重用机制在 JSF 1 中基本上是不可能实现的,因为在 JSF 1 中实现组件是非常困难的事情。
然而,正如 第 2 部分 所述,JSF 2 通过一种名为复合组件 的新特性简化了组件的实现 — 无需 Java 代码和配置。这一特性可以说是 JSF 2 中最重要的部分,因为它真正实现了 JSF 组件的潜力。
在这份有关 JSF 2 的第三篇也是最后一篇文章中,我将展示如何利用新的 Ajax 和事件处理功能(也在 JSF 2 中引入)构建复合组件特性,要从 JSF 2 中获得最大收益,需要遵循下面的技巧:
- 技巧 1:组件化
- 技巧 2:Ajax 化
- 技巧 3:展示进度
对于第一个技巧,我将简要回顾已在 第 2 部分 中详细描述过的两个组件。对于后面的技巧,我将展示如何使用 Ajax 和事件处理功能来改造这些组件。
我在 第 1 部分 中引入的 places 应用程序包含有大量复合组件。其中之一便是 map
组件,它显示一个地址地图,其中包含一个缩放级别下拉菜单,如图 1 所示:
清单 1 显示了经过删减的 map
组件列表:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <html xmlns="http://www.w3.org/1999/xhtml" ... xmlns:composite="http://java.sun.com/jsf/composite" xmlns:places="http://java.sun.com/jsf/composite/components/places"> <!-- INTERFACE --> <composite:interface> <composite:attribute name="title"/> </composite:interface> <!-- IMPLEMENTATION --> <composite:implementation"> <div class="map"> ... <h:panelGrid...> <h:panelGrid...> <h:selectOneMenu onchange="submit()" value="#{cc.parent.attrs.location.zoomIndex}" valueChangeListener="#{cc.parent.attrs.location.zoomChanged}" style="font-size:13px;font-family:Palatino"> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> </h:panelGrid> </h:panelGrid> <h:graphicImage url="#{cc.parent.attrs.location.mapUrl}" style="border: thin solid gray"/> ... </div> ... </composite:implementation> </html> |
组件的一大优点就是可以使用更有效的替代方法替换它们,同时不会影响到相关的功能。比如,在图 2 中,我使用一个 Google Maps 组件替换了 清单 1 中的 image
组件,Google Maps 组件由 GMaps4JSF 提供(见 参考资料):
map
组件的更新后的代码(进行了删减)如清单 2 所示:
清单 2. 使用一个 GMaps4JSF 组件替换 map 图形
<h:selectOneMenu onchange="submit()" value="#{cc.parent.attrs.location.zoomIndex}" valueChangeListener="#{cc.parent.attrs.location.zoomChanged}" style="font-size:13px;font-family:Palatino"> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> ... <m:map id="map" width="420px" height="400px" address="#{cc.parent.attrs.location.streetAddress}, ..." zoom="#{cc.parent.attrs.location.zoomIndex}" renderOnWindowLoad="false"> <m:mapControl id="smallMapCtrl" name="GLargeMapControl" position="G_ANCHOR_TOP_RIGHT"/> <m:mapControl id="smallMapTypeCtrl" name="GMapTypeControl"/> <m:marker id="placeMapMarker"/> </m:map> |
要使用 GMaps4JSF 组件,我从 GMaps4JSF 组件集合中使用 <m:map>
标记替换了 <h:graphicImage>
标记。将 GMaps4JSF 组件与缩放下拉菜单连接起来也很简单,只需为 <m:map>
标记的 zoom
属性指定正确的 backing-bean 属性。
关于缩放级别需要注意一点,那就是当一名用户修改缩放级别时,我将通过 <h:selectOneMenu>
的 onchange
属性强制执行表单提交,如 清单 1 中第一处使用粗体显示的代码行所示。这个表单提交将触发 JSF 生命周期,这实际上将把新的缩放级别推入到保存在父复合组件中的 location
bean 的 zoomIndex
属性中。这个 bean 属性被绑定到输入组件,如 清单 2 中的第一行所示。
由于我没有为与缩放级别修改相关的表单提交指定任何导航,JSF 在处理请求后刷新了同一页面,重新绘制地图以反映新的缩放级别。然而,页面刷新还重新绘制了整个页面,即使只修改了地图图像。在 技巧 2:Ajax 化 中,我将展示如何使用 Ajax,只对图像部分重新绘制,以响应缩放级别的修改。
places 应用程序中使用的另一个组件是 login
组件。图 3 展示了这个 login 组件的实际使用:
清单 3 展示了创建 图 3 所示的 login
组件的标记:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:util="http://java.sun.com/jsf/composite/components/util"> <util:login loginAction="#{user.login}" managedBean="#{user}"/> </ui:composition> |
login
组件只包含两个必需的属性:
-
loginAction
:登录 action 方法 -
managedBean
:包含名称和密码属性的托管
清单 3 中指定的托管 bean 如清单 4 所示:
package com.clarity import javax.faces.context.FacesContext import javax.faces.bean.ManagedBean import javax.faces.bean.SessionScoped @ManagedBean() @SessionScoped public class User { private final String VALID_NAME = "Hiro" private final String VALID_PASSWORD = "jsf" private String name, password; public String getName() { name } public void setName(String newValue) { name = newValue } public String getPassword() { return password } public void setPassword(String newValue) { password = newValue } public String login() { "/views/places" } public String logout() { name = password = nameError = null "/views/login" } } |
清单 4 中的托管 bean 是一个 Groovy bean。在这里使用 Groovy 替代 Java 语言并不会带来多少好处,只是减少了处理分号和返回语句的麻烦。然而,在技巧 2 的 Validation 部分中,我将展示一个对 User
托管 bean 使用 Groovy 的更有说服力的原因。
大多数情况下,您将需要使用提示和按钮文本来配置登录组件,如图 4 所示:
清单 5 展示了生成 图 4 所示的 login
组件的标记:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:util="http://java.sun.com/jsf/composite/components/util"> <util:login loginPrompt="#{msgs.loginPrompt}" namePrompt="#{msgs.namePrompt}" passwordPrompt="#{msgs.passwordPrompt}" loginButtonText="#{msgs.loginButtonText}" loginAction="#{user.login}" managedBean="#{user}"/> </ui:composition> |
在 清单 5 中,我从一个资源包中获取了用于提示的字符串和登录按钮的文本。
清单 6 定义了 login
组件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- Usage: <util:login loginPrompt="#{msgs.loginPrompt}" namePrompt="#{msgs.namePrompt}" passwordPrompt="#{msgs.passwordPrompt}" loginButtonText="#{msgs.loginButtonText}" loginAction="#{user.login}" managedBean="#{user}"> <f:actionListener for="loginButton" type="com.clarity.LoginActionListener"/> </util:login> managedBean must have two properties: name and password. The loginAction attribute must be an action method that takes no arguments and returns a string. That string is used to navigate to the page the user sees after logging in. This component's loginButton is accessible so that you can add action listeners to it, as depicted above. The class specified in f:actionListener's type attribute must implement the javax.faces.event.ActionListener interface. --> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:composite="http://java.sun.com/jsf/composite"> <!-- INTERFACE --> <composite:interface> <!-- PROMPTS --> <composite:attribute name="loginPrompt"/> <composite:attribute name="namePrompt"/> <composite:attribute name="passwordPrompt"/> <!-- LOGIN BUTTON --> <composite:attribute name="loginButtonText"/> <!-- loginAction is called when the form is submitted --> <composite:attribute name="loginAction" method-signature="java.lang.String login()" required="true"/> <!-- You can add listeners to this actionSource: --> <composite:actionSource name="loginButton" targets="form:loginButton"/> <!-- BACKING BEAN --> <composite:attribute name="managedBean" required="true"/> </composite:interface> <!-- IMPLEMENTATION --> <composite:implementation> <div class="prompt"> #{cc.attrs.loginPrompt} </div> <!-- FORM --> <h:form id="form"> <h:panelGrid columns="2"> <!-- NAME AND PASSWORD FIELDS --> #{cc.attrs.namePrompt} <h:inputText id="name" value="#{cc.attrs.managedBean.name}"/> #{cc.attrs.passwordPrompt} <h:inputSecret id="password" size="8" value="#{cc.attrs.managedBean.password}"/> </h:panelGrid> <p> <!-- LOGIN BUTTON --> <h:commandButton id="loginButton" value="#{cc.attrs.loginButtonText}" action="#{cc.attrs.loginAction}"/> </p> </h:form> </composite:implementation> </html> |
和 map
组件一样,login
也可以使用一个 Ajax 升级。在下一个技巧介绍 Validation 时,我将展示如何将 Ajax 验证添加到 login 组件中。
与非 Ajax HTTP 请求相比,Ajax 请求通常需要额外执行两个步骤:在服务器中对表单进行局部处理,接着在客户机上对 Document Object Model (DOM) 进行局部呈现。
通过将 JSF 生命周期分解为两个不同的逻辑部分 —— 执行和呈现,JSF 2 现在支持局部处理和局部呈现。图 5 突出显示了执行部分:
图 6 突出显示了 JSF 生命周期的呈现部分:
将生命周期划分为执行和呈现部分的原理很简单:您可以指定 JSF 在服务器上执行(处理)的组件,以及在返回 Ajax 调用时 JSF 呈现的组件。将使用 JSF 2 中新增的 <f:ajax>
实现这个目的,如清单 7 所示:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <h:selectOneMenu id="menu" value="#{cc.parent.attrs.location.zoomIndex}" style="font-size:13px;font-family:Palatino"> <f:ajax event="change" execute="@this" render="map"/> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> <m:map id="map"...> |
清单 7 对 清单 2 中的第一行所示的菜单进行了修改:我从 清单 2 中删除了 onchange
属性,并添加了一个 <f:ajax>
标记。这个 <f:ajax>
标记指定了以下内容:
- 触发 Ajax 调用的事件
- 在服务器上执行的组件
- 在客户机上呈现的组件
当用户从缩放菜单中选择一个菜单项时,JSF 将对服务器发出 Ajax 调用。随后,JSF 将菜单传递给生命周期的执行部分(@this
表示 <f:ajax>
周围的组件),并在生命周期的 Update Model Values 阶段更新菜单的 zoomIndex
。当 Ajax 调用返回后,JSF 呈现地图组件,后者使用(新设置的)缩放指数重新绘制地图,现在您就有了一个 Ajax 化的缩放菜单,其中添加了一行 XHTML。
但是还可以进一步简化,因为 JSF 为 event
和 execute
属性提供了默认值。
每个 JSF 组件都有一个默认事件,当在组件标记内部嵌入 <f:ajax>
标记时,该事件将触发 Ajax 调用。对于菜单,该事件为 change
事件。这意味着我可以删除 清单 7 中的 <f:ajax>
的 event
属性。<f:ajax>
的 execute
属性的默认值是 @this
,这表示围绕在 <f:ajax>
周围的组件。在本例中,该组件为菜单,因此还可以删除 execute
属性。
通过对 <f:ajax>
使用默认属性值,我可以将 清单 7 简化为清单 8:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <h:selectOneMenu id="menu" value="#{cc.parent.attrs.location.zoomIndex}" style="font-size:13px;font-family:Palatino"> <f:ajax render="map"/> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> <m:map id="map"...> |
这演示了使用 JSF 2 向组件添加 Ajax 有多么容易。当然,前面的例子非常简单:我仅仅是在用户选择某个缩放级别时重新绘制了地图而不是整个页面。验证表单中的各个字段等操作要更加复杂一些,因此接下来我将讨论这些用例。
当用户移出某个字段后对字段进行验证并提供即时的反馈,这始终是一个好的做法。例如,在图 7 中,我使用了 Ajax 对名称字段进行了验证:
该名称字段的标记如清单 9 所示:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <h:panelGrid columns="2"> #{cc.attrs.namePrompt} <h:panelGroup> <h:inputText id="name" value="#{cc.attrs.managedBean.name}" valueChangeListener="#{cc.attrs.managedBean.validateName}"> <f:ajax event="blur" render="nameError"/> </h:inputText> <h:outputText id="nameError" value="#{cc.attrs.managedBean.nameError}" style="color: red;font-style: italic;"/> </h:panelGroup> ... </h:panelGrid> |
我再一次使用了 <f:ajax>
,只不过这一次没有执行输入的默认事件 — change
,因此我将 blur
指定为触发 Ajax 调用的事件。当用户移出名称字段时,JSF 将对服务器发出 Ajax 调用并在生命周期的执行部分运行 name
输入组件。这意味着 JSF 将在生命周期的 Process Validations 阶段调用 name
输入的值修改监听程序(在 清单 9 中指定)。清单 10 展示了这个值修改监听程序:
package com.clarity import javax.faces.context.FacesContext import javax.faces.bean.ManagedBean import javax.faces.bean.SessionScoped import javax.faces.event.ValueChangeEvent import javax.faces.component.UIInput @ManagedBean() @SessionScoped public class User { private String name, password, nameError; ... public void validateName(ValueChangeEvent e) { UIInput nameInput = e.getComponent() String name = nameInput.getValue() if (name.contains("_")) nameError = "Name cannot contain underscores" else if (name.equals("")) nameError = "Name cannot be blank" else nameError = "" } ... } |
这个修改值的监听程序(user
托管 bean 的 validateName()
方法)将验证名称字段并更新 user
托管 bean 的 nameError
属性。
返回 Ajax 调用后,借助 清单 9 中的 <f:ajax>
标记的 render
属性,JSF 呈现 nameError
输出。该输出显示了 user
托管 bean 的 nameError
属性。
在前面的小节中,我展示了如何对单一字段执行 Ajax 验证。但是,有些情况下,需要同时对多个字段进行验证。比如,图 8 展示了 places 应用程序同时验证名称和密码字段:
我在用户提交表单时同时验证了名称和密码字段,因此对这个例子不需要用到 Ajax。相反,我将使用 JSF 2 的新事件系统,如清单 11 所示:
<h:form id="form" prependId="false"> <f:event type="postValidate" listener="#{cc.attrs.managedBean.validate}"/> ... </h:form> <div class="error" style="padding-top:10px;"> <h:messages layout="table"/> </div> |
在 清单 11 中,我使用了 <f:event>
— 类似于 <f:ajax>
,它是 JSF 2 中新增的内容。<f:event>
标记在另一方面还类似于 <f:ajax>
:使用起来很简单。
将一个 <f:event>
标记放到组件标记的内部,当该组件发生指定的事件(使用 type
属性指定)时,JSF 将调用一个使用 listener
属性指定的方法。因此,<f:event>
标记在 清单 11 中的含义就是:对表单进行验证后,对用户传递给这个复合组件的托管 bean 调用 validate()
方法。该方法如清单 12 所示:
package com.clarity import javax.faces.context.FacesContext import javax.faces.bean.ManagedBean import javax.faces.bean.SessionScoped import javax.faces.event.ValueChangeEvent import javax.faces.component.UIInput @ManagedBean() @SessionScoped public class User { private final String VALID_NAME = "Hiro"; private final String VALID_PASSWORD = "jsf"; ... public void validate(ComponentSystemEvent e) { UIForm form = e.getComponent() UIInput nameInput = form.findComponent("name") UIInput pwdInput = form.findComponent("password") if ( ! (nameInput.getValue().equals(VALID_NAME) && pwdInput.getValue().equals(VALID_PASSWORD))) { FacesContext fc = FacesContext.getCurrentInstance() fc.addMessage(form.getClientId(), new FacesMessage("Name and password are invalid. Please try again.")) fc.renderResponse() } } ... } |
JSF 将一个组件系统事件传递给 清单 12 中的 validate()
方法,方法从这个事件中获得对(适用于事件的)组件的引用 — 登录表单。对于这个表单,我使用 findComponent()
方法获得名称和密码组件。如果这些组件的值不为 Hiro 和 jsf,那么我将把一条消息存储到 faces 上下文并要求 JSF 继续处理生命周期的 Render Response 阶段。通过这种方法,就可以避免 Update Model Values 阶段,后者会将坏的名称和密码传递给模型(见 图 5)。
您可能已经注意到,清单 10 和 清单 12 中的验证方法是使用 Groovy 编写的。与 清单 4 不同,后者使用 Groovy 的惟一好处就是避免了分号和返回语句,清单 10 和 清单 12 中的 Groovy 代码使我不必进行类型转换。例如,在 清单 10 中,ComponentSystemEvent.getComponent()
和 UIComponent.findComponent()
都返回类型 UIComponent
。对于 Java 语言,我需要转换这些方法的返回值。Groovy 为我做了这一转换工作。
在 Ajax 化 中,我展示了如何为 map
组件 Ajax 化缩放菜单,因此,当用户修改缩放级别时,places 应用程序将只重新绘制页面的地图部分。另一个常见 Ajax 用例是向用户提供反馈,表示一个 Ajax 事件正在处理中,如图 9 所示:
在 图 9 中,我将使用一个动画 GIF 替换缩放菜单,这个动画 GIF 将在 Ajax 调用期间显示。当 Ajax 调用完成后,我将使用缩放菜单替换进度指示器。清单 13 展示了这一过程:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <h:selectOneMenu id="menu" value="#{cc.parent.attrs.location.zoomIndex}" style="font-size:13px;font-family:Palatino"> <f:ajax render="map" onevent="zoomChanging"/> <f:selectItems value="#{places.zoomLevelItems}"/> ... </h:selectOneMenu> ... <h:graphicImage id="progressbar" style="display: none" library="images" name="orange-barber-pole.gif"/> |
在 清单 13 中,我添加了一个进度条图像(该图像最初不会显示出来),并为 <f:ajax>
指定了 onevent
属性。该属性引用一个 JavaScript 函数,如 清单 14 所示,这个函数将在 清单 13 中的 Ajax 调用被初始化时由 JSF 调用:
function zoomChanging(data) { var menuId = data.source.id; var progressbarId = menuId.substring(0, menuId.length - "menu".length) + "progressbar"; if (data.name == "begin") { Element.hide(menuId); Element.show(progressbarId); } else if (data.name == "success") { Element.show(menuId); Element.hide(progressbarId); } } |
JSF 向 清单 14 中的函数传递一个对象,该对象中包含有一些信息,比如触发了事件的组件的客户机标识符(在本例中为缩放级别菜单),以及 Ajax 请求的当前状态,使用 name
属性表示。
清单 14 中的 zoomChanging()
函数计算进度条图像的客户机标识符,然后使用 Prototype Element
对象在 Ajax 调用期间隐藏和显示对应的 HTML 元素。
相关推荐
学习这部分,你需要了解如何导入和使用JSF的库,例如`javax.faces.*`和`org.primefaces.*`,以及它们提供的功能,如数据验证、转换器、事件处理等。 4. **Ajax4JSF使用手册**:Ajax4JSF是JSF的一个扩展,它为JSF...
例如,使用CSS设计一个响应式的布局,当用户在JSF组件(如输入框或下拉菜单)上触发事件时,通过JavaScript和JSF的Ajax功能向服务器发送请求,获取并动态渲染新的数据。这不仅减少了页面的加载时间,还确保了用户的...
- **Ajax事件处理**:Ajax4JSF允许开发者定义和处理各种Ajax事件,比如点击、更改、验证等,这些事件可以在后台服务器端触发相应的处理逻辑,而无需刷新整个页面。 - **Ajax行为组件**:提供了一系列内置的行为组件...
这些库提供了丰富的组件和API,使得创建异步请求和处理服务器响应变得简单。 在实际项目中,"JSF+Spring+Hibernate+AJAX"的组合常被称为"Java EE的金三角",因为它们能够协同工作,提供一个高效、可扩展的开发平台...
在深入研究这个项目时,开发者首先需要了解JSF和Ajax的基础知识,然后可以查看源码以理解Ajax4jsf如何处理Ajax请求和响应,以及如何在JSF组件中集成Ajax功能。此外,通过运行示例或测试代码,开发者能够快速上手并...
2. **Ajax事件处理**: 通过Ajax4JSF,开发者可以定义触发Ajax请求的用户事件,如按钮点击、表单提交等,并在后台处理这些事件后更新相应的视图。 3. **Ajax请求和响应**: 使用Ajax4JSF,开发者可以指定Ajax请求的...
Ajax4JSF的行为和事件 Ajax4JSF的行为(Behaviors)定义了如何响应用户事件,而事件(Events)则是行为触发的结果。例如,`a4j:support`标签用于添加Ajax行为,而`a4j:ajax`提供了更强大的功能,包括请求触发条件...
- Ajax4jsf的集成和Ajax事件处理 - Seam中的依赖注入和事件处理 - 如何利用Seam进行安全性和国际化设置 - 例子和实战项目,展示如何结合使用这些技术构建实际应用 掌握这些知识点后,开发者将能够高效地构建出功能...
此外,它提供了事件处理机制,允许开发者在后台处理Ajax请求并更新模型和视图。 **7. 性能和最佳实践** 在使用Ajax4JSF时,需要注意性能优化,如减少不必要的网络请求,合理设计Ajax更新区域,避免全页面刷新,以及...
该框架使得在JSF环境中实现Ajax功能变得更加简单,无需编写JavaScript代码,而是通过一组组件和API来实现。 ### 1. **开始使用Ajax4jsf** - **环境需求**:首先,你需要一个支持Ajax4jsf的Java版本以及JavaServer ...
AJAX4JSF級聯,實現省份與城市,只是一個簡單的Demo,沒有詳細省市資料.
在实际开发中,JSF2结合XHTML、RichFaces和AJAX可以创建出功能强大、交互性强的Web应用。开发者需要理解JSF的生命周期、组件模型和EL表达式,熟悉RichFaces组件的用法,以及如何利用AJAX进行异步通信。对于商品管理...
JavaScript Server Faces(JSF)是Java平台上的一种用于构建用户界面的服务器端框架,它简化了创建、维护和管理Web应用程序的复杂性。Facelets是JSF的默认视图定义语言,提供了一种声明式的方式来创建可重用、可维护...
Ajax4jsf架构概述提供了对框架内部工作原理的深入理解,包括请求处理流程、事件模型和组件生命周期。了解这些基础概念对于高效使用框架至关重要。 ##### 发送AJAX请求 发送AJAX请求是使用Ajax4jsf的关键步骤,通过`...
JSF的核心概念包括组件、事件和渲染树,这些元素共同工作以创建动态、交互式的用户界面。 Ajax(Asynchronous JavaScript and XML)是一种在无需重新加载整个网页的情况下更新部分网页内容的技术。通过Ajax,开发者...
这个框架使得开发者能够轻松地在JSF应用中集成Ajax功能,而无需深入理解底层的JavaScript和XMLHttpRequest技术。 ### 1. Ajax4JSF 的核心概念 - **Ajax 组件**:Ajax4JSF 提供了一系列预定义的UI组件,如`...
6. 事件处理:JSF有丰富的事件处理机制,允许开发者定义事件监听器和处理程序。 三、JavaScript与JSF的结合 1. AJAX集成:通过JavaScript和XMLHttpRequest对象,JSF可以实现部分页面更新,提高用户体验。 2. JSF...
《Sun_JSF2AndAjax.pdf》这份文档深入探讨了JavaServer Faces(JSF)2.0框架中Ajax技术的应用与实践,提供了丰富的示例和技巧,帮助开发者理解和掌握在现代Web应用中如何高效地结合JSF与Ajax。 #### JSF与Ajax基础 ...