From b9b21cc072ad83b0e615dfc82177708d9b71a69a Mon Sep 17 00:00:00 2001 From: Chris Yip Date: Tue, 16 Oct 2012 22:18:22 +0800 Subject: [PATCH 1/3] more reliable text validator. --- validator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/validator.js b/validator.js index 99673b0..64a4858 100644 --- a/validator.js +++ b/validator.js @@ -116,14 +116,14 @@ // text[notEmpty] 表单项不为空 // [type=text] 也会进一项 text: function(text){ - var max = this.$item.attr('maxlength') + var max = parseInt(this.$item.attr('maxlength'), 10) , noEmpty - notEmpty = function(text){ + notEmpty = function(text){ return !!text.length && !/^\s+$/.test(text) } - - return max ? notEmpty(text) && text.length <= max : notEmpty(text); + + return isNaN(max) ? notEmpty(text) : notEmpty(text) && text.length <= max; } } From e60939efd16bf205982117b5740b2352cd8d71d1 Mon Sep 17 00:00:00 2001 From: Chris Yip Date: Sun, 21 Oct 2012 18:11:39 +0800 Subject: [PATCH 2/3] support multiple-choice validation --- examples/index.html | 20 ++++++++- validator.js | 105 ++++++++++++++++++++++++++++---------------- 2 files changed, 86 insertions(+), 39 deletions(-) diff --git a/examples/index.html b/examples/index.html index e0f655a..19f8933 100644 --- a/examples/index.html +++ b/examples/index.html @@ -62,11 +62,27 @@

# form validator

- +
- + +
+

+

+ + + + + + +

+ + + +

diff --git a/validator.js b/validator.js index 626557f..dc26cdb 100644 --- a/validator.js +++ b/validator.js @@ -6,8 +6,8 @@ ~function ($) { var patterns, fields, addErrorClass, novalidate, validateForm, validateFields, radios - , removeFromUnvalidFields, asyncValidate, linkageValidate, aorbValidate, validateReturn - , unvalidFields = [] + , removeFromUnvalidFields, asyncValidate, linkageValidate, packageErrorObject, validateReturn + , limitsValidate, unvalidFields = [] // 类型判断 patterns = { @@ -115,6 +115,11 @@ return result; }, + // 目前只检验是否已选,多选需结合 limitsValidate + checkbox: function(){ + return this.$item.attr('checked'); + }, + // text[notEmpty] 表单项不为空 // [type=text] 也会进这项 text: function(text){ @@ -144,31 +149,56 @@ isValidate ? (removeErrorClass.call(this, $item, klass, isErrorOnParent), false) : unvalidFields.push({ $el: addErrorClass.call(this, $item, klass, isErrorOnParent) , type: $item.attr('type') || 'text' - , message: 'unvaild' + , message: 'unvalid' }) }).error(function(){ // 异步错误,供调试用,理论上线上不应该继续运行 }); } - - // 二选一:二个项中必须的一个项是已经填 - // - // - aorbValidate = function($item, klass, isErrorOnParent){ - var id = $item.data('aorb') === 'a' ? 'b' : 'a' - , $pair = $('[data-aorb=' + id + ']', $item.parents('form').eq(0)) - , a = [$item, klass, isErrorOnParent] - , b = [$pair, klass, isErrorOnParent] + // 把有相同 name 属性的元素编为一组,判断组内所有合法元素数量是否在范围内 + // limits: String, 'min,max', 'min,' 只有最小值,',max' 只有最大值,'n' 则只允许有 n 个 + limitsValidate = function($item, klass, parent){ + var $items = $item.closest('form').find('[type=' + $item.attr('type') + '][name="' + $item.attr('name') + '"]') + , limits = $item.attr('data-limits') + , taste = /^(\d)*( *, *)*(\d)*$/.exec(limits) || [] + , min = parseInt(taste[1], 10) + , max = parseInt(taste[3], 10) , result = 0 + , _unvalidFields = [] + , message, field + + $.each($items, function(idx, item){ + result += validate.apply(this, [$(item), klass, parent]) ? 0 : 1 + }) + + isNaN(min) && (min = result) + isNaN(max) && (max = result) + null == taste[2] && (max = min) - result += validateReturn.apply(this, a) ? 0 : 1 - result += validateReturn.apply(this, b) ? 0 : 1; + // FIXME: different error messages for different elements + ;(min > result || result > max) && (message = 'unvalid') - result = result > 0 ? (removeErrorClass.apply(this, a), removeErrorClass.apply(this, b), false) : true; + // TODO: better way to handle error elements + $.each($items, function(){ + field = packageErrorObject.apply(this, [$(this), klass, parent, this.type, message]) + field && _unvalidFields.push(field) + }) + + return _unvalidFields; + } - // 通过则返回 false - return result; + // 返回的错误对象 = { + // $el: {jQuery Element Object} // 当前表单项 + // , type: {String} //表单的类型,如 [type=radio] + // , message: {String} // error message,只有两种值 + // } + packageErrorObject = function($item, klass, parent, type, message){ + return /^(?:unvalid|empty)$/.test(message) ? { + $el: addErrorClass.call(this, $item, klass, parent) + , type: type + , error: message + } : (removeErrorClass.call(this, $item, klass, parent), false); } // 验证后的返回值 @@ -188,16 +218,7 @@ pattern ? (new RegExp(pattern).test(val) || 'unvalid') : patterns[type](val) || 'unvalid'; - // 返回的错误对象 = { - // $el: {jQuery Element Object} // 当前表单项 - // , type: {String} //表单的类型,如 [type=radio] - // , message: {String} // error message,只有两种值 - // } - return /^(?:unvalid|empty)$/.test(message) ? { - $el: addErrorClass.call(this, $item, klass, parent) - , type: type - , error: message - } : (removeErrorClass.call(this, $item, klass, parent), false); + return packageErrorObject.apply(this, [$item, klass, parent, type, message]); } // 获取待校验的项 @@ -208,7 +229,7 @@ // 校验一个表单项 // 出错时返回一个对象,当前表单项和类型;通过时返回 false validate = function($item, klass, parent){ - var async, linkage, aorb, type, val, commonArgs + var async, linkage, type, val, commonArgs // 把当前元素放到 patterns 对象中备用 patterns.$item = $item; @@ -217,19 +238,15 @@ async = $item.attr('data-url'); linkage = $item.attr('data-linkage'); - aorb = $item.attr('data-aorb'); commonArgs = [$item, klass, parent] // 所有都最先测试是不是 empty,checkbox 是可以有值 // 但通过来说我们更需要的是 checked 的状态 // 暂时去掉 radio/checkbox/linkage/aorb 的 notEmpty 检测 - if(!(/^(?:radio|checkbox)$/.test(type) || aorb || linkage) && !patterns['text'](val)) + if(!(/^(?:radio|checkbox)$/.test(type) || linkage) && !patterns['text'](val)) return validateReturn.call(this, $item, klass, parent, 'empty') - // 二选一验证:有可能为空 - if(aorb) return aorbValidate.apply(this, commonArgs); - // 联动验证 if(linkage) return linkageValidate.apply(this, commonArgs); @@ -246,22 +263,36 @@ var field $fields.on(method, function(){ // 如果有错误,返回的结果是一个对象,传入 validedFields 可提供更快的 `validateForm` - (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field); + if ($(this).attr('data-limits')) { + unvalidFields = unvalidFields.concat(limitsValidate.apply(this, [$(this), klass, parent])) + } else { + (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field); + } }) } // 校验表单:表单通过时返回 false,不然返回所有出错的对象 validateForm = function ($fields, method, klass, parent) { if(method && !validateFields.length) return true; - var field + var field, groups = {} // 防止 push 重复项 unvalidFields = []; - $fields.each(function(i) { - (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field); + $fields.each(function() { + if ($(this).attr('data-limits')) { + groups[this.name] instanceof Array || (groups[this.name] = []) + if (1 < groups[this.name].length) return; + groups[this.name].push(this) + } else { + (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field) + } }) + for (var k in groups) { + unvalidFields = unvalidFields.concat(limitsValidate.apply(this, [$(groups[k]), klass, parent])) + } + // FIXME: Checkbox demo 2: validateFields.length alway equal true, can't return invalid elements return validateFields.length ? unvalidFields : false; } From bdf3f8a08d7566b82e91a5cea04cab7670e43f46 Mon Sep 17 00:00:00 2001 From: Chris Yip Date: Tue, 30 Oct 2012 01:21:47 +0800 Subject: [PATCH 3/3] refactor code. --- examples/index.html | 4 +-- validator.js | 88 +++++++++++++++++---------------------------- 2 files changed, 33 insertions(+), 59 deletions(-) diff --git a/examples/index.html b/examples/index.html index 255ae0a..3d8274f 100755 --- a/examples/index.html +++ b/examples/index.html @@ -65,8 +65,6 @@

# form validator


-
-

# form validator

-

+

diff --git a/validator.js b/validator.js index 57d6412..9dd223f 100755 --- a/validator.js +++ b/validator.js @@ -117,7 +117,7 @@ // 目前只检验是否已选,多选需结合 limitsValidate checkbox: function(){ - return this.$item.attr('checked'); + return !!this.$item.attr('checked'); }, // text[notEmpty] 表单项不为空 @@ -166,56 +166,26 @@ , message, field $.each($items, function(idx, item){ - result += validate.apply(this, [$(item), klass, parent]) ? 0 : 1 + result += true === validate.apply(this, [$(item), klass, parent]) ? 1 : 0 }) - isNaN(min) && (min = result) - isNaN(max) && (max = result) - null == taste[2] && (max = min) - + isNaN(min) && (min = 0) + ;(isNaN(max) || !taste[2]) && (max = result) // FIXME: different error messages for different elements ;(min > result || result > max) && (message = 'unvalid') // TODO: better way to handle error elements $.each($items, function(){ - field = packageErrorObject.apply(this, [$(this), klass, parent, this.type, message]) + field = validateReturn.apply(this, [$(this), klass, parent, message]) field && _unvalidFields.push(field) }) return _unvalidFields; } - // 返回的错误对象 = { - // $el: {jQuery Element Object} // 当前表单项 - // , type: {String} //表单的类型,如 [type=radio] - // , message: {String} // error message,只有两种值 - // } - packageErrorObject = function($item, klass, parent, type, message){ - return /^(?:unvalid|empty)$/.test(message) ? { - $el: addErrorClass.call(this, $item, klass, parent) - , type: type - , error: message - } : (removeErrorClass.call(this, $item, klass, parent), false); - } - // 验证后的返回值 validateReturn = function($item, klass, parent, message){ - - if(!$item) return 'DONT VALIDATE UNEXIST ELEMENT'; - - var pattern, type, val, ret - - pattern = $item.attr('pattern'); - type = $item.attr('type') || 'text'; - val = $item.val().trim(); - event = $item.data('event'); - - // HTML5 pattern 支持 - // TODO: new 出来的这个正则是否与浏览器一致? - message = message ? message : - pattern ? (new RegExp(pattern).test(val) || 'unvalid') : - patterns[type](val) || 'unvalid'; - + var ret // 返回的错误对象 = { // $el: {jQuery Element Object} // 当前表单项 // , type: {String} //表单的类型,如 [type=radio] @@ -224,10 +194,10 @@ // NOTE: 把 jQuery Object 传到 trigger 方法中作为参数,会变成原生的 DOM Object return /^(?:unvalid|empty)$/.test(message) ? (ret = { $el: addErrorClass.call(this, $item, klass, parent) - , type: type + , type: $item.attr('type') || 'text' , error: message - }, $item.trigger('after:' + event, $item), ret): - (removeErrorClass.call(this, $item, klass, parent), $item.trigger('after:' + event, $item), false); + }, $item.trigger('after:' + $item.data('event'), $item), ret): + (removeErrorClass.call(this, $item, klass, parent), $item.trigger('after:' + $item.data('event'), $item), false); } // 获取待校验的项 @@ -238,37 +208,42 @@ // 校验一个表单项 // 出错时返回一个对象,当前表单项和类型;通过时返回 false validate = function($item, klass, parent){ - var async, aorb, type, val, commonArgs - // 把当前元素放到 patterns 对象中备用 - patterns.$item = $item; - type = $item.attr('type'); - val = $item.val(); + if(!$item) return 'DONT VALIDATE UNEXIST ELEMENT'; + + var async, type, val, limits, pattern, message async = $item.data('url'); - aorb = $item.data('aorb'); event = $item.data('event'); - commonArgs = [$item, klass, parent] - // 当指定 `data-event` 的时候在检测前触发自定义事件 // NOTE: 把 jQuery Object 传到 trigger 方法中作为参数,会变成原生的 DOM Object event && $item.trigger('before:' + event, $item); + // 异步验证则不进行普通验证 + if(async) return asyncValidate.apply(this, [$item, klass, parent]); + + // 把当前元素放到 patterns 对象中备用 + patterns.$item = $item; + type = $item.attr('type') || 'text'; + val = $item.val(); + limits = $item.attr('data-limits') || false + // 所有都最先测试是不是 empty,checkbox 是可以有值 // 但通过来说我们更需要的是 checked 的状态 // 暂时去掉 radio/checkbox/linkage/aorb 的 notEmpty 检测 - if(!(/^(?:radio|checkbox)$/.test(type) || aorb) && !patterns['text'](val)) - return validateReturn.call(this, $item, klass, parent, 'empty') - - // 二选一验证:有可能为空 - if(aorb) return aorbValidate.apply(this, commonArgs); + if (!limits && !/^(?:radio|checkbox)$/.test(type) && !patterns['text'](val)) + return validateReturn.call(this, $item, klass, parent, 'empty'); - // 异步验证则不进行普通验证 - if(async) return asyncValidate.apply(this, commonArgs); + pattern = $item.attr('pattern'); + // HTML5 pattern 支持 + // TODO: new 出来的这个正则是否与浏览器一致? + message = message ? message : + pattern ? (new RegExp(pattern).test(val) || 'unvalid') : + patterns[type](val) || 'unvalid'; // 正常验证返回值 - return validateReturn.call(this, $item, klass, parent); + return limits ? message : validateReturn.call(this, $item, klass, parent, message); } // 校验表单项 @@ -277,7 +252,7 @@ var field $fields.on(method, function(){ // 如果有错误,返回的结果是一个对象,传入 validedFields 可提供更快的 `validateForm` - if ($(this).attr('data-limits')) { + if (this.getAttribute('data-limits')) { unvalidFields = unvalidFields.concat(limitsValidate.apply(this, [$(this), klass, parent])) } else { (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field); @@ -375,6 +350,7 @@ // 提交校验 $form.on('submit', function(e){ + e.preventDefault() before.call(this, $items); validateForm.call(this, $items, method, klass, isErrorOnParent);