how to refresh the value of computed when it changes - vue.js

I want to change the value of: class = "theme - $ {nightMode}" when I click toggle but it only works if I refresh the page and I can't figure out how to set up a watcher so that 'he looks at the value modify
``` <template>
<div id="app" :class="`theme-${nightMode}`">
<router-view />
<Header />
<Footer />
</div>
</template>
<script>
import Header from '#/components/molecules/Header/index.vue';
import Footer from '#/components/molecules/Footer/index.vue';
export default {
name: 'App',
components: { Header, Footer },
data() {
return {
themeMode: ''
};
},
computed: {
nightMode() {
const mode = localStorage.getItem('DarkMode');
if (mode === 'true') {
console.log('dark');
return 'dark';
} else {
console.log('light');
return 'light';
}
}
},
watch: {
themeMode(newVal) {
this.nightMode = newVal;
}
}
};
</script>
<style lang="scss" src="./assets/scss/style.scss"></style>```

Below are the changes
<template>
<div id="app" :class="`theme-${themeMode}`">
<router-view />
<Header />
<Footer />
</div>
</template>
<script>
import { mapGetters } from 'vuex'; // change Added
import Header from '#/components/molecules/Header/index.vue';
import Footer from '#/components/molecules/Footer/index.vue';
export default {
name: 'App',
components: { Header, Footer },
data() {
return {
themeMode: 'light' // change Added
};
},
computed: {
...mapGetters(['isDark']) // change Added
},
watch: { // change Added
isDark(newVal) {
this.themeMode = newVal ? 'dark' : 'light';
}
},
mounted() {
const mode = localStorage.getItem('DarkMode');
if (mode === 'true') {
console.log('dark');
return 'dark';
} else {
console.log('light');
return 'light';
}
}
};
</script>
<style lang="scss" src="./assets/scss/style.scss"></style>
```

You can't updating computed value by doing things like this.nightMode = newVal;.
Even if this is possible (I guess no) this would be missusing the vue framwork.
I think that it would be better to init themeMode inside the mounted (or created) hook see below:
<template>
<div id="app" :class="appClass">
<router-view />
<Header />
<Footer />
</div>
</template>
<script>
import Header from '#/components/molecules/Header/index.vue';
import Footer from '#/components/molecules/Footer/index.vue';
export default {
name: 'App',
components: { Header, Footer },
data() {
return {
themeMode: '',
};
},
mounted() {
const mode = localStorage.getItem('DarkMode');
this.themeMode = mode === 'true' ? 'dark' : 'light';
},
computed: {
appClass() {
return `theme-${this.themeMode}`;
},
},
};
</script>
EDIT:
Actually your toogle define in Home component is not modifying your local data themeMode, instead, it modify the isDark state of your vuex store.
=> You should directly use the isDark state to set you class:
<template>
<div id="app" :class="appClass">
<router-view />
<Header />
<Footer />
</div>
</template>
<script>
import Header from '#/components/molecules/Header/index.vue';
import Footer from '#/components/molecules/Footer/index.vue';
export default {
name: 'App',
components: { Header, Footer },
mounted() {
this.$store.commit('initializeDarkMode', localStorage.getItem('DarkMode'));
},
computed: {
appClass() {
return `theme-${this.$store.state.isDark === 'true' ? 'dark' : 'light'}`;
},
},
};
</script>

Related

Vue.js 2 data not displayed in the template

I'm using vue.js 2 / vue-cli with axios. I'm facing a problem with the display of my posts (in the wall). I've made my axios request, have gotten my data (in the console), I've written my template and ... nothing was displayed ... I really appreciate your help
my template :
<template>
<v-layout column>
<v-flex xs4>
<panel title="The Wall">
<p v-for="post in posts" :key="post.index">{{ post.title }} - {{ post.content }}</p>
</panel>
</v-flex>
</v-layout>
</template>
my script :
<script>
import Panel from '../components/Panel'
import PostService from "../services/PostService.js";
export default {
components: {
Panel
},
data() {
return {
posts: null
}
},
async mounted() {
this.posts = (await PostService.getAllPosts()).data;
}
}
</script>
Add a data property called cidItem for example and bind it to your props as follows
<template>
<div id="app" class="container" :cid="cidItem">
<Images :cid="cidItem" />
<Video :cid="cidItem" />
<TextType :cid="cidItem" />
<Card :cid="cidItem" />
</div>
</template>
<script>
import axios from 'axios';
import Images from "./components/Images";
import Video from "./components/Video";
import TextType from "./components/TextType";
import Card from "./components/Card";
export default {
name: 'app',
props: ["cid"],
components: {
Images,
Video,
TextType,
Card
},
mounted() {
axios({method: "GET", "url": this.contentDeliveryUrl}).then(result => {
// eslint-disable-next-line
this.content = amp.inlineContent(result.data)[0];
console.log(this.content)
}, error => {
console.error(error);
});
},
data() {
return {
contentDeliveryUrl: 'https://c1.adis.ws/cms/content/query?fullBodyObject=true&query=%7B%22sys.iri%22%3A%22http%3A%2F%2Fcontent.cms.amplience.com%2F${this.cid}%22%7D&scope=tree&store=testing',
content: [],
cidItem:'7e4301de-9c6e-4fab-9e68-3031b94d662d'
}
}
}
</script>
Since your component have the same structure i recommend to use mixins, create a file named myMixins.js and add the following code inside it :
const myMixins = {
props:['cid'],
mounted() {
axios({
method: "GET",
"url": this.contentDeliveryUrl
}).then(result => {
// eslint-disable-next-line
this.content = amp.inlineContent(result.data)[0];
console.log(this.content)
}, error => {
console.error(error);
});
},
data() {
return {
contentDeliveryUrl: 'https://c1.adis.ws/cms/content/query?fullBodyObject=true&query=%7B%22sys.iri%22%3A%22http%3A%2F%2Fcontent.cms.amplience.com%2F${this.cid}%22%7D&scope=tree&store=testing',
content: []
}
}
}
export default mixins;
and inside each component add this :
import myMixins from './myMixins'
export default{
....
mixins: [myMixin]
}

how can i pass a component with props from method to dynamic components in vue?

The idea is simple. To move a whole ready component with props from App to Header, but through a method.
I tried all the combination of with parenthesis/without etc.. but nothing seem to work..
is it possible?
App component:
<template>
<Header
#toggleAddTask="toggleAddTask"
:showAddTask="isShowAddTask"
title="Task Tracker"
func="func"
/>
</template>
<script>
import Header from "./components/Header.vue";
import Footer from "./components/Footer.vue";
import Button from "../src/components/Button.vue";
// import state from './router/index'
export default {
name: "App",
components: {
Header,
Footer,
},
methods: {
func() {
return <Button text="i'm a button" color="red" />;
},
},
};
</script>
Header component:
<template>
<header>
<component v-bind:is="func"></component>
</header>
</template>
<script>
import Button from "./Button.vue";
export default {
name: "Header",
props: ["title", "showAddTask", "func"],
components: {
Button,
},
};
</script>
You could instead of sending props, just decouple the data like so
<template>
<Header
#toggleAddTask="toggleAddTask"
:showAddTask="isShowAddTask"
:dynamic-component="dynamicComponentData"
title="Task Tracker"
/>
</template>
<script>
import Header from "./components/Header.vue";
import Footer from "./components/Footer.vue";
import Button from "../src/components/Button.vue";
export default {
name: "App",
components: {
Header,
Footer,
},
methods: {
dynamicComponentData() {
return {
component: Button,
props: {
text: "I'm a button",
color: "red"
}
}
},
},
};
</script>
With the header like this
<template>
<header>
<component
:is="dynamicComponent.component"
:text="dynamicComponent.props.text"
:color="dynamicComponent.props.color"
/>
</header>
</template>
<script>
export default {
name: "Header",
props: ["title", "showAddTask", "dynamic-component"],
};
</script>
But keep in mind, a better solution for this case would be using slots instead of injecting components.
<template>
<Header
#toggleAddTask="toggleAddTask"
:showAddTask="isShowAddTask"
title="Task Tracker">
<Button text="I'm a button" color="red"/>
</Header>
</template>
<script>
import Header from "./components/Header.vue";
import Footer from "./components/Footer.vue";
import Button from "../src/components/Button.vue";
export default {
name: "App",
components: {
Header,
Footer,
},
};
</script>
And you Header component should have a slot like this
<template>
<header>
<slot />
</header>
</template>
<script>
export default {
name: "Header",
};
</script>
As you asked, this could be a form of adaptor
<template>
<header>
<div class="typeButtons" v-if="componentType.button">
<component
v-text="dynamicProps.text"
:is="dynamicComponent.component"
:text="dynamicProps.textType"
:color="dynamicProps.color"
/>
</div>
<div class="typeInput" v-else-if="componentType.input">
<component
:is="dynamicComponent.component"
:label="dynamicProps.text"
:rules="dynamicProps.rules"
/>
</div>
</header>
</template>
<script>
export default {
name: "Header",
props: ["title", "showAddTask", "dynamic-component"],
computed: {
componentType() {
return {
button:
dynamicComponent.type === "typeOne" ||
dynamicComponent.type === "typeTwo",
input: dynamicComponent.type === "typeThree",
};
},
dynamicProps() {
switch (this.dynamicComponent.type) {
case "typeOne":
return {
text: "Create",
textType: false,
color: "success",
};
case "typeTwo":
return {
text: "Cancel",
textType: true,
color: "error",
};
case "typeThree":
return {
text: "Cancel",
rules: [(v) => Boolean(v)],
};
default:
return { ...this.dynamicComponent.props };
}
},
},
};
</script>

Vue: Variable not safed, when other component is shown

I have two components. One of it gives the value height to the other one, when "submit" is clicked. When "submit" is clicked the first component should be hidden and the second one visible.
It works so far, but it seems like height is not safed in the second component.
Thanks a lot!!
without the v-if it works perfect!
//ComponentOne
<template>
<body>
<div id="aside">
<footer>
<b-button v-on:click="submit">Submit</b-button>
</footer>
</div>
</body>
</template>
<script>
import { EventBus } from '#/main.js'
export default {
data() {
return {
submitp1: false,
height: 5,
width: 6,
}
},
methods: {
submit: function () {
this.submitp1 = !(this.submitp1)
EventBus.$emit('submitp1emit', this.submitp1)
EventBus.$emit('1to2', this.height)
}
},
}
</script>
//ComponentTwo
<template>
<div >
number <br />
height: {{height}}
</div>
</template>
<script>
import { EventBus } from '#/main.js'
export default {
data: function () {
return {
height: '',
}
},
mounted() {
const self = this
EventBus.$on('1to2', function{ height) {
self.height = height
})
}
}
</script>
//main.js
<template>
<div id="app">
<ComponentOne v-if="submitp1 == false" />
<ComponentTwo v-if="submitp1 == true" />
</div>
</template>
<script>
import { EventBus } from '#/main.js'
import ComponentOne from '#/components/p1Comp/ComponentOne.vue'
import ComponentTwo from '#/components/p1Comp/ComponentTwo.vue'
export default {
components: {
ComponentOne,
ComponentTwo
}
data: function () {
return {
submitp1: false
}
},
mounted() {
const self = this
EventBus.$on('submitp1emit', function (submitp1emit) {
self.submitp1 = submitp1emit
})
}
}
</script>
From the Vue documentation:
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
The toggled component is simply not there. As already mentioned, you can use "v-show" instead.

How to update data from vue-tables-2 after action from Template?

I'm using a custom component as a column on vue-tables-2, to do that I'm using a vue-component as described here: vue-components
I've created a button that opens a modal to the user confirm some information, and after that I make a request to the backend and the record is changed on the database.
Now I want to refresh the data on the table, but I don't know how to do that. The documentation said about using the $ref, but this is not an option because my component is not the parent.
How can I do that?
Links to the code:
Component using 'vue-tables-2'
<template>
<div>
<div id="payment">
<input type="checkbox" v-model="onlyPending" #change="filterPay()">Apenas pendentes</input>
<v-server-table url="/api/payments" :columns="columns" :options="options" ></v-server-table>
</div>
</div>
</template>
<script>
import pay from './ModalConfirmPay.vue'
import {Event} from 'vue-tables-2';
export default {
name: "AeraListPayment",
props: ['groupId'],
data: function(){
let groupId = this.groupId;
return {
columns: ['name','value','course','due_date','paid','installment','pay'],
options: {
responseAdapter : function(data) {
data.data = data.data.map(payment => {
payment.paid = payment.paid ? "pago" : "pendente";
return payment;
})
return data;
},
headings: {
installment: 'Parcela',
paid: 'Status',
value: 'Valor',
due_date: 'Vencimento',
pay: 'Ação',
course: 'Curso',
name: 'Nome'
},
templates : {
pay
},
customFilters: ['onlyPending','groupId'],
initFilters:{groupId:groupId,onlyPending:true}
},
onlyPending: true
}
},
methods: {
filterPay(){
Event.$emit('vue-tables.filter::onlyPending', this.onlyPending);
}
}
}
</script>
Component that is being used as a custom column:
<template>
<div>
<button #click.prevent="show">Pagar</button>
<modal :name="modalName">
<p>Confirma o pagamento de {{data.value}} ?</p>
<p>Parcela: {{data.installment}}</p>
<p>Vecimento: {{data.due_date}}</p>
<button #click.prevent="pay">Confirmar</button>
<button #click.prevent="hide">Cancelar</button>
</modal>
</div>
</template>
<script>
import PaymentService from '../../services/PaymentService'
let service = new PaymentService();
export default {
name:"ModalConfirmPay",
props: ["data"],
computed: {
modalName: function () {
// `this` aponta para a instância Vue da variável `vm`
return `confirm-pay-${this.data.clientGroup_id}-${this.data.installment}`
}
},
methods: {
show () {
this.$modal.show(this.modalName);
},
pay ( ) {
service.pay(this.data)
.then(this.hide());
},
hide () {
this.$modal.hide(this.modalName);
}
}
}
</script>
First, defined an EventBus if you don't have
EventBus.vue
import Vue from 'vue'
export default new Vue()
In ListPayment.vue, import EventBus and listen for refresh-table event. Note that I add ref="table" to vue-tables-2 element
<template>
<v-server-table ref="table" ... />
</template>
<script>
import EventBus from './EventBus.vue'
export default {
mounted() {
EventBus.$on('refresh-table', this.refreshTable)
},
beforeDestroy() {
EventBus.$off('refresh-table', this.refreshTable)
},
methods: {
refreshTable() {
this.$refs.table.refresh();
}
}
}
</script>
Finally, emit event in modal
pay() {
service.pay(this.data)
.then(() => {
EventBus.$emit('refresh-table')
})
.then(this.hide());
}

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>