引子 我们在网上经常会碰到一些向导式的表单页面,比如 第一步 >> 第二步 >> 第三步…、基本信息 >> 联系方式 >> 兴趣爱好…。它们都是按类型分到多个有序的页面要你来完成填写的。当然,你也可以把它们全部罗列在一个页面里,那样用户就会感觉茫茫然,分不清主次,可能不会很好的予以配合。 然而像 Struts、WebWork 那样的 MVC 框架未提供类似的实现,都需自己采用某种方式来实现向导,可以用层的隐现方式,或逐步把填写的部分数据放 Session 中,最后汇总处理。幸运的是 Spring MVC 考虑到了这种应用需求,它提供了一个 org.springframework.web.servlet.mvc.AbstractWizardFormController 来满足你。 下面我们来参照 CSDN 的简历向导(界面如下) 来自己动手做个例子
详细的步骤如下(如何创建 Spring MVC 工程在此不细说,本例中用的是 Spring 2.0,其他版本的 Spring 原理无甚差异,只是 Spring 2.0 开始有很方便使用的 form 标签,而在 Spring 1.0 中要用 spring:bind 和 jstl 结合显示数据着实显得很寒碜,由此可见 Spring 1 的 MVC 还太不成熟)。本页中列出的代码有些非关键之处省去了,文后附上了本例完整的工程文件,可下载。一. 定义好需求 为了使我们尽快掌握 AbstractWizardFormController 的使用,而不至让具体业务所纠缠。故而,这里要对 CSDN 的简历向导大大的简化,简化后的向导页和每页的填写内容如下: 1. 求职意向(期望工作地点-必填,期望月薪) 2. 基本信息(姓名-必填、手机号码-必填) 3. 工作经历(单个文本框录入) 4. 项目经验(单个文本框录入) 5. 完成页,Congratulations 每页操作完之后,点击“下一步”按钮进到下一页面,同时要对标识为必填项进行非空验证。用户也可以点“上一步”按钮重填上一页面的信息。用户可以中途点击“取消”按钮取消向导。最后在项目经验向导页面,点击“完成”按钮处理表单数据,成功后显示 Congratulations 页。二. 定义接收表单数据的 Command 类(Resume) 这里我们定义为 com.unmi.bean.Resume,代码如下:
package com.unmi.bean;
public class Resume {
private String workPlace;
private int salary;
private String name;
private String mobile;
private String experience;
private String projects;
}
三. 创建向导控制器(ResumeWizardController)
package com.unmi.webapp.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractWizardFormController;
import com.unmi.bean.Resume;
public class ResumeWiardController extends AbstractWizardFormController {
public ResumeWiardController(){
setCommandClass(Resume. class );
}
protected ModelAndView processFinish(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
Resume resume = (Resume)command;
return new ModelAndView( "congratulations" );
}
protected void validatePage(Object command, Errors errors, int page) {
ResumeValidator validator = (ResumeValidator)getValidator();
if (page == 0 ){
validator.validateWorkplace( "workPlace" , errors);
}
else if (page == 1 ){
validator.validateName( "name" , errors);
validator.validateMobile( "mobile" , errors);
}
}
}
四. 还是提一下 web.xml 的配置 在 web.xml 增加了 Spring 的 DispatchServlet 来处理 /*.html 的 URL,Servlet Name 为 resume
< servlet >
< servlet-name > resume servlet-name >
< servlet-class > org.springframework.web.servlet.DispatcherServlet servlet-class >
servlet >
< servlet-mapping >
< servlet-name > resume servlet-name >
< url-pattern > *.html url-pattern >
servlet-mapping >
五. Spring 配置文件(resume-servlet.xml) 因为上面的 Servlet Name 为 resume,所以可以直接在 WEB-INF 目录中直接配置一个文件名为 resume-servlet.xml 的 Spring 配置文件。这是默认行为,当然,你也可以为 DispatcherServlet 用 contextConfigLocation 属性来指定配置文件。
xml version = "1.0" encoding = "UTF-8" ?>
< beans xmlns = ""
xmlns:xsi = ""
xsi:schemaLocation = " /spring-beans-2.0.xsd" >
< bean name = "/createResume.html" class = "com.unmi.webapp.controller.ResumeWiardController" >
< property name = "resumeService" >
< ref bean = "resumeService" />
property >
< property name = "pages" >
< list >
< value > intention value >
< value > baseinfo value >
< value > experience value >
< value > projects value >
< value > congratulations value >
list >
property >
< property name = "validator" >
< bean class = "com.unmi.webapp.validator.ResumeValidator" > bean >
property >
bean >
< bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver" >
< property name = "prefix" >
< value > /resume/ value >
property >
< property name = "suffix" >
< value > .jsp value >
property >
bean >
beans >
上面对 ResumeWizardController 用 pages 指定了一系列的逻辑视图名,他们依序有一个索引号(以 0 为基数),后面会用到。这些逻辑面在运行时会依据 InternalResourceViewResolver 解析成对应的 jsp 文件名,例如,intention -> /resume/intention.jsp,其余类同。
六. 分步显示向导中的表单页面 任何向导控制器显示的第一个页面都是 pages 属性中列表的第一个页面。其后为了判断接下来的是哪个页面, AbstractWizardFormController 询问它的 getTargetPage() 方法。这个方法返回一个整数,它就是 pages 和中设置页面列表的索引值。
getTargetPage() 方法的缺省实现是根据请求中的一个参数来决定下一步是哪个页面的,这个参数以 "_target" 开头,以数结尾。getTargetPage() 只取这个数字作为页面列表的索引。一般我们会把该参数命名给相应的提交按钮。例如我们在第二个页面 /resume/baseinfo.jsp 中的“上一步”,“下一步” 按钮的 html 代码这样写:
< form action = "/createResume.html" method = "post" >
......
< input type = "submit" value = "上一步" name = "_target0" >
< input type = "submit" value = "下一步" name = "_target2" >
form >
七. 完成或取消向导 那么 AbstractWizardFormController 又是如何知道你点击的是完成按钮,要调用 processFinish() 方法处理表单数据,或是点了取消按钮,要调用 processCancel() 来作些清理工作或作部分数据处理呢?它也是依据于特殊的请求参数,它们分别是 “_finish” 和 “_cancel”。因此,相应的 html 代码就要写成:
< form action = "/createResume.html" method = "post" >
......
< input type = "submit" value = "完成" name = "_finish" >
< input type = "submit" value = "取消" name = "_cancel" >
form >
......
八. 每次验证一个向导表单 对于这种向导式页面,如果你仍是在最后点完成按钮来验证所有表单数据的话,一旦某个数据有问题,你将很难定位是在哪个向导页输入的,并且转向到哪个出错页面也麻烦。所以我们需要在每填完一个表单,点击 “下一步” 或“完成” 按钮时立即就对当前表单数据进行验证。 AbstractWizardFormController 在每次页面跳转时会调用它的 validatePage() 方法。而 validatePage() 方法缺省实现是空的,这要留给你来实现。AbstractWizardFormController 中有两个重载的 validatePage() 方法,代码分别如下:
protected void validatePage(Object command, Errors errors, int page) {
}
protected void validatePage(Object command, Errors errors, int page, boolean finish) {
validatePage(command, errors, page);
}
protected void validatePage(Object command, Errors errors, int page) {
}
protected void validatePage(Object command, Errors errors, int page, boolean finish) {
validatePage(command, errors, page);
} 你可以选择实现其中一个方法来对表单进行验证。如果你希望在点击“完成” 按钮时,能作一些特别的验证,例如,如多个表单的相关联数据进行一致性检查,那你就应该实现带有 boolean finish 参数的 validatePage() 方法。 最好是把验证逻辑单独写在一个实现了 Validator 接口的验证类中,通过 validator 属性配置给你的 ResumeWizardFormController,然后在 validatePage() 方法中取到这个 validator,再调用其中的验证方法 validateXXX()。例如我们前面配置在 resume-servlet.xml 中的 com.unmi.webapp.validator.ResumeValidator。 注意,对于 AbstractWizardFormController,它不会调用配置给它的 validator 的标准的 validate() 方法。在点击“下一步”按钮,即向导正向走时,若验证不通过,则停留在当前页,等待重新输入。然而,当点击“上一步”按钮时,也就是页面索引号递减,向导逆向走时,同样会要求对当前页面输入进行验证,只不过这种情况下验证即使不能通过也不会停留在当前页,仍会转向到上一页面。 这时候,我们在 ResumeWizardController 的 validatePage(Object command, Errors errors, int page) 就可以参照这么写了:
protected void validatePage(Object command, Errors errors, int page) {
Resume resume = (Resume) command;
ResumeValidator validator = (ResumeValidator)getValidator();
if (page == 1 ){
validator.validateName( "name" , errors);
}
}
protected void validatePage(Object command, Errors errors, int page) {
Resume resume = (Resume) command;
ResumeValidator validator = (ResumeValidator)getValidator();
if(page == 1){//判断所在的页面来调用相应的验证方法
validator.validateName("name", errors);
}
} 对于另一个版本的 validatePage(Object command, Errors errors, int page, boolean finish) 方法,那就是:
protected void validatePage(Object command, Errors errors, int page,
boolean finish) {
Resume resume = (Resume) command;
ResumeValidator validator = (ResumeValidator)getValidator();
if (page == 1 ){
validator.validateName( "name" , errors);
}
if (finish){
validator.validate(command,errors);
}
}
protected void validatePage(Object command, Errors errors, int page,
boolean finish) {
Resume resume = (Resume) command;
ResumeValidator validator = (ResumeValidator)getValidator();
if(page == 1){//判断所在的页面来调用相应的验证方法
validator.validateName("name", errors);
}
if(finish){//如果是完成向导时,进行自己的验证,这里直接调用了标准的 validate() 方法
validator.validate(command,errors);
}
} 九. 完整的工程代码 下载地址:
http://www.blogjava.net/Files/Unmi/SpringWizardForm.rar 本工程未去处理有请求参数 _cancel 时,执行 ResumeWizardController.processCancel() 方法的情形,如果你有这样的需求的话可自己去完善。对 ResumeWizardController,也是只实现了 validatePage(Object command, Errors errors, int page) 这个版本的方法。
解压到 Tomcat 下即能运行,浏览 点链接进到向导。包含源代码和所需的 jar 文件。使用的是 Spring 2.0,jsp 页面中用 spring-form 标签。支持国际化,从资历源文件中获取验证错误信息在输入框下方显示。
十. 运行效果 把整个操作过程做成了一个 Gif 动画来展示,能使你一目了然。只恐怕这精彩的部分放在后头,可能鲜有人有此等耐心把滚动条拉至此处。
说明: 1. 浏览 ,点链接进到向导页 2. 操作中测试到了每一个验证的效果,期望工作地点、姓名和手机号码不能为空 3. 验证不通过时,从资源文件中取出错误信息,显示在相应输入框之下。并且页面仍留在当前页 4. 在向导的进行中,点“上一步”,“下一步”按钮时,只要填写提交过的数据一直保留 5. 所有数据填完后,点击“完成”按钮,业务类处理整个表单数据(后台会打印出 resume 信息),页面显示恭喜.
参考资料:1. 《Spring in Action》 第一版,第八章
2.
spring标签介绍 -- 说的是 Spring 中的 spring.tld 中的标签
3. -- 说的是 Spring 2.0 开始的 spring-form.tld 中的标签
4.
阅读(822) | 评论(0) | 转发(0) |