I'm currently working on an very versatile dashboard to display various data.
For the frontend I'm using the latest nuxt and vue version.
My dashboard has many kinds of variations to display data (for example pie charts, line charts,...) these are described in components which are called dynamically.
The Problem is that when I browse from the Page "/" to another (for example "/foo") the interval gets fired again and crashes the app.
That happenes after the lifecycle hook destroyed. I tried to define the interval as an variable and stop it in the beforeDestroy hook but it did not help.
let interval= setInterval(this.fetchData.bind(null, configuration, connector), configuration.refreshTime)
/* later */
clearInterval(interval);
Do you see an error?
Thank you.
Thats the relevant code:
Template
<no-ssr>
<v-container grid-list-md>
<v-layout row wrap v-masonry transition-duration="0.5s" item-selector=".flex" column-width="#grid-sizer">
<v-flex xs1 sm1 md1 lg1 x1 id="grid-sizer"></v-flex>
<component :key="dashboardItem.id" v-for="(dashboardItem,index) in dashboardItems" :is="dashboardItem.type" :connector="dashboardItem.connector"
:type="dashboardItem.type" :configuration="dashboardItem.configuration" :id="dashboardItem.id" :index="index"></component>
</v-layout>
</v-container>
</no-ssr>
Script
import OnlyValue from '#/components/dashboardItems/OnlyValue.vue'
import TrafficLight from '#/components/dashboardItems/TrafficLight.vue'
import ChartAllHover from '#/components/dashboardItems/ChartAllHover.vue'
import PieChart from '#/components/dashboardItems/PieChart.vue'
import Section from '#/components/dashboardItems/Section.vue'
import Gauge from '#/components/dashboardItems/Gauge.vue'...
export default {
name: 'HomePage',
head () {
return {
title: "Dashboard"
}
},
computed: {
...mapGetters({
isAuthenticated: "users/isAuthentificated",
profileName: "profiles/name",
dashboardItems: "profiles/dashboardItems"
})
},
mounted() {
if (typeof this.$redrawVueMasonry === 'function') {
this.$redrawVueMasonry()
}
},
components: {
OnlyValue,
TrafficLight,
ChartAllHover,
PieChart,
Section,
Gauge
}
}
When calling a components it looks the following:
import dashboardItem from '~/mixins/dashboardItem'
export default {
name: "gauge",
mixins: [dashboardItem],
props: {
connector: {
type: String,
required: true
},
type: {
type: String,
required: true
},
configuration: {
type: Object,
required: true
},
id: {
type: Number,
required: true
},
index: {
type: Number,
required: true
}
},
data: () => ({
initOptions: {
renderer: 'svg'
},
options: {
tooltip: {
formatter: "{c}%"
},
series: [{
name: null,
type: 'gauge',
detail: {
formatter: '{value}%'
},
data: null
}]
},
isLoading: true
}),
methods: {
getData(configuration, connector) {
this.fetchData(configuration, connector)
setInterval(this.fetchData.bind(null, configuration, connector), configuration.refreshTime)
},
fetchData(configuration, connector) {
this.getSingleValue(configuration, connector)
.then(data => {
this.isLoading = false
let percent = (data.value / configuration.max) * 100
percent = Math.round(percent * 10) / 10
this.$nextTick(function () {
this.$refs.gauge.mergeOptions({
series: [{
name: data.title,
data: [{
value: percent,
name: data.title
}]
}]
})
})
this.$redrawVueMasonry()
})
.catch(e => console.log(e))
}
},
mounted () {
this.getData(this.configuration, this.connector)
}
}
Related
I am trying to develop guided tour with shepherd: https://www.npmjs.com/package/vue-shepherd but I cannot get the element. So here is my component for guide tour:
<template>
<div></div>
</template>
<script>
import { useShepherd } from 'vue-shepherd';
export default {
props: {
element: {
required: true,
},
id: {
type: Number,
required: true,
},
title: {
type: String,
},
text: {
type: String,
required: true,
},
position: {
type: String,
required: true,
},
},
mounted() {
this.tour.start();
},
data() {
return {
tour: null,
};
},
methods: {
createTour() {
this.tour = useShepherd({
useModalOverlay: true,
});
this.tour.addStep({
title: this.title,
text: this.text,
attachTo: { element: this.element, on: this.position },
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
id: this.id,
});
this.tour.start();
},
},
created() {
this.createTour();
},
};
</script>
and here is my parent component:
<button ref="button">
Click
</button>
<guide :element="element" :title="'Tour'" :text="'Example'" :position="'bottom'" :id="1" />
and the mounted of the parent element:
mounted() {
this.element = this.$refs.button;
},
but the tour doesnt attach the the button element. it just appears in the middle of the page. Why do you think it is?
Looks like a usage problem in vue hooks. The child component's hooks fire before the parent component's hooks. Therefore, at the time of the creation of the tour, the element does not exist. Vue-shepherd does not use vue reactivity.
Use
mounted() {
this.$nextTick(() => {
this.createTour();
});
},
Codesanbox
But it's better to change the component structure. If you are using vue3 you can use my package
In this case it will look like this
<template>
<button v-tour-step:1="step1">
Click
</button>
</template>
<script>
import { defineComponent, inject, onMounted } from "vue";
export default defineComponent({
setup() {
const tour = inject("myTour");
onMounted(() => {
tour.start();
});
const step1 = {
/* your step options */
}
return {
step1,
};
}
});
</script>
I have button which toggle data passed to this ChartComponent. If hold any seconds after toggle and retoggle so its work but if we doing it a bit fast, it is cause
Cannot read properties of null (reading 'getContext')? error.
<template>
<canvas></canvas>
</template>
<script>
import { defineComponent } from 'vue'
import Chart from 'chart.js/auto'
export default defineComponent({
name: 'ChartComponent',
props: {
type: {
type: String,
required: true,
},
data: {
type: Object,
required: true,
},
options: {
type: Object,
default: () => ({}),
},
},
data: () => ({
chart: null,
}),
watch: {
data: {
handler() {
this.chart.destroy()
this.renderChart()
},
deep: true,
},
},
mounted() {
this.renderChart()
},
methods: {
renderChart() {
this.chart = new Chart(this.$el, {
type: this.type,
data: this.data,
options: this.options,
})
},
},
})
</script>
You should unwrap/unproxy this.chart before accessing it by using Vue3 "toRaw" function:
import { toRaw } from "vue";
...
watch: {
data: {
handler() {
toRaw(this.chart).destroy()
this.renderChart()
},
Does not work like this:
<template>
<Bar
:chart-options="chartOptions"
:chart-data="$attrs['chart-data'] || chartData"
:chart-id="chartId"
:dataset-id-key="datasetIdKey"
:plugins="plugins"
:css-classes="cssClasses"
:styles="styles"
:width="width"
:height="height"
/>
</template>
<script>
import { Bar } from 'vue-chartjs/legacy'
import 'chart.js/auto'
export default {
name: 'BarChart',
components: { Bar },
props: {
chartId: {
type: String,
default: 'bar-chart',
},
datasetIdKey: {
type: String,
default: 'label',
},
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 400,
},
cssClasses: {
default: '',
type: String,
},
styles: {
type: Object,
default: () => {},
},
plugins: {
type: Object,
default: () => {},
},
},
data() {
return {
chartData: {},
chartOptions: {
responsive: true,
title: {
display: true,
text: 'Custom Chart Title',
},
},
}
},
}
</script>
tried editing even with this: https://codesandbox.io/s/exciting-ully-2wst65?file=/src/components/Bar.vue
but nothing works.
All info gathered from: https://vue-chartjs.org/api/
Registration of components are not needed if you import auto.
maybe some ideas?
You are using v2 syntax in v3, the internal plugins like the tooltip, title, legend and decimation have been moved from the options namespace to the options.plugins namespace.
So you need to nest the title options in a plugins object within the options object. Then it will work
For those like me, who are looking for a solution using vue-chartjs V4.
To show the Title you need to pass the plugin object, for example:
plugins: {
type: Array,
default: () => [Title]
}
and then pass into the chartOptions props the title configuration, for example:
chartOptions: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: "Chart Title",
},
},
}
sandBox full example:
I'm passing an array of objects into a child component and I want to access this array into the created function of the child component.
When logging the entire this.$props object I'm getting all the data however when I'm logging the array I'm getting an empty proxy object.
Parent component
<script>
import SelectWithSearch from '#/components/formElements/SelectWithSearch.vue';
import AddressService from '#/services/AddressService';
export default {
data() {
return {
city: 312,
cityOptions: [],
};
},
components: { SelectWithSearch },
created() {
this.getCities();
},
methods: {
async getCities() {
const response = await AddressService.getCities();
this.cityOptions = response.data.cities.map((city) => {
return {
id: city.id,
display_name: `${city.sub_city} (${city.zip})`,
};
});
},
},
};
</script>
Child component (SelectWithSearch)
<script>
export default {
props: {
label: { type: String, required: false },
options: { type: Array, required: true },
},
created() {
console.log(this.$props);
console.log(this.$props.options);
console.log(this.options);
console.log(this.label);
},
Output
How can I access the options data in the created function?
I am trying to get data from server using vue and apexcharts, but even after I called data with axios, it gives me undefined..
What have I missed?
template
<apexchart
ref="chart1"
width="100%"
:options="chartOptions" :series="series">
</apexchart>
data from url
{
"pageviews": 1313,
"new_users": 1014
}
script
export default {
data: function () {
return {
series: [],
chartOptions: {
chart: {
type: 'donut',
},
colors: ['#01cd49', '#007568'],
labels: ['new', 're'],
}
},
created: function () {
this.getByVisitor()
},
methods: {
getByVisitor() {
const url = 'url';
axios
.get(url)
.then(response => {
this.$refs.chart1.updateSeries([{
name: 'Sales',
data: response.data
}])
})
.catch(error => (this.byVisitor = error.data));
console.log(`---------------this.$refs.chart1`, this.$refs.chart1);
},
}
See Updating Vue Chart Data
There's no need to directly call the updateSeries() method on the chart component since it is able to react to changes in series. All you have to do is update your series data property
export default {
data: () => ({
series: [], // 👈 start with an empty array here
byVisitor: null, // 👈 you seem to have missed this one for your error data
chartOptions: {
chart: {
type: 'donut',
},
colors: ['#01cd49', '#007568'],
labels: ['new', 're'],
}
}),
created: function() {
this.getByVisitor()
},
methods: {
async getByVisitor() {
const url = 'url';
try {
const { data } = await axios.get(url)
// now update "series"
this.series = [{
name: "Sales",
data
}]
} catch (error) {
this.byVisitor = error.data
}
},
}
}