/*! * vue-validator v3.0.0-alpha.2 * (c) 2016 kazuya kawaguchi * Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.VueValidator = factory()); }(this, (function () { 'use strict'; /* */ function warn (msg, err) { if (window.console) { console.warn('[vue-validator] ' + msg); if (err) { console.warn(err.stack); } } } /* */ // validator configrations var validator = { classes: {} }; var Config = function (Vue) { // define Vue.config.validator configration // $FlowFixMe: https://github.com/facebook/flow/issues/285 Object.defineProperty(Vue.config, 'validator', { enumerable: true, configurable: true, get: function () { return validator }, set: function (val) { validator = val; } }); }; /* */ /** * build-in validators */ /** * required * This function validate whether the value has been filled out. */ function required (val, arg) { var isRequired = arg === undefined ? true : arg; if (Array.isArray(val)) { if (val.length !== 0) { var valid = true; for (var i = 0, l = val.length; i < l; i++) { valid = required(val[i], isRequired); if ((isRequired && !valid) || (!isRequired && valid)) { break } } return valid } else { return !isRequired } } else if (typeof val === 'number' || typeof val === 'function') { return isRequired } else if (typeof val === 'boolean') { return val === isRequired } else if (typeof val === 'string') { return isRequired ? (val.length > 0) : (val.length <= 0) } else if (val !== null && typeof val === 'object') { return isRequired ? (Object.keys(val).length > 0) : (Object.keys(val).length <= 0) } else if (val === null || val === undefined) { return !isRequired } else { return !isRequired } } /** * pattern * This function validate whether the value matches the regex pattern */ function pattern (val, pat) { if (typeof pat !== 'string') { return false } var match = pat.match(new RegExp('^/(.*?)/([gimy]*)$')); if (!match) { return false } return new RegExp(match[1], match[2]).test(val) } /** * minlength * This function validate whether the minimum length. */ function minlength (val, min) { if (typeof val === 'string') { return isInteger(min, 10) && val.length >= parseInt(min, 10) } else if (Array.isArray(val)) { return val.length >= parseInt(min, 10) } else { return false } } /** * maxlength * This function validate whether the maximum length. */ function maxlength (val, max) { if (typeof val === 'string') { return isInteger(max, 10) && val.length <= parseInt(max, 10) } else if (Array.isArray(val)) { return val.length <= parseInt(max, 10) } else { return false } } /** * min * This function validate whether the minimum value of the numberable value. */ function min (val, arg) { return !isNaN(+(val)) && !isNaN(+(arg)) && (+(val) >= +(arg)) } /** * max * This function validate whether the maximum value of the numberable value. */ function max (val, arg) { return !isNaN(+(val)) && !isNaN(+(arg)) && (+(val) <= +(arg)) } /** * isInteger * This function check whether the value of the string is integer. */ function isInteger (val) { return /^(-?[1-9]\d*|0)$/.test(val) } var validators = Object.freeze({ required: required, pattern: pattern, minlength: minlength, maxlength: maxlength, min: min, max: max }); /* */ var Asset = function (Vue) { var ref = Vue.util; var extend = ref.extend; // set global validators asset var assets = Object.create(null); extend(assets, validators); Vue.options.validators = assets; // set option merge strategy var strats = Vue.config.optionMergeStrategies; if (strats) { strats.validators = function (parent, child) { if (!child) { return parent } if (!parent) { return child } var ret = Object.create(null); extend(ret, parent); var key; for (key in child) { ret[key] = child[key]; } return ret }; } /** * Register or retrieve a global validator definition. */ function validator ( id, def ) { if (def === undefined) { return Vue.options['validators'][id] } else { Vue.options['validators'][id] = def; if (def === null) { delete Vue.options['validators']['id']; } } } Vue['validator'] = validator; }; /* */ var Group = function (Vue) { var ref = Vue.util; var extend = ref.extend; return { data: function data () { return { valid: true, dirty: false, touched: false, modified: false, results: {} } }, computed: { invalid: function invalid () { return !this.valid }, pristine: function pristine () { return !this.dirty }, untouched: function untouched () { return !this.touched }, result: function result () { var ret = { valid: this.valid, invalid: this.invalid, dirty: this.dirty, pristine: this.pristine, touched: this.touched, untouched: this.untouched, modified: this.modified }; var results = this.results; this._validityKeys.forEach(function (key) { ret[key] = results[key]; if (ret[key].errors) { var errors = ret.errors || []; ret[key].errors.forEach(function (error) { errors.push(error); }); ret.errors = errors; } }); return ret } }, watch: { results: function results (val, old) { var keys = this._validityKeys; var results = this.results; this.valid = this.checkResults(keys, results, 'valid', true); this.dirty = this.checkResults(keys, results, 'dirty', false); this.touched = this.checkResults(keys, results, 'touched', false); this.modified = this.checkResults(keys, results, 'modified', false); } }, created: function created () { this._validities = Object.create(null); this._validityWatchers = Object.create(null); this._validityKeys = []; this._committing = false; }, destroyed: function destroyed () { var this$1 = this; this._validityKeys.forEach(function (key) { this$1._validityWatchers[key](); delete this$1._validityWatchers[key]; delete this$1._validities[key]; }); delete this._validityWatchers; delete this._validities; delete this._validityKeys; }, methods: { register: function register (name, validity) { var this$1 = this; this._validities[name] = validity; this._validityKeys = Object.keys(this._validities); this.setResults(name, {}); this.withCommit(function () { this$1._validityWatchers[name] = validity.$watch('result', function (val, old) { this$1.setResults(name, val); }, { deep: true, immediate: true }); }); }, unregister: function unregister (name) { var this$1 = this; this._validityWatchers[name](); delete this._validityWatchers[name]; delete this._validities[name]; this._validityKeys = Object.keys(this._validities); this.withCommit(function () { this$1.resetResults(name); }); }, validityCount: function validityCount () { return this._validityKeys.length }, isRegistered: function isRegistered (name) { return name in this._validities }, getValidityKeys: function getValidityKeys () { return this._validityKeys }, checkResults: function checkResults ( keys, results, prop, checking ) { var ret = checking; for (var i = 0; i < keys.length; i++) { var result = results[keys[i]]; if (result[prop] !== checking) { ret = !checking; break } } return ret }, setResults: function setResults (name, val) { var this$1 = this; var newVal = {}; this._validityKeys.forEach(function (key) { newVal[key] = extend({}, this$1.results[key]); }); newVal[name] = extend({}, val); this.results = newVal; }, resetResults: function resetResults (ignore) { var this$1 = this; var newVal = {}; this._validityKeys.forEach(function (key) { if (ignore && ignore !== key) { newVal[key] = extend({}, this$1.results[key]); } }); this.results = newVal; }, withCommit: function withCommit (fn) { var committing = this._committing; this._committing = true; fn(); this._committing = committing; } } } }; /* */ var ValidationClass = function (Vue) { var ValidityGroup = Group(Vue); var Validation = function Validation (options) { if ( options === void 0 ) options = {}; this._result = {}; this._host = options.host; this._named = Object.create(null); this._group = Object.create(null); this._validities = Object.create(null); this._beginDestroy = false; Vue.util.defineReactive(this._host, '$validation', this._result); }; Validation.prototype.register = function register ( field, validity, options ) { if ( options === void 0 ) options = {}; // NOTE: lazy setup (in constructor, occured callstack recursive errors ...) if (!this._validityManager) { this._validityManager = new Vue(ValidityGroup); this._watchValidityResult(); } if (this._validities[field]) { // TODO: should be output console.error return } this._validities[field] = validity; var named = options.named; var group = options.group; var groupValidity = group ? this._getValidityGroup('group', group) || this._registerValidityGroup('group', group) : null; var namedValidity = named ? this._getValidityGroup('named', named) || this._registerValidityGroup('named', named) : null; if (named && group && namedValidity && groupValidity) { groupValidity.register(field, validity); !namedValidity.isRegistered(group) && namedValidity.register(group, groupValidity); !this._validityManager.isRegistered(named) && this._validityManager.register(named, namedValidity); } else if (namedValidity) { namedValidity.register(field, validity); !this._validityManager.isRegistered(named) && this._validityManager.register(named, namedValidity); } else if (groupValidity) { groupValidity.register(field, validity); !this._validityManager.isRegistered(group) && this._validityManager.register(group, groupValidity); } else { this._validityManager.register(field, validity); } }; Validation.prototype.unregister = function unregister ( field, options ) { if ( options === void 0 ) options = {}; if (!this._validityManager) { // TODO: should be output error return } if (!this._validities[field]) { // TODO: should be output error return } delete this._validities[field]; var named = options.named; var group = options.group; var groupValidity = group ? this._getValidityGroup('group', group) : null; var namedValidity = named ? this._getValidityGroup('named', named) : null; if (named && group && namedValidity && groupValidity) { groupValidity.unregister(field); if (groupValidity.validityCount() === 0) { namedValidity.isRegistered(group) && namedValidity.unregister(group); this._unregisterValidityGroup('group', group); } if (namedValidity.validityCount() === 0) { this._validityManager.isRegistered(named) && this._validityManager.unregister(named); this._unregisterValidityGroup('named', named); } } else if (named && namedValidity) { namedValidity.unregister(field); if (namedValidity.validityCount() === 0) { this._validityManager.isRegistered(named) && this._validityManager.unregister(named); this._unregisterValidityGroup('named', named); } } else if (group && groupValidity) { groupValidity.unregister(field); if (groupValidity.validityCount() === 0) { this._validityManager.isRegistered(group) && this._validityManager.unregister(group); this._unregisterValidityGroup('group', group); } } else { this._validityManager.unregister(field); } }; Validation.prototype.destroy = function destroy () { var this$1 = this; var validityKeys = Object.keys(this._validities); var namedKeys = Object.keys(this._named); var groupKeys = Object.keys(this._group); // unregister validity validityKeys.forEach(function (validityKey) { groupKeys.forEach(function (groupKey) { var group = this$1._getValidityGroup('group', groupKey); if (group && group.isRegistered(groupKey)) { group.unregister(validityKey); } }); namedKeys.forEach(function (namedKey) { var named = this$1._getValidityGroup('named', namedKey); if (named && named.isRegistered(validityKey)) { named.unregister(validityKey); } }); if (this$1._validityManager.isRegistered(validityKey)) { this$1._validityManager.unregister(validityKey); } delete this$1._validities[validityKey]; }); // unregister grouped validity groupKeys.forEach(function (groupKey) { namedKeys.forEach(function (namedKey) { var named = this$1._getValidityGroup('named', namedKey); if (named && named.isRegistered(groupKey)) { named.unregister(groupKey); } }); if (this$1._validityManager.isRegistered(groupKey)) { this$1._validityManager.unregister(groupKey); } this$1._unregisterValidityGroup('group', groupKey); }); // unregister named validity namedKeys.forEach(function (namedKey) { if (this$1._validityManager.isRegistered(namedKey)) { this$1._validityManager.unregister(namedKey); } this$1._unregisterValidityGroup('named', namedKey); }); this._beginDestroy = true; }; Validation.prototype._getValidityGroup = function _getValidityGroup (type, name) { return type === 'named' ? this._named[name] : this._group[name] }; Validation.prototype._registerValidityGroup = function _registerValidityGroup (type, name) { var groups = type === 'named' ? this._named : this._group; groups[name] = new Vue(ValidityGroup); return groups[name] }; Validation.prototype._unregisterValidityGroup = function _unregisterValidityGroup (type, name) { var groups = type === 'named' ? this._named : this._group; if (!groups[name]) { // TODO: should be warn return } groups[name].$destroy(); delete groups[name]; }; Validation.prototype._watchValidityResult = function _watchValidityResult () { var this$1 = this; this._watcher = this._validityManager.$watch('results', function (val, old) { Vue.set(this$1._host, '$validation', val); if (this$1._beginDestroy) { this$1._destroyValidityMananger(); } }, { deep: true }); }; Validation.prototype._unwatchValidityResult = function _unwatchValidityResult () { this._watcher(); delete this._watcher; }; Validation.prototype._destroyValidityMananger = function _destroyValidityMananger () { this._unwatchValidityResult(); this._validityManager.$destroy(); this._validityManager = null; }; return Validation }; /* */ var Mixin = function (Vue) { var Validation = ValidationClass(Vue); return { beforeCreate: function beforeCreate () { this._validation = new Validation({ host: this }); } } }; /* */ var baseProps = { field: { type: String, required: true }, validators: { type: [String, Array, Object], required: true }, group: { type: String }, multiple: { type: Boolean }, autotouch: { type: String, default: function () { return 'on' } }, classes: { type: Object, default: function () { return {} } } }; var DEFAULT_CLASSES = { valid: 'valid', invalid: 'invalid', touched: 'touched', untouched: 'untouched', pristine: 'pristine', dirty: 'dirty', modified: 'modified' }; /* */ var States = function (Vue) { var ref = Vue.util; var extend = ref.extend; var isPlainObject = ref.isPlainObject; function initialStates (states, validators, init) { if ( init === void 0 ) init = undefined; if (Array.isArray(validators)) { validators.forEach(function (validator) { states[validator] = init; }); } else { Object.keys(validators).forEach(function (validator) { var props = (validators[validator] && validators[validator]['props'] && isPlainObject(validators[validator]['props'])) ? validators[validator]['props'] : null; if (props) { Object.keys(props).forEach(function (prop) { states[validator] = {}; states[validator][prop] = init; }); } else { states[validator] = init; } }); } } function getInitialResults (validators) { var results = {}; initialStates(results, validators, undefined); return results } function getInitialProgresses (validators) { var progresses = {}; initialStates(progresses, validators, ''); return progresses } var props = extend({ child: { type: Object, required: true }, value: { type: Object } }, baseProps); function data () { var validators = nomalizeValidators(this.validators); return { results: getInitialResults(validators), valid: true, dirty: false, touched: false, modified: false, progresses: getInitialProgresses(validators) } } return { props: props, data: data } }; function nomalizeValidators (target) { return typeof target === 'string' ? [target] : target } /* */ var Computed = function (Vue) { var ref = Vue.util; var isPlainObject = ref.isPlainObject; function setError ( result, field, validator, message, prop ) { var error = { field: field, validator: validator }; if (message) { error.message = message; } if (prop) { error.prop = prop; } result.errors = result.errors || []; result.errors.push(error); } function walkProgresses (keys, target) { var progress = ''; for (var i = 0; i < keys.length; i++) { var result = target[keys[i]]; if (typeof result === 'string' && result) { progress = result; break } if (isPlainObject(result)) { var nestedKeys = Object.keys(result); progress = walkProgresses(nestedKeys, result); if (!progress) { break } } } return progress } function invalid () { return !this.valid } function pristine () { return !this.dirty } function untouched () { return !this.touched } function result () { var this$1 = this; var ret = { valid: this.valid, invalid: this.invalid, dirty: this.dirty, pristine: this.pristine, touched: this.touched, untouched: this.untouched, modified: this.modified }; var keys = this._keysCached(this._uid.toString(), this.results); keys.forEach(function (validator) { var result = this$1.results[validator]; if (typeof result === 'boolean') { if (result) { ret[validator] = false; } else { setError(ret, this$1.field, validator); ret[validator] = !result; } } else if (typeof result === 'string') { setError(ret, this$1.field, validator, result); ret[validator] = result; } else if (isPlainObject(result)) { // object var props = Object.keys(result); props.forEach(function (prop) { var propRet = result[prop]; ret[prop] = ret[prop] || {}; if (typeof propRet === 'boolean') { if (propRet) { ret[prop][validator] = false; } else { setError(ret, this$1.field, validator, undefined, prop); ret[prop][validator] = !propRet; } } else if (typeof propRet === 'string') { setError(ret, this$1.field, validator, propRet, prop); ret[prop][validator] = propRet; } else { ret[prop][validator] = false; } }); } else { ret[validator] = false; } }); return ret } function progress () { var ret = ''; ret = walkProgresses( this._keysCached(this._uid.toString(), this.results), this.progresses ); return ret } return { invalid: invalid, pristine: pristine, untouched: untouched, result: result, progress: progress } }; /* */ var Render = function (Vue) { return { render: function render (h) { return this.child } } }; /* */ var SingleElementClass = function (Vue) { var ref = Vue.util; var looseEqual = ref.looseEqual; var SingleElement = function SingleElement (vm) { this._vm = vm; this.initValue = this.getValue(); this.attachValidity(); }; SingleElement.prototype.attachValidity = function attachValidity () { this._vm.$el.$validity = this._vm; }; SingleElement.prototype.getValue = function getValue () { var el = this._vm.$el; if (el.tagName === 'SELECT') { return getSelectValue(el) } else { if (el.type === 'checkbox') { return el.checked } else { return el.value } } }; SingleElement.prototype.checkModified = function checkModified () { var el = this._vm.$el; if (el.tagName === 'SELECT') { return !looseEqual(this.initValue, getSelectValue(el)) } else { if (el.type === 'checkbox') { return !looseEqual(this.initValue, el.checked) } else { return !looseEqual(this.initValue, el.value) } } }; SingleElement.prototype.listenToucheableEvent = function listenToucheableEvent () { this._vm.$el.addEventListener('focusout', this._vm.willUpdateTouched); }; SingleElement.prototype.unlistenToucheableEvent = function unlistenToucheableEvent () { this._vm.$el.removeEventListener('focusout', this._vm.willUpdateTouched); }; SingleElement.prototype.listenInputableEvent = function listenInputableEvent () { var vm = this._vm; var el = vm.$el; if (el.tagName === 'SELECT') { el.addEventListener('change', vm.handleInputable); } else { if (el.type === 'checkbox') { el.addEventListener('change', vm.handleInputable); } else { el.addEventListener('input', vm.handleInputable); } } }; SingleElement.prototype.unlistenInputableEvent = function unlistenInputableEvent () { var vm = this._vm; var el = vm.$el; if (el.tagName === 'SELECT') { el.removeEventListener('change', vm.handleInputable); } else { if (el.type === 'checkbox') { el.removeEventListener('change', vm.handleInputable); } else { el.removeEventListener('input', vm.handleInputable); } } }; return SingleElement }; function getSelectValue (el) { var value = []; for (var i = 0, l = el.options.length; i < l; i++) { var option = el.options[i]; if (!option.disabled && option.selected) { value.push(option.value); } } return value } /* */ var MultiElementClass = function (Vue) { var ref = Vue.util; var looseEqual = ref.looseEqual; var MultiElement = function MultiElement (vm) { // TODO: should be checked whether included radio or checkbox this._vm = vm; this.initValue = this.getValue(); this.attachValidity(); }; MultiElement.prototype.attachValidity = function attachValidity () { var this$1 = this; this._vm.$el.$validity = this._vm; this._eachItems(function (item) { item.$validity = this$1._vm; }); }; MultiElement.prototype.getValue = function getValue () { return this._getCheckedValue() }; MultiElement.prototype.checkModified = function checkModified () { return !looseEqual(this.initValue, this._getCheckedValue()) }; MultiElement.prototype.listenToucheableEvent = function listenToucheableEvent () { var this$1 = this; this._eachItems(function (item) { item.addEventListener('focusout', this$1._vm.willUpdateTouched); }); }; MultiElement.prototype.unlistenToucheableEvent = function unlistenToucheableEvent () { var this$1 = this; this._eachItems(function (item) { item.removeEventListener('focusout', this$1._vm.willUpdateTouched); }); }; MultiElement.prototype.listenInputableEvent = function listenInputableEvent () { var this$1 = this; this._eachItems(function (item) { item.addEventListener('change', this$1._vm.handleInputable); }); }; MultiElement.prototype.unlistenInputableEvent = function unlistenInputableEvent () { var this$1 = this; this._eachItems(function (item) { item.removeEventListener('change', this$1._vm.handleInputable); }); }; MultiElement.prototype._getCheckedValue = function _getCheckedValue () { var value = []; this._eachItems(function (item) { if (!item.disabled && item.checked) { value.push(item.value); } }); return value }; MultiElement.prototype._getItems = function _getItems () { return this._vm.$el.querySelectorAll('input[type="checkbox"], input[type="radio"]') }; MultiElement.prototype._eachItems = function _eachItems (cb) { var items = this._getItems(); for (var i = 0; i < items.length; i++) { cb(items[i]); } }; return MultiElement }; /* */ var inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]'; var UA = inBrowser && window.navigator.userAgent.toLowerCase(); var isIE9 = UA && UA.indexOf('msie 9.0') > 0; function getClass (el) { var classname = el.className; if (typeof classname === 'object') { classname = classname.baseVal || ''; } return classname } function setClass (el, cls) { if (isIE9 && !/svg$/.test(el.namespaceURI)) { el.className = cls; } else { el.setAttribute('class', cls); } } function addClass (el, cls) { if (el.classList) { el.classList.add(cls); } else { var cur = ' ' + getClass(el) + ' '; if (cur.indexOf(' ' + cls + ' ') < 0) { setClass(el, (cur + cls).trim()); } } } function removeClass (el, cls) { if (el.classList) { el.classList.remove(cls); } else { var cur = ' ' + getClass(el) + ' '; var tar = ' ' + cls + ' '; while (cur.indexOf(tar) >= 0) { cur = cur.replace(tar, ' '); } setClass(el, cur.trim()); } if (!el.className) { el.removeAttribute('class'); } } function toggleClasses (el, key, fn) { if (!el) { return } key = key.trim(); if (key.indexOf(' ') === -1) { fn(el, key); return } var keys = key.split(/\s+/); for (var i = 0, l = keys.length; i < l; i++) { fn(el, keys[i]); } } function memoize (fn) { var cache = Object.create(null); return function memoizeFn (id) { var args = [], len = arguments.length - 1; while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; var hit = cache[id]; return hit || (cache[id] = fn.apply(void 0, args)) } } /* */ var ComponentElementClass = function (Vue) { var ref = Vue.util; var looseEqual = ref.looseEqual; var isPlainObject = ref.isPlainObject; function getValidatorProps (validators) { var normalized = typeof validators === 'string' ? [validators] : validators; var targets = []; if (isPlainObject(normalized)) { Object.keys(normalized).forEach(function (validator) { var props = (normalized[validator] && normalized[validator]['props'] && isPlainObject(normalized[validator]['props'])) ? normalized[validator]['props'] : null; if (props) { Object.keys(props).forEach(function (prop) { if (!~targets.indexOf(prop)) { targets.push(prop); } }); } }); } return targets } var ComponentElement = function ComponentElement (vm, vnode, validatorProps) { this._vm = vm; this._vnode = vnode; this._validatorProps = validatorProps || memoize(getValidatorProps); this.initValue = this.getValue(); this._watchers = []; this.attachValidity(); }; ComponentElement.prototype.attachValidity = function attachValidity () { this._vm.$el.$validity = this._vm; }; ComponentElement.prototype.getValidatorProps = function getValidatorProps$1 () { var vm = this._vm; return this._validatorProps(vm._uid.toString(), vm.validators) }; ComponentElement.prototype.getValue = function getValue () { var this$1 = this; var value = {}; this.getValidatorProps().forEach(function (prop) { value[prop] = this$1._vnode.child[prop]; }); return value }; ComponentElement.prototype.checkModified = function checkModified () { return !looseEqual(this.initValue, this.getValue()) }; ComponentElement.prototype.listenToucheableEvent = function listenToucheableEvent () { this._vm.$el.addEventListener('focusout', this._vm.willUpdateTouched); }; ComponentElement.prototype.unlistenToucheableEvent = function unlistenToucheableEvent () { this._vm.$el.removeEventListener('focusout', this._vm.willUpdateTouched); }; ComponentElement.prototype.listenInputableEvent = function listenInputableEvent () { var this$1 = this; this.getValidatorProps().forEach(function (prop) { this$1._watchers.push(this$1._vnode.child.$watch(prop, this$1._vm.watchInputable)); }); }; ComponentElement.prototype.unlistenInputableEvent = function unlistenInputableEvent () { this._watchers.forEach(function (watcher) { watcher(); }); this._watchers = []; }; return ComponentElement }; /* */ var Elements = function (Vue) { var SingleElement = SingleElementClass(Vue); var MultiElement = MultiElementClass(Vue); var ComponentElement = ComponentElementClass(Vue); return { SingleElement: SingleElement, MultiElement: MultiElement, ComponentElement: ComponentElement } }; /* */ var Lifecycles = function (Vue) { var ref = Elements(Vue); var SingleElement = ref.SingleElement; var MultiElement = ref.MultiElement; var ComponentElement = ref.ComponentElement; function createValidityElement (vm, vnode) { return vm.multiple ? new MultiElement(vm) : checkBuiltInElement(vnode) ? new SingleElement(vm) : checkComponentElement(vnode) ? new ComponentElement(vm, vnode) : null } function watchModelable (val) { this.$emit('input', { result: this.result, progress: this.progress, progresses: this.progresses }); } function created () { this._elementable = null; this._keysCached = memoize(function (results) { return Object.keys(results) }); // for event control flags this._modified = false; // watch validation raw results this._watchValidationRawResults(); var validation = this.$options.propsData ? this.$options.propsData.validation : null; if (validation) { var instance = validation.instance; var name = validation.name; var group = this.group; instance.register(this.field, this, { named: name, group: group }); } } function destroyed () { var validation = this.$options.propsData ? this.$options.propsData.validation : null; if (validation) { var instance = validation.instance; var name = validation.name; var group = this.group; instance.unregister(this.field, { named: name, group: group }); } if (this._unwatchResultProp) { this._unwatchResultProp(); this._unwatchResultProp = null; } if (this._unwatchProgressProp) { this._unwatchProgressProp(); this._unwatchProgressProp = null; } this._unwatchValidationRawResults(); this._elementable.unlistenInputableEvent(); if (this.autotouch === 'on') { this._elementable.unlistenToucheableEvent(); } this._elementable = null; } function mounted () { this._elementable = createValidityElement(this, this._vnode); if (this._elementable) { if (this.autotouch === 'on') { this._elementable.listenToucheableEvent(); } this._elementable.listenInputableEvent(); } else { // TODO: should be warn } if (hasModelDirective(this.$vnode)) { this._unwatchResultProp = this.$watch('result', watchModelable); this._unwatchProgressProp = this.$watch('progress', watchModelable); } toggleClasses(this.$el, this.classes.untouched, addClass); toggleClasses(this.$el, this.classes.pristine, addClass); } return { created: created, destroyed: destroyed, mounted: mounted } }; function checkComponentElement (vnode) { return vnode.child && vnode.componentOptions && vnode.tag && vnode.tag.match(/vue-component/) } function checkBuiltInElement (vnode) { return !vnode.child && !vnode.componentOptions && vnode.tag } function hasModelDirective (vnode) { return ((vnode && vnode.data && vnode.data.directives) || []).find(function (dir) { return dir.name === 'model'; }) } /* */ var Event = function (Vue) { function _fireEvent (type) { var args = [], len = arguments.length - 1; while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; (ref = this).$emit.apply(ref, [ type ].concat( args )); var ref; } return { _fireEvent: _fireEvent } }; /* */ var State = function (Vue) { var ref = Vue.util; var isPlainObject = ref.isPlainObject; function getValue (options) { return this._elementable.getValue() } function checkModified () { return this._elementable.checkModified() } function willUpdateTouched (options) { if (!this.touched) { this.touched = true; toggleClasses(this.$el, this.classes.touched, addClass); toggleClasses(this.$el, this.classes.untouched, removeClass); this._fireEvent('touched'); } } function willUpdateDirty () { if (!this.dirty && this.checkModified()) { this.dirty = true; toggleClasses(this.$el, this.classes.dirty, addClass); toggleClasses(this.$el, this.classes.pristine, removeClass); this._fireEvent('dirty'); } } function willUpdateModified () { var modified = this.modified = this.checkModified(); if (this._modified !== modified) { this._modified = modified; toggleClasses(this.$el, this.classes.modified, modified ? addClass : removeClass); this._fireEvent('modified', modified); } } function handleInputable (e) { this.willUpdateDirty(); this.willUpdateModified(); } function watchInputable (val) { this.willUpdateDirty(); this.willUpdateModified(); } function _initStates (keys, target, init) { if ( init === void 0 ) init = undefined; for (var i = 0; i < keys.length; i++) { var result = target[keys[i]]; if (isPlainObject(result)) { var nestedKeys = Object.keys(result); _initStates(nestedKeys, result, init); } else { target[keys[i]] = init; } } } function reset () { this._unwatchValidationRawResults(); var keys = this._keysCached(this._uid.toString(), this.results); _initStates(keys, this.results, undefined); _initStates(keys, this.progresses, ''); toggleClasses(this.$el, this.classes.valid, removeClass); toggleClasses(this.$el, this.classes.invalid, removeClass); toggleClasses(this.$el, this.classes.touched, removeClass); toggleClasses(this.$el, this.classes.untouched, addClass); toggleClasses(this.$el, this.classes.dirty, removeClass); toggleClasses(this.$el, this.classes.pristine, addClass); toggleClasses(this.$el, this.classes.modified, removeClass); this.valid = true; this.dirty = false; this.touched = false; this.modified = false; this._modified = false; this._watchValidationRawResults(); } function _walkValid (keys, target) { var valid = true; for (var i = 0; i < keys.length; i++) { var result = target[keys[i]]; if (typeof result === 'boolean' && !result) { valid = false; break } if (typeof result === 'string' && result) { valid = false; break } if (isPlainObject(result)) { var nestedKeys = Object.keys(result); valid = _walkValid(nestedKeys, result); if (!valid) { break } } } return valid } function _watchValidationRawResults () { var this$1 = this; this._unwatchResults = this.$watch('results', function (val) { this$1.valid = _walkValid( this$1._keysCached(this$1._uid.toString(), this$1.results), this$1.results ); if (this$1.valid) { toggleClasses(this$1.$el, this$1.classes.valid, addClass); toggleClasses(this$1.$el, this$1.classes.invalid, removeClass); } else { toggleClasses(this$1.$el, this$1.classes.valid, removeClass); toggleClasses(this$1.$el, this$1.classes.invalid, addClass); } this$1._fireEvent(this$1.valid ? 'valid' : 'invalid'); }, { deep: true }); } function _unwatchValidationRawResults () { this._unwatchResults(); this._unwatchResults = undefined; delete this._unwatchResults; } function touch () { this.willUpdateTouched(); } return { getValue: getValue, checkModified: checkModified, willUpdateTouched: willUpdateTouched, willUpdateDirty: willUpdateDirty, willUpdateModified: willUpdateModified, handleInputable: handleInputable, watchInputable: watchInputable, reset: reset, _walkValid: _walkValid, _watchValidationRawResults: _watchValidationRawResults, _unwatchValidationRawResults: _unwatchValidationRawResults, touch: touch } }; /* */ /** * Forgiving check for a promise */ function isPromise (p) { return p && typeof p.then === 'function' } var Validate = function (Vue) { var ref = Vue.util; var extend = ref.extend; var isPlainObject = ref.isPlainObject; var resolveAsset = ref.resolveAsset; function _resolveValidator (name) { var options = (this.child && this.child.context) ? this.child.context.$options : this.$options; return resolveAsset(options, 'validators', name) } function _getValidateRawDescriptor ( validator, field, value ) { var asset = this._resolveValidator(validator); if (!asset) { // TODO: should be warned return null } var fn = null; var rule = null; var msg = null; if (isPlainObject(asset)) { if (asset.check && typeof asset.check === 'function') { fn = asset.check; } if (asset.message) { msg = asset.message; } } else if (typeof asset === 'function') { fn = asset; } else { // TODO: should be warned return null } if (!fn) { // TODO: should be warned return null } var props = null; var validators = this.validators; if (isPlainObject(validators)) { if (isPlainObject(validators[validator])) { if (validators[validator].props && isPlainObject(validators[validator].props)) { props = validators[validator].props; } else { if (validators[validator].rule) { rule = validators[validator].rule; } if (validators[validator].message) { msg = validators[validator].message; } } } else { rule = validators[validator]; } } var descriptor = { fn: fn, value: value, field: field }; if (rule) { descriptor.rule = rule; } if (msg) { descriptor.msg = msg; } if (props) { descriptor.props = props; } return descriptor } function _resolveMessage ( field, msg, override ) { if (override) { return override } return msg ? typeof msg === 'function' ? msg(field) : msg : undefined } function _invokeValidator ( ref, value, cb ) { var this$1 = this; var fn = ref.fn; var field = ref.field; var rule = ref.rule; var msg = ref.msg; var future = fn.call(this.child.context, value, rule); if (typeof future === 'function') { // function future(function () { // resolve cb(true); }, function (err) { // reject cb(false, this$1._resolveMessage(field, msg, err)); }); } else if (isPromise(future)) { // promise future.then(function () { // resolve cb(true); }, function (err) { // reject cb(false, this$1._resolveMessage(field, msg, err)); }).catch(function (err) { cb(false, this$1._resolveMessage(field, msg, err.message)); }); } else { // sync cb(future, future === false ? this._resolveMessage(field, msg) : undefined); } } function _getValidateDescriptors ( validator, field, value ) { var descriptors = []; var rawDescriptor = this._getValidateRawDescriptor(validator, this.field, value); if (!rawDescriptor) { return descriptors } if (!rawDescriptor.props) { var descriptor = { name: validator }; extend(descriptor, rawDescriptor); descriptors.push(descriptor); } else { var propsKeys = Object.keys(!rawDescriptor.props); propsKeys.forEach(function (prop) { var descriptor = { fn: rawDescriptor.fn, name: validator, value: rawDescriptor.value[prop], field: rawDescriptor.field, prop: prop }; if (rawDescriptor.props[prop].rule) { descriptor.rule = rawDescriptor.props[prop].rule; } if (rawDescriptor.props[prop].message) { descriptor.msg = rawDescriptor.props[prop].message; } descriptors.push(descriptor); }); } return descriptors } function _syncValidates (field, cb) { var this$1 = this; var validators = this._keysCached(this._uid.toString(), this.results); var value = this.getValue(); var descriptors = []; validators.forEach(function (validator) { this$1._getValidateDescriptors(validator, field, value).forEach(function (desc) { descriptors.push(desc); }); }); var count = 0; var len = descriptors.length; descriptors.forEach(function (desc) { var validator = desc.name; var prop = desc.prop; if ((!prop && this$1.progresses[validator]) || (prop && this$1.progresses[validator][prop])) { count++; if (count === len) { cb(this$1._walkValid(this$1._keysCached(this$1._uid.toString(), this$1.results), this$1.results)); } return } if (!prop) { this$1.progresses[validator] = 'running'; } else { this$1.progresses[validator][prop] = 'running'; } this$1.$nextTick(function () { this$1._invokeValidator(desc, desc.value, function (ret, msg) { if (!prop) { this$1.progresses[validator] = ''; this$1.results[validator] = msg || ret; } else { this$1.progresses[validator][prop] = ''; this$1.results[validator][prop] = msg || ret; } count++; if (count === len) { cb(this$1._walkValid(this$1._keysCached(this$1._uid.toString(), this$1.results), this$1.results)); } }); }); }); } // TODO:should be refactor!! function _validate (validator, value, cb) { var this$1 = this; var descriptor = this._getValidateRawDescriptor(validator, this.field, value); if (descriptor && !descriptor.props) { if (this.progresses[validator]) { return false } this.progresses[validator] = 'running'; this.$nextTick(function () { this$1._invokeValidator(descriptor, descriptor.value, function (ret, msg) { this$1.progresses[validator] = ''; this$1.results[validator] = msg || ret; if (cb) { this$1.$nextTick(function () { cb.call(this$1, null, ret, msg); }); } else { var e = { result: ret }; if (msg) { e['msg'] = msg; } this$1._fireEvent('validate', validator, e); } }); }); } else if (descriptor && descriptor.props) { var propsKeys = Object.keys(descriptor.props); propsKeys.forEach(function (prop) { if (this$1.progresses[validator][prop]) { return } this$1.progresses[validator][prop] = 'running'; var values = descriptor.value; var propDescriptor = { fn: descriptor.fn, value: values[prop], field: descriptor.field }; if (descriptor.props[prop].rule) { propDescriptor.rule = descriptor.props[prop].rule; } if (descriptor.props[prop].message) { propDescriptor.msg = descriptor.props[prop].message; } this$1.$nextTick(function () { this$1._invokeValidator(propDescriptor, propDescriptor.value, function (result, msg) { this$1.progresses[validator][prop] = ''; this$1.results[validator][prop] = msg || result; var e = { prop: prop, result: result }; if (msg) { e['msg'] = msg; } this$1._fireEvent('validate', validator, e); }); }); }); } else { // TODO: var err = new Error(); cb ? cb.call(this, err) : this._fireEvent('validate', validator, err); } return true } // TODO: should be re-design of API function validate () { var this$1 = this; var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; var validators; var value; var cb; var ret = true; if (args.length === 3) { validators = [args[0]]; value = args[1]; cb = args[2]; } else if (args.length === 2) { if (isPlainObject(args[0])) { validators = [args[0].validator]; value = args[0].value || this.getValue(); cb = args[1]; } else { validators = this._keysCached(this._uid.toString(), this.results); value = args[0]; cb = args[1]; } } else if (args.length === 1) { validators = this._keysCached(this._uid.toString(), this.results); value = this.getValue(); cb = args[0]; } else { validators = this._keysCached(this._uid.toString(), this.results); value = this.getValue(); cb = null; } if (args.length === 3 || (args.length === 2 && isPlainObject(args[0]))) { ret = this._validate(validators[0], value, cb); } else { validators.forEach(function (validator) { ret = this$1._validate(validator, value, cb); }); } return ret } return { _resolveValidator: _resolveValidator, _getValidateRawDescriptor: _getValidateRawDescriptor, _getValidateDescriptors: _getValidateDescriptors, _resolveMessage: _resolveMessage, _invokeValidator: _invokeValidator, _validate: _validate, _syncValidates: _syncValidates, validate: validate } }; /* */ var Methods = function (Vue) { var ref = Vue.util; var extend = ref.extend; var methods = {}; extend(methods, Event(Vue)); extend(methods, State(Vue)); extend(methods, Validate(Vue)); return methods }; /* */ var ValidityControl = function (Vue) { var ref = Vue.util; var extend = ref.extend; var ref$1 = States(Vue); var props = ref$1.props; var data = ref$1.data; var computed = Computed(Vue); var lifecycles = Lifecycles(Vue); var ref$2 = Render(Vue); var render = ref$2.render; var methods = Methods(Vue); var validity = { props: props, data: data, render: render, computed: computed, methods: methods }; extend(validity, lifecycles); return validity }; /* */ var Validity = function (Vue) { var ref = Vue.util; var extend = ref.extend; return { functional: true, props: baseProps, render: function render ( h, ref ) { var props = ref.props; var data = ref.data; var children = ref.children; return children.map(function (child) { if (!child.tag) { return child } var newData = extend({}, data); newData.props = extend({}, props); // TODO: should be refactored newData.props.classes = extend(extend(extend({}, DEFAULT_CLASSES), Vue.config.validator.classes), newData.props.classes); newData.props.child = child; return h('validity-control', newData) }) } } }; /* */ var ValidityGroup = function (Vue) { var ref = Vue.util; var extend = ref.extend; var props = extend({ tag: { type: String, default: 'fieldset' } }, baseProps); return { functional: true, props: props, render: function render ( h, ref ) { var props = ref.props; var data = ref.data; var children = ref.children; var child = h(props.tag, children); var newData = extend({}, data); newData.props = extend({}, props); // TODO: should be refactored newData.props.classes = extend(extend(extend({}, DEFAULT_CLASSES), Vue.config.validator.classes), newData.props.classes); newData.props.child = child; newData.props.multiple = true; return h('validity-control', newData) } } }; /* */ var Validation = function (Vue) { var ref = Vue.util; var extend = ref.extend; return { functional: true, props: { name: { type: String }, tag: { type: String, default: 'form' } }, render: function render ( h, ref ) { var props = ref.props; var data = ref.data; var parent = ref.parent; var children = ref.children; var slots = ref.slots; if (!parent._validation) { // TODO: should be warned return children } var tag = props.tag || 'form'; walkChildren(parent._validation, props.name, children); var newData = extend({ attrs: {}}, data); if (tag === 'form') { newData.attrs.novalidate = true; } return h(tag, newData, children) } } }; function walkChildren (validation, name, children) { children.forEach(function (child) { if (child && child.componentOptions && child.componentOptions.propsData && child.componentOptions.tag === 'validity-control') { child.componentOptions.propsData.validation = { instance: validation, name: name }; } child.children && walkChildren(validation, name, child.children); }); } /* */ var Component = function (Vue) { return { 'validity-control': ValidityControl(Vue), 'validity': Validity(Vue), 'validity-group': ValidityGroup(Vue), 'validation': Validation(Vue) } }; /* */ // TODO: should be defined strict type function mapValidation (results) { var res = {}; normalizeMap(results).forEach(function (ref) { var key = ref.key; var val = ref.val; res[key] = function mappedValidation () { var validation = this.$validation; if (!this._isMounted) { return null } var paths = val.split('.'); var first = paths.shift(); if (first !== '$validation') { warn(("unknown validation result path: " + val)); return null } var path; var value = validation; do { path = paths.shift(); value = value[path]; } while (paths.length > 0 && value !== undefined) return value }; }); return res } // TODO: should be defined strict type function normalizeMap (map) { return Array.isArray(map) ? map.map(function (key) { return ({ key: key, val: key }); }) : Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); }) } /* */ var installed = false; function plugin (Vue, options) { if ( options === void 0 ) options = {}; if (installed) { warn('already installed.'); return } Config(Vue); Asset(Vue); installMixin(Vue); installComponent(Vue); installed = true; } function installMixin (Vue) { Vue.mixin(Mixin(Vue)); } function installComponent (Vue) { var components = Component(Vue); Object.keys(components).forEach(function (id) { Vue.component(id, components[id]); }); } plugin.mapValidation = mapValidation; // for standalone plugin.version = '3.0.0-alpha.2'; if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(plugin); } var index = { install: plugin, mapValidation: mapValidation }; return index; })));