JSF 2 事件处理、JavaScript 和 Ajax
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 显示了经过删减的 map 组件列表:
清单 1. map 组件
?
组件的一大优点就是可以使用更有效的替代方法替换它们,同时不会影响到相关的功能。比如,在图 2 中,我使用一个 Google Maps 组件替换了 清单 1 中的 image 组件,Google Maps 组件由 GMaps4JSF 提供(见 参考资料):
图 2. GMaps4JSF 的 map 图像
map 组件的更新后的代码(进行了删减)如清单 2 所示:
清单 2. 使用一个 GMaps4JSF 组件替换 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,只对图像部分重新绘制,以响应缩放级别的修改。
清单 3 展示了创建 图 3 所示的 login 组件的标记:
清单 3. 最基础的 login:只包含必需的属性
清单 5 展示了生成 图 4 所示的 login 组件的标记:
清单 5. 配置 login 组件
?
在 清单 5 中,我从一个资源包中获取了用于提示的字符串和登录按钮的文本。
清单 6 定义了 login 组件:
清单 6. 定义 login 组件
?
和 map 组件一样,login 也可以使用一个 Ajax 升级。在下一个技巧介绍 Validation 时,我将展示如何将 Ajax 验证添加到 login 组件中。
图 6 突出显示了 JSF 生命周期的呈现部分:
图 6. JSF 生命周期的呈现部分
将生命周期划分为执行和呈现部分的原理很简单:您可以指定 JSF 在服务器上执行(处理)的组件,以及在返回 Ajax 调用时 JSF 呈现的组件。将使用 JSF 2 中新增的 <f:ajax> 实现这个目的,如清单 7 所示:
清单 7. 一个 Ajax 缩放菜单
?
清单 7 对 清单 2 中的第一行所示的菜单进行了修改:我从 清单 2 中删除了 onchange 属性,并添加了一个 <f:ajax> 标记。这个 <f: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:
清单 8. 简化后的 Ajax 缩放菜单
该名称字段的标记如清单 9 所示:
清单 9. 名称字段
?
我再一次使用了 <f:ajax>,只不过这一次没有执行输入的默认事件 — change,因此我将 blur 指定为触发 Ajax 调用的事件。当用户移出名称字段时,JSF 将对服务器发出 Ajax 调用并在生命周期的执行部分运行 name 输入组件。这意味着 JSF 将在生命周期的 Process Validations 阶段调用 name 输入的值修改监听程序(在 清单 9 中指定)。清单 10 展示了这个值修改监听程序:
清单 10. validateName() 方法
?
这个修改值的监听程序(user 托管 bean 的 validateName() 方法)将验证名称字段并更新 user 托管 bean 的 nameError 属性。
返回 Ajax 调用后,借助 清单 9 中的 <f:ajax> 标记的 render 属性,JSF 呈现 nameError 输出。该输出显示了 user 托管 bean 的 nameError 属性。
我在用户提交表单时同时验证了名称和密码字段,因此对这个例子不需要用到 Ajax。相反,我将使用 JSF 2 的新事件系统,如清单 11 所示:
清单 11. 使用 <f:event>
?
在 清单 11 中,我使用了 <f:event> — 类似于 <f:ajax> ,它是 JSF 2 中新增的内容。<f:event> 标记在另一方面还类似于 <f:ajax>:使用起来很简单。
将一个 <f:event> 标记放到组件标记的内部,当该组件发生指定的事件(使用 type 属性指定)时,JSF 将调用一个使用 listener 属性指定的方法。因此,<f:event> 标记在 清单 11 中的含义就是:对表单进行验证后,对用户传递给这个复合组件的托管 bean 调用 validate() 方法。该方法如清单 12 所示:
清单 12. validate() 方法
在 图 9 中,我将使用一个动画 GIF 替换缩放菜单,这个动画 GIF 将在 Ajax 调用期间显示。当 Ajax 调用完成后,我将使用缩放菜单替换进度指示器。清单 13 展示了这一过程:
清单 13. 监视 Ajax 调用
?
在 清单 13 中,我添加了一个进度条图像(该图像最初不会显示出来),并为 <f:ajax> 指定了 onevent 属性。该属性引用一个 JavaScript 函数,如 清单 14 所示,这个函数将在 清单 13 中的 Ajax 调用被初始化时由 JSF 调用:
清单 14. 响应 Ajax 请求的 JavaScript
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 元素。
