I have a form component that is responsible for handling validation of all inputs contained inside it. When the form is mounted it starts listening for errors emitted from child components, and it runs validation, on submit before emitting its own submit event which is consumed by parent components. Code below will explain better, this system works nicely on single forms, but when you introduce scoped forms (multiple forms on the same page) this breaks, and passing scope to validateAll bypasses validation altogether.
Here is some code (some things have been omitted, but this is all working code):
formHelper.js (mixin)
import {Event, Logger} from "@app/common/services";
export const formHelper = {
methods: {
validateForm(scope) {
//On button pressed run validation
Promise.all([
Event.emit("v:validate-child"), // Fire each child validation
this.$validator.validateAll(scope) // Fire parent validation
])
.then(() => this.submitForm());
},
submitForm() {
// If after we process all child errors we still have some in the parent bag,
// then we need to abort and show an error, fields will be marked individually
if (this.errors.any()) {
Logger.error("Oops! There are some problems here...");
} else {
this.$emit("submit");
}
}
},
mounted() {
Event.on("v:errors-changed", (newErr, oldErr) => {
// Go through each error and add it to the form error bag if we don't have it already registered
newErr.forEach(error => {
if (!this.errors.has(error.field)) {
this.errors.add(error.field, error.msg);
}
});
// if we get old errors, then lets remove all of them from the form bag
if (oldErr) {
oldErr.forEach(error => {
this.errors.remove(error.field);
});
}
});
}
};
Form component
// as you can see the form name is optional and if not provided it will be a random unique string
<template lang="html">
<form @submit.prevent="validateForm(name)" :data-vv-scope="name">
<slot></slot> <!--input components will live here-->
<div class="panel-footer form-footer text-right" v-if="$slots['form-actions']">
<slot name="form-actions"></slot>
</div>
</form>
</template>
<script lang="babel">
import {formHelper} from "../../formHelper.mixin";
export default {
name: "form",
props: {
name: {
type: String,
default: Math.random().toString(36).substr(2)
}
},
mixins: [formHelper]
};
</script>
Input helper (mixin)
import {isArray} from "../../utils";
import {Event, Logger} from "../../services";
export const inputHelper = {
props: {
...,
validation: {type: String, default: ""}
},
methods: {
onValidate() {
this.$validator.validateAll()
.catch(() => {
Event.emit("v:errors-changed", this.errors.errors);
});
},
},
computed: {
...
},
mounted() {
// This will be fired from the form component whenever submit is dispatched
Event.on("v:validate-child", this.onValidate);
//Watch for the changes to the childs error bag and pass back to the parent
this.$watch(() => this.errors.errors, (newErr, oldErr) => {
Event.emit("v:errors-changed", newErr, oldErr);
});
},
beforeDestroy() {
// When destroying the element remove the listeners on the bus.
// Useful for dynamically adding and removing child components
Event.emit("v:errors-changed", [], this.errors.errors);
Event.off("v:validate-child", this.onValidate);
}
};
Input component
<template>
<div :class="{'form-group form-material floating': true, 'has-error': errors.has(nameKey)}">
<div :class="{'input-group': addon}">
<span v-if="addon" class="input-group-addon">{{addon}}</span>
<div :class="{'form-control-wrap': addon}">
<input class="form-control"
:id="id"
:class="inputClasses"
v-bind="{ type, value, placeholder, disabled }"
@input="$emit('input', $event.target.value)"
@blur="e => $emit('blur', e)"
v-validate="validation"
:data-vv-as="label"
:name="nameKey" />
<span class="form-control-feedback"
v-if="fields.failed(nameKey) || fields.passed(nameKey)">
<i class="icon"
:class="{'md-close': fields.failed(nameKey), 'md-check light-green-500': fields.passed(nameKey)}"></i>
</span>
<small class="help-block" v-if="errors.has(nameKey)">
{{ errors.first(nameKey) }}
</small>
<label class="floating-label" :for="id">{{label | capitalize}}</label>
</div>
</div>
</div>
</template>
<script lang="babel">
import {inputHelper} from "../../inputHelper.mixin";
export default {
name: "InputText",
props: {
type: {
type: String,
default: "text"
}
},
mixins: [inputHelper]
};
</script>
Usage
<form-component @submit="submitForm" name="someUniqueFormName">
<input-text-component
label="I'm a label"
v-model="someModal"
validation="required|min:2|max:3" />
<div slot="form-actions">
<button type="submit" class="btn btn-primary">submit</button>
</div>
</form-component>
As you can see above is a little bit complex, but is really flexible because you can pass the veeValidate rule to the input component. It works nicely if you only have 1 form and are not trying to use data-vv-scope. Any clue as to what I might be missing here? do I need to specify data-vv-scope with he form name on every child? Any help and suggestions would be appreciated.
You would need to specify the data-vv-scope property on each child unfortunately as it does not work for custom components, only native inputs because they have the form property set.
@logaretm It seems this is not fixed and we need to do for child componets like this:
<!-- there is no data-vv-scope here, cause it doesn't work -->
<v-form>
<!-- we need to add scope to also data-vv-name to make it work -->
<v-text-field
label="Address"
v-model="newBlog.address"
:error-messages="errors.collect('add-blog.address')"
v-validate="'required'"
data-vv-scope="add-blog"
data-vv-name="add-blog.address"
required
></v-text-field>
</v-form>
It would be nice if you mention this in doc.
it would be good if you update the docs specifying that data-vv-scope works only with the form. The docs made me believe it will work with any childrens
Would be even better if this feature can be implemented :)
Most helpful comment
it would be good if you update the docs specifying that data-vv-scope works only with the form. The docs made me believe it will work with any childrens
Would be even better if this feature can be implemented :)