UNPKG

vue-form

Version:

Form validation for Vue.js

1,273 lines (1,112 loc) 35.7 kB
(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; })));