My parent component like this :
<template>
<div ref="modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content modal-content-data">
<form id="form-data">
...
<location-select .../>
...
</form>
</div>
</div>
</div>
</template>
<script>
import LocationSelect from './LocationSelect.vue'
export default{
name: 'CartModal',
components:{
LocationSelect,
},
mounted(){
$(this.$refs.modal).on('hidden.bs.modal', () => {
Object.assign(this.$data, this.$options.data())
})
}
}
</script>
If modal hidden, it will reset data in parent component and it works
I want to reset data also in child component
I try like this :
<template>
<select class="form-control" v-model="selected" ...>
...
</select>
</template>
<script>
export default{
name: 'LocationSelect',
...
created() {
$(this.$parent.$refs.modal).on('hidden.bs.modal', () => {
Object.assign(this.$data, this.$options.data())
})
}
};
</script>
But it does not work
The child component no reset the data
How can I solve this problem?
The main problem with this code is the handler in LocationSelect is being added before this.$parent.$refs.modal exists. A ref does not exist until the component is mounted.
The easiest way to solve this would be to move the code to mounted.
mounted() {
$(this.$parent.$refs.modal).on('hidden.bs.modal', () => {
Object.assign(this.$data, this.$options.data())
})
}
Or you could leave it in created and use nextTick.
created() {
this.$nextTick(() => {
$(this.$parent.$refs.modal).on('hidden.bs.modal', () => {
Object.assign(this.$data, this.$options.data())
})
})
}
Another way to handle this would be to add a ref to the LocationSelect component and add a method that clears it that can be called from the parent. In LocationSelect add this method:
methods:{
clear(){
Object.assign(this.$data, this.$options.data())
}
}
In the parent template add a ref:
<location-select ref="locationSelect" ... />
And in your parent's mounted:
mounted(){
$(this.$refs.modal).on('hidden.bs.modal', () => {
Object.assign(this.$data, this.$options.data())
this.$refs.locationSelect.clear()
})
}
However, the most idiomatic way to handle this with Vue would be to modify the component to support v-model and then it would be automatically cleared when the parent is cleared.
<template>
<select class="form-control" v-model="selected" ...>
...
</select>
</template>
<script>
export default {
props: ["value"],
name: 'LocationSelect',
computed:{
selected:{
get(){ return this.value },
set(v) { this.$emit('input', v) }
}
},
};
</script>
And then in the parent template:
<location-select v-model="someDataValue" ... />
If you did this, then when the parent is clear, the child is automatically cleared as well.
Related
<template v-for='item in 777'>
<ParentComponent>
<ChildComponent /
</ParentComponent>
</template>
Now, I want some events in ParentComponent to trigger some events in ChildComponent, however it's rendering generated components, I don't want to use Ref and prove&inject to do this, is there a more elegant way to implement it?
You can try with scoped slot, prop and watcher:
const app = Vue.createApp({})
app.component('parent', {
template: `
<div>
<button #click="trigger = true">evt</button>
<slot :trig="trigger"></slot>
</div>
`,
data: () => ({
trigger: false,
}),
})
app.component('child', {
template: `
<div>{{ evt }}</div>
`,
props: {
trig: {
type: Boolean,
default: false
}
},
data: () => ({
evt: 'a',
}),
watch: {
trig(val) {
if(val) this.trigEvt()
}
},
methods: {
trigEvt() {
this.evt = 'b'
}
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<template v-for='item in 7'>
<parent>
<template v-slot="{trig}">
<child :trig="trig"></child>
</template>
</parent>
</template>
</div>
I want to call the toggleDetails method of postDetails from the parent component's img tag and I don't seem to understand how to make it work.
Parent:
<div v-for="post in loggedInUser.posts" :key="post._id">
<postDetails :post="post"></postDetails>
<img #click.prevent="toggleDetails" class="grid-item" :src="post.imgUrl" />
</div>
</div>
Child:
<template>
<section v-if="this.isDetailsOpen">
{{this.post.desc}}
</section>
</template>
<script>
export default {
props: {
post: Object,
},
data() {
return {
isDetailsOpen: false
};
},
methods: {
toggleDetails() {
this.isDetailsOpen = !this.isDetailsOpen;
}
}
}
</script>
If you only want to emit an event from parent to child, you can do something like this:
Parent component:
<postDetails
ref="post"
:post="post"
></postDetails>
<img
#click.prevent="toggleDetails()"
class="grid-item"
:src="post.imgUrl"
/>
methods: {
toggleDetails () {
this.$refs.post.toggleDetails()
}
}
Child component:
methods: {
toggleDetails () {
this.isDetailsOpen = !this.isDetailsOpen
}
}
But to be able to send events between any components you would have to create an eventBus (eventHub).
I have a beginner question about passing function from parent to child. In my example, I want to to use the child more times and sometimes it should to do someting else v-on:focus. How can i do that? There are options to pass it with prop but i don't know how and i think it's not good to do it ? Maybe with EventBus and if yes then how ? I want to know the right way how to do it in VueJs.
Here is the Parent Component:
import Child from "./child.js";
export default {
name: "app",
components: {
Child
},
template: `
<div>
<child></child>
<child></child>
<child></child>
</div>
`
};
And here is the child Component:
export default {
name: "test",
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="functionFromChild">
</div>
</div>
`,
methods: {
functionFromChild() {
//run the function from parent
}
}
};
You can pass the function as any other prop
import Child from "./child.js";
export default {
name: "app",
components: {
Child
},
methods: {
calledFromChild(id){
console.log(id)
}
},
template: `
<div>
<child :callback="calledFromChild" id="1"></child>
<child :callback="calledFromChild" id="2"></child>
<child :callback="calledFromChild" id="3"></child>
</div>
`
};
And then in the child
export default {
name: "test",
props: ["callback", "id"],
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="() => this.calledFromChild(this.id)">
</div>
</div>
`,
}
I'm also adding an id to the child so you know which child is making the call.
But this is not a good idea. You should use emit from your child to send an event, and listen to it from the parent.
In the child
export default {
name: "test",
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="handleFocus">
</div>
</div>
`,
methods: {
handleFocus() {
this.$emit('focusEvent')
}
}
};
And in the parent
<child #focusEvent="handleFocusFromChild"></child>
A working example here
This should work:
const Child = {
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="functionFromChild">
</div>
</div>
`,
props: {
functionFromParent: Function
},
methods: {
functionFromChild: function() {
this.functionFromParent();
}
},
data() {
return {
message: 'Oh hai from the component'
}
}
}
const App = {
template: `
<div>
<h1>Quick test</h1>
<p>{{ message }}</p>
<Child :functionFromParent="functionOnParent"/>
<Child :functionFromParent="functionOnParent"/>
<Child :functionFromParent="functionOnParent"/>
</div>
`,
components: {Child},
methods: {
functionOnParent: function(){
console.log("there we go");
}
},
data() {
return {
message: 'Hello'
}
}
}
new Vue({
render: h => h(App),
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>
If you're trying to call a function in the parent from the child component, then try
this.$parent.parentMethod()
This will invoke the method in parent component.
I'm building simple modal component with Vue. I want to use this component in a parent components and to be able to toggle it from the parent.
This is my code now of the modal component:
<script>
export default {
name: 'Modal',
data() {
return {
modalOpen: true,
}
},
methods: {
modalToggle() {
this.modalOpen = !this.modalOpen
},
},
}
</script>
<template>
<div v-if="modalOpen" class="modal">
<div class="body">
body
</div>
<div class="btn_cancel" #click="modalToggle">
<i class="icon icon-cancel" />
</div>
</div>
</template>
I use the v-if to toggle the rendering and it works with the button i created inside my modal component.
However my problem is: I don't know how to toggle it with simple button from parent component. I don't know how to access the modalOpen data from the modal component
Ok, let's try to do it right. I propose to make a full-fledged component and control the opening and closing of a modal window using the v-model in parent components or in other includes.
1) We need declare prop - "value" in "props" for child component.
<script>
export default {
name: 'Modal',
props: ["value"],
data() {
return {
modalOpen: true,
}
},
methods: {
modalToggle() {
this.modalOpen = !this.modalOpen
},
},
}
</script>
2) Replace your "modalToggle" that:
modalToggle() {
this.$emit('input', !this.value);
}
3) In parent components or other includes declare "modal=false" var and use on component v-model="modal" and any control buttons for modal var.
summary
<template>
<div v-if="value" class="modal">
<div class="body">
body
</div>
<div class="btn_cancel" #click="modalToggle">
<i class="icon icon-cancel" />
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: ["value"],
methods: {
modalToggle() {
this.$emit("input", !this.value);
}
}
};
</script>
Example:
Vue.component('modal', {
template: '<div v-if="value" class="modal"><div class="body">modal body</div><div class="btn_cancel" #click="modalToggle">close modal<i class="icon icon-cancel" /></div></div>',
props: ["value"],
methods: {
modalToggle() {
this.$emit('input', !this.value);
}
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data:() =>({
modal: false
}),
methods: {
openModal() {
this.modal = !this.modal;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div #click="openModal">Btn modal</div>
<modal v-model="modal"></modal>
</div>
With your current implementation I would suggest you to use refs
https://v2.vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements
So in your parent component add ref="child" to modal (child) component and then open your modal by calling this.$refs.child.modalToggle()
You can use an "activator" slot
You can use ref="xxx" on the child and access it from the parent
My case like this
I have two component, parent and child component
My parent component like this :
<template>
...
<div class="row">
...
<location-select level="continentList" type="1"/>
...
<location-select level="countryList" type="2"/>
...
<location-select level="cityList" type="3"/>
...
</div>
</template>
<script>
...
export default{
...
}
</script>
The parent component is a modal bootstrap
My child component like this :
<template>
<select class="form-control" v-model="selected" #change="changeLocation">
<option value="0" disabled>Select</option>
<template v-for="option in options">
<option v-bind:value="option.id" >{{ option.name }}</option>
</template>
</select>
</template>
<script>
...
export default{
props: ['level','type'],
data() { return { selected: '' };},
computed:{
...mapGetters([
'getContinentList', 'getCountryList','getCityList'
]),
options(){
const n = ['getContinentList', 'getCountryList','getCityList']
return this[n[this.type-1]]
}
},
methods:{
...mapActions([
'getLocationList'
]),
changeLocation(event){
if(this.type==1){
this.getLocationList([{type:2},{level:'countryList'}])
this.getLocationList([{type:3},{level:'cityList'}])
}else if(this.type==2){
this.getLocationList([{type:3},{level:'cityList'}])
}
}
},
created() {
if(this.type==1)
this.getLocationList([{type:1},{level:'continentList'}])
if(this.type==2 && this.selected!='')
this.getLocationList([{type:2},{level:'countryList'}])
if(this.type==3 && this.selected!='')
this.getLocationList([{type:3},{level:'cityList'}])
},
mounted() {
$(this.$parent.$refs.modal).on('hidden.bs.modal', (e) => {
Object.assign(this.$data, this.$options.data())
})
},
};
</script>
If the modal show, I select continent, country and city. Then I close the modal
After that I show the modal again. Then I select country and city first, the data is still exist
I want to reset the data. So if I open modal again, before I choose continent, country and city data is not showing
I try :
Object.assign(this.$data, this.$options.data())
and this :
Object.assign(this.$data,this.$options.data.call(this))
and this too :
this.$forceUpdate()
when modal hidden
But, it does not work
Seems it must update data computed:{...}. But I'm still confused to do it
How can I solve this problem?
Have you tried reseting the selected data attribute in mounted lifecycle hook?
...
mounted() {
let $vm = this;
$(this.$parent.$refs.modal).on('hidden.bs.modal', (e) => {
$vm.selected = '';
});
},
...
Just v-if the modal component and it will rerender/recalculate everything when toggled.
Parent of Parent component:
<template>
<div class="parent-of-parent">
<your-modal-component v-if="isModalOpened" #close="closeModal"></your-modal-component>
</div>
</template>
<script>
export default{
data() {
return { isModalOpened: false };
},
methods: {
openModal() {
this.isModalOpened = true;
}, // method to open it
closeModal() {
this.isModalOpened = false;
}, // method to attach to emit event from modal on close
},
};
</script>
Be sure to attach click event on the icon you click for close or whatever.
And attach a method to the event which emits like this.
this.$emit('closeModal');
If you have problem animating the modal use <transition>
<transition name="fadeIn">
<your-modal-component v-if="isModalOpened" #close="closeModal"></your-modal-component>
</transition>