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"/> <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的初始化的代码。