表单批量验证,是一个常见应用场景,刚开始写代码那会儿,就做过这个验证,抠了很长时间,才把验证做完。这种验证,从做开发开始,到今天,怎么样也做过数次了,确切地说,每家公司都做过。
可是,每隔一段时间,就会忘个差不多,不记得当初是怎么做的了。最近,又遇到了批量验证。前台验证插件还是那几款插件,然而很久没写了,也想不起当初是怎么做的了,又得从来再来一遍。
重新搜索资料也是需要花费时间的,想避免这个浪费,就得整理总结,便于日后翻看。下面,进入正题。
环境说明:
springboot2.1.4 搭建的简易后台
thymeleaf模板
bootstrap前端框架
jquery、jquery.validate前端验证插件
首先,使用springboot搭建一个简易后台,pom中引入文件如下:
<!-- web开发架包引用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- thymeleaf架包引用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
第二步,构建一个测试的Controller后台,来接收批量验证的数据。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.example.entity.Pager;
import com.example.entity.Test;
/**
* 表单批量验证测试demo
* @author 程就人生
* @date 2019年9月11日
*/
@RestController
@RequestMapping("/index")
public class TestController {
/**
* 页面初始化
* @return
*
*/
@GetMapping
public ModelAndView index(){
return new ModelAndView("/index");
}
/**
* 提交操作
* @param test 接收提交的数据
* @return
*
*/
@PostMapping
public Object save(@RequestBody Test... tests){
Map<String,String> returnMap = new HashMap<String,String>();
for(Test test : tests){
System.out.println(test.getUserName() + ":" + test.getUserMobile() + ":" + test.getEmail() + ":" + test.getStatus());
}
returnMap.put("message", "共提交数据:" +tests.length + "条");
return returnMap;
}
/**
* 列表查询,异步获取数据
* @param pager
* @return
*
*/
@GetMapping("/searchByCriteria")
public Object searchByCriteria(Pager pager){
List<Test> list = new ArrayList<Test>();
//模拟一条已经存在的数据,也可以模拟多条
Test test = new Test();
test.setUserUid("123456");
test.setUserName("aa");
test.setUserMobile("15994587889");
//0,未删除;1:已删除;
test.setIsDelete((byte)0);
list.add(test);
Map<String,Object> map = new HashMap<String,Object>();
map.put("total", 0);
map.put("code", 200);
map.put("rows", list);
return map;
}
}
第三步,构建前端页面来进行js验证。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
<meta http-equiv="Cache-Control" content="no-siteapp" />
<title>表单批量验证demo</title>
<meta name="keywords" content="">
<meta name="description" content="">
<!--[if lt IE 9]>
<meta http-equiv="refresh" content="0;ie.html" />
<![endif]-->
<link rel="stylesheet" th:href="@{/css/bootstrap.min14ed.css?v=3.3.6}">
<link rel="stylesheet" th:href="@{/css/bootstrap-table.min.css}">
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeIn">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-content">
<form id="testForm" >
<button id="addButton" class="btn btn-info" type="button"><i class="fa fa-plus"></i> 新增</button>
<button id="saveButton" class="btn btn-info" type="submit"><i class="fa fa-check"></i> 提交保存</button>
<div class="table-responsive">
<!-- bootstrap Table 使用 -->
<table id="table"></table>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/js/jquery.min.js?v=2.1.4}"></script>
<script th:src="@{/js/bootstrap.min.js?v=3.3.6}"></script>
<!-- jquery验证start -->
<script th:src="@{/js/validate/jquery.validate.min.js}"></script>
<script th:src="@{/js/validate/jquery.validate-extend.js}"></script>
<script th:src="@{/js/validate/messages_zh.min.js}"></script>
<!-- jquery验证end -->
<!-- bootstrap-table插件start -->
<script th:src="@{/js/bootstrap-table.min.js}"></script>
<script th:src="@{/js/bootstrap-table-zh-CN.min.js}"></script>
<!-- bootstrap-table插件end -->
<script th:inline="javascript">
$('#table').bootstrapTable({
url: "/index/searchByCriteria", //请求后台的URL(*)
method: 'GET', //请求方式(*)
toolbar: '.searchArea',
clickEdit: true,
striped: true, //是否显示行间隔色
cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
pagination: false, //是否显示分页(*)
sortable: false, //是否启用排序
sortOrder: "asc", //排序方式
sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*)
queryParamsType:"limit",
contentType:"application/x-www-form-urlencoded; charset=UTF-8",
pageList: [10, 25, 50, 100], //可供选择的每页的行数(*)
queryParams:queryParams,
search: false, //是否显示表格搜索
strictSearch: false,
showColumns: false, //是否显示所有的列(选择显示的列)
showRefresh: false, //是否显示刷新按钮
minimumCountColumns: 2, //最少允许的列数
clickToSelect: true, //是否启用点击选中行
//height: 500, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度
uniqueId: "ID", //每一行的唯一标识,一般为主键列
showToggle: false, //是否显示详细视图和列表视图的切换按钮
cardView: false, //是否显示详细视图
detailView: false, //是否显示父子表
columns: [
{
field: 'userName',
title: '姓名',
halign: 'center',//表头居中
formatter: function (value, row, index) {
return '<input type="hidden" name="userUid'+index+'" id="userUid'+index+'" value="'+(row.userUid==undefined?'':row.userUid)+'" /><input type="text" maxlength=20 required="true" name="userName'+index+'" id="userName'+index+'" value="'+value+'" />';
}
}, {
field: 'userMobile',
title: '手机号码',
halign: 'center',
formatter: function (value, row, index) { //isMobile是jquery.validate-extend.js中的方法,可以通过class='isMobile'来绑定验证
return '<input type="hidden" name="isDelete'+index+'" id="isDelete'+index+'" value="'+(row.isDelete==undefined?0:row.isDelete)+'" /><input class="isMobile" type="text" maxlength=30 required="true" name="userMobile'+index+'" id="userMobile'+index+'" value="'+value+'" />';
}
},{
field: 'email',
title: '电子邮箱',
halign: 'center',
formatter: function (value, row, index) { //email是validate自带的验证方法,可以直接使用type='email'来绑定验证
return '<input name="email'+index+'" id="email'+index+'" type="email" value="'+(row.email==null?'':row.email)+'" />';
}
}, {
field: 'status',
title: '状态',
halign: 'center',
align: "center",
formatter: function (value, row, index) {
return '<input type="radio" name="status'+index+'" value="1" />启用<input type="radio" name="status'+index+'" checked value="0" />禁用';
}
}, {
field: 'deleteLabel',
title: '操作',
halign: 'center',
align: 'center',
formatter: function (value, row, index) {
return '<button class="btn btn-warning btn-circle" type="button" onclick="hiddenRow('+index+')" >删除<i class="fa fa-times"></i></button>';
}
}],
onLoadSuccess: function () {
//TODO
},
formatLoadingMessage: function(){
return "请稍等,正在加载中。。。";
},
onLoadError: function () {
//showTips("数据加载失败!");
}
});
function searchByCriteria() {
$('#table').bootstrapTable('refresh');
};
// 查询全部信息的参数
function queryParams(params) {
var temp = {};
return temp;
};
//新增一行表格
$("#addButton").click(function(){
//获取table中有没有具体的数据行数,没有时,会返回0
var length = $('#table').bootstrapTable('getData').length;
//获取table中的行数,无数据时,会有一个提示无数据的tr行
var trLength = $('#table>tbody').children("tr").length;
if(length == 0 && trLength == 1){
$('#table').bootstrapTable('append',{
index:length,
userName:'',
userMobile:'',
email:'',
status:0
})
//增加手机号码的验证,isMobile是jquery.validate-extend.js的验证方法,如果有新的电话号码格式,可以在里面追加规则
$("#userMobile"+length).attr("class","isMobile");
}else{
var appendHtml = '<tr data-index="'+trLength+'"><td><input name="userUid'+trLength+'" id="userUid'+trLength+'" type="hidden" value=""><input name="userName'+trLength+'" id="userName'+trLength+'" required="true" type="text" maxlength="30" ></td>';
appendHtml += '<td><input name="isDelete'+trLength+'" id="isDelete'+trLength+'" type="hidden" value="0"><input class="isMobile" name="userMobile'+trLength+'" id="userMobile'+trLength+'" required="true" type="text" maxlength="20" /></td>';
appendHtml +='<td><input name="email'+trLength+'" id="email'+trLength+'" type="email" /></td>'
appendHtml += '<td style="text-align: center;" ><input type="radio" name="status'+trLength+'" value="1" />启用<input type="radio" name="status'+trLength+'" checked value="0" />禁用</td><td style="text-align: center; "><button class="btn btn-warning btn-circle" onclick="hiddenRow('+trLength+')" type="button">删除<i class="fa fa-times"></i></button></td></tr>';
$('#table>tbody').append(appendHtml);
}
});
//对删除的数据进行隐藏
function hiddenRow(index){
$("#isDelete"+index).val(1);
//删除后,去掉必填验证
$("#userName"+index).removeAttr("required");
$("#userMobile"+index).removeAttr("required");
$('#table').children("tbody").children("tr").eq(index).hide();
}
//获取表单,对表单参数的封装
$.fn.serializeList = function(){
var testList = new Array();
var o = {};
$('#table').children("tbody").children("tr").each(function(i){
if($("#userUid"+i).val()!=''||$("#isDelete"+i).val()!=1){
o = {};
o.userUid = $("#userUid"+i).val();
o.isDelete = $("#isDelete"+i).val();
o.userName = $("#userName"+i).val();
o.userMobile = $("#userMobile"+i).val();
o.email = $("#email"+i).val();
//单选框值的获取
o.status = $("input[name='status"+i+"']:checked").val();
testList[testList.length] = o;
}
});
return testList;
};
//form表单验证
var returnvalidate = $("#testForm").validate({
submitHandler: function(form) { //通过之后回调
$.ajax({
type: "POST",
url: "/index",
data: JSON.stringify($(form).serializeList()),
dataType: "json",
contentType: "application/json",
success : function(data) {
alert(data.message);
/* if (data.code == 200) {
//刷新父页面
searchByCriteria();
} else{
alert(data.message);
} */
}
});
},
invalidHandler: function(form, validator) { //不通过回调
return false;
}
});
</script>
</body>
</html>
最后,测试。
对于每一行,都测试一下,验证不通过时,是否进行信息提示,运行结果图如下:
输入验证全部通过后,后台打印结果:
a:13555555555:2@qq.com:1
b:13555555555:2@qq.com:1
c:13555555555:2@qq.com:0
jquery.validate是一款很好用也很常用的前端js验证插件,它验证的一个核心是通过表单控件name进行唯一判断和验证。如果是一个数组,多个控件都是一个name,那就需要注意了。
在jquery.validate验证里,如果是一个数组,只要数组里的有一个验证通过,其他验证不通过也不进行提示。这主要因为在插件里,有这么一段代码被注释掉了。如果把这段代码放开,可能又会引起其它的问题。
//if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) { // return false; //}
既然如此,对于表单的批量验证,还是要遵循验证它的原则,通过name指定唯一标识来进行验证,绕开不能通过数组来进行验证的问题。以前是这么解决的,有些技术难题解决不了,就绕过去,但是这次又围着这个技术难题搜了很多资料,现在想来,完全没有必要。
表单批量验证,一般都是针对表单数据量不是很大,但是又需要批量新增、修改、删除的操作。这是很常用的业务场景,数据量多的时候,一条一条处理太费时。
在使用jquery.validate验证时,通过这次整理,对jquery.validate-extend.js中扩展的方法有了一个了解,以前只知道引用这个文件,但是怎么用不知道。因为没有去看jquery.validate-extend.js里面的代码。
这次终于知道了,像手机验证,在这个js文件中就已经包含了,无需再重新写一个,直接使用就可以了。除了常见的将isMobile加入到rules里这一种方式外,在批量验证时,还可以以class的方式绑定验证,这是以前所不知道的。
jquery.validate-extend.js中的isMobile方法,对于新加的手机号14开头的,在这里进行扩展。
通过这个demo,对jquey的validate的认识又加深了一步,如要了解更多,还是要查看官方文档或者其他比较详尽的文档。
// 手机号码验证
jQuery.validator.addMethod("isMobile", function(value, element) {
var length = value.length;
return this.optional(element) || (length == 11 && /^(((13[0-9]{1})|(14[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/.test(value));
}, "请正确填写您的手机号码。");
参考资料:
https://www.runoob.com/jquery/jquery-plugin-validate.html
https://www.jquery123.com/