Set data dynamically in array of objects in Vue component - vue.js

Given the following Vue code:
export default class StatsBox extends Vue {
stats = [
{
title: "Schedule Updates",
data: "12", // want to dynamically set this
},
{
title: "Other Widget",
data: "65", // want to dynamically set this
},
]
}
And this in the template:
<v-layout>
<v-flex v-for="(stat, index) in stats" v-bind:key="index">
<v-list-item-content>
<v-list-item-subtitle v-text="stat.title"></v-list-item-subtitle>
<br />
<v-list-item-title v-text="stat.data"></v-list-item-title>
</v-list-item-content>
</v-flex>
</v-layout>
How could I go about setting the data values dynamically? I have a module imported from my store like this:
import { myModule } from "#/store/modules/module1";
And I wish to use a getter on that module to populate the data in my objects.
I have tried adding the module getter to data directly in the objects but this doesn't work. For example:
{
title: "Schedule Updates",
data: myModule.getUpdates,
},

Related

Vue.js pass $store data from different modules

Hi I need to understand how to "pass" some $store values from settings module to header module, being the $store updated by settingsmodule
I have this app.vue module:
<template>
<v-app>
<router-view name="sidebar" />
<router-view name="header" :handleSettingsDrawer="handleSettingsDrawer" />
<v-main class="vue-content">
<v-fade-transition>
<router-view name="settings" />
</v-fade-transition>
</v-main>
<router-view name="footer" />
<app-settings-drawer
:handleDrawer="handleSettingsDrawer"
:drawer="settingsDrawer"
/>
</v-app>
</template>
in the <router-view name="header" /> there are a couple of v-menu to select currency and language:
<v-menu offset-y close-on-click>
<template v-slot:activator="{ on }">
<v-btn v-on="on" small fab class="mr-3">
<v-avatar size="30">
<v-img :src="currentLocaleImg"></v-img>
</v-avatar>
</v-btn>
</template>
<v-list dense class="langSel">
<v-list-item
v-for="(item, index) in langs"
:key="index"
#click="handleInternationalization(item.value,item.rtl)"
>
<v-list-item-avatar tile class="with-radius" size="25">
<v-img :src="item.img"></v-img>
</v-list-item-avatar>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
in the script section I import the availableLanguages and availableCurrencies , I set them in data() where I also reference the user:
data() {
return {
items: [
{ icon: "settings", text: "Settings",link: "/settings" },
{ icon: "account_balance_wallet", text: "Account", link:"/tos" },
{ divider: true },
{ icon: "login", text: "Log In",link:"/auth/login" },
{ icon: "desktop_access_disabled", text: "Lock Screen",link:"/auth/lockscreen" },
{ icon: "exit_to_app", text: "Logout",link:"/auth/logout" },
],
langs: availableLocale,
currs: availableCurrency,
user: this.$store.state.userdata.user,
};
},
then, in computed: I have the 2 functions that should get the current value of curr and lang:
currentLocaleImg()
{
return this.langs.find((item) => item.value === this.$store.state.userdata.user.locale).img;
},
currentCurrencyImg() {
return this.currs.find((itemc) => itemc.value === this.$store.state.userdata.user.currency).img;
}
}
BUT the value of this.$store.state.userdata.user is updated firing the loadData() function mounted in the <router-view name="settings" /> module of App.vue
mounted(){
this.loadData();
},
methods:
{
async loadData(){
let jwt=sessionStorage.getItem('jwt');
try{
const res = await this.$http.post('/ajax/read_settings.php', {"jwt":jwt});
this.$store.dispatch("setUser", res.data.user);
this.$store.dispatch("setLocale", res.data.user.locale);
this.$store.dispatch("setCurrency", res.data.user.currency);
}
the problem is that header module does not have the value of this.$store.state.userdata.user.locale
(and .currency) and I get twice the following message:
vue.runtime.esm.js:1888 TypeError: Cannot read property 'img' of undefined
I do not know how to "pass" the data from one module to the other (or perhaps because the header is rendered before the settings module) and therefore the value of the currency and of the language are not known yet
What's wrong in this procedure?
How can I fix it?
There's a race condition in which you try to use data in the template that isn't loaded yet. But first, avoid setting store data directly into component data because the component data won't be updated when the store changes. Replace this:
user: this.$store.state.userdata.user
With a computed:
computed: {
user() {
return this.$store.state.userdata.user;
}
}
You can use v-if to conditionally render only when some data is available:
<v-img :src="currentLocaleImg" v-if="user"></v-img>
The currentLocaleImg computed will not even trigger until user has some data so you will avoid the error. Do the same for currentCurrencyImg if necessary.

How to display Checkbox in DataTable body Vuetify

I have a Vue application which is built using Vuetify and I want to create a table where cell content is a checkbox v-checkbox but it renders html string instead of checkbox inside table body.
I use v-data-table as a main table.
For illustration:
My Code:
<v-data-table :headers="headers"
:items="menuaccess"
:search="search"
sort-by="alias"
class="elevation-3"
v-html="value"> <--- I tried add this but not working
<!-- other code -->
Data source snippet (I will get data from API later) :
headers: [
{ text: '', align: 'start', value: 'alias' },
{ text: 'ASSET_EDIT', value: '<v-checkbox class="mx-2" label="Example"></v-checkbox>' },
{ text: 'ASSET_VERIFICATION', value: '<v-checkbox class="mx-2" label="Example"></v-checkbox>' },
{ text: 'ASSET_VIEW', value: '<v-checkbox class="mx-2" label="Example"></v-checkbox>' },
{ text: 'CUSTOMER_COMPANY_EDIT', value: '<v-checkbox class="mx-2" label="Example"></v-checkbox>' },
],
You need to place code inside slot template.
<v-data-table :headers="headers"
:items="menuaccess"
:search="search"
sort-by="alias"
class="elevation-3">
<template v-slot:item.property_name="{ item }">
<v-simple-checkbox v-model="item.property_name" />
</template>
</v-data-table>
Where property_name is name of the property on your data object.
Read more about it in the documentation.

Vuetify: Data table slot value to toggle another slot value

I am using a Vuetify data table and I am using v-slot:item on two of the columns to insert switches.
<v-data-table
:headers="headers"
:items="records"
:search="search"
:items-per-page="5"
>
<template v-slot:item.monitor="{ item }">
<v-switch v-model="item.monitor" color="success"></v-switch>
</template>
<template v-slot:item.manage="{ item }">
<v-switch v-model="item.manage" color="success"></v-switch>
</template>
</v-data-table>
What I want to do is set the switches like an Item Group so that only one may be selected (on) at a time, but both can be false (off). So only one can have a true value.
I have tried using the Item Group component and got that to work, but I am not sure if it is even possible within the table the way I want since these two switches are in separate slots. I am under the impression that the v-item should be sibling components, so that makes me think that would't work for this situation.
The way I see it, there are two different approches to this problem. You can either:
Watch the records array to detect changes in either monitor or manage attributes of one of its objects.
Do not use v-model directive on the switches but provide a readonly value, then change it manually on click.
I prefer the second option. You can do it like so:
Template section
<div id="app">
<v-app id="inspire">
<v-data-table :headers="headers" :items="records">
<template v-slot:item.monitor="{ item }">
<v-switch
:input-value="item.monitor"
readonly
color="success"
#click.stop="setSwitchesForItem(item.id, 'monitor', !item.monitor)"
/>
</template>
<template v-slot:item.manage="{ item }">
<v-switch
:input-value="item.manage"
readonly
color="success"
#click.stop="setSwitchesForItem(item.id, 'manage', !item.manage)"
/>
</template>
</v-data-table>
</v-app>
</div>
A couple of things to note here:
v-model has been replaced by :input-value
readonly props has been set
the click event calls a custom method. It requires the .stop suffix because of a bug documented here
Script section
export default {
// ...
data() {
return {
headers: [
{ text: "Name", value: "name" },
{ text: "Monitor", value: "monitor" },
{ text: "Manage", value: "manage" }
],
records: [
{
id: 1,
name: "Item 1",
monitor: true,
manage: false
},
{
id: 2,
name: "Item 2",
monitor: false,
manage: true
},
{
id: 3,
name: "Item 3",
monitor: false,
manage: false
}
]
};
},
methods: {
setSwitchesForItem(itemId, attributeName, attributeValue) {
const record = this.records.find(r => r.id === itemId);
// In case the new value is true, we need to make sure the other one is set to false
if (attributeValue) {
record[attributeName === "monitor" ? "manage" : "monitor"] = false;
}
record[attributeName] = attributeValue;
}
}
}
Link to the Codepen.

vue: Dynamically inject property data into <component />

I'm trying to create a layout component which dynamically creates given components from a parent in either one or the other column. In the layout component I'm using the directive to create the children components. That works, however I'm having a hard time to get the data into the components themselves (they have different sets of properties).
Therefore:
How to inject property data into the < component :is="componentFromParent" / > directive?
I've got something to work, but it's pretty messy. I could do it via a "genericProps" property which contains all the data. Then forcing the children to update and they, via updated(), use Object.assign(this, 'props', this.genericProps) to unpack the genericProps and overwrite it's $props.
It feels pretty much as if I'm working against some princiles of vue.js here. When debugging the this.$options.components of the layout component are filled, however all these components don't have any propsData assigned. But directly using Object.assign() on the this.$options.components doesn't work, as it seems the layout component as the actually instances of these components as it's this.$childrend.
Also I've tried looping through the this.$childrend and assigning the propsData of the this.$options.components but there would have to be a key to match the components with it's correct child and at that point the childrend don't have any properties filled.
Code snippets to illustrate the (ugly) example which kinda works:
Parent Template
<template>
<two-column-layout :firstColumn="$vuetify.breakpoint.mdAndUp ? firstColumn : singleColumn"
:secondColumn="$vuetify.breakpoint.mdAndUp ? secondColumn : []"
/>
</template>
Parent Code
This code is called via mounted() or via watch when the async call from the API is finished.
createMetadataContent() {
let currentContent = this.currentMetadataContent;
const components = this.$options.components;
currentContent = this.mixinMethods_enhanceMetadataEntry(currentContent, this.cardBGImages);
if (currentContent && currentContent.title !== undefined) {
this.body = metaDataFactory.createBody(currentContent);
this.$set(components.MetadataBody, 'genericProps', this.body);
this.citation = metaDataFactory.createCitation(currentContent);
this.$set(components.MetadataCitation, 'genericProps', this.citation);
// a few more components and data here
this.firstColumn = [
components.MetadataBody,
// a few more components here
];
this.secondColumn = [
components.MetadataCitation,
// a few more components here
];
this.singleColumn = [
components.MetadataBody,
components.MetadataCitation,
// a few more components here
];
this.$forceUpdate();
}
}
TwoColumnLayout Template
<template>
<v-layout row wrap>
<v-flex>
<v-layout column>
<v-flex mb-2
v-for="(entry, index) in firstColumn"
:key="`left_${index}`"
>
<component :is="entry"
:genericProps="entry.genericProps"
/>
</v-flex>
</v-layout>
</v-flex>
<v-flex v-if="secondColumn" >
<v-layout column>
<v-flex mb-2
v-for="(entry, index) in secondColumn"
:key="`right_${index}`"
>
<component :is="entry"
:genericProps="entry.genericProps"
/>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
</template>
TwoColumnLayout Code
updated() {
this.$children.forEach((child) => {
child.$forceUpdate();
});
},
Child Code
props: {
genericProps: Object,
id: String,
citationText: String,
citationXmlLink: String,
ciationIsoXmlLink: String,
ciationGCMDXmlLink: String,
fixedHeight: Boolean,
showPlaceholder: Boolean,
},
updated: function updated() {
if (this.genericProps) {
Object.assign(this, 'props', this.genericProps);
}
},
I once had a similar situation where I had a dynamic <compnonent :is="currentComponent"> and solved it by storing a 'childPayload'-attribute to my data and passed it as prop to the children. Whenever my payload changed, it got refreshed in the children.
<template>
<div>
<div class="btn btn-primary" #click="changePayload">Change Payload</div>
<component :is="isWhat" :payload="payload"></component>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
isWhat: 'div',
payload: { any: 'data', you: 'want', to: 'pass'}
}
},
methods: {
changePayload() { // any method or api call can change the data
this.payload = { some: 'changed', data: 'here' }
}
}
}
</script>
I could solve it via slot but I've still needed to use forceUpdate() in the LayoutComponent...
However with the genericProps it's good enough for my case.
Parent Template
<template v-slot:leftColumn >
<div v-for="(entry, index) in firstColumn" :key="`left_${index}`" >
<component :is="entry" :genericProps="entry.genericProps" />
</div>
</template>
<template v-slot:rightColumn>
<div v-for="(entry, index) in secondColumn" :key="`right_${index}`" >
<component :is="entry" :genericProps="entry.genericProps" />
</div>
</template>
Parent Code
This code is called via mounted() or via watch when the async call from the API is finished.
createMetadataContent() {
let currentContent = this.currentMetadataContent;
const components = this.$options.components;
currentContent = this.mixinMethods_enhanceMetadataEntry(currentContent, this.cardBGImages);
if (currentContent && currentContent.title !== undefined) {
this.body = metaDataFactory.createBody(currentContent);
this.$set(components.MetadataBody, 'genericProps', this.body);
this.citation = metaDataFactory.createCitation(currentContent);
this.$set(components.MetadataCitation, 'genericProps', this.citation);
// a few more components and data here
this.firstColumn = [
components.MetadataBody,
// a few more components here
];
this.secondColumn = [
components.MetadataCitation,
// a few more components here
];
this.singleColumn = [
components.MetadataBody,
components.MetadataCitation,
// a few more components here
];
this.$forceUpdate();
}
}
TwoColumnLayout Template
<v-flex v-bind="firstColWidth" >
<v-layout column>
<slot name="leftColumn" />
</v-layout>
</v-flex>
<v-flex v-if="secondColumn" v-bind="secondColWidth" >
<v-layout column>
<slot name="rightColumn" />
</v-layout>
</v-flex>
TwoColumnLayout Code
updated() {
this.$children.forEach((child) => {
child.$forceUpdate();
});
},
Child Code
All child components have the genericProps object which is accessed via mixin method
props: {
genericProps: Object,
},
computed: {
title() {
return this.mixinMethods_getGenericProp('title');
},
id() {
return this.mixinMethods_getGenericProp('id');
},
description() {
return this.mixinMethods_getGenericProp('description');
},
}
Mixin method
mixinMethods_getGenericProp(propName) {
return this.genericProps[propName] ? this.genericProps[propName] : null;
},

Updata value of a chart with data in computed part

I have to make charts in vue js. But the data I get are in a vuex store. I can get access to them with the computed, but I didn't manage to update the chart with.
Here's the code:
<template>
<v-content>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
<v-flex v-for="(chart, id) in charts" :key="id" v-bind="{['xs'+chart.size]: true}">
<RedmineChart :name="chart.name" :type="chart.type" :getData="chart.getData"
:refresh="chart.refresh" :chartOptions="chart.chartOptions" :info="chart.info"
:context="chart.context"/>
</v-flex>
</v-layout>
</v-container>
</v-content>
<script>
import {mapGetters, mapState} from 'vuex'
export default {
name: 'globalView',
computed: {
...mapGetters('tickets', {
archiTickets: 'ticketsFromArchi',
}),
},
data() {
return {
temps: [
{
name: 'Assigned / Late (Archi)',
info: '<b>Assigned:</b> Number of <b>open</b> redmine assigned to Groupe Archi SOA</br><b>Late:</b> Number of redmine assigned to Groupe Archi SOA not delivered at due date',
type: 'Number',
refresh: 5,
size: 6,
getData() {
return new Promise((resolve, reject) => {
let res = []
res.push({label: 'Assigned', number: archiTickets.length})
})
}
}
],
}
</script>
When I debug, it says that archoTickets is undefined even with this.archiTickets.
Is there a way to get rid of this problem or should I change the struct of the application?
Thank you