How to make chartist update on Vuejs - vue.js

First of all a happy new year to everyone.
I would like to call update at chartist-js.
main.js
import Chartist from "chartist";
Vue.prototype.$Chartist = Chartist;
Component.vue
<chart-card
:chart-data="performanceUser.data"
:chart-options="performanceUser.options"
chart-type="Line"
data-background-color="green">
</chart-card>
Component.vue -> methods
getStatsUser(){
UsersAPI.getUserPerformance(this.users.filters.user.active).then(r => {
this.performanceUser.data.labels = r.data.labels;
this.performanceUser.data.series = r.data.series;
this.$Chartist.update();
});
}

There are a couple of things you need to do. First, you don't need to patch Vue prototype object with Chartist instance. Just import Chartist package wherever you need it. Prototype patching is required when you need singleton or stateful construct.
Second, I assume all your chart rendering logic will be inside your chart-card component. It will roughly look like:
<template>
<!-- Use vue.js ref to get DOM Node reference -->
<div class="chart-container" ref="chartNode"></div>
</template>
<script>
import Chartist from 'chartist';
export default {
// data is an object containing Chart X and Y axes data
// Options is your Chartist chart customization options
props: ['data', 'options'],
// Use of mounted is important.
// Otherwise $refs will not work
mounted() {
if (this.data && this.options) {
// Reference to DOM Node where you will render chart using Chartist
const divNode = this.$refs.chartNode;
// Example of drawing Line chart
this.chartInstance = new Chartist.Line(divNode, this.data, this.options);
}
},
// IMPORTANT: Vue.js is Reactive framework.
// Hence watch for prop changes here
watch: {
data(newData, oldDate) {
this.chartInstance.update(newData, this.options);
},
options(newOpts) {
this.chartInstance.update(this.data, newOpts);
}
}
}
</script>
Finally, in your calling component, you will have:
getStatsUser() {
UsersAPI.getUserPerformance(this.users.filters.user.active).then(r => {
// Since component is watching props,
// changes to `this.performanceUser.data`
// should automatically update component
this.performanceUser.data = {
labels: r.data.labels,
series: r.data.series
};
});
}
I hope this gives you an idea of how to build Vue wrapper for Chartist graphs.

Related

Echarts OnClick Methods not reaching external methods in Vue

So I have implemented Echarts with a Vue application, on one of the charts, I am trying to get the item clicked and pass it back to the parent component that way I can do specific calculations to it.
The 'on click' method works and I can console.log('params') easily, however, trying to reach any other functions outside of it is not possible for some reason...
here is my code...
data() {
return {
myChart: null,
selectedState: {}
}
}.
mounted() {
this.myChart = echarts.init(document.getElementById("geoMap"))
this.myChart.on('click', function(params){
// It will run the console.log with correct info, but the
// method is not reachable...
console.log(params)
this.setSelectedState(params)
})
},
// Inside my vue script this is just a method to set the data for now...
methods: {
setSelectedState(params){
this.selectedState = params
},
}
any help would be nice!! thanks!
You're not in the Vue component context when listening to the chart event, so you have to change your callback function to an arrow one to access the component's this :
this.myChart.on('click', params => {
this.setSelectedState(params)
});
methods: {
setSelectedState(params) {
console.log(params);
this.selectedState = params
}
}
By the way, you should use ref instead of getting your div with document.getElementById to attach your chart :
<div ref="geoMap"></div>
this.myChart = echarts.init(this.$refs.geoMap);

VueJS: Use Vuex watcher to redraw/reflow a component

I have trouble accessing the Vue component. It is a chart which uses computed properties, which are reactive.
In the documentation I found an example how to "reflow" a the chart, which works fine. However I want to trigger this behaviour whenever the state changes.
mounted() {
this.isMounted = true;
this.$store.watch(state => {
if (state.lineData.data.length > 0) {
this.reflow(); // ### Want to access the reflow method here
// this.$computed.chartOptions.chart desired something like this
}
});
},
computed: {
chartOptions() {
var ref = this;
return {
chart: {
map: worldMap,
height: "620px",
events: {
click: function() {
console.log(this);
this.reflow(); ### This works
}
}
},
}
}
Is there a way to access the computed property from the mounted method or am I doing here something fundamentally wrong? Is there a better way to do this? The click event from the highcharts API is reserved and does not allow me to use custom methods :-/
I found a (for me) good solution:
import Highcharts from "highcharts"; // access to all highcharts components
Highcharts.charts[0].reflow(); // select the correct one an reflow it

Simple Vue store pattern - initial server fetch not reacting

This app isn't complicated. I'm trying to create a simple store (not keen to use Vuex for something this light) which should coordinate server requests and make sure there's a single source of truth across the app.
store.js
import Vue from "vue"
import axios from "axios"
class Store {
items = []
constructor() {
this.fetchData()
}
fetchData() {
axios
.get("/api/items")
.then(response => this.fillFieldsFromServer(response.data))
}
fillFieldsFromServer(data) {
// NONE OF THESE WORK
// 1: this.items = data
// 2: this.items = this.items.concat(data)
// 3: Array.prototype.push.apply(this.items, data)
}
}
export const itemStore = Vue.observable(new Store())
component.vue
<template>
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
</template>
<script>
import { itemStore } from "../../stores/item-store.js"
export default {
computed: {
items() {
return itemStore.items
},
},
}
</script>
Obviously I'm fundamentally misunderstanding something here.
What I thought would happen:
The store singleton is created
A server request is fired off
Vue makes the store singleton reactive
The component renders with an empty list
The component watches store.items
The server request returns
The store updates items
The component sees that changes
The component re-renders with the server data
But what's actually happening is that step (8) doesn't occur. The server request returns fine, but the component doesn't see the change so it doesn't re-render.
Obviously I'm doing something wrong. But what?
Vue.observable makes an object reactive by recursively replacing existing properties with get/set accessors, this allows to detect when they are changed. As for arrays, Array.prototype methods that mutate existing array are also replaced to track their calls.
This isn't supposed to work because Array.prototype.push.apply !== store.items.push:
Array.prototype.push.apply(this.items, data)
It should be either:
fillFieldsFromServer(data) {
this.items = data;
}
Or:
fillFieldsFromServer(data) {
this.items.push(...data);
}
Here is a demo.

Vue Test Utils - Skip created hook

I want to skip all of the methods that are being called within the created() hook. Is there a way to do this?
So instead of this
created() {
this.getAllocations();
this.getModels();
this.getTeams();
this.getCustodians();
this.getDefaultFeeStructure();
}
I want this
created() { }
It's worth noting, I cannot actually change the component itself, but for testing purposes, this needs to be done.
You can accomplish this with a global mixin (see https://v2.vuejs.org/v2/guide/mixins.html#Global-Mixin)
However, for your case you need a custom merge strategy to prevent the created hook on the component from being run:
Hook functions with the same name are merged into an array so that all of them will be called. Mixin hooks will be called before the component’s own hooks. (https://v2.vuejs.org/v2/guide/mixins.html#Option-Merging)
See a working example at https://jsfiddle.net/rushimusmaximus/9akf641z/3/
Vue.mixin({
created() {
console.log("created() in global mixin")
}
});
const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = (parent, child) => {
return mergeCreatedStrategy(parent);
};
new Vue ({
el: "#vue-app",
template: '<p>See console output for logging. Rendered at {{renderDate}}</p>',
data() {
return {
renderDate: new Date()
}
},
created() {
console.log("created() in component")
}
})

How to access Vue Instance from mixins?

I would like to implement this kind of logic that assign this.$store.value to local data.
This is how I do in pages/index.vue for instance.
method: {
this.value = this.$store.value
}
I want to write it down into mixins because I actually have another logics around it and I use some pages.
However, I don't know how should I access this(VueInstnce) from mixins?
It is not supported by Vue because mixin runs first before component's code,
then mixin is bound (merged) by Vue to the component instance so it's easy to access mixin from component/instance scope, but not vice versa.
To achieve your need I think the mixin method (like created) should be run (for example) with a given reference to the instance of your component as a parameter, but it's not like that.
However, if you reorganize your code to run what you need from instance.created
accessing there methods and data of mixin is possible and passing arguments on your own:
var mixin = {
data: {mixin: 'mixin'},
created: function () {
console.log('mixin hook called')
},
methods: { test: function(arg){console.log(arg); } }
};
vm=new Vue({
data: {component: 'component'},
mixins: [mixin],
created: function () {
console.log('called hook of ' + this.component + ' and accessing ' + this.mixin)
},
});
vm.test(vm.mixin);
vm.test(vm.component); // no problem to run mixin's method with component's data
> mixin hook called
> called hook of component and accessing mixin
> mixin
> component
Okay so I don't know if it's considered a bad practice but I have managed to accomplish one-way data transmission without an event bus.
I am using vuejs 3 with composition api. Requirement: all components shall be able to access a global singleton component shown on top of whole app.
plugin.js - here we use the created event in mixin to get a reference of an component instance. In example below I always have just 1 tracker component instance (global popup). If you have a different more complex scenario I would recommend sticking with event bus solution instead..
import Tracker from "#/plugins/ProgressTracker/components/Tracker.vue";
export default {
install(app, params = {}) {
app.component("tracker", Tracker);
let instance = undefined;
app.mixin({
created() {
if (this.$options.name === "ProgressTrackerPopup") {
instance = this;
}
},
});
const progressTracker = () => {
//
};
progressTracker.show = function () {
instance.show();
};
app.config.globalProperties.$progressTracker = progressTracker;
},
};
useProgressTracker.js - globally reusable composable function that exposes show method
import { ref, computed, getCurrentInstance } from "vue";
export default function useProgressTracker() {
const internalInstance = getCurrentInstance();
const progressTracker = internalInstance.appContext.config.globalProperties.$progressTracker;
const show = () => {
progressTracker.show();
};
return {
show,
};
}
Tracker.vue - component that we need to globally access from any other component (methods of it).. name is important. It shall be set in order for the mixin to be able to detect your component creation
<template>
<div class="onTop" v-if="isShow">test</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "ProgressTrackerPopup",
setup() {
var isShow = ref(false);
const show = () => {
isShow.value = true;
};
return {
isShow,
show,
};
},
};
</script>
<style scoped>
.onTop{
position: absolute;
z-index: 1999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #0000004f;
}
</style>
this is it. Don't forget to register the plugin:
import ProgressTracker from "#/plugins/plugin.js";
// ..
app.use(ProgressTracker, {});
Now when you want the pop-up to be shown you invoke:
// <tracker />
import useProgressTracker from "#/plugins/ProgressTracker/use/useProgressTracker.js";
const tracker = useProgressTracker();
tracker.show();
The last line of code will basically invoke the show method on global component instance itself! Whereas if you used an event bus instead - you would have subscribed to the pop event on target component itself.
I find this solution to be useful when you don't want to deal with an event bus and the case is relatively trivial (you only have 1 global instance at all times). Though, you could of course use an array of instances and loop-invoke the methods on them in sequence.. :)