On a page I have a Vuetify v-form/v-text-field. Users can type a query in to this form and press Enter to update the page.
Sometimes the query is erroneous and a v-dialog modal pops up to explain the error. It's a complex error and can't just be packed in the rules attribute of the v-text-field.
The problem: when the modal is dismissed, the v-text-field is no longer focused and requires a click before it can be used again. This interrupts the user and requires a shift from keyboard to mouse and back.
What is Vue best practice for re-focusing on the input that triggered the modal?
I can think of one idea it doesn't feel Vue-ish: put a ref on the v-text-field and watch the dialog data property. If dialog becomes false, call this.$refs.input.focus(). However, this seems like the old-fashioned, imperative (not reactive) way to do it. For example:
<template>
<v-form v-model='valid' #submit.prevent='submit'>
<v-text-field
placeholder="Search..."
v-model="query"
:rules="[foo]"
ref='input'
autofocus
></v-text-field>
<v-dialog
v-model="dialog"
#keydown.esc='dialog = false'
>
{{ someErrorMessage }}
</v-dialog>
</v-form>
</template>
// Vue instance
export default {
data: function() {
return {
dialog: false,
query: "",
valid: false
}
},
...
watch: {
// Focus on query after dismissing an error
dialog(newState) {
if (!newState) {
this.$refs.input.focus();
}
}
},
methods: {
foo(value) {
// ... validate value
},
submit(e) {
if (!this.valid) {
this.dialog = true;
return;
}
}
}
}
In the dialog element, I'd probably call a method instead of directly changing dialog to false. Then in that method, I'd add focus to the textarea with some plain old javascript.
Related
I have a Modal component in Vue.
This Modal component has a few form fields in it:
<template>
<div :class="visible">
<input type="text" v-model="form.name">
<input type="text" v-model="form.email">
<input type="text" v-model="form.pass">
<button #click="sendForm">Submit</button>
<button #click="closeModal">Close</button>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
form: {
name: '',
email: '',
pass: ''
}
}
},
methods: {
sendForm() {
// Send form
},
showModal() {
this.visible = true
},
closeModal() {
this.visible = false
}
},
}
</script>
When the modal becomes visible, I am showing the form fields to the user. Let's say the user fills out the form fields but does not click on Submit. Instead, he closes the modal.
In the case, I want to reset the modal completely to its original form. Basically destroy the component. Then re-initialize it when the user clicks on the button to make it visible.
When the modal becomes visible again, the component should be a new instance and all the component data should be clear.
What I tried: I tried to clear the form values in the component on close. However, with more complicated forms/components it could become hard to reset each and every data property to their original state.
How can I destroy the component and re-create it? Are there any best practices for this situation?
You can create method to reset component data:
reset() {
Object.assign(this.$data, this.$options.data())
}
And call it, when you close modal.
Be carefull! It's works only on data properties.
Currently, you can set rules that will work once the user changes the value of the input. For example:
Template part
<v-text-field
v-model="title"
label="Title"
></v-text-field>
Logic
export default {
data () {
return {
title: '',
email: '',
rules: {
required: value => !!value || 'Required.'
},
}
}
}
When the user focuses and removes focus from that element, or when the user deletes all its content, the required rule is triggered.
But what happens if we want to start with the rule enabled as soon as the component is mounted or created? Is there a way to achieve this?
I searched around vuetify but I could not find info about this nor examples in my humble google searches. I will appreciate help. I'm new in vue world. Thanks.
You could do the following:
Create a v-form and place your textfields inside the form. Don't forget to give your v-form a v-model and a ref too.
On mounted you can access the v-form via this.$refs and call .validate() just as Jesper described in his answer. In the codesandbox below you can see that the textfields immediately go red and display the "Required." text.
<v-form v-model="formValid" ref="myForm">
<v-text-field label="Field 1" :rules="rules.required"></v-text-field>
<v-text-field label="Field 2" :rules="rules.required"></v-text-field>
</v-form>
<script>
export default {
data() {
return {
formValid: false,
rules: {
required: [value => !!value || "Required."]
}
};
},
mounted() {
this.$refs.myForm.validate();
}
};
</script>
Example:
You should change your validation a little bit to achieve this.
<ValidationProvider rules="required" v-slot="{ errors }" ref="title">
<v-text-field
v-model="title"
label="Title"
></v-text-field>
</ValidationProvider>
And then you should call this.$refs.title.validate()
If you trigger this when mounted() is called, it should validate all the fields right away, as you're requesting.
When using the #submit directive in Vuetify, console.log is not working in my code. But when I only use #click and then call my function it works. Any idea how to make that work?
This code works:
<v-btn color="primary" text #click="submit">I accept</v-btn>
HTML in template
<v-btn color="primary" text #submit="submit">I accept</v-btn>
Function being called
methods: {
submit() {
console.log.$refs
// Form validation check
if (!this.$refs.invoiceform.validate()) {
console.log("Not good");
} else {
console.log("Data ok");
}
},
Your problem I believe is that v-btn does not have a #submit event so you need to put that on the v-form element if you are using it. Make sure your button is inside the form element and it is of the type submit. Use prevent on the form submit event to prevent it from causing the browser to try to post your form.
<v-form #submit.prevent="submit">
...//some inputs
<v-btn type="submit">I accept</v-btn>
</v-form>
About the problem
I am using Laravel 5.6.7, Vue.js. I have modal div which being opened and closed on button click. I type something. Validation fires. I close the modal div. Then clicking button to open it. I see that the validation messages still there.
Component Template
<template>
<div>
<form role="form">
<input name="LastName" type="text" ref="LastName" v-validate
data-vv-rules="required" v-model="createForm.LastName">
<p v-if="errors.has('LastName')">{{ errors.first('LastName') }}</p>
<button v-else type="button" #click="validateBeforeSubmit()">
Create
</button>
</form>
</div>
</template>
Component Script
<script>
export default {
data() {
return {
createForm: {
LastName: ''
}
};
},
created() {
this.InitializeForm();
},
methods: {
InitializeForm() {
this.createForm.LastName = "";
},
validateBeforeSubmit() {
this.$validator.validateAll();
}
}
}
</script>
My findings
if you check the input type text above, I added ref attribute. Tried the below code to set the value to false for aria-invalid attribute.
this.$refs.LastName.setAttribute("aria-invalid", "false");
It sets the attribute value but validation errors are still there. Is there any proper way to get rid of workarounds like above?
I think, when I set the first value or I click on it...some attribute value is being set and due to that form errors occur.
Assuming that you are using vee-validate,
To clear all errors,
this.$validator.errors.clear();
To clear 1 single error only,
this.$validator.errors.remove('LastName');
Add 1 of the code above to the modal close event listener and the error would be gone the next time you opened it..
vuetify says: If you want to programmatically open or close the dialog, you can do so by using v-model with a boolean value.
However I am quite unclear on what this means. Saying "using v-model" is vague at best. The parent component knows on setup if it should open but I am unclear on how to dynamically change this in the child. Am i supposed to pass it using v-bind?
<login v-bind:showDialog></login>
If so how does the child component deal with this?
Vuetify Dialog info here: https://vuetifyjs.com/components/dialogs
As I understand you have a child component which have a dialog within it. Not sure that this is 100% right, but this is how I implement it. Child component with dialog:
<template>
<v-dialog v-model="intDialogVisible">
...
</template>
<script>
...
export default {
props: {
dialogVisible: Boolean,
...
},
computed: {
intDialogVisible: {
get: function () {
if (this.dialogVisible) {
// Some dialog initialization code could be placed here
// because it is called only when this.dialogVisible changes
}
return this.dialogVisible
},
set: function (value) {
if (!value) {
this.$emit('close', some_payload)
}
}
}
in parent component we use it:
<my-dilaog :dialogVisible="myDialogVisible"
#close="myDialogClose">
</my-dialog>
data () {
return {
myDialogVisible: false
}
},
methods: {
myDialogClose () {
this.myDialogVisible = false
// other code
}
}
Дмитрий Алферьев answer's is correct but get "Avoid mutating a prop directly" warning, because when close dialog, v-dialog try change v-model to false, while we passed props to v-model and props value won't change. to prevent the warning we should use :value , #input
<template>
<v-dialog :value="dialog" #input="$emit('update:dialog',false)" #keydown.esc="closeDialog()" >
...
</v-dialog>
</template>
<script>
export default {
props: {
dialog: Boolean
},
methods: {
closeDialog(){
this.$emit('closeDialog');
}
}
In parent
<template>
<v-btn color="primary" #click="showDialog=true"></v-btn>
<keep-alive>
<my-dialog
:dialog.sync="showEdit"
#closeDialog="closeDialog"
>
</my-dialog>
</keep-alive>
</template>
<script>
data(){
return {
showEdit:false,
},
},
methods: {
closeDialog(){
this.showEdit = false;
},
}
v-model is a directive. You would use v-model, not v-bind.
The page you link has several examples. If you click on the <> button on the first one, it shows HTML source of
<v-dialog v-model="dialog">
v-model makes a two-way binding on a prop that is named value inside the component. When you set the bound variable's value to true, the dialog will display; when false, it will hide. Also, if the dialog is dismissed, it will set the variable's value to false.