From 5a26ef1b4f4aaccd0c84137cd1edd7ecb0c8bce1 Mon Sep 17 00:00:00 2001 From: David Higgins Date: Thu, 14 Dec 2017 19:39:51 -0500 Subject: [PATCH 1/2] fixes #358 - support "validateBeforeSubmit" with async validators * added `validateAsync` (default: false) form option * added `isAsync` parameter to FormGenerator.validate() * FormGenerator.validate() and AbstractField.validate() return Promise when `validateAsync` or `isAsync` is true * renamed `click` handler to `onClick` in FieldSubmit * added `onValidationError(model, schema, errors)` to FieldSubmit schema to handle validation errors * added async validator support for FieldSupport * changed `each` to `forEach` in various places, as "each" is an alias to "forEach" and "forEach" looks more explicit * removed call to Vue.util.hyphenate as this is no longer supported by Vue, replaced with equivalent `String.replace` expression * updated fieldSubmit.spec to add "valid form" and "invalid form" tests, valid forms will always call `onSubmit`, invalid forms will not call `onSubmit` (when validateBeforeSubmit = true) * various code clean up --- src/fields/abstractField.js | 77 ++++++++++++++-------- src/fields/core/fieldSubmit.vue | 30 +++++---- src/formGenerator.vue | 74 +++++++++++++-------- src/utils/validators.js | 3 +- test/unit/specs/VueFormGenerator.spec.js | 15 ++--- test/unit/specs/fields/fieldSubmit.spec.js | 53 ++++++++++----- test/unit/specs/util.js | 2 +- 7 files changed, 159 insertions(+), 95 deletions(-) diff --git a/src/fields/abstractField.js b/src/fields/abstractField.js index 4752bfc6..cee32aab 100644 --- a/src/fields/abstractField.js +++ b/src/fields/abstractField.js @@ -1,4 +1,4 @@ -import { get as objGet, each, isFunction, isString, isArray, debounce } from "lodash"; +import { get as objGet, forEach, isFunction, isString, isArray, debounce } from "lodash"; import validators from "../utils/validators"; import { slugifyFormID } from "../utils/schema"; @@ -59,53 +59,72 @@ export default { methods: { validate(calledParent) { + // console.log('abstractField', 'validate', calledParent); this.clearValidationErrors(); + let validateAsync = objGet(this.formOptions, "validateAsync", false); + + let results = []; if (this.schema.validator && this.schema.readonly !== true && this.disabled !== true) { let validators = []; if (!isArray(this.schema.validator)) { validators.push(convertValidator(this.schema.validator).bind(this)); } else { - each(this.schema.validator, (validator) => { + forEach(this.schema.validator, (validator) => { validators.push(convertValidator(validator).bind(this)); }); } - each(validators, (validator) => { - let addErrors = err => { - if (isArray(err)) - Array.prototype.push.apply(this.errors, err); - else if (isString(err)) - this.errors.push(err); - }; - - let res = validator(this.value, this.schema, this.model); - if (res && isFunction(res.then)) { - // It is a Promise, async validator - res.then(err => { - if (err) { - addErrors(err); + forEach(validators, (validator) => { + if(validateAsync) { + results.push(validator(this.value, this.schema, this.model)); + } else { + let result = validator(this.value, this.schema, this.model); + if(result && isFunction(result.then)) { + result.then((err) => { + if(err) { + this.errors = this.errors.concat(err); + } let isValid = this.errors.length == 0; this.$emit("validated", isValid, this.errors, this); - } - }); - } else { - if (res) - addErrors(res); + }); + } else if(result) { + results = results.concat(result); + } } }); - } - if (isFunction(this.schema.onValidated)) { - this.schema.onValidated.call(this, this.model, this.errors, this.schema); - } + let handleErrors = (errors) => { + // console.log('abstractField', 'all', errors); + let fieldErrors = []; + forEach(errors, (err) => { + // console.log('abstractField', 'err', err); + if(isArray(err) && err.length > 0) { + fieldErrors = fieldErrors.concat(err); + } else if(isString(err)) { + fieldErrors.push(err); + } + }); + // console.log('abstractField', 'fieldErrors', 'final', fieldErrors); + if (isFunction(this.schema.onValidated)) { + this.schema.onValidated.call(this, this.model, fieldErrors, this.schema); + } - let isValid = this.errors.length == 0; - if (!calledParent) - this.$emit("validated", isValid, this.errors, this); + let isValid = fieldErrors.length == 0; + if (!calledParent) { + this.$emit("validated", isValid, fieldErrors, this); + } + this.errors = fieldErrors; + // console.log('abstractField', 'this.errors', this.errors); + return fieldErrors; + }; + + if(!validateAsync) { + return handleErrors(results); + } - return this.errors; + return Promise.all(results).then(handleErrors); }, debouncedValidate() { diff --git a/src/fields/core/fieldSubmit.vue b/src/fields/core/fieldSubmit.vue index 4bf975ee..346e983d 100644 --- a/src/fields/core/fieldSubmit.vue +++ b/src/fields/core/fieldSubmit.vue @@ -1,26 +1,34 @@