diff --git a/README.md b/README.md index 7e21977..cf409d7 100755 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ $('#form_id').validator(options); ```js options = { // 需要校验的表单项,(默认是 `[required]`),支持任何 jQuery 选择器可以选择的标识 - identifie: {String}, + identifier: {String}, // 校验不通过时错误时添加的 class 名(默认是 `error`),当校验为空时,还同时拥有 `empty` 这个 classname klass: {String}, @@ -41,8 +41,27 @@ options = { before: {Function}, // 表单检验之前 after: {Function}, // 表单校验之后,只有 __return true__ 才会提交表单 + allowReturn: {Boolean | true} // 是否禁用表单回车提交,false 禁用 } ``` +全局配置,类似$.ajaxSetup,可以把一些共用配置抽出来,不用每次单独写,比如 errorCallback。setup 放在 validator 实例化之前。 + +```js +$.validatorSetup({ + // 错误出现时 `klass` 放在当前表单项还是父节点(默认是当前表单项) + isErrorOnParent: {Boolean}, + + // 触发表单项校验的方法,当是 false 在点 submit 按钮之前不校验(默认是 `blur`) + method: {String | false}, + + // 出错时的 callback,第一个参数是所有出错表单项集合 + errorCallback(unvalidFields): {Function}, + + before: {Function}, // 表单检验之前 + after: {Function}, // 表单校验之后,只有 __return true__ 才会提交表单 + allowReturn: {Boolean | true} // 是否禁用表单回车提交,false 禁用 +}) +``` ### 二、验证表单 ```js @@ -162,6 +181,22 @@ $('#event').on('after:hello', function(event, element){ ``` +#### 7. 自定义pattern +定义了一个全局 `FormValidator` 对象,里面有 `registerPattern` 和 `unRegisterPattern` 两个方法,支持 **自定义pattern**,html中的`data-pattern`被用以识别自定义`pattern`,属性值为`string`。若需要使用 `data-pattern`,则 `$el.attr('pattern')`应为空。自定义方法中传入了当前表单项的值以及当前表单项,自定义方法应该返回Boolean,例: +```html + +``` + +``` javascript + FormValidator.registerPattern("userName", function(val, $el){ + return val > 0 ? true : false + }) + + FormValidator.unRegisterPattern("userName") + + +``` + ## 通用约定和代码规范: - 以 2-spaces 作为缩进 diff --git a/index.html b/index.html index 2ff5f9b..1ca9939 100755 --- a/index.html +++ b/index.html @@ -91,23 +91,47 @@

# form validator

123
+
+

+ +

+ - - +

+ +

+ diff --git a/validator.js b/validator.js index 3d03b4d..42155d5 100755 --- a/validator.js +++ b/validator.js @@ -8,6 +8,17 @@ var patterns, fields, errorElement, addErrorClass, removeErrorClass, novalidate, validateForm , validateFields, radios, removeFromUnvalidFields, asyncValidate, getVal , aorbValidate, validateReturn, unvalidFields = [] + , setupOptions = {} + + window.FormValidator = { + unRegisterPattern: function (name) { + delete patterns[String(name)]; + }, + + registerPattern: function (name, fn) { + patterns[String(name)] = fn; + } + } // 类型判断 patterns = { @@ -15,8 +26,8 @@ // 当前校验的元素,默认没有,在 `validate()` 方法中传入 // $item: {}, - email: function(text){ - return /^(?:[a-z0-9]+[_\-+.]?)*[a-z0-9]+@(?:([a-z0-9]+-?)*[a-z0-9]+.)+([a-z]{2,})+$/i.test(text); + email: function (text) { + return /^(?:[a-z0-9]+[_\-+.]+)*[a-z0-9]+@(?:([a-z0-9]+-?)*[a-z0-9]+.)+([a-z]{2,})+$/i.test(text) }, // 仅支持 8 种类型的 day @@ -49,7 +60,7 @@ number: function(text){ var min = +this.$item.attr('min') , max = +this.$item.attr('max') - , result = /^\-?(?:[1-9]\d*|0)(?:[.]\d)?$/.test(text) + , result = /^\-?(?:[1-9]\d*|0)(?:[.]\d+)?$/.test(text) , text = +text , step = +this.$item.attr('step'); @@ -126,14 +137,23 @@ // [type=text] 也会进这项 text: function(text){ + if(!(text = $.trim(text)).length) return; + var max = parseInt(this.$item.attr('maxlength'), 10) - , noEmpty + , min = parseInt(this.$item.attr('minlength'), 10) + , range - notEmpty = function(text){ - return !!text.length && !/^\s+$/.test(text) + range = function () { + var ret = true + , length = text.length + + if(min) ret = length >= min + if(max) ret = ret && (length <= max) + + return ret } - return isNaN(max) ? notEmpty(text) : notEmpty(text) && text.length <= max; + return range() } } @@ -179,23 +199,22 @@ // 验证后的返回值 validateReturn = function($item, klass, parent, message){ - if(!$item) return 'DONT VALIDATE UNEXIST ELEMENT'; var pattern, type, val, ret, event pattern = $item.attr('pattern'); pattern && pattern.replace('\\', '\\\\'); - type = $item.attr('type') || 'text'; + type = $item.data('pattern') || $item.attr('type') || 'text'; // hack ie: 像 select 和 textarea 返回的 type 都为 NODENAME 而非空 type = patterns[type] ? type : 'text'; - val = $.trim(getVal($item)); + val = getVal($item); event = $item.data('event'); // HTML5 pattern 支持 message = message ? message : pattern ? ((new RegExp(pattern)).test(val) || 'unvalid') : - patterns[type](val) || 'unvalid'; + patterns[type](val, $item) || 'unvalid'; // 返回的错误对象 = { // $el: {jQuery Element Object} // 当前表单项 @@ -203,20 +222,25 @@ // , message: {String} // error message,只有两种值 // } // NOTE: 把 jQuery Object 传到 trigger 方法中作为参数,会变成原生的 DOM Object - if(message === 'unvalid') removeErrorClass($item, klass, parent); + if(message === 'unvalid') (removeErrorClass($item, klass, parent), removeSuccessClass($item, parent)); return /^(?:unvalid|empty)$/.test(message) ? (ret = { $el: addErrorClass.call(this, $item, klass, parent, message) , type: type , error: message }, $item.trigger('after:' + event, $item), ret): - (removeErrorClass.call(this, $item, klass, parent), $item.trigger('after:' + event, $item), false); + (removeErrorClass.call(this, $item, klass, parent), addSuccessClass.call(this, $item, parent), $item.trigger('after:' + event, $item), false); } // 获取待校验的项 - fields = function(identifie, form) { + fields = function(identifie, form) { return $(identifie, form); } + // 校验校验项是否存在 + isExist = function($item, $form) { + return $form.find($item).length ? true : false; + } + // 获取待校验项的值 getVal = function($item){ return $item.val() || ($item.is('[contenteditable]') ? $item.text() : ''); @@ -245,8 +269,9 @@ // 所有都最先测试是不是 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(!(/^(?:radio|checkbox)$/.test(type) || aorb) && !patterns['text'](val)){ + return !!$item.attr("required") ? validateReturn.call(this, $item, klass, parent, val.length ? 'unvalid' : 'empty') : false + } // 二选一验证:有可能为空 if(aorb) return aorbValidate.apply(this, commonArgs); @@ -259,32 +284,41 @@ } // 校验表单项 - validateFields = function($fields, method, klass, parent) { - // TODO:坐成 delegate 的方式? - var reSpecialType = /^radio|checkbox/ - , field - $.each($fields, function(i, f){ - $(f).on(reSpecialType.test(f.type) || "SELECT" === f.tagName ? 'change blur' : method, function(){ - // 如果有错误,返回的结果是一个对象,传入 validedFields 可提供更快的 `validateForm` + validateFields = function($fields, method, klass, parent, identifier) { + // 现在是 delegate 的方式,为了动态地增加校验元素 + var field + + $(this).on('change blur', 'select,radio,checkbox', function(){ + var $items = $(this); + + if ($(this).is(identifier)) { + $items = $('input[type="' + this.type + '"][name="' + this.name + '"]', + $items.closest('form')); + } + $items.each(function(){ + (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field); + }); + }) + + $(this).on(method, identifier, function() { + if (!/^select|radio|checkbox/.test($(this).type)) { var $items = $(this); - if (reSpecialType.test(this.type)) { - $items = $('input[type=' + this.type + '][name=' + this.name + ']', - $items.closest('form')); - } $items.each(function(){ (field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field); }); - }) + } }) } // 校验表单:表单通过时返回 false,不然返回所有出错的对象 - validateForm = function ($fields, method, klass, parent) { + validateForm = function ($fields, method, klass, parent, $form, identifier) { if(method && !validateFields.length) return true; - unvalidFields = $.map($fields, function(el){ - var field = validate.call(null, $(el), klass, parent); - if(field) return field; + unvalidFields = $.map(fields(identifier, $form), function(el){ + if (isExist(el, $form)) { + var field = validate.call(null, $(el), klass, parent); + if(field) return field; + } }) return validateFields.length ? unvalidFields : false; @@ -313,7 +347,16 @@ return $item.data('parent') ? $item.closest($item.data('parent')) : parent ? $item.parent() : $item; } + addSuccessClass = function($item, parent) { + return errorElement($item, parent).addClass("success"); + } + + removeSuccessClass = function($item, parent) { + return errorElement($item, parent).removeClass("success"); + } + addErrorClass = function($item, klass, parent, emptyClass){ + removeSuccessClass($item, parent); return errorElement($item, parent).addClass(klass + ' ' + emptyClass); } @@ -327,10 +370,16 @@ return $form.attr('novalidate') || $form.attr('novalidate', 'true') } - // 真正的操作逻辑开始,yayayayayayaya! + // 禁用 return,防止回车触发 submit + noreturn = function($form){ + $form.on("keydown", function (e) { + if (e.keyCode === 13) return false; + }) + } + // 用法:$form.validator(options) // 参数:options = { - // identifie: {String}, // 需要校验的表单项,(默认是 `[required]`) + // identifier: {String}, // 需要校验的表单项,(默认是 `[required]`) // klass: {String}, // 校验不通过时错误时添加的 class 名(默认是 `error`) // isErrorOnParent: {Boolean} // 错误出现时 class 放在当前表单项还是(默认是 element 本身) // method: {String | false}, // 触发表单项校验的方法,当是 false 在点 submit 按钮之前不校验(默认是 `blur`) @@ -339,41 +388,77 @@ // before: {Function}, // 表单检验之前 // after: {Function}, // 表单校验之后,只有返回 True 表单才可能被提交 // } + + $.validatorSetup = function(options) { + setupOptions.errorCallback = options.errorCallback || null + setupOptions.after = options.after || null + setupOptions.before = options.before || null + setupOptions.isErrorOnParent = options.isErrorOnParent || false + setupOptions.method = options.method === false ? false : 'blur' + setupOptions.allowReturn = options.allowReturn || true + } + $.fn.validator = function(options) { - var $form = this - , options = options || {} - , identifie = options.identifie || '[required]' - , klass = options.error || 'error' - , isErrorOnParent = options.isErrorOnParent || false - , method = options.method || 'blur' - , before = options.before || function() {return true;} - , after = options.after || function() {return true;} - , errorCallback = options.errorCallback || function(fields){} - , $items = fields(identifie, $form) - - // 防止浏览器默认校验 - novalidate($form); - - // 表单项校验 - method && validateFields.call(this, $items, method, klass, isErrorOnParent); - - // 当用户聚焦到某个表单时去除错误提示 - $form.on('focusin', identifie, function(e) { - removeErrorClass.call(this, $(this), 'error unvalid empty', isErrorOnParent); - }) - // 提交校验 - $form.on('submit', function(e){ + if (typeof options === 'string') { + switch (options) { + case 'fields': + return unvalidFields; + } + } - before.call(this, $items); - validateForm.call(this, $items, method, klass, isErrorOnParent); + var options = options || {} + , identifier = options.identifier || '[required]' + , klass = options.klass || 'error' + , isErrorOnParent = options.isErrorOnParent || setupOptions.isErrorOnParent || false + , method = options.method === false ? false : options.method || (setupOptions.method === false ? false : setupOptions.method || 'blur') + , before = options.before || setupOptions.before || function() {return true;} + , after = options.after || setupOptions.after || function() {return true;} + , errorCallback = options.errorCallback || setupOptions.errorCallback || function(fields){} + , allowReturn = options.allowReturn === undefined ? + setupOptions.allowReturn === undefined ? true : setupOptions.allowReturn + : options.allowReturn + + this.each(function(){ + var $form = $(this) + , getItems = function() { + return fields(identifier, $form) + } - // 当指定 options.after 的时候,只有当 after 返回 true 表单才会提交 - return unvalidFields.length ? - (e.preventDefault(), errorCallback.call(this, unvalidFields)) : - (after.call(this, e, $items) && true); - }) + if (!allowReturn) { + noreturn($form) + } + + // 防止浏览器默认校验 + novalidate($form); + + // 表单项校验 + method && validateFields.call(this, getItems, method, klass, isErrorOnParent, identifier); + // 当用户聚焦到某个表单时去除错误提示 + $form.on('focusin', identifier, function(e) { + removeErrorClass.call(this, $(this), 'error unvalid empty', isErrorOnParent); + }) + + // 提交校验 + $form.on('submit', function(e){ + + before.call(this, getItems()); + validateForm.call(this, getItems, method, klass, isErrorOnParent, $form, identifier); + + // 当有未通过验证的表单项时阻止其他 submit 事件触发 + // 当有两个或以上的 submit 存在时, 阻止当前 submit 事件的默认行为 + // 当指定 options.after 的时候,只有当 after 返回 true 表单才会提交 + if (unvalidFields.length) { + e.preventDefault(); + e.stopImmediatePropagation(); + return errorCallback.call(this, unvalidFields); + } else { + if ($._data($form[0], "events").submit.length > 1) e.preventDefault(); + return after.call(this, e, getItems()); + } + }) + }) } }(jQuery);