UNPKG

async-validate.js

Version:

transform all validator to async or promise

594 lines (534 loc) 18.5 kB
(function(root, factory) { var PromiseProto = null; try { PromiseProto = window.Promise; for(var key in PromiseProto) { break; } }catch(e) { PromiseProto = Promise; } if(typeof define === 'function' && define.amd) { define(function() { return factory(root, PromiseProto); }); } else if(typeof exports === 'object') { module.exports = factory(root, PromiseProto); } else { root.AValidate = factory(root, PromiseProto); } })(this, function(win, Promise) { "use strict"; // polyfill if(!("defer" in Promise) || !Promise.defer) { Object.defineProperty(Promise, 'defer', { writable:true }); Promise.defer = function() { var deferred = {} deferred.promise = new Promise(function (resolve, reject) { deferred.resolve = resolve deferred.reject = reject }); deferred.catch = function(e) { return deferred.promise.catch(e); }; return deferred; }; } if(!("whatever" in Promise)) { Promise.whatever = function(values, format) { var defer = Promise.defer(); var len = values.length; var success = []; var error = []; !format && (format = function(value) { return value; }); function done() { if(error.length) { defer.reject(format(error)); }else { defer.resolve(success); } } function resolve(value, i) { success.push(value); len--; if(len == 0) { done(); } } function reject(message) { error.push(message); len--; if(len == 0) { done(); } } values.forEach(function(item, i) { if(!AValidate._is(item, 'promise')) { resolve(item, i); return; } item.then(function(value) { resolve(value, i); }, function(message) { reject(message); }); }); return defer.promise; }; } // object equal var eq = function(a, b, aStack, bStack) { if (a === b) return a !== 0 || 1 / a == 1 / b; if (a == null || b == null) return a === b; var className = Object.prototype.toString.call(a); if (className != Object.prototype.toString.call(b)) return false; switch (className) { case '[object String]': return a == String(b); case '[object Number]': return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); case '[object Date]': case '[object Boolean]': return +a == +b; case '[object RegExp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } if ( typeof a != 'object' || typeof b != 'object') return false; var length = aStack.length; while (length--) { if (aStack[length] == a) return bStack[length] == b; } var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && ( aCtor instanceof aCtor) && _.isFunction(bCtor) && ( bCtor instanceof bCtor)) && ('constructor' in a && 'constructor' in b)) { return false; } aStack.push(a); bStack.push(b); var size = 0, result = true; if (className == '[object Array]') { size = a.length; result = size == b.length; if (result) { while (size--) { if (!( result = eq(a[size], b[size], aStack, bStack))) break; } } } else { for (var key in a) { if (a.hasOwnProperty(key)) { size++; if (!( result = b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) break; } } if (result) { for (key in b) { if (b.hasOwnProperty(key) && !(size--)) break; } result = !size; } } aStack.pop(); bStack.pop(); return result; }; var AValidate = function(data, config, extra) { return AValidate.sync(data, config, extra); }; AValidate.equal = eq; AValidate.sync = function(config, data, extra) { data = AValidate._serialize(data); var errors = null; var funcs = AValidate._build(data, config, false); for(var i=0;i<funcs.length;i++) { var func = funcs[i]; var result = func.func.call(this, { value:data[func.field], param:func.param, data:data }); var field = func.field; var cond = func.cond; if(!result) { if(!errors) { errors = {}; } if(!errors[field]) { errors[field] = {}; } errors[field][cond] = 'error'; } } return errors; }; AValidate.async = function(config, data, extra) { data = AValidate._serialize(data); var promises = []; var funcs = AValidate._build(data, config, true); var PROMISE_TEMPLATE = [ 'var _this = this;', 'setTimeout(function() {', ' var result = {{condition}};', ' if(result) {', ' resolve();', ' }else {', ' reject();', ' }', '}, 1);' ].join("\n"); funcs.forEach(function(func) { var defer = Promise.defer(); defer._resolve = function(value) { defer.resolve(); }; defer._reject = function(value) { value = value || "error"; defer.reject([func.field, func.cond, value]); }; try { if(func.func && AValidate._is(func.func, 'function') && func.func.__promise__) { func.func.call(AValidate, { value:data[func.field], param:config[func.field][func.cond], data:data }, defer._resolve, defer._reject); }else { new Function( 'opt', 'resolve', 'reject', PROMISE_TEMPLATE.replace( "{{condition}}", "("+(func.cond in AValidate.rules ? "_this.rules." + func.cond : func.func.toString())+")(opt)" ) ).call(AValidate, { value:data[func.field], param:config[func.field][func.cond], data:data }, defer._resolve, defer._reject); } promises.push(defer.promise); }catch(err) { console.log && console.log(func.field, err); } }); var buildPromises = Promise.whatever(promises, function(values) { var data = {}; values.forEach(function(item) { var field = item[0]; var cond = item[1]; var value = item[2]; if(!data[field]) { data[field] = {}; } data[field][cond] = value; }); return data; }); return buildPromises; }; AValidate._serialize = function(data) { if(typeof data == 'object' && (typeof window != 'undefined' && data instanceof window.FormData)) { var result = {}; var entries = data.entries(); var item = null; while(item = entries.next()) { if(item) { if (item.done) { break; }else { result[item.value[0]] = item.value[1]; } }else { break; } } return result; } return data; }; AValidate._type = function(data) { var result = (typeof data).toLowerCase(); // Browser implement the Native Promise if(data instanceof Promise) { return 'promise'; } if(data instanceof Array) { return 'array'; } if(result == 'function') { if(typeof result.then == 'function') { return 'promise'; } } if(result == 'object') { if(data instanceof RegExp) { return 'regexp'; } if(data instanceof Array) { return 'array'; } } return result; }; AValidate._is = function(data, type) { return AValidate._type(data) === type.toLowerCase(); }; AValidate._empty = function(data) { return (AValidate._is(data, 'number') && (isNaN(data) && !isFinite(data))) || (data === null) || (data == undefined) || (data.toString().length == 0); }; AValidate._buildCondition = function(type, value) { var condition = ''; switch(type) { case 'boolean':; case 'number':{ condition = 'opt.value == ' + value; };break; case 'array':{ condition = 'this.equal(opt.value, ' + JSON.stringify(value) + ', [], [])'; };break; case 'string':{ condition = 'opt.value == "' + value + '"'; };break; case 'regexp':{ condition = value.toString() + '.test(opt.value)'; };break; }; return condition; }; AValidate._toFunction = function(key, cond) { var type = AValidate._type(key); if(type == 'promise') { throw new TypeError('use API:AValidate.async to support Promise'); } var func = null; if(cond in AValidate.rules) { return AValidate.rules[cond]; }else { var build = function(type, value) { var condition = AValidate._buildCondition(type, value); return AValidate._is(value, 'function') ? value : new Function("opt", 'return ' + condition); }; func = build(type, key); } return func; }; AValidate._toPromise = function(key, cond) { var build = function(type, value) { var condition = AValidate._buildCondition(type, value); if(AValidate._is(value, 'function')) { value.__promise__ = true; return value; }else { var builder = new Function("opt", 'return ' + condition); builder.__promise__ = false; return builder; } }; var type = AValidate._type(key); var func = null; if(cond in AValidate.rules) { func = AValidate.rules[cond]; }else { func = build(type, key); } return func; }; AValidate._build = function(data, config, isAsync) { var funcs = []; for(var field in config) { if(AValidate._is(config[field], 'object')) { for(var cond in config[field]) { funcs.push({ func:isAsync ? AValidate._toPromise(config[field][cond], cond) : AValidate._toFunction(config[field][cond], cond), param:config[field][cond], field:field, cond:cond }); } }else { funcs.push({ func:isAsync ? AValidate._toPromise(config[field]) : AValidate._toFunction(config[field]), field:field, cond:'error' }); } } return funcs; } AValidate.add = function(key, data, force) { if(key in AValidate.rules) { if(!force) { throw new Error("can't override the rules:" + key); } } if(AValidate._is(data, 'function')) { AValidate.rules[key.toString()] = data; }else { throw new Error("rules must be a function."); } }; AValidate.rules = { required:function(opt) { var value = opt.value,param = opt.param; if(param) { if(AValidate._is(value, 'object')) { for(var key in value) { if(value.hasOwnProperty(key)) return true; } return false; } else { return !AValidate._empty(value); } } return true; }, length:function(opt) { if(AValidate._empty(opt)) { return true; } var value = opt.value,param = opt.param; var is = param ? param.is : undefined; var min = param ? param.min : undefined; var max = param ? param.max : undefined; if(!AValidate._is(param, 'object') && !AValidate._empty(param)) { is = param.toString(); } var data = value + ""; if(!AValidate._empty(is)) { return data.length == is; }else { if(AValidate._is(min, 'number') && AValidate._is(max, 'number')) { return data.length >= min && data.length <= max; } else if(AValidate._is(min, 'number') && AValidate._is(max, 'undefined')) { return data.length >= min; } else if(AValidate._is(min, 'undefined') && AValidate._is(max, 'number')) { return data.length <= max; } else { return true; } } }, number:function(opt) { if(AValidate._empty(opt)) { return true; } var value = opt.value,param = opt.param; if(!AValidate._is(value, 'number') && !AValidate._is(value, 'string')) return false; if(AValidate._is(param, 'number') && !isNaN(param)) { param = {'==': param}; } var operator = { '>':function(num1, num2) { return num1 > num2; }, '>=':function(num1, num2) { return num1 >= num2; }, '==':function(num1, num2) { return num1 == num2; }, '<':function(num1, num2) { return num1 < num2; }, '<=':function(num1, num2) { return num1 <= num2; } }; param = param || {}; var or = param.or || false; var errors = []; var count = 0; for(var key in param) { if(key in operator) { count++; var result = operator[key].call(null, value, param[key]); if(!result) { errors.push(true); } } } if(or) { return errors.length < count; }else { return !errors.length; } }, include:function(opt) { var value = opt.value,param = opt.param; if(AValidate._is(param, 'string')) { param = param.split(""); } if(!AValidate._is(param, 'array')) return true; if(param.indexOf(value) >= 0) { return true; } return false; }, exclude:function(opt) { var value = opt.value,param = opt.param; if(AValidate._is(param, 'string')) { param = param.split(""); } if(!AValidate._is(param, 'array')) return true; if(param.indexOf(value) < 0) { return true; } return false; }, email:function(opt) { var value = opt.value,param = opt.param; if(typeof param == 'undefined' || !param) { return true; } if(!AValidate._is(value, "string")) return false; var pattern = /^[a-z0-9\u007F-\uffff_]+(?:\.[a-z0-9\u007F-\uffff_]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i; return pattern.test(value); }, char:function(opt) { opt = opt || {}; // number, english, symbol, chinese var rules = { "number":"0-9", "symbol":"`~!@#¥%……&*()_-=+[]|;:,.", "english":"a-zA-Z", "chinese":"\u0391-\uFFE5" }; var check_char = ""; for(var key in opt) { if(opt[key] && key in rules) { check_char += rules[key]; } } if(check_char) { var check_reg = new RegExp("^["+check_char+"]+$", "g"); return !!check_reg.test(opt.value); }else { return true; } } }; return AValidate; });