(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) : typeof define === 'function' && define.amd ? define(['jquery'], factory) : (global.ObjValidation = factory(global.jQuery)); }(this, (function (jQuery) { 'use strict'; jQuery = 'default' in jQuery ? jQuery['default'] : jQuery; function EventObserver(validEvent) { this._observers = {}; this._validEvent = validEvent; } EventObserver.prototype = { _isValidEvent: function _isValidEvent(eventType) { if (!this._validEvent) return true; return this._validEvent.indexOf(eventType) !== -1; }, on: function on(eventType, handler) { if (!this._isValidEvent(eventType)) return; var observers = this._observers; var typeObservers = observers[eventType]; if (!typeObservers) typeObservers = observers[eventType] = []; typeObservers.push(handler); }, once: function once(eventType, handler) { var self = this; var wrap = function wrap() { self.off(eventType, wrap); handler.apply(null, arguments); }; this.on(eventType, wrap); }, off: function off(eventType, handler) { if (!this._isValidEvent(eventType)) return; var observers = this._observers; var typeObservers = observers[eventType]; if (!typeObservers) return; var i = typeObservers.indexOf(handler); if (i === -1) { return; } typeObservers.splice(i, 1); }, fire: function fire(eventType) { if (!this._isValidEvent(eventType)) return; var observers = this._observers; var typeObservers = observers[eventType]; if (!typeObservers) return; var args = Array.prototype.slice.call(arguments).slice(1); typeObservers.forEach(function (handler) { handler.apply(null, args); }); } }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; var __checkers = {}; var __defaultParamOfRule = {}; // @todo // 让checker按顺序号执行,这样的话,可以让远程验证在本地验证成功后再执行 // 错误消息多语言 function Validator(rules, obj, propLabels) { // init event var validEvent = ['pendingStart', 'pendingEnd', 'reset', 'validated']; var _eventObserver = this._eventObserver = new EventObserver(validEvent); this.on = _eventObserver.on.bind(_eventObserver); this.off = _eventObserver.off.bind(_eventObserver); this.once = _eventObserver.once.bind(_eventObserver); this._fire = _eventObserver.fire.bind(_eventObserver); this.validateErrors = {}; this._pendingCount = 0; this._propPendingCount = {}; if (obj) this.setValidateTarget(obj, propLabels); this._validObservers = []; this._inValidObservers = []; this._pendingStartObservers = []; this._pendingEndObservers = []; this._validatedObservers = []; this._resetObservers = []; this.defaultParamOfRule = {}; this.rules = {}; var myCheckers = {}; var checkers = __checkers; if (checkers) { for (var p in checkers) { myCheckers[p] = checkers[p]; } } this.checkers = myCheckers; for (var prop in rules) { this.addRule(prop, rules[prop]); } } Validator.addChecker = function (name, checker) { if ((typeof name === 'undefined' ? 'undefined' : _typeof(name)) === 'object') { for (var p in name) { __checkers[p] = name[p]; } return; } __checkers[name] = checker; }; Validator.setGlobalRuleOption = function (rule, param) { __defaultParamOfRule[rule] = param; }; var validateAllRunning = false; var proto = { setDefaultRuleOption: function setDefaultRuleOption(rule, param) { this.defaultParamOfRule[rule] = param; }, // 设置要验证的对象 setTarget: function setTarget(obj, propLabels) { this.reset(); if (obj) { this._validateTarget = obj; this._propLabels = propLabels; } }, getPropValue: function getPropValue(prop) { return this._validateTarget[prop]; }, isPropNeedCheck: function isPropNeedCheck(prop) { return Object.keys(this._getPropRule(prop)).length > 0; }, _getTarget: function _getTarget() { return this._validateTarget; }, _getCheckerByRule: function _getCheckerByRule(name) { return this.checkers[name]; }, getInvalidProps: function getInvalidProps() { var self = this; var inValidProps = Object.keys(this.validateErrors).filter(function (prop) { return !self.isValid(prop); }); return inValidProps; }, // @todo 当prop为数组时,如何让验证器,验证一次,将相关属性都标记为错误 addRule: function addRule(prop, name, option) { var self = this; if (Array.isArray(prop)) { prop = prop.join(','); } if ((typeof name === 'undefined' ? 'undefined' : _typeof(name)) === 'object') { var map = name; for (var p in map) { this.addRule(prop, p, map[p]); } return; } if (name === 'type') { this._addTypeRule(prop, option); } else { this._getPropRule(prop)[name] = option; } }, _clearRules: function _clearRules() { this.rules = {}; }, _addTypeRule: function _addTypeRule(prop, type) { var typeRules = type.rules; for (var rule in typeRules) { this.addRule(prop, rule, typeRules[rule]); } }, _getPropRule: function _getPropRule(prop) { return this.rules[prop] || (this.rules[prop] = {}); }, _addErrorTo: function _addErrorTo(prop, rule, error) { if (arguments.length < 3 && (typeof rule === 'undefined' ? 'undefined' : _typeof(rule)) === 'object') { error = rule; for (var aRule in error) { this._addErrorTo(prop, aRule, error[aRule]); } return; } if (!this.validateErrors[prop]) { this.validateErrors[prop] = {}; } this.validateErrors[prop][rule] = error; }, _clearErrorsFor: function _clearErrorsFor(prop, rule) { if (!rule) { delete this.validateErrors[prop]; } else { var errors = this.validateErrors[prop]; if (errors) { delete errors[rule]; if (Object.keys(errors).length === 0) { delete this.validateErrors[prop]; } } } }, getErrors: function getErrors(prop) { if (!prop) return this._getAllErrors(); var result = []; var errors = this.validateErrors[prop]; if (errors) { for (var rule in errors) { result.push(errors[rule]); } } return result; }, _getAllErrors: function _getAllErrors() { var result = {}; var errors = this.validateErrors; for (var p in errors) { result[p] = this.getErrors(p).join('\n'); } return result; }, // 所有属性验证是否通过 isValid: function isValid() { if (arguments[0]) { return this.isPropValid(arguments[0]); } var count = 0; var errors = this.validateErrors; for (var p in errors) { count += Object.keys(errors[p]).length; } return count === 0; }, isPropValid: function isPropValid(prop) { return !this.validateErrors[prop] || Object.keys(this.validateErrors[prop]).length === 0; }, validate: function validate(prop, callback, option) { var propType = typeof prop === 'undefined' ? 'undefined' : _typeof(prop); if (propType === 'function') { option = callback; callback = prop; prop = null; } else if (propType === 'object') { option = prop; prop = callback = null; } if (callback && (typeof callback === 'undefined' ? 'undefined' : _typeof(callback)) === 'object') { option = callback; callback = null; } if (!option) option = {}; if (prop) { return this._validateProp(prop, callback, option); } else { return this._validateAll(callback, option); } }, _validateAll: function _validateAll(callback, option) { var checkFully = option.checkFully; if (validateAllRunning) return; validateAllRunning = true; if (typeof checkFully === 'function') { callback = checkFully; checkFully = false; } var self = this; self.reset(); if (callback) self.once('validated', callback); Object.keys(self.rules).forEach(function (prop) { self._validatePropExp(prop, null, option); }); if (self._pendingCount === 0) { var result = self.isValid(); validateAllRunning = false; self._fire('validated', result, self.getErrors()); return result; } else { return 'pending'; } }, onPending: function onPending(startObserver, endObserver) { if (startObserver) this.on('pendingStart', startObserver); if (endObserver) this.on('pendingEnd', endObserver); }, reset: function reset() { this.validateErrors = {}; this._pendingCount = 0; this._propPendingCount = {}; this._fire('reset'); }, _countingPending: function _countingPending(props) { var self = this; if (self._pendingCount === 0) { self._fire('pendingStart'); } props.forEach(function (p) { if (!self._propPendingCount[p]) { self._propPendingCount[p] = 1; } else { self._propPendingCount[p]++; } self._pendingCount++; }); }, _getSortedRuleNames: function _getSortedRuleNames(rules) { var ruleNames = Object.keys(rules); if (rules.remote) { var remoteAt = ruleNames.indexOf('remote'); if (remoteAt !== ruleNames.length - 1) { ruleNames.splice(remoteAt, 1); ruleNames.push('remote'); } } return ruleNames; }, _mergeRuleDefaultParam: function _mergeRuleDefaultParam(rule, param) { var self = this; if (param && Object.prototype.toString.call(param) === '[object Object]') { var globalDefault = __defaultParamOfRule[rule] || {}; var defaultParam = self.defaultParamOfRule[rule] || {}; param = this._deepMerge({}, globalDefault, defaultParam, param); } return param; }, _wrapCallback: function _wrapCallback(props, rule, callback) { var self = this; return function (result) { self._pendingCount--; //props: p1+p2, 向rule相关所有属性添加错误 props.forEach(function (p) { self._propPendingCount[p]--; if (result !== true) { self._addErrorTo(p, rule, result); } if (self._propPendingCount[p] === 0) { if (self._pendingCount === 0) { var isValid = self.isValid(); self._fire('pendingEnd', isValid); if (validateAllRunning) { validateAllRunning = false; self._fire('validated', isValid, self.getErrors()); } } if (callback) callback(self.isValid(p), self.getErrors(p)); } }); }; }, _getAllRuleKeyOfProp: function _getAllRuleKeyOfProp(prop, includeRelated) { var simpleExps = []; var plusExps = []; var rules = this.rules; if (rules[prop]) { simpleExps.push(prop); } for (var exp in this.rules) { if (this._isGroupExp(exp)) { var names = this._parseGroupProps(exp); var i = names.indexOf(prop); if (i !== -1) { plusExps.push(exp); } } } return simpleExps.concat(plusExps); }, _isGroupExp: function _isGroupExp(exp) { return exp.indexOf(',') !== -1; }, _parseGroupProps: function _parseGroupProps(exp) { return exp.split(',').map(function (p) { return p.trim(); }); }, getRelatedProps: function getRelatedProps(prop) { var simpleExps = []; var rules = this.rules; for (var exp in this.rules) { if (this._isGroupExp(exp)) { var names = this._parseGroupProps(exp); var i = names.indexOf(prop); if (i !== -1) { names.splice(i, 1); names.forEach(function (n) { if (simpleExps.indexOf(n) === -1) { simpleExps.push(n); } }); } } } return simpleExps; }, _validateProp: function _validateProp(prop, callback, option) { var self = this; var checkFully = option.checkFully; this._clearErrorsFor(prop); var propExps = this._getAllRuleKeyOfProp(prop); var len = propExps.length; if (!len) return; // clear related prop error propExps.forEach(function (exp) { var props = self._parseGroupProps(exp); if (props.length > 1 && props[0] !== prop) { (function () { var rules = Object.keys(self.rules[exp]); props.forEach(function (p) { if (p !== prop) { // except self rules.forEach(function (r) { return self._clearErrorsFor(p, r); }); } }); })(); } }); if (len === 1) { return this._validatePropExp(propExps[0], callback, option); } var wrapCb = callback; if (callback && len > 1) { wrapCb = function wrapCb() { var isValid = self.isValid(prop); if (!isValid && !checkFully) { return callback(isValid, self.getErrors(prop)); } len--; if (len === 0) { callback(isValid, self.getErrors(prop)); } }; } var hasPending = false; for (var i = 0, l = propExps.length; i < l; i++) { var result = this._validatePropExp(propExps[i], wrapCb, option); if (result === true) continue; if (result === 'pending' && !hasPending) { hasPending = 'pending'; continue; } if (!checkFully) { return false; } } return hasPending || self.isValid(prop); }, _checkRule: function _checkRule(props, rule, param, callback) { var self = this; if (rule === 'type') return true; // get value var value; if (props.length > 1) { value = props.map(function (p) { return self.getPropValue(p); }); } else { value = self.getPropValue(props[0]); if (rule !== 'required' && (value === '' || value === null || value === undefined)) return true; } var checker = self._getCheckerByRule(rule); //是自定义的checker, rule name也是自定义的 if (!checker && param) { var pt = typeof param === 'undefined' ? 'undefined' : _typeof(param); if (pt === 'function') { checker = param; param = undefined; } else if (pt === 'object' && param.checker) { //validator.addRule('p1,p2', 'check_p1_p2_sum', {checker: function(){...}, message: 'xxx'} ) checker = param.checker; } } if (!checker) return true; // merge param param = self._mergeRuleDefaultParam(rule, param); if (param && param.checker) delete param.checker; var wrapCb = self._wrapCallback(props, rule, callback); if (param && param.markRelatedProps) { props.forEach(function (p) { self._clearErrorsFor(p, rule); }); } else { self._clearErrorsFor(props[0], rule); } var context = self._getTarget(); var localeLabels = self._propLabels; var labels = props; if (localeLabels) { labels = props.map(function (p) { return localeLabels[p] || p; }); } var result = checker.apply(context, [value, param, wrapCb, props, labels]); return result; }, //验证某个属性,callback仅用于异步验证器的回调,全是同步验证器的话,返回值即是验证结果 _validatePropExp: function _validatePropExp(prop, callback, option) { var checkFully = option.checkFully; var self = this, props = self._parseGroupProps(prop), rules = self.rules[prop], errorsCount = 0; if (!rules) { if (callback) callback(true); return true; } // 把remote放到队尾 var ruleNames = self._getSortedRuleNames(rules); for (var i = 0, l = ruleNames.length; i < l; i++) { var rule = ruleNames[i]; var param = rules[rule]; var result = this._checkRule(props, rule, param, callback); if (result === true) continue; // counting pending if (result === 'pending') { self._countingPending(props); } else { // result is error message if (result) { errorsCount++; if (param && param.markRelatedProps) { props.forEach(function (p) { self._addErrorTo(p, rule, result); }); } else { self._addErrorTo(props[0], rule, result); } if (!checkFully) { break; } } } } var valid = errorsCount === 0; if (self._propPendingCount[props[0]] > 0) { return 'pending'; } if (callback) { callback(valid, self.getErrors(prop)); } return valid; }, _deepMerge: function _deepMerge(object) { var source, key, srcValue, objValue; var isValidObj = function isValidObj(o) { return o && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === 'object'; }; for (var i = 1; i < arguments.length; i++) { source = arguments[i]; for (key in source) { srcValue = source[key]; objValue = object[key]; if (isValidObj(srcValue) && isValidObj(objValue)) { this._deepMerge(objValue, srcValue); } else { object[key] = srcValue; } } } return object; } }; // 兼容之前的版本 proto.setValidateTarget = proto.setTarget; proto.hasRule = proto.isPropNeedCheck; proto.getProp = proto.getPropValue; proto.getContext = proto._getTarget; proto.setDefaultParamForRule = proto.setDefaultRuleOption; Validator.setDefaultParamForRule = Validator.setGlobalRuleOption; // 兼容旧的事件绑定,解绑 proto.onReset = function (observer) { this.on('reset', observer); }; proto.unReset = function (observer) { this.off('reset', observer); }; proto.onValidatedAll = function (observer) { this.on('validated', observer); }; proto.unValidated = function (observer) { this.off('validated', observer); }; Validator.prototype = proto; var $ = jQuery; function validateForm() { if (this.constructor != validateForm) { return new validateForm.apply(null, arguments); } this.initialize.apply(this, arguments); } var proto$1 = validateForm.prototype; var lastValue; proto$1.initialize = function (form, validator, option) { if (!option) option = {}; var defaults = { immedicate: true, event: 'change', submit: true, validateOnSubmit: false, popupMessage: false, checkFully: true, excludes: '', i18n: function i18n(msg) { return msg; }, alert: window.alert }; for (var p in defaults) { if (!(p in option)) { option[p] = defaults[p]; } } var self = this; var i18n = option.i18n; var myAlert = option.alert; this.errorElementCls = 'validator-error'; this.form = form; this.validator = validator; this.option = option; validator.on('validated', function (isValid) { if (!isValid) { var invalidProps = validator.getInvalidProps(); var alertMsges = []; var popup = option.popupMessage; var msg; if (popup) { invalidProps.forEach(function (prop) { msg = i18n(prop) + ':' + validator.getErrors(prop).join('
'); alertMsges.push(msg); }); } else { invalidProps.forEach(function (prop) { var msges = validator.getErrors(prop); var el = $(form).find('[name=' + prop + ']'); if (el.length) { if (option.excludes) { var exWrap = $(option.excludes)[0]; if (exWrap && $.contains(exWrap, el[0])) return; } self.toggleError(el, false, msges); } else { msg = i18n(prop) + ': ' + msges.join('
'); alertMsges.push(msg); } }); } if (alertMsges.length) myAlert(alertMsges.join('
')); } }); validator.on('reset', function () { $('.has-error', self.form).removeClass('has-error'); $('.' + self.errorElementCls, self.form).remove(); }); if (option.immedicate) { // @todo 仅处理那些声明了验证规则的 $(form).on(option.event, ':input', function () { var el = $(this); if (this.hasAttribute('validelay')) return; // .replace(/ +/g, ',').replace(/,,/g,',') var prop = el.attr('name'); if (!prop) return; if (option.excludes) { var exWrap = $(option.excludes)[0]; if (exWrap && $.contains(exWrap, this)) return; } if (!validator.isPropNeedCheck(prop)) return; if (validator.getPropValue(prop) === lastValue) return; var relatedProps = validator.getRelatedProps(prop); var validateRelated = relatedProps.length > 0; validator.validate(prop, function (isValid) { var msges; if (!isValid) msges = validator.getErrors(prop); self.toggleError(el, isValid, msges); if (validateRelated) { relatedProps.forEach(function (name) { var rpError = validator.getErrors(name); if (name === '') return; var rpEl = $(form).find('[name=' + name + ']'); if (rpEl.length) { self.toggleError(rpEl, !rpError.length, rpError); } }); } }, { checkFully: option.checkFully }); }).on('focus', function () { lastValue = this.value; }); } if (option.submit && $(form).prop('tagName') === 'FORM') { $(form).submit(function (event) { if (option.validateOnSubmit) { event.preventDefault(); self.validator.validate(function (isValid) { if (isValid) { //不会带上原来触发submit的button的值 $(form).submit(); } }); } else { if (!validator.isValid()) { event.preventDefault(); } } }); } }; proto$1.toggleError = function (element, valid, msges) { var self = this; self.removeError(element); if (!valid) { self.highlight(element); if (msges) { var errorEl = self.createErrorElement(msges); self.errorPlacement(errorEl, element); } } else { self.unhighlight(element); } }; proto$1.createErrorElement = function (errorMsges) { return $('').addClass('help-block').addClass(this.errorElementCls).html(errorMsges.join('
')); }; proto$1.removeError = function (element) { var errorCls = '.' + this.errorElementCls; if (element.parent('.input-group').length) { element.parent().parent().find(errorCls).remove(); } else { element.parent().find(errorCls).remove(); } }; proto$1.highlight = function (element) { $(element).closest('.form-group').addClass('has-error'); }; proto$1.unhighlight = function (element) { $(element).closest('.form-group').removeClass('has-error'); }; proto$1.errorPlacement = function (error, element) { if (element.parent('.input-group').length) { error.insertAfter(element.parent()); } else { error.insertAfter(element); } }; function format$1(temp) { var data = Array.prototype.slice.call(arguments, 1); for (var i = 0, l = data.length; i < l; i++) { temp = temp.replace(new RegExp('\\{' + i + '\\}', 'g'), data[i]); } return temp; } function utf8Length$1(str) { var s = str.length; for (var i = str.length - 1; i >= 0; i--) { var code = str.charCodeAt(i); if (code > 0x7f && code <= 0x7ff) { s++; } else if (code > 0x7ff && code <= 0xffff) { s += 2; } if (code >= 0xDC00 && code <= 0xDFFF) { i--; } } return s; } function hasValue$1(value) { return value !== undefined && value !== null && value !== ''; } function arrayFrom(arrayLike) { return Array.prototype.slice.call(arrayLike); } var util = { format: format$1, utf8Length: utf8Length$1, hasValue: hasValue$1, arrayFrom: arrayFrom }; var localeDict = {}; var DEFAULT_LOCALE = 'en'; var currLocale = DEFAULT_LOCALE; var currDict = {}; var i18n = { setCurrLocale: function setCurrLocale(locale) { currLocale = locale; currDict = localeDict[currLocale] || {}; }, getLocaleString: function getLocaleString(key) { return currDict[key]; }, addLocale: function addLocale(locale, dict) { var currDict = localeDict[locale]; if (!currDict) currDict = localeDict[locale] = {}; for (var p in dict) { currDict[p] = dict[p]; } this.setCurrLocale(locale); } }; var hasValue = util.hasValue; var utf8Length = util.utf8Length; var format = util.format; function resultMaker(option, msgKey) { return function (valid) { if (valid) return valid; if (option && option.message) return option.message; var msg = i18n.getLocaleString(msgKey); if (arguments.length <= 1) { return msg; } var params = util.arrayFrom(arguments).slice(1); params.unshift(msg); return util.format.apply(null, params); }; } var checkers = { depends: function depends(value, option, callback, props, labels) { var valid = value.slice(1).every(function (v) { return hasValue(v); }); return resultMaker(option, 'depends')(valid, labels[0], labels.slice(1).join(' ')); }, uniq: function uniq(value, option) { var getItem = option.getItem; var checker = option.checker; var list = option.collection || option.getCollection.call(this); var exists = false; if (checker) { exists = list.some(function (item) { return checker(value, item); }); } else { if (getItem) { exists = list.some(function (item) { return getItem(item) === value; }); } else { exists = list.some(function (item) { return value === item; }); } } return resultMaker(option, 'uniq')(!exists); }, required: function required(value, option) { var m = resultMaker(option, 'required'); if (!hasValue(value)) return m(false); if (Array.isArray(value)) { var m2 = resultMaker(option, 'required:array'); return m2(value && value.length > 0); } if (typeof value === 'string') { return m(value.length > 0); } return true; }, chosed: function chosed(value, option) { var unchosedValue = option && option.unchosedValue || -1; return resultMaker(option, 'chosed')(value != unchosedValue); }, email: function email(value, option) { if (/^[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)) { return true; } else { return resultMaker(option, 'email')(false); } }, url: function url(value, option) { // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ if (/^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value)) { return true; } else { resultMaker(option, 'url')(false); } }, date: function date(value, option) { var valid = !/invalid|NaN/.test(new Date(value).toString()); return resultMaker(option, 'url')(valid); }, dateISO: function dateISO(value, option) { var valid = /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value); return resultMaker(option, 'dateISO')(valid); }, number: function number(value, option) { var valid = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value); return resultMaker(option, 'number')(valid); }, digits: function digits(value, option) { var valid = /^\d+$/.test(value); return resultMaker(option, 'digits')(valid); }, decimal: function decimal(value, option) { if (typeof option === 'number') { option = { precision: option }; } var valid = new RegExp('^[0-9,]+(\\.\\d{0,' + option.precision + '})?$').test(value); return resultMaker(option, 'decimal')(valid, option.precision); }, // based on http://en.wikipedia.org/wiki/Luhn/ creditcard: function creditcard(value, option) { var m = resultMaker(option, 'creditcard'); // accept only spaces, digits and dashes if (/[^0-9 \-]+/.test(value)) { return m(false); } var nCheck = 0, nDigit = 0, bEven = false, n, cDigit; value = value.replace(/\D/g, ''); // Basing min and max length on // http://developer.ean.com/general_info/Valid_Credit_Card_Types if (value.length < 13 || value.length > 19) { return m(false); } for (n = value.length - 1; n >= 0; n--) { cDigit = value.charAt(n); nDigit = parseInt(cDigit, 10); if (bEven) { if ((nDigit *= 2) > 9) { nDigit -= 9; } } nCheck += nDigit; bEven = !bEven; } return m(nCheck % 10 === 0); }, length: function length(value, option) { if (typeof option === 'number') { option = { max: option }; } var len = option.utf8Bytes ? utf8Length(value) : value.length; if ('max' in option && 'min' in option) { return resultMaker(option, 'length:between')(len >= option.min && len <= option.max, option.min, option.max); } if ('max' in option) { return resultMaker(option, 'length:max')(len <= option.max, option.max); } else if ('min' in option) { return resultMaker(option, 'length:min')(len >= option.min, option.min); } }, count: function count(value, option) { if (typeof option === 'number') { option = { max: option }; } var valid = false; if (option.max) { valid = value.length <= option.max; } if (valid !== true) return resultMaker(option, 'count:max')(false, option.max); if (option.min) { return resultMaker(option, 'count:min')(value.length >= option.min, option.min); } }, min: function min(value, option) { if (typeof option === 'number') { option = { min: option }; } return resultMaker(option, 'min')(value >= option.min, option.min); }, max: function max(value, option) { if (typeof option === 'number') { option = { max: option }; } return resultMaker(option, 'max')(value <= option.max, option.max); }, range: function range(value, option) { return resultMaker(option, 'range')(value >= option.min && value <= option.max, option.min, option.max); }, async: function async(value, option, callback, props, labels) { option.validate(value, option, callback, props, labels); return 'pending'; }, greaterThan: function greaterThan(value, option) { if (typeof option === 'number') { option = { value: option }; } return resultMaker(option, 'greaterThan')(Number(value) > option.value, option.value); }, lessThan: function lessThan(value, option) { if (typeof option === 'number') { option = { value: option }; } return resultMaker(option, 'lessThan')(Number(value) < option.value, option.value); }, compare: function compare(value, option, callback, props, labels) { if (typeof option === 'string') { option = { operate: option }; } if (!option) option = {}; var p1 = value[0]; var p2 = value[1]; if (!hasValue(p1)) return true; if (!hasValue(p2)) return true; var Type = option.type || Number; p1 = new Type(p1); p2 = new Type(p2); var valid = false; var key = ''; switch (option.operate) { case '>': valid = p1 > p2; key = 'compare:greaterThan'; break; case '>=': valid = p1 >= p2; key = 'compare:greaterThanOrEqual'; break; case '<': valid = p1 < p2; key = 'compare:lessThan'; break; case '<=': valid = p1 <= p2; key = 'compare:lessThanOrEqual'; break; case '=': valid = p1 == p2; key = 'compare:equal'; break; case '!=': valid = p1 != p2; key = 'compare:notEqual'; break; } return resultMaker(option, key)(valid, labels[0], labels[1]); }, pattern: function pattern(value, option) { var regexp = 'regexp' in option ? option.regexp : option; if (typeof regexp === 'string') { regexp = new RegExp('^(?:' + regexp + ')$'); } return resultMaker(option, 'pattern')(regexp.test(value)); }, time: function time(value, option) { var valid = /^([01]\d|2[0-3])(:[0-5]\d){1,2}$/.test(value); return resultMaker(option, 'time')(valid); } }; var vueMixin = { data: function data() { return { validateError: {} }; }, created: function created() { var vm = this; var option = this.$options.validate; if (!option) return; var target = option.target; var vmTarget = vm.$get(target); var labels = option.labels; var validator = option.validator; var rules = option.rules; // rules 优先于 validator if (rules) { validator = new Validator(rules); } else { if (typeof validator === 'function') { validator = validator(); } } vm.$validator = validator; validator.setTarget(vmTarget, labels); // validate prop when changed vm.$watch(target, function (val, oldVal) { validator.setTarget(val, labels); }); function validateProp(watchExp, prop) { vm.$watch(watchExp, function () { validator.validate(prop, function (isValid) { vm.$set('validateError.' + prop, validator.getErrors(prop).join('\n')); // reset related props state var rProps = validator.getRelatedProps(prop); rProps.forEach(function (rprop) { vm.$set('validateError.' + rprop, validator.getErrors(rprop).join('\n')); }); }, { deep: true }); }); } var props = option.targetProps || Object.keys(vmTarget); props.forEach(function (prop) { validateProp(target + '.' + prop, prop); }); // handle validator reset validator.on('reset', function () { vm.validateError = {}; }); validator.on('validated', function (isValid) { if (!isValid) { vm.validateError = validator.getErrors(); } }); }, beforeDestory: function beforeDestory() { if (!this.$validator) return; this.$validator.off('reset', this._onValidatorReset); this.$validator.setTarget(null); } }; /* * Translated default messages for the jQuery validation plugin. * Locale: ZH (Chinese, 中文 (Zhōngwén), 汉语, 漢語) */ var zhLocales = { depends: '{0}依赖{1},请先填写{1}', uniq: '你输入的内容已存在,此项必须唯一', required: '这是必填字段', 'required:array': '请至少输入一项', chosed: '必选项', email: '请输入有效的电子邮件地址', url: '请输入有效的网址', date: '请输入有效的日期', dateISO: '请输入有效的日期 (YYYY-MM-DD)', number: '请输入有效的数字', digits: '请输入正整数', creditcard: '请输入有效的信用卡号码', equalTo: '你的输入不相同', 'length:max': '最多可以输入 {0} 个字符', 'length:min': '最少要输入 {0} 个字符', 'length:between': '请输入长度在 {0} 到 {1} 之间的字符串', 'count:max': '最多包含{0}项', 'count:min': '最少包含{0}项', max: '请输入不大于 {0} 的数值', min: '请输入不小于 {0} 的数值', range: '请输入范围在 {0} 到 {1} 之间的数值', greaterThan: '请输入大于 {0} 的数值', lessThan: '请输入小于 {0} 的数值', extension: '请输入有效的后缀', pattern: '格式无效', time: '请输入有效的时间', 'compare:greaterThan': '{0} 须大于 {1}', 'compare:greaterThanOrEqual': '{0} 须大于等于 {1}', 'compare:lessThan': '{0} 须小于 {1}', 'compare:lessThanOrEqual': '{0} 须小于或等于 {1}', 'compare:equal': '{0} 须等于 {1}', 'compare:notEqual': '{0} 不能等于 {1}' }; var enLocales = { depends: '{0} depends {1}', uniq: 'should be unique', required: 'required', 'required:array': 'should have at least one', chosed: 'required', email: 'invalid email', url: 'invalid url', date: 'invalid date', dateISO: 'invalid date ( ISO )', number: 'invalid number', digits: 'invalid digits', decimal: 'Please enter a correct {0} decimal', creditcard: 'invalid credit card number', 'length:between': 'should between {0} and {1} characters long', 'length:max': 'should at least {0} characters', 'length:min': 'should no more than {0} characters', 'count:max': 'count should no more than {0}', 'count:min': 'count should no less than {0}', min: 'should less than or equal to {0}', max: 'should more than or equal to {0}', range: 'should between {0} and {1}', greaterThan: 'should greater than {0}', lessThan: 'should less than {0}', pattern: 'invalid format', time: 'should between 00:00 and 23:59', 'compare:greaterThan': '{0} should greater than {1}', 'compare:greaterThanOrEqual': '{0} should greater than or equal {1}', 'compare:lessThan': '{0} should less than {1}', 'compare:lessThanOrEqual': '{0} should less than or equal {1}', 'compare:equal': '{0} should equal {1}', 'compare:notEqual': '{0} should not equal {1}' }; var ObjValidation = Validator; // add static member ObjValidation.i18n = i18n; ObjValidation.validateForm = validateForm; ObjValidation.vueMixin = vueMixin; ObjValidation.addChecker(checkers); // i18n i18n.addLocale('zh', zhLocales); i18n.addLocale('en', enLocales); i18n.setCurrLocale('en'); return ObjValidation; })));