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>
Related
In my parent component I have a button and child component:
<div> #click="editMe(edit)" />
<edit-team :openModal="openModal"/>
</div>
export default {
data() {
return {
openModal: false,
};
},
method: {
editMe(edit) {
this.openModal = true;
},
}
}
So after I click the editMe button, I am expecting openModal becomes true and goes true to child component.
<template>
<el-dialog
:visible.sync="modal"
/>
</template>
<script>
export default {
props: {
openModal: {
type: Boolean,
required: true,
},
},
data() {
return {
modal: this.openModal,
};
},
</script>
But unfortunately, the modal is not opening because props comes as false always. I assigned openModal to new variable because it was giving me a warning about mutating props. So how do you think I can send the props in right value?
In child just try this
<template>
<el-dialog
:visible.sync="openModal"
/>
</template>
<script>
export default {
props: {
openModal: {
type: Boolean,
required: true,
},
},
},
</script>
If you have sibilngs components and you need to retrieve data, you can use the emit keyword and emit events
Then it will work like this :
The sibling emit the event to show the modal
The parent update the showModalData to true
This child is re-rendered
Vue.component('modal', {
template: '#modal',
props: ['show'],
});
Vue.component('buttonModal', {
template: '#buttonModal',
methods: {
showModal(){
this.$emit('show-modal-button', true)
}
}
});
new Vue({
el: '#app',
data: () => ({
showModalData: false
}),
methods: {
editMe() {
this.showModalData = true
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<p>this is the parent</p>
<hr>
<button-modal #show-modal-button="editMe">Show modal</button-modal>
<hr>
<modal :show="showModalData" />
</div>
<template id="modal">
<div>
props show : {{ show }}
<h2 v-if="show">This is the modal</h2>
</div>
</template>
<template id="buttonModal">
<div>
<p>This is the sibilng</p>
<button #click="showModal"> Show the modal through sibiling components </button>
</div>
</template>
In this project, we can login from login.vue by clicking login button and if it is success then we can see Lnb.vue in dashboard.vue
I thought if i code like this then pageSso will be 1 when I check the checkbox in login.vue in Lnb.vue then it will not show only "Account" menu.
When I used console.log(pageSso) at mounted cycle it showed pageSso was 0. What would be the problem?
store/store.js
export const state = () => ({
pageSso: 0,
})
export const getters = {
pageSso: (state) => state.pageSso,
}
export const mutations = {
setPageSso(state, data) {
console.log('mutations setPageSso data', data)
state.pageSso = data
}
}
export const actions = {
setPageSso({
commit
}, data) {
console.log('actions setPageSso data', data)
commit('setPageSso', data)
},
}
pages/login.vue
<template>
<input
class="checkbox_sso"
type="checkbox"
v-model="sso"
true-value="1"
false-value="0" >SSO checkbox
<button class="point" #click="submit">login</button>
</template>
<script>
export default {
data() {
return {
sso: '',
}
},
computed: {},
methods: {
submit() {
this.$store.dispatch('store/setPageSso', this.sso)
//this.$store.dispatch('store/login', data)
},
</script>
pages/dashboard.vue
<template>
<div class="base flex">
<Lnb />
<div class="main">
<Gnb />
<nuxt-child />
</div>
</div>
</template>
<script>
import Lnb from '#/components/Lnb'
import Gnb from '#/components/Gnb'
export default {
components: {
Lnb,
Gnb
},
mounted() {},
}
</script>
components/Lnb.vue
<template>
<ul>
<li :class="{ active: navbarState == 7 ? true : false }">
<a href="/dashboard/settings">
<img src="../assets/images/ico_settings.svg" alt="icon" /> Settings
</a>
</li>
<li v-show="pageSso != 1" :class="{ active: navbarState == 8 ? true : false }">
<a href="/dashboard/user">
<img src="../assets/images/ico_user.svg" alt="icon" />
Account
</a>
</li>
</ul>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState('store', {
// navbarState: (state) => state.navbarState,
pageSso: (state) => state.pageSso,
}),
},
mounted() {
console.log('pageSso ->', this.pageSso);
},
methods: {
},
}
</script>
Your console.log(pageSso) logs 0 because the mounted hook of Lnb.vue happens once, and it happens when the component is inserted into the DOM.
You insert Lnb into the DOM unconditionally in this line of dashboard.vue:
<Lnb />
and this is roughly when it's mounted hook is triggered.
Your pageSso seems to be changed only after you triggered the submit() method, which — I guess — happens way later, when you submit the login form.
Your Lnb.vue currently is always mounted. If you don't want to show it until pageSso is equal to 1, add a v-if on it in dashboard.vue like this:
<Lnb v-if="pageSso === 1" />
You currently don't have pageSso variable in dashboard.vue, you must take it from the store.
N.B.: Mind the difference between v-show and v-if directives: v-show only hides the component with display: none; while v-if actually removes or inserts the component from/to the DOM. With v-show, the component gets mounted even if you don't see it. With v-if, the component's mounted hook will fire each time the condition evaluates to true.
I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>
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 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.