添加校验和菜单
本章将向你展示怎样为personForm添加校验逻辑,使得firstName和lastName在页面上变成必填field,并且添加一个屏幕列表来显示数据库中所有人的记录。
这部分内容依赖于Part III: 创建Action和JSP
关于本章
本章将向你展示怎样使用Struts的Validator 为你的PersonForm对象添加校验逻辑(客户端和服务器端)。我们也会使用Display Tag Library 创建一个屏幕列表来显示数据库中所有的people。
内容
· [1] 为Person.java 添加XDoclet Validator标签
· [2] 检查和测试添加了校验的JSP
· [3] 为 DAO和Manager Test添加testGetPeople方法
· [4] 为DAO和Manager添加getPeople()方法
· [5] 为Action Test 添加testSearch() 方法
· [6] 为Action添加search方法
· [7] 创建personList.jsp和Canoo test
· [8] 为菜单(Menu)添加链接(Link)
为Person.java 添加XDoclet Validator标签
正常情况下,如果你想使用Struts的Validator,你必须用手写validation.xml文件。如果你不使用AppFuse, 你也必须在ApplicationResources_en.properties文件中配置Validator Plugin和error key。如果你想获得更多的信息,请参见Validation Made Easy Tutorial 。
我们要感谢Xdoclet,它使得这一切都变得很简单 – 你只需要为Person类添加几个@struts.validator标签。打开它 (src/dao/**/model/Person.java),然后修改getFirstName()和getLastName() 方法的JavaDoc,使它们包含 @struts.validator type="required" 标签。
/**
* @struts.validator type="required"
* @hibernate.property column="first_name" length="50"
*/
public String getFirstName() {
return this.firstName;
}
/**
* @struts.validator type="required"
* @hibernate.property column="last_name" length="50"
*/
public String getLastName() {
return this.lastName;
}
你还可以在这个标签里添加一个msgkey属性( attribute),它用来覆盖错误的默认消息的key。
@struts.validator type="required" msgkey="errors.required"
type="required"的默认的Key已经是errors.required了,所以我经常保留它做为默认值。这个key被定义在web/WEB-INF/classes/ApplicationResources_*.properties中。 你也许注意到:虽然XDoclet的文档 说应该把这些标签放在Setter部分,但我们放到了getter部分,这是因为模板文件(metadata/template/struts_form.xdt) 会将这些标签放在产生的PersonForm.java的Setter部分。
现在如果你保存Person.java,然后运行ant clean webdoclet,将会在目录build/你的项目名称/WEB-INF/下产生一个validation.xml文件. 它应该有一个为"personForm"使用的入口:
<form name="personForm">
<field property="firstName"
depends="required">
<arg0 key="personForm.firstName"/>
</field>
<field property="lastName"
depends="required">
<arg0 key="personForm.lastName"/>
</field>
</form>
在我们的PersonForm.jsp中可以使用客户端校验,我们在PersonForm.jsp文件的底部添加一个<html:javascript>标签和一些脚本。下面的内容是已经存在的(感谢viewgen) – 你只需要将它们从注释中放开,它们被注释的原因是:如果指定了一个Form的名字并且校验规则不存在,Validator将会抛出一个异常。
我个人认为这是一个 a bug ,但Struts的提交者们并不这样认为.
<html:javascript formName="personForm" cdata="false"
dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript"
src="<html:rewrite page="/scripts/validator.jsp"/>"></script>
检查和测试添加了校验的JSP
现在你已经为你的Form配置了Validation,当这个Form在action-mapping中被配置后,并且设置了validate="true",这些校验规则将会生效。在上一章中, 我们为PersonAction添加"savePerson" action-mapping。这个action-mapping 的Xdoclet标签应该是这样的:
* @struts.action name="personForm" path="/savePerson" scope="request"
* validate="true" parameter="method" input="edit"
这样,只要web/pages
/personForm.jsp有<html:form action="savePerson">,当我们保存窗体时,校验将会生效。运行ant db-load deploy,启动Tomcat,然后浏览http://localhost:8080/appfuse/editPerson.html?id=1 。如果你删除了firstName和lastName field的值,然后单击save按钮,你应该会看到下面的JavaScript提示的警告:
如果你确定一切都真正的如同期望的那样工作后,你就可以关掉JavaScript以保证可以使用服务器端的校验可以正常使用。在 Mozilla Firebird (我喜欢的浏览器)中,这些很容易,只是到Tools → Options → Web Features,然后去掉"Enable JavaScript"。现在如果你清除field的值,然后保存,你会看到如下的界面:
如果你看不到上面的错误,那么可能你出现了下面的问题:
· 窗体显示保存成功,但firstName和lastName field是空的。
这是因为web/pages/personForm.jsp 中的<html:form>有 action="editPerson" – 你要改为action="savePerson"。
· 你单击了save,但出现了一个空白页面(blank page)。
你的"savePerson" forward的"input" attribute配置不正确,一定要保证它指向了一个local或者global action-forward。在这个例子中,它应该是 input="edit"(指向.personDetail tile的定义)。根据我的经验,input的值必须是一个forward,不能是一个指向action的path。
如果你只想使用服务器端校验(server-side validation(没有JavaScript)) ,你可以删除<html:form>的onsubmit属性(在 web/pages/personForm.jsp中) ,同时也要删除页面底部的Validator JavaScript标签:
<html:javascript formName="personForm" cdata="false"
dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript"
src="<html:rewrite page="/scripts/validator.jsp"/>"></script>
为 DAO和Manager Test添加testGetPeople方法
为了创建一个List screen (也叫做master screen), 我们需要创建一个方法,这个方法将返回person 表中的所有记录,我们先为类PersonDAOTest和PersonManagerTest添加测试方法。我通常将这个方法命名为getEntities (如: getUsers),但你也可以使用getAll或者search这样的名字 – 这只是个人喜好问题。
打开test/dao/**/dao/PersonDAOTest.java然后添加testGetPeople方法:
public void testGetPeople() {
person = new Person();
List results = dao.getPeople(person);
assertTrue(results.size() > 0);
}
我把一个person对象传入getPeople的原因是将来可以使用person的某个(些)属性进行过滤。 是否在getPeople()方法中添加这个参数是可选的,但本篇文章在剩余部分都假设你已经这样做了。
现在打开 test/service/**/service/PersonManagerTest.java,然后添加 testGetPeople 方法:
public void testGetPeople() {
List results = mgr.getPeople(new Person());
assertTrue(results.size() > 0);
}
为了使这些测试类能够被编译通过, 你需要为类PersonDAO和接口PersonManager添加getPeople()方法,然后实现它们。
为DAO和Manager添加getPeople()方法
打开src/dao/**/dao/PersonDAO.java,然后添加getPeople()方法声明(method signature):
public List getPeople(Person person);
现在为src/service/**/service/PersonManager.java添加同样的方法声明。保存你的文件,然后为你的测试文件添加必要的import。下一步我们需要在我们的实现类中实现getPeople() 方法。 打开 src/dao/**/dao/hibernate/PersonDAOHibernate.java,然后添加下面的方法:
public List getPeople(Person person) {
return getHibernateTemplate().find("from Person");
}
你可能注意到这里没有用person参数做任何事情. 现在它只是一个占位符 – 将来你可能需要使用Hibernate's Query Language (HQL) or using Criteria Queries 来过滤它的属性值。
下面是一个使用 Criteria Query的例子:
Example example = Example.create(person)
.excludeZeroes() // exclude zero valued properties
.ignoreCase(); // perform case insensitive string comparisons
try {
return getSession().createCriteria(Person.class)
.add(example)
.list();
} catch (Exception e) {
throw new DataAccessException(e.getMessage());
}
return new ArrayList();
现在我们在src/service/**/impl/PersonManagerImpl.java 中实现getPeople() 方法:
public List getPeople(Person person) {
return dao.getPeople(person);
}
保存你的修改,下面的命令应该都可以执行:
· ant test-dao -Dtestcase=PersonDAO
· ant test-service -Dtestcase=PersonManager
如果一切都工作正常 – 那真是漂亮的工作! 现在你需要为Web层添加这个retrieve all 功能。
为Action Test 添加testSearch() 方法
打开test/web/**/action/PersonActionTest.java,然后添加如下方法:
public void testSearch() {
setRequestPathInfo("/editPerson");
addRequestParameter("method", "Search");
actionPerform();
verifyForward("list");
assertNotNull(getRequest().getAttribute(Constants.PERSON_LIST));
verifyNoActionErrors();
}
只有你在src/dao/**/Constants.java 文件中添加了PERSON_LIST变量后,这个类才能编译通过。
我一般会复制一个已经存在的类似的变量 – 如:USER_LIST.
/**
* The request scope attribute that holds the person list
*/
public static final String PERSON_LIST = "personList";
保存你修改的文件,因为PersonAction.search() 方法不存在,
所以你还不能运行ant test-web -Dtestcase=PersonAction。
为Action添加search方法
打开src/web/**/action/PersonAction.java,然后在文件顶部添加Xdoclet标签- 为list screen添加forward。
* @struts.action-forward name="list" path="/WEB-INF/pages/PersonFormList.jsp"
现在在PersonAction类的主题部分添加search方法。
我使用UserAction.search()作为这个方法的模板。
public ActionForward search(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
if (log.isDebugEnabled()) {
log.debug("Entering 'search' method");
}
PersonManager mgr = (PersonManager) getBean("personManager");
List people = mgr.getPeople(null);
request.setAttribute(Constants.PERSON_LIST, people);
// return a forward to the person list definition
return mapping.findForward("list");
}
运行ant test-web -Dtestcase=PersonAction.
多漂亮啊!
BUILD SUCCESSFUL
Total time: 1 minute 26 seconds
创建personFormList.jsp(PersonList。jsp)和Canoo test
在目录web/pages 下应该已经有一个PersonFormList.jsp(PersonList.Jsp)文件了,如果没有你可以使用viewgen创建它。使用命令行,进入到目录extras/viewgen下,运行ant -Dform.name=PersonForm。这将会在目录extras/viewgen/build下产生一个PersonFormList.jsp(PersonList.Jsp)文件。
在目录web/pages下找到PersonFormList.jsp(PersonList.Jsp),打开。
这个文件的头部是一个 <bean:struts>标签,它将edit screen的 forward作为一个page范围的变量。这个"editPerson"应该已经有一个值了。(This should already have a value of "editPerson")
<%-- For linking to edit screen --%>
<bean:struts id="editURL" forward="editPerson"/>
将下面内容添加到metadata/web/global-forwards.xml,也是一个列表的视图。这样,它们就可以被包含到struts-config.xml文件中了。
(Add this to the metadata/web/global-forwards.xml, as well as one for viewing the list. This way, they will get included in our struts-config.xml file.)
<forward name="editPerson" path="/editPerson.html"/>
<forward name="viewPeople" path="/editPerson.html?method=Search"/>
你用来创建这个JSP的模板应该有一个对应硬编码的id属性的列。所以Xdoclet会把它添加2次。从PersonFormList.jsp(PersonList.Jsp)文件中删除下面的内容。
<display:column property="id" sort="true" headerClass="sortable"
titleKey="personForm.id"/>
如果你知道更改 extras/viewgen/src/List_jsp.xdt文件能够不包含这个列标签的办法,请告诉我。
你可能想去改变的另一件事情是:这个例子中产生的名字是"persons" ,它可能是people,在第31行附近,你应该可以看到下面的内容:
<display:setProperty name="paging.banner.items_name" value="persons"/>
更改为:
<display:setProperty name="paging.banner.items_name" value="people"/>
最后,在web/WEB-INF/classes/ApplicationResources_en.properties 文件中添加title 和heading的key (personList.title和personList.heading)。打开这个文件,然后添加下面的内容:
# -- person list page --
personList.title=Person List
personList.heading=All People
此时,你应该可以执行ant clean deploy,启动Tomcat,查看这个页面: http://localhost:8080/appfuse/editPerson.html?method=Search 。
现在我们已经有一个List Screen, 我们来修改这个添加或者删除了一个新Person后显示的页面。打开 src/web/**/action
/PersonAction.java,在save, delete和cancel方法中改变mapping.findForward("mainMenu")为下面这样:
return mapping.findForward("viewPeople");
你也需要改变test/web/**/action/PersonActionTest.java的testRemove方法的 verifyForward("mainMenu") 为verifyForward("viewPeople") 。最后,Canoo test的 "AddPerson"和"DeletePerson"需要被修改。打开test/web/web-tests.xml,然后在"AddPerson" target 部分更改如下内容:
<verifytitle stepid="Main Menu appears if save successful"
text="${webapp.prefix}${mainMenu.title}"/>
改为:
<verifytitle stepid="Person List appears if save successful"
text="${webapp.prefix}${personList.title}"/>
然后,在"DeletePerson" target部分,将下面的内容:
<verifytitle stepid="display Main Menu"
text="${webapp.prefix}$(mainMenu.title)"/>
更改为:
<verifytitle stepid="display Person List" text="${webapp.prefix}${personList.title}"/>
我们使用"viewPeople"代替"list",这样search方法将会被执行。这比使用指向PersonForm.jsp的简单的list (它是一个forward)要好。
我们要测试这个显示页是否工作,在test/web/web-tests.xml中创建一个新的JSP测试:
<!-- Verify the people list screen displays without errors -->
<target name="SearchPeople"
description="Tests search for and displaying all people">
<canoo name="searchPeople">
&config;
<steps>
&login;
<invoke stepid="click View People link" url="/editPerson.html?method=Search"/>
<verifytitle stepid="we should see the personList title"
text="${webapp.prefix}${personList.title}"/>
</steps>
</canoo>
</target>
我们在"PersonTests" target 中添加"SearchPeople" target,这样它将和其他与person相关的测试用例一起执行。
<!-- runs person-related tests -->
<target name="PersonTests"
depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
description="Call and executes all person test cases (targets)">
<echo>Successfully ran all Person JSP tests!</echo>
</target>
现在我们可以运行ant test-canoo -Dtestcase=SearchPeople (或者:Tomcat没有运行时,我们执行ant test-jsp) 。
为菜单(Menu)添加链接(Link)
对于这个list的最后一步: 使用户可以看到add, edit和delete功能。最简单的办法是在web/pages/mainMenu.jsp文件中添加一个链接的列表:
NOTE: 不要使用mainMenu.jsp 中的其他的链接,这样可以保证JSP能够被其他的Web框架使用(如:Spring MVC、WebWork)。
[The other links in mainMenu.jsp don't use so this JSP can be shared among the various web framework implementations in AppFuse (如:Spring MVC and WebWork).]
<li>
<html:link forward="viewPeople">
<fmt:message key="menu.viewPeople"/>
</html:link>
</li>
在web/WEB-INF/classes/ApplicationResources_en.properties 中有一个条目: menu.viewPeople
menu.viewPeople=View People
另一种办法是:添加下面的内容到web/WEB-INF/menu-config.xml中:
[The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:]
<Menu name="PeopleMenu" title="menu.viewPeople" forward="viewPeople"/>
要保证<Menus> tag中有上面的XML代码,而不是在另一个<Menu>中。 然后添加这个心菜单到web/pages/menu.jsp – 现在它看起来应该是这样的:
<%@ include file="/common/taglibs.jsp"%>
<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
<menu:displayMenu name="AdminMenu"/>
<menu:displayMenu name="UserMenu"/>
<menu:displayMenu name="PeopleMenu"/>
<menu:displayMenu name="FileUpload"/>
<menu:displayMenu name="FlushCache"/>
<menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>
现在你运行ant clean deploy,启动Tomcat,然后查看 http://localhost:8080/appfuse/mainMenu.html ,你应该可以看到下面的内容:
需要注意的是左边多了一个新的链接,它从mainMenu.jsp中获取,在右边的菜单中也多了一个新的链接,它从menu.jsp中获取。
这就是它!
恭喜你,至此,你已经完成了全部的使用AppFuse进行开发的流程!如果你成功的运行了上面的所有的测试用例,那我们现在开始真正的测试。停止Tomcat,运行ant clean test-all. 这将运行所有的工程中的单元测试. 提示:使用ant setup-db、 setup-tomcat、 test-all 可以很容易的对我们的系统雏形进行测试。另外,如果你正在寻找稳定性和健壮性的例子 - checkout Struts Resume.
多好的一天!
BUILD SUCCESSFUL
Total time: 2 minutes 31 seconds
如果你喜欢,你可以在这里下载我们这个指南中创建的文件。