simple-vue3-validator
Version:
lightweight yet flexible validation lib for vue.js - fork of simple-vue-validator
243 lines (225 loc) • 7.49 kB
JavaScript
'use strict';
var utils = require('./utils');
var ValidationBag = require('./validation-bag');
var get = require('lodash.get');
var mixin = {
Promise: null,
beforeMount: function () {
this.$setValidators(this.$options.validators);
if (this.validation) {
// set vm to validation
this.validation._setVM(this);
}
},
beforeUnmount: function () {
unwatch(this.$options.validatorsUnwatchCallbacks);
},
data: function () {
if (this.$options.validators) {
return {
validation: new ValidationBag()
};
}
return {};
},
methods: {
$setValidators: function (validators) {
unwatch(this.$options.validatorsUnwatchCallbacks);
// validate methods contains all application validate codes
var validateMethods = {};
this.$options.validateMethods = validateMethods;
var unwatchCallbacks = [];
this.$options.validatorsUnwatchCallbacks = unwatchCallbacks;
// generate validate methods and watch properties change for validators
if (validators) {
Object.keys(validators).forEach(function (key) {
var properties = key.split(',');
properties = properties.map(function (property) {
return property.trim();
});
var getters = properties.map(function (property) {
return generateGetter(this, property);
}, this);
var validator = validators[key];
var options = {};
if (!utils.isFunction(validator)) {
options = utils.omit(validator, 'validator');
validator = validator.validator;
}
if (options.cache) {
// cache the validation result, so that async validator can be fast when submitting the form
var option = options.cache === 'last' ? 'last' : 'all';
validator = cache(validator, option);
}
var validation = this.validation;
var validateMethod = function () {
if (utils.mode === 'conservative' && !validation.activated) { // do nothing if in conservative mode and $validate() method is not called before
return getPromise().resolve(false);
}
var args = getters.map(function (getter) {
return getter();
});
var rule = validator.apply(this, args);
if (rule) {
if (!rule._field) {
// field defaults to the first property
rule.field(properties[0]);
}
return this.validation.checkRule(rule);
} else {
return getPromise().resolve(false);
}
}.bind(this);
// add to validate method list
validateMethods[properties[0]] = validateMethod;
// watch change and invoke validate method
var validateMethodForWatch = validateMethod;
if (options.debounce) {
// TODO what if custom field name is used?
var decoratedValidateMethod = function () {
if (decoratedValidateMethod.sessionId !== this.validation.sessionId) {
// skip validation if it's reset before
return getPromise().resolve(false);
}
return validateMethod.apply(this, arguments);
}.bind(this);
var debouncedValidateMethod = utils.debounce(decoratedValidateMethod, parseInt(options.debounce));
var field = properties[0];
validateMethodForWatch = function () {
// eagerly resetting passed flag if debouncing is used.
this.validation.resetPassed(field);
// store sessionId
decoratedValidateMethod.sessionId = this.validation.sessionId;
debouncedValidateMethod.apply(this, arguments);
}.bind(this);
}
if (utils.mode !== 'manual') { // have to call $validate() to trigger validation in manual mode, so don't watch,
watchProperties(this, properties, validateMethodForWatch).forEach(function (unwatch) {
unwatchCallbacks.push(unwatch);
});
}
}, this);
}
},
$validate: function (fields) {
if (this.validation._validate) {
return this.validation._validate;
}
this.validation.activated = true;
var validateMethods = this.$options.validateMethods;
if (utils.isUndefined(fields)) {
validateMethods = Object.keys(validateMethods).map(function (key) {
return validateMethods[key];
});
} else {
fields = utils.isArray(fields) ? fields : [fields];
validateMethods = fields.map(function (field) {
return validateMethods[field];
});
}
if (utils.isEmpty(validateMethods)) {
return getPromise().resolve(true);
} else {
var always = function() {
this.validation._validate = null;
}.bind(this);
this.validation._validate = getPromise()
.all(validateMethods.map(function (validateMethod) {
return validateMethod();
}))
.then(function (results) {
always();
return results.filter(function (result) {
return !!result;
}).length <= 0;
}.bind(this))
.catch(function(e) {
always();
throw e;
});
return this.validation._validate;
}
}
}
};
function unwatch(list) {
if (list) {
list.forEach(function (unwatch) {
unwatch();
});
}
}
function generateGetter(vm, property) {
var names = property.split('.');
return function () {
var value = vm;
for (var i = 0; i < names.length; i++) {
if (utils.isNull(value) || utils.isUndefined(value)) {
break;
}
value = value[names[i]];
}
return value;
};
}
function watchProperties(vm, properties, callback) {
return properties.map(function (property) {
return vm.$watch(
function () {
return get(vm, property)
},
function () {
vm.validation.setTouched(property)
callback.call()
}
)
});
}
function cache(validator, option) {
return function () {
var cache = validator.cache;
if (!cache) {
cache = [];
validator.cache = cache;
}
var args = Array.prototype.slice.call(arguments);
var cachedResult = findInCache(cache, args);
if (!utils.isUndefined(cachedResult)) {
return cachedResult;
}
var result = validator.apply(this, args);
if (!utils.isUndefined(result)) {
if (result.then) {
return result.tab(function (promiseResult) {
if (!utils.isUndefined(promiseResult)) {
if (option !== 'all') {
cache.splice(0, cache.length);
}
cache.push({args: args, result: promiseResult});
}
});
} else {
if (option !== 'all') {
cache.splice(0, cache.length);
}
cache.push({args: args, result: result});
return result;
}
}
};
}
function getPromise() {
if (mixin.Promise) {
return mixin.Promise;
}
return require('es6-promise').Promise;
}
function findInCache(cache, args) {
var items = cache.filter(function (item) {
return utils.isEqual(args, item.args);
});
if (!utils.isEmpty(items)) {
return items[0].result;
}
}
module.exports = mixin;