Parent component:
<template>
<v-btn v-on:click="dialog = !dialog">
</v-btn>
</template
<script>
export default {
data: () => ({
dialog: false
}
</script
Child component:
<template>
<v-layout>
<v-dialog v-model="toggledialog">
<v-btn color="green darken-1" flat="flat"
#click.native="toggledialog = false">Close</v-btn>
</v-dialog>
</v-layout>
</template>
<script>
export default {
data: () => ({
toggledialog: false,
}),
watch: {
dialog: function(){
this.toggledialog = true
},
},
props:['dialog']
}
</script>
This code works but I really don't like this workaround with watch.
Questions:
How to open dialog in child component when v-model="toggledialog" if it doesn't watch for props:['dialog']?
How to change dialog to false in parent component when I i change it in child component v-model="dialog" if in Vue JS it is not allowed to change props?
Pass value prop as value to v-dialog, and re-emit input v-dialog whenever you want to close it:
//CustomDialog.vue
<v-dialog :value="value" #input="$emit('input')">
<v-btn color="green darken-1" flat="flat"
#click.native="$emit('input')"
>Close</v-btn>
</v-dialog>
...
props:['value']
and add v-model to your parent
//Parent.vue
<custom-dialog v-model="dialog">
So no data and no watch
example
Related
I have
<template>
<div class="dashboard">
<Navbar />
<MainPanel :title="Dashboard" :icon="dasboard" />
</div>
</template>
<script>
import MainPanel from '../../components/MainPanel'
import Navbar from '../../components/Navbar'
export default {
name: 'Dashboard',
components: {
Navbar,
MainPanel
}
}
</script>
As you can see, I'm trying to reuse my MainPanel component.
MainPanel
<template>
<v-container fluid class="my-5">
<v-row>
<v-flex col-xs-12>
<v-card elevation="2" class="pb-15">
<v-flex xs12 class="text-center">
<v-card-title>
<v-btn dark text color="black">
<v-icon right class="mr-2">{{ icon }}</v-icon>
<span>{{ title }}</span>
</v-btn>
</v-card-title>
</v-flex>
</v-card>
</v-flex>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'MainPanel',
props: {
icon: String,
title: String
},
data() {
return {
icon: '',
title: ''
}
}
}
</script>
<style lang=""></style>
In the console, I kept getting this error
[Vue warn]: Property or method "dasboard" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Can someone please give me some hints ?
It seems a very simple mistake:
:title="Dashboard"
in Vue using a column (:) to prepend a prop or an attribute on a component, will use a property defined or in props or in data.
While if you use title="dashboard" you'll actually pass a string, that is what you want
Using the binding sign : when using raw values in props means that values should be present as properties inside the script options :
<MainPanel title="Dashboard" icon="dasboard" />
According Vue2 official guide, props can simply get value from attribute or pass by v-bind to achieve responsive.
So in this case, you might want to using title instead :title.
<MainPanel title="Dashboard" icon="dasboard" />
If you want to make MainPanel title can be change responsively then you can clam a variable in data and pass by v-bind:
<MainPanel :title="varaibleForTitle" :icon="varaibleForIcon" />
By the way, you can pass a string value by v-bind - even though I don't understand why would anyone want to do this.
<MainPanel :title="'MainPanel'" :icon="'dasboard'" />
Given the following component
<template>
<div class="text-center">
<v-dialog
v-model="dialog"
width="500"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="red lighten-2"
dark
v-bind="attrs"
v-on="on"
>
Click Me
</v-btn>
</template>
<v-card>
<v-card-title class="headline grey lighten-2">
Privacy Policy
</v-card-title>
<v-card-text>
Some text
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
text
#click="handleAsyncAction"
>
I accept
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
#Component()
export class MyDialog extends Vue {
dialog: booolean = false;
async handleAsyncAction() {
const value = await this.$emit('click'); // you can't really do that
if(value === 'x') {
dialog = false; //<--- then close
} else {
alert('error!')
}
}
}
</script>
Used by some parent like this:
<template>
<div>
<MyDialog #click="makeSomeAjaxRequest($event)"
</div>
</template>
<script type="ts">
import Vue from "vue";
import Component from "vue-class-component";
import MyDialog from "./MyDialog"
#Component({
components: { MyDialog }
})
export default class ParentComponent extends Vue {
async makeSomeAjaxRequest() {
const data = await fetch('http://example.com');
return data.toString();
}
}
</script>
Using React you'll be just able to pass some function from the parent and wrap the whole thing up, but from my understanding, this is not the Vue way of doing things.
How should I approach making the child component 'await' for the call to be back?
Vue stores all of the relevant event listeners inside the component under this.$listeners
By leveraging that, you'll be able to invoke the function without emitting an actual event:
// MyDialog
async handleAsyncAction() {
//#ts-ignore since Vue 2s' TS definitions condone such behavior
const value = await this.$listeners.click();
if(value === 'x') {
dialog = false; //<--- then close
} else {
alert('error!')
}
}
}
You can see a complete example in an article named Reacting to Promises from event listeners in Vue.js
Hello I have a Child component that the main function is to filter and render a list of items. This child component is to be used in multiple parent components(views) and depending on the parent component the child component need to render a different child component (grand child).
Parent Component
<template>
<main>
//Child Component
<list-component
name="my items"
//List of Items I need to render
:list="items.list"
>
//Slot Passing my grandchild component
<template slot="child-component">
<component :is="child_component" :items="item"></component>
</template>
</list-component>
</main>
</template>
<script>
import ListComponent from '.ListComponent';
import ItemComponent from '.ItemComponent.vue';
export default {
components: {
ListComponent,
ItemComponent
},
data() {
return {
child_component: 'ItemComponent'
};
},
}
</script>
ListComponent.vue (child component)
<template>
<main>
<v-row class="ma-0">
<v-col v-for="(item, index) in list" :key="index" class="pa-0">
// I would like render my grandchild component here.
<slot name="child-component"></slot>
</v-col>
</v-row>
</main>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
},
list: {
type: Array,
required: true
}
}
};
</script>
ItemComponent.vue (grand child)
<template>
<div>
<v-img
src="item.image"
></v-img>
<v-row>
<span>{{
item.name
}}</span>
</v-row>
</div>
</template>
<script>
export default {
props: {
item: {
type: Object,
required: true
}
}
}
</script>
So basically I need to be able to pass ItemComponent.vue(grandchild) from the View(grandfather) to the View's ListComponent.vue(child) so that the child component can loop trough the items passed from the parent and use the grand child to render the information.
Also where do I declare the props for the grandchild?
I was able to find a solution after all I will leave this for those who need it.
basically in the child component we need to give access to the attribute to the parent trough the slot by binding the item like:
<slot name="child-component" :item="item"></slot>
and on the parent we can access it by binding the slot and giving a name to the object in this case I chose child and notice that on the component we can access item by declaring child.item
<template v-slot:child-component="child">
<component :is="child_component" :itinerary="child.item"></component>
</template>
now i have two components in my Dashboard:
Dashboard
<template>
<v-app>
<Toolbar :drawer="app.drawer"></Toolbar>
<Sidebar :drawer="app.drawer"></Sidebar>
</v-app>
</template>
<script>
import Sidebar from './components/layouts/Sidebar'
import Toolbar from './components/layouts/Toolbar'
import {eventBus} from "./main";
import './main'
export default {
components: {
Sidebar,
Toolbar,
},
data() {
return {
app: {
drawer: null
},
}
},
created() {
eventBus.$on('updateAppDrawer', () => {
this.app.drawer = !this.app.drawer;
});
},
}
</script>
Sidebar
<template>
<div>
<v-navigation-drawer class="app--drawer" app fixed
v-model="drawer"
:clipped="$vuetify.breakpoint.lgAndUp">
</v-navigation-drawer>
</div>
</template>
<script>
import {eventBus} from "../../main";
export default {
props: ['drawer'],
watch: {
drawer(newValue, oldValue) {
eventBus.$emit('updateAppDrawer');
}
},
}
</script>
Toolbar
<template>
<v-app-bar app>
<v-app-bar-nav-icon v-if="$vuetify.breakpoint.mdAndDown"
#click="updateAppDrawer">
</v-app-bar-nav-icon>
</v-app-bar>
</template>
<script>
import {eventBus} from "../../main";
export default {
props: ['drawer'],
methods: {
updateAppDrawer() {
eventBus.$emit('updateAppDrawer');
}
}
}
</script>
So now i have an infinite loop because when i press on icon in app-bar - watch in Sidebar, Sidebar understanding it like changes and starts a loop, updating drawer value in Dashboard, then Sidebar
catching changes in watch and starts new loop.
Also i have this warning, but it is another question.
[Vue warn]: Avoid mutating a prop
directly since the value will be overwritten whenever the parent
component re-renders. Instead, use a data or computed property based
on the prop's value. Prop being mutated: "drawer"
Thanks.
I have similar issue and able to solve by following this link
https://www.oipapio.com/question-33116
Dashboard
<template>
<v-app>
<Toolbar #toggle-drawer="$refs.drawer.drawer = !$refs.drawer.drawer"></Toolbar>
<Sidebar ref="drawer"></Sidebar>
</v-app>
</template>
Sidebar
<template>
<v-navigation-drawer class="app--drawer" app fixed v-model="drawer" clipped></v-navigation-drawer>
</template>
<script>
export default {
data: () => ({
drawer: true
})
}
</script>
Toolbar
<template>
<v-app-bar app>
<v-app-bar-nav-icon #click.stop="$emit('toggle-drawer')">
</v-app-bar-nav-icon>
</v-app-bar>
</template>
I've got two components. One parent, and one child component. In the child component I'm using a Vuetify Button component. When a user clicks the button, it should emit from the child to the parent component.
I've tried using different Vue methods to emit and capture the emit.
These are the components I'm using. I'm working with "Single file components".
// customer.vue | Parent component
<template>
<v-container grid-list-xl fluid>
<v-layout row wrap>
<v-dialog
persistent max-width="600px">
<customer-messages #cancelmessage="handleCancelMessage"></customer-messages>
</v-dialog>
</v-layout>
</v-container>
</template>
<script>
import CustomerMessages from "#/views/customer-care/customer-care_customer-messages.vue"
export default {
components: {
CustomerMessages,
},
data() {
return {
methods: {
handleCancelMessage() {
console.log('emit worked!');
}
}
}
}
};
// customer-care_customer-messages.vue | Child component
<template>
<v-btn
color="blue darken-1"
flat
#click="cancelMessage">
Cancel
</v-btn>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
cancelMessage() {
this.$emit('cancelmessage');
}
}
};
</script>
I expect the parent component to log "emit worked!". Instead, I keep getting the following error message:
[Vue warn]: Invalid handler for event "cancelmessage": got undefined
found in
---> <CustomerMessages> at src/views/customer-care/customer-care_customer-messages.vue
<ThemeProvider>
<VDialog>
<VCard>
<Customer> at src/views/customer-care/customer.vue
<VContent>
<VApp>
<App> at src/app.vue
<Root>