现在的位置: 首页 > easyui > Form > validatebox > 正文
jQuery Easyui 源码分析之validatebox组件
2012年12月10日 validatebox, 源码分析 ⁄ 共 13031字 评论数 11 ⁄ 被围观 18,993 views+

validatebox属于非常简单的一个组件了,不过大多form都会用到这玩意,所以深入了解一下还是有必要的,话不多说了,直接上带注释的代码:

  1. /**  
  2.  * jQuery EasyUI 1.3.1  
  3.  * 源码基于1.3.1  
  4.  * Licensed under the GPL terms  
  5.  * To use it on other terms please contact us  
  6.  * Copyright(c) 2009-2012 stworthy [ stworthy@gmail.com ]  
  7.  * 注释由小雪完成,更多内容参见www.easyui.info  
  8.  */  
  9. (function($) {   
  10.     function init(target) {   
  11.         $(target).addClass("validatebox-text");   
  12.     };   
  13.     /** 
  14.      * 内部消毁接口  
  15.      * @param {Object} target 组件对应的input  
  16.      */  
  17.   
  18.     function destroyBox(target) {   
  19.         var data = $.data(target, "validatebox");   
  20.         //设置validating为false,以阻止当前正在对target做校验的事件处理程序。    
  21.         data.validating = false;   
  22.         var tip = data.tip;   
  23.         if(tip) {   
  24.             //如果有消息框,则移除消息框。   
  25.             tip.remove();   
  26.         }   
  27.         //解除绑定在DOM上的事件处理程序。   
  28.         $(target).unbind();   
  29.         //移除DOM。   
  30.         $(target).remove();   
  31.     };   
  32.     /**
  33.      * [bindEvents description]  
  34.      * @param  {[type]} target [description]  
  35.      * @return {[type]}  
  36.      */  
  37.     function bindEvents(target) {   
  38.         var box = $(target);   
  39.         //获取绑定在target上的数据   
  40.         var validatebox = $.data(target, "validatebox");   
  41.         //首先解除绑定在target上命名空间为validatebox所有事件处理程序,为什么要这么干呢?   
  42.         //第一、为了避免重复绑定,你懂的,jQuery事件绑定机制会累加绑定,所以一般重新绑定前都会解绑定;   
  43.         //第二、还有一个细节也要注意,这里解除的只是命名空间为validatebox的事件,也就是说用户自己用代码绑定的事件完全可以被兼容。   
  44.         //解除绑定之后会挨过重新来绑定focus、blur、mouseenter、mouseleave事件,命名空间均为validatebox。   
  45.         box.unbind(".validatebox").bind("focus.validatebox"function() {   
  46.             //focus事件是核心中的核心了   
  47.   
  48.             //首先设置validating属性,这个变量用于标示当前是否处于校验状态。   
  49.             validatebox.validating = true;   
  50.             //设置value属性,该属性保存target里面的值,定义这个有啥用,且往后看。   
  51.             validatebox.value = undefined;   
  52.             //定义一个立即运行的函数,为什么要这么定义呢,是蛋疼?还是有某种目的,我们且继续往下分析。   
  53.             (function() {   
  54.                 if(validatebox.validating) {//如果校验开关是打开的,则进行校验   
  55.                     if(validatebox.value != box.val()) {//看到了吧将validatebox.value与target值想比较,不一样才进行校验,这样当然能提高性能。   
  56.                         //将target的值赋给validatebox.value   
  57.                         validatebox.value = box.val();   
  58.                         //对target进行校验,validate内部会显示校验失败的提示tip   
  59.                         validate(target);   
  60.                     } else {   
  61.                         //尝试显示提示信息(这地方是否显示取决于validatebox.tip是否为空了,也就是取决于匿名函数第一次运行的结果)   
  62.                         showTip(target);   
  63.                     }   
  64.                     /**
  65.                      * 每隔200毫秒运行一下当前执行的函数  
  66.                      * 当前执行的是什么函数?不正是前面定义的立即运行的匿名函数么?  
  67.                      * 所以说前面定义匿名函数并不是因为蛋疼,而是有着这样一个递归调用的目的。  
  68.                      */  
  69.                     setTimeout(arguments.callee, 200);   
  70.                 }   
  71.             })();   
  72.         }).bind("blur.validatebox"function() {   
  73.             //失去焦点时关闭validatebox.validating,免得那自我递归的匿名函数跟苍蝇似的运行个没完没了。   
  74.             validatebox.validating = false;   
  75.             //移除消息提示框,你懂的,要不然一堆tip在页面上不能及时释放,很蛋疼的。   
  76.             removeTip(target);   
  77.         }).bind("mouseenter.validatebox"function() {   
  78.             //mouseenter事件,如果有validatebox-invalid的话(即target值其实是不符合规则的),则创建tip并显示tip。   
  79.             if(box.hasClass("validatebox-invalid")) {   
  80.                 createTipToShow(target);   
  81.             }   
  82.         }).bind("mouseleave.validatebox"function() {   
  83.             /**
  84.              * 鼠标离开时,如果是validatebox.validating是关闭的,则移除tip  
  85.              * 因为鼠标离开不代表失去焦点,所以这个地方并不强制停止校验  
  86.              */  
  87.             if(!validatebox.validating) {   
  88.                 removeTip(target);   
  89.             }   
  90.         });   
  91.     };   
  92.     /**
  93.      * [createTipToShow 创建提示框tip,并尝试显示之,非常简单,不多说。]  
  94.      * @param  {[type]} target [description]  
  95.      * @return {[type]}  
  96.      */  
  97.     function createTipToShow(target) {   
  98.         var message = $.data(target, "validatebox").message;   
  99.         var tip = $.data(target, "validatebox").tip;   
  100.         if(!tip) {   
  101.             tip = $("<div class=\"validatebox-tip\">" + "<span class=\"validatebox-tip-content\">" + "</span>" + "<span class=\"validatebox-tip-pointer\">" + "</span>" + "</div>").appendTo("body");   
  102.             $.data(target, "validatebox").tip = tip;   
  103.         }   
  104.         tip.find(".validatebox-tip-content").html(message);   
  105.         showTip(target);   
  106.     };   
  107.     /**
  108.      * [showTip 尝试显示tip,非常简单,不多说。]  
  109.      * @param  {[type]} target [description]  
  110.      * @return {[type]}  
  111.      */  
  112.     function showTip(target) {   
  113.         var box = $(target);   
  114.         var tip = $.data(target, "validatebox").tip;   
  115.         if(tip) {   
  116.             tip.css({   
  117.                 display: "block",   
  118.                 left: box.offset().left + box.outerWidth(),   
  119.                 top: box.offset().top   
  120.             });   
  121.         }   
  122.     };   
  123.     /**
  124.      * [removeTip 尝试移除tip,非常简单,不多说。]  
  125.      * @param  {[type]} target [description]  
  126.      * @return {[type]}  
  127.      */  
  128.     function removeTip(target) {   
  129.         var tip = $.data(target, "validatebox").tip;   
  130.         if(tip) {   
  131.             tip.remove();   
  132.             $.data(target, "validatebox").tip = null;   
  133.         }   
  134.     };   
  135.     /**
  136.      * [validate validate函数是实际完成校验的函数]  
  137.      * @param  {[type]} target [description]  
  138.      * @return {[type]}  
  139.      */  
  140.     function validate(target) {   
  141.         var data = $.data(target, "validatebox");   
  142.         var options = $.data(target, "validatebox").options;   
  143.         var tip = $.data(target, "validatebox").tip;   
  144.         var box = $(target);   
  145.         var value = box.val();   
  146.   
  147.         function setTipMessage(msg) {   
  148.             $.data(target, "validatebox").message = msg;   
  149.         };   
  150.         if(options.required) {//如果为必填项则做初步校验   
  151.             if(value == "") {   
  152.                 box.addClass("validatebox-invalid");   
  153.                 setTipMessage(options.missingMessage);   
  154.                 if(data.validating) {   
  155.                     createTipToShow(target);   
  156.                 }   
  157.                 return false;   
  158.             }   
  159.         }   
  160.         if(options.validType) {   
  161.             /**
  162.              * [result 匹配出校验类型]  
  163.              * result[1]为规则类型  
  164.              * result[2]为规则入参,例如length规则都是写为length[1,10],1和10就是入参。  
  165.              * @type {RegExp}  
  166.              */  
  167.             var result = /([a-zA-Z_]+)(.*)/.exec(options.validType);   
  168.             var rule = options.rules[result[1]];   
  169.             if(value && rule) {   
  170.                 var param = eval(result[2]);   
  171.                 if(!rule["validator"](value, param)) {   
  172.                     box.addClass("validatebox-invalid");   
  173.                     var message = rule["message"];   
  174.                     /**
  175.                      * 这个地方是不是蛋疼呢?  
  176.                      * 我看是真的蛋疼了,如果规则有入参的话,则在规则外部重新设置规则的消息  
  177.                      * 个人觉得某个规则的消息应该完全在规则内部定义,不可被外部改变。  
  178.                      * 所以说这个地方有待改进,也很容易改进。  
  179.                      */  
  180.                     if(param) {   
  181.                         for(var i = 0; i < param.length; i++) {   
  182.                             message = message.replace(new RegExp("\\{" + i + "\\}""g"), param[i]);   
  183.                         }   
  184.                     }   
  185.                     //options.invalidMessage是用户自己设置的消息   
  186.                     setTipMessage(options.invalidMessage || message);   
  187.                     if(data.validating) {   
  188.                         createTipToShow(target);   
  189.                     }   
  190.                     return false;   
  191.                 }   
  192.             }   
  193.         }   
  194.         box.removeClass("validatebox-invalid");   
  195.         removeTip(target);   
  196.         return true;   
  197.     };   
  198.     /**
  199.      * validatebox的构造函数  
  200.      **/  
  201.     $.fn.validatebox = function(options, param) {   
  202.         //看到了吧,easyui是通过入参来判断是构造组建还是调用组建方法。   
  203.         if(typeof options == "string") {   
  204.             //如果options是字符串型,则调用validatebox对用户提供的接口方法。   
  205.             return $.fn.validatebox.methods[options](this, param);   
  206.         }   
  207.         //如果构造函数没有入参,则设置options为{}空对象。   
  208.         options = options || {};   
  209.   
  210.         //this指向jq选择器返回DOM对象列表,用each方法逐个初始化每个DOM。   
  211.         return this.each(function() {   
  212.             //获取绑定在DOM上名为validatebox的全局对象。   
  213.             var state = $.data(this"validatebox");   
  214.             if(state) {   
  215.                 //如果validatebox已被初始化过,则覆盖原有配置参数。   
  216.                 $.extend(state.options, options);   
  217.             } else {   
  218.                 //如果validatebox未被初始化过,则初始化validatebox组建。   
  219.                 init(this);   
  220.                 //绑定一个名为validatebox的对象到DOM上,   
  221.                 //该对象只包含options属性,options属性的获取优先级规则为:   
  222.                 //传入的options>属性转换器>默认值   
  223.                 $.data(this"validatebox", {   
  224.                     options: $.extend({}, $.fn.validatebox.defaults, $.fn.validatebox.parseOptions(this), options)   
  225.                 });   
  226.             }   
  227.             //绑定/重绑定事件   
  228.             bindEvents(this);   
  229.         });   
  230.     };   
  231.     $.fn.validatebox.methods = {   
  232.         destroy: function(jq) {   
  233.             return jq.each(function() {   
  234.                 destroyBox(this);   
  235.             });   
  236.         },   
  237.         validate: function(jq) {   
  238.             return jq.each(function() {   
  239.                 validate(this);   
  240.             });   
  241.         },   
  242.         isValid: function(jq) {   
  243.             return validate(jq[0]);   
  244.         }   
  245.     };   
  246.     $.fn.validatebox.parseOptions = function(target) {   
  247.         var t = $(target);   
  248.         return $.extend({}, $.parser.parseOptions(target, ["validType""missingMessage""invalidMessage"]), {   
  249.             required: (t.attr("required") ? true : undefined)   
  250.         });   
  251.     };   
  252.     $.fn.validatebox.defaults = {   
  253.         required: false,   
  254.         validType: null,   
  255.         missingMessage: "This field is required.",   
  256.         invalidMessage: null,   
  257.         //定义默认规则   
  258.         rules: {   
  259.             email: {   
  260.                 validator: function(value) {   
  261.                     return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((")(((( |  )*(
  262. ))?( |  )+)?(([\x01-\x08 \x0e-\x1f\x7f]|!|[#-[]|[]-~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-      
  263. -\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*((( |  )*(  
  264. ))?( |  )+)?(")))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);   
  265.                 },   
  266.                 message: "Please enter a valid email address."  
  267.             },   
  268.             url: {   
  269.                 validator: function(value) {   
  270.                     return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);   
  271.                 },   
  272.                 message: "Please enter a valid URL."  
  273.             },   
  274.             length: {   
  275.                 validator: function(value, param) {   
  276.                     var len = $.trim(value).length;   
  277.                     return len >= param[0] && len <= param[1];   
  278.                 },   
  279.                 message: "Please enter a value between {0} and {1}."  
  280.             },   
  281.             remote: {   
  282.                 /**
  283.                  * [validator 远端验证规则]  
  284.                  * @param  {[type]} value [description]  
  285.                  * @param  {[type]} param [param[0]为url,param[1]为要传给后台的属性名,其值为value]  
  286.                  * @return {[type]}  
  287.                  */  
  288.                 validator: function(value, param) {   
  289.                     var data = {};   
  290.                     data[param[1]] = value;   
  291.                     var isValidate = $.ajax({   
  292.                         url: param[0],   
  293.                         dataType: "json",   
  294.                         data: data,   
  295.                         async: false,   
  296.                         cache: false,   
  297.                         type: "post"  
  298.                     }).responseText;   
  299.                     return isValidate == "true";   
  300.                 },   
  301.                 message: "Please fix this field."  
  302.             }   
  303.         }   
  304.     };   
  305. })(jQuery);  

目前有 11 条留言 其中:访客:6 条, 博主:5 条

  1. weiyingqin : 2012年12月11日10:43:15  -49楼 @回复 回复

    😛 学习了

  2. zoezhang : 2012年12月19日18:10:51  -48楼 @回复 回复

    为什么要settimeout?focus然后onchange不行吗?settimeout很浪费资源,网速慢点的话就一步一个卡(貌似这里ajax的async设为了false)


    • 管理员
      世纪之光 : 2012年12月19日21:27:42  地下1层 @回复 回复

      如果我没有记错onchange事件是在当前元素“失去焦点”且“内容发生变化时”才能触发,这样显然不能满足要求呀。

      • zoezhang : 2012年12月20日08:58:30  地下2层 @回复 回复

        那啥,我以前改过,在1.3版本,把源码里的focus改了,然后再加了一个change方法

        box.unbind(".validatebox").bind("focus.validatebox",function(){
        _37a.validating=true;
        _37a.value=undefined;
        if (box.val().length == 0) {  
          _37a.validating=true;
          if (_37a.validating) {  
          _37f(_379); 
          }  
        };  
        }).bind("change.validatebox",function(){
        _37a.validating=true;
          if (_37a.validating) {  
          _37f(_379); 
          }
        }).bind("blur.validatebox",function(){
        _37a.validating=false;
        _37b(_379);
        }).bind("mouseenter.validatebox",function(){
        if(box.hasClass("validatebox-invalid")){
        _37c(_379);
        }
        }).bind("mouseleave.validatebox",function(){
        _37b(_379);
        });
        };

        • 管理员
          世纪之光 : 2012年12月20日09:00:01  地下3层 @回复 回复

          onchange事件不符合要求的,焦点始终在输入框内不会触发该事件,你自己可以回过头测试一下。


        • 管理员
          世纪之光 : 2012年12月20日09:07:04  地下3层 @回复 回复

          当然了,如果降低要求的话(即放弃在文本框失去焦点之前所做的校验),change事件也是可以的

          • zoezhang : 2012年12月20日12:56:02  地下4层 @回复 回复

            “即放弃在文本框失去焦点之前所做的校验”,这个可以吧?有输入或选择就有改变就发生校验吧?


            • 管理员
              世纪之光 : 2012年12月20日14:41:32  地下5层 @回复 回复

              没有的,你亲自测试一下就知道了,change事件是在失去焦点后才会发生,如果我输入期间内容不停变化,但始终不失去焦点,那么我内容变化的过程就不会触发change,这个我自己测试过。

  3. Supersky : 2013年07月28日23:56:28  -47楼 @回复 回复

    validatebox好像有一个bug,但是好像没人提过,在IE7下,无论是否required:true,总是会进行必填校验,直接去官网的demo同样如此。
    Demo如下:http://www.jeasyui.com/demo/main/index.php?plugin=ValidateBox

    注意观察Birthday,显然这个不是required的,但IE7下却并不是如此。

    不过我没有真正的IE7测试,而是使用IE10切换到IE7模式,以及利用搜狗的兼容模式(js识别出来是msie 7.0)。不知道是否也有朋友遇到过此问题呢?


    • 管理员
      世纪之光 : 2013年07月29日09:38:39  地下1层 @回复 回复

      官网的demo并不一定准确,它运行的代码不一定是它贴出来的部分,看1.3.3压缩包demo/validatebox/basic.html的话,对于纯粹的validatebox组件是不存在这个问题的,但是如果是基于combo组件的其它组件(如combobox组件)就存在你说的这个问题了,主要问题还是出在combo组件上,它初始化的时候就做校验了(可以通过修改源码注释掉),同时在初始化的时候会setValue,而serValue是会做校验的(也需要修改源码)。综合起来,问题出现在combo组件上,需要修改源码才能解决。

  4. 阿飞 : 2013年12月26日19:34:31  -46楼 @回复 回复

    遇到了个棘手的问题,我用combobox验证var a = $this.combobox(“isValid”); 但是为什么他返回的不是boolean 而是一个OBJECT a[0]则是一个input DOM对象, 大神求解释 easyui版本1.3.4

给我留言

留言无头像?


×