第三部分: 这个指南将会告诉你如何创建 Tapestry 页面和 HTML 模版。同时也将说明如何编写 JUnit 测试以测试PersonForm页面。我们创建的这个JSP页面将会使用我们在”创建Managers类“ 指南创建的PersonManager类。在大多数的web框架(web frameworks)中,控制逻辑都写在一个类似"Action" 的类中。但是在Tapestry中,这些控制逻辑通常可以以类似"Page"的方式被引用。使用这些pages的方法被称为listeners。这份指南不会讲述关于Tapestry工作机制的问题,但是会知道你快速上手使用Taperstry框架。如果你希望跟深入的学习Taperstry的有关知识,我建议你阅读Howard Lewis Ship'的 Tapestry in Action一书。当我在把Tapersty集成到Appfuse的过程中就把这部书放在身边以便随时查阅。感谢Howard的帮助! 现在让我们开始在Appfuse's的整体架构下创建新的页面和HTML模版。如果你此时还没有安装Tapestry模块,请马上运行ant install-tapestry。 在这一步,你将自动生成一个HTML模版以显示Person对象的信息。这个模版将包含符合Taperstry语法规则的待填充表单元素 - 就是那些HTML文件中带有"jwcid" 的属性。用来自动生成HTML模版的AppGen工具是基于 StrutsGen工具实现的 - 这个工具最初是由Erik Hatcher开发的。基本上是由一对Java类和一组XDoclet模版组成。这些文件都可以在extras/appgen目录下找到。 下面给出产生这个HTML模版文件和一个包含了form的标签元素的properties文件的具体步骤: person.added=Person has been added successfully. # -- person list page -- # -- person detail page -- Appfsue的web应用程序安全性保证所有 *.html url-patterns 都是有保护的 (除了 /signup.html 和 /passwordHint.html), 这将保证客户端必须通过Page(Tperstry 框架页面条专逻辑控制器对应的 Page )来访问 template 文件。 要为PersonForm创建一个 Junit 测试,首先在 test/web/**/action 目录下创建一个 PersonFormTest.java 文件。 此时将不能通过编译因为你还没有创建被测试的 PersonForm 。 在 src/web/**/action 目录下创建 PersonForm.java 文件,输入下面的内容: 你可能注意到在文件中使用了一组键(keys) - "person.deleted","person.added" 和 "person.updated"。所有的这些键值定义在你的 i18n 绑定文件(ApplicationResources_en.properties)中。你在这篇指南的开头应该已经添加了这部分内容。如果你希望在程序中改变这些基本信息,加入 person 的 name 或者其他内容,只需要在对应的信息内容中简单得添加一个 "{0}" 然后再程序中使用 setMessage(format(key, stringtoreplace)) 方法填充具体的内容。 你现在可能注意到了我们在这里调用 PersonManager 的代码和我们 PersonManagerTest 的相应代码是一样的。因为 PersonForm 和 PersonManagerTest 都是PersonManagerImpl 的客户 , 所以这是个优雅的结构。 现在你要告诉 Tapestry 这个 page 的存在了。你要做的是在 web/WEB-INF/tapestry.application 文件中加入 page 入口。 如果你把 HTML 模板文件保存在 WEB-INF 目录下,上面的步骤是不需要的。希望Taperstry未来的版本允许你设置全局路径。 你看 PersonFormTest 可以发现所有的测试依赖于数据库 person 表中一条 id=1 的纪录( testRemove 方法依赖于 id=2 的纪录 ),所以要在示例数据文件( metadata/sql/sample-data.xml )中加入这些纪录。我通常在文件的底部加入这些内容 - 这个顺序并不重要因为它和其他数据表没有任何关系。 在运行所有的测试以前 DBUnit 会加载这些数据到数据库中,所以这些纪录对你的 Form 测试是可靠的。 保证你的项目中的文件都正确保存。那样你运行ant test-web -Dtestcase=PersonForm - 所有的事情就像你最初期望的那样。 现在执行 ant db-load deploy,启动 Tomcat 在浏览中输入 http://localhost:8080/appfuse/personForm.html ,你将看到如下的界面(略): 在 Tapestry 中,URLs 显得有点丑陋,不过他们包含了大量的信息。与其他的框架只需要你简单调用Action中的方法不一样, - 你需要调用 Page 类中的 listeners 。为了调用PrsonForm 对象中的 "edit" listener ,需要在 web/pages/mainMenu.html 文件中加入下面的代码。 最后为了提高界面的用户友好性,你也许希望在表单的上方加入信息,这可以在 personForm.html 中前面使用 <span key="..."/> 加入所需要显示的信息。 最后一步(可选步骤)是创建一个 Canoo WebTest 测试这个 HTML 模板。 你可以使用下面的步骤测试adding、editing 和 saving操作。 Canoo 测试相当灵活,只需要通过在一个XML文件中配置实现。为了增加 add, edit, save 和 delete 操作的测试,打开 test/web/web-tests.xml 文件并且加入下面的XML。你可以看到一个命名为 PersonTests 目标的片断可以运行所有相关的测试。 完成了前面的操作后,你可以在Tomcat运行的状态下运行 ant test-canoo -Dtestcase=PersonTests; 也可以在没有 Tomcat 运行的情况下运行 ant test-html -Dtestcase=PersonTests , Ant 会启动启动/停止 Tomcat。为了在运行所有 Canoo 测试的时候能够包括 PersonTests, 在"run-all-tests" target. 中加入对应的dependency。 你可能注意到Canoo测试没有客户端的日志记录。如果你想看看它到底做了什么,你可以在 web/WEB-INF/classes/log4j.properties 中加入 tweak the log4j settings 。 下面的内容: 第四部分: 加入校验和列表页面 - 说明如何增加校验逻辑来使得 firstName 和 lastName 是必填字段。也将展示如何增加一个列表页面显示数据库中所有的person纪录。
创建Tapestry框架页面
说明
内容提要
使用XDoclet创建 pageForm.html 模版[#1]
# -- person form --
personForm.id=Id
personForm.firstName=First Name
personForm.lastName=Last Name
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.
personList.title=Person List
personList.heading=Persons
personDetail.title=Person Detail
personDetail.heading=Person Information
body#pageName element.class { background-color: blue }
创建PersonFormTest以测试[#2]
package org.appfuse.webapp.action;
import java.util.ResourceBundle;
import org.appfuse.model.Person;
import org.appfuse.service.Manager;
public class PersonFormTest extends BasePageTestCase {
private PersonForm page;
private Manager manager;
protected void setUp() throws Exception {
super.setUp();
page = (PersonForm) getPage(PersonForm.class);
// unfortunately this is a required step if you're calling
// getMessage in the page class
page.setBundle(ResourceBundle.getBundle(MESSAGES));
page.setValidationDelegate(new Validator());
// this manager can be mocked if you want a more "pure" unit test
manager = (Manager) ctx.getBean("manager");
page.setManager(manager);
// default request cycle
page.setRequestCycle(getCycle(request, response));
}
protected void tearDown() throws Exception {
super.tearDown();
page = null;
}
public void testAdd() throws Exception {
Person person = new Person();
// set required fields
person.setFirstName("firstName");
person.setLastName("lastName");
page.setPerson(person);
page.save(page.getRequestCycle());
assertFalse(page.hasErrors());
}
public void testEdit() throws Exception {
MockRequestCycle cycle = (MockRequestCycle) page.getRequestCycle();
cycle.addServiceParameter(new Long(1));
page.edit(cycle);
assertNotNull(page.getPerson());
assertFalse(page.hasErrors());
}
public void testSave() {
assertNotNull(manager);
Person person = (Person) manager.getObject(Person.class, new Long(1));
// update fields
person.setFirstName("firstName");
person.setLastName("lastName");
page.setPerson(person);
page.save(page.getRequestCycle());
assertFalse(page.hasErrors());
}
public void testRemove() throws Exception {
Person person = new Person();
person.setId(new Long(2));
page.setPerson(person);
page.delete(page.getRequestCycle());
assertFalse(page.hasErrors());
}
}
创建 PersonForm [#3]
package org.appfuse.webapp.action;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageRenderListener;
import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;
public abstract class PersonForm extends BasePage implements PageRenderListener {
public abstract PersonManager getPersonManager();
public abstract void setPersonManager(PersonManager mgr);
public abstract void setPerson(Person person);
public abstract Person getPerson();
public void pageBeginRender(PageEvent event) {
if ((getPerson() == null) && !event.getRequestCycle().isRewinding()) {
setPerson(new Person());
} else if (event.getRequestCycle().isRewinding()) { // add
setPerson(new Person());
}
}
public void cancel(IRequestCycle cycle) {
if (log.isDebugEnabled()) {
log.debug("Entering 'cancel' method");
}
cycle.activate("mainMenu");
}
public void delete(IRequestCycle cycle) {
if (log.isDebugEnabled()) {
log.debug("entered 'delete' method");
}
getPersonManager().removePerson(getPerson().getId().toString());
MainMenu nextPage = (MainMenu) cycle.getPage("mainMenu");
nextPage.setMessage(getMessage("person.deleted"));
cycle.activate(nextPage);
}
public void edit(IRequestCycle cycle) {
Object[] parameters = cycle.getServiceParameters();
Long id = (Long) parameters[0];
if (log.isDebugEnabled()) {
log.debug("getting person with id: " + id);
}
setPerson(getPersonManager().getPerson(id.toString()));
cycle.activate(this);
}
public void save(IRequestCycle cycle) {
if (getValidationDelegate().getHasErrors()) {
return;
}
boolean isNew = (getPerson().getId() == null);
getPersonManager().savePerson(getPerson());
String key = (isNew) ? "person.added" : "person.updated";
if (isNew) {
MainMenu nextPage = (MainMenu) cycle.getPage("mainMenu");
nextPage.setMessage(getMessage(key));
cycle.activate(nextPage);
} else {
PersonForm nextPage = (PersonForm) cycle.getPage("personForm");
nextPage.setMessage(getMessage(key));
cycle.activate("personForm"); // return to current page
}
}
}
<page name="personForm" specification-path="pages/personForm.page"/>
运行 PersonFormTest [#4]
<table name='person'>
<column>id</column>
<column>first_name</column>
<column>last_name</column>
<row>
<value>1</value>
<value>Matt</value>
<value>Raible</value>
</row>
<row>
<value>2</value>
<value>James</value>
<value>Davidson</value>
</row>
</table>
Total time: 12 seconds
在浏览器中查看这个表单[#5]
<a jwcid="@DirectLink" listener="ognl:requestCycle.getPage('personForm').listeners.edit"
parameters="ognl:new java.lang.Long(1)">Edit Person</a>
[Optional] 创建一个Canoo WebTest 以模拟测试 PesonForm 对浏览器中操作的响应[#6]
<!-- runs person-related tests -->
<target name="PersonTests"
depends="EditPerson,SavePerson,AddPerson,DeletePerson"
description="Call and executes all person test cases (targets)">
<echo>Successfully ran all Person HTML Template tests!</echo>
</target>
<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson"
description="Tests editing an existing Person's information">
<canoo name="editPerson">
&config;
<steps>
&login;
<clicklink label="Edit Person"/>
<verifytitle stepid="we should see the personDetail title"
text="${webapp.prefix}${personDetail.title}"/>
</steps>
</canoo>
</target>
<!-- Edit a person and then save -->
<target name="SavePerson"
description="Tests editing and saving a user">
<canoo name="savePerson">
&config;
<steps>
&login;
<clicklink label="Edit Person"/>
<verifytitle stepid="we should see the personDetail title"
text="${webapp.prefix}${personDetail.title}"/>
<!-- update some of the required fields -->
<setinputfield stepid="set firstName" name="firstNameField" value="Canoo"/>
<setinputfield stepid="set lastName" name="lastNameField" value="WebTest"/>
<clickbutton label="${button.save}" stepid="Click Save"/>
<verifytitle stepid="Page re-appears if save successful"
text="${webapp.prefix}${personDetail.title}"/>
</steps>
</canoo>
</target>
<!-- Add a new Person -->
<target name="AddPerson"
description="Adds a new Person">
<canoo name="addPerson">
&config;
<steps>
&login;
<invoke stepid="View Person Form" url="/personForm.html"/>
<verifytitle stepid="we should see the personDetail title"
text="${webapp.prefix}${personDetail.title}"/>
<!-- enter required fields -->
<setinputfield stepid="set firstName" name="firstNameField" value="Jack"/>
<setinputfield stepid="set lastName" name="lastNameField" value="Raible"/>
<clickbutton label="${button.save}" stepid="Click button 'Save'"/>
<verifytitle stepid="Main Menu appears if save successful"
text="${webapp.prefix}${mainMenu.title}"/>
<verifytext stepid="verify success message" text="${person.added}"/>
</steps>
</canoo>
</target>
<!-- Delete existing person -->
<target name="DeletePerson"
description="Deletes existing Person">
<canoo name="deletePerson">
&config;
<steps>
&login;
<clicklink label="Edit Person"/>
<clickbutton label="${button.delete}" stepid="Click button 'Delete'"/>
<verifytitle stepid="display Main Menu" text="${webapp.prefix}${mainMenu.title}"/>
<verifytext stepid="verify success message" text="${person.deleted}"/>
</steps>
</canoo>
</target>
Total time: 27 seconds
创建 Tapestry page 和 HTML 模版 - 介绍如何在AppFuse 项目中创建 Tapestry页面和模版。
appfuse使用Taperstry框架(一)——创建Tapestry框架页面
我将以斜体字说明在 实际过程 使用的经验。
注意: 如果你希望为特别的页面自定义一个CSS,你可以在这个文件的最上方加入 <body id="pageName"/> 标签 (紧跟在</content> 标签后面)。 SiteMesh会特别处理并把它放在最终的页面中。你也可用使用如下的代码一个页面一个页面的自定义CSS:
BUILD SUCCESSFUL
注意: Tapestry 会自动把焦点设置在表单中第一个必须输入的字段中。如果希望改变焦点所在位置,可以查看 mailing list archives。
BUILD SUCCESSFUL
No TrackBacks
TrackBack URL: http://www.wujianrong.com/mt-tb.cgi/855

Leave a comment