Checking validation of VTextField - vue.js

I have a CodePen demonstrating the issue at Checking validation of VTextField. The CodePen code is below as well.
Inside of the updateTitle method, I would like to be able to check to see whether or not the value is valid, because I need to run some custom logic when the value changes and what is executed will depend upon whether or not the value is valid.
I can see that v-text-field has a valid and validationState property. Is it possible to access either property inside of updateTitle?
HTML
<div id="app">
<v-app id="inspire">
<v-form>
<v-container>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
:value = "title"
:rules="[rules.required, rules.counter]"
label="Title"
counter
maxlength="20"
#input="updateTitle"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-form>
</v-app>
</div>
JS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
title: 'Preliminary report',
rules: {
required: value => !!value || 'Required.',
counter: value => value.length <= 10 || 'Max 10 characters'
}
}
},
methods: {
updateTitle( value ) {
console.log( "update", value );
}
}
})

The answer is to provide a ref to the v-text-field. This allows one to access the field via the special $refs property and call the validate function. The changes to the CodePen are:
<v-text-field
ref = "theField"
:value = "title"
:rules="[rules.required, rules.counter]"
label="Title"
counter
maxlength="20"
#input="updateTitle"
></v-text-field>
updateTitle( value ) {
const isValid = this.$refs.theField.validate();
console.log( "update", value, isValid );
}

Related

How to use v-form inside a v-for and perform validation for a specific form?

I have an array of objects which I should loop through and show a form for each object's properties. The Save button is out of the for loop. In the attached sandbox, the 2nd object doesn't contain lastname. So, how do I perform validation on click of Save button only for the 2nd form? And is there any way to validate all the forms at once? Please refer to the sandbox for a better understanding.
https://codesandbox.io/s/jolly-kepler-m260fh?file=/src/components/Playground.vue
Check this codesandbox I made: https://codesandbox.io/s/stack-72356987-form-validation-example-4yv87x?file=/src/components/Playground.vue
You can validate all v-text-field at once if you move the v-form outside the for loop. All you need to do is give the form a ref value and a v-model to make use of the built in vuetify validation methods.
<template>
<v-container class="pl-10">
<v-form ref="formNames" v-model="validForm" lazy-validation>
<v-row v-for="(name, index) in names" :key="index">
<v-col cols="12">
<v-text-field
v-model="name.firstName"
outlined
dense
solo
:rules="rulesRequired"
/>
</v-col>
...
</v-row>
</v-form>
<v-btn type="submit" #click="submitForm" :disabled="!validForm">
Submit
</v-btn>
</v-container>
</template>
Then in the submit button all you need to do is call the validate() method of the form through the $refs object. You can also disable the submit button if any of the elements in the form don't pass the validation rules using the v-model of the form to disable the submit button.
<script>
export default {
name: "playground",
data: () => ({
validForm: true,
names: [
{
firstName: "john",
lastName: "doe",
age: 40,
},
{
firstName: "jack",
lastName: "",
age: 30,
},
],
rulesRequired: [(v) => !!v || "Required"],
}),
methods: {
submitForm() {
if (this.$refs.formNames.validate()) {
// Form pass validation
}
},
},
};
</script>
As submit button is outside of the forms. We can perform that validation on submit event by iterating the names array and check if any value is empty and then assign a valid flag value (true/false) against each object.
Demo :
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
names: [
{
firstName: "john",
lastName: "doe",
age: 40,
valid: false
},
{
firstName: "jack",
lastName: "",
age: 30,
valid: false
},
],
requiredRule: [v => !!v || 'Value is required']
}),
methods: {
submitForm() {
this.names.forEach((obj, index) => {
if (!obj.lastName) {
obj.valid = false;
console.log(
`${index + 1}nd form is not valid as LastName is not available`
);
} else {
obj.valid = true;
}
});
// Now you can filter out the valid form objects based on the `valid=true`
}
}
})
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.6/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.6/dist/vuetify.min.css"/>
<div id="app">
<v-app id="inspire">
<v-container class="pl-10">
<v-row v-for="(name, index) in names" :key="index">
<v-form v-model="name.valid">
<v-col cols="12">
<v-text-field v-model="name.firstName" outlined dense solo required :rules="requiredRule" />
</v-col>
<v-col cols="12">
<v-text-field v-model="name.lastName" outlined dense solo required :rules="requiredRule" />
</v-col>
<v-col cols="12">
<v-text-field v-model="name.age" outlined dense solo required :rules="requiredRule" />
</v-col>
</v-form>
</v-row>
<v-btn type="submit" #click="submitForm"> Submit </v-btn>
</v-container>
</v-app>
</div>

"this is null" when change in v-select [NuxtJs]

this is my first post! Hope you can help.
So I'm on some new project to migrate to nuxtJs.
I have some trouble with my v-select and v-model, here's the code:
_id.vue:
<template>
<v-container fluid white>
<v-card>
<v-card-title class="headline">
TEST:
</v-card-title>
<v-card-text>
<p>info :</p>
<p>{{this.host.typeS}}</p>
<v-select
v-model="this.host.typeS"
:items="this.fields.typeS"
item-value="value"
item-text="value"
dense outlined>
</v-select>
</v-card-text>
</v-card>
</v-container>
</template>
script
export default {
name: 'EditPage',
data() {
return {
tab: null,
fields: [],
host: {},
tabHeaders: ["tab1", "tab2", "tab3", "tab4", "tab5", "tab6"]
}
},
async fetch() {
this.fields = await fetch("APIfields")
.then(res => res.json());
if (this.$nuxt._route.params.id) {
this.host = await fetch("APIhost" + this.$nuxt._route.params.id).then(res => res.json());
} else {
this.host = {};
}
}
}
responses
APIfields:
{"typeS": [{"value": "p","order": 100}, {"value":"v","order": 100}],
"otherThings": [{"value":"se", "order": 100},{"value":"sa", "order": 100}]}
APIhost/id:
{"typeS": "p",
"otherThings": "se"}
When everything run, My select is initiated with the value "p" and when clicked I see all the values this field can have.
When I try selecting "v" in the v-select, I'm redirected to my error layout, and the console say "Type error: this is null".
Without the v-model, I don't have any error, but the v-select have no selected value.
The goal is to initialise a form based on an existing host, change the data inside the form and then submit it to the database. The form should also be used to create new hosts (empty form)
The same error appear in my textfields and checkboxes.
Do you have any idea or track I can follow?
you don't need this when working on vue templates
<template>
<v-container fluid white>
<v-card>
<v-card-title class="headline">
TEST:
</v-card-title>
<v-card-text>
<p>info :</p>
<p>{{host.typeS}}</p>
<v-select
v-model="host.typeS"
:items="fields.typeS"
item-value="value"
item-text="value"
dense outlined>
</v-select>
</v-card-text>
</v-card>
</v-container>
</template>
note that :items takes an array but you are using fields.typeS so it's better to set field in data as
fields: {}
since it's an object

Adding more than 1 field in v-for is causing an infinite loop

I am trying to add a payment form for the user to fill out of field.type === 'payment'. However, when I add more than one field to add inside of the v-for loop, I get a “You may have an infinite update loop in a component render function.” error. What can I do to avoid this? Here is a snippet of what I'm trying to do
<div v-for="(field, key) in page.fields" :key="key">
<v-row v-if="field.type === 'payment'">
<v-col cols="12" sm="8">
<v-text-field //ADDING THIS FIELD BY ITSELF WORKS FINE
label="Card Number"
prepend-inner-icon="credit_card"
v-model="card_number"
/>
</v-col>
<v-col cols="12" sm="4">
<v-text-field //WHEN I TRY TO ADD IN THIS FIELD, THE LOOP ERROR OCCURS
label="CVV"
v-model="cvv"
/>
</v-col>
</v-row>
</div>
<script>
computed: {
...mapGetters('formbuilder', ['form'])
},
watch: {
form(newVal) {
this.page = newVal;
}
},
data() {
return {
cvv: '',
card_number: '',
page: {}
}
}
</script>

Validate vuetify textfield only on submit

temp.vue
<v-form ref="entryForm" #submit.prevent="save">
<v-text-field label="Amount" :rules="numberRule"r></v-text-field>
<v-btn type="submit">Save</v-btn>
</v-form>
<script>
export default {
data: () => ({
numberRule: [
v => !!v || 'Field is required',
v => /^\d+$/.test(v) || 'Must be a number',
],
}),
methods: save () {
if (this.$refs.entryForm.validate()){
//other codes
}
}
}
</script>
What happens here is while typing in the text field itself the rule gets executed. I want to execute the rule only on submit. How to do that in vuetify text field?
Vuetify rules are executed when the input gets value,
But if you want that to happen only on the form submit, you have remodified the rules that are being bound to that input,
Initially, rules should be an empty array, when you click on the button you can dynamically add/remove the rules as you wanted, like this in codepen
CODEPEN
<div id="app">
<v-app id="inspire">
<v-form ref="entryForm" #submit.prevent="submitHandler">
<v-container>
<v-row>
<v-col
cols="12"
md="6"
>
<v-text-field
v-model="user.number"
:rules="numberRules"
label="Number"
required
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-btn type="submit" color="success">Submit</v-btn>
</v-row>
</v-container>
</v-form>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
valid: false,
firstname: '',
user: {
number: ''
},
numberRules: []
}),
watch: {
'user.number' (val) {
this.numberRules = []
}
},
methods: {
submitHandler () {
this.numberRules = [
v => !!v || 'Field is required',
v => /^\d+$/.test(v) || 'Must be a number',
]
let self = this
setTimeout(function () {
if (self.$refs.entryForm.validate()){
//other codes
alert('submitted')
}
})
}
}
})
If you're like me and just want to prevent validation from running on every key stroke, apply validate-on-blur prop on your text fields and now validation will only be perform after user has completed typing the whole input.
So not an exact answer to the OP, but I think this is what most of us want to achieve. This prop has been documented here.
I have another way to solve this problem without setting up watchers:
<v-form lazy-validation v-model="valid" ref="form">
<v-text-field
class="w-100"
light
label="Nome"
v-model="form.nome"
:rules="[rules.required]"
rounded
required
outlined
hide-details="auto"
></v-text-field>
<v-btn
rounded
height="50"
width="200"
:disabled="!valid"
:loading="isLoading"
class="bg-btn-secondary-gradient text-h6 white--text"
#click="submitContactForm()"
>
Enviar
</v-btn>
</v-form>
There is a prop called lazy-validation on vuetify, as you can see on the docs: https://vuetifyjs.com/en/api/v-form/#functions
So, the v-form has a method that you can see through $refs called validate(), and it can return true or false, based on your form rules.
And, the function that will trigger the validation on submit will be like this:
submitContactForm() {
const isValid = this.$refs.form.validate();
if (isValid) {
alert("Obrigado pelo contato, sua mensagem foi enviada com sucesso!");
this.form = {
nome: "",
celular: "",
email: "",
mensagem: ""
};
this.$refs.form.resetValidation(); // Note that v-form also has another function called resetValidation(), so after we empty our fields, it won't show the validation errors again.
}
},

force a new validation if another related field changed

I want to validate two textfields which are related to each other. The first one must be smaller than the second one (e.g. min/max, start date/end date).
So for the coding part I created this
HTML:
<div id="app">
<v-app id="inspire">
<v-text-field
v-model="values[0]"
:rules="firstValidation"
></v-text-field>
<v-text-field
v-model="values[1]"
:rules="secondValidation"
></v-text-field>
</v-app>
</div>
JS:
new Vue({
el: '#app',
data () {
return {
values: [1, 2]
}
},
computed: {
firstValidation: function () {
return [value => parseFloat(value) < this.values[1] || "Must be less than second value"]
},
secondValidation: function () {
return [value => parseFloat(value) > this.values[0] || "Must be greater than first value"]
}
}
})
I will also provide a snippet here
https://codepen.io/anon/pen/NZoaew?editors=1010
When I change the value of one field the other one will not revalidate. Steps to reproduce:
change the value of the first field to 12
the second field has a value of 2 so you will get an error
change the value of the second field to 22
now the form is valid but the first field still throws the error because it didn't revalidate.
remove one character from the first field
now this field revalidates and you can submit it. Is there a mechanism to revalidate the other field on changes and vice versa?
I think a possible solution would be to listen to the #input event of a field but how would you force the other field to revalidate then?
This will validate them both but only show an error in the field once the user has typed in that field:
new Vue({
el: '#app',
data () {
return {
values: [1, 2],
firstValidation: [true],
secondValidation: [true]
}
},
methods: {
validate: function (index) {
const valid = (this.values[0] - this.values[1]) < 0
if (valid) {
this.firstValidation = [true];
this.secondValidation = [true];
return;
}
if (index > 0)
this.secondValidation = ["Must be greater than first value"];
else
this.firstValidation = ["Must be less than second value"];
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.16/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-text-field
v-model="values[0]"
#input="validate(0)"
:rules="firstValidation"
></v-text-field>
<v-text-field
v-model="values[1]"
#input="validate(1)"
:rules="secondValidation"
></v-text-field>
</v-app>
</div>
(I'm not sure why the styles aren't rendering but it uses the same scripts as your codepen)
Any reason why your data model is an array? Be mindful of this. From the docs
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
i.e. when values[1] = parseFloat($event) is called, it is NOT reactive. I would avoid this if possible, otherwise use $set. Then to force validation of the field when another field changes, you will have to watch for the change and then manually call validation.
Template
<div id="app">
<v-form ref="form"> <!-- Need a reference to the form to validate -->
<v-app id="inspire">
<v-text-field
:value="values[0]"
:rules="firstValidation"
#input="$set(values, 0, parseFloat($event))"
></v-text-field>
<v-text-field
:value="values[1]"
:rules="secondValidation"
#input="$set(values, 1, parseFloat($event))"
></v-text-field>
<v-btn #click="submit">Submit</v-btn>
</v-app>
</form>
</div>
Code
Add this to your component
methods: {
submit: function(){
console.log(this.values);
},
validate: function() {
// manually call validation
this.$refs.form.validate();
}
},
watch: {
// watch for change and then validate
values: 'validate'
}
See updated working codepen