Vuetify.js: v-dialog is not working when i close and open again - vuetify.js

I using a Vuetify.js for my project
when I used v-dialog component I got a problem that closing the v-dialog and open again.
here is my code.
<div #click="dialog=true">click here</div>
<v-dialog v-model="dialog">
<alert-popup />
</v-dialog>
data() {
return {
dialog : false
}
this is work when I open dialog first time but when I open again only can see the opacity black page
I don`t know which part is wrong. please reply this question. thanks

You didn't provide any code to reproduce the behavior. But I guess the you need provide <v-card> component inside the dialog component. If you don't provide that it just shows the opacity issue.Putting a v-card will eliminate the opacity issue.
<div id="app">
<v-app id="inspire">
<v-container>
<v-row justify="start">
<v-btn #click="openDialog">Open</v-btn>
<v-dialog v-model="dialog" max-width="300px">
<v-card>
<v-card-title>My Dialog</v-card-title>
<v-divider></v-divider>
<v-card-text>
This is text for dialog
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn x-small color="blue darken-1" text #click="dialog = false">Close</v-btn>
<v-btn x-small color="blue darken-1" text #click="dialog = false">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</v-container>
</v-app>
</div>
Here is the working code where there is a v-card component inside v-dialog.
I also see that you've used a custom component called alert-popup inside the v-dialog.If that is the case it needs to be refactored a bit to achieve what you're looking for.
Creating a separate component which you are already doing except <v-dialog>.
Emitting a close event from <alert-popup> component so that open/close and clicking open won't cause issue.
HTML:
<div id="app">
<v-app id="inspire">
<v-container>
<v-row justify="start">
<v-btn #click="openDialog">Open</v-btn>
<alert-popup :dialog="dialog" #close="closeMyDialog" />
</v-row>
</v-container>
</v-app>
</div>
Javascript:
let AlertPopup = Vue.component("AlertPopup", {
props: {
dialog: {
type: Boolean,
default: false
}
},
data: () => ({
open: false
}),
methods: {
close() {
this.open = false;
this.$emit("close");
}
},
watch: {
dialog(value) {
this.open = value;
}
},
created() {
this.open = this.dialog;
},
template: `
<v-dialog v-model="open" max-width="300px">
<v-card>
<v-card-title>My Dialog</v-card-title>
<v-divider></v-divider>
<v-card-text>
This is text for dialog
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn x-small color="blue darken-1" text #click="close">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
`
});
new Vue({
el: "#app",
vuetify: new Vuetify(),
components: {
AlertPopup
},
data: (vm) => ({
dialog: false
}),
computed: {},
methods: {
closeMyDialog() {
this.dialog = false;
},
openDialog() {
this.dialog = true;
}
}
});
If look at the AlertPopup component, it just take a prop named dialog and that we can pass from the main component, when button is clicked, it sets to true, which triggers the AlertPopup component.
NOTE:
If we don't emit the close event from the AlertPopup component, then the modal dialog will not open on the second time onwards. The reason for this behavior is the fact that, when the button is clicked from the parent component,which set the dialog to true and pass it to the AlertPopup and whatever things happens inside the AlertPopup remains inside that component and dialog property of parent component never changes.
Here is a working example of how the modal component are triggered from parent component. If we remove the close event, it will not open the modal second time.
Update: It turns out we are unnecessary complicating things, we don't
even need to emit an event, we can pass in the close function as a
prop and react to that, whenever the close button is clicked, it would
call the closeAlert().
Thanks to #Pratik149 for pointing out the bug, I have attached the click outside event handler.
Here is the completely re-factored code.
<div id="app">
<v-app id="inspire">
<v-container>
<v-row justify="start">
<v-btn #click="openDialog">Open</v-btn>
<alert-popup :dialog="dialog" #close="closeMyDialog" :close="closeMyDialog" />
</v-row>
</v-container>
</v-app>
</div>
And the component logic is updated as follow
let AlertPopup = Vue.component("AlertPopup", {
props: {
dialog: {
type: Boolean,
default: false
},
close: {
type: Function,
default: () => {}
}
},
data: () => ({
open: false
}),
methods: {
closeAlert() {
this.close();
}
},
template: `
<v-dialog v-model="dialog" max-width="300px" #click:outside="closeAlert">
<v-card>
<v-card-title>My Dialog</v-card-title>
<v-divider></v-divider>
<v-card-text>
This is text for dialog
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn x-small color="blue darken-1" text #click="closeAlert">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
`
});
new Vue({
el: "#app",
vuetify: new Vuetify(),
components: {
AlertPopup
},
data: (vm) => ({
dialog: false
}),
computed: {},
methods: {
closeMyDialog() {
debugger;
this.dialog = false;
},
openDialog() {
this.dialog = true;
}
}
});
Finally here is the updated codepen

Related

vuetifyjs v-list. If click to item second time item not active. v-model="undefined"

I use standard v-list component of vuetifyjs. For create menu and show list of items.
But if I click second time the active element is hide. And I don't see active element of menu. It is bad for my menu. Link for example v-list
If use pug template below
v-list(dense)
v-list-item-group(color="success" v-model="selectedItem")
v-list-item(v-for="(gallery, key) in galleries" :key="key")
v-list-item-content
v-list-item-title(v-text="gallery")
I have solution. It needs watch variable and set force index by key of clicked item. If we have 'undefined' set force data method
Run examle solution Solution
Code below:
<template>
<div id="app">
<v-app id="inspire">
<v-card max-width="300" >
<v-list dense>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(item, i) in items"
:key="i"
>
<v-list-item-content #click="setItem(i)">
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-app>
</div>
</template>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
index: 0,
selectedItem: 0,
items: [
{ text: 'Real-Time'},
{ text: 'Audience' },
{ text: 'Conversions'},
],
}),
watch: {
selectedItem() {
if (typeof this.selectedItem === 'undefined') {
setTimeout(() => {
this.selectedItem = this.index;
}, 500);
}
},
},
methods: {
setItem(index) {
this.index = index;
},
},
})
Another solution would be to disable the v-list-item that has the same index as the selectedItem
<template>
<div id="app">
<v-app id="inspire">
<v-card max-width="300" >
<v-list dense>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(item, i) in items"
:key="i"
:disabled="i == selectedItem"
>
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-app>
</div>
</template>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selectedItem: 0,
items: [
{ text: 'Real-Time'},
{ text: 'Audience' },
{ text: 'Conversions'},
],
}),
})

Submit Vuetify form with the enter button

Thought this would be straight forward but it isn't. With vuetify forms how do I bind a form to the enter button so that it's submit function is invoked with the enter button?
Figures I'd find a work around as soon as I posted the question. I found the answer here:
https://github.com/vuetifyjs/vuetify/issues/1545
Basically I had to add an event listener to the component to attach the enter key press to my authenticate method. Here is the component in questions:
<template>
<v-card>
<v-card-title>
<span class="headline">Login</span>
</v-card-title>
<v-form v-model="isValid">
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="username"
label="User Name"
prepend-icon="mdi-account circle"
:rules="userNameRequired"
required
></v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="password"
label="Password"
:type="showPassword ? 'text' : 'password'"
prepend-icon="mdi-lock"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
#click:append="showPassword = !showPassword"
:rules="passwordRequired"
required
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close"> Close </v-btn>
<v-btn color="blue darken-1" text #click="authenticate" :disabled="!isValid">
Login
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
<script>
import { authenticationService } from "../services/authenticationService/authentication.service";
import User from "../models/user";
export default {
name: "LoginForm",
props: ["userForAuthentication"],
data: () => ({
username: "",
password: "",
user: {},
isValid: true,
showPassword: false,
userNameRequired: [(v) => !!v || "User Name is required"],
passwordRequired: [(v) => !!v || "Password is required"],
}),
methods: {
async authenticate() {
try {
const response = await authenticationService.authenticateUser(
this.$data.username,
this.$data.password
);
if (response.status === 200) {
this.$data.user.shallowClone(response.data.user);
await this.resetData();
this.$emit(
"user-logging-in-event",
this.$data.user,
response.data.token
);
this.$toasted.success(`${this.$data.user.fullName} is logged in.`, {
duration: 3000,
});
} else if (response.status === 400) {
this.$toasted.error("Username or Password is incorrect.", {
duration: 3000,
});
} else {
this.$toasted.error(
"An error occurred while trying to authenticate the user",
{
duration: 3000,
}
);
}
} catch (error) {
this.$toasted.error(error, {
duration: 3000,
});
}
},
close() {
this.$emit("user-logging-in-event", null, null);
},
async resetData() {
this.$data.username = "";
this.$data.password = "";
},
},
mounted() {
let self = this;
window.addEventListener("keyup", function (event) {
if (event.keyCode === 13) {
self.authenticate();
}
});
this.$data.user = new User();
this.$data.user.shallowClone(this.$props.userForAuthentication);
},
};
</script>

How to make Vutify VMenu right aligned?

I need to make right aligned v-menu with "attach" option.
Template:
<div id="app">
<v-app id="inspire">
<h1>VMenu bug with "right" option</h1>
<div class="place"></div>
<div class="text-center">
<v-btn
color="primary"
dark
#click="show = !show"
>
Dropdown
</v-btn>
</div>
<v-menu attach=".place" v-model="show" :right="true">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click=""
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app>
</div>
JS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
show: false,
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
I expect right aligned menu in ".place" element. But the menu is left aligned. Also top border of menu is under the ".place" element. It is strange. How can I fix it?
Demo
It is possible to align the content of v-menu to right border of the page
Here is the working codepen: https://codepen.io/chansv/pen/OJyjWmX
<div id="app">
<v-app id="inspire">
<h1>VMenu bug with "right" option</h1>
<div>
<div id="attachMenu" style="float: right;position: relative;width: 134px;left: 27px;"></div>
</div>
<div class="text-center">
<v-btn
color="primary"
dark
#click="show = !show"
>
Dropdown
</v-btn>
</div>
<v-menu attach="#attachMenu" v-model="show" :right="true">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click=""
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
show: false,
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
You almost got the right solution, the position with the right and left prop is inverted
So this is what you need to do:
<v-menu attach=".place" v-model="show" left>
I am using Vuetify 3 and I had the same problem. I solved it by using
<v-menu location="bottom end">
See :
https://next.vuetifyjs.com/en/components/menus/#location
https://next.vuetifyjs.com/en/components/overlays/#location-strategies

Vue component does not render and ignores props

I am having a problem with a Vue component which should just show a simple dialog, the component looks like this:
<template>
<v-layout row justify-center>
<v-dialog
v-model="show"
max-width="290"
:persistent="persistent"
>
<v-card>
<v-card-title class="headline grey lighten-2">{{header}}</v-card-title>
<v-card-text v-html="text">
{{text}}
</v-card-text>
<v-card-actions>
<v-layout>
<v-flex xs6 md6 lg6 class="text-xs-center">
<v-btn block
color="primary"
flat
#click="closeDialog(true)"
>
{{agree_button_text}}
</v-btn>
</v-flex>
<v-flex xs6 md6 lg6 class="text-xs-center">
<v-btn block
color="warning"
flat
#click="closeDialog(false)"
>
{{abort_button_text}}
</v-btn>
</v-flex>
</v-layout>
</v-card-actions>
</v-card>
</v-dialog>
</v-layout>
</template>
<script>
export default {
props:
{
persistent:
{
type: Boolean,
required: false,
default: false
},
header:
{
type: String,
required: false,
default: ""
},
text:
{
type:String,
required: false,
default:""
},
abort_button_text:
{
type: String,
required: false,
default:"Avbryt"
},
agree_button_text:
{
type: String,
required: false,
default: "OK"
},
value:
{
}
},
data ()
{
return {
show: this.value
}
},
methods:
{
closeDialog:
function(retval)
{
this.show = false;
this.$emit('close-dialog-event',retval);
}
}
}
</script>
In app.js I have added the following:
require('./bootstrap');
import babelPolyfill from 'babel-polyfill';
import Vuetify from 'vuetify'
window.Vue = require('vue');
var vueResource = require('vue-resource');
window.socketIo = require('vue-socket.io');
Vue.use(vueResource);
Vue.use(Vuetify);
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('simple-dialog', require('./components/common/SimpleDialog.vue'));
And on the page where I use the component I have:
<div id="overview">
<simple-dialog show="true"
:header="dialog_header"
:text="dialog_message"
#close-dialog-event="display_dialog = false"></simple-dialog>
<v-app>
<v-content>
<v-container>
FOO AND BAR
</v-container>
</v-content>
</v-app>
</div>
I don't get any errors that the component is not loaded.
And if I try to change the name of the component in the app.js file and then try to load the component it throws an error that the component can not be found. So in others words it seems that it loads successfully. However, if I change the name of the props, e.g. changing
<simple-dialog show="true"
:header="dialog_header"
:text="dialog_message"
#close-dialog-event="display_dialog = false"></simple-dialog>
to
<simple-dialog show_bla="true"
:asdasd="dialog_header"
:asdasd="dialog_message"
#close-dialog-event="display_dialog = false"></simple-dialog>
it does not care. It does not even throw an error about that those props either does not exists or are invalid. The javascript used by the page:
var app = new Vue({
el: '#overview',
data:
{
display_dialog:true,
dialog_header:'',
dialog_message:'',
},
methods:
{
}
});
What could the problem be? Thanks for any help!
Well, When you're sending the value to the component and your prop show is initialized as an empty object.
replace
value: {}
to
value
or
the pass default value to false
value: {
type: Boolean
default: false
}
Hope this helps!

Vuetify Snackbar leave event

I manage to implement a global Vuetify Snackbar.
My problem is to detect when the snackbar close. I read that this component support Vue transition event since 1.2. But it work only on the enter event not the leave ones.
here a fiddle for comprehension.
<transition #before-enter="beforeEnter" #before-leave="beforeLeave" #after-enter="afterEnter" #after-leave="afterLeave" #leave="leave">
<v-snackbar v-model="snackbar" top right>
Hello
<v-btn #click="snackbar = false" dark>Close</v-btn>
</v-snackbar>
</transition>
I faced the same problem and solved this way:
export default {
data: () => ({
errorMessage: '',
snackTimeout: 6000,
}),
watch: {
errorMessage() {
setTimeout(() => {
this.clearErrorMessage();
}, this.snackTimeout);
},
},
methods: {
setErrorMessage(message) {
this.snackMessage = message;
},
clearErrorMessage() {
this.snackMessage = '';
},
},
};
<template>
<v-snackbar
:value="errorMessage"
:timeout="snackTimeout"
top
>
{{ errorMessage }}
<v-btn
color="error"
flat
#click.stop="clearErrorMessage"
>
{{ 'close' }}
</v-btn>
</v-snackbar>
</template>
Define an attribute with the timeout and another with the message to show by the snackBar.
Define a function to set the message and another to clear it.
Define a watch for the message text and set a timer with the same timeout of the snackBar to clear it.
The snackBar appears only when the message is not empty.
You can use get and set methods to handle reading and updating the bound model separately.
I made a generic snackbar component that can be triggered from any other component. I'm using Vuex, vue-property-decorator and typescript here, so adjust accordingly.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<template>
<v-snackbar v-model="snackbar" max-width="100%">
<template v-slot:action="{ attrs }">
{{ text }}
<v-btn color="primary" text fab v-bind="attrs">
<v-icon dark #click="close()"> mdi-close-circle-outline </v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
#Component({})
export default class Snackbar extends Vue {
get snackbar() {
return this.$store.state.snackbar.show
}
set snackbar(show: boolean) {
this.$store.dispatch('updateSnackbar', { show, text: '' })
}
get text() {
return this.$store.state.snackbar.text
}
public close() {
this.$store.dispatch('updateSnackbar', { show: false, text: '' })
}
}
</script>

Resources