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

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

Related

Problem with button activation with v-slot activator

I'm using v-slot:activator with v-btn in my Vuejs project. it works fine but the button stays hovered as if it was pressed
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="#F65C38" dark class="mt-1 mr-2" width="209px" v-on="on" v-bind="attrs"> Example Btn </v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-form ref="form" v-model="valid">
<v-container>
<v-row>
</v-row>
</v-container>
</v-form>
</v-card-text>
<v-card-actions class="d-flex justify-center">
<v-btn color="#f66037" plain #click="close" elevation="4" dark width="209" class="mb-6"> No </v-btn>
<v-btn color="#F65C38" #click="save" dark width="209" class="mb-6"> save </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
data:
dialog: false
watch:
dialog(val) {
val || this.close();
},
method:
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
before click
after click
You can remove focus manually using native JS method:
document.activeElement.blur()
In your example you can place this row into $nextTick:
...
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
document.activeElement.blur()
});
},
Test this at CodePen.

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

Vue warning and dialog only appears once

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

Controlling visibility of multiple sibling identical components from the same parent while keeping isolated scope

Using Vue and Vuetify Data Tables I built a table in which each row presents an item of an array of objects.
Each row had a button which triggered a dialog which presented a form in which you could edit each row's data.
Here's the problem with instructions: https://codepen.io/gkatsanos-the-bold/pen/VwZzKeq
new Vue({
el: "#app",
vuetify: new Vuetify(),
data: () => ({
gateways: [],
dialog: false,
form: {
title: ""
}
}),
mounted() {
this.getGateways().then(data => {
this.gateways = data;
});
},
methods: {
async getGateways() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts"
);
const data = response.json();
return data;
},
openDialog(item) {
setTimeout(() => {
this.dialog = true;
this.form.title = this.item.title
});
}
}
});
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-content>
<v-container>
<v-data-table :items="gateways">
<template #item="{ item }">
<tr>
<td>{{ item.id }}</td>
<td>{{ item.title }}</td>
<td>
<v-menu>
<template #activator="{ on }">
<v-btn small class="meter-point-options-menu action-column-item" v-on="on">
open menu
</v-btn>
</template>
<v-list>
<v-list-item>
<v-btn color="red lighten-2" dark #click="openDialog()">
Click Me
</v-btn>
<v-dialog v-model="dialog" width="500">
<v-card>
<v-card-title>
ID: {{ item.id }}
</v-card-title>
<v-card-title>
TEXT: {{ item.title }}
</v-card-title>
<v-card-text>
<br> Issues:
<ol>
<li>Every time you close and open a Dialog / Popup, all of the previous ones open together because they're still in the DOM.</li>
<li>Notice that after navigating the pages, some of the ID and texts shown in the modal dialogs are different from the item opened!</li>
<v-form ref=form>
</v-form>
</v-card-text>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="primary" text #click="dialog = false">
close this
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-list-item>
</v-list>
</v-menu>
</td>
</tr>
</template>
</v-data-table>
</v-container>
</v-content>
</v-app>
</div>
As you see opening one dialog opens all the previous ones.
How should I tackle the issue of controlling the visibility of the v-dialogs in a unique fashion? I am thinking of moving them outside of the loop and having only one v-dialog and not one for every row of the table.

Categories

Resources