JSF 的最大卖点在于它是一种基于组件的框架。这意味着您可以实现供其他人重用的组件。这种强大的重用机制在 JSF 1 中基本上是不可能实现的,因为在 JSF 1 中实现组件是非常困难的事情。
然而,正如 第 2 部分 所述,JSF 2 通过一种名为复合组件 的新特性简化了组件的实现 — 无需 Java 代码和配置。这一特性可以说是 JSF 2 中最重要的部分,因为它真正实现了 JSF 组件的潜力。
在这份有关 JSF 2 的第三篇也是最后一篇文章中,我将展示如何利用新的 Ajax 和事件处理功能(也在 JSF 2 中引入)构建复合组件特性,要从 JSF 2 中获得最大收益,需要遵循下面的技巧:
对于第一个技巧,我将简要回顾已在 第 2 部分 中详细描述过的两个组件。对于后面的技巧,我将展示如何使用 Ajax 和事件处理功能来改造这些组件。
技巧 1:组件化我在 第 1 部分 中引入的 places 应用程序包含有大量复合组件。其中之一便是 map 组件,它显示一个地址地图,其中包含一个缩放级别下拉菜单,如图 1 所示:
图 1. places 应用程序的 map 组件清单 1 显示了经过删减的 map 组件列表:
清单 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/> </composite:interface> <!-- IMPLEMENTATION --> <composite:implementation"> <div> ... <h:panelGrid...> <h:panelGrid...> <h:selectOneMenu onchange="submit()" value="#{cc.parent.attrs.location.zoomIndex}" valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> </h:panelGrid> </h:panelGrid> <h:graphicImage url="#{cc.parent.attrs.location.mapUrl}"/> ... </div> ... </composite:implementation> </html>
组件的一大优点就是可以使用更有效的替代方法替换它们,同时不会影响到相关的功能。比如,在图 2 中,我使用一个 Google Maps 组件替换了 中的 image 组件,Google Maps 组件由 GMaps4JSF 提供(见 ):
图 2. GMaps4JSF 的 map 图像map 组件的更新后的代码(进行了删减)如清单 2 所示:
清单 2. 使用一个 GMaps4JSF 组件替换 map 图形<h:selectOneMenu onchange="submit()"value="#{cc.parent.attrs.location.zoomIndex}" valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> ... <m:map address="#{cc.parent.attrs.location.streetAddress}, ..." zoom="#{cc.parent.attrs.location.zoomIndex}" renderOnWindowLoad="false"> <m:mapControl position="G_ANCHOR_TOP_RIGHT"/> <m:mapControl/> <m:marker/> </m:map>
要使用 GMaps4JSF 组件,我从 GMaps4JSF 组件集合中使用 <m:map> 标记替换了 <h:graphicImage> 标记。将 GMaps4JSF 组件与缩放下拉菜单连接起来也很简单,只需为 <m:map> 标记的 zoom 属性指定正确的 backing-bean 属性。
关于缩放级别需要注意一点,那就是当一名用户修改缩放级别时,我将通过 <h:selectOneMenu> 的 onchange 属性强制执行表单提交,如 中第一处使用粗体显示的代码行所示。这个表单提交将触发 JSF 生命周期,这实际上将把新的缩放级别推入到保存在父复合组件中的 location bean 的 zoomIndex 属性中。这个 bean 属性被绑定到输入组件,如 中的第一行所示。
由于我没有为与缩放级别修改相关的表单提交指定任何导航,JSF 在处理请求后刷新了同一页面,重新绘制地图以反映新的缩放级别。然而,页面刷新还重新绘制了整个页面,即使只修改了地图图像。在 中,我将展示如何使用 Ajax,只对图像部分重新绘制,以响应缩放级别的修改。
login 组件places 应用程序中使用的另一个组件是 login 组件。图 3 展示了这个 login 组件的实际使用:
图 3. login 组件展示了创建 所示的 login 组件的标记:
清单 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 组件只包含两个必需的属性:
中指定的托管 bean 如清单 4 所示:
清单 4. User.groovypackage 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" } }