Trigger child component method from parent event - vue.js

I am very new to VueJS and JavaScript and your help would be much appreciated.
My method "greet" is not working and I am unsure why. When I click on the button "change to bar" I get the error:
[Vue warn]: Property or method "greet" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Chatjsvue> at src\components\vueChartjs\Chatjsvue.vue
<App> at src\App.vue
<Root>
chartjs.vue
<template src="../../views/chartjshtml/chartsjs.html"></template>
<script>
import LineChart from '#/assets/javascripts/chartjsline'
export default {
components: {
'line-chart': LineChart
}
}
</script>
chartsjs.html
<div class="wrapper">
<div>
<ul>
<li><button v-on:click="greet()">change to bar</button></li>
<li><line-chart></line-chart></li>
</ul>
</div>
</div>
chartsjsline.js
import { Line } from 'vue-chartjs'
export default {
extends: Line,
data() {
return {
datacollection: {
//Data to be represented on x-axis
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datasets: [{
label: 'Activities ChartJS Line',
backgroundColor: '#f87979',
pointBackgroundColor: 'white',
borderWidth: 1,
pointBorderColor: '#249EBF',
//Data to be represented on y-axis
data: [40, 20, 30, 50, 90, 10, 20, 40, 50, 70, 90, 100]
}]
},
//Chart.js options that controls the appearance of the chart
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: true
}
}],
xAxes: [{
gridLines: {
display: false
}
}]
},
legend: {
display: true
},
responsive: true,
maintainAspectRatio: false
},
}
},
methods: {
greet() {
alert('hello');
}
},
mounted() {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.datacollection, this.options)
}
}

Your <line-chart> component (instance of LineChart) is a child component of your Chatjsvue component.
The child methods remain bound to those instances.
However it is extremely easy from the parent component to access its child component, and from there to execute their methods:
Keep a reference to your child component, using the ref special attribute: <line-chart ref="myLineChart"></line-chart>
Within a parent method, access the referred child using this.$refs.myLineChart.
Then you have access to everything on this child instance, including its methods: this.$refs.myLineChart.greet()
Working example:
Vue.component('chart-js', {
template: '#chartjs',
methods: {
lineChildGreet() {
// Access the child component through $refs.
// Then you can execute its methods.
this.$refs.myLineChart.greet();
},
},
});
Vue.component('line-chart', {
template: '#lineChart',
methods: {
greet() {
alert('hello');
},
},
});
var app = new Vue({
el: '#app',
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<chart-js></chart-js>
</div>
<template id="chartjs">
<div class="wrapper">
<div>
<ul>
<li><button v-on:click="lineChildGreet">greet on child component</button></li>
<!-- Keep a REFerence to the child component -->
<li><line-chart ref="myLineChart"></line-chart></li>
</ul>
</div>
</div>
</template>
<template id="lineChart">
<span>LineChart</span>
</template>

You need to add the greet() method to the chartjs.vue component, instead of chartsjsline.

Related

Pass array data to component and make

I try to get familiar with Vue2.
As you can see below I try to pass in the "markers"-Array but the map does not change. How can I change the markers array? How can I make the markers array reactive so that map markers change?
Thanks for helping me!
views/Map.vue
<template>
<div id="MapWrapper">
<Map :markers = "markers"/>
</div>
</template>
<script>
import Map from 'src/components/MapSoal.vue'
export default {
name: 'MapView',
components: {
Map
},
data () {
return {
markers: [
{name: "greenMarker", lngLat: [13.8022177, 51.0069449], color: "green", text: "<h1>This is the green marker</h1>"},
{name: "orangeMarker", lngLat: [13.8022177 - 0.05, 51.0069449], color: "orange", text: "<h1>this is the orange marker</h1>"},
]
}
}
}
</script>
components/MapSoal.vue
<template>
<MglMap
:accessToken="accessToken"
:mapStyle="mapStyle"
:center="center"
:zoom="zoom"
>
<MglMarker v-for="marker in markers" :key="marker.name" :coordinates="marker.lngLat" :color="marker.color">
<MglPopup>
<VCard v-html="marker.text"></VCard>
</MglPopup>
</MglMarker>
<MglGeolocateControl></MglGeolocateControl>
<MglNavigationControl></MglnavigationControl>
</MglMap>
</template>
export default {
data() {
return {
accessToken: 'SOME_API_KEY',
mapStyle: 'mapbox://styles/mapbox/streets-v11?optimize=true',
center: [13.8022177, 51.0069449],
zoom: 9, // starting zoom
markers: []
};
},
}
You're not declaring markers as a prop in your Map component, you're declaring markers in your data object, so the Map component's got a separate data variable named markers, completely unrelated to your parent component's data. Try removing your markers array from data in your Map component and add it in a props object in the component like this:
props: {
markers: {
type: Array
}
},
or
props: ['markers'],
so that your Map component looks like this:
<template>
<MglMap
:accessToken="accessToken"
:mapStyle="mapStyle"
:center="center"
:zoom="zoom"
>
<MglMarker v-for="marker in markers" :key="marker.name" :coordinates="marker.lngLat" :color="marker.color">
<MglPopup>
<VCard v-html="marker.text"></VCard>
</MglPopup>
</MglMarker>
<MglGeolocateControl></MglGeolocateControl>
<MglNavigationControl></MglnavigationControl>
</MglMap>
</template>
export default {
props: ['markers'],
data() {
return {
accessToken: 'SOME_API_KEY',
mapStyle: 'mapbox://styles/mapbox/streets-v11?optimize=true',
center: [13.8022177, 51.0069449],
zoom: 9, // starting zoom
};
},
}

Cannot render multiple times the same amchart3 graph in a vue component

I'm using amcharts3 to generate graphs, Bootstrap 4 for the style and Vue2 for the components. I have a parent component which shows a donut chart which is a component on its own, and I want to render those charts many times as I need
Parent component
<template>
<div class="w-100 container-fluid">
<div class="row">
<!-- Here it goes the Donut Charts -->
</div>
</div>
</template>
<script>
import donutChart from './donutChart';
export default {
components:{
'donut-chart': donutChart
},
data(){
return{
graficos: [
{id: 'grafico', valor:100, valor2: 0},
{id: 'dona', valor:75, valor2: 25},
{id: 'homero', valor:50, valor2:50},
]
}
}
}
</script>
<style></style>
donutChart.vue (the amcharts3 library is already loaded in the Vue project)
<template>
<div class="col">
<div :id="this.dato.id"></div>
</div>
</template>
<script>
export default {
props:{
dato: {
type: Object,
default: {id: 'chartdiv', valor:50, valor2:50}
}
},
data() {
return {
}
},
created: function(){
console.log(this.dato)
AmCharts.makeChart( this.dato.id, {
"type": "pie",
"theme": "none",
"responsive": {
"enabled": true
},
"minWidth": 200,
"maxWidth": 400,
"dataProvider": [ {
"title": "et0",
"value": this.dato.valor
},
{
"title": "Restante",
"value": this.dato.valor2
} ],
"startDuration": 0,
"titleField": "title",
"valueField": "value",
"labelRadius": 5,
"radius": "40%",
"innerRadius": "80%",
"allLabels": [{
"y": "46%",
"align": "center",
"size": 14,
"text": "50%",
"color": "#555"
}]
});
},
mounted: function(){
},
methods:{
}
}
</script>
<style></style>
The problem is the donutChart.vue component is rendered in the parent, and it shows the chart
<donut-chart></donut-chart>
But when I try to render multiple times the same component and/or passing data via props the chart doesn't render at all
<donut-chart :dato="{id: 'grafico', valor: 100, valor2: 0}"></donut-chart>
<donut-chart :dato="{id: 'dona', valor: 75, valor2: 25}"></donut-chart>
<donut-chart :dato="{id: 'homero', valor: 50, valor2 :50}"></donut-chart>
I'm doing something wrong?
Well. I've managed to see what's going on and it looks so stupid. In donutChart.vue there´s an style that only applies to #chartdiv element, I didn't post in the question because I didn't figure that code until now. Now I'm applying the same style for every component and now the charts are renderized

How do I pass custom options into chartjs

I'm trying to be able to add notes to my charts but i'm stuck on how I would pass my individual photon object into my chartOptions variable to be used in the tooltips label function?
<template>
<swiper-slide v-for="(photon,key) in $store.state.photons" :key='key'>
<line-chart width="80vw" :dataset="dataset" :library="chartOptions" class="animated slideInLeft delay-0.01s"
v-else :data="photon.data.rbChannel" ytitle="R/B Channel" :download="true"></line-chart>
<line-chart width="80vw" :dataset="dataset" :library="chartOptions" class="animated slideInRight delay-0.01s"
v-else :data="photon.data.tempF" ytitle="Temperature F" :colors="['#ff0000']" :download="true"></line-chart>
</swiper-slide>
</template>
chartOptions variable
chartOptions: {
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
console.log(data.labels[tooltipItem.index])
console.log(tooltipItem)
return data.labels[tooltipItem.index]
}
}
},
height: '400px',
pan: {
enabled: false,
mode: 'xy',
},
zoom: {
enabled: true,
mode: 'x',
},
drag: true,
gridLines: {
zeroLineColor: "rgba(0,255,0,1)"
}
}

Vue 2.5: Passing prop to component. Prop gets bundled in variable name

I'm new to Vue.js and my first try is to make a simple line-chart using ChartJS (vue-chartjs bundle).
I've used the "HelloWorld.vue" as base material, and created a LineChart.js
The problem is that in HelloWorld, i got my variable called datacollection, this name gets passed into my LineChart.js. How do I fix so I dont get the variable name as an object
I get:
datacollection:
{
labels: {...},
datasets: {...}
}
I want:
{
labels: {...},
datasets: {...}
}
Thus, in my LineChart.js I need to do .datacollection. This will make my LineChart.js less reusable, since I always have to remember to name all my variables calling LineChart 'datacollection'.
LineChart.js:
import { Line } from 'vue-chartjs'
export default {
extends: Line,
props: ['data', 'options'],
watch: {
'data': function (value) {
// I get the update from backend, but I have to use .datacollection (the variable name from the calling page)
console.log('Ändrat: ', value)
this.renderChart(value.datacollection, this.options)
}
},
data () {
return {
gradient: null,
gradient2: null
}
},
mounted () {
console.log('data in component', this.data)
/*
this.data contains datacollection: {
labels: {
...
},
datasets: {
....
}
}
This wont render any graph since I dont do .datacollection
*/
this.renderChart(this.data, this.options)
}
}
My Graph.vue page:
<template>
<div class='hello'>
<h1>{{ msg }}</h1>
<h2>Graph</h2>
<!-- this.datacollection is printed as expected, {labels: {}, datasets: {}} -->
<p>{{ this.datacollection }}</p>
<section>
<line-chart
:data='{datacollection}'
:options='{chartOptions}'
:width="400"
:height="200"
>
</line-chart>
</section>
<section>
<reactive-example></reactive-example>
</section>
</div>
</template>
<script>
export default {
name: 'Graph',
mounted: function () {
this.axios.get('graph/').then(response => {
console.log(response.data)
this.datacollection = response.data
})
},
data: function () {
return {
msg: 'Welcome to Your Vue.js App',
datacollection: {
labels: ['January', 'February'],
datasets: [
{
label: 'First',
backgroundColor: '#f87979',
data: [40, 20]
},
{
label: 'Second',
backgroundColor: '#aa7979',
data: [20, 30]
}
]
}
}
}
}
</script>
<!-- Add 'scoped' attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
My dependenicies (versions)
"dependencies": {
"axios": "^0.17.1",
"chart.js": "^2.7.1",
"vue": "^2.5.2",
"vue-axios": "^2.0.2",
"vue-chartjs": "^3.0.2",
"vue-router": "^3.0.1"
}
What do I miss?
In your template, you have the following:
<line-chart
:data='{datacollection}'
:options='{chartOptions}'
:width="400"
:height="200"
>
</line-chart>
In ES2015, {datacollection} is shorthand (see New notations in ECMAScript 2015) for creating a new object with a datacollection property with the value of datacollection as its value. In Vue, everything in the quotes of a binding is treated as a javascript expression, so in most modern browsers what that syntax does is create a new object with a datacollection property and pass that object to the component.
Instead, just remove the braces.
<line-chart
:data='datacollection'
:options='chartOptions'
:width="400"
:height="200"
>
</line-chart>

VueJs data binding issue

I try to bind attributes and some data to my template but the code below doesn't work. What I need is to render n-amount of templates depends of amount of printedForms objects and implement in each template data from proper object.
Please give any ideas what wrong with my code.
P.S. I've warning in console as follows:
[Vue warn]: Error when evaluating expression "printedForm.docNumber": TypeError: Cannot read property 'docNumber' of undefined (found in component: )
<div id="app">
<printing-form v-for="printedForm in printedForms" track-by="id"></printing-form>
</div>
<template id="printingForm-template">
<img v-bind="printedForm.attributes">
<div>{{ printedForm.docNumber }}</div>
</template>
My VueJs code below:
Vue.component('printing-form', {
template: '#printingForm-template'
});
new Vue({
el: '#app',
data: {
printedForms: [
{
id: 1,
docNumber: 7,
attributes: {
src: '/waybill/img/4p_bus.png',
width: 1400,
height: 980
}
},
{
id: 2,
docNumber: 7777,
attributes: {
src: '/waybill/img/4p_cargo.png',
width: 1400,
height: 980
}
},
{
id: 3,
docNumber: 10000,
attributes: {
src: '/waybill/img/4p_selfMove.png',
width: 1400,
height: 980
}
}
]
}
});
you need to bind a printedForm property :printed-form="printedForm" like that
<printing-form v-for="printedForm in printedForms"
track-by="id" :printed-form="printedForm"></printing-form>
and define it in component props
Vue.component('printing-form', {
template: '#printingForm-template',
props: ['printedForm']
});
Vue props
Notice when using camelCased prop names as attributes, you need to use their kebab-case (hyphen-delimited) equivalents
Vue Docs