前面章节中,我们学会了Tapestry如何处理普通链接以及事件链接的传值。本节,我们会学习一样的东西,不过,要学习一些HTML表单的操作。
Tapestry 对表单的支持很好,用一节来讲解是太少了。这里我们学习一些基本功能,包括一些常用的开发模式。让我们创建一个地址簿项目来开始学习。
我们先来创建一些存储信息的实体类。这些类我们放在叫 entities 的包中。和pages(放置组件类的包)包不同,entities 不是Tapestry必须的包,只是一个惯例(方便,快捷)
Tapestry 认为public属性才是JavaBeans 属性;因为 Address 对象只是'沉默的对象',没必要去写getter以及setter方法。这里,我们定义实体类所有属性为公共的:
package
com.example.tutorial1.entities;
import
com.example.tutorial1.data.Honorific;
public
class
Address
{
public
Honorific honorific;
public
String firstName;
public
String lastName;
public
String street1;
public
String street2;
public
String city;
public
String state;
public
String zip;
public
String email;
public
String phone;
}
|
我们需要定义枚举类型的Honorific:
package
com.example.tutorial1.data;
public
enum
Honorific
{
MR, MRS, MISS, DR
}
|
地址页面
接下来创建地址相关的页面:新建地址,编辑地址,搜索并列表展示。我们需要创建一个子文件夹来保存这些文件。先来创建第一个页面:"address/Create" (需要带着路径--我们会用一分钟来讲解如何映射到相应类和模板).
首先,修改Index.tml,增加一个创建地址的链接:
<
h1
>地址簿</
h1
>
<
ul
>
<
li
><
t:pagelink
page
=
"address/create"
>新建地址</
t:pagelink
></
li
>
</
ul
>
|
现在,我们需要 address/Create 页,先创建一个空白模板来测试我们的链接是否生效。
<
html
t:type
=
"layout"
title
=
"新建地址"
<
em
>即将到来....</
em
>
</
html
>
|
(注意: Tapestry 5.4,使用 tapestry_5_4.xsd
替换.)
接着,响应类:
package
com.example.tutorial1.pages.address;
public
class
CreateAddress
{
}
|
那么 ... 为什么不直接命名类为"Create"而是 "CreateAddress" ? 实际上我们命名为 "Create", 系统依旧可以工作,但是较长的类名同样有效。Tapestry 自动裁剪多余的类名后缀(com.example.tutorial1.pages.address
.CreateAddress) 。
Tapestry 实际上为页面创建了许多类别名;其中任意一个都可以用于页面链接。你可以在控制台中看到这些信息:
[INFO] TapestryModule.ComponentClassResolver Available pages (
12
):
(blank): com.example.tutorial1.pages.Index
ComponentLibraries: org.apache.tapestry5.corelib.pages.ComponentLibraries
Error404: com.example.tutorial1.pages.Error404
ExceptionReport: org.apache.tapestry5.corelib.pages.ExceptionReport
GameOver: com.example.tutorial1.pages.GameOver
Guess: com.example.tutorial1.pages.Guess
Index: com.example.tutorial1.pages.Index
PageCatalog: org.apache.tapestry5.corelib.pages.PageCatalog
PropertyDisplayBlocks: org.apache.tapestry5.corelib.pages.PropertyDisplayBlocks
PropertyEditBlocks: org.apache.tapestry5.corelib.pages.PropertyEditBlocks
ServiceStatus: org.apache.tapestry5.corelib.pages.ServiceStatus
T5Dashboard: org.apache.tapestry5.corelib.pages.T5Dashboard
address/Create: com.example.tutorial1.pages.address.CreateAddress
address/CreateAddress: com.example.tutorial1.pages.address.CreateAddress
|
Tapestry 构造URL时,使用最短的别名。
以后,你的应用可能会有很多实体类:也许你会有个 "user/Create" 页和 "payment/Create" 页以及"account/Create"页。你可以拥有很多不同包内的类命名为Create, 在Java中是合法的,但是使用上不太理想。也许你会错误的编辑Payment Create页而你实际想修改Account Create页。
Tapestry 建议您使用具有描述性的类名: CreateAddress, 而不是 Create,这样不会带来很多麻烦 (从又长又丑的URL角度来看). URL依然为 http://localhost:8080/tutorial1/address/create.
记住, Tapestry指定了组件名称和模板名称对应,模板名称也应该像Java类一样命名:CreateAddress.tml.
首页也是这样工作的。一个类命名为 com.example.tutorial1.pages.address.AddressIndex 可以作为 "address/Index"来访问.但是,Tapestry对于命名为Index的页渲染有个特殊的规则,可以使用 http://localhost:8080/tutorial1/address/. 换句话说,你可以在任意文件夹放置Index页,Tapestry会为它构建一个较短的URL... 没必要为类命名为Index(多个包下,使用同一个名字让人迷惑;你可以在每个包中包含它来解决这个问题。Tapestry 使用一种智能便捷方式来保证指向正确的URL。
使用BeanEditForm
现在开始组合逻辑表单。 对于客户端表单,Tapestry 有一个特殊组件: Form 组件, 当然还有表单控制的组件比如 Checkbox 和 TextField. 稍后我们来详细介绍...现在通过 Tapestry的BeanEditForm 组件,来完成一些重要的东西。
为CreateAddress 模板添加以下代码 (替换 "即将到来..."):
<
t:beaneditform
object
=
"address"
/>
|
在 CreateAddress 类添加属性:
@Property
private
Address address;
|
刷新页面,你可能会在页面顶部看到如下警告:
如果看到了警告,意味着你的应用需要创建一个HMAC密码。只需要编辑 AppModule.java 类 (在 services 包里),添加如下代码到 contributeApplicationDefaults 方法:
// Set the HMAC pass phrase to secure object data serialized to client
configuration.add(SymbolConstants.HMAC_PASSPHRASE,
""
);
|
但是,不要使用空字符串,使用一个长的随机字符串(比如非常长的起码30位的密码)私自保存。
完成后,重启应用,点击新建地址链接,你会看到类似的效果:
在这里,Tapestry 完成了很多工作。已经创建了包含每个属性的表单,而且枚举Honorfic属性已经提供了下拉列表。
此外,Tapestry 将属性名("city", "email", "firstName")转换成易读模式 ("City", "Email", "First Name"). 实际上,这些都是 <label> 元素,点击标签,光标会成为第一响应者。
很厉害吧;多么简洁美观的用户界面。多么美丽的表单,让我们开始对它自定义吧。
修改顺序
BeanEditForm 必须知道如何正确的排序,对应Public属性,按字母排序。对于标准JavaBean属性,BeanEditForm默认以类中的getter方法顺序排序 (如果可能,使用行号).
以下是一个较好的排序方式:
- honorific
- firstName
- lastName
- street1
- street2
- city
- state
- zip
- phone
我们可以使用 BeanEditForm的reorder参数来实现排序,以逗号隔开:
<
t:beaneditform
object
=
"address"
reorder
=
"honorific,firstName,lastName,street1,street2,city,state,zip,email,phone"
/>
|
自定义标签
对于Tapestry来说,自定义标签非常容易。只需要为它创建一个 message 目录 .
在Tapestry 里,允许每个页面和组件拥有自己message目录。这是一个标准的Java properties 文件,需要和页面或组件同名,扩展名为.properties。一个message目录由很多行组成,每一行都有一个key和一个value以等号连接。
所有这些会创建一个特殊命名的message entry,带有"-label"后缀。和其它地方一样,Tapestry 忽略大小写。
也许你需要重启应用来强迫Tapestry加载改变,因为这里新建了文件:
当然我们也可以自定义下拉列表名称。我们只需要在message目录添加一些entry来匹配现有的枚举类型。修改CreateAddress.properties 并添加:
注意,我们没必要包含所有枚举类型,默认自动转换 。每个选项标签都会单独去.properties查找。
最后,默认的提交按钮是 "Create/Update" (BeanEditForm 不知道如何使用).我们来修改成 "Create Address".
这是一个BeanEditForm组件。它不是一个属性,我们也就没办法在message目录修改名称,因为那只对属性有效。幸好,BeanEditForm组件包含一个可改变名称的参数,只需要修改 CreateAddress 组件模板:
<
t:beaneditform
submitlabel
=
"Create Address"
object
=
"address"
reorder
=
"honorific,firstName,lastName,street1,street2,city,state,zip,email,phone"
/>
|
submitlabel 默认参数值是 "Create/Update",但我们可以重写成特殊的值。
最终效果:
在继续之前,我们说说message目录。Message 目录不只是重命名标签或下拉选项,稍后章节我们会讲到message目录如何使用在本地化和国际化中。
接下来我们来设置标签引用来替换模板内设置提交按钮标签;标签的实际内容会存储在Message 目录。
在 Tapestry 中, 当绑定一个参数时,也许你提供的值是包含前缀的。前缀指引Tapestry如何识别参数值...是属性的名字?组件的id?消息Key值?大多数都有默认前缀: "prop:",当你没提供时,默认就是这个 (这会使模板页尽可能精简).
这里我们从message目录引用,因此我们使用 "message:" 前缀:
<
t:beaneditform
object
=
"address"
submitlabel
=
"message:submit-label"
reorder
=
"honorific,firstName,lastName,street1,street2,city,state,zip,email,phone"
/>
|
我们在message 目录设置 submit-label :
最后,正确的HTML发送到客户端,不管你包含标签文本或正确的在message目录设置。从长远的角度看,后面的方法维护起来更方便。
增加校验
在考虑存储地址对象前,我们需要确保填写的值是有效的。比如,一些值是必须的,手机号和电子邮件有特殊的格式。
BeanEditForm 使用Tapestry特殊的注解 @Validate 来检查,可添加在每个属性的getter 或setter方法 。
编辑实体类Address, 为 lastName, firstName, street1, city, state 和 zip fields, 添加 注解 @Validate :
@Validate
(
"required"
)
public
String firstName;
|
"required"字符串是什么?它就是你的特殊期望校验。包含一些期望校验类型名称。许多的校验器被构建,比如 "required", "minLength" 和 "maxLength". 和其它地方一样,Tapestry 忽略大小写。
使用逗号分割开校验名称可以完成多重校验。一些校验是可以配置的(使用等号)。所以你可以写成 "required,minLength=5" 来校验不为空,最少5个字符。
当改变一个实体类时,你可能会被自己迷惑到。比如添加了@Validate注解,没有在浏览器看到效果。不要担心,在Tapestry中,只有组件类和大部分服务类是不需要重启服务的。对于数据和实体类则行不通,所以这里你需要停止应用,重启Jetty来看运行效果。
重启,刷新,点击新建地址按钮:
这只是点击新建地址按钮后的提示;所有的属性都被校验并进行错误提示。每个属性的错误提示都使用了红色高亮提示用户,不符合条件的输入框也都成了红色高亮,这样用户就能一眼看出哪里有问题了。光标也聚焦到第一个错误输入框。所有这些到在客户端完成,不需要和服务端进行交互。
一旦错误被更正,表单就可以提交,所有的校验也会在服务端进行(当客户端禁用JS时)
那么 ... 我们来谈谈比"required or not"更好用的东西. Tapestry 构建了属性长度校验支持,包括正则表达式。邮编这样的东西非常适合使用正则。
@Validate
(
"required,regexp=^\\d{5}(-\\d{4})?$"
)
public
String zip;
|
我们来试一下;重启应用,输入邮编"abcd".
在输入后,点击新建地址按钮,你会看到以上结果。
当提交表单时,现代浏览器会自动校验表单属性的正则,就像上面展示的一样。老版的浏览器不支持,但仍旧可以使用上面说到的注解方法校验输入框。
无论如何,正确的校验行为需要正确的提示信息。你的用户也行不知道什么是正则。
幸好, 我们可以自定义校验提示信息。我们只需要知道属性名称(zip)和校验名称(regexp)。我们可以在Message目录添加一下信息:
刷新,提交表单:
该方法不仅仅适用于正则,任意的校验方法都可以使用。
让我们进一步学习。我们可以移除正则匹配模式。如果你在属性前只提供了注解@Validate,Tapestry会搜索页面容器message目录的限制条件以及错误提示信息。
@Validate
(
"required,regexp"
)
public
String zip;
|
现在,添加正则表达式到 CreateAddress message 目录:
重启应用,你将看到 ... 同样的效果. 当构建复杂的应用时,这样的写法会变的很漂亮。在Message目录,你可以修改正则表达式来调整校验方式,这样的好处是不需要重启应用。
这里我们还可以为手机号和电子邮件添加正则表单式(你自己来完成吧!)。我们还没能自定义 BeanEditForm 的组件.
现在你也许会很好奇提交成功后该怎么做,让我们下一节来讨论吧!