I'm using Highcharts stock in Vue, what I want to implement is when I hover one stock point, the tooltip shows up, and at the same time, at other places may be top of the stock chart, as same as data in tooltip in other styles. My way is to export data in the tooltip formatter function, but in the formatter function this doesn't refer to Vue instance. I wonder if there any way I can access hovering tooltip data, or if there any other way can realize the effect.
the effect i want to realize
<template>
<div>
<div class="outerTooltip">open: xxx</div>
<highcharts :constructor-type="'stockChart'" :options="stockOptions"></highcharts>
</div>
</template>
<script>
import { Chart } from "highcharts-vue";
import Highcharts from "highcharts";
import stockInit from "highcharts/modules/stock";
import { data } from "./stockData";
stockInit(Highcharts);
export default {
name: "StockVue",
computed: {
stockOptions() {
return {
...,
tooltip: {
shared: true,
formatter(){
console.log(this)
// how can i export data in this to Vue, so i can show it in outerTooltip dynamically
retuen `
open: ${this.points[0].point.open}
`
}
}
}
}
}
}
codesandbox example
I think that you only need to assign the reference of the Vue instance to a variable and then use it in formatter function. Remember also to add a reference to the computed property.
Demo: https://codesandbox.io/s/highcharts-vue-test-forked-c7pvw?file=/src/components/StockVue.vue
Assign the reference of the Vue instance to a variable that worked for me.
export default {
name: "StockVue",
method: {
exportTooltipData(data){
console.log(data)
}
},
computed: {
stockOptions() {
const exportFn = this.exportTooltipData;
return {
...,
tooltip: {
shared: true,
formatter(){
exportFn(this);
retuen `
open: ${this.points[0].point.open}
`
}
}
}
}
}
}
Related
I'm struggling with how Vue updates props/child components.
Suppose the following component:
<template>
<v-card>
<Modification v-model="newObject"></Modification>
<OtherComponent #close="resetObject"></OtherComponent>
</v-card>
</template>
<script>
import { MyClass } from "classes";
import Modification from "b";
import OtherComponent from "a";
export default {
name: "MyForm",
components: { OtherComponent, Modification },
props: {
existingObject: {
type: [MyClass, typeof undefined],
required: false,
default: undefined
}
},
data() {
return {
newObject: undefined
};
},
created() {
this.newObject =
this.existingObject !== undefined
? this.existingObject.clone()
: new MyClass();
},
methods: {
resetObject() {
this.newObject =
this.existingObject !== undefined
? this.existingObject.clone()
: new MyClass();
}
}
};
</script>
How MyClass is defined:
export class MyClass {
constructor({ a= null, b=null} = {}) {
this.a = a;
this.b = b;
}
toPayload(){
return { a:this.a , b:this.b };
}
clone() {
return new MyClass(this.toPayload());
}
}
This component receives an existing class instance of MyClass, clones it (clone => new MyClass(...)) and passes it to the Modification component which does some modification upon user input. So far so good, the modification works. However once the customEvent is fired and the resetObject method is called the newObject is reset but the Modification component is not updated with the now reset newObject - it still displays the old, modified values. I also checked inside the Modification component wether or not the update happens: It doesn't.
Why is this the case? Am I missing a step? Am I not aware of a Vue specific mechanism?
Note: I found this blog which provides solutions to force the Modificationcomponent to update. For now it seems to hacky for me to be "THE" solution.
Thanks in advance.
EDIT:
Adding a computed property which includes a console.log(JSON.stringify(this.newObject)) fires everytime newObject is updated.
Also adding a <span> {{ newObject.a }} </span> to the template updates evertime.
Both these tests convince me that the variable not only should be but actually IS reactive.
EDIT 2:
The Modification component consists, for now, of 2 Input components.
It looks like this.
<template>
<v-card-text>
<ModifyA v-model="object.a" #input="handleInput" />
<ModifyB v-model="object.b" #input="handleInput" />
</v-card-text>
</template>
<script>
import { MyClass } from "classes";
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
methods: {
handleInput() {
this.$emit("input", this.object);
}
}
};
</script>
If I try adding the ModifyA Input into the component instead of the Modification component like this
<template>
<v-card>
<ModifyA v-model="newObject.a"></Modification>
<OtherComponent #close="resetObject"></OtherComponent>
</v-card>
</template>
the resetObject also resets the value shown in the ModifyA component.
You didn't show how MyClass clones your object.
I'm guessing something in there isn't reactive.
You can check by doing console.log() and see what it says on the console.
If it's reactive, it should show something like MyClass {__ob__: Observer}
You can probably use this.$set('propName', value) to fix your problem
Docs: https://v2.vuejs.org/v2/api/#vm-set
Adds a property to a reactive object, ensuring the new property is also reactive, so triggers view updates. This must be used to add new properties to reactive objects, as Vue cannot detect normal property additions (e.g. this.myObject.newProperty = 'hi').
Either there is a typo in your post, or the typo also exists in your code and is the source of your problem.
In your post you're binding "newObjekt" to the Modification component, but your parent component has the property "newObject"
is this the source of your issue?
I found the solution in this answer.
As I edited my original post with the definition of the Modification component
<template>
<v-card-text>
<ModifyA v-model="object.a" #input="handleInput" />
<ModifyB v-model="object.b" #input="handleInput" />
</v-card-text>
</template>
<script>
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
methods: {
handleInput() {
this.$emit("input", this.object);
}
}
};
</script>
it shows the "problem" why the Fields ModifyA and ModifyB do not update if the value updates in the parent component.
As seen in the above definition the variable object is only set to the value once the Component is initialized. It follows that object is not reactive on behalf of value.
To solve this one can use the approach of the above mentioned answer:
<template>
<v-card-text>
<ModifyA v-model="object.a" />
<ModifyB v-model="object.b" />
</v-card-text>
</template>
<script>
import { Shift } from "classes";
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
watch: {
value(val) {
this.object = val;
},
object(value) {
this.$emit("input", value);
}
}
};
</script>
Due to the watcher, the object variable is updated whenever the value get's updated by the parent.
Within vue3 is it possible to bind a value from the vuex store to a component style? for example, i have a color saved within the store as backgroundColor and would like to do something like below:
Store:
store = {
state: {
branding: {
color: '#f00'
}
}
}
component script
export default {
name: 'component',
components: {
Header,
Footer
},
branding() {
return this.$store.state.branding.color;
},
}
component style
.page{
background-color: v-bind(branding.colour);
}
i have seen an example of this working:
export default {
name: 'component',
components: {
Header,
Footer
},
data(){
return {
color: '#f00'
}
},
}
.page{
background-color: v-bind(colour);
}
but i need to grab the data from the store. im pretty new to vue but not too sure on how to resolve this.
UPDATE 6 April 2022
Apparently I was wrong and in Vue 3 it is possible to use v-bind inside CSS as shown in the documentation. So it should be possible to use Vuex getter or local computed property.
You can not access JavaScript variables from CSS. You should either set inline style to your HTML tag(s) or define the color as CSS variable and then access it from other CSS rules.
<template>
<div :style="{backgroundColor: brandingColor}">test</div>
</template>
<script>
export default
{
computed:
{
brandingColor()
{
return this.$store.state.branding.color;
}
}
}
</script>
OR
// main.js
new Vue({
created()
{
const style = document.documentElement.style;
const theme = this.$store.state.branding;
for (const key in theme)
{
style.setProperty('--branding-' + key, theme[key]);
}
}
});
MyComponent.vue:
<template>
<div class="page">text</div>
</template>
<style>
.page
{
background-color: var(--branding-color);
}
</style>
I'm trying to reload the data on a bar chart from chart.js, but im not able to.
This is the chart:
<script>
import { Bar } from 'vue-chartjs'
export default {
extends: Bar,
props: ["chartdata"],
data: () => ({
options: {
responsive: true,
maintainAspectRatio: false
}
}),
computed: {
chartData: function() {
return this.data;
}
},
watch: {
data: function() {
this._chart.destroy();
this.renderChart(this.data, this.options);
}
},
mounted () {
this.renderChart(this.chartdata, this.options)
}
}
</script>
This is how I implement it:
<chart
:chartdata="chartdata"
:options="options"/>
After getting the data from the api and form the chartdata I try to trigger the watch by updating the container data that I'm sending like this:
const cd = {
labels: labels,
datasets: [{
label: '# of Votes',
data: data
}]}
this.chartdata = cd
}
But It's not working and I don't know why.
Neither familiar with the library or vue.js in general, but the documentation says the following about updating the chart...
Chart.js does not provide a live update if you change the datasets.
However, vue-chartjs provides two mixins to achieve this.
reactiveProp reactiveData Both mixins do actually achieve the same.
Most of the time you will use reactiveProp. It extends the logic of
your chart component and automatically creates a prop named chartData
and adds a vue watch on this prop. On data change, it will either call
update() if only the data inside the datasets has changed or
renderChart() if new datasets were added.
You're not using the mentioned props in the documentation. You can read more it on the official documentation
If you are using vue-chartjs, the library has its own way to handle reactive data in charts:
// ChartBar.js
import { Bar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Bar,
mixins: [reactiveProp],
props: ['options'], // passed from the parent
mounted () {
// this.chartData is created in the mixin (pass it as any prop with :chart-data="data").
this.renderChart(this.chartData, this.options)
}
}
Now use the component
<chart
:chart-data="chartdata"
:options="options"
/>
You can read more about it here: https://vue-chartjs.org/guide/#updating-charts,
there are some caveats
Code
CodeEditor.vue:
<template>
<div class="ace-container">
<div class="ace-editor" ref="ace"></div>
</div>
</template>
<script>
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'
import 'ace-builds/src-noconflict/theme-monokai'
import 'ace-builds/src-noconflict/mode-javascript'
export default {
mounted() {
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 60,
minLines: 10,
fontSize: 14,
theme: this.themePath,
mode: this.modePath,
tabSize: 4
})
},
data() {
return {
aceEditor: null,
themePath: 'ace/theme/monokai',
modePath: 'ace/mode/javascript'
}
},
methods: {
setCode(code) {
this.aceEditor.setValue(code);
},
getCode() {
return this.aceEditor.getValue();
},
}
}
</script>
<style>
.ace-editor {
width: 600px;
height: 600px;
}
</style>
QuizExecution.vue: (partly)
<template>
<v-app height="100%">
<div id="qz-wrapper">
<!--
<v-textarea id="programmingText" v-model="answerData[question.id]"
#change="saveAnswer(qe.id, question.id)" label="Code" outlined></v-textarea>
-->
<CodeEditor id="programmingText" v-model="answerData[question.id]"
#change="saveAnswer(qe.id, question.id)"></CodeEditor>
</div>
</v-app>
</template>
<script>
import Vue from 'vue'
import CodeEditor from "./CodeEditor";
export default {
components: {CodeEditor},
data() {
return {
// ..
}
}
}
</script>
<style scoped>
</style>
Description
With vuetify's <v-textarea>, I can use v-model to bind its content to a data property, in bi-direction dynamically, so that could init on load, and save on change with a #change property.
Then I want to replace the input area with ace-editor, which support features like syntax highlight.
So, I have defined a component as in CodeEditor.vue, then import & use it in QuizExecution.vue.
But, the v-model and #change won't work on the <CodeEditor> tag.
Questions
How to apply v-model and #click on this <CodeEditor> with in QuizExecution.vue.
Aka. init it with data from container component, and retrieve its content on change and trigger an event to save.
Or, is there anyway to achieve the same result: init on creation & save on change.
You can use props and watch the change events with #update_question_id;
<CodeEditor id="programmingText" :question_id="answerData[question.id]"
#update_question_id="answerData[question.id]=#event"
></CodeEditor>
....
watch:{
answerData(){
saveAnswer(this.qe.id, this.question.id)
}
}
CodeEditor.vue:
You can get the question_id value with props. I think it would be string or number.
And also watch question_id then use $emit to send change $event to main component.
export default {
props:{
question_id: [String,Number]
},
watch:{
question_id(val){
this.$emit("update_question_id",val)
}
}
.....
I currently run into the issue that my imported function is not referenced during render. It's easier explained by this code:
<template>
<div v-for="(addOn, index) in JSON.parse(this.restaurants[0].categories[categoryId].addons)">
<label>
<span>{{ addOn.name }} (+ ${{ addZeroes(addOn.price) }})</span>
</label>
</div>
</template>
<script>
import { addZeroes } from "../../../js/helpers";
export default {
data() {
return {
// populated via AJAX
restaurants: [
{
categories: []
}
],
}
},
}
</script>
<style>
</style>
and the error is:
[Vue warn]: Property or method "addZeroes" 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.
What's the proper way to call a helper function inside a Vue template?
Thanks for any hints!
You could add it to your component:
import { addZeroes } from "../../../js/helpers";
export default {
data() {
return {
// populated via AJAX
restaurants: [
{
categories: []
}
],
}
},
methods: {
addZeroes // shorthand for addZeroes: addZeroes
}
}