VueJS child component button changes class of parent - vue.js

I have a Navigation component with a button to toggle dark mode/light mode. When I click this button, I would like to change a class in the App.vue. How to pass the data from the button to the App.vue?
I believe I have to emit something from the Top Nav and v-bind the class in App.vue, but I don't quite understand how to get it change the class.
When I run this, the button does not change the class on the div.
App.vue
<template>
<div id="app" :class="[isActive ? 'darkmode' : '']>
<header>
<top-nav #change-mode="enableDarkMode"></top-nav>
</header>
...
</div>
</template>
<script>
export default {
name: "App",
components: {
TopNav,
},
props: ['isActive'],
data() {},
methods: {
enableDarkMode(isActive) {
console.log(isActive)
},
},
};
</script>
Top Nav Component
<template>
...
<div>
<button
:class="[isActive ? 'dark' : 'light']"
#click="toggle">
{{ isActive ? "DarkMode" : "LightMode" }}
</button>
</div>
</template>
<script>
export default {
name: "TopNav",
data() {
return {
isActive: false,
};
},
components: {},
methods: {
toggle() {
this.isActive = !this.isActive;
this.$emit('change-mode', this.isActive )
console.log('emit child')
},
},
};
</script>

From the snippet you provided it seems like the App.vue has isActive props instead of data for the method enableDarkMode to manage. Vue's props is not to be updated by the component they belong to because of how the data flow works in Vue props. With the App.vue being the parent of Top Nav component, you probably want it to be like this:
App.vue
<template>
<div id="app" :class="[isActive ? 'darkmode' : '']>
<header>
<top-nav #change-mode="enableDarkMode"></top-nav>
</header>
...
</div>
</template>
<script>
export default {
name: "App",
components: {
TopNav,
},
// this is probably not needed because App.vue is the parent component
// props: ['isActive'],
data() {
return {
isActive: false,
};
},
methods: {
enableDarkMode(isActive) {
// manages the data
this.isActive = isActive;
},
},
};
</script>
Top Nav component
<template>
...
<div>
<button
:class="[isActive ? 'dark' : 'light']"
#click="toggle">
{{ isActive ? "DarkMode" : "LightMode" }}
</button>
</div>
</template>
<script>
export default {
name: "TopNav",
data() {
return {
isActive: false,
};
},
components: {},
methods: {
toggle() {
this.isActive = !this.isActive;
this.$emit('change-mode', this.isActive )
console.log('emit child')
},
},
};
</script>

Related

how to send click button's value to a props

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>

How to create a method with props passed to child component (Vue3)

I am struggling on the last part of my starter project with outputting the results onto its own component.
I have created a method in the parent component to push the results to an array, and I am then passing that array as props to the child component.
If I just display the array in my child component it works fine, however what I am trying to do is then create a method in my child component based on the results passed via the props.
When I try to do this I am not getting anything outputted, is this something which you can do in Vue?
Parent:
<template>
<button #click="decreaseCount">Decrease</button>
<button #click="increaseCount">Increase</button>
<div class="counterDiv">Counter: {{ count }}</div>
<button #click="calculateResults">Submit</button>
<results v-if="resultsVisible" :results="results"></results>
</template>
<script>
import Results from "./components/Results.vue";
export default {
name: "App",
components: {
Results,
},
data() {
return {
count: 0,
results: [],
resultsVisible: false,
};
},
methods: {
increaseCount() {
this.count += 1;
},
decreaseCount() {
this.count -= 1;
},
calculateResults() {
this.results.push(this.count);
this.resultsVisible = true;
},
},
};
</script>
Child:
<template>
<div class="finalResults">Results: {{ this.resultsText }}</div>
</template>
<script>
export default {
props: ["results"],
data() {
return {
resultsText: "",
};
},
methods: {
returnText() {
if (results < 10) {
this.resultsText = "Below 10";
}
},
},
};
</script>
I have created a very basic example of my problem below
You should emit a custom event from child component which has as handler the parent method:
child :
<template>
<div class="finalResults">Results: {{ this.resultsText }}</div>
</template>
<script>
export default {
props: ["results"],
data() {
return {
resultsText: "",
};
},
methods: {
returnText() {
if (this.results < 10) {
this.resultsText = "Below 10";
this.$emit('push-item', this.resultsText )
}
},
},
};
</script>
in parent component :
<results v-if="resultsVisible" :results="results" #push-item="pushResult"></results>
...
methods:{
pushResult(resulttext){...}
....
}

Vue.js how can i add dynamic component to the dom

in my app i need to add components to the dom after fetching data from an api
i have a component called Carousel and a component called Home
so i don't know how many carousels i need in my Home component
the question is : how can i add components using for loop from inside methods:{}
My Code :
Home.vue
<template>
<div>
<template
v-for="widget in widgets"
v-bind:is="Carousel">
{{ widget }}
</template>
</div>
</template>
<script>
import Carousel from './widgets/Carousel.vue'
export default {
components: {
Carousel
},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
addWidget() {
this.widgets.push('Carousel')
},
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
this.addWidget()
});
}
}
}
</script>
Carousel.vue
<template>
<div>
<div :class="this.type == 'full' ? '' : 'container'">
Slider
</div>
</div>
</template>
<script>
export default
{
name:'Carousel',
props:[
'type',
'showTitle',
'dots',
'controls',
'data'
],
methods:
{
}
}
</script>
How to create as much carousels is i need using loop
the above code just give me a string 'carousel'
i want the component to be rendered
Here is a working example of dynamic components with properties
<div>
<template v-for="item in widgets">
<component :is="item.name" :name="item.name" v-if="item.name==='carousel'"></component>
</template>
</div>
The data in widgets
widgets: [
{name: 'carousel'},
{name: 'carousel'},
{name: 'test'},
{name: 'carousel'},
]
then it will create three components.
I created a test component like this to check this
components: {
'carousel': {
template: "<div>Dynamic component {{name}}</div>",
props: ["name"]
}
},

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

Vue component data not updating from props

I'm building a SPA with a scroll navigation being populated with menu items based on section components.
In my Home.vue I'm importing the scrollNav and the sections like this:
<template>
<div class="front-page">
<scroll-nav v-if="scrollNavShown" #select="changeSection" :active-section="activeItem" :items="sections"></scroll-nav>
<fp-sections #loaded="buildNav" :active="activeItem"></fp-sections>
</div>
</template>
<script>
import scrollNav from '.././components/scrollNav.vue'
import fpSections from './fpSections.vue'
export default {
data() {
return {
scrollNavShown: true,
activeItem: 'sectionOne',
scrollPosition: 0,
sections: []
}
},
methods: {
buildNav(sections) {
this.sections = sections;
console.log(this.sections)
},
changeSection(e) {
this.activeItem = e
},
},
components: {
scrollNav,
fpSections
}
}
</script>
this.sections is initially empty, since I'm populating this array with data from the individual sections in fpSections.vue:
<template>
<div class="fp-sections">
<keep-alive>
<transition
#enter="enter"
#leave="leave"
:css="false"
>
<component :is="activeSection"></component>
</transition>
</keep-alive>
</div>
</template>
<script>
import sectionOne from './sections/sectionOne.vue'
import sectionTwo from './sections/sectionTwo.vue'
import sectionThree from './sections/sectionThree.vue'
export default {
components: {
sectionOne,
sectionTwo,
sectionThree
},
props: {
active: String
},
data() {
return {
activeSection: this.active,
sections: []
}
},
mounted() {
this.buildNav();
},
methods: {
buildNav() {
let _components = this.$options.components;
for(let prop in _components) {
if(!_components[prop].hasOwnProperty('data')) continue;
this.sections.push({
title: _components[prop].data().title,
name: _components[prop].data().name
})
}
this.$emit('loaded', this.sections)
},
enter(el) {
twm.to(el, .2, {
autoAlpha : 1
})
},
leave(el, done) {
twm.to(el, .2, {
autoAlpha : 0
})
}
}
}
</script>
The buildNav method loops through the individual components' data and pushes it to a scoped this.sections array which are then emitted back to Home.vue
Back in Home.vue this.sections is populated with the data emitted from fpSections.vue and passed back to it as a prop.
When I inspect with Vue devtools the props are passed down correctly but the data does not update.
What am I missing here? The data should react to props when it is updated in the parent right?
:active="activeItem"
this is calld "dynamic prop" not dynamic data. You set in once "onInit".
For reactivity you can do
computed:{
activeSection(){ return this.active;}
}
or
watch: {
active(){
//do something
}
}
You could use the .sync modifier and then you need to emit the update, see my example on how it would work:
Vue.component('button-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
props: ['counter'],
watch: {
counter: function(){
this.$emit('update:counter',this.counter)
}
},
})
new Vue({
el: '#counter-sync-example',
data: {
foo: 0,
bar: 0
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="counter-sync-example">
<p>foo {{ foo }} <button-counter :counter="foo"></button-counter> (no sync)</p>
<p>bar {{ bar }} <button-counter :counter.sync="bar"></button-counter> (.sync)</p>
</div>