【转】Tapestry 入门

1)Foreach组件: 
首先,我们接触到的第一Tapestry组件是Foreach组件。Foreach是一个循环组件,它遍历source参数,并在表现其内容前更新value参­数。这是Tapestry组件参数的至关重要特性:将 
一个属性与一个组件参数绑定,组件不仅读取被绑定的属性,而且更新属性。 
Foreach 组件使用<span>标签,当其表现(render)时,并不直接生成任何HTML 代码。 
它仅仅是将其包含的HTML 标签和包含的组件重复表现。 
<span jwcid="@Foreach" source="ognl:messageList" value="ognl:item" 
index="ognl:foreachIndex"> 
这里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<list.length; i++) { 
item = (MagMessage)list.get(i); 
foreachIndex = i; 
/** 
* 实现HTML 代码 
*/ 
} 

item 要获得Foreach 每次循环赋予的对象, 我们需要对其进行声明, 在MessageBoard.page 文件中,我们可以看见: 
<property-specification name="item" type="com.cdmcs.bo.Persistable "/> 
在这里,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 中: 
<span jwcid="@Foreach" source="ognl:writeBackList" 
value="ognl:item"> 
<tr> 
<td colspan="4"><hr color=#000000></td> 
</tr> 
<tr> 
<td width="25%" align="left" valign="top"><img 
src="images/messageBoard/b1.gif" width="15" align="absmiddle" 
height="15"> 
<span jwcid="@Insert" 
value="ognl:item.username"/><span jwcid="@Conditional" 
condition='ognl:item.sex.equals("1")'>先生</span><span 
jwcid="@Conditional" condition='ognl:item.sex.equals("0")'>女士</span> 
回复: 
</td> 
<td colspan="2" align="left"><span jwcid="@Insert" 
value=" ognl:@com.cdmcs.util.FormatString@formatNewline(item.content)" 
raw="true">内容文字&nbsp;</span></td> 
</tr> 
<tr align="right"> 
<td colspan="2" align="left">联系电话:<span 
jwcid="@Insert" value="ognl:item.telephone"/>&nbsp;</td> 
<td width="45%" align="left">回复日期:<span 
jwcid="@Insert" value="ognl:item.messageDate"/></td> 
<td width="1%" align="left">&nbsp;</td> 
</tr> 
</span> 
Foreach 组件还有一个参数class="ognl: beans.evenOdd.next",在这里我们并没有使用。含义是:在对应的page 
文件找寻类似: 
<bean name="evenOdd" class="org.apache.tapestry.bean.EvenOdd"/> 
的定义。以便使用CSS 来修饰Foreach 循环(在style.css 文件中): 
tr.even { 
background-color: #ffffff; 
} 

tr.odd { 
background-color: #eeeeee; 
} 

2)Insert组件: 
我们可以在HTML 模板中看见大量的Insert 组件。这个组件很简单,就是实现out.print 
()的功能。 
<span jwcid="@Insert" value="ognl:item.username"/> 
我们刚刚提到,在Foreach 组件每次循环遍历list 的时候,把对象赋予item,现在我们就是取出item 
对象(MagMessage)中的username 字段的值。这里我们使用了 
OGNL 表达式。等同于:item.getUsername()。 
在组件规范中,我们可以看到Insert 组件还有一个参数raw,该参数是boolean 
类型,其默认值为false。如果设为true,我们会看见,Insert 组件会解析value 参数 
的值中所包含的HTML 标签。 
3 
)Image 组件: 
<img jwcid="@Image" image="ognl:getAsset('image' + item.head)" alt="头 
像" src="images/messageBoard/1.gif" width="32" height="32" border="0"/> 
图4 
Image 组件是Tapestry 标准组件,用于插入<img>标签,通过image 参数生成标签src的属性。标签alt 用来显示图片名称。src 
参数在这里存在的原因是为了方便美工,这样在美工就可以在"所见即所得"编辑器中(如Dreamware)对页面进行编辑。实际上该组件在 
运行时,会在page 文件中寻找匹配image 参数的图片路径动态替换src 的路径。 
这里需要介绍一下Asset,Asset被用访问静态文件如images和stylesheets。Image组件的image参数必须是IAsset类型的对&shy;象而不是String,并通过getAsset()方法作为一个 
IAsset的对象返回。Asset对象执行IAsset接口,getAsset()方法从AbstractComponent基础类中继承,能够访问在页面规&shy;范中标示为<context-asset>的元素。如: 
<context-asset name="image1" path="images/messageBoard/1.gif"/> 
<context-asset name="image2" path="images/messageBoard/2.gif"/> 
<context-asset name="image3" path="images/messageBoard/3.gif"/> 
<context-asset name="image4" path="images/messageBoard/4.gif"/> 
<context-asset name="image5" path="images/messageBoard/5.gif"/> 
<context-asset name="image6" path="images/messageBoard/6.gif"/> 
<context-asset name="image7" path="images/messageBoard/7.gif"/> 
<context-asset name="image8" path="images/messageBoard/8.gif"/> 
<context-asset name="image9" path="images/messageBoard/9.gif"/> 
<context-asset name="image10" path="images/messageBoard/10.gif"/> 
<context-asset name="image11" path="images/messageBoard/11.gif"/> 
<context-asset name="image12" path="images/messageBoard/12.gif"/> 
也可以使用Asset来定义页面规范,例如在我们的CdcinBorder.html里面有: 
<html jwcid="@Shell" stylesheet="ognl:assets.stylesheet"> 
Shell 组件用于定义HTML 页面的页面规范声明,而stylesheet 是组件Shell 的一个参数,用于获取的CSS 
在CdcinBorder.page 里面定义: 
<context-asset name="stylesheet" path="style/style.css"/> 
回到我们对Image 组件的使用, 
<img jwcid="@Image" image="ognl:getAsset('image' + item.head)" alt="头 
像" src="images/messageBoard/1.gif" width="32" height="32" border="0"/> 
item(MagMessage)的head 字段保存的内容,仅仅是一个两位数的Integer 
对象,在这里我之所以加上一个"image"的前缀,是有意的画蛇添足,为了方便识别。比如:item 的 
head 字段值为2,那么'image'+2 即image2,组件Image 从page 文件中找到name="image2" 
的图片路径:images/messageBoard/2.gif" 
我们还可以通过另外一种方式来使用Image 组件,在MessageBoard.java 文件里面,我们添一个方法,该方法声明IAsset 接口: 
public IAsset getImageName() { 
return getAsset("Image1"); 
} 

这时,HTML 模板里面的Image 组件使用方式可以稍微改改: 
<img jwcid="@Image" image="ognl: imageName " alt=" 头像" 
src="images/messageBoard/1.gif" width="32" height="32" border="0"/> 
组件Image 将会去page 文件中寻找name="image1"的图片路径。 
那么,如果图片来自数据库,page 文件在实际使用之前,并不知道真实的图片路径。 
我们该怎么做呢? 
这是成都建信首页上的"图片新闻"。图片路径来自数据库,在获取数据库数据之前,我们根本就无法知道图片路径,当然也就无法未卜先知地在page 
页面里定义图片路径。所 
以,在Home.html 对应的HomePage.java 文件中,我们使用了另外一种方式: 
public IAsset getNewsImage() { 
return new ExternalAsset(getPictureUp(), null); 
} 

getPictureUp()方法获取了数据库中的图片路径,然后返回一个ExternalAsset 对象,在Home.html: 
<img jwcid="@Image" image="ognl:newsImage" height="99" border="0"/> 
即可根据数据库动态地表现图片。 
4)Conditional 组件 
该组件只有一个参数condition,如果condition 参数的值为true,就运行Conditional 组件的<span>标签范围内的HTML 
模板,相反则不运行。 
<span jwcid="@Conditional" condition='ognl:item.sex.equals("1")'> 
先生 
</span> 
<span jwcid="@Conditional" condition='ognl:item.sex.equals ("0")'> 
女士 
</span> 
因为在数据库中,我们存储的性别类型为String 类型的1 和0,1 代表"男",0 代表"女",所以当我们取出item.sex 字段的值后,需要把0 
和1 转换为客户明白的词汇。 
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 组件 
<a jwcid="@GenericLink" href="ognl:'http://' + item.homepage" 
disabled="ognl:item.homepage==null"><img 
src="images/messageBoard/home.gif" width="16" height="16" 
align="absmiddle" border="0"/>主页</a>&nbsp;&nbsp; 
<a jwcid="@GenericLink" href="ognl:'mailto:' + item.email " 
disabled="ognl:item.email==null"><img 
src="images/messageBoard/mail.gif" width="16" height="16" 
align="absmiddle" border="0"/>电子邮件</a> 
图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 组件 
<a jwcid="@ExternalLink" page="MessageBoard" parameters='ognl:new 
java.lang.Object[]{new java.lang.Integer("3"), item.id}' 
disabled="ognl:item.replyType==2 "><img 
src="images/messageBoard/replay.gif" width="16" height="16" 
align="absmiddle" border="0"/>回复</a> 
图6 
跳转后的页面: 
注意URLs,其含义我们后面再说明,不过请记住,ExternalLink 组件使用的是Tapestry的9 种service 中的external 
service。 
ExternalLink 组件要比GenericLink 组件复杂一些,在这里,我们使用了三个参数,现在我们来分别介绍它们: 
page="MessageBoard"这个参数的作用,就是将要跳转的页面名称,还记得在athena.application 
文件里面,我们怎么配置HTML 页面到PAGE 文件的映射关系吗? 
是的,ExternalLink 组件的page 参数根据athena.application 文件中的配置找到对应的page 文件: 
<page name="MessageBoard" 
specification-path="/WEB-INF/MessageBoard.page"/> 
然后page 文件中,根据: 
<page-specification class=" com.cdmcs.tapestry.page.MessageBoard"> 
找到对应的java 文件。在MessageBoard.java 文件中,我们发现该类继承了一个接口并实现了一个方法: 
public abstract class MessageBoard 
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。至于第二个参数I&shy;RequestCycle 
cycle,可以不需要理会。因为cycle 传递进来的实际上就是当前访问该页面的requestcycle。 
对于parameters 参数Integer ig1 = (Integer) 
parameters[0],这里传过来一个Integer("3"),是用来控制MessageBoard.html 页面显示哪一个部分的 
Conditional 模块。前面我曾经提到,我们将"留言列表","发表留言"以及"留言回复"三个模块做到一个HTML 
页面中,然后通过Conditonal 来判断在何种适合的状态下 
显示何种模块。这里,传递的是Integer("3"),就是说,当<span jwcid="@Conditional" 
condition="ognl:columnNo==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组件的同名参数 
一样,都是用来判断该链接是否能够被"点击"。 
二,发表留言: 
<form jwcid="leaveWord@Form" delegate="ognl:beans.delegate"> 
<table width="83%" border="0" cellspacing="0" cellpadding="0"> 
<tr> 
<td width="21" height="19" 
background="images/bl_1.gif">&nbsp;</td> 
<td width="572" background="images/bl_2.gif">&nbsp;</td> 
<td width="20" background="images/bl_4.gif">&nbsp;</td> 
</tr> 
<tr> 
<td height="50" background="images/bl_5.gif">&nbsp;</td> 
<td align="center"><table width="98%" border="0" 
cellspacing="0" cellpadding="0"> 
<tr> 
<td width="10" height="35">&nbsp;</td> 
<td colspan="3" align="center"><span class="style2"> 本 
站网友应遵守中华人民共和国的各项有关法律法规</span></td> 
<td width="9">&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td colspan="3" align="left"> 
<table width="100%" border="0" cellpadding="0" 
cellspacing="1" bgcolor="#CCCCCC"> 
<tr> 
<td height="80" bgcolor="#FFFFFF"> 1.不得在此发布任 
何色情、非法、以及危害国家安全的言论; <br /> 
2.互相尊重,遵守互联网络道德; <br /> 
3.严禁互相恶意攻击、漫骂; <br /> 
4.本站所有留言必须通过管理员审核才能显示; <br /> 
5.本网站对以上情况的留言有删除和追究其法律责任的权力。 
</td> 
</tr> 
</table> 
<br /></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td width="102" height="25" align="center">*姓名:</td> 
<td colspan="2" align="left"> 
<span jwcid="@FieldLabel" 
field="ognl:components.userNameLeaveWord"/> 
<input maxlength="200" 
jwcid="userNameLeaveWord"/><font color="#FF0000">请输入用户名*</font> 
</td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">性别:</td> 
<td colspan="2" align="left"><select name="select4" 
jwcid="@PropertySelection" value="ognl:sexOptionSelected" 
model="ognl:sexSelectionModel"> 
<option value="1" selected>先生</option> 
<option value="2">女士</option> 
</select></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">工作单位:</td> 
<td colspan="2" align="left"><input name="Input" 
value="ognl:company" jwcid="workCompany@TextField"/></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">联系电话:</td> 
<td colspan="2" align="left"><input name="Input2" 
value="ognl:telephone" jwcid="@TextField"/></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">联系地址:</td> 
<td colspan="2" align="left"><input name="Input3" 
value="ognl:address" jwcid="@TextField"/></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">电子邮件:</td> 
<td colspan="2" align="left"> 
<span jwcid="@FieldLabel" 
field="ognl:components.emailLeaveWord"/> 
<input maxlength="200" 
jwcid="emailLeaveWord"/><font color="#FF0000">请输入正确的Email格式 
</font> 
</td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">您的主页:</td> 
<td colspan="2" align="left"><input name="Input4" 
value="ognl:homePage" jwcid="@TextField"/></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">留言标题:</td> 
<td colspan="2" align="left"> 
<span jwcid="@FieldLabel" 
field="ognl:components.titleLeaveWord"/> 
<input maxlength="200" jwcid="titleLeaveWord"/><font 
color="#FF0000">请输入留言标题*</font> 
</td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td align="center" class="biao22">* 留言内容:</td> 
<td colspan="2" align="left"> 
<span jwcid="textareaLeaveWord"><textarea 
name="textfield23" cols="40" rows="6" 
class="MultiLineText"></textarea></span> 
<font color="#FF0000">请输入留言内容*</font> 
</td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="35" align="center">选择表情:</td> 
<td colspan="2" align="left"><table width="90%" 
border="0" cellpadding="0" cellspacing="1" bgcolor="#CCCCCC"> 
<tr> 
<td height="29" bgcolor="#FFFFFF"> <span 
jwcid="@RadioGroup" selected="ognl:headImage"> 
<input jwcid="@Radio" value="1" type="radio"/> 
<img src="images/messageBoard/1.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="2" type="radio"/> 
<img src="images/messageBoard/2.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="3" type="radio"/> 
<img src="images/messageBoard/3.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="4" type="radio"/> 
<img src="images/messageBoard/4.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="5" type="radio"/> 
<img src="images/messageBoard/5.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="6" type="radio"/> 
<img src="images/messageBoard/6.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="7" type="radio"/> 
<img src="images/messageBoard/7.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="8" type="radio"/> 
<img src="images/messageBoard/8.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="9" type="radio"/> 
<img src="images/messageBoard/9.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="10" type="radio"/> 
<img src="images/messageBoard/10.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="11" type="radio"/> 
<img src="images/messageBoard/11.gif" width="32" 
height="32"> 
<input jwcid="@Radio" value="12" type="radio"/> 
<img src="images/messageBoard/12.gif" width="32" 
height="32"> </span> </td> 
</tr> 
</table></td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">指定何人回复:</td> 
<td colspan="2" align="left"> 
<select name="select5" jwcid="@PropertySelection" 
value="ognl:replyTypeOptionSelected" 
model="ognl:replyTypeSelectionModel"> 
<option value="1" selected>任何人可回复</option> 
<option value="2">指定管理员回复</option> 
</select> 
</td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td height="25" align="center">指定回复单位:</td> 
<td colspan="2" align="left"> 
<select name="select3" jwcid="@PropertySelection" 
value="ognl:unitIdOptionSelected" model="ognl:unitSelectionModel"> 
<option value="1">部门1</option> 
<option value="2">部门2</option> 
</select> 
</td> 
<td>&nbsp;</td> 
</tr> 
<tr> 
<td>&nbsp;</td> 
<td align="right" class="biao22">&nbsp;</td> 
<td width="111" align="center"><span 
jwcid="@ImageSubmit" image="ognl:assets.submitImage " 
listener="ognl:listeners.leaveWord"><img src="images/button/tj.gif" 
width="50" height="18" border="0"/></span></td> 
<td width="329" align="center"><a jwcid="@ExternalLink" 
page="MessageBoard" parameters='ognl:new java.lang.Integer("2")'><img 
src="images/button/cz.gif" width="50" height="18" border="0"></a></td> 
<td>&nbsp;</td> 
</tr> 
</table></td> 
<td background="images/bl_7.gif">&nbsp;</td> 
</tr> 
<tr> 
<td height="17" background="images/bl_10.gif">&nbsp;</td> 
<td background="images/bl_11.gif">&nbsp;</td> 
<td background="images/bl_12.gif">&nbsp;</td> 
</tr> 
</table> 
</form> 
这次,页面功能比较复杂了哦,我们会接触到许多新组件,甚至包括JavaScript 验证。

你可能感兴趣的:(入门,tapestry)