forked from sofish/validator.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidator.js
More file actions
executable file
·345 lines (277 loc) · 12.3 KB
/
validator.js
File metadata and controls
executable file
·345 lines (277 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/*! Simple Validator
* @author: sofish https://github.com/sofish
* @copyright: MIT license */
// 约定:以 /\$\w+/ 表示的字符,比如 $item 表示的是一个 jQuery Object
~function ($) {
var patterns, fields, errorElement, addErrorClass, removeErrorClass, novalidate, validateForm
, validateFields, radios, removeFromUnvalidFields, asyncValidate, linkageValidate
, aorbValidate, validateReturn, unvalidFields = []
// 类型判断
patterns = {
// 当前校验的元素,默认没有,在 `validate()` 方法中传入
// $item: {},
email: function(text){
return /^(?:[a-z0-9]+[_\-+.]?)*[a-z0-9]+@(?:([a-z0-9]+-?)*[a-z0-9]+.)+([a-z]{2,})+$/i.test(text);
},
// 仅支持 8 种类型的 day
// 20120409 | 2012-04-09 | 2012/04/09 | 2012.04.09 | 以上各种无 0 的状况
date: function (text) {
var reg = /^([1-2]\d{3})([-/.])?(1[0-2]|0?[1-9])([-/.])?([1-2]\d|3[01]|0?[1-9])$/
, taste, d;
if (!reg.test(text)) return false;
taste = reg.exec(text);
year = +taste[1], month = +taste[3] - 1, day = +taste[5];
d = new Date(year, month, day);
return year === d.getFullYear() && month === d.getMonth() && day === d.getDate();
},
// 手机:仅中国手机适应;以 1 开头,第二位是 3-9,并且总位数为 11 位数字
mobile: function(text){
return /^1[3-9]\d{9}$/.test(text);
},
// 座机:仅中国座机支持;区号可有 3、4位数并且以 0 开头;电话号不以 0 开头,最 8 位数,最少 7 位数
// 但 400/800 除头开外,适应电话,电话本身是 7 位数
// 0755-29819991 | 0755 29819991 | 400-6927972 | 4006927927 | 800...
tel: function(text){
return /^(?:(?:0\d{2,3}[- ]?[1-9]\d{6,7})|(?:[48]00[- ]?[1-9]\d{6}))$/.test(text);
},
number: function(text){
var min = +this.$item.attr('min')
, max = +this.$item.attr('max')
, result = /^\-?(?:[1-9]\d*|0)(?:[.]\d)?$/.test(text)
, text = +text
, step = +this.$item.attr('step');
// ignore invalid range silently
isNaN(min) && (min = text - 1);
isNaN(max) && (max = text + 1);
// 目前的实现 step 不能小于 0
return result && (isNaN(step) || 0 >= step ?
(text >= min && text <= max) : 0 === (text + min) % step && (text >= min && text <= max));
},
// 判断是否在 min / max 之间
range: function(text){
return this.number(text);
},
// 支持类型:
// http(s)://(username:password@)(www.)domain.(com/co.uk)(/...)
// (s)ftp://(username:password@)domain.com/...
// git://(username:password@)domain.com/...
// irc(6/s)://host:port/... // 需要测试
// afp over TCP/IP: afp://[<user>@]<host>[:<port>][/[<path>]]
// telnet://<user>:<password>@<host>[:<port>/]
// smb://[<user>@]<host>[:<port>][/[<path>]][?<param1>=<value1>[;<param2>=<value2>]]
url: function(text){
var protocols = '((https?|s?ftp|irc[6s]?|git|afp|telnet|smb):\\/\\/)?'
, userInfo = '([a-z0-9]\\w*(\\:[\\S]+)?\\@)?'
, domain = '([a-z0-9]([\\w]*[a-z0-9])*\\.)?[a-z0-9]\\w*\\.[a-z]{2,}(\\.[a-z]{2,})?'
, port = '(:\\d{1,5})?'
, ip = '\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}'
, address = '(\\/\\S*)?'
, domainType = [protocols, userInfo, domain, port, address]
, ipType = [protocols, userInfo, ip, port, address]
, validate
validate = function(type){
return new RegExp('^' + type.join('') + '$', 'i').test(text);
};
return validate(domainType) || validate(ipType);
},
// 密码项目前只是不为空就 ok,可以自定义
password: function(text){
return this.text(text);
},
// radio 根据当前 radio 的 name 属性获取元素,只要 name 相同的这几个元素中有一个 checked,则验证难过
radio: function(){
// TODO: a better way?!
var form = this.$item.parents('form').eq(0)
, identifier = 'input:radio[name=' + this.$item.attr('name') + ']'
, result = false
radios || (radios = $(identifier, form))
// TODO: a faster way?!
radios.each(function(i, item){
if(item.checked && !result) return result = true;
})
return result;
},
// text[notEmpty] 表单项不为空
// [type=text] 也会进这项
text: function(text){
var max = parseInt(this.$item.attr('maxlength'), 10)
, noEmpty
notEmpty = function(text){
return !!text.length && !/^\s+$/.test(text)
}
return isNaN(max) ? notEmpty(text) : notEmpty(text) && text.length <= max;
}
}
// 异步验证
asyncValidate = function($item, klass, isErrorOnParent){
var data = $item.data()
, url = data['url']
, method = data['method'] || 'get'
, key = data['key'] || 'key'
, text = $item.val()
, params = {}
params[key] = text;
$[method](url, params).success(function(isValidate){
var message = isValidate ? 'IM VALIDED' : 'unvalid';
return validateReturn.call(this, $item, klass, isErrorOnParent, message);
}).error(function(){
// 异步错误,供调试用,理论上线上不应该继续运行
});
}
// 二选一:二个项中必须的一个项是已经填
// <input data-aorb="a" >
// <input data-aorb="b" >
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]
, result = 0
result += validateReturn.apply(this, a) ? 0 : 1
result += validateReturn.apply(this, b) ? 0 : 1;
result = result > 0 ? (removeErrorClass.apply(this, a), removeErrorClass.apply(this, b), false) : true;
// 通过则返回 false
return result;
}
// 验证后的返回值
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';
// 返回的错误对象 = {
// $el: {jQuery Element Object} // 当前表单项
// , type: {String} //表单的类型,如 [type=radio]
// , message: {String} // error message,只有两种值
// }
// NOTE: 把 jQuery Object 传到 trigger 方法中作为参数,会变成原生的 DOM Object
return /^(?:unvalid|empty)$/.test(message) ? (ret = {
$el: addErrorClass.call(this, $item, klass, parent)
, type: type
, error: message
}, $item.trigger('after:' + event, $item), ret):
(removeErrorClass.call(this, $item, klass, parent), $item.trigger('after:' + event, $item), false);
}
// 获取待校验的项
fields = function(identifie, form) {
return $(identifie, form);
}
// 校验一个表单项
// 出错时返回一个对象,当前表单项和类型;通过时返回 false
validate = function($item, klass, parent){
var async, aorb, type, val, commonArgs
// 把当前元素放到 patterns 对象中备用
patterns.$item = $item;
type = $item.attr('type');
val = $item.val();
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);
// 所有都最先测试是不是 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(async) return asyncValidate.apply(this, commonArgs);
// 正常验证返回值
return validateReturn.call(this, $item, klass, parent);
}
// 校验表单项
validateFields = function($fields, method, klass, parent) {
// TODO:坐成 delegate 的方式?
var field
$fields.on(method, function(){
// 如果有错误,返回的结果是一个对象,传入 validedFields 可提供更快的 `validateForm`
(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
// 防止 push 重复项
unvalidFields = [];
$fields.each(function(i) {
(field = validate.call(this, $(this), klass, parent)) && unvalidFields.push(field);
})
return validateFields.length ? unvalidFields : false;
}
// 从 unvalidField 中删除
removeFromUnvalidFields = function($item){
var obj, index
// 从 unvalidFields 中删除
obj = $.grep(unvalidFields, function(item) {
return item['$el'] = $item;
})[0];
if(!obj) return;
index = unvalidFields.indexOf(obj);
return unvalidFields.splice(index, 1);
}
// 添加/删除错误 class
// @param `$item` {jQuery Object} 传入的 element
// @param [optional] `klass` {String} 当一个 class 默认值是 `error`
// @param [optional] `parent` {Boolean} 为 true 的时候,class 被添加在当前出错元素的 parentNode 上
errorElement = function($item, parent){
return $item.data('parent') ? $item.closest($item.data('parent')) : parent ? $item.parent() : $item;
}
addErrorClass = function($item, klass, parent){
errorElement($item, parent).addClass(klass)
}
removeErrorClass = function($item, klass, parent){
removeFromUnvalidFields.call(this, $item);
errorElement($item, parent).removeClass(klass)
}
// 添加 `novalidate` 到 form 中,防止浏览器默认的校验(样式不一致并且太丑)
novalidate = function($form){
return $form.attr('novalidate') || $form.attr('novalidate', 'true')
}
// 真正的操作逻辑开始,yayayayayayaya!
// 用法:$form.validator(options)
// 参数:options = {
// identifie: {String}, // 需要校验的表单项,(默认是 `[required]`)
// klass: {String}, // 校验不通过时错误时添加的 class 名(默认是 `error`)
// isErrorOnParent: {Boolean} // 错误出现时 class 放在当前表单项还是(默认是 element 本身)
// method: {String | false}, // 触发表单项校验的方法,当是 false 在点 submit 按钮之前不校验(默认是 `blur`)
// errorCallback(unvalidFields): {Function}, // 出错时的 callback,第一个参数是出错的表单项集合
//
// before: {Function}, // 表单检验之前
// after: {Function}, // 表单校验之后,只有返回 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('submit', function(e){
before.call(this, $items);
validateForm.call(this, $items, method, klass, isErrorOnParent);
// 当指定 options.after 的时候,只有当 after 返回 true 表单才会提交
return after.call(this, $items) && unvalidFields.length === 0 ? true : e.preventDefault(), errorCallback.call(this, unvalidFields);
})
}
}(jQuery);