Spring MVC form data binding and ajax form

Model和View绑定是虽然不是MVC模式的标配,但是Model和View的绑定,为开发者提供的非常方便的方式:视图的数据自动和模型同步并装配完成,避免了繁琐的手工装配过程。

SpringMVC提供了复杂的绑定机制和验证机制(前面的一个文章介绍了更复杂的动态列表的绑定)

我们先看看绑定机制:

我们以广告订单为例,在请求创建订单表单的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"/>&nbsp;&nbsp;
					<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:errorsclass="error"path="xxx"/>显示错误提示:

这样一个包含后端验证的Model和View绑定的全部逻辑已经完成。但是新的问题又来了,我们需要的是支持ajax的form,因为我们的系统基本上所有的页面都是ajax局部刷新的,

而一个普通的表单提交会直接转向到结果页面。而我们需要的是返回的页面直接嵌入到我们指定的标签下。

幸好jquery有一个plugin,叫做jqueryformplugin,提供了能够满足我们需求的ajaxform

写了一个简单的方法,可以直接将我们表单变成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的初始化的代码。

相关推荐