• jquery-validate v1.19.2 源码分析


    一、代码主体结构

     二、源码分析

      2.1  外层结构

      

    分析步骤

    a. 简化结构如下

     1 (function(param){
     2 
     3       console.info(param);
     4 
     5   }( "123" ));
     6 
     7   执行结果:
     8 
     9   123
    10 
    11   立即执行函数

    b. 根据步骤a即可理解外层结构的意思

     1     * 将封装的插件代码函数以参数的形式传入工厂方法, 函数内容根据环境探测结果采用不同方式执行,以兼容多种环境10     
    11      * 如果当前执行环境是amd{Asynchronous Module Definition,即异步模块加载机制},则需要添加jquery依赖,回调函数 为传入的factory
    12      if ( typeof define === "function" && define.amd ) {
    13         define( ["jquery"], factory );
    14       }    
    15  
    16      * 如果当前环境是 CommonJS Modules , 则如下方式引入依赖执行factory
    17      if (typeof module === "object" && module.exports) {
    18         module.exports = factory( require( "jquery" ) );
    19     }
    20    
    21      * 其他普通浏览器环境直接执行函数
    22       factory(jQuery);
    23      

     2.2  $.validator 的定义

        

      分析步骤

      a. 简化结构如下

     1 // 定义构造函数
     2 $.validator = function( options, form ) {
     3     // do init method...
     4 };
     5 
     6 // 扩展 $.validator 函数属性,包括原型对象
     7 $.extend( $.validator, {
     8     prop: value,
     9     prototype: {
    10         prop: value
    11     }
    12     // ... other props
    13 });

      b.构造函数逻辑代码

    1 $.validator = function( options, form ) {
    2     // 对当前对象的配置赋值 = 将{}, 默认配置,自定义配置 合并到目标对象 settings 
    3     this.settings = $.extend( true, {}, $.validator.defaults, options );
    4     // 对当前的form赋值
    5     this.currentForm = form;
    6     // 初始化当前表单的 validator对象
    7     this.init();
    8 };

     

      c. 分析$.validator 函数 扩展的属性

      1 // 默认配置,可自定义修改
      2 defaults: {
      3         // 提示消息
      4         messages: {},
      5         // 分组
      6         groups: {},
      7         // 自定义校验规则
      8         rules: {},
      9         // 错误提示的类样式
     10         errorClass: "error",
     11         // 等待的类样式 
     12         pendingClass: "pending",
     13         // 有效的类样式
     14         validClass: "valid",
     15         // 错误提示的元素
     16         errorElement: "label",
     17         // xx
     18         focusCleanup: false,
     19         // xx
     20         focusInvalid: true,
     21         // xx 
     22         errorContainer: $( [] ),
     23         // xx
     24         errorLabelContainer: $( [] ),
     25         // 提交事件之前校验规则 
     26         onsubmit: true,
     27         // 忽略hidden属性
     28         ignore: ":hidden",
     29         // 默认忽略title的校验
     30         ignoreTitle: false,
     31         
     32         // 聚焦,当 focusCleanup 为true时,取消错误高亮效果
     33         onfocusin: function( element ) {
     34             this.lastActive = element;
     35 
     36             // Hide error label and remove error class on focus if enabled
     37             if ( this.settings.focusCleanup ) {
     38                 if ( this.settings.unhighlight ) {
     39                     this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
     40                 }
     41                 this.hideThese( this.errorsFor( element ) );
     42             }
     43         },
     44         
     45         // 失去焦点 校验元素
     46         onfocusout: function( element ) {
     47             if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) {
     48                 this.element( element );
     49             }
     50         },
     51         
     52         // 检查键盘事件,是否触发提交 
     53         onkeyup: function( element, event ) {
     54 
     55             // Avoid revalidate the field when pressing one of the following keys
     56             // Shift       => 16
     57             // Ctrl        => 17
     58             // Alt         => 18
     59             // Caps lock   => 20
     60             // End         => 35
     61             // Home        => 36
     62             // Left arrow  => 37
     63             // Up arrow    => 38
     64             // Right arrow => 39
     65             // Down arrow  => 40
     66             // Insert      => 45
     67             // Num lock    => 144
     68             // AltGr key   => 225
     69             var excludedKeys = [
     70                 16, 17, 18, 20, 35, 36, 37,
     71                 38, 39, 40, 45, 144, 225
     72             ];
     73 
     74             if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
     75                 return;
     76             } else if ( element.name in this.submitted || element.name in this.invalid ) {
     77                 this.element( element );
     78             }
     79         },
     80         
     81         // selects, radio, checkbox , 其他方式的option 元素的 点击事件
     82         onclick: function( element ) {
     83             
     84             // Click on selects, radiobuttons and checkboxes
     85             if ( element.name in this.submitted ) {
     86                 this.element( element );
     87 
     88             // Or option elements, check parent select in that case
     89             } else if ( element.parentNode.name in this.submitted ) {
     90                 this.element( element.parentNode );
     91             }
     92         },
     93         // 高亮效果
     94         highlight: function( element, errorClass, validClass ) {
     95             if ( element.type === "radio" ) {
     96                 this.findByName( element.name ).addClass( errorClass ).removeClass( validClass );
     97             } else {
     98                 $( element ).addClass( errorClass ).removeClass( validClass );
     99             }
    100         },
    101         
    102         // 取消高亮效果
    103         unhighlight: function( element, errorClass, validClass ) {
    104             if ( element.type === "radio" ) {
    105                 this.findByName( element.name ).removeClass( errorClass ).addClass( validClass );
    106             } else {
    107                 $( element ).removeClass( errorClass ).addClass( validClass );
    108             }
    109         }
    110 }
     1     // 修改掉 $.validator.defaults 默认配置
     2     setDefaults: function( settings ) {
     3         $.extend( $.validator.defaults, settings );
     4     },
     5     
     6     // 类规则设置
     7     classRuleSettings: {
     8         required: { required: true },
     9         email: { email: true },
    10         url: { url: true },
    11         date: { date: true },
    12         dateISO: { dateISO: true },
    13         number: { number: true }, // 包含小数
    14         digits: { digits: true }, // 数字
    15         creditcard: { creditcard: true }
    16     },
    17     
    18     // 默认的规则提示
    19     messages: {
    20         required: "This field is required.",
    21         remote: "Please fix this field.",
    22         email: "Please enter a valid email address.",
    23         url: "Please enter a valid URL.",
    24         date: "Please enter a valid date.",
    25         dateISO: "Please enter a valid date (ISO).",
    26         number: "Please enter a valid number.",
    27         digits: "Please enter only digits.",
    28         equalTo: "Please enter the same value again.",
    29         maxlength: $.validator.format( "Please enter no more than {0} characters." ),
    30         minlength: $.validator.format( "Please enter at least {0} characters." ),
    31         rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ),
    32         range: $.validator.format( "Please enter a value between {0} and {1}." ),
    33         max: $.validator.format( "Please enter a value less than or equal to {0}." ),
    34         min: $.validator.format( "Please enter a value greater than or equal to {0}." ),
    35         step: $.validator.format( "Please enter a multiple of {0}." )
    36     },
    37 
    38     // 自动创建范围 : false
    39     autoCreateRanges: false,
     1     //原型函数分析
     2     
     3     // 检验提示错误,错误列表大小
     4     size: function() {
     5         return this.errorList.length;
     6     },
     7     
     8     // 当前大小 = 0, 校验通过
     9     valid: function() {
    10         return this.size() === 0;
    11     }
    12     
    13     // 校验错误的个数
    14     numberOfInvalids: function() {
    15         return this.objectLength( this.invalid );
    16     },
    17     
    18     // 返回传入对象长度
    19     objectLength: function( obj ) {
    20         /* jshint unused: false */
    21         var count = 0,
    22             i;
    23         for ( i in obj ) {
    24     
    25             // This check allows counting elements with empty error
    26             // message as invalid elements
    27             if ( obj[ i ] !== undefined && obj[ i ] !== null && obj[ i ] !== false ) {
    28                 count++;
    29             }
    30         }
    31         return count;
    32     },
    33     
    34     // 聚焦到最后一个错误提示的元素
    35     focusInvalid: function() {
    36         if ( this.settings.focusInvalid ) {
    37             try {
    38                 $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] )
    39                 .filter( ":visible" )
    40                 .trigger( "focus" )
    41     
    42                 // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
    43                 .trigger( "focusin" );
    44             } catch ( e ) {
    45     
    46                 // Ignore IE throwing errors when focusing hidden elements
    47             }
    48         }
    49     },
    50     
    51     // 查找最后一个元素active
    52     findLastActive: function() {
    53         var lastActive = this.lastActive;
    54         return lastActive && $.grep( this.errorList, function( n ) {
    55             return n.element.name === lastActive.name;
    56         } ).length === 1 && lastActive;
    57     },
     1 // xx
     2     clean: function( selector ) {
     3         return $( selector )[ 0 ];
     4     },
     5     
     6     // 错误样式 
     7     errors: function() {
     8         var errorClass = this.settings.errorClass.split( " " ).join( "." );
     9         return $( this.settings.errorElement + "." + errorClass, this.errorContext );
    10     },
    11     
    12     // 重置 
    13     resetInternals: function() {
    14         this.successList = [];
    15         this.errorList = [];
    16         this.errorMap = {};
    17         this.toShow = $( [] );
    18         this.toHide = $( [] );
    19     },
    20     
    21     // 重置
    22     reset: function() {
    23         this.resetInternals();
    24         this.currentElements = $( [] );
    25     },
    26     
    27     // form 准备
    28     prepareForm: function() {
    29         this.reset();
    30         this.toHide = this.errors().add( this.containers );
    31     },
    32     
    33     // 元素准备
    34     prepareElement: function( element ) {
    35         this.reset();
    36         this.toHide = this.errorsFor( element );
    37     },
    38     
    39     //  从错误列表里过滤出元素
    40     errorsFor: function( element ) {
    41         var name = this.escapeCssMeta( this.idOrName( element ) ),
    42             describer = $( element ).attr( "aria-describedby" ),
    43             selector = "label[for='" + name + "'], label[for='" + name + "'] *";
    44     
    45         // 'aria-describedby' should directly reference the error element
    46         if ( describer ) {
    47             selector = selector + ", #" + this.escapeCssMeta( describer )
    48                 .replace( /s+/g, ", #" );
    49         }
    50         
    51         // [1,23,3].filter( function(i ) { return  i == 23;}); // 从数组中过滤
    52         
    53         return this
    54             .errors()
    55             .filter( selector );
    56     },
    57     
    58     // 正则替换字符串返回结果
    59     escapeCssMeta: function( string ) {
    60         return string.replace( /([\!"#$%&'()*+,./:;<=>?@[]^`{|}~])/g, "\$1" );
    61     },
    62     
    63     // 获取元素的id或name值
    64     idOrName: function( element ) {
    65         return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name );
    66     },
      1     // 返回元素的message【自定义或默认】
      2     customDataMessage: function( element, method ) {
      3         return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() +
      4             method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" );
      5     },
      6     
      7     // 通过名称读取message
      8     customMessage: function( name, method ) {
      9         var m = this.settings.messages[ name ];
     10         return m && ( m.constructor === String ? m : m[ method ] );
     11     },
     12     
     13     // 返回第一个非undefined的参数值
     14     findDefined: function() {
     15         for ( var i = 0; i < arguments.length; i++ ) {
     16             if ( arguments[ i ] !== undefined ) {
     17                 return arguments[ i ];
     18             }
     19         }
     20         return undefined;
     21     },
     22     
     23     // The second parameter 'rule' used to be a string, and extended to an object literal
     24     // of the following form:
     25     // rule = {
     26     //     method: "method name",
     27     //     parameters: "the given method parameters"
     28     // }
     29     // 现在还支持老的行为(rule == string),下一个版本没了
     30     // The old behavior still supported, kept to maintain backward compatibility with
     31     // old code, and will be removed in the next major release. ; 
     32     defaultMessage: function( element, rule ) {
     33         if ( typeof rule === "string" ) {
     34             rule = { method: rule };
     35         }
     36         // 按次序读取message,直到找到一个非undefined的message
     37         var message = this.findDefined(
     38                 this.customMessage( element.name, rule.method ),
     39                 this.customDataMessage( element, rule.method ),
     40     
     41                 // 'title' is never undefined, so handle empty string as undefined
     42                 !this.settings.ignoreTitle && element.title || undefined,
     43                 $.validator.messages[ rule.method ],
     44                 "<strong>Warning: No message defined for " + element.name + "</strong>"
     45             ),
     46             theregex = /$?{(d+)}/g;
     47         if ( typeof message === "function" ) {
     48             message = message.call( this, rule.parameters, element );
     49         } else if ( theregex.test( message ) ) {
     50             message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters );
     51         }
     52     
     53         return message;
     54     },
     55     
     56     // 通过元素和规则格式化message,放到错误列表
     57     formatAndAdd: function( element, rule ) {
     58         var message = this.defaultMessage( element, rule );
     59     
     60         this.errorList.push( {
     61             message: message,
     62             element: element,
     63             method: rule.method
     64         } );
     65     
     66         this.errorMap[ element.name ] = message;
     67         this.submitted[ element.name ] = message;
     68     },
     69     
     70     // 检查是否有包裹层
     71     addWrapper: function( toToggle ) {
     72         if ( this.settings.wrapper ) {
     73             toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
     74         }
     75         return toToggle;
     76     },
     77     
     78     // 显示错误提示 
     79     defaultShowErrors: function() {
     80         var i, elements, error;
     81         // 迭代errorlist,显示错误提示
     82         for ( i = 0; this.errorList[ i ]; i++ ) {
     83             error = this.errorList[ i ];
     84             if ( this.settings.highlight ) {
     85                 this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
     86             }
     87             this.showLabel( error.element, error.message );
     88         }
     89         
     90         // 如果错误个数大于0 ; xx
     91         if ( this.errorList.length ) {
     92             this.toShow = this.toShow.add( this.containers );
     93         }
     94         
     95         // 如果setting.success 为true,显示正常校验的提示
     96         if ( this.settings.success ) {
     97             for ( i = 0; this.successList[ i ]; i++ ) {
     98                 this.showLabel( this.successList[ i ] );
     99             }
    100         }
    101         
    102         // 校验通过的元素取消高亮效果
    103         if ( this.settings.unhighlight ) {
    104             for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) {
    105                 this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass );
    106             }
    107         }
    108         
    109         //  $([11,2,3]).not([2,3]); console.info(a.toArray());
    110         this.toHide = this.toHide.not( this.toShow );
    111         
    112         // xx
    113         this.hideErrors();
    114         
    115         // xx
    116         this.addWrapper( this.toShow ).show();
    117     },
    118     
    119     showLabel: function( element, message ) {
    120         var place, group, errorID, v,
    121             error = this.errorsFor( element ),
    122             elementID = this.idOrName( element ),
    123             describedBy = $( element ).attr( "aria-describedby" );
    124     
    125         if ( error.length ) {
    126     
    127             // Refresh error/success class 干掉有效的样式添加错误提示的样式
    128             error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass );
    129     
    130             // Replace message on existing label 替换错误提示
    131             error.html( message );
    132         } else {
    133     
    134             // Create error element 创建错误提示元素
    135             error = $( "<" + this.settings.errorElement + ">" )
    136                 .attr( "id", elementID + "-error" )
    137                 .addClass( this.settings.errorClass )
    138                 .html( message || "" );
    139     
    140             // Maintain reference to the element to be placed into the DOM
    141             place = error;
    142             if ( this.settings.wrapper ) {
    143     
    144                 // Make sure the element is visible, even in IE
    145                 // actually showing the wrapped element is handled elsewhere
    146                 place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent();
    147             }
    148             
    149             // 插入提示文本
    150             if ( this.labelContainer.length ) {
    151                 this.labelContainer.append( place );
    152             } else if ( this.settings.errorPlacement ) {
    153                 // 自定义 错误替换 函数
    154                 this.settings.errorPlacement.call( this, place, $( element ) );
    155             } else {
    156                 place.insertAfter( element );
    157             }
    158     
    159             // link 错误到元素
    160             if ( error.is( "label" ) ) {
    161     
    162                 // If the error is a label, then associate using 'for'
    163                 error.attr( "for", elementID );
    164     
    165                 // If the element is not a child of an associated label, then it's necessary
    166                 // to explicitly apply aria-describedby
    167             } else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) {
    168                 errorID = error.attr( "id" );
    169     
    170                 // Respect existing non-error aria-describedby
    171                 if ( !describedBy ) {
    172                     describedBy = errorID;
    173                 } else if ( !describedBy.match( new RegExp( "\b" + this.escapeCssMeta( errorID ) + "\b" ) ) ) {
    174     
    175                     // Add to end of list if not already present
    176                     describedBy += " " + errorID;
    177                 }
    178                 $( element ).attr( "aria-describedby", describedBy );
    179     
    180                 // If this element is grouped, then assign to all elements in the same group
    181                 group = this.groups[ element.name ];
    182                 if ( group ) {
    183                     v = this;
    184                     $.each( v.groups, function( name, testgroup ) {
    185                         if ( testgroup === group ) {
    186                             $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm )
    187                                 .attr( "aria-describedby", error.attr( "id" ) );
    188                         }
    189                     } );
    190                 }
    191             }
    192         }
    193         // 定义success的时候,
    194         if ( !message && this.settings.success ) {
    195             error.text( "" );
    196             if ( typeof this.settings.success === "string" ) {
    197                 // string时 添加样式 
    198                 error.addClass( this.settings.success );
    199             } else {
    200                 // function时,调用函数
    201                 this.settings.success( error, element );
    202             }
    203         }
    204         this.toShow = this.toShow.add( error );
    205     },
     1         // 从当前元素中去掉校验不通过的元素
     2         validElements: function() {
     3             return this.currentElements.not( this.invalidElements() );
     4         },
     5         
     6         // 从errorList中返回 校验不通过的元素集合
     7         invalidElements: function() {
     8             return $( this.errorList ).map( function() {
     9                 return this.element;
    10             } );
    11         },
    12 
    13         // 校验目标 
    14         validationTargetFor: function( element ) {
    15 
    16             // If radio/checkbox, validate first element in group instead 如果元素是radio,checkbox;查找组
    17             if ( this.checkable( element ) ) {
    18                 element = this.findByName( element.name );
    19             }
    20 
    21             // Always apply ignore filter
    22             return $( element ).not( this.settings.ignore )[ 0 ];
    23         },
    24 
    25         // 检查是否是radio或checkbox
    26         checkable: function( element ) {
    27             return ( /radio|checkbox/i ).test( element.type );
    28         },
    29 
    30         // 表单元素查找
    31         findByName: function( name ) {
    32             return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" );
    33         },
    34         
    35         // 获取元素节点的长度
    36         getLength: function( value, element ) {
    37             switch ( element.nodeName.toLowerCase() ) {
    38             case "select":
    39                 // 下拉
    40                 return $( "option:selected", element ).length;
    41             case "input":
    42                 if ( this.checkable( element ) ) { 
    43                     // radio checkbox
    44                     return this.findByName( element.name ).filter( ":checked" ).length;
    45                 }
    46             }
    47             // 普通的表单
    48             return value.length;
    49         },
    50 
    51         // 
    52         depend: function( param, element ) {
    53             return this.dependTypes[ typeof param ] ? this.dependTypes[ typeof param ]( param, element ) : true;
    54         },
    55         
    56         // 类型依赖    
    57         dependTypes: {
    58             "boolean": function( param ) {
    59                 return param;
    60             },
    61             "string": function( param, element ) {
    62                 return !!$( param, element.form ).length;
    63             },
    64             "function": function( param, element ) {
    65                 return param( element );
    66             }
    67         },
    68 
    69         // 非必填项 
    70         optional: function( element ) {
    71             var val = this.elementValue( element );
    72             return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch";
    73         },
     1         // 构建errorlist和successlist 调用函数显示错误label
     2         showErrors: function( errors ) {
     3             if ( errors ) {
     4                 var validator = this;
     5         
     6                 // Add items to error list and map
     7                 $.extend( this.errorMap, errors );
     8                 this.errorList = $.map( this.errorMap, function( message, name ) {
     9                     return {
    10                         message: message,
    11                         element: validator.findByName( name )[ 0 ]
    12                     };
    13                 } );
    14         
    15                 // Remove items from success list
    16                 this.successList = $.grep( this.successList, function( element ) {
    17                     return !( element.name in errors );
    18                 } );
    19             }
    20             if ( this.settings.showErrors ) {
    21                 this.settings.showErrors.call( this, this.errorMap, this.errorList );
    22             } else {
    23                 this.defaultShowErrors();
    24             }
    25         },
    26         
    27         // 检测$.fn.resetForm 是否存在
    28         resetForm: function() {
    29             if ( $.fn.resetForm ) {
    30                 $( this.currentForm ).resetForm();
    31             }
    32             this.invalid = {};
    33             this.submitted = {};
    34             this.prepareForm();
    35             this.hideErrors();
    36             var elements = this.elements()
    37                 .removeData( "previousValue" )
    38                 .removeAttr( "aria-invalid" );
    39             
    40             this.resetElements( elements );
    41         },
    42         
    43         // 重置元素提示样式
    44         resetElements: function( elements ) {
    45             var i;
    46         
    47             if ( this.settings.unhighlight ) {
    48                 for ( i = 0; elements[ i ]; i++ ) {
    49                     this.settings.unhighlight.call( this, elements[ i ],
    50                         this.settings.errorClass, "" );
    51                     this.findByName( elements[ i ].name ).removeClass( this.settings.validClass );
    52                 }
    53             } else {
    54                 elements
    55                     .removeClass( this.settings.errorClass )
    56                     .removeClass( this.settings.validClass );
    57             }
    58         },
    59         
    60         // 隐藏错误
    61         hideErrors: function() {
    62             this.hideThese( this.toHide );
    63         },
    64         
    65         // xx
    66         hideThese: function( errors ) {
    67             errors.not( this.containers ).text( "" );
    68             this.addWrapper( errors ).hide();
    69         },
      1 // 规范化所有规则
      2         normalizeRules: function( rules, element ) {
      3             
      4             // 迭代rules
      5             $.each( rules, function( prop, val ) {
      6         
      7                 // 当规则值为false,非必填项,规则去除
      8                 if ( val === false ) {
      9                     delete rules[ prop ];
     10                     return;
     11                 }
     12                 // 检查参数是否是否有依赖
     13                 if ( val.param || val.depends ) {
     14                     var keepRule = true;
     15                     switch ( typeof val.depends ) {
     16                     case "string":
     17                         keepRule = !!$( val.depends, element.form ).length;
     18                         break;
     19                     case "function":
     20                         keepRule = val.depends.call( element, element );
     21                         break;
     22                     }
     23                     // 规则有效用规则,规则无效删除规则
     24                     if ( keepRule ) {
     25                         rules[ prop ] = val.param !== undefined ? val.param : true;
     26                     } else {
     27                         $.data( element.form, "validator" ).resetElements( $( element ) );
     28                         delete rules[ prop ];
     29                     }
     30                 }
     31             } );
     32         
     33             // Evaluate parameters
     34             $.each( rules, function( rule, parameter ) {
     35                 rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter;
     36             } );
     37         
     38             // 格式化数字 最大长度,最小长度
     39             $.each( [ "minlength", "maxlength" ], function() {
     40                 if ( rules[ this ] ) {
     41                     rules[ this ] = Number( rules[ this ] );
     42                 }
     43             } );
     44             
     45             // 范围规则
     46             $.each( [ "rangelength", "range" ], function() {
     47                 var parts;
     48                 if ( rules[ this ] ) {
     49                     if ( $.isArray( rules[ this ] ) ) {
     50                         rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ];
     51                     } else if ( typeof rules[ this ] === "string" ) {
     52                         parts = rules[ this ].replace( /[[]]/g, "" ).split( /[s,]+/ );
     53                         rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ];
     54                     }
     55                 }
     56             } );
     57         
     58             // 是否自动创建范围规则, 是: 删除大小规则,创建范围规则
     59             if ( $.validator.autoCreateRanges ) {
     60         
     61                 // Auto-create ranges
     62                 if ( rules.min != null && rules.max != null ) {
     63                     rules.range = [ rules.min, rules.max ];
     64                     delete rules.min;
     65                     delete rules.max;
     66                 }
     67                 if ( rules.minlength != null && rules.maxlength != null ) {
     68                     rules.rangelength = [ rules.minlength, rules.maxlength ];
     69                     delete rules.minlength;
     70                     delete rules.maxlength;
     71                 }
     72             }
     73         
     74             return rules;
     75         },
     76         
     77         // 规范化rule, e.g., "required" to {required:true}
     78         normalizeRule: function( data ) {
     79             if ( typeof data === "string" ) {
     80                 var transformed = {};
     81                 $.each( data.split( /s/ ), function() {
     82                     transformed[ this ] = true;
     83                 } );
     84                 data = transformed;
     85             }
     86             return data;
     87         },
     88         
     89         // 元素
     90         element: function( element ) {
     91             var cleanElement = this.clean( element ),
     92                 checkElement = this.validationTargetFor( cleanElement ),
     93                 v = this,
     94                 result = true,
     95                 rs, group;
     96             
     97             if ( checkElement === undefined ) {
     98                 delete this.invalid[ cleanElement.name ];
     99             } else {
    100                 this.prepareElement( checkElement );
    101                 this.currentElements = $( checkElement );
    102 
    103                 // If this element is grouped, then validate all group elements already
    104                 // containing a value
    105                 group = this.groups[ checkElement.name ];
    106                 if ( group ) {
    107                     $.each( this.groups, function( name, testgroup ) {
    108                         if ( testgroup === group && name !== checkElement.name ) {
    109                             cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) );
    110                             if ( cleanElement && cleanElement.name in v.invalid ) {
    111                                 v.currentElements.push( cleanElement );
    112                                 result = v.check( cleanElement ) && result;
    113                             }
    114                         }
    115                     } );
    116                 }
    117 
    118                 rs = this.check( checkElement ) !== false;
    119                 result = result && rs;
    120                 if ( rs ) {
    121                     this.invalid[ checkElement.name ] = false;
    122                 } else {
    123                     this.invalid[ checkElement.name ] = true;
    124                 }
    125 
    126                 if ( !this.numberOfInvalids() ) {
    127 
    128                     // Hide error containers on last error
    129                     this.toHide = this.toHide.add( this.containers );
    130                 }
    131                 this.showErrors();
    132 
    133                 // Add aria-invalid status for screen readers
    134                 $( element ).attr( "aria-invalid", !rs );
    135             }
    136 
    137             return result;
    138         },
    139         
    140         // 迭代表单元素
    141         elements: function() {
    142             var validator = this,
    143                 rulesCache = {};
    144 
    145             // 查找通过的元素不包含submit和reset按纽, image,dissabled, 校验外元素
    146             return $( this.currentForm )
    147             .find( "input, select, textarea, [contenteditable]" )
    148             .not( ":submit, :reset, :image, :disabled" )
    149             .not( this.settings.ignore )
    150             .filter( function() {
    151                 var name = this.name || $( this ).attr( "name" ); // For contenteditable
    152                 var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false";
    153 
    154                 if ( !name && validator.settings.debug && window.console ) {
    155                     console.error( "%o has no name assigned", this );
    156                 }
    157 
    158                 // Set form expando on contenteditable
    159                 if ( isContentEditable ) {
    160                     // 找到当前元素的最接近的父节点form元素
    161                     this.form = $( this ).closest( "form" )[ 0 ];
    162                     this.name = name;
    163                 }
    164 
    165                 // Ignore elements that belong to other/nested forms
    166                 if ( this.form !== validator.currentForm ) {
    167                     return false;
    168                 }
    169 
    170                 // Select only the first element for each name, and only those with rules specified
    171                 if ( name in rulesCache || !validator.objectLength( $( this ).rules() ) ) {
    172                     return false;
    173                 }
    174 
    175                 rulesCache[ name ] = true;
    176                 return true;
    177             } );
    178         },
    179 
    180         elementValue: function( element ) {
    181             var $element = $( element ),
    182                 type = element.type,
    183                 isContentEditable = typeof $element.attr( "contenteditable" ) !== "undefined" && $element.attr( "contenteditable" ) !== "false",
    184                 val, idx;
    185 
    186             if ( type === "radio" || type === "checkbox" ) {
    187                 // radio或checkbox
    188                 return this.findByName( element.name ).filter( ":checked" ).val();
    189             } else if ( type === "number" && typeof element.validity !== "undefined" ) {
    190                 // 数字的情况下,返回元素的值是否正确
    191                 return element.validity.badInput ? "NaN" : $element.val();
    192             }
    193 
    194             if ( isContentEditable ) {
    195                 // 文本编辑器 获取文本内容 text
    196                 val = $element.text();
    197             } else {
    198                 // 获取元素值
    199                 val = $element.val();
    200             }
    201             
    202             // 文件类型,获取文件名,读取文件最后一个/
    203             if ( type === "file" ) {
    204 
    205                 // Modern browser (chrome & safari)
    206                 if ( val.substr( 0, 12 ) === "C:\fakepath\" ) {
    207                     return val.substr( 12 );
    208                 }
    209 
    210                 // Legacy browsers
    211                 // Unix-based path
    212                 idx = val.lastIndexOf( "/" );
    213                 if ( idx >= 0 ) {
    214                     return val.substr( idx + 1 );
    215                 }
    216 
    217                 // Windows-based path
    218                 idx = val.lastIndexOf( "\" );
    219                 if ( idx >= 0 ) {
    220                     return val.substr( idx + 1 );
    221                 }
    222 
    223                 // Just the file name
    224                 return val;
    225             }
    226             
    227             // 如果是字符串类型,去掉回车符
    228             if ( typeof val === "string" ) {
    229                 return val.replace( /
    /g, "" );
    230             }
    231             return val;
    232         },
    233         
    234         validationTargetFor: function( element ) {
    235             // If radio/checkbox, validate first element in group instead
    236             if ( this.checkable( element ) ) {
    237                 element = this.findByName( element.name );
    238             }
    239             // Always apply ignore filter
    240             return $( element ).not( this.settings.ignore )[ 0 ];
    241         },
    242         
    243         // 检查 校验元素
    244         check: function( element ) {
    245             element = this.validationTargetFor( this.clean( element ) );
    246 
    247             var rules = $( element ).rules(),
    248                 rulesCount = $.map( rules, function( n, i ) {
    249                     return i;
    250                 } ).length,
    251                 dependencyMismatch = false,
    252                 val = this.elementValue( element ),
    253                 result, method, rule, normalizer;
    254 
    255             // Prioritize the local normalizer defined for this element over the global one
    256             // if the former exists, otherwise user the global one in case it exists.
    257             if ( typeof rules.normalizer === "function" ) {
    258                 normalizer = rules.normalizer;
    259             } else if (    typeof this.settings.normalizer === "function" ) {
    260                 normalizer = this.settings.normalizer;
    261             }
    262 
    263             // If normalizer is defined, then call it to retreive the changed value instead
    264             // of using the real one.
    265             // Note that `this` in the normalizer is `element`.
    266             if ( normalizer ) {
    267                 val = normalizer.call( element, val );
    268 
    269                 // Delete the normalizer from rules to avoid treating it as a pre-defined method.
    270                 delete rules.normalizer;
    271             }
    272 
    273             for ( method in rules ) {
    274                 rule = { method: method, parameters: rules[ method ] };
    275                 try {
    276                     // 重要
    277                     result = $.validator.methods[ method ].call( this, val, element, rule.parameters );
    278 
    279                     // If a method indicates that the field is optional and therefore valid,
    280                     // don't mark it as valid when there are no other rules
    281                     if ( result === "dependency-mismatch" && rulesCount === 1 ) {
    282                         dependencyMismatch = true;
    283                         continue;
    284                     }
    285                     dependencyMismatch = false;
    286 
    287                     if ( result === "pending" ) {
    288                         this.toHide = this.toHide.not( this.errorsFor( element ) );
    289                         return;
    290                     }
    291 
    292                     if ( !result ) {
    293                         this.formatAndAdd( element, rule );
    294                         return false;
    295                     }
    296                 } catch ( e ) {
    297                     if ( this.settings.debug && window.console ) {
    298                         console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e );
    299                     }
    300                     if ( e instanceof TypeError ) {
    301                         e.message += ".  Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.";
    302                     }
    303 
    304                     throw e;
    305                 }
    306             }
    307             if ( dependencyMismatch ) {
    308                 return;
    309             }
    310             if ( this.objectLength( rules ) ) {
    311                 this.successList.push( element );
    312             }
    313             return true;
    314         },
     1         form: function() {
     2             this.checkForm();
     3             $.extend( this.submitted, this.errorMap );
     4             this.invalid = $.extend( {}, this.errorMap );
     5             if ( !this.valid() ) {
     6                 $( this.currentForm ).triggerHandler( "invalid-form", [ this ] );
     7             }
     8             this.showErrors();
     9             return this.valid();
    10         },
    11         
    12         // 校验form表单所有元素,获取校验结果 当 this.valid()> 0的时候,表示当前的form表单校验不通过
    13         checkForm: function() {
    14             this.prepareForm();
    15             for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) {
    16                 this.check( elements[ i ] );
    17             }
    18             return this.valid();
    19         },
    20         
    21         // 旧值
    22         previousValue: function( element, method ) {
    23             method = typeof method === "string" && method || "remote";
    24         
    25             return $.data( element, "previousValue" ) || $.data( element, "previousValue", {
    26                 old: null,
    27                 valid: true,
    28                 message: this.defaultMessage( element, { method: method } )
    29             } );
    30         },
    31         
    32         // Cleans up all forms and elements, removes validator-specific events 重置
    33         destroy: function() {
    34             this.resetForm();
    35         
    36             $( this.currentForm )
    37                 .off( ".validate" )
    38                 .removeData( "validator" )
    39                 .find( ".validate-equalTo-blur" )
    40                     .off( ".validate-equalTo" )
    41                     .removeClass( "validate-equalTo-blur" )
    42                 .find( ".validate-lessThan-blur" )
    43                     .off( ".validate-lessThan" )
    44                     .removeClass( "validate-lessThan-blur" )
    45                 .find( ".validate-lessThanEqual-blur" )
    46                     .off( ".validate-lessThanEqual" )
    47                     .removeClass( "validate-lessThanEqual-blur" )
    48                 .find( ".validate-greaterThanEqual-blur" )
    49                     .off( ".validate-greaterThanEqual" )
    50                     .removeClass( "validate-greaterThanEqual-blur" )
    51                 .find( ".validate-greaterThan-blur" )
    52                     .off( ".validate-greaterThan" )
    53                     .removeClass( "validate-greaterThan-blur" );
    54         }
     1 init: function() {
     2     this.labelContainer = $(this.settings.errorLabelContainer);
     3     this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
     4     this.containers = $(this.settings.errorContainer).add(this.settings.errorLabelContainer);
     5     this.submitted = {};
     6     this.valueCache = {};
     7     this.pendingRequest = 0;
     8     this.pending = {};
     9     this.invalid = {};
    10     this.reset();
    11 
    12     var currentForm = this.currentForm,
    13         groups = (this.groups = {}),
    14         rules;
    15     $.each(this.settings.groups, function(key, value) {
    16         if (typeof value === "string") {
    17             value = value.split(/s/);
    18         }
    19         $.each(value, function(index, name) {
    20             groups[name] = key;
    21         });
    22     });
    23     
    24     // 格式化规则
    25     rules = this.settings.rules;
    26     $.each(rules, function(key, value) {
    27         rules[key] = $.validator.normalizeRule(value);
    28     });
    29     
    30     // 事件绑定
    31     function delegate(event) {
    32         var isContentEditable = typeof $(this).attr("contenteditable") !== "undefined" && $(this).attr("contenteditable") !==
    33             "false";
    34 
    35         // Set form expando on contenteditable
    36         if (!this.form && isContentEditable) {
    37             this.form = $(this).closest("form")[0];
    38             this.name = $(this).attr("name");
    39         }
    40 
    41         // Ignore the element if it belongs to another form. This will happen mainly
    42         // when setting the `form` attribute of an input to the id of another form.
    43         if (currentForm !== this.form) {
    44             return;
    45         }
    46 
    47         var validator = $.data(this.form, "validator"),
    48         // 如:onfocusin , 查看onfocusin实现逻辑
    49             eventType = "on" + event.type.replace(/^validate/, ""),
    50             settings = validator.settings;
    51         if (settings[eventType] && !$(this).is(settings.ignore)) {
    52             settings[eventType].call(validator, this, event);
    53         }
    54     }
    55     
    56     // 给未来的元素[ [type=xx]表单元素 ] 绑定事件 onfocusin, onfocusout , onkeyup
    57     $(this.currentForm)
    58         .on("focusin.validate focusout.validate keyup.validate",
    59             ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " +
    60             "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " +
    61             "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " +
    62             "[type='radio'], [type='checkbox'], [contenteditable], [type='button']" delegate)
    63 
    64         // Support: Chrome, oldIE
    65         // "select" is provided as event.target when clicking a option 兼容IE,chrome,绑定click, select事件
    66         .on("click.validate", "select, option, [type='radio'], [type='checkbox']", delegate);
    67 
    68     if (this.settings.invalidHandler) {
    69         $(this.currentForm).on("invalid-form.validate", this.settings.invalidHandler);
    70     }
    71 }

      d. 默认的校验规则函数

      1 // 规则校验函数
      2 
      3         check: function(){
      4                 ...
      5                 result = $.validator.methods[ method ].call( this, val, element, rule.parameters );
      6                 ...
      7         }
      8         
      9          
     10         // https://jqueryvalidation.org/jQuery.validator.methods/
     11         methods: {
     12             
     13             // required 校验规则
     14             required: function( value, element, param ) {
     15         
     16                 // Check if dependency is met    
     17                 if ( !this.depend( param, element ) ) {
     18                     return "dependency-mismatch";
     19                 }
     20                 // 检查元素节点是否是select
     21                 if ( element.nodeName.toLowerCase() === "select" ) {
     22         
     23                     // Could be an array for select-multiple or a string, both are fine this way
     24                     var val = $( element ).val();
     25                     // 已选中至少一个值, 且值长度大于0
     26                     return val && val.length > 0;
     27                 }
     28                 // 检查元素是否是checkable[radio,checkbox]元素 至少一个值
     29                 if ( this.checkable( element ) ) {
     30                     return this.getLength( value, element ) > 0;
     31                 }
     32                 // 检查元素值存在,且值长度大于0
     33                 return value !== undefined && value !== null && value.length > 0;
     34             },
     35         
     36             // email  校验规则
     37             email: function( value, element ) {    
     38         
     39                 // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
     40                 // Retrieved 2014-01-14 
     41                 // If you have a problem with this implementation, report a bug against the above spec
     42                 // Or use custom methods to implement your own email validation
     43                 return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value );
     44             },
     45         
     46             // url校验规则
     47             url: function( value, element ) {
     48         
     49                 // Copyright (c) 2010-2013 Diego Perini, MIT licensed
     50                 // https://gist.github.com/dperini/729294
     51                 // see also https://mathiasbynens.be/demo/url-regex
     52                 // modified to allow protocol-relative URLs
     53                 return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?//)(?:S+(?::S*)?@)?(?:(?!(?:10|127)(?:.d{1,3}){3})(?!(?:169.254|192.168)(?:.d{1,3}){2})(?!172.(?:1[6-9]|2d|3[0-1])(?:.d{1,3}){2})(?:[1-9]d?|1dd|2[01]d|22[0-3])(?:.(?:1?d{1,2}|2[0-4]d|25[0-5])){2}(?:.(?:[1-9]d?|1dd|2[0-4]d|25[0-4]))|(?:(?:[a-zu00a1-uffff0-9]-*)*[a-zu00a1-uffff0-9]+)(?:.(?:[a-zu00a1-uffff0-9]-*)*[a-zu00a1-uffff0-9]+)*(?:.(?:[a-zu00a1-uffff]{2,})).?)(?::d{2,5})?(?:[/?#]S*)?$/i.test( value );
     54             },
     55         
     56             // 日期规则
     57             date: ( function() {
     58                 var called = false;
     59         
     60                 return function( value, element ) {
     61                     if ( !called ) {
     62                         called = true;
     63                         if ( this.settings.debug && window.console ) {
     64                             console.warn(
     65                                 "The `date` method is deprecated and will be removed in version '2.0.0'.
    " +
     66                                 "Please don't use it, since it relies on the Date constructor, which
    " +
     67                                 "behaves very differently across browsers and locales. Use `dateISO`
    " +
     68                                 "instead or one of the locale specific methods in `localizations/`
    " +
     69                                 "and `additional-methods.js`."
     70                             );
     71                         }
     72                     }
     73         
     74                     return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() );
     75                 };
     76             }() ),
     77         
     78             // https://jqueryvalidation.org/dateISO-method/
     79             dateISO: function( value, element ) {
     80                 return this.optional( element ) || /^d{4}[/-](0?[1-9]|1[012])[/-](0?[1-9]|[12][0-9]|3[01])$/.test( value );
     81             },
     82         
     83             // 数字 包括小数
     84             number: function( value, element ) {
     85                 return this.optional( element ) || /^(?:-?d+|-?d{1,3}(?:,d{3})+)?(?:.d+)?$/.test( value );
     86             },
     87         
     88             // 整数
     89             digits: function( value, element ) {
     90                 return this.optional( element ) || /^d+$/.test( value );
     91             },
     92         
     93             // 字符最小长度
     94             minlength: function( value, element, param ) {
     95                 var length = $.isArray( value ) ? value.length : this.getLength( value, element );
     96                 return this.optional( element ) || length >= param;
     97             },
     98         
     99             // 字符最大长度
    100             maxlength: function( value, element, param ) {
    101                 var length = $.isArray( value ) ? value.length : this.getLength( value, element );
    102                 return this.optional( element ) || length <= param;
    103             },
    104         
    105             // 范围长度
    106             rangelength: function( value, element, param ) {
    107                 var length = $.isArray( value ) ? value.length : this.getLength( value, element );
    108                 return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] );
    109             },
    110         
    111             // 最小值
    112             min: function( value, element, param ) {
    113                 return this.optional( element ) || value >= param;
    114             },
    115         
    116             // 最大值
    117             max: function( value, element, param ) {
    118                 return this.optional( element ) || value <= param;
    119             },
    120         
    121             // 范围值
    122             range: function( value, element, param ) {
    123                 return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] );
    124             },
    125         
    126             step: function( value, element, param ) {
    127                 var type = $( element ).attr( "type" ),
    128                     errorMessage = "Step attribute on input type " + type + " is not supported.",
    129                     supportedTypes = [ "text", "number", "range" ],
    130                     re = new RegExp( "\b" + type + "\b" ),
    131                     notSupported = type && !re.test( supportedTypes.join() ),
    132                     decimalPlaces = function( num ) {
    133                         var match = ( "" + num ).match( /(?:.(d+))?$/ );
    134                         if ( !match ) {
    135                             return 0;
    136                         }
    137         
    138                         // Number of digits right of decimal point.
    139                         return match[ 1 ] ? match[ 1 ].length : 0;
    140                     },
    141                     toInt = function( num ) {
    142                         return Math.round( num * Math.pow( 10, decimals ) );
    143                     },
    144                     valid = true,
    145                     decimals;
    146         
    147                 // Works only for text, number and range input types
    148                 // TODO find a way to support input types date, datetime, datetime-local, month, time and week
    149                 if ( notSupported ) {
    150                     throw new Error( errorMessage );
    151                 }
    152         
    153                 decimals = decimalPlaces( param );
    154         
    155                 // Value can't have too many decimals
    156                 if ( decimalPlaces( value ) > decimals || toInt( value ) % toInt( param ) !== 0 ) {
    157                     valid = false;
    158                 }
    159         
    160                 return this.optional( element ) || valid;
    161             },
    162         
    163             // 匹配某个元素
    164             equalTo: function( value, element, param ) {
    165         
    166                 // Bind to the blur event of the target in order to revalidate whenever the target field is updated
    167                 var target = $( param );
    168                 if ( this.settings.onfocusout && target.not( ".validate-equalTo-blur" ).length ) {
    169                     target.addClass( "validate-equalTo-blur" ).on( "blur.validate-equalTo", function() {
    170                         $( element ).valid();
    171                     } );
    172                 }
    173                 return value === target.val();
    174             },
    175         
    176             // 远程校验
    177             remote: function( value, element, param, method ) {
    178                 if ( this.optional( element ) ) {
    179                     return "dependency-mismatch";
    180                 }
    181         
    182                 method = typeof method === "string" && method || "remote";
    183         
    184                 var previous = this.previousValue( element, method ),
    185                     validator, data, optionDataString;
    186         
    187                 if ( !this.settings.messages[ element.name ] ) {
    188                     this.settings.messages[ element.name ] = {};
    189                 }
    190                 previous.originalMessage = previous.originalMessage || this.settings.messages[ element.name ][ method ];
    191                 this.settings.messages[ element.name ][ method ] = previous.message;
    192         
    193                 param = typeof param === "string" && { url: param } || param;
    194                 optionDataString = $.param( $.extend( { data: value }, param.data ) );
    195                 if ( previous.old === optionDataString ) {
    196                     return previous.valid;
    197                 }
    198         
    199                 previous.old = optionDataString;
    200                 validator = this;
    201                 this.startRequest( element );
    202                 data = {};
    203                 data[ element.name ] = value;
    204                 $.ajax( $.extend( true, {
    205                     mode: "abort",
    206                     port: "validate" + element.name,
    207                     dataType: "json",
    208                     data: data,
    209                     context: validator.currentForm,
    210                     success: function( response ) {
    211                         var valid = response === true || response === "true",
    212                             errors, message, submitted;
    213         
    214                         validator.settings.messages[ element.name ][ method ] = previous.originalMessage;
    215                         if ( valid ) {
    216                             submitted = validator.formSubmitted;
    217                             validator.resetInternals();
    218                             validator.toHide = validator.errorsFor( element );
    219                             validator.formSubmitted = submitted;
    220                             validator.successList.push( element );
    221                             validator.invalid[ element.name ] = false;
    222                             validator.showErrors();
    223                         } else {
    224                             errors = {};
    225                             message = response || validator.defaultMessage( element, { method: method, parameters: value } );
    226                             errors[ element.name ] = previous.message = message;
    227                             validator.invalid[ element.name ] = true;
    228                             validator.showErrors( errors );
    229                         }
    230                         previous.valid = valid;
    231                         validator.stopRequest( element, valid );
    232                     }
    233                 }, param ) );
    234                 return "pending";
    235             }
    236         }
      1         
      2         // 添加类规则,如果时字符串,直接赋值,如果是对象,扩展classRuleSettings
      3         addClassRules: function( className, rules ) {
      4             if ( className.constructor === String ) {
      5                 this.classRuleSettings[ className ] = rules;
      6             } else {
      7                 $.extend( this.classRuleSettings, className );
      8             }
      9         },
     10         
     11         // 获取某个元素的class规则
     12         classRules: function( element ) {
     13             var rules = {},
     14                 classes = $( element ).attr( "class" );
     15         
     16             if ( classes ) { 
     17                 $.each( classes.split( " " ), function() {
     18                     if ( this in $.validator.classRuleSettings ) { 
     19                         // 防止重复和无用添加
     20                         $.extend( rules, $.validator.classRuleSettings[ this ] );
     21                     }
     22                 } );
     23             }
     24             return rules;
     25         },
     26         
     27         // 规范化属性规则 由于 html下可以通过属性定义规则
     28         normalizeAttributeRule: function( rules, type, method, value ) {
     29         
     30             // type={ number, range, text} method={min max step}
     31             if ( /min|max|step/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) {
     32                 value = Number( value );
     33         
     34                 // Support Opera Mini, which returns NaN for undefined minlength
     35                 if ( isNaN( value ) ) {
     36                     value = undefined;
     37                 }
     38             }
     39         
     40             if ( value || value === 0 ) {
     41                 rules[ method ] = value;
     42             } else if ( type === method && type !== "range" ) {
     43         
     44                 // Exception: the jquery validate 'range' method
     45                 // does not test for the html5 'range' type
     46                 rules[ method ] = true;
     47             }
     48         },
     49         
     50         // 获取元素的属性规则
     51         attributeRules: function( element ) {
     52             var rules = {},
     53                 $element = $( element ),
     54                 type = element.getAttribute( "type" ),
     55                 method, value;
     56         
     57             for ( method in $.validator.methods ) {
     58         
     59                 // Support for <input required> in both html5 and older browsers
     60                 if ( method === "required" ) {
     61                     value = element.getAttribute( method );
     62         
     63                     // Some browsers return an empty string for the required attribute
     64                     // and non-HTML5 browsers might have required="" markup
     65                     if ( value === "" ) {
     66                         value = true;
     67                     }
     68         
     69                     // Force non-HTML5 browsers to return bool
     70                     value = !!value;
     71                 } else {
     72                     value = $element.attr( method );
     73                 }
     74         
     75                 this.normalizeAttributeRule( rules, type, method, value );
     76             }
     77         
     78             // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs
     79             if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) {
     80                 delete rules.maxlength;
     81             }
     82         
     83             return rules;
     84         },
     85         
     86         // 数据
     87         dataRules: function( element ) {
     88             var rules = {},
     89                 $element = $( element ),
     90                 type = element.getAttribute( "type" ),
     91                 method, value;
     92         
     93             for ( method in $.validator.methods ) {
     94                 value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() );
     95         
     96                 // Cast empty attributes like `data-rule-required` to `true`
     97                 if ( value === "" ) {
     98                     value = true;
     99                 }
    100         
    101                 this.normalizeAttributeRule( rules, type, method, value );
    102             }
    103             return rules;
    104         },
    105         
    106         // 静态规则
    107         staticRules: function( element ) {
    108             var rules = {},
    109                 validator = $.data( element.form, "validator" );
    110         
    111             if ( validator.settings.rules ) {
    112                 rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {};
    113             }
    114             return rules;
    115         },
    116         
    117         // 规范化规则及 删除非必需规则
    118         normalizeRules: function( rules, element ) {
    119             // Handle dependency check
    120             $.each( rules, function( prop, val ) {
    121                 // Ignore rule when param is explicitly false, eg. required:false
    122                 if ( val === false ) {
    123                     delete rules[ prop ];
    124                     return;
    125                 }
    126                 if ( val.param || val.depends ) {
    127                     var keepRule = true;
    128                     switch ( typeof val.depends ) {
    129                     case "string":
    130                         keepRule = !!$( val.depends, element.form ).length;
    131                         break;
    132                     case "function":
    133                         keepRule = val.depends.call( element, element );
    134                         break;
    135                     }
    136                     if ( keepRule ) {
    137                         rules[ prop ] = val.param !== undefined ? val.param : true;
    138                     } else {
    139                         $.data( element.form, "validator" ).resetElements( $( element ) );
    140                         delete rules[ prop ];
    141                     }
    142                 }
    143             } );
    144         
    145             // Evaluate parameters
    146             $.each( rules, function( rule, parameter ) {
    147                 rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter;
    148             } );
    149         
    150             // Clean number parameters
    151             $.each( [ "minlength", "maxlength" ], function() {
    152                 if ( rules[ this ] ) {
    153                     rules[ this ] = Number( rules[ this ] );
    154                 }
    155             } );
    156             $.each( [ "rangelength", "range" ], function() {
    157                 var parts;
    158                 if ( rules[ this ] ) {
    159                     if ( $.isArray( rules[ this ] ) ) {
    160                         rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ];
    161                     } else if ( typeof rules[ this ] === "string" ) {
    162                         parts = rules[ this ].replace( /[[]]/g, "" ).split( /[s,]+/ );
    163                         rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ];
    164                     }
    165                 }
    166             } );
    167             
    168             // 
    169             if ( $.validator.autoCreateRanges ) {
    170                 // Auto-create ranges
    171                 if ( rules.min != null && rules.max != null ) {
    172                     rules.range = [ rules.min, rules.max ];
    173                     delete rules.min;
    174                     delete rules.max;
    175                 }
    176                 if ( rules.minlength != null && rules.maxlength != null ) {
    177                     rules.rangelength = [ rules.minlength, rules.maxlength ];
    178                     delete rules.minlength;
    179                     delete rules.maxlength;
    180                 }
    181             }
    182         
    183             return rules;
    184         },
    185         
    186         // 规范化规则,字符串变对象{}
    187         normalizeRule: function( data ) {
    188             if ( typeof data === "string" ) {
    189                 var transformed = {};
    190                 $.each( data.split( /s/ ), function() {
    191                     transformed[ this ] = true;
    192                 } );
    193                 data = transformed;
    194             }
    195             return data;
    196         },
    197         
    198         // 添加自定义的规则
    199         addMethod: function( name, method, message ) {
    200             $.validator.methods[ name ] = method;
    201             $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ];
    202             // 默认参个数 < 3时
    203             if ( method.length < 3 ) {
    204                 $.validator.addClassRules( name, $.validator.normalizeRule( name ) );
    205             }
    206         },

       2.3  扩展jQuery实例

     

      a. 结构分析

      扩展jQuery实例,即可获取通过jQuery实例调用函数

    1 $.extend( $.fn, {
    2     log: function() {
    3         console.info("currVal : " + $(this).val());
    4     }
    5 }); 

    6 $('#kw').log("123");
    
    
    7 VM415:3 currVal : 输入框里的值

      b. 具体函数分析

     1 // 操作规则 cmmand : add, remove
     2         rules: function( command, argument ) {
     3             var element = this[ 0 ],
     4                 isContentEditable = typeof this.attr( "contenteditable" ) !== "undefined" && this.attr( "contenteditable" ) !== "false",
     5                 settings, staticRules, existingRules, data, param, filtered;
     6         
     7             // If nothing is selected, return empty object; can't chain anyway
     8             if ( element == null ) {
     9                 return;
    10             }
    11         
    12             if ( !element.form && isContentEditable ) {
    13                 element.form = this.closest( "form" )[ 0 ];
    14                 element.name = this.attr( "name" );
    15             }
    16         
    17             if ( element.form == null ) {
    18                 return;
    19             }
    20         
    21             if ( command ) {
    22                 settings = $.data( element.form, "validator" ).settings;
    23                 staticRules = settings.rules;
    24                 existingRules = $.validator.staticRules( element );
    25                 switch ( command ) {
    26                 case "add":
    27                     $.extend( existingRules, $.validator.normalizeRule( argument ) );
    28         
    29                     // Remove messages from rules, but allow them to be set separately
    30                     delete existingRules.messages;
    31                     staticRules[ element.name ] = existingRules;
    32                     if ( argument.messages ) {
    33                         settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages );
    34                     }
    35                     break;
    36                 case "remove":
    37                     if ( !argument ) {
    38                         delete staticRules[ element.name ];
    39                         return existingRules;
    40                     }
    41                     filtered = {};
    42                     $.each( argument.split( /s/ ), function( index, method ) {
    43                         filtered[ method ] = existingRules[ method ];
    44                         delete existingRules[ method ];
    45                     } );
    46                     return filtered;
    47                 }
    48             }
    49         
    50             data = $.validator.normalizeRules(
    51             $.extend(
    52                 {},
    53                 $.validator.classRules( element ),
    54                 $.validator.attributeRules( element ),
    55                 $.validator.dataRules( element ),
    56                 $.validator.staticRules( element )
    57             ), element );
    58         
    59             // Make sure required is at front
    60             if ( data.required ) {
    61                 param = data.required;
    62                 delete data.required;
    63                 data = $.extend( { required: param }, data );
    64             }
    65         
    66             // Make sure remote is at back
    67             if ( data.remote ) {
    68                 param = data.remote;
    69                 delete data.remote;
    70                 data = $.extend( data, { remote: param } );
    71             }
    72         
    73             return data;
    74         }
     1 // 校验
     2         valid: function() {
     3             var valid, validator, errorList;
     4             
     5             if ( $( this[ 0 ] ).is( "form" ) ) {
     6                 // 是form元素,直接校验form及其下属元素
     7                 valid = this.validate().form();
     8             } else {
     9                 // 构建当前元素的validator对象,校验当前元素
    10                 errorList = [];
    11                 valid = true;
    12                 validator = $( this[ 0 ].form ).validate();
    13                 this.each( function() {
    14                     valid = validator.element( this ) && valid;
    15                     if ( !valid ) {
    16                         errorList = errorList.concat( validator.errorList );
    17                     }
    18                 } );
    19                 validator.errorList = errorList;
    20             }
    21             return valid;
    22         },
      1 // 校验对象
      2         validate: function( options ) {
      3         
      4             // 如果未选中任何元素,打印日志,直接return
      5             if ( !this.length ) {
      6                 if ( options && options.debug && window.console ) {
      7                     console.warn( "Nothing selected, can't validate, returning nothing." );
      8                 }
      9                 return;
     10             }
     11         
     12             // 检查当前元素是否已经构建过validator对象
     13             var validator = $.data( this[ 0 ], "validator" );
     14             if ( validator ) {
     15                 return validator;
     16             }
     17         
     18             // Add novalidate tag if HTML5.
     19             this.attr( "novalidate", "novalidate" );
     20             
     21             // 创建一个validator对象,new $.validator( options, this[ 0 ] );
     22             validator = new $.validator( options, this[ 0 ] );
     23             //  当前元素 data存储 validator
     24             $.data( this[ 0 ], "validator", validator );
     25             
     26             // 如果配置了onsubmit属性
     27             if ( validator.settings.onsubmit ) {
     28                 // submit绑定点击事件
     29                 this.on( "click.validate", ":submit", function( event ) {
     30                     // Track the used submit button to properly handle scripted
     31                     // submits later. 如果当前事件是提交按纽触发
     32                     validator.submitButton = event.currentTarget;
     33         
     34                     // Allow suppressing validation by adding a cancel class to the submit button
     35                     if ( $( this ).hasClass( "cancel" ) ) {
     36                         validator.cancelSubmit = true;
     37                     }
     38         
     39                     // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button
     40                     if ( $( this ).attr( "formnovalidate" ) !== undefined ) {
     41                         validator.cancelSubmit = true;
     42                     }
     43                 } );
     44         
     45                 // Validate the form on submit
     46                 this.on( "submit.validate", function( event ) {
     47                     if ( validator.settings.debug ) {
     48                         // debug 模式下,无需执行submit
     49                         event.preventDefault();
     50                     }
     51                     
     52                     function handle() {
     53                         var hidden, result;
     54         
     55                         // Insert a hidden input as a replacement for the missing submit button
     56                         // The hidden input is inserted in two cases:
     57                         //   - A user defined a `submitHandler`
     58                         //   - There was a pending request due to `remote` method and `stopRequest()`
     59                         //     was called to submit the form in case it's valid
     60                         if ( validator.submitButton && ( validator.settings.submitHandler || validator.formSubmitted ) ) {
     61                             hidden = $( "<input type='hidden'/>" )
     62                                 .attr( "name", validator.submitButton.name )
     63                                 .val( $( validator.submitButton ).val() )
     64                                 .appendTo( validator.currentForm );
     65                         }
     66         
     67                         if ( validator.settings.submitHandler && !validator.settings.debug ) {
     68                             result = validator.settings.submitHandler.call( validator, validator.currentForm, event );
     69                             if ( hidden ) {
     70         
     71                                 // And clean up afterwards; thanks to no-block-scope, hidden can be referenced
     72                                 hidden.remove();
     73                             }
     74                             if ( result !== undefined ) {
     75                                 return result;
     76                             }
     77                             return false;
     78                         }
     79                         return true;
     80                     }
     81         
     82                     // Prevent submit for invalid forms or custom submit handlers
     83                     if ( validator.cancelSubmit ) {
     84                         validator.cancelSubmit = false;
     85                         return handle();
     86                     }
     87                     if ( validator.form() ) {
     88                         if ( validator.pendingRequest ) {
     89                             validator.formSubmitted = true;
     90                             return false;
     91                         }
     92                         return handle();
     93                     } else {
     94                         validator.focusInvalid();
     95                         return false;
     96                     }
     97                 } );
     98             }
     99         
    100             return validator;
    101         },
    102     
  • 相关阅读:
    AJAX学习笔记
    JQuery 学习笔记-2017.05.22
    十二.GUI
    十一.文件
    十.模块和库
    九.类的进化(魔法方法、特性和迭代器)
    八.异常
    七.类的继承
    六.函数和类
    五.条件、循环和其他语句
  • 原文地址:https://www.cnblogs.com/pengsn/p/14071858.html
Copyright © 2020-2023  润新知