Reputation:
I am trying to validate both input fields when one changes its value. This is required because otherwise the validation between those two fields would not work properly.
I created an example to reproduce the problem, the html code should be self explanatory
<div id="app">
<v-app id="inspire">
<v-text-field
:value="values[0]"
:rules="firstRules"
@input="setFirstValue"
></v-text-field>
<v-text-field
:value="values[1]"
:rules="secondRules"
@input="setSecondValue"
></v-text-field>
</v-app>
</div>
It is important to note that a v-model
is not possible because this component takes in the values as a prop
and passes the updated values back to the parent via emitting update events.
The vue instance:
new Vue({
el: '#app',
data () {
return {
values: [5345, 11],
firstRules: [true],
secondRules: [true]
}
},
created: function () {
this.setFirstValue(this.values[0])
this.setSecondValue(this.values[1])
},
computed: {
firstValidation: function () {
return [value => value.length < this.values[1].length || "Must have less characters than second value"]
},
secondValidation: function () {
return [value => value.length > this.values[0].length || "Must have more characters than first value"]
}
},
methods: {
setFirstValue: function (newValue) {
this.values[0] = newValue
this.firstRules = this.validateValue(this.values[0], this.firstValidation)
this.secondRules = this.validateValue(this.values[1], this.secondValidation)
},
setSecondValue: function (newValue) {
this.values[1] = newValue
this.secondRules = this.validateValue(this.values[1], this.secondValidation)
this.firstRules = this.validateValue(this.values[0], this.firstValidation)
},
validateValue: function (value, rules) {
for (const rule of rules) {
const result = rule(value)
if (typeof result === 'string') {
return [result]
}
}
return [true]
}
}
})
On "start" the rules return a valid state but I want to validate both fields when loading the component (created hook?) to update this state immediately.
I have to put the validation rules to the computed properties because they have to access the current values. Otherwise they would validate old values.
Each input event will validate both fields and updates the rules state.
I created an example to play around here
https://codepen.io/anon/pen/OeKVME?editors=1010
Unfortunately two problems come up:
How can I setup a validation for both fields when one field updates?
Upvotes: 17
Views: 24289
Reputation: 141
If you cannot call this.$refs.form.validate();
because it is in a parent component, what you can do is put a ref attribute on your v-text-field.
<v-text-field ref="textFieldRef"></v-text-field>
And call it's validate fonction where you want
this.$refs.textFieldRef.validate();
Upvotes: 1
Reputation: 1
I have to do like below to make it work.
watch: {
rangeAmuount: {
async handler() {
await this.$nextTick()
if (this.$refs.form) (this.$refs.form as any).validate()
},
deep: true,
},
}
PS: I'm using typescript on Vue2.
Upvotes: 0
Reputation: 55644
Seems like you're overthinking things.
By default, a vuetify input's validation logic only triggers when the value bound to that input changes. In order to trigger the validation for the other input, you can wrap both inputs in a v-form
component and give it a ref
attribute. That way, you'll have access to that component's validate
method, which will trigger the validation logic for any inputs inside the form.
The template would look something like this:
<v-form ref="form">
<v-text .../>
<v-text .../>
</v-form>
And to trigger the validation in your script:
mounted() {
this.$refs.form.validate();
}
The above will validate the form when the component is mounted, but you'll also need to trigger the validation for both inputs whenever either input's value changes. For this, you can add a watcher to values
. However, you'll need to call the form's validate
method after Vue has updated the DOM to reflect the change in values
.
To do this, either wrap the call in a this.$nextTick
call:
watch: {
values() {
this.$nextTick(() => {
this.$refs.form.validate();
});
}
}
Or use an async
function and await this.$nextTick
:
watch: {
async values() {
await this.$nextTick();
this.$refs.form.validate();
}
}
So now validation will trigger for both inputs when the component is initialized and whenever either value changes. However, if you prefer to keep the validation call in one spot instead of in both the mounted
hook and the values
watcher, you can make the watcher immediate
and get rid of the call in the mounted
hook.
So here's the final example:
watch: {
immediate: true,
async handler() {
await this.$nextTick();
this.$refs.form.validate();
}
}
So now the validation logic is triggering when it would be expected to, but there is still one issue with your validation logic. When your component initializes, the values
data property is set to an array of Number
type values, which don't have a length
property. So if, for example, you changed just the first input to "5"
and the second input was still 11
, then (11).length
is undefined
and "5".length < undefined
is false
.
Anyways, you'll need to change the values you're comparing to strings before comparing their lengths. Something like this:
value => (value + '').length < (this.values[1] + '').length
Finally, because you are able to dynamically call validate
on the form, there's an opportunity to reduce much of the complexity of your component.
Here's a simplified version:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return {
values: [5345, 11]
}
},
computed: {
rules() {
const valid = (this.values[0] + '').length < (this.values[1] + '').length;
return {
first: [() => valid || "Must have less characters than second value"],
second: [() => valid || "Must have more characters than first value"]
};
}
},
watch: {
values: {
immediate: true,
async handler() {
await this.$nextTick();
this.$refs.form.validate();
}
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-app id="inspire">
<v-form ref="form">
<v-text-field v-model="values[0]" :rules="rules.first"></v-text-field>
<v-text-field v-model="values[1]" :rules="rules.second"></v-text-field>
</v-form>
</v-app>
</div>
Upvotes: 26