Chart can't be populated from data component [vue-highcharts wrapper] - vue.js

I'm starting with the Higcharts wrapper for vue. Currently I'm migrating the code of a stockchart that I was using outside vue without problems into the wrapper. Everything is going well except that I can't populate the chart from component data or computed variables. Only from hard-written array or from component props.
This is the code:
<template>
<highcharts
class="stock"
v-bind:constructor-type="'stockChart'"
v-bind:options="config"
v-bind:deepCopyOnUpdate="false"
></highcharts>
</template>
<script>
export default {
name: "stockChart",
props: {
options: {
type: Object,
required: true
}
},
data: function() {
return {
config: {
series: [{
name: this.options.title,
//data: [[1,3],[2,7],[3,9],[4,2],[5,0],[10,13]] //THIS WORKS!
//data: this.options.plotData //THIS ALSO WORKS!!
data: this.plotData //THIS DOESN'T...
}],
(...)
},
plotData: [[1,3],[2,7],[3,9],[4,2],[5,0],[10,13]]
}
},
computed: {
// THIS ALSO ISN'T WORKING... THAT IS HOW I WANT IT TO WORK
/*plotData: function(){
return this.options.xData.map((e,i) => [e, this.options.yData[i]]);
}*/
}
}
</script>
<style scoped>
.stock {
width: 70%;
margin: 0 auto
}
</style>
I don't understand anything. The three methods should be equivalent. Why I can load data from props but not from data or computed? I can't find any good documentation about the vue wrapper to understand why is this happening.
Thanks for your help,
H25E

The answer is very simple. The reason is that the Vue defines all component data only after returning a whole data object, so you should not use this keyword to refer other component data within data definition. In order to make it work correctly, you should keep the plotData within component's data, but move the config into the computed properties. Take a look on the code below:
props: {
options: {
type: Object,
required: true
}
},
data: function() {
return {
plotData: [[1,3],[2,7],[3,9],[4,2],[5,0],[10,13]]
}
},
computed: {
config: function() {
return {
series: [{
name: this.options.title,
data: this.plotData
}]
}
},
}

Related

How to pass custom data in FullCalender Vue with eventDataTransform

I have custom data points for each event (for example event.organizer). I want this included in the calendar within the event section.
I looked over the documentation (https://fullcalendar.io/docs/eventDataTransform), but it is pretty slacking, especially in regards to the Vue component (https://fullcalendar.io/docs/vue)
Can anyone assist in how the Vue Component should look? Do I need to add a prop? The following did not work.
import { resources, events } from "/mockdata.js";
<FullCalendar #eventDataTransform="eventDataTransform" :options="calendarOptions" />
export default {
components: {
FullCalendar,
},
data() {
return {
calendarOptions: {
plugins: [dayGridPlugin, interactionPlugin, resourceTimelinePlugin],
initialView: "resourceTimeline",
initialDate: "2021-06-18",
resources,
events,
},
};
},
methods: {
eventDataTransform: function(json) {
console.log(json)
},
},
};
Found within example. https://github.com/fullcalendar/fullcalendar-example-projects/blob/master/vue3-typescript/src/Demo.vue#L115
<FullCalendar :options="calendarOptions">
<template v-slot:eventContent='arg'>
<i>{{ arg.event.extendedProps.organizer }}</i>
</template>
</FullCalendar>

Why my Vue Child Component have already assign prop to $data at first, but still accidentally mutate the Parent $data?

Please see this minimum example
App.vue
<template>
<EditPerseon :previousPerson="persons[0]" />
</template>
<script>
import EditPerseon from "./EditPerseon.vue";
export default {
data() {
return {
persons: [
{ name: "Bob", age: 18 },
{ name: "Amy", age: 20 },
],
};
},
watch: {
$data: {
handler() {
console.log("My $data have been mutated!");
},
deep: true,
},
},
components: {
EditPerseon,
},
};
</script>
EditPerseon.vue
<template>
<input v-model="currentPerson.name" />
</template>
<script>
export default {
props: {
previousPerson: Object,
},
data() {
return {
currentPerson: this.previousPerson,
};
},
};
</script>
Also on CodeSandbox:
https://codesandbox.io/s/why-my-vue-child-component-have-already-assign-prop-to-data-at-first-but-still-accidentaly-mutate-the-parent-data-fueuw?fontsize=14&hidenavigation=1&theme=dark
EditPerseon.vue is my child component, but it still accidentally mutate my parent's $data.
Why is this happening?
How can I prevent this?
When you define an object (or an array) assigning it to another object in javaScript, you end up with two references to the same object. I think you believe that your line:
currentPerson: this.previousPerson,
is somehow creating a new object currentPerson that copies the values from this.previousPerson. However, what it's happening is that now you have two different variables that both are linked with the original object: so changes to currentPerson will actually modify the parent's person[0].
One thing you can do to mitigate this is to forge a new object in the child component:
data() {
return {
currentPerson: {...this.previousPerson},
};
},
or:
data() {
return {
currentPerson: {
name: this.previousPerson.name
},
};
},
The first option will fail if some fields of the object are objects themselves. The second one is safer but more verbose. You may try some packages like cloneDeep to safely clone objects.
A good general resource to get more info about the deal with object references and copies: https://javascript.info/object-copy.
Apart from this, I believe watcher handlers always trigger at created, so the handler will fire even if no change is detected (but I'm not 100% sure about this atm).
Because objects passes by reference. When you change to current person actually you change the original one in the memory.Because of that vue gives warning for that. You need to copy to prevent this.
data() {
return {
currentPerson: {...this.previousPerson},
};
},

How do I access programmatically created refs in vue.js?

I would like to access refs in a vue.js component, where the ref itself is created dynamically like so:
<style>
</style>
<template>
<div>
<lmap class="map" v-for="m in [1, 2, 3]" :ref="'map' + m"></lmap>
</div>
</template>
<script>
module.exports = {
components: {
lmap: httpVueLoader('components/base/map.vue'),
},
mounted: function(){
console.log('all refs', this.$refs);
// prints an object with 3 keys: map1, map2, map3
console.log('all ref keys', Object.keys(this.$refs));
// would expect ["map1", "map2", "map3"], prints an empty array instead
Vue.nextTick().then(() => {
console.log('map1', this.$refs["map1"]);
// would expect a DOM element, instead prints undefined
})
},
destroyed: function(){
},
methods: {
},
}
</script>
However this seems not to work (see above in the comments), and I can't figure why.
I think the problem is that you are importing the component asynchronously, with httpVueLoader, which then downloads and imports the component only when the component is rendered from the dom, therefore, the component has not yet been imported into the nextTick callback.
I suggest you put a loaded event in the map.vue component, maybe in mounted lifecycle , which will be listened to in the father, example #loaded = "showRefs"
surely when the showRefs(){ } method is invoked, you will have your refs populated ;)
Try using a template string e.g
`map${m}`
You have to wait until components have been rendered / updated. This works:
module.exports = {
data: function () {
return {
};
},
components: {
lmap: httpVueLoader('components/base/map.vue'),
},
mounted: function(){
},
destroyed: function(){
},
updated: function(){
Vue.nextTick().then(() => {
console.log('all ref keys', Object.keys(this.$refs));
console.log('map1', this.$refs['map1'][0].$el);
})
},
methods: {
},
}

Data property no longer reactive after doing simple re-assignment?

I have a data property called current_room where initially it has an empty object {}.
I have a component that will receive current_room as a "prop".
In the parent component, in the mounted() hook, re-assignment takes place: this.current_room = new_room
In the child component, the current_room prop appears to be... an empty object. In the parent component, it's not an empty object, it has the data I expect to see.
What would be the proper way to make this work? It seems as though simple re-assignment doesn't work in this case, that once I define a property on the data object... and that property is an object... I have to add/remove properties to the object, rather than just wholesale re-assigning a new object to that data property.
I guess it's just a simple mistake somewhere in your code. Because following to your question - it should work, furthermore I created a simple example where I defined components and functionality as you've described - and it works. I will provide async example to make you sure for 100 percents.
Here is the working example:
App.vue
<template>
<div id="app">
<CurrentRoom :room="current_room" />
</div>
</template>
<script>
import CurrentRoom from './components/CurrentRoom.vue'
export default {
name: "App",
components: {
CurrentRoom
},
data () {
return {
current_room: {}
}
},
mounted () {
setTimeout(() => {
this.current_room = {
door: true,
windowsCount: 2,
wallColor: 'white',
members: [
{
name: 'Heisenberg',
age: 46
},
{
name: 'Pinkman',
age: 26
}
]
}
}, 2000)
}
};
</script>
CurrentRoom.vue
<template>
<div>
Current room is: <br>
<pre>{{ room }}</pre>
</div>
</template>
<script>
export default {
name: 'CurrentRoom',
props: {
room: {
type: Object,
default: () => {}
}
}
}
</script>
Codesandbox demo:
https://codesandbox.io/s/epic-banach-clyz4
And for the end, following to your question:
... What would be the proper way to make this work? ...
The answer is - 'Please, compare your code with provided example'

Dynamic Vue components with sync and events

I'm using <component v-for="..."> tags in Vue.js 2.3 to dynamically render a list of components.
The template looks like this:
<some-component v-for="{name, props}, index in modules" :key="index">
<component :is="name" v-bind="props"></component>
</some-component>
The modules array is in my component data() here:
modules: [
{
name: 'some-thing',
props: {
color: '#0f0',
text: 'some text',
},
},
{
name: 'some-thing',
props: {
color: '#f3f',
text: 'some other text',
},
},
],
I'm using the v-bind={...} object syntax to dynamically bind props and this works perfectly. I also want to bind event listeners with v-on (and use .sync'd props) with this approach, but I don't know if it's possible without creating custom directives.
I tried adding to my props objects like this, but it didn't work:
props: {
color: '#f3f',
text: 'some other text',
'v-on:loaded': 'handleLoaded', // no luck
'volume.sync': 'someValue', // no luck
},
My goal is to let users re-order widgets in a sidebar with vuedraggable, and persist their layout preference to a database, but some of the widgets have #events and .synced props. Is this possible? I welcome any suggestions!
I don't know of a way you could accomplish this using a dynamic component. You could, however, do it with a render function.
Consider this data structure, which is a modification of yours.
modules: [
{
name: 'some-thing',
props: {
color: '#0f0',
text: 'some text',
},
sync:{
"volume": "volume"
},
on:{
loaded: "handleLoaded"
}
},
{
name: 'other-thing',
on:{
clicked: "onClicked"
}
},
],
Here I am defining two other properties: sync and on. The sync property is an object that contains a list of all the properties you would want to sync. For example, above the sync property for one of the components contains volume: "volume". That represents a property you would want to typically add as :volume.sync="volume". There's no way (that I know of) that you can add that to your dynamic component dynamically, but in a render function, you could break it down into it's de-sugared parts and add a property and a handler for updated:volume.
Similarly with the on property, in a render function we can add a handler for an event identified by the key that calls a method identified in the value. Here is a possible implementation for that render function.
render(h){
let components = []
let modules = Object.assign({}, this.modules)
for (let template of this.modules) {
let def = {on:{}, props:{}}
// add props
if (template.props){
def.props = template.props
}
// add sync props
if (template.sync){
for (let sync of Object.keys(template.sync)){
// sync properties are just sugar for a prop and a handler
// for `updated:prop`. So here we add the prop and the handler.
def.on[`update:${sync}`] = val => this[sync] = val
def.props[sync] = this[template.sync[sync]]
}
}
// add handers
if (template.on){
// for current purposes, the handler is a string containing the
// name of the method to call
for (let handler of Object.keys(template.on)){
def.on[handler] = this[template.on[handler]]
}
}
components.push(h(template.name, def))
}
return h('div', components)
}
Basically, the render method looks through all the properties in your template in modules to decide how to render the component. In the case of properties, it just passes them along. For sync properties it breaks it down into the property and event handler, and for on handlers it adds the appropriate event handler.
Here is an example of this working.
console.clear()
Vue.component("some-thing", {
props: ["volume","text","color"],
template: `
<div>
<span :style="{color}">{{text}}</span>
<input :value="volume" #input="$emit('update:volume', $event.target.value)" />
<button #click="$emit('loaded')">Click me</button>
</div>
`
})
Vue.component("other-thing", {
template: `
<div>
<button #click="$emit('clicked')">Click me</button>
</div>
`
})
new Vue({
el: "#app",
data: {
modules: [{
name: 'some-thing',
props: {
color: '#0f0',
text: 'some text',
},
sync: {
"volume": "volume"
},
on: {
loaded: "handleLoaded"
}
},
{
name: 'other-thing',
on: {
clicked: "onClicked"
}
},
],
volume: "stuff"
},
methods: {
handleLoaded() {
alert('loaded')
},
onClicked() {
alert("clicked")
}
},
render(h) {
let components = []
let modules = Object.assign({}, this.modules)
for (let template of this.modules) {
let def = {
on: {},
props: {}
}
// add props
if (template.props) {
def.props = template.props
}
// add sync props
if (template.sync) {
for (let sync of Object.keys(template.sync)) {
// sync properties are just sugar for a prop and a handler
// for `updated:prop`. So here we add the prop and the handler.
def.on[`update:${sync}`] = val => this[sync] = val
def.props[sync] = this[template.sync[sync]]
}
}
// add handers
if (template.on) {
// for current purposes, the handler is a string containing the
// name of the method to call
for (let handler of Object.keys(template.on)) {
def.on[handler] = this[template.on[handler]]
}
}
components.push(h(template.name, def))
}
return h('div', components)
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app"></div>