vue-form
Version:
Form validation for Vue.js
1,273 lines (1,112 loc) • 35.7 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.VueForm = factory());
}(this, (function () { 'use strict';
var emailRegExp = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; // from angular
var urlRegExp = /^(http\:\/\/|https\:\/\/)(.{4,})$/;
var email = function email(value, attrValue, vnode) {
return emailRegExp.test(value);
};
email._allowNulls = true;
var number = function number(value, attrValue, vnode) {
return !isNaN(value);
};
number._allowNulls = true;
var url = function url(value, attrValue, vnode) {
return urlRegExp.test(value);
};
url._allowNulls = true;
var validators = {
email: email,
number: number,
url: url,
required: function required(value, attrValue, vnode) {
if (attrValue === false) {
return true;
}
if (value === 0) {
return true;
}
if (vnode.data.attrs && typeof vnode.data.attrs.bool !== 'undefined' || vnode.componentOptions && vnode.componentOptions.propsData && typeof vnode.componentOptions.propsData.bool !== 'undefined') {
// bool attribute is present, allow false pass validation
if (value === false) {
return true;
}
}
if (Array.isArray(value)) {
return !!value.length;
}
return !!value;
},
minlength: function minlength(value, length) {
return value.length >= length;
},
maxlength: function maxlength(value, length) {
return length >= value.length;
},
pattern: function pattern(value, _pattern) {
var patternRegExp = new RegExp('^' + _pattern + '$');
return patternRegExp.test(value);
},
min: function min(value, _min, vnode) {
if ((vnode.data.attrs.type || '').toLowerCase() == 'number') {
return +value >= +_min;
}
return value >= _min;
},
max: function max(value, _max, vnode) {
if ((vnode.data.attrs.type || '').toLowerCase() == 'number') {
return +_max >= +value;
}
return _max >= value;
}
};
var config = {
validators: validators,
formComponent: 'vueForm',
formTag: 'form',
messagesComponent: 'fieldMessages',
messagesTag: 'div',
showMessages: '',
validateComponent: 'validate',
validateTag: 'div',
fieldComponent: 'field',
fieldTag: 'div',
formClasses: {
dirty: 'vf-form-dirty',
pristine: 'vf-form-pristine',
valid: 'vf-form-valid',
invalid: 'vf-form-invalid',
touched: 'vf-form-touched',
untouched: 'vf-form-untouched',
focused: 'vf-form-focused',
submitted: 'vf-form-submitted',
pending: 'vf-form-pending'
},
validateClasses: {
dirty: 'vf-field-dirty',
pristine: 'vf-field-pristine',
valid: 'vf-field-valid',
invalid: 'vf-field-invalid',
touched: 'vf-field-touched',
untouched: 'vf-field-untouched',
focused: 'vf-field-focused',
submitted: 'vf-field-submitted',
pending: 'vf-field-pending'
},
inputClasses: {
dirty: 'vf-dirty',
pristine: 'vf-pristine',
valid: 'vf-valid',
invalid: 'vf-invalid',
touched: 'vf-touched',
untouched: 'vf-untouched',
focused: 'vf-focused',
submitted: 'vf-submitted',
pending: 'vf-pending'
},
Promise: typeof Promise === 'function' ? Promise : null
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
function getClasses(classConfig, state) {
var _ref;
return _ref = {}, defineProperty(_ref, classConfig.dirty, state.$dirty), defineProperty(_ref, classConfig.pristine, state.$pristine), defineProperty(_ref, classConfig.valid, state.$valid), defineProperty(_ref, classConfig.invalid, state.$invalid), defineProperty(_ref, classConfig.touched, state.$touched), defineProperty(_ref, classConfig.untouched, state.$untouched), defineProperty(_ref, classConfig.focused, state.$focused), defineProperty(_ref, classConfig.pending, state.$pending), defineProperty(_ref, classConfig.submitted, state.$submitted), _ref;
}
function addClass(el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className += ' ' + className;
}
}
function removeClass(el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
}
function vModelValue(data) {
if (data.model) {
return data.model.value;
}
return data.directives.filter(function (v) {
return v.name === 'model';
})[0].value;
}
function getVModelAndLabel(nodes, config) {
var foundVnodes = {
vModel: [],
label: null,
messages: null
};
if (!nodes) {
return foundVnodes;
}
function traverse(nodes) {
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.componentOptions) {
if (node.componentOptions.tag === hyphenate(config.messagesComponent)) {
foundVnodes.messages = node;
}
}
if (node.tag === 'label' && !foundVnodes.label) {
foundVnodes.label = node;
}
if (node.data) {
if (node.data.model) {
// model check has to come first. If a component has
// a directive and v-model, the directive will be in .directives
// and v-modelstored in .model
foundVnodes.vModel.push(node);
} else if (node.data.directives) {
var match = node.data.directives.filter(function (v) {
return v.name === 'model';
});
if (match.length) {
foundVnodes.vModel.push(node);
}
}
}
if (node.children) {
traverse(node.children);
} else if (node.componentOptions && node.componentOptions.children) {
traverse(node.componentOptions.children);
}
}
}
traverse(nodes);
return foundVnodes;
}
function getName(vnode) {
if (vnode.data && vnode.data.attrs && vnode.data.attrs.name) {
return vnode.data.attrs.name;
} else if (vnode.componentOptions && vnode.componentOptions.propsData && vnode.componentOptions.propsData.name) {
return vnode.componentOptions.propsData.name;
}
}
var hyphenateRE = /([^-])([A-Z])/g;
function hyphenate(str) {
return str.replace(hyphenateRE, '$1-$2').replace(hyphenateRE, '$1-$2').toLowerCase();
}
function randomId() {
return Math.random().toString(36).substr(2, 10);
}
// https://davidwalsh.name/javascript-debounce-function
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this,
args = arguments;
var later = function later() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
function isShallowObjectDifferent(a, b) {
var aValue = '';
var bValue = '';
Object.keys(a).sort().filter(function (v) {
return typeof a[v] !== 'function';
}).forEach(function (v) {
return aValue += a[v];
});
Object.keys(b).sort().filter(function (v) {
return typeof a[v] !== 'function';
}).forEach(function (v) {
return bValue += b[v];
});
return aValue !== bValue;
}
var vueFormConfig = 'VueFormProviderConfig' + randomId();
var vueFormState = 'VueFormProviderState' + randomId();
var vueFormParentForm = 'VueFormProviderParentForm' + randomId();
var hasOwn = Object.prototype.hasOwnProperty;
var toStr = Object.prototype.toString;
var isArray = function isArray(arr) {
if (typeof Array.isArray === 'function') {
return Array.isArray(arr);
}
return toStr.call(arr) === '[object Array]';
};
var isPlainObject = function isPlainObject(obj) {
if (!obj || toStr.call(obj) !== '[object Object]') {
return false;
}
var hasOwnConstructor = hasOwn.call(obj, 'constructor');
var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
// Not own constructor property must be Object
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) { /**/ }
return typeof key === 'undefined' || hasOwn.call(obj, key);
};
var extend = function extend() {
var options, name, src, copy, copyIsArray, clone;
var target = arguments[0];
var i = 1;
var length = arguments.length;
var deep = false;
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
if (target == null || (typeof target !== 'object' && typeof target !== 'function')) {
target = {};
}
for (; i < length; ++i) {
options = arguments[i];
// Only deal with non-null/undefined values
if (options != null) {
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target !== copy) {
// Recurse if we're merging plain objects or arrays
if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = extend(deep, clone, copy);
// Don't bring in undefined values
} else if (typeof copy !== 'undefined') {
target[name] = copy;
}
}
}
}
}
// Return the modified object
return target;
};
var vueForm = {
render: function render(h) {
var _this = this;
return h(this.tag || this.vueFormConfig.formTag, {
on: {
submit: function submit(event) {
if (_this.state.$pending) {
event.preventDefault();
_this.vueFormConfig.Promise.all(_this.promises).then(function () {
_this.state._submit();
_this.$emit('submit', event);
_this.promises = [];
});
} else {
_this.state._submit();
_this.$emit('submit', event);
}
},
reset: function reset(event) {
_this.state._reset();
_this.$emit('reset', event);
}
},
class: this.className,
attrs: {
'novalidate': ''
}
}, [this.$slots.default]);
},
props: {
state: {
type: Object,
required: true
},
tag: String,
showMessages: String
},
inject: { vueFormConfig: vueFormConfig },
provide: function provide() {
var _ref;
return _ref = {}, defineProperty(_ref, vueFormState, this.state), defineProperty(_ref, vueFormParentForm, this), _ref;
},
data: function data() {
return {
promises: []
};
},
created: function created() {
var _this2 = this;
if (!this.state) {
return;
}
var controls = {};
var state = this.state;
var formstate = {
$dirty: false,
$pristine: true,
$valid: true,
$invalid: false,
$submitted: false,
$touched: false,
$untouched: true,
$focused: false,
$pending: false,
$error: {},
$submittedState: {},
_id: '',
_submit: function _submit() {
_this2.state.$submitted = true;
_this2.state._cloneState();
},
_cloneState: function _cloneState() {
var cloned = JSON.parse(JSON.stringify(state));
delete cloned.$submittedState;
Object.keys(cloned).forEach(function (key) {
_this2.$set(_this2.state.$submittedState, key, cloned[key]);
});
},
_addControl: function _addControl(ctrl) {
controls[ctrl.$name] = ctrl;
_this2.$set(state, ctrl.$name, ctrl);
},
_removeControl: function _removeControl(ctrl) {
delete controls[ctrl.$name];
_this2.$delete(_this2.state, ctrl.$name);
_this2.$delete(_this2.state.$error, ctrl.$name);
},
_validate: function _validate() {
Object.keys(controls).forEach(function (key) {
controls[key]._validate();
});
},
_reset: function _reset() {
state.$submitted = false;
state.$pending = false;
state.$submittedState = {};
Object.keys(controls).forEach(function (key) {
var control = controls[key];
control._hasFocused = false;
control._setUntouched();
control._setPristine();
control.$submitted = false;
control.$pending = false;
});
}
};
Object.keys(formstate).forEach(function (key) {
_this2.$set(_this2.state, key, formstate[key]);
});
this.$watch('state', function () {
var isDirty = false;
var isValid = true;
var isTouched = false;
var isFocused = false;
var isPending = false;
Object.keys(controls).forEach(function (key) {
var control = controls[key];
control.$submitted = state.$submitted;
if (control.$dirty) {
isDirty = true;
}
if (control.$touched) {
isTouched = true;
}
if (control.$focused) {
isFocused = true;
}
if (control.$pending) {
isPending = true;
}
if (!control.$valid) {
isValid = false;
// add control to errors
_this2.$set(state.$error, control.$name, control);
} else {
_this2.$delete(state.$error, control.$name);
}
});
state.$dirty = isDirty;
state.$pristine = !isDirty;
state.$touched = isTouched;
state.$untouched = !isTouched;
state.$focused = isFocused;
state.$valid = isValid;
state.$invalid = !isValid;
state.$pending = isPending;
}, {
deep: true,
immediate: true
});
/* watch pristine? if set to true, set all children to pristine
Object.keys(controls).forEach((ctrl) => {
controls[ctrl].setPristine();
});*/
},
computed: {
className: function className() {
var classes = getClasses(this.vueFormConfig.formClasses, this.state);
return classes;
}
},
methods: {
reset: function reset() {
this.state._reset();
},
validate: function validate() {
this.state._validate();
}
}
};
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var scope_eval = createCommonjsModule(function (module) {
// Generated by CoffeeScript 1.10.0
(function() {
var hasProp = {}.hasOwnProperty,
slice = [].slice;
module.exports = function(source, scope) {
var key, keys, value, values;
keys = [];
values = [];
for (key in scope) {
if (!hasProp.call(scope, key)) continue;
value = scope[key];
if (key === 'this') {
continue;
}
keys.push(key);
values.push(value);
}
return Function.apply(null, slice.call(keys).concat(["return eval(" + (JSON.stringify(source)) + ")"])).apply(scope["this"], values);
};
}).call(commonjsGlobal);
});
function findLabel(nodes) {
if (!nodes) {
return;
}
for (var i = 0; i < nodes.length; i++) {
var vnode = nodes[i];
if (vnode.tag === 'label') {
return nodes[i];
} else if (nodes[i].children) {
return findLabel(nodes[i].children);
}
}
}
var messages = {
inject: { vueFormConfig: vueFormConfig, vueFormState: vueFormState, vueFormParentForm: vueFormParentForm },
render: function render(h) {
var _this = this;
var children = [];
var field = this.formstate[this.fieldname];
if (field && field.$error && this.isShown) {
Object.keys(field.$error).forEach(function (key) {
if (_this.$slots[key] || _this.$scopedSlots[key]) {
var out = _this.$slots[key] || _this.$scopedSlots[key](field);
if (_this.autoLabel) {
var label = findLabel(out);
if (label) {
label.data = label.data || {};
label.data.attrs = label.data.attrs || {};
label.data.attrs.for = field._id;
}
}
children.push(out);
}
});
if (!children.length && field.$valid) {
if (this.$slots.default || this.$scopedSlots.default) {
var out = this.$slots.default || this.$scopedSlots.default(field);
if (this.autoLabel) {
var label = findLabel(out);
if (label) {
label.data = label.data || {};
label.data.attrs = label.data.attrs || {};
label.data.attrs.for = field._id;
}
}
children.push(out);
}
}
}
return h(this.tag || this.vueFormConfig.messagesTag, children);
},
props: {
state: Object,
name: String,
show: {
type: String,
default: ''
},
tag: {
type: String
},
autoLabel: Boolean
},
data: function data() {
return {
formstate: null,
fieldname: ''
};
},
created: function created() {
this.fieldname = this.name;
this.formstate = this.state || this.vueFormState;
},
computed: {
isShown: function isShown() {
var field = this.formstate[this.fieldname];
var show = this.show || this.vueFormParentForm.showMessages || this.vueFormConfig.showMessages;
if (!show || !field) {
return true;
}
return scope_eval(show, field);
}
}
};
var validate = {
render: function render(h) {
var _this = this;
var foundVnodes = getVModelAndLabel(this.$slots.default, this.vueFormConfig);
var vModelnodes = foundVnodes.vModel;
var attrs = {
for: null
};
if (vModelnodes.length) {
this.name = getName(vModelnodes[0]);
if (foundVnodes.messages) {
this.$nextTick(function () {
var messagesVm = foundVnodes.messages.componentInstance;
if (messagesVm) {
messagesVm.fieldname = messagesVm.fieldname || _this.name;
}
});
}
if (this.autoLabel) {
var id = vModelnodes[0].data.attrs.id || this.fieldstate._id;
this.fieldstate._id = id;
vModelnodes[0].data.attrs.id = id;
if (foundVnodes.label) {
foundVnodes.label.data = foundVnodes.label.data || {};
foundVnodes.label.data.attrs = foundVnodes.label.data.attrs || {};
foundVnodes.label.data.attrs.for = id;
} else if (this.tag === 'label') {
attrs.for = id;
}
}
vModelnodes.forEach(function (vnode) {
if (!vnode.data.directives) {
vnode.data.directives = [];
}
vnode.data.directives.push({ name: 'vue-form-validator', value: { fieldstate: _this.fieldstate, config: _this.vueFormConfig } });
vnode.data.attrs['vue-form-validator'] = '';
vnode.data.attrs['debounce'] = _this.debounce;
});
} else {
//console.warn('Element with v-model not found');
}
return h(this.tag || this.vueFormConfig.validateTag, { 'class': this.className, attrs: attrs }, this.$slots.default);
},
props: {
state: Object,
custom: null,
autoLabel: Boolean,
tag: {
type: String
},
debounce: Number
},
inject: { vueFormConfig: vueFormConfig, vueFormState: vueFormState, vueFormParentForm: vueFormParentForm },
data: function data() {
return {
name: '',
formstate: null,
fieldstate: {}
};
},
methods: {
getClasses: function getClasses$$1(classConfig) {
var s = this.fieldstate;
return Object.keys(s.$error).reduce(function (classes, error) {
classes[classConfig.invalid + '-' + hyphenate(error)] = true;
return classes;
}, getClasses(classConfig, s));
}
},
computed: {
className: function className() {
return this.getClasses(this.vueFormConfig.validateClasses);
},
inputClassName: function inputClassName() {
return this.getClasses(this.vueFormConfig.inputClasses);
}
},
mounted: function mounted() {
var _this2 = this;
this.fieldstate.$name = this.name;
this.formstate._addControl(this.fieldstate);
var vModelEls = this.$el.querySelectorAll('[vue-form-validator]');
// add classes to the input element
this.$watch('inputClassName', function (value, oldValue) {
var out = void 0;
var _loop = function _loop(i, el) {
if (oldValue) {
Object.keys(oldValue).filter(function (k) {
return oldValue[k];
}).forEach(function (k) {
return removeClass(el, k);
});
}
out = [];
Object.keys(value).filter(function (k) {
return value[k];
}).forEach(function (k) {
out.push(k);
addClass(el, k);
});
};
for (var i = 0, el; el = vModelEls[i++];) {
_loop(i, el);
}
_this2.fieldstate._className = out;
}, {
deep: true,
immediate: true
});
},
created: function created() {
var _this4 = this;
this.formstate = this.state || this.vueFormState;
var vm = this;
var pendingValidators = [];
var _val = void 0;
var prevVnode = void 0;
this.fieldstate = {
$name: '',
$dirty: false,
$pristine: true,
$valid: true,
$invalid: false,
$touched: false,
$untouched: true,
$focused: false,
$pending: false,
$submitted: false,
$error: {},
_className: null,
_id: 'vf' + randomId(),
_setValidatorVadility: function _setValidatorVadility(validator, isValid) {
if (isValid) {
vm.$delete(this.$error, validator);
} else {
vm.$set(this.$error, validator, true);
}
},
_setValidity: function _setValidity(isValid) {
this.$valid = isValid;
this.$invalid = !isValid;
},
_setDirty: function _setDirty() {
this.$dirty = true;
this.$pristine = false;
},
_setPristine: function _setPristine() {
this.$dirty = false;
this.$pristine = true;
},
_setTouched: function _setTouched() {
this.$touched = true;
this.$untouched = false;
},
_setUntouched: function _setUntouched() {
this.$touched = false;
this.$untouched = true;
},
_setFocused: function _setFocused(value) {
this.$focused = typeof value === 'boolean' ? value : false;
if (this.$focused) {
this._setHasFocused();
} else {
this._setTouched();
}
},
_setHasFocused: function _setHasFocused() {
this._hasFocused = true;
},
_hasFocused: false,
_validators: {},
_validate: function _validate(vnode) {
var _this3 = this;
if (!vnode) {
vnode = prevVnode;
} else {
prevVnode = vnode;
}
this.$pending = true;
var isValid = true;
var emptyAndRequired = false;
var value = vModelValue(vnode.data);
_val = value;
var pending = {
promises: [],
names: []
};
pendingValidators.push(pending);
var attrs = vnode.data.attrs || {};
var childvm = vnode.componentInstance;
if (childvm && childvm._vfValidationData_) {
attrs = extend({}, attrs, childvm._vfValidationData_);
}
var propsData = vnode.componentOptions && vnode.componentOptions.propsData ? vnode.componentOptions.propsData : {};
Object.keys(this._validators).forEach(function (validator) {
// when value is empty and current validator is not the required validator, the field is valid
if ((value === '' || value === undefined || value === null) && validator !== 'required') {
_this3._setValidatorVadility(validator, true);
emptyAndRequired = true;
// return early, required validator will
// fall through if it is present
return;
}
var attrValue = typeof attrs[validator] !== 'undefined' ? attrs[validator] : propsData[validator];
var isFunction = typeof _this3._validators[validator] === 'function';
// match vue behaviour, ignore if attribute is null or undefined. But for type=email|url|number and custom validators, the value will be null, so allow with _allowNulls
if (isFunction && (attrValue === null || typeof attrValue === 'undefined') && !_this3._validators[validator]._allowNulls) {
return;
}
var result = isFunction ? _this3._validators[validator](value, attrValue, vnode) : vm.custom[validator];
if (typeof result === 'boolean') {
if (result) {
_this3._setValidatorVadility(validator, true);
} else {
isValid = false;
_this3._setValidatorVadility(validator, false);
}
} else {
pending.promises.push(result);
pending.names.push(validator);
vm.vueFormParentForm.promises.push(result);
}
});
if (pending.promises.length) {
vm.vueFormConfig.Promise.all(pending.promises).then(function (results) {
// only concerned with the last promise results, in case
// async responses return out of order
if (pending !== pendingValidators[pendingValidators.length - 1]) {
//console.log('ignoring old promise', pending.promises);
return;
}
pendingValidators = [];
results.forEach(function (result, i) {
var name = pending.names[i];
if (result) {
_this3._setValidatorVadility(name, true);
} else {
isValid = false;
_this3._setValidatorVadility(name, false);
}
});
_this3._setValidity(isValid);
_this3.$pending = false;
});
} else {
this._setValidity(isValid);
this.$pending = false;
}
}
};
// add custom validators
if (this.custom) {
Object.keys(this.custom).forEach(function (prop) {
if (typeof _this4.custom[prop] === 'function') {
_this4.custom[prop]._allowNulls = true;
_this4.fieldstate._validators[prop] = _this4.custom[prop];
} else {
_this4.fieldstate._validators[prop] = _this4.custom[prop];
}
});
}
this.$watch('custom', function (v, oldV) {
if (!oldV) {
return;
}
if (isShallowObjectDifferent(v, oldV)) {
_this4.fieldstate._validate();
}
}, {
deep: true
});
},
destroyed: function destroyed() {
this.formstate._removeControl(this.fieldstate);
}
};
var field = {
inject: { vueFormConfig: vueFormConfig },
render: function render(h) {
var foundVnodes = getVModelAndLabel(this.$slots.default, this.vueFormConfig);
var vModelnodes = foundVnodes.vModel;
var attrs = {
for: null
};
if (vModelnodes.length) {
if (this.autoLabel) {
var id = vModelnodes[0].data.attrs && vModelnodes[0].data.attrs.id || 'vf' + randomId();
vModelnodes[0].data.attrs.id = id;
if (foundVnodes.label) {
foundVnodes.label.data = foundVnodes.label.data || {};
foundVnodes.label.data.attrs = foundVnodes.label.data.attrs = {};
foundVnodes.label.data.attrs.for = id;
} else if (this.tag === 'label') {
attrs.for = id;
}
}
}
return h(this.tag || this.vueFormConfig.fieldTag, { attrs: attrs }, this.$slots.default);
},
props: {
tag: {
type: String
},
autoLabel: {
type: Boolean,
default: true
}
}
};
var debouncedValidators = {};
function addValidators(attrs, validators, fieldValidators) {
Object.keys(attrs).forEach(function (attr) {
var prop = attr === 'type' ? attrs[attr].toLowerCase() : attr.toLowerCase();
if (validators[prop] && !fieldValidators[prop]) {
fieldValidators[prop] = validators[prop];
}
});
}
function compareChanges(vnode, oldvnode, validators) {
var hasChanged = false;
var attrs = vnode.data.attrs || {};
var oldAttrs = oldvnode.data.attrs || {};
var out = {};
if (vModelValue(vnode.data) !== vModelValue(oldvnode.data)) {
out.vModel = true;
hasChanged = true;
}
Object.keys(validators).forEach(function (validator) {
if (attrs[validator] !== oldAttrs[validator]) {
out[validator] = true;
hasChanged = true;
}
});
// if is a component
if (vnode.componentOptions && vnode.componentOptions.propsData) {
var _attrs = vnode.componentOptions.propsData;
var _oldAttrs = oldvnode.componentOptions.propsData;
Object.keys(validators).forEach(function (validator) {
if (_attrs[validator] !== _oldAttrs[validator]) {
out[validator] = true;
hasChanged = true;
}
});
}
if (hasChanged) {
return out;
}
}
var vueFormValidator = {
name: 'vue-form-validator',
bind: function bind(el, binding, vnode) {
var fieldstate = binding.value.fieldstate;
var validators = binding.value.config.validators;
var attrs = vnode.data.attrs || {};
var inputName = getName(vnode);
if (!inputName) {
console.warn('vue-form: name attribute missing');
return;
}
if (attrs.debounce) {
debouncedValidators[fieldstate._id] = debounce(function (fieldstate, vnode) {
if (fieldstate._hasFocused) {
fieldstate._setDirty();
}
fieldstate._validate(vnode);
}, attrs.debounce);
}
// add validators
addValidators(attrs, validators, fieldstate._validators);
// if is a component, a validator attribute could be a prop this component uses
if (vnode.componentOptions && vnode.componentOptions.propsData) {
addValidators(vnode.componentOptions.propsData, validators, fieldstate._validators);
}
fieldstate._validate(vnode);
// native listeners
el.addEventListener('blur', function () {
fieldstate._setFocused(false);
}, false);
el.addEventListener('focus', function () {
fieldstate._setFocused(true);
}, false);
// component listeners
var vm = vnode.componentInstance;
if (vm) {
vm.$on('blur', function () {
fieldstate._setFocused(false);
});
vm.$on('focus', function () {
fieldstate._setFocused(true);
});
el.addEventListener('focusout', function () {
fieldstate._setFocused(false);
}, false);
el.addEventListener('focusin', function () {
fieldstate._setFocused(true);
}, false);
vm.$on('vf:validate', function (data) {
if (!vm._vfValidationData_) {
addValidators(data, validators, fieldstate._validators);
}
vm._vfValidationData_ = data;
fieldstate._validate(vm.$vnode);
});
}
},
update: function update(el, binding, vnode, oldVNode) {
var validators = binding.value.config.validators;
var changes = compareChanges(vnode, oldVNode, validators);
var fieldstate = binding.value.fieldstate;
var attrs = vnode.data.attrs || {};
var vm = vnode.componentInstance;
if (vm && vm._vfValidationData_) {
attrs = extend({}, attrs, vm[vm._vfValidationData_]);
}
if (vnode.elm.className.indexOf(fieldstate._className[0]) === -1) {
vnode.elm.className = vnode.elm.className + ' ' + fieldstate._className.join(' ');
}
if (!changes) {
return;
}
if (changes.vModel) {
// re-validate all
if (attrs.debounce) {
fieldstate.$pending = true;
debouncedValidators[fieldstate._id](fieldstate, vnode);
} else {
if (fieldstate._hasFocused) {
fieldstate._setDirty();
}
fieldstate._validate(vnode);
}
} else {
// attributes have changed
// to do: loop through them and re-validate changed ones
//for(let prop in changes) {
// fieldstate._validate(vnode, validator);
//}
// for now
fieldstate._validate(vnode);
}
}
};
function VueFormBase(options) {
var _components;
var c = extend(true, {}, config, options);
this.provide = function () {
return defineProperty({}, vueFormConfig, c);
};
this.components = (_components = {}, defineProperty(_components, c.formComponent, vueForm), defineProperty(_components, c.messagesComponent, messages), defineProperty(_components, c.validateComponent, validate), defineProperty(_components, c.fieldComponent, field), _components);
this.directives = { vueFormValidator: vueFormValidator };
}
var VueForm = function (_VueFormBase) {
inherits(VueForm, _VueFormBase);
function VueForm() {
classCallCheck(this, VueForm);
return possibleConstructorReturn(this, (VueForm.__proto__ || Object.getPrototypeOf(VueForm)).apply(this, arguments));
}
createClass(VueForm, null, [{
key: 'install',
value: function install(Vue, options) {
Vue.mixin(new this(options));
}
}, {
key: 'installed',
get: function get$$1() {
return !!this.install.done;
},
set: function set$$1(val) {
this.install.done = val;
}
}]);
return VueForm;
}(VueFormBase);
VueFormBase.call(VueForm);
// temp fix for vue 2.3.0
VueForm.options = new VueForm();
return VueForm;
})));