vue-chartjs v4 reference to the chart object - vue.js

chartjs v3.7.1
vue-chartjs v4.0.4
in version 3 of vue-chartjs, I could reference the chart object with this.$data._chart because the component is extends from the chart component.
but in version 4, it just uses the chart component directly, how do I get a reference of the chart object? I tried the :ref directive, but it only return the dom element, not the chart object.
and I couldn't find any example with v4.0.4

After toying around with the same/similar problem myself, I found a solution for me:
No, idea if it's an ok solution, or if I am breaking with some core principles whatsoever, since tony19 wrote, that the chart instance is not exposed as public property. So, please, keep that in mind.
I am using (among others):
vue: ^3.2.33
vue-chart-3: ^3.1.8
chart.js: ^3.7.1
chartjs-plugin-zoom: ^1.2.1
and I want to call resetZoom() on my chart. For that, I need that chart object to call that function on. I assume, that's the same chart object you are looking for? If not, please, comment, and I can delete the answer.
I my vue file I got something like:
<template>
<BarChart ref="mychart" />
</template>
And then I call in the function of my reset-zoom button:
resetZoom() {
const r = this.$refs.mychart as any;
r.chartInstance.resetZoom();
}
So: using a ref to get the object, and then chartInstance

Related

Property 'complete' does not exist on type 'EventTarget'

In Ionic Vue, I am trying to use ion-refresher. According to the documentation, I should end with 'event.target.complete()' but this gives me the following error: Property 'complete' does not exist on type 'EventTarget'.
What should I do, that is what the official documentation tells me. Thank you.
https://ionicframework.com/docs/api/refresher
The complete() method is a method specific to the ion-refresher component in Ionic, so it will not be available on the EventTarget interface. To fix the error, you should make sure you are referencing the ion-refresher element correctly within your Vue component's template.
In your template, ensure you are using the correct event binding for the ionRefresh event, it should be like this:
<ion-refresher slot="fixed" #ionRefresh="onRefresh($event)">
</ion-refresher>
And in your script you should be able to do this:
methods: {
onRefresh(event) {
// perform your refresh logic here
event.target.complete();
}
}
event.target is the actual ion-refresher element in the DOM, and the complete() method is a method on that element that tells the ion-refresher to stop displaying the refreshing spinner.
Make sure that you have imported the ion-refresher component in your Vue file and also in your main.ts file for your Ionic Vue app.

Vue Chart JS and options reactivity

I'm trying to recreate the doughnut to pie change in behavior as seen here:
https://www.chartjs.org/samples/latest/scriptable/pie.html
I'm using VueJS version of Chart JS and after recreating this it seems to not be reactive at all.
Here is the method that I use to change the chart to the other one:
togglePieDoughnut() {
this.options.cutoutPercentage = 50;
}
As you can see it does not work as intended, even tough I used reactiveprop mixin.
EDIT: To be precise I want to recreate the chart update behaviour as seen in the example on chartjs.org website. I do not want to rerender the chart, rather update it so the transition remains smooth.
Seems like the issue is the template isn't reacting to the data changes. Best way to force template re-render is to bind a key, for our example, we are changing this value, the template will update when its changed:
:key="options.cutoutPercentage"
Codepen example:
https://codesandbox.io/s/vue-chartjs-demo-t8vxu?file=/src/App.vue
What if we add a watch to the options variable and rerender the chart when it happens?
watch: {
options: function() {
this._chart.destroy();
this.renderChart(this.donut, this.options);
}
}
When you look at the code of Piechart.vue, it seems that it only render one time on mounted. Thats why when changing the options, it not gonna reflected in the chart because there is no function to rerender.
The only way is you have to remove the old pie chart and create a new one when options changed. There's a lot of way to do the force re-render, but still the cleanest way is as procoib said, attach a key to it.
When using object as props and update one property in it, the reactivity system will not trigger the change, because the object is the same and only one property updated. Thus, the child component will not get the updated value.
What can you do is to recreated the object with the updated property. See below code:
this.options = Object.assign({}, this.options, { cutoutPercentage: 50 });
And in the child component, use watcher re-render the chart
watch: {
options(newVal) {
}
}

Dynamically instantiating a component in Vue.js

Following this tutorial, I'm trying to programmatically create instances of a component on my page.
The main snippet is this:
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
However I get two errors:
The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".
For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"
I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.
1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..
import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";
let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });
2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.
See this CodeSandbox for an example of how you may go about this.
This is another way to instantiate a component in Vue.js, you can use two different root elements.
// Instantiate you main app
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//
// Then instantiate your component dynamically
//
// Create a component or import it.
const Hello = {
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
};
// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);
// Use componentClass to instantiate your component.
const vm = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
// then mount it to an element.
.$mount('#mount');
It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)
new (Vue.extend(YourNewComponent))({
parent: this,
propsData: {
whatever: 'some value',
},
}).$mount(el.querySelector('.some-id'))
If you don't need the reference to the parent, you can just leave "parent: this," out.
Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.
I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).
So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.
The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.

Put a slot in every component in Vue.js

I have a component called vue-select that is a third-party packaged that I installed. I want to put a slot template in every instance of this component.
I mean I want to do something like this:
<v-select>
<span slot="no-options">
<li>sample text</li>
</span>
</v-select>
and I don't want to do this in every v-select that I have in my project.
How can I do this to dry my code ?
thank You :)
The slot is useful when you want to make parts of component's template different. If you always want it to be the same piece of template, then don't make it a slot. Simply add the markup you want into the template of the component.
This is similar to not putting something as an argument of a function if you don't want to be possible to change it.
function spin (element) {
const angle = 360
}
If you want an option to have some common content but still change it sometimes, put the default content in the <slot> tags in the template of the component.
This is similar to adding a default argument in a function:
function spin (element, angle = 360) { }
If you already have a third-party component which has defined slots and their default content, and thus you cannot change them, wrap them in a different component firstly and then use the wrapper component in the rest of the code.
This is similar to adding a new function which calls the previous one, but hard-codes some arguments.
function halfSpin (element) {
spin(element, 180)
}

Kendo & Aurelia: jQuery(...).kendoPager is not a function

I'm trying to get Kendo working in Aurelia and it is not going too easy...
The following call inside the VM attached() hook throws a "jQuery(...).kendoPager is not a function" exception in shim.min.js:1444:
jQuery("#pager").kendoPager({
dataSource: dataSource
});
I've experimented with a number of ways to define the GlobalBehavior.jQueryPlugins() setting with the following being my best attempt thus far in main.js:
import {GlobalBehavior} from 'aurelia-templating-resources';
GlobalBehavior.jQueryPlugins["kendopager"] = "kendoPager";
Unfortunately there is not much documentation about this so one is prodding in the dark a bit so any help will be appreciated.
Normal jQuery functions work fine here so the problem does appear to be related to using Kendo.
Thanks in advance
You installed dependency with JSPM, but you also need to import it in your VM class file. Put this import statement at the top of the file:
import {kendoUi} from 'kendo-ui';
After that you will be able to use in attached hook:
jQuery("#pager").kendoPager({
dataSource: dataSource
});
Just one note, it's better not to refer to DOM elements but hardcoded selectors. You would better create a reference to element in template
<div ref="pager"></div>
and then in view-model have
jQuery(this.pager).kendoPager({
dataSource: dataSource
});