还在为表单批量验证烦恼,看看使用jquery.validate如何做?!

表单批量验证,是一个常见应用场景,刚开始写代码那会儿,就做过这个验证,抠了很长时间,才把验证做完。这种验证,从做开发开始,到今天,怎么样也做过数次了,确切地说,每家公司都做过。

可是,每隔一段时间,就会忘个差不多,不记得当初是怎么做的了。最近,又遇到了批量验证。前台验证插件还是那几款插件,然而很久没写了,也想不起当初是怎么做的了,又得从来再来一遍。

重新搜索资料也是需要花费时间的,想避免这个浪费,就得整理总结,便于日后翻看。下面,进入正题。

环境说明:
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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容