Vue.js dynamically rendering svg - javascript

I'm trying to render a svg file containing Vue.js template syntax at specific places. The file gets rendered correctly.
Upon instantiation of the inner element (the svg), the template syntax gets replaced, but a vue warning is emitted:
vue.js:597 [Vue warn]: Property or method "data" 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.
In the SVG there is a text containing the template syntax {{data}}
Vue.component('svg-show', {
props: {
model: {
required: true
},
compiled:null
},
render: function (h) {
return h("div",
[
h(this.compiled, {
props: {
data: this.model
}
})
]);
},
created() {
},
mounted() {
console.log("mounted");
console.log(this.model.SVG);
this.compiled = Vue.compile("<div>" + model.SVG + "</div>");
}
});
https://jsfiddle.net/dg2hkeby/10/
i also tried to use a static variable for the data property, but this did not succeed either.
How would one achieve what i'm trying to do using Vue.js?

Related

Vue 2 watch fails for mixin data property, but works if the component has the data property

I am trying to move some functionality to a vue mixin from the component, to be able to use it in multiple components.
This (simplified version of the code) works:
export default {
data() {
return {
file: {},
audioPlayer: {
sourceFile: null,
},
};
},
watch: {
'audioPlayer.SourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
}
But if I move the audioPlayer data object to a mixin, the watch does no longer fire.
Is this expected behavior?
N.b. I resolved this by directly making the 'file' data property into a computed value, which works in this particular case, but the behavior is still strange.
You need a lowercase s. sourceFile not SourceFile
watch: {
'audioPlayer.sourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}

Vue: How to bind property dynamically to a new DOM element

Say, I have a component with the following template:
<div id="root"></div>
Then somehow I call the code that appends a new element to it (using jQuery for now):
$('#root').append('<div id="a1" v-bind:styles="styles.a1"></div>')
where "styles.a1" is just an object from component's data:
data() {
return {
styles: { a1: {color: 'red'} }
};
}
I suppose the newborn element's binding will not work because Vue doesn't know anything about this new element and its binding.
But maybe there is some way to implement the desired logic?
PS I know that Vue's nature is to manipulate with data, not DOM but here is the situation I have to deal with.
If you're using Vue components you shouldn't be manipulating the DOM.
You should either use template logic for that.
<template v-if="someCondition">
<div id="a1" v-bind:styles="styles.a1"></div>
</template>
Wrap non Vue code inside it's own Vue component.
Vue.component('date-picker', {
template: '<input/>',
mounted: function() {
$(this.$el).datepicker();
},
beforeDestroy: function() {
$(this.$el).datepicker('hide').datepicker('destroy');
}
});
https://vuejsdevelopers.com/2017/05/20/vue-js-safely-jquery-plugin/
Use a custom directive.
Vue.directive('demo', {
bind: function () {
$(this.el).css({color:'#fff', backgroundColor:this.arg});
},
update: function (value) {
$(this.el).html(
'argument - ' + this.arg + '!<br>' +
'key - ' + this.key + '<br>' +
'value - ' + value);
}
});
You can bind a style without a value first,
data() {
return {
styles: { a1: '' }
};
}
then you can add data into that from an event via normal JS. (JQuery isn't even necessary here).
methods: {
quack() {
this.styles.a1 = { color: red }
}
}
Edit: I got it wrong. Seems Vue does not detect newly added elements after being mounted. So what you can do is mount the Vue instance after the appended elements are added or to re-render the whole instance with forceUpdate . (The latter will re-render the whole instance so I recommend breaking down into multiple Vue instances for the sake of performance)
ForceUpdate

Vue-meta: metaInfo doesn't have an access to computed properties

I'm using vue-meta to dynamically change my meta tags. I want to change it only on some particular pages.
I'm using metaInfo function and try to change, for example, a title. But data from my getter is undefined which is why I cannot change the title in meta tags. It seems like metaInfo function try to access data before the component actually has it.
Here is my code in the component:
<template>
...
</template>
<script>
export default {
metaInfo() {
return {
title: this.getViewPage.data.meta.title, // data is undefined
};
},
created() {
this.loadViewPage();
},
computed: {
...mapGetters(['getViewPage']),
},
methods: {
...mapActions(['loadViewPage']),
};
</script>
vue-meta just creates computed property from your metaInfo function (according to plugin source code), so I assume that your loadViewPage action fills data object asynchronously and your problem just transforms to null-checking problem.
So you should check data before using its properties, and when data will be loaded metaInfo will update object as well:
metaInfo() {
// don't know your return object structure,
// maybe you should check whole this.getViewPage
let data = this.getViewPage.data;
return {
title: data ? data.meta.title : "some placeholder title",
}
};

VueJS: unable to update a component property from inside a watcher

I am building a Vue 2 Webpack application that uses Vuex. I am trying to update a component's local state by watching a computed property which is getting data from the Vuex store. This is what the inside of the <script></script> section of my component looks like:
export default {
name: 'MyComponent',
data() {
return {
// UI
modal: {
classes: {
'modal__show-modal': false,
},
tags: [],
},
};
},
computed: {
tagList() {
return this.$store.getters.tagList;
},
},
watch: {
tagList: (updatedList) => {
this.modal.tags = updatedList;
},
},
};
As you can see, I have a computed property called tagList which fetches data from the store. I have a watcher that watches tagList so that whenever the store's data changes, I can update modal.tags to the new value.
As per Vue documentation, I can call this.propertyName and update my local component state but when I call this.modal.tags = updatedList;, I get the following error:
[Vue warn]: Error in callback for watcher "tagList": "TypeError: Cannot set property 'tags' of undefined"
Why does this error occur even though it looks no different than what is in Vue.js's documentation?
Don't use arrow functions.
Change from:
watch: {
tagList: (updatedList) => {
this.modal.tags = updatedList;
},
},
To:
watch: {
tagList(updatedList) { // changed this line
this.modal.tags = updatedList;
},
},
Vue docs mention this a few times:
Don't use arrow
functions
on an options property or callback, such as created: () => console.log(this.a) or vm.$watch('a', newValue => this.myMethod()).
Since arrow functions are bound to the parent context, this will not
be the Vue instance as you'd expect, often resulting in errors such as
Uncaught TypeError: Cannot read property of undefined
or
Uncaught TypeError: this.myMethod is not a function
It is basically a context/scope issue. When using arrow functions, the this does not refer to the Vue instance, but the enclosing context of where the component was declared (probably window).
That is because of the scope issue. You are calling this. from another context. So, within arrow functions, you don't have access to vuejs data.
I suggest you change the watch to:
tagList (updatedList) {
this.modal.tags = updatedList;
},

How to access child component data in v-if outside of component?

In short, I want to get a value from a child component and check what it is in the parent. I have a working implementation using computed properties and a reference via v-ref on the child component, but I was wondering if I am doing it the right way and if there's a better/proper way to do it.
To get to specifics, I have a component with checkboxes, the checked checkboxes' values within this component are kept in the components data in an array variable named selected. Outside of the component I want to conditionally show a <div> using v-if however I'm unsure how to correctly grab the child component's selected value.
Here's a brief overview of my code:
component mark up
<student-table
v-ref:student-table
:data="students"
:course="course"
:columns="columns"
>
</student-table>
component registration
Vue.component('student-table', {
/* unrelated code */
data: function () {
return {
selected: []
}
},
/* unrelated code */
})
main vue instance
var vueApp = new Vue({
/* unrelated code */
computed: {
selected: function () {
return this.$refs.studentTable.selected.length
}
},
/* unrelated code */
})
Then in my html I can reference selected and I'll get the length of StudentTable.selected and thus be able to use it in my v-if
Thanks for any guidance or help!
Edit
I'm getting this in my console:
[Vue warn]: Error when evaluating expression "function () {
return this.$refs.studentTable.selected.length
}". Turn on debug mode to see stack trace.
There are several ways to share data between parents / components such as 2-way binding between parent/child and also sending and listening for events.
Here is an events example with $broadcast and $dispatch:
parent vue:
var parentVue = new Vue({
...
compiled: function(){
this.$on('receiveDataFromChild', function(){
//do something with the data from the child
});
},
methods: {
checkChildForData: function(){
this.$broadcast('pleaseSendDataToYourMama');
}
}
});
child vue:
var childVue = new Vue({
...
compiled: function(){
this.$on('pleaseSendDataToYourMama', function(){
this.$dispatch('receiveDataFromChild',this.someImportantData);
});
}
});
This is how I now have it working, I'm not sure this is the best way but I'm not getting any console.warn alerts in my console. Would love any feedback. Many thanks to #Douglas.Sesar
// child
Vue.component('student-table', {
parent: vueApp,
data: function () {
return {
selected: []
}
},
watch: {
selected: function() {
this.$dispatch('updateSelected', this.selected);
}
},
})
// parent
var vueApp = new Vue({
components: {
child: studentTable
},
data: {
selected: []
},
events: {
updateSelected: function(selected) {
this.selected = selected;
}
},
})

Categories