Model和View绑定是虽然不是MVC模式的标配,但是Model和View的绑定,为开发者提供的非常方便的方式:视图的数据自动和模型同步并装配完成,避免了繁琐的手工装配过程。
Spring MVC提供了复杂的绑定机制和验证机制(前面的一个文章介绍了更复杂的动态列表的绑定)
我们先看看绑定机制:
我们以广告订单为例,在请求创建订单表单的action中,我们添加一个新创建的模型advertiseOrder:
@RequestMapping("/getCreateForm")
public ModelAndView getCreateForm(){
ModelAndView mav = new ModelAndView();
//add the model which the
mav.addObject("advertiseOrder", new AdvertiseOrder());
//other data the form need
List<User> sales = userManager.getUsersByType(User.SALE_TYPE);
mav.addObject("saleList",sales);
List<User> customerAids = userManager.getUsersByType(User.CUSTOMER_AID);
mav.addObject("customerAidList",customerAids);
mav.setViewName("advertiseOrders/create_order");
return mav;
}
在表单的页面_order_form.jsp,我们使用spring的form标签,指定commandName为我们在控制器中的名字advertiseOrder,
然后表单的每个数据域的path和模型的字段对应。另外使用form:select的option自动会被选中为value和模型对应字段的值相同的。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:form commandName="advertiseOrder" id="orderForm" action="${param.actionURL}" method="post">
<div class="form">
<table cellpadding="0" cellspacing="0" >
<tr>
<th>广告主</th>
<td>
<form:hidden class="textbox" path="customerId"/>
<input class="textbox" id="customerName" type="text" value="${advertiserName}"/>
<span class="po">广告主名称</span>
<form:errors class="error" path="customerId"/>
</td>
</tr>
<tr>
<th><form:label for="name" path="name">订单名称</form:label></th>
<td>
<form:hidden path="id"/>
<form:input class="textbox" path="name"/>
<span class="po">包含广告主名称,计费类型,业务类型等</span>
<form:errors class="error" path="name"/>
</td>
</tr>
<tr>
<th><form:label for="startTime" path="startTime">订单时间</form:label></th>
<td>
<div class="qcbox">
<div class="labelContainer"></div>
<div class="boxWrapper">
<div class="boxContainer">
<div class="sinfo" title=""></div>
<div class="sicon" id="fromTimeIcon"></div>
<div style="clear: both;"></div>
</div>
</div>
<form:input path="startTime" class="textbox0"/>
</div>
<div class="ln">-</div>
<div class="qcbox">
<div class="labelContainer"></div>
<div class="boxWrapper">
<div class="boxContainer">
<div class="sinfo" title=""></div>
<div class="sicon" id="toTimeIcon"></div>
<div style="clear: both;"></div>
</div>
</div>
<form:input path="endTime" class="textbox0"/>
</div>
<form:errors class="error" path="startTime"/>
<form:errors class="error" path="endTime"/>
</td>
</tr>
<tr>
<th><form:label path="description">订单说明</form:label>
<td>
<form:textarea class="textbox" path="description"></form:textarea>
</td>
</tr>
<tr>
<th><form:label path="saleId">销售人员</form:label></th>
<td><div class="fg">
<div class="list">
<form:select class="name" path="saleId" onchange="onSelectOrderSale()">
<option value="">选择销售人员</option>
<form:options items="${saleList}" itemValue="id" itemLabel="usrName"></form:options>
</form:select>
<form:hidden path="sale"/>
</div>
<form:errors class="error" path="saleId"/>
</div>
</td>
</tr>
<tr>
<th><form:label path="customerAidId">客服人员</form:label></th>
<td><div class="list">
<form:select class="name" path="customerAidId" onchange="onSelectOrderCustomerAid()">
<option value="">选择客服人员</option>
<form:options items="${customerAidList}" itemValue="id" itemLabel="usrName"/>
</form:select>
<form:hidden path="customerAid"/>
</div>
<form:errors class="error" path="customerAidId"/>
</td>
</tr>
<tr>
<th></th>
<td><input class="button" type="submit" value="保 存" /></td>
</tr>
</table>
</div>
</form:form>
表单提交到创建订单的action:我们使用ModelAttribute的注解绑定表单的对象和@Valid注解需要验证的模型(注:BindingResult参数必须在@ModelAttribute注解参数的下一个):
@RequestMapping("/create")
public ModelAndView create(@ModelAttribute("advertiseOrder") @Valid AdvertiseOrder order,
BindingResult result,HttpSession session){
ModelAndView mav = new ModelAndView();
if(result.hasErrors()){
mav.setViewName("advertiseOrders/create_order");
List<User> sales = userManager.getUsersByType(User.SALE_TYPE);
mav.addObject("saleList",sales);
List<User> customerAids = userManager.getUsersByType(User.CUSTOMER_AID);
mav.addObject("customerAidList",customerAids);
mav.addObject("errors", result);
mav.addObject("advertiseOrder", order);
}else{
User user = (User) session.getAttribute("user");
order.setOperator(user.getUsrName());
advertiseOrderManager.create(order);
mav.setViewName("redirect:list");
}
return mav;
}
如果字段的类型需要转换(spring自定默认提供多了很多类型转换,比如String和Integer,Float,Date之间的转换),我们也可以自定义类型转换,比如Timestamp类型,我们需要指定的格式字符串和Timestamp类型的转化的Editor。在执行业务逻辑之前,我们还需要对模型的数据进行验证,也就是表单后台验证,我们只需要在控制器中的使用@InitBinder中注册自定义的类型转换器和添加指定模型验证即可:
@InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
if(binder.getTarget() instanceof AdvertiseOrder){
binder.setValidator(new AdvertiseOrderValidator());
//自定义字段类型转换的customerEditor
binder.registerCustomEditor(java.sql.Timestamp.class, new TimestampEditor("yyyy-MM-dd", true));
}
}
然后写我们验证器代码:
package com.qunar.advertisement.advertiser.vo;
import java.sql.Timestamp;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.qunar.advertisement.advertiser.model.AdvertiseOrder;
public class AdvertiseOrderValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return clazz == AdvertiseOrder.class;
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "customerId", "field.required",new Object[]{"广告主"});
ValidationUtils.rejectIfEmpty(errors, "name", "field.required",new Object[]{"订单名称"});
ValidationUtils.rejectIfEmpty(errors, "startTime", "field.required",new Object[]{"订单开始时间"});
ValidationUtils.rejectIfEmpty(errors, "endTime", "field.required",new Object[]{"订单结束时间"});
ValidationUtils.rejectIfEmpty(errors, "saleId", "field.required",new Object[]{"销售人员"});
ValidationUtils.rejectIfEmpty(errors, "customerAidId", "field.required",new Object[]{"客服人员"});
if(errors.hasErrors())
return;
AdvertiseOrder order = (AdvertiseOrder)target;
Timestamp startTime = order.getStartTime();
Timestamp endTime = order.getEndTime();
if(endTime.before(startTime)){
errors.rejectValue("endTime", "endTime_must_after_startTime");
}
}
}
我们在messages_zh.properties配置要显示的错误信息:
引用
field.required=({0})不能为空
endTime_must_after_startTime=结束时间必须晚于开始时间
还需要在配置messageSource指定错误信息配置文件路径、cache时间和编码:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/messages/messages" />
<property name="cacheSeconds" value="0" />
<property name="defaultEncoding" value="UTF-8"/>
</bean>
在页面中使用<form:errors class="error" path="xxx"/>显示错误提示:
这样一个包含后端验证的Model和View绑定的全部逻辑已经完成。但是新的问题又来了,我们需要的是支持ajax的form,因为我们的系统基本上所有的页面都是ajax局部刷新的,
而一个普通的表单提交会直接转向到结果页面。而我们需要的是返回的页面直接嵌入到我们指定的标签下。
幸好jquery有一个plugin,叫做jquery form plugin,提供了能够满足我们需求的ajax form
写了一个简单的方法,可以直接将我们表单变成ajax的方式提交:
function ajax_form(formId){
$('#' + formId).ajaxForm({
target: '#main'//结果目标页面插入的id为#main的element下
});
}
我们在下面的代码放在_order_form.jsp的最后:
<script language="javascript">
ajax_form('orderForm');
</script>
这样当_order_form.jsp片段被加载时,会执行ajax_form的初始化的代码。