How can I reset data in child component from parent component on vue.js 2? - vue.js

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

How to let the parent component trigger the event of the child component without using refs in VueJs

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

Calling a child method from a button on a parent vue component

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).

Vuejs passing function from parent to child

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.

Vue modal component using 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

How can I reset dropdown data if modal closed on vue component?

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>