How to validate on input event with vee validate 3 - vue.js

I'm trying to make validation on input event, but the problem that when the event onInput fire in console.log(valid) - is not correct value because it validates the previos character, how to validate correcty on input event?
question.vue
<template>
<div>
<ValidationProvider v-slot="{ errors, passed }" name="question" rules="required|max:10000">
<e-form-group :error="errors[0]">
<template v-slot:label>
Question Text
</template>
<e-textarea
v-model="question"
name="question"
size="small"
#input="onInput($event, passed, name)"
></e-textarea>
</e-form-group>
</ValidationProvider>
<ValidationProvider v-slot="{ errors, passed }" name="button" rules="required|max:10000">
<e-form-group :error="errors[0]">
<template v-slot:label>
Button Text
</template>
<e-textarea v-model="button" name="button" size="small" #input="onInput($event, passed, name)"></e-textarea>
</e-form-group>
</ValidationProvider>
</div>
</template>
<script>
export default {
name: 'Question',
props: {
questionId: {
type: Number
}
},
data: () => ({
question: '',
button: '',
}),
methods: {
onInput(event, valid, name) {
console.log(event);
console.log(valid);
if (!valid) this.$emit({questionId: this.questionId, name: name})
},
},
};
</script>

The default is to validate on input already, so what you want to do is trigger the validation yourself, get the result, and then emit your event:
<ValidationProvider v-slot="{ errors, validate }" name="question" rules="required|max:10000">
<e-form-group :error="errors[0]">
<template v-slot:label>
Question Text
</template>
<e-textarea
v-model="question"
name="question"
size="small"
#input="onInput($event, validate, name)"
></e-textarea>
</e-form-group>
</ValidationProvider>
methods: {
onInput(event, validate, name) {
var self = this;
validate(event).then(function(result) {
if (result.valid) self.$emit({questionId: self.questionId, name: name})
});
},
},

Related

vuejs2 slots how to communicate with parent

I want to create a formRow component that have a slot for the input field and vee-validate for validation
this is my markup
//form
<vFormRow validate="required" v-slot="{ onInput, errors }">
<vTextfield #input="onInput" :errors="errors"/>
</vFormRow>
I tried to emit the value to the parent and get the value with #input="onInput" on the slot, but this doesn't work
//formrow
<template>
<div class="mb-4">
<v-validation-provider
v-slot="{ errors }"
:rules="validate"
>
<label>{{ label }}</label>
<slot
:onInput="onInput"
:errors="errors"
/>
<vFormError
:message="errors[0]"
/>
</v-validation-provider>
</div>
</template>
<script>
import { ValidationProvider as vValidationProvider } from 'vee-validate'
import vRadioButton from '#primitives/textfield'
import vFormError from '#primitives/formError'
export default {
components: {
vTextfield,
vFormError,
vValidationProvider
},
props: {
value: {
type: String,
default: '',
},
validate: {
type: String,
required: true,
}
},
methods: {
onInput(value) {
console.log(value)
}
}
}
</script>
//textfield
<template>
<div>
<input :value="value" #input="onInput"/>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
errors: {
type: Array,
default: () => {}
}
},
data: {
return {
// local_value: ''
}
}
methods: {
onInput(e) {
// this.local_value = e.target.value
this.$emit('input', e.target.value)
}
}
}
</script>
What I'm doing wrong?
You're not actually passing the slot props to <vTextfield>. I think you might be assuming that data bindings and event handlers on the <slot> are automatically applied to the slot's children, but that's not true.
To use the value slot prop, destructure it from v-slot, and bind it to the <vTextfield> child:
<vFormRow validate="required" v-slot="{ value }">
<vTextfield :value="value" />
</vFormRow>
You could also pass the onInput method as a slot prop:
// vFormRow.vue
<slot :onInput="onInput" />
Then bind the event handler to <vTextfield>:
<vFormRow validate="required" v-slot="{ onInput }">
<vTextfield #input="onInput" />
</vFormRow>

VeeValidate 3 setErrors error message for current input change

I have validation on two inputs. And I want to set the error message on two inputs using the setErrors() method of vee-validate the same time when I change data in the input.
Problem: Don't show message error on current input change data
Expect Result: I want to see the message error on two inputs.
<template>
<div>
<ValidationObserver ref="adad" v-slot="{ passes }">
<ValidationProvider name="min" mode="lazy" rules="required" v-slot="{ errors }">
<input type="number" v-model.number="minValue" #change="handleErrors">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<ValidationProvider name="max" mode="lazy" rules="required" v-slot="{ errors }">
<input type="number" v-model.number="maxValue" #change="handleErrors">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<button #click="passes(handleErrors)">Validate</button>
</ValidationObserver>
</div>
</template>
<script>
import { extend } from "vee-validate";
extend("between", {
params: ["min", "max"],
validate(value, { min, max }) {
return value >= min && value <= max;
},
message: "This field value must be between {min} and {max}"
});
export default {
data: () => ({
minValue: 0,
value: 1,
maxValue: 10
}),
methods: {
handleErrors() {
console.log("====> handleErrors");
this.$refs.adad.setErrors({
min: "hello world",
max: "hello world"
});
}
}
};
</script>

How to pass form input elements via scoped slots to child component

How do I access the data entered in my input elements, which are passed through via a slot, to my child component that opens up a modal with the form elements inside of it?
I've been reading the vue docs about scoped slots but honestly, I just can't figure it out how to make it work in my example. None of the examples make use of an input element with a v-model that is being passed to the child component.
I have created a component "BaseFormModal" which contains the following code:
Note that the validation (vee-validate) occurs inside here, so this child component emits a "submit" event when the data is considered valid, which I then pick up in my parent component.
<template v-slot:default="slotProps">
<b-modal ref="base-form-modal" :title="title" :no-close-on-backdrop="true" #ok.prevent="onSubmit">
<validation-observer ref="observer" v-slot="{handleSubmit}">
<b-form ref="form" #submit.stop.prevent="handleSubmit(onSubmit)">
<slot />
</b-form>
</validation-observer>
</b-modal>
</template>
<script>
import { ValidationObserver } from 'vee-validate'
export default {
name: 'BaseFormModal',
components: {
ValidationObserver,
},
props: {
title: {
type: String,
required: true,
},
},
data () {
return {
formData: {},
}
},
methods: {
async onSubmit () {
let valid = await this.$refs.observer.validate()
if (!valid) {
return
}
this.$emit('submit', this.formData)
this.$nextTick(() => {
this.$refs['base-form-modal'].hide()
})
this.formData = {}
},
showModal () {
this.$refs['base-form-modal'].show()
},
},
}
</script>
<style lang="scss" scoped>
</style>
In my page, I have a button which opens up the modal, like so:
<b-button variant="primary" #click="$refs['addOrgUserModal'].showModal()">
<i class="far fa-plus" aria-hidden="true" /> {{ $t('organisation_settings_manage_users_add_user') }}
</b-button>
Then I have defined the base form modal component in my page as this:
<base-form-modal
ref="addOrgUserModal"
:title="$tU('organisation_settings_manage_users_add_user_modal_title')"
#submit="addOrgUser"
>
<b-row>
<b-col md="6">
<form-control-wrapper :rules="{required: true}" :label="$tU('first_name_label')">
<b-form-input
v-model="user.firstName"
type="text"
lazy-formatter
:formatter="trimSpaces"
:placeholder="$t('first_name_field_placeholder')"
/>
</form-control-wrapper>
</b-col>
<b-col md="6">
<form-control-wrapper :rules="{required: true}" :label="$tU('family_name_label')">
<b-form-input
v-model="user.familyName"
type="text"
lazy-formatter
:formatter="trimSpaces"
:placeholder="$t('family_name_field_placeholder')"
/>
</form-control-wrapper>
</b-col>
</b-row>
</base-form-modal>

Get all input values from dynamic form in Vuejs

I have some reusable components:
TextField
Form
And a component where I import them both. I pass the TextField component as props to the Form. In this Form component I have a submit button which needs to get all the values from the passed TextField components. As far as I could read in the docs I could use the v-model to get the values. But for some reason I can't find how to get those values in my Form component. Maybe I am missing something and I hope someone can help me with it. I already took a look at this question: Get all Input values - Vuejs. However, this didn't solve my problem.
The TextField component looks like this:
<template>
<v-text-field
v-model="vModel"
:rules="rules"
:counter="counter"
:label="label"
:required="required"
:placeholder="placeholder"
:value="value"
></v-text-field>
</template>
<script>
export default {
name: "TextField",
props: {
rules: Array,
counter: Number,
label: String,
required: {
type: Boolean,
default: true
},
placeholder: String,
value: String
},
data: () => ({
vModel: '',
}),
};
</script>
The Form component looks like this:
<template>
<v-form>
<v-container>
<slot></slot>
<v-btn class="mr-4" #click="submit">Submit</v-btn>
</v-container>
</v-form>
</template>
<script>
export default {
methods: {
submit () {
// console.log form data
}
}
};
</script>
And the component where I import both components:
<template>
<Form>
<v-row>
<v-col cols="12" md="4">
<TextField :label="'Email address'" :vModel="email"/>
</v-col>
</v-row>
</Form>
</template>
<script>
import Form from "../../../components/Form/Form";
import TextField from "../../../components/Form/TextField";
export default {
components: {
Form,
TextField,
},
data: () => ({
email: '',
})
};
</script>
I also created a CodeSandBox.
Can anyone give me some advice on how I might get the v-model values from the TextField components inside the Form component? If it's not possible, or I might do this better in another way please let me know.
v-model is simply a shorthand to create two things:
a :value binding (passed as a prop to your component)
a #input event handler
Currently, the vModel variable in your TextField component may receive the value, but it does not send it back to the parent component.
You could try something like this:
TextField
<template>
<v-text-field
v-model="localValue"
:rules="rules"
:counter="counter"
:label="label"
:required="required"
:placeholder="placeholder"
></v-text-field>
</template>
<script>
export default {
name: "TextField",
props: {
rules: Array,
counter: Number,
label: String,
required: {
type: Boolean,
default: true
},
placeholder: String,
value: String
},
data: () => ({
localValue: '',
}),
created() {
this.localValue = this.value;
this.$watch('localValue', (value) => {
this.$emit('input', value);
}
}
};
</script>
Form
<template>
<v-form>
<v-container>
<slot></slot>
<v-btn class="mr-4" #click="submit">Submit</v-btn>
</v-container>
</v-form>
</template>
<script>
export default {
props: ['form'],
methods: {
submit () {
alert(JSON.stringify(this.form));
}
}
};
</script>
Your final component:
<template>
<Form :form="form">
<v-row>
<v-col cols="12" md="4">
<TextField :label="'Email address'" v-model="formvalentin#whatdafox.com"/>
</v-col>
</v-row>
</Form>
</template>
<script>
import Form from "../../../components/Form/Form";
import TextField from "../../../components/Form/TextField";
export default {
components: {
Form,
TextField,
},
data: () => ({
form: {
email: ''
}
})
};
</script>
More information on v-model: https://v2.vuejs.org/v2/guide/forms.html
Not exactly sure as to what you doing in your code, but to simplify the template and component with v-model would look something like the code below.
ContactUs.vue
<template>
<form method="POST"
autocomplete="off"
class="form--container relative box-col-center w-full"
name="contact"
action="/form/contact"
#submit.prevent="onSubmit">
<input class="form--field font-poppins w-full"
type="text"
name="name"
v-model="field.name"
placeholder="Your name"
autocomplete='name'>
<input class="form--field font-poppins w-full"
type="email"
id="email"
name="email"
v-model="field.email"
placeholder="Your email"
autocomplete='email'>
<textarea class="textarea form--field font-poppins w-full"
id="body"
name="body"
placeholder="I'd like to know about ..."
v-model="field.body"
rows="5">
</textarea>
<button type="submit"
#click.prevent="onSubmit()"
class="container--row container--center btn--purple btn--040 w-2/3 lg:w-full">
<span class="text--w-full uppercase">Submit</span>
</button>
</form>
</template>
<script>
export default {
name: 'ContactUs',
data() {
return {
fields:{
name: '',
email: '',
body: ''
},
}
},
methods: {
onSubmit() {
let vm = this;
return new Promise((resolve, reject) => {
axios.post('/forms/contact', vm.fields)
.then(response => {
resolve(response.data);
}).catch(err => {
reject(err.response);
});
});
},
}
}
</script>
If you are attempting to create a Form service class then it would almost the same, except you would abstract the form logic to that class.
FormService.js
export default class Form {
/**
* Create a new Form instance.
*
* #param {object} data
* #param type
*/
constructor(data) {
this.originalData = data;
for (let field in data) {
this[field] = data[field];
}
}
/**
* Submit the form.
*
* #param {string} requestType
* #param {string} url
*/
submit(requestType, url) {
return new Promise((resolve, reject) => {
axios[requestType](url, this.data())
.then(response => {
resolve(response.data);
}).catch(err => {
reject(err.response);
});
});
}
}
and then in your component, you would inject the form services and use the data
ContactUs.vue
import Form from '../services/FormService.js';
export default {
name: 'ContactUs',
data() {
return {
fields: new Form({
name: '',
email: '',
body: ''
}),
}
},
methods: {
onSubmit() {
let self = this;
self.form.post('/forms/contact')
.then(response => {
}).catch((err) => {
})
},
}

Vue: How do you bind a value to an input through a wrapper component?

I know you can use v-model to bind a value to an input in the same component. How do you create a wrapper component for an input and bind a value to it?
Login.vue
<template>
<div id="Login">
<Input v-bind:value="email"/>
<Input v-bind:value="password"/>
</div>
</template>
<script>
import Input from './Input.vue'
import Button from './Button'
export default {
name: 'Login',
components: {
Input,
Button,
},
data: () => ({
email:'test',
password:'test',
}),
methods: {
login: () => { debugger; }, //this.email and this.password are still set to test
}
}
</script>
Input.vue
<template>
<div class="input>
<input v-model="value"/>
</div>
</template>
<script>
export default {
name: 'Input',
props: {
value: String,
},
}
</script>
Current set up results in
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
Is the only way to do this by emitting an event?
If I got it correctly, you can try to create transparent wrapper (in my case AppInput)
SomeParent.vue
<div>
<AppInput v-model="parentModel" />
</div>
AppInput.vue
<template>
<input
class="app-input"
v-bind="$attrs"
:value="value"
v-on="{
...$listeners,
input: event => $emit('input', event.target.value)
}">
</template>
<script>
export default {
name: "AppInput",
inheritAttrs: false,
props: ["value"]
};
</script>
one of articles
The best way is use v-model for wrapper and on/emit for input
<div id="Login">
<Input v-model="email"/>
<Input v-model="password"/>
</div>
...
<div class="input>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</div>
You can implement v-model directly in the input component by doing so.
<template>
<div class="input>
<input :value="value" #input="$emit('input', $event.target.value)"/>
</div>
</template>
<script>
export default {
name: 'Input',
props: ["value"]
}
</script>
And then use it in your parent component like this:
<template>
<div id="Login">
<Input v-model="email"/>
<Input v-model="password"/>
</div>
</template>
<script>
import Input from './Input.vue'
import Button from './Button'
export default {
name: 'Login',
components: {
Input,
Button,
},
data: () => ({
email:'test',
password:'test',
}),
methods: {
login: () => { debugger; }, //this.email and this.password are still set to test
}
}
</script>
See here