Tapestry开发指南


成都麦柯 技术开发二部 Tapestry开发指南 Tapestry技术论坛:http://forum.cdmcs.com/viewforum.php?f=17 我的Email:gzdlw_Adong@Hotmail.com (技术问题请在论坛发贴) 成都麦柯系统集成有限公司 www.cdmcs.com 1 成都麦柯 技术开发二部 目 录 第一章 Tapestry 入门………………………………………….4 第一节 Tapestry 基础概念…………………………………...…….4 第二节 eclipse 结合 spindle 的配置……………………………….9 第三节 创建简单的 Tapestry 项目……………………………..10 第四节 说明 Tapestry 项目中各种文件的作用及配置………….13 第二章 Tapestry 组件实战……………………………….………….20 第一节 熟悉 Tapestry 组件……………………….…………….20 1, Foreach 组件………………………………………..…….…25 2, Insert 组件…………………………..……………………....27 3, Image 组件………………..………………………….…….27 4, Conditional 组件…………………………………………..…29 5, GenericLink 组件………………………………………….…30 6, ExternalLink 组件………………………………………..…..30 7, Form 组件………………………………………………..…38 8, FieldLabel 组件和 ValidField 组件……………………………....42 9, PageLink 组件…………………………………………….....47 10, DirectLink 组件………………………………………….…..47 11, TextField 组件……………………………………………….53 12, RadioGroup 和 Radio 组件……………………………………..54 13, PropertySelection 组件……………………………………55 14, Table 组件………………………………………………….58 成都麦柯系统集成有限公司 www.cdmcs.com 2 成都麦柯 技术开发二部 第二节 自定义组件………………………………………………68 1, 轻量级的 Table 组件…………………………………………..70 2, 带长度效验的 TextArea………………………………………..85 第三章 Tapestry 运行机制……………………………….....97 第一节 Tapestry 基本原理………………………………………..97 第二节 Tapestry 运行机制………………………………….…..106 第三节 对 Engine 类的介绍……………………………………..114 成都麦柯系统集成有限公司 www.cdmcs.com 3 成都麦柯 技术开发二部 第一章 Tapestry 入门 第一节 Tapestry 基础概念 1, Tapestry 所处的状况 1) 为什么需要 web 框架? 多数情况下,跨不同的应用来重用商业组件是困难的,因为由商业组件提供的功能 在其它应用里不需要:它可能是唯一针对你的应用的。然而,应用服务可以在机构跨应 用重用,跟跨机构一样好,因为它们提供的服务对不同类型的应用都有用。这些应用服 务可能包括请求路由(对一个 MVC 结构),错误处理,以通用的方式产生的客户端脚 本和已准备好的标签库。 2) Tapestry 所处的状况 以下是各种 web 框架比较: (by Matt Raible) struts Spring MVC WebWork Tapestry JSF 优 点 广泛流行;拥有 很多信息和示 例;HTML 标 签库是一个非 常好的东西。 生命周期由重 写绑定,效验等 确定;能够无缝 隙地与各种表 现层程序结合 在一起,如 JSP,XSL 等; IC 使得很容易 测试。 结构简单,容易 被扩展;标签库 容易被自定义, 并获得 Velocity 支持;拦截机制 成熟可靠。 一旦学会,效率 很高;HTML 模板很合适美 工;有良好的社 区支持。 J2EE 规范;能 够快速和容易 地开发;丰富的 导航框架。 缺 点 ActionForms 非 常恶心;不能够 做单元测试;大 量的邮件列表 令人无法忍受 很少被使用;需 要在 JSP 中写 入大量代码;太 灵活了,反而不 能得到通用的 控制。 其文档最近才 出现,示例很 少;客户端效验 很不成熟。 文档非常概念, 而缺少实践;学 习曲线陡峭,示 例非常少; 标签依赖 JSPs;技术不够 成熟;实现资源 不单纯。 Matt Raible 认为: 如果是快速却低技术含量的项目,他会选择 struts,因为 struts 比较成熟; 如果是高技术含量的企业级项目,他会选择 Tapestry,因为 Tapestry 可重用组件; 如果是开源项目,他会选择 WebWork,因为使用 WebWork 需要对框架有更深入的认识。 成都麦柯系统集成有限公司 www.cdmcs.com 4 成都麦柯 技术开发二部 2, Tapestry 目前在我们的项目中所处的状况: 我们的成都建信的网站部分全部是用 Tapestry 做的。在过去的三个月中,我们已经解决 了 Tapestry 的绝大部分技术问题。 我们可以在论坛上随处可见,大部分人对 Tapestry 的学习曲线问题都有顾虑。追究其根 本,原因在于:Tapestry 的理念颠覆了传统的 web 应用层框架的理念。其实所谓传统,无非 就是 struts 和 JSP,因为 struts 应用最为广泛,而 JSP 几乎算是所有 JAVA 表现层框架的本源。 Tapestry 也是源于 JSP。 我不可否认,任何 web 表现层都可以用 JSP 来实现。就如同任何 java 程序都可以使用 J2SDK 来编写。但是,需要非常关注的是,我们如果真这样做,的确对程序员来说“最简 单”,但是却牺牲了工作效率。这绝对是得不偿失的。 目前成都建信网站部分,基本上是冰箱哥哥和我搭档。我负责网站表现层,而冰箱哥哥 负责向表现层提供业务逻辑。随着网站表现层部分的不断开发,我们发现一个不可否认的事 实,随着项目功能实现越多,表现层的工作越显得轻松。因为大量重复使用的 Tapestry 组件, 以及高度复用的表现层逻辑,使得工作效率大幅度提升。就如同 Hibernate 使得我们数据库 业务方面的工作效率大幅提升一样,Tapestry 使得我们在工作上的“轻松”和“快捷”也同 样不可忽视。 3, 为什么要使用 Tapestry ? Tapestry 的灵魂就四个字:基于组件。 Tapestry 就是目前比较主流的两种“基于组件”的表现层框架之一,另外一种“基于 组件”表现层框架是 JSF,但是 JSF 还不够成熟。 现在,表现层对于我们来说,就是“搭积木”。 连接渠道 连接渠道 复用 Tapestry 组件是一个“黑盒子”,用于表现 HTML 响应,以及响应 HTTP 请求。 HTML 模板 Page 规范 HTML 模板 Page 规范 Page 规范 HTML 模板 Java 文件 Java 文件 Tapestry 组件通过其规范定义。规范是一个 XML 文档,其中定义了组件类型,参数,组件 模板,包含的组件以及被包含组件之间的联系,还有所有的 assets。 在运行的时候,规范指定并实例一个类,当页面所包含的组件被表现的时候,组件将访 问 HTML 模板,找到静态的 HTML 并植入组件。 使用 Tapestry 我们能够获得以下好处: 1) 能够保证对 HTML 最少限度的干扰,也就是说,美工和表现层程序员有了非常明确的分 工。Tapestry 对 HTML 页面的介入可以仅仅是增加一个 jwcid 属性。 2) 基于组件,使得我们的表现层变成由一个个“积木”堆积而成。 Page 规范就是这些积 木之间的纽带。而 java 文件担当了处理逻辑的角色。 3) 由于表现层逻辑全部放在了 java 文件里面,使得我们对逻辑的操作达到了“随心所欲” 的地步。 4) 随着我们项目的深入,我们积累的各种积木也就越来越多,也就说,我们的工作越来越 成都麦柯系统集成有限公司 www.cdmcs.com 5 成都麦柯 技术开发二部 轻松,工作效率越来越高。 以上 4 个特点,都是JSP所不能够带给我们的。尤其是工作效率的提升。当然,实际上 Tapestry所带给我们的远不至这些好处。我们都知道,作为程序员,即便我们工作如何的严 谨,仍然不能够避免BUG的问题。因为不论我们如何“仔细”,但是在编程过程中,仍然无 法解决一些隐蔽的“陷阱”,甚至有些“陷阱”直到暴露之前,我们都还没有意识到其存在。 现在有了Tapestry这层“保险”,虽然我们不可否认,由于对Tapestry的熟练程度不足,可能 会给我们带来新的“陷阱”,但是Tapestry为我们解决的“陷阱”远比带给我们的“陷阱”更 多更隐蔽。 4, Tapestry 基础概念: Tapestry是一个全面web application框架,是使用JAVA写的。 Tapestry不是一个application server,Tapestry是一个使用在application server中的框架。 Tapestry不是一个application,Tapestry是一个用来创建web application的框架。 Tapestry不是JSP的一种使用方式,Tapestry和JSP只能够选择一种。 Tapestry不是一个脚本环境,Tapestry使用一种组件对象模式(component object model), 这并不是一种简单的脚本,而是用于生成高动态性高互交性的web页面。 Tapestry基于Java Servlet API version 2.2,兼容于JDK 1.2以上版本,Tapestry通过变换多 样的组件模式,将一个web application分离为一个联合组件。每一个组件都拥有其特殊的责 任用于显示web页面或者响应HTML请求。 1) Tapestry工作原理 Tapestry应用程序由几个页面组成,这些页面都是由独立的,可重复使用,可配置的组 件组成。 下面是用于描述Tapestry应用程序的基本术语: 页面(Page):应用程序由一堆命名唯一的页面组成,每个页面有一个模板和若干 组件; 模板(Template):一个用于页面(或一个组件)的HTML模板。Tapestry中,一个 模板包括基本的HTML markup,以及一些用于标记组件的特殊属性的标签。 组件(Component):用于Tapestry页面的可重复使用的对象。当一个页面表现时, 或者页面中的一个链接被触发时,组件产生相应的HTML代码。多个组件也可以用 来构成一个新的组件。 参数(Parameter):组件拥有一些参数,用于组件属性与页面属性之间的连接。组 件通常读取自己的参数,但是一些组件(与HTML forms相关)能够更新自己的参 数,并且更新与参数绑定的页面属性。 2) Tapestry与MVC Tapestry组件扮演着控制器Controller的角色,是模式层(Model)中pure-domain objects和包含有组件的HTML模板之间的媒介。大多数情况下,这种方式应用于页面(页 面也是Tapestry组件),但是在某些情况中,一个组件拥有自己的模板,包含着更多的 组件,并且支持与使用者的互交。 页面通过配置一系列属性表达式(Property expressions)连接模式层和表现层。属 性表达式使用另外一种开源框架OGNL(Object Graph Navigation Language)。OGNL的 开源工程(project)独立于Tapestry,但是在Tapestry中起很重要的作用。OGNL主要的 成都麦柯系统集成有限公司 www.cdmcs.com 6 成都麦柯 技术开发二部 目的在于读取和更新对象的Java Bean属性。 3) markup和domain object。 mockup:page mockups是静态HTML页面,用于表现这些动态页面在应用程序运行 时的样子。也就是指在HTML模板中,将会在应用程序运行时被Tapestry组件替换 掉的那部分旧HTML代码。Tapestry组件是动态的,当对HTML模板做美工时, markup的存在将会提供很大的方便。这样,Tapestry程序员可以完全与美工人员各 负其责。 domain object:应用程序的运行,最终取决于整个团队中JAVA部分的构架师和程 序员。在大多数应用程序中,怎样连接用户接口和domain objects成为一个问题。 Domain objects是中间层对象,是应用层,在整个应用程序中,它们是全局对象, 将数据保存到数据库,或者实现你的特殊业务。通常,我们涉及到这些问题:这些 对象中储存着什么信息,怎样将不同的对象关联在一起,以及它们怎样读取数据, 或着将数据储存到数据库。 servlet作为控制器,收到请求。定位并更新domain objects,读取或更新数据库数据。 控制器servlet选择一个表现层(JSP)表现响应。表现层绘制domain objects并最终将响应 页面发送客户端。 4) 页面结构: 在Tapestry应用程序中,一个页面(page)由一个HTML模块,一个页面规范(page specification),和一个JAVA页面类(page class)构成。 每个Tapestry页面有一个特殊的唯一的名称。页面名称被用来定位页面规范和HTML 模板。页面规范的一部分用来实例化JAVA类,这部分称为页面类(page class),包括 指定应用程序中的一些特殊属性和方法。 表现(rendering)页面的第一步是实例化页面。Tapestry框架读取页面规范和HTML 模板并生成页面实例。一个Tapestry页面不是一个单一的对象。页面对象是树对象的根 对象,这些对象包括页面模板中的组件,HTML模板中的内容,以及一些用来连接分散 区域的对象。 最简单的页面: 一个HTML模板; 一个页面规范; 该规范使用XML,必须声明: 一个页面类; 该类必须继承BasePage类。 public abstract class HomePage extends BasePage { 成都麦柯系统集成有限公司 www.cdmcs.com 7 成都麦柯 技术开发二部 } 只要HTML模板中使用Tapestry,就必须声明页面规范和页面类,即便页面规范和页 面类都没有任何属性或方法,变量。 5) 关于属性标签: jwcid属性:(Java Web Component ID)在模板中用来指定组件。 标签:HTML标签是一个用来包容text和elements的容器,其本身 并不能显示任何内容,仅仅是作为一个stylesheet协助对页面显示的控制。 @记号:用来标明一个隐式组件。 6) 属性指定机制(specified properties) 属性指定就是由Tapestry自动生成典型的JavaBean属性。在代码中,定义抽象方法 用来读取和更新属性,只需定义即将使用的方法。Tapestry自己会生成一个子类来实现 你的方法。你甚至不用声明变量,只需要在页面规范中指明类型即可。如: global.springContext.getBean("guestBookService") 在这里,我们定义了两个属性(property)。一个属性命名为:guestBookService, 它的类型是:GuestBookService,并且我们将一个 global.springContext.getBean("guestBookService")的实例赋给了它。两一个属性命 名为:item,类型是:Persistable。 在MessageBoard类里面,当我们需要调用这两个属性的时候,仅仅需要调用该属性 的抽象JavaBean方法,而不需要定义与初始化实例。如: public abstract GuestBookService getGuestBookService(); /** 判断是否有资格进入留言板 */ public boolean isAccessDisable() { return getGuestBookService().isBooleanIPForbid(this.getUserIP()); } 实际上,Tapestry会自动创建一个子类来继承MessageBoard类,并实现以JavaBean 方式实现变量。这样做有三个好处: 第一:减轻程序员负担; 第二:当请求失效过期的时候,Tapestry可以确保自动重置属性。 第三:属性可以被定义为persistent。 这种机制与EJB的container-managed persistence(CMP)类似。 成都麦柯系统集成有限公司 www.cdmcs.com 8 成都麦柯 技术开发二部 7) 组件的分类: 按照组件的使用方式: 隐式组件:组件类型和其结构直接在HTML模板中申明的组件。通常,Tapestry已经 定义好的组件都是以隐式组件的方式使用。 显示组件:其组件类型和结构储存在页面规范中。通常,自定义的组件都是以显示 组件的方式使用。 实际上Tapestry的组件逻辑比较复杂,再加上OGNL表达式和属性指定机制,使得 写注释都变得很不容易。所以在阅读别人写的Tapestry代码的时候,难免有雾里看花, 尤抱琵琶半遮面的感觉。我的看法是,假如有看不懂的代码,暂时死记硬背先。因为 Tapestry是一个功能强大的框架,其组件的可重复使用(reusable)能力非常强。通常例 子程序的页面类中的某一个方法,就已经能够解决与此方法相关的一系列问题。 第二节 eclipse 结合 spindle 的配置 我使用的是 eclipse2.1+resin3.0.3,另外还需要借助 spindle 的 Tapestry 插件(下载地址是: http://spindle.sourceforge.net/updates)。使用 eclipse 的下载并自动安装插件的功能,安装后在 eclipse/plugins/目录下,会增加三个 spindle 子目录,打开 com.iw.plugins.spindle.core_0.0.53 目录,再打开 plugin.xml 配置文件文件,其中有几行关于 Tapestry 包以及 ognl 包的配置: 如果你希望 spindle 插件使用 Tapestry3.0.1 包,可以在这里修改包名,并在 com.iw.plugins.spindle.core_0.0.53 目录下,添加 tapestry-3.0.1.jar 和 tapestry-contrib-3.0.1.jar 两个包。 使用 resin3.0.3,需要安装插件(www.resin.com)。在 eclipse 配置 resin 稍微有些麻烦, 如果你习惯使用 tomcat,那么下面的内容你可以跳过。本文档所使用的示例,均以 resin 作 为 web 服务器。 安装好插件,并将 resin3.0.3 下载解压,将 resin3.0.3/bin/目录下的 resin.dll 文件拷贝到 windows/ system32/目录下。打开 eclipse,对 resin3.0.3 的运行目录进行配置: 成都麦柯系统集成有限公司 www.cdmcs.com 9 成都麦柯 技术开发二部 第三节 创建简单的 Tapestry 项目 首先,我们通过 spindle 的插件来创建一个默认的 Tapestry 项目: 我们将项目起名为 MyTapestry,一切使用默认配置,创建项目之后,我们可以看见如 下的目录结构: 成都麦柯系统集成有限公司 www.cdmcs.com 10 成都麦柯 技术开发二部 在 eclipse/workspace/MyTapestry/目录下,建 lib 目录,将 Tapestry 以及 resin 的各种包文 件拷贝进去,在 eclipse 中刷新,然后导入包文件: 成都麦柯系统集成有限公司 www.cdmcs.com 11 成都麦柯 技术开发二部 运行 resin,再停止项目,然后刷新项目,会发现 MyTapestry 目录下多了一个 resin.conf 文件,打开 resin.conf 文件,注意图中深色部分: 将深色部分修改为: OK,现在让我们来测试一下 Tapestry 项目是否正常运行,将 Home.html 文件打开,写 入如下代码: 成都麦柯系统集成有限公司 www.cdmcs.com 12 成都麦柯 技术开发二部 然后运行 resin 项目,打开一个浏览器,输入:http://127.0.0.1:8080/app 即可: OK,Tapestry 项目顺利运行。 第四节 说明 tapestry 项目中各种文件的作用及配置 我做了一个简单的示例,实际上就是经典的购物车范例的 Tapestry 翻版。打开 MyTapestry 项目,可以看见这个示例的整体结构: 成都麦柯系统集成有限公司 www.cdmcs.com 13 成都麦柯 技术开发二部 1, 配置文件的简单介绍 总体说来,Tapestry 项目中,有 6 种配置文件:web.xml 文件,application 文件,library 文件,Engine 文件(对象),Global 文件(对象)和 Visit 文件(对象)。这 6 种中,只有 web.xml 文件和 application 文件是必须。 在 Tapestry 框架中,每一个页面被分割为三个部分:html 模板,page 规范和页面 java 文件。 1) web.xml 文件 每一个 web 项目都有一个 web.xml 文件,Tapestry 项目的 web.xml 文件,其实并没有太 特别之处: MyTapestry redirect org.apache.tapestry.RedirectFilter MyTapestry org.apache.tapestry.ApplicationServlet 1 MyTapestry /app 这些都是固定的配置,如果希望对 session 有效时间进行设置,可以添加: 60 另外,Tapestry 的 FriendlyURLs 也需要对该 web.xml 文件进行设置。FriendlyURLs 是指 屏蔽 Tapestry 特有的 URLs,使其改变为平常我们熟悉 URLs 格式。不过,FriendlyURLs 属 于 Tapestry 的深层次应用了,我们以后再介绍。 2) application 文件 application 文件是指以项目名称为文件名,以 application 为后缀的文件,例如: MyTapestry.application。 application 文件放在 WEB-INF 目录下,该文件定义了整个 Tapestry 项目的所有文件映射。 例如在 MyTapestry.application 文件中: 成都麦柯系统集成有限公司 www.cdmcs.com 14 成都麦柯 技术开发二部 com.tapestry.Visit com.tapestry.Global 我们实现了如下功能: 第二, application 文件定义了 Engine 对象的映射路径: 第三, application 文件定义了模板编码和输出编码: 第四, application 文件定义了 Visit 对象和 Global 对象的映射路径: com.tapestry.Visit com.tapestry.Global 第五, application 文件定义了页面 html 模板与 page 规范之间的映射路径: 第六, application 文件定义了组件 jwc 模板与 page 规范之间的映射路径: 第七, application 文件定义了组件包的调用路径: 3) Engine 文件(对象) Engine 对象,是指继承于 BaseEngine.java 的文件。Engine 对象是 Tapestry 的底层对象, 是 Tapestry 框架的核心部分。 4) Global 文件(对象) Global 对象是指在 application 文件中声明的 java 文件,Global 对象实际上就是普通 web 应用程序中的 application 对象。 5) Visit 文件(对象) Visit 对象是指在 application 文件中声明的 java 文件,通常实现序列化接口。Visit 对象 实际上就是普通 web 应用程序中的 session 对象。 成都麦柯系统集成有限公司 www.cdmcs.com 15 成都麦柯 技术开发二部 6) library 文件 library 文件很像 application 文件,用于定义自定义组件的 jwc 模板到 page 文件之间的映 射。这种映射关系也可以定义在 application 文件中。如果希望将自己开发的组件打包,那么 就会用到以 library 为后缀的文件。 7) html 模板(jwc 模板) html 模板与 jwc 模板实际上是一样的,均是 HTML 静态页面,相比于普通的静态 HTML 页面,仅仅是在 Tapestry 的 html 模板和 jwc 模板中嵌入了一个 jwcid 属性。因此,Tapestry 对美工的干涉非常小。html 模板是指页面的静态 HTML 页面,而 jwc 是指组件的静态 HTML 页面。 8) page 规范 page 规范是一个 XML 文件,page 文件是 html 模板与页面(组件)java 文件的纽带。对 于组件使用的各种参数声明,我们可以放在 html 模板中,也可以放在 page 规范中。 9) 页面 java 文件(组件 java 文件) 页面 java 文件继承于 BasePage 类,而组件 java 文件继承于 BaseComponent 类。页面(组 件)java 文件均是 abstract 类。在 Tapestry 应用程序实际运行时,Tapestry 会自动提供一个 supclass 继承于这个页面(组件)java 文件来实现其中页面(组件)逻辑。 2,先说 Border 打开 MyTapestry 的所有 HTML 模板,均可以发现: $Content$的含义是一旦页面表现的时候,只表现中间 的部分,不在$Content$包含的内容,在页面表现时会被剔掉。对应的还有一个$remove$, 其意义正好和$Content$相反,凡是所包围的内容,在页面 表现时会被剔掉。 之所以有$Content$和$remove$存在的原因是为了方便美工,在“所见即所得”编辑器 中,美工可以看见页面在实际运行时并不会显示的部分。 我们定义了一个 Border 组件,该组件对应的 Border.java 文件继承于 BaseComponent, 在 Border 组件的 jwc 模板里面,我们定义了 HTML 页面的,和等标签, 然后利用@RenderBody 组件将运行的页面嵌入进来。 因此,在使用了@RenderBody 嵌入 Border 组件的页面里,其实是一个页面 HTML 模板与 一个组件 jwc 模板,同时(实际上是有先后的)运行来表示一个页面。 3,用户登陆 成都麦柯系统集成有限公司 www.cdmcs.com 16 成都麦柯 技术开发二部 既然是表单,当然需要使用 form,Tapestry 的组件 Form,在运行时,会自动向 HTML 写入 form 标签,所以,如果在这里用 监听方法 submit,一旦 form 提交 submit,就会触发这个监听方法: public void submit(IRequestCycle cycle) { if (!this.getPassword().equals("123456")) { this.setError(true); return; } // 将用户名存入Visit对象。 Visit visit = (Visit) this.getPage().getVisit(); visit.setUsername(this.getUsername()); cycle.activate("FirstPage"); //跳转页面 } 对于输入表框组件 TextField,在运行时会自动向 HTML 模板写入 利用 ONGL,一旦 submit,就会调用 setUsername(String s)方法写入表框的输入内容。 对于密码的输入,唯一的不同,是使用 TextField 的 hidden 参数,当该参数为 true 时,向 HTML 模板写入 密码错误! 当用户第一次访问登陆页面时,error在java文件中初始化值为false。一旦提交,触发监 听方法,如果用户输入的密码不等于123456,则this.setError(true);并return退出监听方 法。由于没有跳转页面,此时退出监听方法,即回到了当前登陆页面。而Conditional组件此 时通过参数condition获取getError()所得到的就是true。 4,购物车 成都麦柯系统集成有限公司 www.cdmcs.com 17 成都麦柯 技术开发二部 黑线所围,是 Border 组件所表现内容。红线所围,才是 FirstPage 页面所表现。蓝线 所勾是用户的登陆名。 前面我们提到过,页面使用组件,实际上是组件与页面的整体融合。我们在 FirstPage.html 中使用 Border 组件,那么在表现页面的时候,不光页面的 java 文件 FirstPage.java 文件会执行,组件 Border 的 java 文件 Border.java 也会执行。 在 Home 页面,我们输入用户名,然后将用户名存入 Visit 对象,其实就是 session 中。 跳转到 FirstPage 页面,首先,Border 页面一执行,就会触发一个监听器 PageEnderListener。 实现 PageEnderListener 接口,并重写 PageBeginRender 方法,就使得 Border 组件在表现 之前,能够触发 PageBeginRender 方法。 public void pageBeginRender(PageEvent event) { Visit visit = (Visit) this.getPage().getVisit(); if (visit.getUsername() != null) { this.setUsername(visit.getUsername()); //从Visit对象中获取用户名 this.setRegister(true); //确定用户已经登陆 } } 在该方法中,我们取出 Visit 对象中所保存的用户名,即可通过 Insert 组件表现在页 面上: 用户名称 我们顺便在介绍 ExternalLink 组件,PageLInk 和 DirectLink 组件。DirectLink 组件借助 其 listener 参数中的监听方法,一旦点击链接就触发监听方法。 注销用户 在 Border.java 中有 clearUser 监听方法: /** 注销用户session,并跳转到登陆页面 */ public void clearUser(IRequestCycle cycle) { MyEngine engine = (MyEngine) getPage().getEngine(); engine.logout(); cycle.activate("Home"); } 对于 ExternalLink,传递参数的方式却与 DirectLink 组件恰恰相反。该组件利用 page 参 数指明跳转页面,利用 parameters 参数向 URLs 中添加传递值。所以,必须在下一个页面来 成都麦柯系统集成有限公司 www.cdmcs.com 18 成都麦柯 技术开发二部 接收 URLs 中传递过来的值。 帐目 在这里,生成带有一个 Integer 对象的 URLs,跳转到 SecondPage 页面: 为了从URLs中获取传递过来的参数,需要在SecondPage.java文件中实现IExternalPage 类及 activateExternalPage 方法。在 activateExternalPage 方法中接收出地过来的参数: /** 实现ExternalLink所必须的方法,参数通过URLs传递过来 */ public void activateExternalPage( Object[] parameters, IRequestCycle cycle) { Integer ig = (Integer)parameters[0]; //获取URLs的参数 this.setColumnNo(ig); } 至于 PageLink 组件,相当于不带参数传递功能的 ExternalLink 组件。仅仅需要在该组 件的 page 参数中写入下一个页面的名称即可。 购买 话说 FirstPage 页面,首先映入眼帘的是一个 Form 组件,这次的 Form 组件,使用方法 与 Home 不太一样,没有设置 listener 监听方法。那么,在表单提交的时候,实际上是有 Submit 组件的监听方法来控制 submit 后的操作。 相比于 Form 组件中设置监听方法,在 Submit 组件中设置监听方法显得更灵活。特别 是当一个 Form 中有多个 submit 的时候,被点击的 Submit 只会触发自己的监听方法。 表单 Form 所包围的,是一个 Foreach 组件。 该组件是一个循环列表组件,它根据 source 参数中的 List 或者 Object[]进行遍历循环。 从其 index 参数可以看得出,就如同一个 for 循环,每次遍历,index 的值都加 1。Value 参 数中所指即为 List 或 Object[]中的遍历对象。所以,在 Insert 组件中,每次循环的时候,以 value 参数中对象为基础,取出该对象的成员变量。 名称 Foreach 组件还有两个参数:element 和 class。参数 element 表示该 Foreach 循环以 HTML 的和所包围的内容循环。参数 class 调用 CSS 来装饰 Foreach 遍历列表。 成都麦柯系统集成有限公司 www.cdmcs.com 19 成都麦柯 技术开发二部 bean 是 page 规范的其中一种定义方式,它通过 org.apache.tapestry.bean.EvenOdd 类调用 CSS。在 style.css 中可以看到: tr.even { background-color: #ffffff; } tr.odd { background-color: #eeeeee; } 第二章 Tapestry 组件实战 第一节 熟悉Tapestry组件 成都建信网有个留言板,该留言板有三个功能: 第一:进入留言板后,列出已有的所有留言; 第二:实现留言功能,也就是说,要有表单提交功能; 第三:对每一个留言,都有回复功能,回复只能针对特定留言。 Tapestry实现留言板功能,需要三个文件:MessageBoard.html,MessageBoard.page 和MessageBoard.java。我们怎样将这三个文件联系在一起呢?首先,在web-inf文件夹下有 一个athena.application。该文件指定了html文件与page文件之间的联系: 我们实际上还有另外一种方法,可以指定page文件到HTML模板的映射,在page文件中可 以添加如下一句: 打开MessageBoard.page文件,我们可以发现,声明Tapestry规范之后的第一件事,就是指 定page文件所指向的java文件: 一,列出所有留言: 为了实现留言板的第一个功能,列出数据库中所有关于留言的数据,页面如图: 成都麦柯系统集成有限公司 www.cdmcs.com 20 成都麦柯 技术开发二部 图 1 我们可以看到,是多个 图 2 的循环。下面我们来研究一下,怎样实现这个功能: 先看 HTML 模板:
成都麦柯系统集成有限公司 www.cdmcs.com 21 成都麦柯 技术开发二部
     
  头像  
     
   
     
留言主题:
主页       电子邮件       回复
 
 
成都麦柯系统集成有限公司 www.cdmcs.com 23 成都麦柯 技术开发二部
姓 名
性 别 先生 女士
工作单位
联系电话  
日期时间  
  成都麦柯系统集成有限公司 www.cdmcs.com 24 成都麦柯 技术开发二部
  内容文字  
 

先生女士 回复: 内容文字 
联系电话:  回复日期:  
 
 
     

1)Foreach组件: 首先,我们接触到的第一Tapestry组件是Foreach组件。Foreach是一个循环组件,它遍历 source参数,并在表现其内容前更新value参数。这是Tapestry组件参数的至关重要特性:将 一个属性与一个组件参数绑定,组件不仅读取被绑定的属性,而且更新属性。 Foreach 组件使用标签,当其表现(render)时,并不直接生成任何 HTML 代码。 它仅仅是将其包含的 HTML 标签和包含的组件重复表现。 这里 source 参数通过 OGNL 从 MessageBoard.java 里面的获取 getMessageList()方 法: /** 获取留言列表 */ public List getMessageList() { return getGuestBookService().findAllMagMessage(maxResult, pageNo, 1); } 这里返回的是一个 List,source 参数可以接收 List 类型或者 Object[]类型。 findAllMagMessage(maxResult, pageNo, 1) 是我们底层业务的一个方法,传递参数, 返回一个包含 MagMessage 对象的 List。我们是使用 spring+Hibernate 作为业务逻 辑层的框架,所以 MagMessage 继承于 Persistable 对象。 value="ognl:item"通过 OGNL 表达式,获取 item,而 item 的值就是 Foreach 在 每次循环遍历 List 时,赋的值。打个比方,Foreach 就好象实现了如下代码: Persistable item = new Persistable(); for(int i=0, i 在这里,Tapestry 就是利用了 Java 的反射机制使得 Tapestry 能够访问类的方法,属 性和构造函数。Tapestry 自己构造一个 MessageBoard.java 的子类,写入 item 的 JavaBean 方 法。所以,不要觉得奇怪,这就是为什么我们的 java 文件里面并没有 getItem()和 setItem() 方法,仍然可以通过 OGNL 表达式赋值给 item 或者获取 item 的值。 index="ognl:foreachIndex"的含义是 Foreach 循环是,取出循环索引。我们来看 看这段代码: /** 获取回复列表 */ public List getWriteBackList() { MagMessage mm = (MagMessage) (getMessageList().get(this.getForeachIndex())); int i = mm.getId().intValue(); List list = getGuestBookService().findAllMagMessageByReplyId(i, 2); return list; } 是的,通过每次 Foreach 循环的索引,我们就可以直接得到 Foreach 循环的对象。source 参数的值是一个 list,有了索引,我们微可以通过 list.get(索引)来得到每次循环遍历的对 象。在这里,我们需要取出 Foreach 每次循环的对象,从该对象中取出 id,然后再通过 id 去查询对应该留言的所有回复。这个 getWriteBackList()返回的 list,我们使用在外 部 Foreach 嵌套的内部 Foreach 中:
先生女士 回复: 内容文字  联系电话:  回复日期:   Foreach 组件还有一个参数 class="ognl:beans.evenOdd.next",在这里我们并没 有使用。含义是:在对应的 page 文件找寻类似: 的定义。以便使用 CSS 来修饰 Foreach 循环(在 style.css 文件中): tr.even { background-color: #ffffff; } tr.odd { background-color: #eeeeee; } 2)Insert组件: 我们可以在 HTML 模板中看见大量的 Insert 组件。这个组件很简单,就是实现 out.print ()的功能。 我们刚刚提到,在 Foreach 组件每次循环遍历 list 的时候,把对象赋予 item,现 在我们就是取出 item 对象(MagMessage)中的 username 字段的值。这里我们使用了 OGNL 表达式。等同于:item.getUsername()。 在组件规范中,我们可以看到 Insert 组件还有一个参数 raw,该参数是 boolean 类 型,其默认值为 false。如果设为 true,我们会看见,Insert 组件会解析 value 参数 的值中所包含的 HTML 标签。 3)Image 组件: 头  像 成都麦柯系统集成有限公司 www.cdmcs.com 27 成都麦柯 技术开发二部 图 4 Image 组件是 Tapestry 标准组件,用于插入标签,通过 image 参数生成标签 src 的属性。标签 alt 用来显示图片名称。src 参数在这里存在的原因是为了方便美工,这样在 美工就可以在“所见即所得”编辑器中(如 Dreamware)对页面进行编辑。实际上该组件在 运行时,会在 page 文件中寻找匹配 image 参数的图片路径动态替换 src 的路径。 这里需要介绍一下Asset,Asset被用访问静态文件如images和stylesheets。Image组件 的image参数必须是IAsset类型的对象而不是String,并通过getAsset()方法作为一个 IAsset的对象返回。Asset对象执行IAsset接口,getAsset()方法从AbstractComponent基础 类中继承,能够访问在页面规范中标示为的元素。如: 也可以使用Asset来定义页面规范,例如在我们的CdcinBorder.html里面有: Shell 组件用于定义 HTML 页面的页面规范声明,而 stylesheet 是组件 Shell 的一个 参数,用于获取的 CSS 在 CdcinBorder.page 里面定义: 回到我们对 Image 组件的使用, 头  像 item(MagMessage)的 head 字段保存的内容,仅仅是一个两位数的 Integer 对象,在这 里我之所以加上一个“image”的前缀,是有意的画蛇添足,为了方便识别。比如:item 的 head 字段值为 2,那么’image’+2 即 image2,组件 Image 从 page 文件中找到 name=”image2” 的图片路径:images/messageBoard/2.gif" 我们还可以通过另外一种方式来使用 Image 组件,在 MessageBoard.java 文件里面,我 成都麦柯系统集成有限公司 www.cdmcs.com 28 成都麦柯 技术开发二部 们添一个方法,该方法声明 IAsset 接口: public IAsset getImageName() { return getAsset("Image1"); } 这时,HTML 模板里面的 Image 组件使用方式可以稍微改改:  头像 组件 Image 将会去 page 文件中寻找 name=”image1”的图片路径。 那么,如果图片来自数据库,page 文件在实际使用之前,并不知道真实的图片路径。 我们该怎么做呢? 这是成都建信首页上的“图片新闻”。图片路径来自数据库,在获取数据库数据之前, 我们根本就无法知道图片路径,当然也就无法未卜先知地在 page 页面里定义图片路径。所 以,在 Home.html 对应的 HomePage.java 文件中,我们使用了另外一种方式: public IAsset getNewsImage() { return new ExternalAsset(getPictureUp(), null); } getPictureUp()方法获取了数据库中的图片路径,然后返回一个 ExternalAsset 对象, 在 Home.html: 即可根据数据库动态地表现图片。 4)Conditional 组件 该组件只有一个参数 condition,如果 condition 参数的值为 true,就运行 Conditional 组 件的标签范围内的 HTML 模板,相反则不运行。 先生 女士 因为在数据库中,我们存储的性别类型为 String 类型的 1 和 0,1 代表“男”,0 代表“女”, 所以当我们取出 item.sex 字段的值后,需要把 0 和 1 转换为客户明白的词汇。 成都麦柯系统集成有限公司 www.cdmcs.com 29 成都麦柯 技术开发二部 Conditional 组件非常方便,主要是用来根据 condition 参数来判断需要显示的 HTML 模 板模块。在后续的介绍中,你会发现,我们留言板的“列出留言”,“发表留言”和“回复留 言”三个 HTML 模板是做在一个 HTML 文件中的。但是在实际显示的时候,我们当然只希 望用户看见其中一个。所以我们使用 Conditional 参数来判断何时显示合适的模块。 在组件规范里面,有另外一个组件包 Contrib,其中有两个组件:contrib:When 和 contrib:Otherwise。它们的功能和 Conditional 组件一样。就如同 f-else,这两个组件可以成 套使用,如果 contrib:When 的参数 condition 为 true,则执行 contrib:When 范围内的 HTML 模块,如果为 false,则直接跳到 contrib:Otherwise 的范围去执行其包围的 HTML 模块。另外,还有一个组件 contrib:Choose 也有相似功能。 5)GenericLink 组件 主页   电子邮件 图 5 这个组件有个 href 参数,其作用很明显,就是提供一个链接地址,从 item.homepage 和 item.email 中可以取出地址,只是我们需要在“主页”的链接地址前添加'http://', 在“电子邮件”前添加'mailto:'。 Tapestry 总共有 6 种标准的 link 组件:GenericLink, ActionLink, DirectLink,ServiceLink, ExternalLink 和 PageLink,在后续部分,我们会陆续 介绍另外 5 种 link 组件。组件 GenericLink 通常用于跳转到应用程序范围以外的链接。 因为“主页”和“电子邮件”地址是用户在留言的时候自己填的,有可能有,也有可能 没有。如果没有,就不应该“点击”,所以 GenericLink 有个参数 disabled,该参数默认为 false,当为 true 时,不能被“点击”。关于 disabled 参数,所有 link 足见都有,作 用均相同。 6)ExternalLink 组件 回复 图 6 跳转后的页面: 注意 URLs,其含义我们后面再说明,不过请记住,ExternalLink 组件使用的是 Tapestry 的 9 种 service 中的 external service。 ExternalLink 组件要比 GenericLink 组件复杂一些,在这里,我们使用了三个参数,现 在我们来分别介绍它们: page="MessageBoard"这个参数的作用,就是将要跳转的页面名称,还记得在 athena.application 文件里面,我们怎么配置 HTML 页面到 PAGE 文件的映射关系吗? 是的,ExternalLink 组件的 page 参数根据 athena.application 文件中的配置找到 对应的 page 文件: 然后 page 文件中,根据: 找到对应的 java 文件。在 MessageBoard.java 文件中,我们发现该类继承了一个接口并 实现了一个方法: public abstract class MessageBoard 成都麦柯系统集成有限公司 www.cdmcs.com 31 成都麦柯 技术开发二部 extends PagingNavigation implements IExternalPage, PageRenderListener 以及: public void activateExternalPage(Object[] parameters,IRequestCycle cycle) { Integer ig1 = (Integer) parameters[0]; this.setColumnNo(ig1); if (parameters.length == 2) { Integer ig2 = (Integer) parameters[1]; Visit visit = (Visit) getPage().getVisit(); visit.setMessageId(ig2); } } 对于继承 PagingNavigation 类和实现 PageRenderListener 接口,我们以后在解 释。对于 ExternalLink 组件,我们只需要知道,它必须继承 IExternalPage 接口,并 实现 public void activateExternalPage(Object[] parameters,IRequestCycle cycle)方法。 parameters='ognl:new java.lang.Object[]{new java.lang.Integer("3"), item.id}' 也许你有个疑问,既然必须实现 public void activateExternalPage(Object[] parameters,IRequestCycle cycle)方法,那么 这个方法的两个参数从何而来?是的,第一个参数 Object[] parameters 就是来自于在 ExternalLink 组件中定义的 parameters 参数。在这里,我们抛了一个数组参数 new java.lang.Object[]{new java.lang.Integer("3"), item.id} ,所以 activateExternalPage 方法在接收参数的时候,parameters[0]自然就是一个 Integer(“3”),而 parameters[1]就是 item.id。至于第二个参数 IRequestCycle cycle,可以不需要理会。因为 cycle 传递进来的实际上就是当前访问该页面的 requestcycle。 对于 parameters 参数 Integer ig1 = (Integer) parameters[0],这里传过来 一个 Integer(“3”),是用来控制 MessageBoard.html 页面显示哪一个部分的 Conditional 模块。前面我曾经提到,我们将“留言列表”,“发表留言”以及“留言回 复”三个模块做到一个 HTML 页面中,然后通过 Conditonal 来判断在何种适合的状态下 显示何种模块。这里,传递的是 Integer(“3”),就是说,当 的时候,我们显示“留言回复”模块。至于这个模块到底实现了些什么内容,我们后面再说。 对于parameters第二个参数,item.id实际上是通过OGNL表达式将MagMessage对 象的ID取出来,然后: Integer ig2 = (Integer) parameters[1]; Visit visit = (Visit) getPage().getVisit(); visit.setMessageId(ig2); 存入Visit对象中。Visit对象在Tapestry中是一个比较特别的对象,另外还有一个 Globle对象。在这里,我们只需要记住,visit对象相当于session对象,而Globle对 象相当于Application对象。在后面,讲到Tapestry运行机制的时候,我们再详细了解。 disabled="ognl:item.replyType==2"这个参数和GenericLink组件的同名参数 一样,都是用来判断该链接是否能够被“点击”。 成都麦柯系统集成有限公司 www.cdmcs.com 32 成都麦柯 技术开发二部 二,发表留言:
     
 
  本 站网友应遵守中华人民共和国的各项有关法律法规  
 
1.不得在此发布任 何色情、非法、以及危害国家安全的言论;
2.互相尊重,遵守互联网络道德;
3.严禁互相恶意攻击、漫骂;
4.本站所有留言必须通过管理员审核才能显示;
5.本网站对以上情况的留言有删除和追究其法律责任的权力。

 
  *姓 名: 成都麦柯系统集成有限公司 www.cdmcs.com 33 成都麦柯 技术开发二部 请输入用户名*  
  性 别:  
  工作单位:  
  联系电话:  
  联系地址:  
  电子邮件: 请输入正确的Email格式  
  您的主页:  
  留言标题: 请输入留言标题*  
  * 留言内容: 请输入留言内容*  
  选择表情:
 
  指定何人回复:  
  指定回复单位:  
     
 
     
成都麦柯系统集成有限公司 www.cdmcs.com 37 成都麦柯 技术开发二部 这次,页面功能比较复杂了哦,我们会接触到许多新组件,甚至包括 JavaScript 验证。 7)Form 组件 现在讨论在Tapestry里最能激动人心,也最能让人头晕的组件:Form。 Form在HTML里与在Tapestry里有很大的联系,但是Tapestry里Form组件逻辑和运行方 式远比在HTML中复杂。对于HTML中Form的运行原理,我就不多说了,仅仅列张表,以方 便与Tapestry对比。 成都麦柯系统集成有限公司 www.cdmcs.com 38 成都麦柯 技术开发二部 Tapestry引进一个全新的概念:task-oriented component面向任务组件。Tapestry将原始的 Form元素称为element-oriented component面向元素组件。但是Tapestry为了衔接HTML,并 没 有完全抛弃面向元素组件。 下面是HTML与Tapestry相互对应的列表: Form组件与一个监听方法绑定。Tapestry为Form中的每一个组件提供一个ID,如同原来 的name参数,用来确定各种参数值。通过OGNL来读取和更新属性,所以程序员只需要关心 成都麦柯系统集成有限公司 www.cdmcs.com 39 成都麦柯 技术开发二部 OGNL。 Form组件的内部运行结构为: name=”service”,name=”sp”和name=”Form0”是Tapestry自动添加的。 一旦form被submitted,Tapestry必须完成一些与非Tapestry应用程序一样的工作。 确定被询问(query)参数的名字; 提取被询问参数的值; 完成所有类型转换(如:String转换为Integer); 将转换后的值赋给当前页面的属性; Tapestry使用一种不同的方式来连接被询问参数的名称和当前组件。当Form再次表现 rewind时,这些名称和关系会被重新获得,这种方式称为重新解析(rewind phase)。当form 的submission进行时,重新解析允许Form遍历其内部所有的组件。通过这种方式,在初始化 表现时,form都将会以同一种顺序访问各个控制组件,并且获得与初始化完全相同的元素ID (在后面我们了解Tapestry的动态script时,会发现Form组件按顺序访问其包含组件的特性, 实际上就是JavaScript的form.elements)。 重新解析仅仅发生在Form以及其body里面,而不是整个页面。在这里,我们需要了解一 下,什么是组件的body?简单地说,就是组件所包含(一对span标签)的范围,在后面学习 自定义组件的时候,会发现,实际上每个组件都有一个renderComponent()方法,该方法 用于表现组件的“内容”,即组件的body的本质。如果将与form不相关的组件放在Form里 面,在重新解析时它们产生HTML输出,将会被丢弃。 重新解析时,组件将会从Form组件那里获得自己的元素ID,以便能够正确地表现。每个 组件也可以决定,是否让重新解析发生。通过元素ID,组件能够从引入的请求request中提取 当前被询问参数,并将值赋给页面property。 1, 面向元素组件: 组件 介绍 Checkbox 生成一个check box,并编辑一个绑定于selected的boolean类 型参数。 Form 生成
元素,包含其它组件。 ImageSubmit 生成一个可点击的图标,用于提交表单。 Option 生成一个
还剩115页未读

继续阅读

pdf贡献者

openfan

贡献于2010-09-05

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!