Validate vuetify textfield only on submit - vue.js

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.
}
},

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>

Vuetify reset form after submitting

I am using a form inside dialog using vuetify.
Imported the component in page like this -
<template>
<div>
<topicForm :dataRow="dataRow" v-model="dialog" />
</div>
</template>
methods: {
openDialog(item = {}) {
this.dataRow = item;
this.dialog = true;
},
}
Dialog form code -->
<template>
<div>
<v-dialog v-model="value" max-width="500px" #click:outside="close">
<v-card outlined class="pt-5">
<v-form ref="form" class="px-3">
<v-card-text class="pt-5">
<v-row no-gutters>
<v-text-field
required
outlined
label=" Name"
v-model="data.name"
:rules="[rules.required]"
></v-text-field>
</v-row>
<v-row no-gutters>
<v-textarea
required
outlined
label=" Description"
v-model="data.description"
></v-textarea>
</v-row>
</v-card-text>
</v-form>
<v-divider> </v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
large
dark
outlined
color="success"
#click="save"
class="ma-3"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
props: [
"dataRow",
"value",
// ----------------------
],
methods: {
save() {
if (this.$refs.form.validate()) {
this.$root
.$confirm("Are you sure you want to save?")
.then((confirm) => {
if (confirm) {
this.ADD_TOPIC_DATA(this.data)
.then((data) => {
this.FETCH_TOPIC_DATA();
this.$refs.form.reset();
this.$refs.form.resetValidation();
this.close();
})
.catch((err) => {
console.log(err)
});
}
});
}
},
close() {
this.$emit("input", false);
},
}
watch: {
dataRow(val) {
this.data = { ...val };
},
},
Problem I am having is after adding a data, then if I try to add again by opening the dialog, the required field shows validation error, which is name here!
Image of that -->
Searched in stackoverflow. Found that should use this.$refs.form.reset(). Used that in save method without success. Also used this.$refs.form.resetValidation(), but don't work.
Any suggestion?
Thanks in advance.
The problem here is you're assigning new value to dataRow when opening the dialog which triggers validation inside the dialog. You could also use lazy-validation prop which allows you to only manually trigger the validation.

Checking validation of VTextField

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 );
}

Vuetify how can I disable button till all validation rules are true?

I am using Vuetify to create a simple form where the data is being validated by some rules associated with each field. What I am trying to do is disable the Submit button if any of those validation rules fails. How can I do so? What I am trying to understand is how to check the state of each rule so that I can bind the disabled property on the button.
<template>
<v-card>
<v-card-text>
<v-combobox
v-model="addTool.name"
:rules="rules.name"
:items="toolNames"
counter
label="Name of the tool"
></v-combobox>
<v-combobox
multiple
:small-chips="true"
:deletable-chips="true"
:hide-selected="true"
:clearable="true"
v-model="addTool.categories"
:rules="rules.categories"
label="Select/Add categories"
:items="this.toolCategories"
:search-input.sync="searchCat"
#change="searchCat = ''"
></v-combobox>
<v-text-field
v-model="addTool.site"
label="URL for the tool. It is best to use a Github repo link."
:rules="rules.site"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn #click="submit" color="grey darken-4" class="green--text text--accent-2" dark>Add</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
data() {
return {
dialog: false,
searchCat: "",
addToolMessage: undefined,
addTool: {
name: "",
categories: [],
created_on: "",
site: ""
},
rules: {
name: [
v => !!v || "Required.",
v => v.length < 80 || "Name is too long",
v =>
!this.toolsData
.map(n => n.name.toLowerCase())
.includes(v.toLowerCase()) || "This tool already exists"
],
categories: [v => v.length != 0 || "At least one category is needed"],
site: [
v => !!v || "Required.",
v =>
new RegExp(
"^(?:(?:https?|ftp)://)(?:S+(?::S*)?#)?(?:(?!(?:10|127)(?:.d{1,3}){3})(?!(?:169.254|192.168)(?:.d{1,3}){2})(?!172.(?:1[6-9]|2d|3[0-1])(?:.d{1,3}){2})(?:[1-9]d?|1dd|2[01]d|22[0-3])(?:.(?:1?d{1,2}|2[0-4]d|25[0-5])){2}(?:.(?:[1-9]d?|1dd|2[0-4]d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:.(?:[a-z\u00a1-\uffff]{2,})).?)(?::d{2,5})?(?:[/?#]S*)?$",
"ig"
).test(v) || "Not a valid URL",
v =>
!this.toolsData.map(u => u.site).includes(v) ||
"This tool already exists"
]
}
};
}
};
</script>
Vuetify will track the validity of form if you wrap input elements in v-form and then attach v-model to form like this:
<v-form v-model="isFormValid">
<!-- all input elements go here -->
</v-form>
<!-- disable if form is not valid -->
<v-btn :disabled="!isFormValid">Add</v-btn>
You also need to add isFormValid to the component's data field:
data: () => ({
isFormValid: false,
})
You can read more here: https://vuetifyjs.com/en/components/forms
You can create a computed property do to it. Lets say name and site are requireds, so:
<temlate>
<v-btn :disabled="isAddButtonDisabled" #click="submit" color="grey darken-4" class="green--text text--accent-2" dark>Add</v-btn>
</template>
<script>
export default {
data() {
return {
addTool: {
name: "",
categories: [],
created_on: "",
site: ""
},
};
},
computed: {
isAddButtonDisabled() {
return !(this.addTool.name || this.addTool.site);
// means u have neither name nor site
},
},
};
</script>
If you need to check if form is valid, you can do:
export default {
computed: {
formValid () {
// loop over all contents of the fields object and check if they exist and valid.
return Object.keys(this.addTool).every(field => {
return this.addTool[field] && this.addTool[field].valid;
});
}
}
}
From here: https://github.com/logaretm/vee-validate/issues/853

Vue.js why my watcher is not working?

After setting a watcher on an input data , I am trying to display both old value and new value, but they are both undefined... what could be wrong ?
see codepen.io
HTML
<div id="app">
<v-app id="inspire">
<v-container mt-1 grid-list-xl">
<v-layout row wrap justify-space-around>
<v-flex d-flex sm6 md6>
<v-layout row wrap>
<v-flex d-flex>
<v-card class="flexcard" dark tile flat>
<v-card-title class="card__title justify-center">PROFILE</v-card-title>
<form #submit.prevent="onUpdateProfile()">
<v-card-text class="card__text grow">
<v-text-field #keyup.native="setDirty" placeholder="Enter your new email address" :readonly="isReadOnly" label="Email" v-model="user.email" prepend-icon="email">
</v-text-field>
</v-card-text>
<v-card-actions>
<v-tooltip v-if="isReadOnly" right>
<v-btn #click="onEditProfile" icon class="primary" large slot="activator">
<v-icon color="white">edit</v-icon>
</v-btn>
<span>EDIT</span>
</v-tooltip>
<v-btn v-if="isProfileEditing" round #click="cancel()">CANCEL</v-btn>
<v-btn v-if="isProfileDirty" round color="primary" type="submit">UPDATE</v-btn>
</v-card-action>
</form>
</v-card>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
JS
new Vue({
el: '#app',
mounted () {
this.user.email = 'john.doe#example.com'
},
data: () => ({
user: {
email: ''
},
cache: {
user: {
email: ''
}
},
editIcon: {
email: 'edit'
},
isProfileEditing: false,
isProfileDirty: false,
isReadOnly: true
}),
watch: {
user: function (newUser, oldUser) {
console.log('Old User Email: ', oldUser.email)
console.log('New User Email: ', newUser.email)
}
},
computed: {
},
methods: {
onUpdateProfile () {
console.log('UPDATE PROFILE: ', this.user.emaill)
},
onEditProfile () {
this.isProfileEditing = true
this.isReadOnly = false
this.cache.user.email = this.user.email
this.user.email = ''
},
cancel () {
this.isProfileEditing = false
this.isProfileDirty = false
if (!this.isReadOnly) {
this.isReadOnly = true
this.user.email = this.cache.user.email
}
},
setDirty () {
this.isProfileDirty = true
}
}
})
To listen to changes for a property in an object you should add deep watcher. To do so add deep: true property to your watcher.
watch: {
user: {
handler: function (newUser, oldUser) {
console.log('Old User Email: ', oldUser.email)
console.log('New User Email: ', newUser.email)
},
deep: true
}
}
Here is the updated pen
But if you are trying to make use of the oldUser , then the watcher cannot help you bacause:
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
That's the reason you see the same value for both newUser and oldUser when user.email is updated.
Reference - vm.watch API
If you want to watch for changes nested data you have to set deep option of watcher. However you can't access an old data in handler as Vue doesn’t keep a copy of the pre-mutate value of objects. In your case, the code of your watch object should look like this below:
watch: {
user: {
handler: function (newUser) {
console.log('New User Email: ', newUser.email)
},
deep: true
}
}
Reference: Vue's official API