一、代码主体结构
二、源码分析
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