转自:http://www.ibm.com/developerworks/cn/java/j-jsf2fu2/ 用 JavaServer Faces 2 实现可扩展 UI |
级别: 中级 David Geary 2009 年 6 月 25 日 模板和复合组件是 Java™Server Faces (JSF) 2 的两个功能强大的特性,借助这两个特性,您就可以实现易于修改和扩展的用户界面。在本文 早在 2000,当我还是 JSF 2 现在的默认显示技术 — Facelets — 就是一个模板框架,在很大程度上基于的是 Tiles。JSF 2 还提供了一个功能强大的机制,称为复合组件
在我作为软件开发人员从事的第一项工作中,我的任务是为基于 UNIX® 的计算机辅助设计和计算机辅助制造(CAD/CAM)系统实现一个 GUI。 最初,一切进行顺利,但是一段时间后,我的代码开始问题不断。待到代码发布的时候,系统已经相当脆弱,我甚至都害怕修复 bug,而这次的代码发布自然也伴随着一连串的 bug 报告。 如果我在这个项目中遵循了 DRY 原则 — 不重复自己(Don’t Repeat Yourself),我本可以让自己不至于这么悲惨。DRY 原则最初由 Dave Thomas 和 Andy Huntprinciple 提出(参见 参考资料
我的 CAD/CAM 应用程序并不符合 DRY 原则 — 它具有太多关注点之间的交叉 — 因此在一个地方所做的更改常常会在其他地方引起意想不到的更改。 JSF 1 在几个方面违背了 DRY 原则,比如,它强迫您提供托管 beans 的两种表示 — 一个使用 XML,一个使用 Java 代码。对多重表示的需求让创建和更改托管 bean 更加困难。正如我在本系列 第 1 部分 除托管 beans 之外,就连一些看似有益的实践 — 比如在所有视图中包括相同的样式表 — 也违背了 DRY 原则,并会导致混乱。比如,如果要更改样式表的名字,就必须更改多个视图。如果可能,最好是封装此样式表包含。 DRY 原则同样适用于代码设计。如果多个方法均包含遍历树的代码,一种好的做法是(比如在一个子类中)封装遍历树的算法。 在实现 UI 时,因大多数更改均在开发过程中发生,所以遵守 DRY 原则尤其重要。 JSF 2 在很多方面都支持 DRY 原则,其中之一就是通过模板 我在 第 1 部分 图 1. places 应用程序的视图:Login、source viewer 和 places
与很多 Web 应用程序一样,这个 places 应用程序包含多个具有相同布局的视图。JSF 模板功能让您能够在一个模板内封装该布局 — 及其他共享工件,比如 JavaScript 和 Cascading Style Sheets(CSS)。清单 1 是 图 1 清单 1. places 模板:/templates/masterLayout.xhtml
正如 清单 1 如为 清单 2. login 视图
这个 login 视图为窗口的标题、头和右菜单使用了模板的默认内容。它只定义了特定于此 login 视图的功能:内容部分和左菜单。 通过为窗口标题、头或右菜单提供 清单 3. source-viewer 视图
source-viewer 视图定义了内容部分以及右菜单的内容。它还覆盖了由 清单 1 清单 4 显示了 places 视图(图 1 清单 4. places 视图
请注意清单 2 使用 JSF 模板功能的另一个有趣之处是类似清单 2 与使用模板的视图类似,模板本身也更改甚少。由于大量常见功能都封装在几乎不用维护的代码中,这样一来,您就可以将精力集中于视图的实际内容 — 比如,login 页面的左菜单应该有些什么内容。专心于视图的实际内容就是下一个技巧的主旨所在。
在我的 CAD/CAM GUI 发布后不久,我花了几个月的时间与另一位开发人员 Bob 致力于一个新的项目。我们以 Bob 的代码为基础,而且不可思议地是,我们还能轻松进行更改并修复 bug。 我很快意识到 Bob 的代码和我的代码之间的最大区别是他编写了小 虽然 Bob 和我那时都没有意识到,但是我们过去一直在使用 Smalltalk 的一种设计模式,称为 Composed Method(参见 参考资料
使用 Composed Method 模式的好处已经有大量书面记载(详细说明,请参见 Neal Ford 的 “演化架构与紧急设计:组合方法和 SLAP JSF 2 鼓励使用较小的视图段组装视图。模板封装了常见功能,进而将视图分成了更小的块。JSF 2 还提供了一个 图 2. login 页面的左菜单 清单 5 显示了定义该菜单内容的文件: 清单 5. login 视图左菜单的实现
图 3 显示了 places 视图的左菜单: 图 3. places 视图的左菜单 places 视图的左菜单的实现如清单 6 所示: 清单 6. places 视图的左菜单的实现
清单 7. Places.logout() 方法
对我而言,清单 6 清单 8. 重构 places 视图的左菜单
清单 9 显示了 addressForm.xhtml: 清单 9. addressForm.xhtml
清单 10 显示了 logoutIcon.xhtml: 清单 10. logoutIcon.xhtml
在从多个小文件组装视图时,就可享受到 图 4. places 应用程序的视图 我 当然,您可以使用任何目录结构来组织您的 XHTML 文件。如果组织得合理,定位想要修改的代码就会非常容易。 除了遵循 DRY 原则和使用 Composed Method 模式之外,还有一种好的做法是在定制组件内封装功能。组件是一种功能强大的重用机制,而且您应该充分利用这种强大性。与 JSF 1 不同,使用 JSF 2 更易于实现定制组件。
在我还是一个男孩的时候,我有两个最喜欢的玩具:一个是化学组合(chemistry set),一个是 LEGO 拼装玩具。这两种玩具让我能够通过组合基本的构建块来创建东西,而这也成为了我一生的爱好,只不过现在是打着软件开发的幌子。 JSF 的优势一直都在于其组件模型,但这种优势直到现在才完全实现,因为用 JSF 1 很难实现定制组件。您必须要编写 Java 代码、指定 XML 配置,并对 JSF 的生命周期有深刻的理解。有了 JSF 2,您就能够轻松实现定制组件:
在本文的剩余部分,我将向您介绍如何为 places 应用程序实现三个定制组件:一个图标、一个 login 面板和一个显示了地址地图和天气信息的面板。但是首先,让我先来概括介绍一下 JSF 2 复合组件。 JSF 2 综合了 Facelets 模板 一般情况下,是在 resources 目录下的 XHTML 内实现复合组件,并将它们完全通过约定链接到一个名称空间和标记。图 5 展示了我是如何为 places 应用程序组织这些复合组件的: 图 5. places 应用程序的组件 要使用复合组件,需要声明一个名称空间并使用标记。此名称空间通常为
而要使用
最后,若要使用 place 组件,则可按如下所示的这样做:
places 应用程序使用了图 6 所示的这两个图标: 图 6. places 应用程序的图标
每个图标都是一个链接。当用户单击 图 6 可以为链接指定一个 CSS 类名和图像,并且还可以向链接附加方法。当用户单击一个被关联的链接时,JSF 就会调用那些方法。 清单 11 给出了 清单 11. 使用 icon 组件显示标记
清单 12 给出了如何使用 清单 12. 使用 icon 组件执行登出
清单 13 给出了 清单 13. icon 组件
与其他复合组件类似,清单 13
请注意,清单 13 清单 14. 重构后的 icon 组件
在 清单 14
如果不能指定 有了 JSF 2,就可以实现完全可配置的复合组件。例如,places 应用程序就包含了一个 图 7. places 应用程序的 login 组件 清单 15 显示了这个 places 应用程序是如何使用 清单 15. 使用 login 组件
清单 16. login 组件
在 login 组件的界面,我已经在 与 清单 16 清单 17. Log In 按钮的动作侦听器
JSF 2 让您能够在无需任何 Java 代码或配置的情况下实现完全可配置的组件。除此之外,您还可以嵌套复合组件,这样一来,您就可以将复杂的组件拆分成更小的、更易于管理的块。比如,图 8 所示的 图 8. places 应用程序的 place 组件 清单 18 给出了 places 应用程序是如何使用 清单 18. 使用 place 组件
清单 19. place 组件
在 清单 19 清单 20. map 组件
请注意 清单 20 但是,您无需严格依赖于嵌套组件中的父属性,正如我在 清单 19 清单 21 显示了这个 清单 21. weather 组件
因此,在想要实现嵌套组件时,您就有了选择。您可以让嵌套的组件依赖于其父组件的属性,也可以要求父组件将属性显式地传递给其内嵌套的组件。比如,清单 19 是选择实现组件-显式属性,还是选择依赖于父属性,这是耦合和方便性之间的权衡问题。在本例中,
在本文中,我向您展示了如何使用 JSF 2 的模板和复合组件特性来实现易于维护和扩展的 UI。在本系列的最后一篇文章,我将探讨如何在复合组件中使用 JavaScript、如何使用 JSF 2 的新事件模型以及如何利用 JSF 2 对 Ajax 的内置支持。
学习
获得产品和技术
讨论
|