Vue.js dialog/modal closes on parent component - javascript

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>

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>

For loop in both parent and child component vuejs

I want to display items of each category.
For this I have a parent component that passes the category to the child component and it filters out the items based on that category and shows it for each of the categories. The child component loops through the items in that category.
The item array contains items of all category. I cant quite get the idea on how to do this and which lifecycle hooks I should use.
If anyone would take a look that would be appreciated.
The Parent Component:
<template>
<div>
<home-categories v-for="category in storeCategories" :key="category.name" :category="category.name"></home-categories>
</div>
</template>
<script>
import HomeCategories from "#/components/home/HomeCategories";
export default {
name: "HomeCategoryParent",
components: {
HomeCategories
},
created() {
this.$store.dispatch("bindStoreCategories")
},
computed: {
storeCategories() {
return this.$store.getters.storeCategories;
}
}
}
</script>
<style scoped>
</style>
The Child Component:
<template>
<v-container fluid style="margin-top: -20px;" class="google-font">
<p
class="google-font mt-0 mb-0"
style="font-weight: 200; font-size: 120%; padding: 0px;"
>
<b>{{ category }}</b>
</p>
<v-card
class="d-flex flex-row disableScroll"
flat
tile
style="margin-top: 16px; padding: 2px; overflow-x: auto;"
>
<v-card max-width="250" v-for="item in items" :key="item.name">
<v-img
class="white--text align-end"
height="200px"
src="../../assets/img/category_blank.jpg"
>
<v-card-title>{{ item.ITEM_NAME }}</v-card-title>
</v-img>
<v-card-subtitle class="pb-0">{{item.price}}</v-card-subtitle>
<v-card-text class="text--primary">
<div>{{item.desc}}</div>
<div>{{item.unit}}</div>
</v-card-text>
<v-card-actions>
<v-btn color="orange" text>
Add
</v-btn>
</v-card-actions>
</v-card>
</v-card>
</v-container>
</template>
<script>
export default {
data() {
return {
items: []
}
},
name: "HomeCategories",
props: ["category"],
created() {
this.$store.dispatch("bindStoreItems");
let all_items = this.$store.getters.storeItems;
this.items = this.$store.getters.storeItems;
all_items.forEach(item => {
let itemObj = {
category: null,
name: null,
desc: null,
price: null,
unit: null,
isAvail: null,
}
if(item.CATEGORY === this.category){
itemObj.category = item.CATEGORY;
itemObj.name = item.ITEM_NAME;
itemObj.desc = item.ITEM_DESC;
itemObj.price = item.ITEM_PRICE;
itemObj.unit =item.ITEM_UNIT;
itemObj.isAvail =item.ITEM_AVAIL;
this.items.push(itemObj);
}
})
},
};
</script>
<style scoped></style>

api data does not appear using VUEX and AXIOS in the VUETIFY app

I'm trying to present the data coming from my API using VUEX and AXIOS, however on my screen nothing appears as shown in the image:
But in console.log the data appears:
In Vue dev tools it appears as follows:
I tried different ways but due to my little experience I was not successful and I would like a help.
Follow my codes:
Clients.vue
<template>
<!-- Inicio do CONTAINER principal -->
<v-container fluid>
<v-row
justify="center"
>
<!-- Inicio do BLOCO principal -->
<v-col
md="9"
xs="12"
>
<nav-bar />
<v-card
class="mx-auto"
flat
height="900"
>
<v-list-item three-line>
<v-list-item-content>
<v-list-item-title class="display-1 font-weight-black">{{ pageName }}</v-list-item-title>
</v-list-item-content>
<!-- Inicio BLOCO button actions -->
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="success"
depressed
large
>
New Client
</v-btn>
</v-card-actions>
<!-- Final BLOCO button actions -->
</v-list-item>
<!-- Inicio COMPONENTE TAB -->
<v-card
flat
tile
>
<v-col>
<v-tabs>
<v-tab class="text-capitalize">Search</v-tab>
<v-tab class="text-capitalize">Dashboard</v-tab>
<v-tab-item>
<v-divider></v-divider>
<!-- InĂ­cio BLOCO Table-->
<v-card
flat
tile
>
<v-col
justify="center"
>
<v-col
md="5"
>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
outlined
dense
class="pt-3"
></v-text-field>
</v-col>
<v-data-table
:headers="headers"
:items="clients"
:search="search"
></v-data-table>
</v-col>
</v-card>
<!-- Final BLOCO Table-->
</v-tab-item>
</v-tabs>
</v-col>
</v-card>
<!-- Final COMPONENTE TAB -->
</v-card>
</v-col>
<!-- Final do BLOCO principal -->
</v-row>
</v-container>
<!-- Final do CONTAINER principal -->
</template>
<script>
import { mapState} from 'vuex';
const state = mapState(['clients']);
export default {
name: 'Clients',
computed: state,
data: () => ({
pageName: 'Clients',
search: '',
headers: [
{ text: 'Name', value: 'firstName' },
{ text: 'Phone', value: 'phone' },
{ text: 'E-mail', value: 'email' },
],
}),
created () {
this.initialize()
},
methods: {
initialize () {
//console.log(this.$store)
this.$store.dispatch('loadData') // dispatch loading
},
},
}
</script>
store/modules/client.module.js
import axios from 'axios'
const URL = 'http://192.168.15.11:3000/clients';
const client = {
state: () => ({
clients: [],
loading: true
}),
mutations: {
updateClients(state, clients) {
state.clients = clients
},
changeLoadingState(state, loading) {
state.loading = loading
}
},
actions: {
loadData({commit}) {
axios.get(URL).then((response) => {
console.log(response.data, this)
commit('updateClients', response.data)
commit('changeLoadingState', false)
})
}
},
getters: {
//
}
}
export default client;
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth.module'
import client from './modules/client.module'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
auth:auth,
client:client,
},
})
Note: The authentication part is working perfectly in this form of store modules

How to render dynamic elements in VueJS?

I am trying to create an app where users can select what kind of vuetify element they would like to render on the page? So I have 4 options that users can select from. I want to render the respective vuetify component on click, so if the user selects divider a <v-divider> </v-divider> should render, for a spacer, a <v-spacer></v-spacer> and for a toolbar a <v-toolbar></v-toolbar> and if they select text then a <v-btn></v-btn> with text would be displayed. I am really stuck on how I can do it.
This is a sample codepen
new Vue({
el: "#app",
data() {
return {
elements: [{
title: "Divider",
value: "divider"
},
{
title: "Spacer",
value: "spacer"
},
{
title: "Toolbar",
value: "toolbar"
},
{
title: "Text",
value: "text"
}
],
selected: []
};
},
methods: {
renderElements() {
console.log(this.selected);
this.selected = [];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout column>
<v-flex v-for="el in elements" :key="el.value">
<v-checkbox :value="el.value" v-model="selected" :label="el.title"></v-checkbox>
</v-flex>
<v-btn #click="renderElements"> Render Dynamic Elements</v-btn>
</v-layout>
</v-container>
</v-app>
</div>
I would really appreciate some help with this.
It seems you need to use dynamic components:
<component v-for="(el, i) in selected" :key="i" :is="el.value"></component>
new Vue({
el: "#app",
data() {
return {
elements: [
{
title: "Divider",
value: "v-divider"
},
{
title: "Spacer",
value: "v-spacer"
},
{
title: "Toolbar",
value: "v-toolbar"
},
{
title: "Text",
value: "v-btn"
}
],
selected: []
};
},
methods: {
renderElements() {
console.log(this.selected);
this.selected = [];
}
}
});
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout column>
<v-flex v-for="el in elements" :key="el.value">
<v-checkbox :value="el" v-model="selected" :label="el.title">
</v-checkbox>
</v-flex>
<v-btn #click="renderElements"> Render Dynamic Elements</v-btn>
<component v-for="(el, i) in selected" :key="i" :is="el.value"></component>
</v-layout>
</v-container>
</v-app>
</div>
new Vue({
el: "#app",
data() {
return {
elements: [
{
title: "Divider",
value: "v-divider",
show: false
},
{
title: "Spacer",
value: "v-spacer",
show: false
},
{
title: "Toolbar",
value: "v-toolbar",
show: false
},
{
title: "Text",
value: "v-btn",
show: false
}
],
selected: []
};
},
methods: {
renderElements() {
console.log(this.selected);
for(let i=0; i<this.elements.length; i++)
{
this.elements[i].show = this.selected.includes(i);
}
}
}
});
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout column>
<v-checkbox v-for="(item, i) in elements"
:key="i"
:label="item.title"
:value="i"
v-model="selected"
></v-checkbox>
<v-btn #click="renderElements"> Render Dynamic Elements</v-btn>
<component v-for="(item, i) in elements" :key="i + 10" :is="item.value" v-if="item.show">{{ item.title }}</component>
</v-layout>
</v-container>
</v-app>
</div>

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

Categories

Resources