VueJS: Use Vuex watcher to redraw/reflow a component - vue.js

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

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);

How to make chartist update on Vuejs

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.

vue.js two way data-binding between components

Please take a look at this not-working pseudo code:
Vue.component('child', {
props: [],
template: '<div><input v-model="text"></div>',
data: function() {
return {child-text: ""}
}
})
Vue.component('parent', {
template: '<h1> {{text}} </h1>'
data: function() {
return {parent-text: ""}
}
})
What is the most elegant way to fix this code that whenever the user changes the content of input box in child component, then the variable child-text in child component and the variable parent-text in parent component will change automatically? I also want that if the variable child-text and/or parent-text change then the content of input box will change respectively?
I solved this with my own little data store, its a very simple approach but works good enough for me without the necessity to dive into Vuex.
First, I create my data store somewhere before initializing anything else.
window.globalData = new Vue({
data: {
$store: {}
},
});
After that, I add a global Mixin that allows to get and set data to the global storage.
Vue.mixin({
computed: {
$store: {
get: function () { return window.globalData.$data.$store },
set: function (newData) { window.globalData.$data.$store = newData; }
}
}
});
Then, every component can access the data storage by this.$store. You can check a working example here:
https://codesandbox.io/s/62wvro7083

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.. :)

Component without template

I have a bit of code that makes an api call to a server and returns some JSON.
It did exist as a method in my component but as it is getting a bit long I want to extract it to it's own file
In vuejs what is the best practice here.
should it be a component without a template? How would this work?
will I just create an es6 module?
I would suggest using a mixin here.
In a file like myCoolMixin.js define your mixin...
export default {
methods: {
myAwesomeMethod() {
//do something cool...
}
}
}
You can define anything in a mixin just like a component. e.g. data object, computed or watched properties, etc. Then you simply include the mixin in your component.
import myCoolMixin from '../path/to/myCoolMixin.js'
export default {
mixins: [myCoolMixin],
data: function() {
return: {
//...
}
},
mounted: function() {
this.myAwesomeMethod(); // Use your method like this!
}
}
More on Mixins here: https://v2.vuejs.org/v2/guide/mixins.html
Mixins work, or you could create a plugin. Here's the docs example:
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
Vue Plugins