Tapestry
1)概述:
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请求。
2)Tapestry工作原理
Tapestry应用程序由几个页面组成,这些页面都是由独立的,可重复使用,
可配置的组件组成。
下面是用于描述Tapestry应用程序的基本术语:
1,页面(Page):应用程序由一堆命名唯一的页面组成,每个页面有一个模
板和若干组件;
2,模板(Template):一个用于页面(或一个组件)的HTML模板。Tapestry
中,一个模板包括基本的HTML markup,以及一些用于标记组件的特殊
属性的标签。
3,组件(Component):用于Tapestry页面的可重复使用的对象。当一个页
面表现时,或者页面中的一个链接被触发时,组件产生相应的HTML代码。
多个组件也可以用来构成一个新的组件。
4,参数(Parameter):组件拥有一些参数,用于组件属性与页面属性之间的
连接。组件通常读取自己的参数,但是一些组件(与HTML forms相关)
能够更新自己的参数,并且更新与参数绑定的页面属性。
3)Tapestry与MVC
Tapestry组件扮演着控制器Controller的角色,是模式层(Model)中
pure-domain objects和包含有组件的HTML模板之间的媒介。大多数情况下,
这种方式应用于页面(页面也是Tapestry组件),但是在某些情况中,一个组
件拥有自己的模板,包含着更多的组件,并且支持与使用者的互交。
页面通过配置一系列属性表达式(Property expressions)连接模式层和表
现层。属性表达式使用另外一种开源框架OGNL(Object Graph Navigation
Language)。OGNL的开源工程(project)独立于Tapestry,但是在Tapestry
中起很重要的作用。OGNL主要的目的在于读取和更新对象的Java Bean属性。
4)Tapestry classes
Tapestry框架由400多个类和接口组成,但是构建一个Tapestry应用程序仅
需要少数几个类,接口和方法。
1,两个关键接口:IComponent和IPage
这两个接口分别用于定义Tapestry组件和页面。所有的Tapestry代码都是继承
于接口,而不是继承于实现。所以IComponect一向被用于传递参数或者返回值,
而不是使用其实现AbstractComponent。AbstractComponent类是一个基础类,用来
实现组件。AbstractComponect是一个抽象类,定义却不实现renderComponect()
方法。它的子类实现这个方法,使用JAVA代码生成所有HTML。
BaseComponect继承AbstractComponent,增加初始化逻辑以便定位和读取一
个模板。所以大多数自定义组件继承BaseComponent。
在Tapestry中,页面作为一个特殊的组件,Ipage继承Icomponent和BasePage
继承BaseComponent。所以当要创建新页面的时候,继承BasePage。
2,三个很有用的接口:IRequestCycle,IMarkupWriter,和IEngine
大多数Tapestry页面和组件直接使用这三个接口的引用。
IRequestCycle:一个request cycle储存着当前请求的信息。它跟踪有关的活动
页面,用于响应response。在特殊事件中,它通常用来访问Servlet API 对象
(HttpServletRequest, HttpSession, HttpServletResponse)。
IMarkupWriter:一个markup复写器用来生成HTML输出,当一个页面收到一
个响应(response)的时候。它的运作很像java.io.PrintWriter,但是它包含了其它有
用的方法,以生成markup输出(包括XML-style元素和属性)。
IEngine:这是引擎是一个重要对象,用于处理Tapestry应用程序挂起。最初,
这个引擎用来维护服务器端的状态。但是它也可以作为一个处理Tapestry内部子
系统的网关。
5)关于morkup和domain object。
1,mockup:page mockups是静态HTML页面,用于表现这些动态页面在应用
程序运行时的样子。也就是指在HTML模板中,将会在应用程序运行时被
Tapestry组件替换掉的那部分旧HTML代码。Tapestry组件是动态的,当对
HTML模板做美工时,markup的存在将会提供很大的方便。这样,Tapestry
程序员可以完全与美工人员各负其责。
2,domain object:应用程序的运行,最终取决于整个团队中JAVA部分的构
架师和程序员。在大多数应用程序中,怎样连接用户接口和domain objects成
为一个问题。Domain objects是中间层对象,是应用层,在整个应用程序中,
它们是全局对象,将数据保存到数据库,或者实现你的特殊业务。通常,我
们涉及到这些问题:这些对象中储存着什么信息,怎样将不同的对象关联在
一起,以及它们怎样读取数据,或着将数据储存到数据库。
servlet作为控制器,收到请求。定位并更新domain objects,读取或更新
数据库数据。控制器servlet选择一个表现层(JSP)表现响应。表现层绘制
domain objects并最终将响应页面发送客户端。
6)页面结构:
在Tapestry应用程序中,一个页面(page)由一个HTML模块,一个页面
规范(page specification),和一个JAVA页面类(page class)构成。
每个Tapestry页面有一个特殊的唯一的名称。页面名称被用来定位页面规
范和HTML模板。页面规范的一部分用来实例化JAVA类,这部分称为页面类
(page class),包括指定应用程序中的一些特殊属性和方法。
表现(rendering)页面的第一步是实例化页面。Tapestry框架读取页面规
范和HTML模板并生成页面实例。一个Tapestry页面不是一个单一的对象。页
面对象是树对象的根对象,这些对象包括页面模板中的组件,HTML模板中
的内容,以及一些用来连接分散区域的对象。
最简单的页面:
1 一个HTML模板;
2 一个页面规范;
该规范使用XML,必须声明:
<?xml version="1.0"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification
3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.
dtd">
<page-specification class="hangman1.Home"/>
3 一个页面类;
该类必须继承BasePage类。
Public hangman1 extends BasePage {
}
只要HTML模板中使用Tapestry,就必须声明页面规范和页面类,即便页
面规范和页面类都没有任何属性或方法,变量。
7)关于属性标签:
a) jwcid属性:(Java Web Component ID)在模板中用来指定组件。
b) <span>标签:HTML<span>标签是一个用来包容text和elements的容器,
其本身并不能显示任何内容,仅仅是作为一个stylesheet协助对页面显示
的控制。
c) @记号:用来标明一个隐式组件。
8)监听方法(Listener method):
监听方法是普通的实例方法,其签名为:
public void method(IRequestCycle cycle)
这个方法必须是public,return void,并且只有一个类型为IRequestCycle
的参数。所有的页面和组件从AbstractComponent基础类中继承一个listeners
参数。listeners参数包含一个嵌套参数,以便类中每一个监听方法得以执行。
这里有一个接口:IActionListener,以及一些JAVA的反射机制,用来连接组
件和页面的监听方法。
一个类中可以有多个监听方法,每一个监听方法必须有一个不重复的名
字,从父类继承的监听方法同样可以通过listeners参数使用。
9)Visit对象
Visit对象是一个应用程序空间,用来储存应用程序逻辑和数据。这个对
象能被应用程序中所有的页面和组件访问,并且包含WEB应用程序中某一个
客户端的特殊信息。一个单一Visit对象实例被应用程序中所有的页面共享。
该对象类似HttpSession在典型servlet应用程序中扮演的角色。实际上,Visit
对象最终作为一个HttpSession属性被储存。
为了在应用程序中使用一些通用数据,Tapestry认可Visit对象。Tapestry
并不知道也不关心Visit对象的类型。在框架中也没有定义特殊的Visit类,每
一个应用程序自己定义Visit类。页面方法访问Visit对象时并不会指定具体的
类型:
public Object getVisit();
注意强制转换类型:
Visit visit = (Visit)getVisit();
Visit对象是框架自动生成的,在第一次运行时被引用。你必须配置
Tapestry提供实例化,一旦Visit对象生成,就将会持久化储存在
HttpSession中。
10) PageRenderListener接口
这个接口用来通知页面实例,当页面第一次运行时,应该首先执行
pageBeginRender()方法。这个方法的签名为:
public void pageBeginRender(PageEvent event)如:
11)属性指定机制(specified properties)
属性指定就是由Tapestry自动生成典型的JavaBean属性。在代码中,你定义
抽象方法用来读取和更新属性,你必须只定义需要使用的方法。Tapestry自己会
生成一个子类来实现你的方法。你甚至不用声明变量,只需要在页面规范中指明
类型即可。如:
<page-specification class="examples.Login">
<property-specification name="message"
type="java.lang.String"/>
<property-specification name="userName"
type="java.lang.String"/>
<property-specification name="password"
type="java.lang.String"/>
</page-specification>
Tapestry会自动创建一个子类来继承Login类,并实现以JavaBean方式实现变
量。这样做有三个好处:
第一:减轻程序员负担;
第二:Tapestry可以确保自动重置属性,当请求失效过期的时候。
第三:属性可以被定义为persistent。
这种机制与EJB的container-managed persistence(CMP)一样。
12) 组件的分类:
按照组件的使用方式:
a) 隐式组件:组件类型和其结构直接在HTML模板中申明的组件。通常,
Tapestry已经定义好的组件都是以隐式组件的方式使用。
b) 显示组件:其组件类型和结构储存在页面规范中。通常,自定义的组件都
是以显示组件的方式使用。
我个人倾向于将组件按照其工作方式分为三类:
1)容器组件:指组件中可以包含其它组件的组件。
目前接触到的容器组件有:Shell,Body,Conditional和Form四
种。其中最简单的是:Shell和Body,它们仅仅是用在HTML模
板的开头,用来声明<html>,<head>和<title>元素和CSS规范。
最复杂的是Form容器组件,这个组件可以包容若干组件。而这
些被包容的组件有根据它们原理上的不同,分为面向元素组件
和面向任务组件两大类。而Conditional组件根据使用情况,分
为两种,较Form简单。
2)普通组件:指容器组件以外的Tapestry定义组件,可以独立于容器组件单
独运行的组件。
3)自定义组件:
上面这种分类纯粹是我自己的理解,并不是绝对。实际上Tapestry的组件逻辑
非常复杂,再加上OGNL表达式和属性指定机制。甚至使得写注释都变得很
不容易。所以在阅读别人写的Tapestry代码的时候,难免有雾里看花,尤抱琵
琶半遮面的感觉。我的看法是,假如有看不懂的代码,暂时死记硬背先。因
为Tapestry是一个功能强大的框架,其组件的可重复使用(reusable)能力非
常强。通常例子程序的页面类中的某一个方法,就已经能够解决与此方法相
关的一系列问题。
13) 普通组件:
a) DirectLink组件:
用来生成一个从应用程序中反馈回来的特殊类型。这个组件是
Tapestry中两种主要互交产生方式之一,另外一种是user-submitted forms。
DirectLink组件表现为一个HTML<a>元素,用来提供一个URL,当用户点
击时,触发页面中一个特定的监听方法。如:
<a href="#" jwcid="@DirectLink"
listener="ognl:listeners.start">
<img src="images/start.png" width="250"
height="23"
border="0" alt="Start"/></a>
实际运行原理为:
组件容器通过调用RenderComponent()方法,将组件表现为Java代码。而
renderBody()方法是DirectLink组件从基础类AbstractComponent中继承的。
DirectLink组件通过renderComponent()方法来调用renderBody()方法,来表
现组件内容(被DirectLink的<a>和</a>标签包围的静态<img>标签)。
DirectLink组件有几个参数以及一个请求(listener),这个监听参数用来
找到监听方法,并且一旦用户点击链接访问WEB浏览器,监听方法就会
执行。
b)Image组件
Tapestry标准组件,用于插入<img>标签,通过image参数生成标签src
的属性。标签alt用来显示图片名称。如:
<IMG jwcid="@Image"
alt="ognl:visit.game.incorrectGuessesLeft"
image="ognl:digitImage"
height="36"
src="images/Chalkboard_3x8.png"
width="36" border="0"/>
这里需要介绍一下Asset:
Asset被用访问静态文件如images和stylesheets。Image组件的image参数
必须是asset object而不是String,并通过getAsset()方法作为一个object返回。
如:
public IAsset getDigitImage()
{
Visit visit = (Visit)getVisit();
int guessesLeft =
visit.getGame().getIncorrectGuessesLeft();
return getAsset("digit" + guessesLeft);
}
Asset对象执行Iasset接口,getAsset()方法从AbstractComponent基础类中继
承,能够访问在页面规范中标示为<context-asset>的元素。如:
<context-asset name="digit0"
path="images/Chalkboard_1x7.png"/>
<context-asset name="digit1"
path="images/Chalkboard_1x8.png"/>
<context-asset name="digit2"
path="images/Chalkboard_2x7.png"/>
<context-asset name="digit3"
path="images/Chalkboard_2x8.png"/>
<context-asset name="digit4"
path="images/Chalkboard_3x7.png"/>
<context-asset name="digit5"
path="images/Chalkboard_3x8.png"/>
使用Asset来定义页面规范:
c) Foreach组件
Foreach是一个循环组件,它遍历source参数,并在表现其内容前更新
value参数。这是Tapestry组件参数的至关重要特性:将一个属性与一个组件
参数绑定,组件不仅读取被绑定的属性,而且更新属性。
Foreach组件使用<span>标签,当其表现(render)时,并不直接生成
任何HTML代码。它仅仅是将内容(Text)和包含的组件重复表现。
d)Insert组件
这个组件很简单,使用起来很像JSP中的out.print()。只需要指定
value参数即可。
10)Form组件
现在讨论在Tapestry里最能激动人心,也最能让人头晕的组件:Form。
Form在HTML里与在Tapestry里有很大的联系,但是Tapestry里Form组件逻辑
和运行方式远比在HTML中复杂。对于HTML中Form的运行原理,我就不多说了,
仅仅列张表,以方便与Tapestry对比。
Tapestry引进一个全新的概念:task-oriented component面向任务组件。
Tapestry将原始的Form称为element-oriented component面向元素组件。但是
Tapestry为了衔接HTML,并没有完全抛弃面向元素组件。
下面是HTML与Tapestry相互对应的列表:
Form组件与一个监听方法绑定。Tapestry为Form中的每一个组件提供一个
ID,如同原来的name参数,用来确定各种参数值。通过OGNL来读取和更新属性,
所以程序员只需要关心OGNL。
Form组件的内部运行结构为:
name=”service”,name=”sp”和name=”Form0”是Tapestry自动添加的。
一旦form被submitted,Tapestry必须完成一些与非Tapestry应用程序一样的工
作。
确定被询问(query)参数的名字;
提取被询问参数的值;
完成所有类型转换(如:string转换为integer);
将转换后的值赋给当前页面的属性;
Tapestry使用一种不同的方式来连接被询问参数的名称和当前组件。当Form
再次表现时,这些名称和关系会被重新获得,这种方式称为重新解析(rewiind
phase)。当form的submission进行时,重新解析允许Form遍历其内部所有的组件。
通过这种方式,在初始化表现时,form都将会以同一种顺序访问各个控制组件,
并且获得与初始化完全相同的元素ID。
重新解析仅仅发生在Form以及其body里面,而不是整个页面。如果将与form
不相关的组件放在Form里面,如果在重新解析时它们产生HTML输出,将会被丢
弃。
重新解析时,组件将会从Form组件那里获得自己的元素ID,以便能够正确地
表现。每个组件也可以决定,是否让重新解析发生。通过元素ID,组件能够从引
入的请求中提取当前被询问参数,并将值赋给页面property。
1,面向元素组件:
每一个面向元素组件都拥有相似的目的和使用方法。这些组件用来编辑
属性。当一个Form组件被表现,它读取property,并用这个值构造HTML元素
和属性以回复响应。当Form被submitted,相同的组件读取被询问参数的值并
用这个值更新相同的property。
a) TextField组件
这个组件很简单,唯一值得注意的就是TextField组件将HTML中的Text和
Password两个元素合二为一,通过参数hidden来控制,hidden参数默认为false,
当声明它为true时,如同HTML中的Password元素。
b) Checkbox组件
当Form被submitted的时候,一个参数会被询问以检查check box是否被
checked。Tapestry Checkbox组件用来编辑一个boolean页面属性。这个属性被
绑定于组件的selected参数。当组件被表现的时候,这个属性会被读取。如果
其值为true,那么组件会包括一个被标定为selected的<input>元素。如:
<input type="checkbox" jwcid="@Checkbox"
selected="ognl:accepted"> I accept the terms and
conditions.
c) Radio and RadioGroup组件
这两个组件一起工作,RadioGroup为每一个Redio提供一个唯一的ID,
即Redio的value参数。RadioGroup跟踪当前被选中的属性值,并且只有一个
Redio能够被选中。
需要注意的是,value参数可以是任意类型:一个integer,一个string甚至一个
对象。当Form被submitted的时候,RadioGroup和Radio组件返回一个被
selected的ID。Radio组件将被selected的value参数传给RadioGroup组件,
RadioGroup组件更新绑定与selected参数绑定的页面属性。如:
d) Select and Option组件
在HTML中,<select>和<option>是一起使用的,以实现下拉菜单和多选。
每一个option有一个label用于在菜单中显示内容(string),一个value用
于传递值(当submited时),还有一个selected参数用于标明是否被选中。Select
and Option组件运作与Radio and RadioGroup组件相似,如:
e) Submit and ImageSubmit组件
Submit组件是一个标准的submit按钮,而ImageSubmit组件是一个可点
击的image。如:
<input type="submit" jwcid="@Submit"
listener="ognl:listeners.moveUp"
label="Move Up"/>
<input type="submit" jwcid="@Submit"
listener="ognl:listeners.moveDown"
label="Move Down"/>
监听方法用于处理submitted后的工作,而label用于按钮的显示。
关于失效链接(stale link):
当form的动态部分用来表现持久化属性的时候,web浏览器和服务器有
可能不同步,就造成了一个固有的问题。当form被submitted的时候,Tapestry
会发觉引入的form与储存在服务器中的状态不匹配,这就是失效连接。当
Tapestry发觉失效连接,就会抛出StaleLinkException。
在TapestryInAction的ToDo例子里面,当我们执行以下操作的时候,就
会造成失效连接:
1, 启动应用程序;
2, 来到ToDo页面;
3, 点击Add Item按钮;
4, 按下浏览器的后退按钮;
5, 点击Update按钮。
对于失效连接,是Tapestry特有的没有办法避免的问题。所以我们在处理
这种情况的时候要非常小心。至于为什么产生失效连接,这与Form组件运行
机制有关,通常发生在Form组件使用DirectLink组件循环的时候。Tapestry为
Form及包含在其中的所有组件自动分配一个唯一的元素ID。当submitted的时
候,Tapestry将form里面动态的数据输入到服务器中的持久化属性。然后,
点击浏览器上的后退按钮,浏览器回到被submitted之前的状态,如果这个时
候,再次发生submitted,Tapestry会发现,form里面的动态数据和服务器中
的持久化属性不匹配,因为服务器中的持久化属性已经被改动。
在以前的servlet应用程序里面不会发生这种情况,因为在后退之后再
submitted,HttpSession保存的状态是后退之前放在缓存中的状态,所以不会
造成失效连接。而Tapestry是动态加载组件,并且为每个组件指定唯一的不
可重复的ID。
为了解决失效连接问题,Tapestry提供了另外一个组件listEdit来取代
Form+DirectLink组合来完成循环遍利功能。
2, 面向任务组件
a) PropertySelection组件
为了容易便捷地创建下拉菜单,对应于select-option组件,
PropertySelection功能更加强大。
<select jwcid="priority@PropertySelection"
value="ognl:item.priority"
model="ognl:priorityModel"/>
参数model用来指定PropertySelection的具体运作,所以有两种方式完成
model参数,
第一种方式:构造一个类实现IpropertySelectionModel接口,
为model参数构造一个类。需要注意以下几个必须的方法:
getOptionCount()返回option的数量。
getLabel(int index)返回显示在下拉菜单中的string。
getOption(int index)返回的对象作为参数value的值,需要注意
的是,这里的返回类型为Object,也就是说,不仅仅可以是String或
原始类型,也可以是任意的Object,或者是Enum(Tapestry自己构
造的一种Integer类型,并且认为使用这种类型比使用Integer更好。
其典型用法为:
)。
getValue(int index)返回选项option值。
Object translateValue(String value)将option值转换为属性
值。
第二种方式:声明属于IpropertySelectionModel接口的方法。
1) 这种方式一旦构造,就是永恒的。构造只发生在第一次需要时,并储
存数据以便响应以后的请求。
2) 这种构造通过适当地局部使用ResourceBundle。
3) 从页面返回现场属性(locale property),现场应该是可见的。
在这段代码中,还使用了Tapestry的框架类
EnumPropertySelectionModel,实现接口
IpropertySelectionModel,并连接Enums。第一参数是一组Enum值,
用于下拉菜单中显示的内容,第二个参数是一个ResourceBundle包含我
们将要使用的局部化labels,ResourceBundle的keys是Enum实例的名
字。(ResourceBundle是JDK提供的一个工具类。一个ResourceBundle
是一个包含string keys和value的容器,用以读取文件中对应的数据。)
b) Hidden组件:
没有示例,看了半天不知所谓,不晓得怎么用。通过组件规范说明大概结构
为:
1,value参数,object类型,可in或out,必须被声明。当HTML响应请求
时,读取value属性,当submitted时,写出value值。
2,id参数,object类型,只能in,可不必声明。
3,listener方法。只能in,可不必声明。
4,encode参数,boolean型,只能in,可不必声明,默认为true。当为true
时,自动将value类型object转换为string。
c) ListEdit组件:
作为From+DirectLink组合的替代品,可以避免失效连接。它的运行原理
是,将ListEdit组件中包容的所有组件的ID作为Map集合保存起来,同时实现
循环遍历。因此在循环的过程中,它实际上保存了每次循环的状态,因此不
论浏览器怎样后退,ListEdit都能够找到该页面与服务器持久化属性对应的状
态,从而防止不匹配现象发生。
ListEdit组件的使用方法类似DirectLink。
ListEdit必须通过ListEditMap容器使用,在使用前,需要初始化该容器。例如:
1,pageBeginRender()方法用来初始化页面;
2,每一个item被添加到ListEditMap,但是要确保key/value同时被保存。
3,ListEdit组件在设置ListEditMap的key property之后调用监听方法,通
过map的getValue()方法返回与先前储存在pageBeginRender()方法中
相应的对象。如果当前key没有被储存进map,那么返回null。--------
这样做,可以使用户在点击浏览器后退按钮之后再点击submit,不会
发生失效连接。
4, ListEdit组件储存了很多form中的item Ids,页面类必须使用这些ID来
确定item属性以便实例化ToDoItem4。所有这些是通过ListEditMap
的监听方法synchronizieItem()实现的。
d) Upload组件和DatePicker组件
Upload组件用来上传文件,DatePicker组件用来显示时间。
我对这两个组件的看法是,它们的使用方法单一,而且很少被使用,可以直
接照搬例子。
11)Conditional组件
当一个页面中,任意组件被执行,却没有跳转到其它页面时,Conditional组
件就会评估condition参数(一个OGNL表达式),要么执行其body,要么跳过。
如:
有一点需要注意的时,condition参数通过OGNL返回的未必是一个boolean值。
这与我们平常习惯的判断方式不同。例如在上面这个例子里,只要message非空
或非null,就会马上执行Insert组件输出message。所以condition参数通过OGNL返
回的甚至可能是一个任意类型的对象。当然,按照JAVA的观点,String属性也是
对象。只是当返回对象时,Conditional组件的逻辑,显得更复杂。
另外,Conditioal组件可以作为Validation的容器,显示错误信息。
12)验证机制(validation)
比如说,我们要求一个TextField输入的内容必须为String,或者要求password
的输入必须高于6位等,这是就要用到验证机制。
验证机制通过FieldLabel和ValidField两个组件并配搭Conditional组件。
一个FieldLabel搭配一个ValidField。每一个FieldLabel通过FieldLable的field参
数连接到ValidField。FieldLable可以调整自己以反映field参数。如果field中有错
误,FieldLabel能够表现出来,另外FieldLabel获得field用户表现名称以便显示field
(ValidField也有一个相同功能的参数displayName)。这样确保field label和用于错
误信息的field匹配。
Validator是负责将用户输入的信息由string转换为任意类型,并且执行验证检
查。Validator本身并不是组件,它们实现Ivalidator接口。Validator被ValidField组
件使用,实现转换和验证功能。
每一个ValidField组件有一个validator参数,用来绑定validator对象。Validator
对象是共享的,它们并不特别属于某一个ValidField。多个ValidField能够共享同
一个validator实例。
所有validator都包含一个boolean类型的required属性。当required属性被设为
true时,不允许field的输入为空。
Tapestry提供一系列的Ivalidator接口实现:StringValidator用来确认string输入,
以及最少输入字符数目;NumberValidator用来确认numeric输入,以及最大和最
小输入值;DateValidator用来确认Date输入。
Validation的最后一个部分是(验证代理)validation delegate:一个对象,用
来跟踪form 中field的错误信息,并且将错误信息分派给每一个field。
1,使用验证代理。
验证代理有两个功能,第一,它追踪一个form中的每一个错误ValidField。
当form被点击的时候,每一个ValidField传递用户提供的string到对应field的
validator对象。Validator对象可能会将string转换为一个object类,如:Long或者
Double,或者仍然让它保持为String。Validator对象会提供任何验证,如:将转换
后的值填充到一个指定队列。如果转换后的值没有通过验证,validator回报告错
误并返回至ValidField。
Validator报告这些错误是通过ValidationException。ValidField捕捉到异
常,并使用验证代理来记录出错的ValidField元素ID,异常信息以及用户输入的
错误信息。
验证代理的第二个功能,是不连续能力。代理(delegate)的职责是装饰
(decorating)field和label的出错信息。在表现额外的HTML之前,在表现
FieldLabel之后,在表现ValidField之前或之后,验证代理都有可能触发,甚至
ValidField的<input>元素写入额外的属性。逻辑关系如下:
方法writeLabelPrefix()和writeLabelSuffix()允许验证代理将
label装饰为另外的HTML。Validator的
renderValidatorContribution()方法主要用来向客户端写入
JavaScript脚本。验证代理的方法(主要是:writeAttributes())用来
当field出现错误时,装饰<input>元素(被ValidField表现)。
Validator对象和验证代理对象,是Form和ValidField组件必须的。它
们通过组件的参数,将组件联系在一起(Form组件的delegate参数和
ValidField组件的Validator参数)。这些对象必须在使用前实例化和构造,
可以通过在页面类使用JAVA代码中或者在页面规范中使用:helper beans来
实现。下面是两种最简单的构造实例化:
这里的helper bean等同于上面的那个Java代码段。这个<bean>元素指
定了三件事:
你想要实例化的JAVA类。
构造helper bean的所有properties.
Bean的生命周期(lifecycle)
默认情况下,一个Bean的生命周期是request,也就是一旦的request
2,错误显示
Conditioal组件可以作为Validation的容器,显示错误信息。代理(delegate)
的hasErrors属性,默认状态下为false,但是当验证代理发现页面中存在错误
的时候,会将这个属性设为true。如:
其中,监听方法总是询问代理的hasErrors属性。如果该属性为false,则
代表输入验证为安全,并进行下一步。如果为true为真,则运行Conditional
组件,显示错误信息。
直接显示错误信息,会显得笨拙,界面不友好。所以Conditional与
Delegator两个组件配合,以便输出格式化的错误信息。在上面这个例子中,
验证代理同OGNL表达式被捕获,OGNL表达式beans.delegate是通过helper
bean定义的引用。验证代理返回的是“错误对象”,而不仅仅是一个string,
因此我们不能够简单地用Insert组件来显示错误信息。取而代之的是
Delegator组件,这个组件调用render()方法来显示那个“错误对象”。
实际上,这些“错误对象”是可表现对象,而不仅仅是一个简单的string。
做一个全面的假设,那么“错误对象”能够表现HTML的所有类型,而不仅
仅是images, JavaScript pop-up windows, links, formating-----任何HTML。为应
用程序自定义validator能够提供自定义的“错误对象”。验证代理为了方便,
包含一个firstError属性,作为一个表现对象,当第一个field发生错误的时候,
被传递给Delegator,Delegator决定错误信息的显示。
3, Form组件开始:
与前面介绍的Form组件不同,这里它使用了另外一个参数delegate,这个
参数用来连接Form和验证代理。在Form中的每一个FieldLabel和ValidField组
件必须使用用一个验证代理。
<form jwcid="@Form"
listener="ognl:listeners.formSubmit"
delegate="ognl:beans.delegate">
OGNL表达式beans.delegate解释一个验证代理。所有这个Form中的
组件都将共享这个代理。
4, FieldLabel和ValidField的使用
HTML模板中每个ValidField需FieldLabel包含,FieldFabel通过参数
field将数据传送给ValidField。
<span jwcid="@FieldLabel"
field="ognl:components.inputFirstName">First
Name
</span>
OGNL表达式components.inputFirstName是一个引用指向页面的
inputFirstName组件。每个页面提供一个只读Map包含所有的组件。这个
Map的keys就是组件的ID。使用OGNL,你可以很容易地访问Map中的
value。当然,建立一个引用,你必须知道组件的ID,这就是为什么
ValidField组件使用显示ID的原因。
一旦开始表现(render),FieldLabel抛弃它的body(First Name),并且从
ValidField中获得正确的field名字作为label显示。这也许很麻烦,但是却非
常有用。第一,使得显示的label与用来显示错误信息的field的名字匹配。
第二,如果ValidField局部化名字,Fieldlabel仍然能够使用locale-specific
value来匹配。
在这个例子中,每个ValidField都作为显示组件来构造,而不是隐式
组件。每个ValidField出现在HTML模板中,但是类型以及大多数参数在页
面规范中声明。这些ValidField不是匿名的,而是拥有真实ID。
<input type="text" jwcid="inputLastName" size="50"/>
bsf-2.3.0.jar
commons-beanutils-1.6.1.jar
commons-codec-1.2.jar
commons-collections-2.1.jar
commons-digester-1.5.jar
commons-fileupload-1.0.jar
commons-lang-1.0.jar
commons-logging-1.0.2.jar
jakarta-oro-2.0.6.jar
javassist-2.5.1.jar
ognl-2.6.7.jar
tapestry-3.0.3.jar
tapestry-contrib-3.0.3.jar
xercesImpl.jar
xmlParserAPIs.jar
Home.java
packagemo.org.cpttm.album;
importorg.apache.tapestry.*;
importorg.apache.tapestry.html.*;
publicabstractclassHomeextendsBasePage{
publicStringgetImageUrl(intimageId){
returngetEngine().getService("image").getLink(
getRequestCycle(),
null,
newObject[]{newInteger(imageId)}).getURL();
}
publicabstractStringgetImageId();
publicvoidonOk(IRequestCyclecycle){
cycle.activate("Upload");
}
}
ImageDB.java
packagemo.org.cpttm.album;
importjava.io.*;
importjavax.servlet.*;
publicclassImageDB{
publicstaticbyte[]loadImage(intimageId,ServletContextcontext){
FileimageFile=newFile(
context.getRealPath("/WEB-INF/imagedb"),
imageId+".jpg");
try{
FileInputStreaminput=newFileInputStream(imageFile);
try{
byte[]imageData=newbyte[(int)imageFile.length()];
input.read(imageData);
returnimageData;
}finally{
input.close();
}
}catch(IOExceptione){
thrownewRuntimeException(e);
}
}
publicstaticvoidsaveImage(intimageId,byte[]imageData,ServletCont
extcontext){
FileimageFile=newFile(
context.getRealPath("/WEB-INF/imagedb"),
imageId+".jpg");
try{
FileOutputStreamoutput=newFileOutputStream(imageFile);
try{
output.write(imageData);
}finally{
output.close();
}
}catch(IOExceptione){
thrownewRuntimeException(e);
}
}}ImageService.java
packagemo.org.cpttm.album;
importjava.io.*;
importjavax.servlet.*;
importjavax.servlet.http.*;
importorg.apache.tapestry.*;
importorg.apache.tapestry.engine.*;
importorg.apache.tapestry.request.*;
publicclassImageServiceextendsAbstractService{
publicStringgetName(){
return"image";
}
publicvoidservice(
IEngineServiceViewengine,
IRequestCyclecycle,
ResponseOutputStreamoutput)throwsServletException,IOException
{
intimageId= ((Integer)getParameters(cycle)[0]).intValue();
byteimageData[]=ImageDB.loadImage(
imageId,
cycle.getRequestContext().getServlet().getServletContext());
HttpServletResponseresponse=cycle.getRequestContext().getRespo
nse();
response.setContentType("image/jpeg");
response.setHeader("Content-disposition","attachment;filename=fo
o.jpg");
response.setContentLength(imageData.length);
try{
OutputStreamout=response.getOutputStream();
out.write(imageData);
}catch(IOExceptione){
thrownewApplicationRuntimeException(e);
}
}
publicILinkgetLink(IRequestCyclecycle,IComponentcomponent,Obje
ct[]args){
returnconstructLink(cycle,getName(),null,args,false);
}
}
Upload.java
packagemo.org.cpttm.album;
importjava.io.*;
importorg.apache.tapestry.*;
importorg.apache.tapestry.html.*;
importorg.apache.tapestry.request.*;
publicabstractclassUploadextendsBasePage{
publicabstractIUploadFilegetFile();
publicvoidonOk(IRequestCyclecycle){
if(getFile().getFileName().length()==0){
return;
}
byteimageData[]=newbyte[(int)getFile().getSize()];
InputStreamfileInput=getFile().getStream();
try{
fileInput.read(imageData);
}catch(IOExceptione){
thrownewRuntimeException(e);
}
ImageDB.saveImage(101,imageData,cycle.getRequestContext().get
Servlet().getServletContext());
cycle.activate("Home");
}
}
Album.application
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEapplicationPUBLIC
"-//ApacheSoftwareFoundation//TapestrySpecification3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<!--generatedbySpindle,http://spindle.sourceforge.net-->
<applicationname="Album"engine-class="org.apache.tapestry.engi
ne.BaseEngine">
<descriptiong>>addadescription</description>
<pagename="Home"specification-path="Home.page"/>
<servicename="image"class="mo.org.cpttm.album.ImageService"/
>
</application>Home.html
<html>
<ahref=""jwcid="download">Downloadphoto#101</a>
<imgsrc="/Album/app?service=image&sp=101"/>
<formjwcid="form">
<inputtype="Submit"value="OK"/>
</form>
</html>
Home.page
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEpage-specificationPUBLIC
"-//ApacheSoftwareFoundation//TapestrySpecification3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<!--generatedbySpindle,http://spindle.sourceforge.net-->
<page-specificationclass="mo.org.cpttm.album.Home">
<property-specificationname="imageId"type="java.lang.String"/>
<componentid="download"type="ServiceLink">
<static-bindingname="service"value="image"/>
<bindingname="parameters"expression="101"/>
</component>
<componentid="form"type="Form">
<bindingname="listener"expression="listeners.onOk"/>
</component>
</page-specification>
Upload.html
<html>
<formjwcid="uploadForm">
<inputtype="File"jwcid="upload"/><p>
<inputtype="Submit"value="OK"/>
</form>
</html>
Upload.page
<property-specificationname="file"type=<?xmlversion="1.0"encodi
ng="UTF-8"?>
<!DOCTYPEpage-specificationPUBLIC
"-//ApacheSoftwareFoundation//TapestrySpecification3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<!--generatedbySpindle,http://spindle.sourceforge.net-->
<page-specificationclass="mo.org.cpttm.album.Upload">
"org.apache.tapestry.request.IUploadFile"
class=tag>/>
<componentid="uploadForm"type="Form">
<bindingname="listener"expression="listeners.onOk"/>
</component>
<componentid="upload"type="Upload">
<bindingname="file"expression="file"/>
</component>
</page-specification>web.xml
<?xmlversion="1.0"?>
<web-appxmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/TR/xmlschema-1/"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2
_4.xsd"version="2.4">
<display-name>Album</display-name>
<servlet>
<servlet-name>Album</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-clas
s>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Album</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-app>