Vue warning and dialog only appears once - javascript

Having vue ui, creating a basic new project with Babel and Lint, I installed deps vuetify, vuetify-loader, and vue-bootstrap. All I want is a simple 'open dialog' button that open a dialog defined in a separate component (file). The dialog shows, without problems/warnings, but when I close it (either by clicking elsewhere or on one of the buttons, I get a warning about "Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders." Clicking the button again has no longer effect. Although the "JAAA" is shown in the console. The code:
HelloWorld.vue
<template>
<div class="hello">
<v-btn #click="openDialog" class="btn btn-info">Open dialog</v-btn>
<Dialog :showDialog="showDialog"></Dialog>
</div>
</template>
<script>
import Dialog from "./Dialog";
export default {
name: 'HelloWorld',
components: {
Dialog
},
props: {
msg: String
},
data() {
return {
showDialog: false
}
},
methods: {
openDialog() {
this.showDialog = true
window.console.log('JAAA')
}
}
}
</script>
Dialog.vue
<template>
<div>
<v-dialog v-model="showDialog" width="500">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
Remark
</v-card-title>
<v-card-text>
Remark: <input type="text">
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="primary" text #click="hideDialog">
Done
</v-btn>
<v-btn color="primary" text #click="hideDialog">
Cancel
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
name: "Dialog",
props: ['showDialog'],
methods: {
hideDialog() {
this.showDialog = false;
}
}
}
</script>

Mutating the value in child will not reflect to parent, the props data flows at the time of child component created hook and while closing you are try to mutate it in child level and the state is not shared with the parent. From the next time onwards its just calls updated hook in dialog box
Make these changes to the Dialog.vue component
<template>
<div>
<v-dialog v-model="displayDialog" width="500">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
Remark
</v-card-title>
<v-card-text>
Remark: <input type="text">
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="primary" text #click="hideDialog">
Done
</v-btn>
<v-btn color="primary" text #click="hideDialog">
Cancel
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
name: "Dialog",
props: {
showDialog: {
type: Boolean,
}
},
data() {
return {
displayDialog: false,
};
},
methods: {
hideDialog() {
this.displayDialog = false;
}
},
watch: {
showDialog(val) {
this.displayDialog = val;
}
}
}
</script>

You should not change the values of props directly in a component as the change will not be reflected in your parent.
You can instead convert your dialog component to use a v-model instead combined with a computed property in your child to emit changes to your parent so that it knows the value has been updated.
HelloWorld.vue
<template>
<div class="hello">
<v-btn #click="openDialog" class="btn btn-info">Open dialog</v-btn>
<Dialog v-model="showDialog"></Dialog>
</div>
</template>
<script>
import Dialog from "./Dialog";
export default {
name: 'HelloWorld',
components: {
Dialog
},
props: {
msg: String
},
data() {
return {
showDialog: false
}
},
methods: {
openDialog() {
this.showDialog = true
window.console.log('JAAA')
}
}
}
</script>
Dialog.vue
<template>
<div>
<v-dialog v-model="displayDialog" width="500">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
Remark
</v-card-title>
<v-card-text>
Remark: <input type="text">
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="primary" text #click="hideDialog">
Done
</v-btn>
<v-btn color="primary" text #click="hideDialog">
Cancel
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
name: "Dialog",
props: {
value: {
type: Boolean,
default: false
}
},
computed: {
displayDialog: {
get() {
// returns the value of your prop
return this.value
},
set(newValue) {
// v-model listens to the input event, so emitting `input` with a value
// will update the model with that value
this.$emit('input', newValue)
}
};
},
Methods: {
hideDialog() {
this.displayDialog = false;
}
}
}
</script>

This worked for me =>
To close the dialog all you have to do is emit an event to the parent component and change the value of dialog property(ie close dialog from parent not from child)
<div class="hello">
<Dialog v-model="showDialog" #closeDialog="showDialog=false"> <Dialog>
</div>
dialog component =>
<v-dialog v-model="displayDialog" width="500">
.....
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="primary" text #click="$emit('closeDialog')">
Done
</v-btn>
<v-btn color="primary" text #click="$emit('closeDialog')">
Cancel
</v-btn>
</v-card-actions>
......
</v-dialog>
export default {
name: "Dialog",
props: {
showDialog: {
type: Boolean,
}
},
data() {
return {
displayDialog:this.showDialog,
};
},

Related

How to set parent component data from child component in vuejs

Below is the parent component and child component. I am trying to access tabs_value data in the parent component from the child component but it is returning as undefined.
this.$parent.tabs_value returns as undefined when I try to access it inside the run method in the child component.
Please help me find where I am going wrong? Below is the code
Parent Component
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm12 lg12>
<div>
<v-card>
<v-tabs v-model="tabs_value"
color="black"
centered
show-arrows
>
<v-toolbar-title>Custom</v-toolbar-title>
<v-spacer></v-spacer>
<v-tab href="#build">Build</v-tab>
<v-tab href="#run">Run</v-tab>
</v-tabs>
<v-tabs-items v-model="tabs_value">
<v-tab-item value="#build" id="build">
<Build ref="build_reports" />
</v-tab-item>
<v-tab-item value="#run" id="run">
<Run :reports="reports" ref="run_reports" />
</v-tab-item>
</v-tabs-items>
</v-card>
</div>
</v-flex>
</v-layout>
</div>
</template>
<script>
import Build from 'views/build.vue'
import Run from 'views/run.vue'
import URLs from 'views/routes'
export default {
components: {
Build,
Run
},
data: function() {
return {
tabs_value: 'build',
isLoaded: true,
reports: []
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.$axios.get(URLs.REPORTS_URL)
.then(response => {
this.reports = response.data
});
}
}
};
</script>
Child Component run.vue
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm12 lg12>
<div>
<v-card>
<div>
<v-data-table
:headers="headers"
:items="reports"
hide-default-footer
:mobile-breakpoint="0">
<template slot="item" slot-scope="props">
<tr>
<td>{{props.item.name}}</td>
<td>
<div>
<v-tooltip attach left>
<template v-slot:activator="{ on, attrs }">
<a v-bind="attrs" v-on="on"
class="" href='javascript:void(0);'
#click="run(props.item)"><i small slot="activator" dark color="primary" class="fas fa-play"></i></a>
</template>
<span>Run</span>
</v-tooltip>
</div>
</td>
</tr>
</template>
<template slot="no-data" >
<v-alert id='no-data' :value="true" color="error" icon="warning">
No Reports Yet
</v-alert>
</template>
</v-data-table>
</div>
</v-card>
</div>
</v-flex>
</v-layout>
</div>
</template>
<script>
import URLs from 'views/routes'
export default {
props: ['reports'],
data: function() {
return {
headers: [
{ text: 'Name', value: 'name', sortable: false },
{ text: 'Actions', sortable: false }
],
}
},
methods: {
run(report) {
debugger
// this.$parent.tabs_value returns as undefined
}
}
}
</script>
you can use component events i.e $emit.
Below is example which will tell you how to use $emit. (https://vuejs.org/guide/components/events.html#emitting-and-listening-to-events)
Parent Component
<template>
<ChildComponent #updateTabsValue="updateTabsValue"/>
</template>
<script>
export default {
data(){
return {
tabsValue: 'tabs',
};
},
methods:{
updateTabsValue(val){
this.tabsValue = val;
}
},
}
</script>
Child Component
<template>
<button #click="$emit('updateTabsValue','newVal')"/>
</template>

Vue.js: How can I put a Login Modal inside a dropdown Menu?

I have the following dropdown menu:
<template>
<v-menu close-on-click transition="slide-y-transition">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" v-bind="attrs" v-on="on">
Menu
</v-btn>
</template>
<v-list>
<v-list-item v-for="(item, index) in menuItemsMisc" :key="index" v-model="item.model">
<v-list-item-title>
<v-btn block color="white" #click="item.fn">{{ item.title }}</v-btn>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<!-- Modal code here -->
</template>
<script>
export default {
name: 'MenuBar',
data: () => ({
loginModal: false,
purchaseModal: false,
menuItemsMisc: [
{ title: 'Login',
model: 'loginModal',
fn: () => { this.loginModal = true}
},
{ title: 'Purchase',
model: 'purchaseModal',
fn: () => { this.purchaseModal = true }
},
]
}),
}
</script>
And I am trying to display this Login Modal When the Login Button is clicked in the dropdown.
<v-dialog v-model="loginModal" persistent max-width="500px">
<v-card class="elevation-12">
<v-toolbar color="primary" dark flat>
<v-toolbar-title>Login form</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text>
<v-form>
<v-text-field name="login" prepend-icon="mdi-account" type="text"></v-text-field>
<v-text-field id="password" name="password" prepend-icon="mdi-lock" type="password">
</v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary">Login</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
But whenever I click the Login or Purchase Button, I have an error that says:
TypeError: Cannot set property 'loginModal' of undefined
What is the Problem here?
From the Vue docs on v-model:
You can use the v-model directive to create two-way data bindings on form input, textarea, and select elements. It automatically picks the correct way to update the element based on the input type.
The v-model property on your <v-dialog> component is expecting it to be an input of some type.
You should be able to simply change this to a v-if:
<v-dialog v-if="loginModal" persistent max-width="500px">
This will cause the <v-dialog> component to display when your button is clicked.
EDIT: Please also make sure your data property on the Vue instance is declared as a class-style function. If you use a lambda function you will lose the this scope when referring to this.loginModal:
export default {
...
data() {
return {
...
}
}
}

Vuetify table will not render when it is placed inside another component

I made a table component in my project as I have multiple tables that I want to all look the same.
I insert my table onto my page and everything is working fine.
On the table is a dialog which opens correctly but inside that dialog is another one of my table components and this does not render.
It will work if I change the name of the component and have two separate instances but that is not what I am trying to do.
How can I get my table working across all components? Using Vue CLI.
Table component:
<template>
<v-data-table :headers="headers" :items="items" :no-data-text="noDataText" :dark="dark">
<template v-slot:top>
<v-toolbar :dark="dark" flat>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-btn #click="dialog=true" color="success" class="mr-2">
Load Default Frames
<v-icon right>mdi-download</v-icon>
</v-btn>
<v-btn color="primary" class="mr-2">
Create New Frame
<v-icon right>mdi-image-plus</v-icon>
</v-btn>
<Dialog v-model="dialog" />
<!-- <Frame v-model="dialog" :editedFrame="editedFrame" :oldIndex="oldIndex" #close="close" />
<DefaultFrames v-model="defaultFramesDialog" :selectable="true" :items="defaultFrames" />-->
</v-toolbar>
</template>
</v-data-table>
</template>
Dialog component:
<template>
<v-dialog v-model="dialog">
<v-card :dark="dark">
<v-card-title>
{{ name}}
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-btn icon #click="dialog = false">
<!-- <v-icon #click="$emit('close')">mdi-close</v-icon> -->
</v-btn>
</v-card-title>
<Table :is="child_component" :headers="frameHeaders" :items="defaultFrames" />
<v-card-actions>
<v-btn #click="log">Log</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
Table.vue
<template>
<v-btn #click.stop="emitOpenDialog">Open Dialog</v-btn>
</template>
<script>
export default {
methods: {
emitOpenDialog() {
// I've use vueBus for emiting
this.$bus.emit("open-dialog")
}
}
}
</script>
Dialog.vue
<template>
<v-dialog v-model="dialog"> ... </v-dialog>
</template>
<script>
export default {
data: () => ({
dialog: false
},
created() {
this.$bus.on("open-dialog", this.openDialog)
},
beforeUnmount() {
this.$bus.off("open-dialog")
},
methods: {
openDialog() {
this.dialog = true
}
}
}

how to trigger two events with #click in vue.js

I am building a task manager app using Vue.js, Vuetify, and Firebase. Clicking "Add new note" opens a Vuetify dialog box which prompts the user to input data. Clicking save closes the dialog box while simultaneously submitting and rendering the inputted data to a task card on the screen. The rendered task card includes a "view/edit" button which, when clicked, opens a second dialog box for the user to view and edit data. My issue is with editing. I currently have the "view/edit" button set up with an #click event that triggers the modal. I need this view/edit #click event to also trigger a function that binds the data in a selected task card to the second dialog box, which can then be edited. I attempted to achieve this by setting up the #click event in the "view/edit" button like so:
<v-btn color="primary" dark #click.stop="dialogUpdate = true; editTodo(todo)">
View/Edit
</v-btn>
...as you can see, the #click event contains "dialog = true", which is used to toggle the Vuetify dialog box, and "editTodo(todo)", which triggers the editTodo function that binds the inputted data to the second dialog box. This currently works fine, but my issue is that I am not clear on whether or not I should actually be adding two functions to one click event. Other Stack Overflow posts that I have researched suggest that this is not a good practice. If this is wrong, then how would you recommend configuring the "view/edit" button so that opening the second dialog box also triggers editTodo function? My full component is below. Thank you!
<template>
<div id="app" data-app>
<v-layout justify-center>
<v-btn color="primary" dark #click.stop="dialog = true">
Add New Note
</v-btn>
<v-dialog v-model="dialog" max-width="290">
<v-card color="#f9efaf">
<v-form #submit.prevent="addTodo">
<v-card-text>
<textarea-autosize v-model="newTitle" :min-height="50" placeholder="add title"></textarea-autosize>
<textarea-autosize v-model="newTodo" type="text" style="width: 100%" :min-height="100" placeholder="add note"></textarea-autosize>
</v-card-text>
<v-btn type="submit" color="green darken-1" text #click="dialog = false">
Add
</v-btn>
</v-form>
<v-card-actions>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogUpdate" max-width="290">
<v-card color="#f9efaf">
<v-form #submit.prevent="updateTodoText">
<v-card-text>
<textarea-autosize v-model="todoEditTitle" :min-height="50" placeholder="add title"></textarea-autosize>
<textarea-autosize v-model="todoEditText" type="text" :min-height="100" placeholder="add note"></textarea-autosize>
</v-card-text>
<v-btn type="submit" color="green darken-1" text #click="dialogUpdate = false">
Update
</v-btn>
</v-form>
<v-card-actions>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-layout>
<v-container>
<v-flex md12 class="elevation-0">
<v-layout wrap>
<v-flex md3 v-for="todo in todos" :key="todo.id">
<v-card color="#f9efaf" class="card-container">
<textarea-autosize v-model="todo.title" class="todo-text" readonly style="width: 60%"></textarea-autosize>
<textarea-autosize v-model="todo.text" class="todo-text" readonly></textarea-autosize>
<v-btn #click="deleteTodo(todo.id)">Delete</v-btn>
<v-btn color="primary" dark #click.stop="dialogUpdate = true; editTodo(todo)">
View/Edit
</v-btn>
</v-card>
</v-flex>
</v-layout>
</v-flex>
</v-container>
</div>
</template>
<script>
import { todosCollection } from './firebase';
import { mapState } from 'vuex'
export default {
name: 'app',
created() {
this.getData();
},
data () {
return {
todos: [],
newTitle: '',
newTodo: '',
currentlyEditing: null,
todoEditTitle: '',
todoEditText: '',
dialog: false,
dialogUpdate: false
}
},
methods: {
getData(){
const todos = []
todosCollection.orderBy('createdAt').get()
.then(snapshot => {
snapshot.forEach(doc => {
let userData = doc.data()
userData.id = doc.id
todos.push(userData)
})
this.todos = todos
})
},
addTodo() {
todosCollection.add({
title: this.newTitle,
text: this.newTodo,
createdAt: new Date()
})
.then(() => {
this.newTitle = '',
this.newTodo = ''
})
},
deleteTodo(id) {
todosCollection.doc(id).delete()
.then(() => {
console.log('Document successfully deleted')
})
.then(() => {
this.getData()
})
},
editTodo(todo) {
this.currentlyEditing = todo.id
this.todoEditText = todo.text
this.todoEditTitle = todo.title
},
updateTodoText() {
todosCollection.doc(this.currentlyEditing).update({
text: this.todoEditText,
title: this.todoEditTitle
})
.then(() => {
this.getData()
})
.catch(function(error) {
console.error("Error updating document text: ", error);
});
this.currentlyEditing = null;
this.todoEditText = '';
this.todoEditTitle = '';
}
}
}
</script>
<style>
body {
margin: 0;
padding: 0;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: 0;
padding: 0;
}
.card-container {
margin: 10px;
padding: 10px;
}
</style>
SOLUTION:
<v-btn color="primary" dark #click.stop="editTodo(todo)">
View/Edit
</v-btn>
editTodo(todo) {
this.dialogUpdate = true
this.currentlyEditing = todo.id
this.todoEditText = todo.text
this.todoEditTitle = todo.title
},
This is the solution. dialogUpdate = true is supposed to be wrapped inside the editTodo() function, along with code used to bind the inputted data to the second dialog box.
<v-btn color="primary" dark #click.stop="editTodo(todo)">
View/Edit
</v-btn>
editTodo(todo) {
this.dialogUpdate = true
this.currentlyEditing = todo.id
this.todoEditText = todo.text
this.todoEditTitle = todo.title
},

Vue.js dialog/modal closes on parent component

I am trying to open my CanvasPreview Component in another component but it fails,
first, it quickly shows the dialog/modal afterward it gets hidden again if I open the Vue Dev tool
the showCanvasPreview is set to false if I manually edit it in my console to true the modal gets shown.
So I guess that it gets set to false again, but I can't see why.
This is the dialog/modal component:
<template>
<v-dialog
v-model="show"
>
<v-card>
<v-card-actions>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
</v-layout>
</v-container>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import CanvasPreviewSourceUpload from './CanvasPreviewSourceUpload';
export default {
components: {
'canvas-preview-source-upload': CanvasPreviewSourceUpload
},
props: {
imgSrc: String,
visible: Boolean
},
computed: {
show: {
get () {
return this.visible;
},
set (visible) {
if (!visible) {
this.$emit('closePreview');
}
}
}
},
}
</script>
And in my parent component I call the preview component like this:
<template>
<div>
//... some more html
<div id="canvas-body">
<canvas id="pdf-render"></canvas>
<canvas id="selectCanvas"
#mousedown="markElementOnMouseDown"
#mousemove="updatePreview"
#mouseup="markElementOnMouseUp">
</canvas>
</div>
<canvas-preview
:imgSrc="this.targetImage.src"
:visible="showCanvasPreview"
#closePreview="showCanvasPreview=false">
</canvas-preview>
</div>
</template>
<script>
import CanvasPreview from '#/js/components/CanvasPreview';
export default {
components: {
'canvas-preview': CanvasPreview
},
props: {
'name': String
},
data: () => ({
showCanvasPreview: false,
...
}),
methods: {
markElementOnMouseUp (event) {
this.isDragging = false;
this.targetImage.src = this.clipCanvas.toDataURL();
this.targetImage.style.display = 'block';
this.showCanvasPreview = true;
console.log("mouseup: " + this.showCanvasPreview);
},
}
</script>
Try this one
<v-dialog
v-model="show"
>
<v-card>
<v-card-actions>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
<canvas-preview-source-upload
:imgSrc="imgSrc"
#close.stop="show=false">
</canvas-preview-source-upload>
</v-layout>
</v-container>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import CanvasPreviewSourceUpload from './CanvasPreviewSourceUpload';
export default {
components: {
'canvas-preview-source-upload': CanvasPreviewSourceUpload
},
data: ()=> ({
show: false
}),
props: {
imgSrc: String,
visible: Boolean
},
watch: {
show(isShow){
if (!isShow) {
this.$emit('closePreview');
}
}
visible(isVisible) {
this.show = isVisible;
}
}
}
</script>```
Something like this should allow you to open a v-dialog from a separate component..
If you supply a CodePen or CodeSandbox with your code in it, we would be able to better assist you.
[CodePen mirror]
const dialog = {
template: "#dialog",
props: {
value: {
type: Boolean,
required: true
},
},
computed: {
show: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
}
}
},
};
const dialogWrapper = {
template: "#dialogWrapper",
components: {
appDialog: dialog,
},
data() {
return {
isShown: false,
}
}
}
new Vue({
el: "#app",
components: {
dialogWrapper
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.6/dist/vuetify.min.js"></script>
<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#1.5.6/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app>
<v-content>
<dialog-wrapper/>
</v-content>
</v-app>
</div>
<script type="text/x-template" id="dialog">
<v-dialog v-model="show">
<v-card>
<v-card-actions pa-0>
<v-spacer/>
<v-btn dark small color="red" #click="show = false">Close</v-btn>
<v-spacer/>
</v-card-actions>
<v-card-title class="justify-center">
<h2>
Hello from the child dialog
</h2>
</v-card-title>
</v-card>
</v-dialog>
</script>
<script type="text/x-template" id="dialogWrapper">
<div>
<h1 class="text-xs-center">I am the wrapper/parent</h1>
<v-container>
<v-layout justify-center>
<v-btn color="primary" dark #click.stop="isShown = true">
Open Dialog
</v-btn>
</v-layout>
</v-container>
<app-dialog v-model="isShown"></app-dialog>
</div>
</script>

Categories

Resources