Nuxt - Cannot access data inside a method - vue.js

I'm using CKEditor 5 + CKFinder (Modal Mode) to select an image using the #click event. The problem is that I don't have access to data inside the onInit function.
Here is the method:
data() {
return {
post: {
thumbnail: null,
},
};
},
methods: {
openModal() {
console.log(this.post.thumbnail) // NO PROBLEM! this.post.thumbnail IS ACCESSIBLE
CKFinder.modal( {
chooseFiles: true,
width: 800,
height: 600,
onInit: function( finder ) {
finder.on( 'files:choose', function( evt ) {
var file = evt.data.files.first();
this.post.thumbnail = file.getUrl(); // PROBLEM !! $this.post is undefined
} );
}
} );
},
},
And this is my Template:
<div class="btn btn-danger" #click="openModal">Choose Image</div>
<img class="mx-auto d-block" :src="post.thumbnail" />

As mentioned in the comment, you need to use an arrow function to resolve this in the vue object.
Whenever you use function () {}, this refers to the properties of the function, not the Vue object that you intend to reference
// example
methods () {
openModal() {
onInit: function () {
this.post // 'this' is the onInit function, not the Vue object
}
}
}
// solution
methods () {
openModal() {
onInit: () => {
this.post // 'this' is the Vue object
}
}
}
Answer
data() {
return {
post: {
thumbnail: null,
},
};
},
methods: {
openModal() {
console.log(this.post.thumbnail) // NO PROBLEM! this.post.thumbnail IS ACCESSIBLE
CKFinder.modal( {
chooseFiles: true,
width: 800,
height: 600,
onInit: finder => {
finder.on( 'files:choose', evt => {
var file = evt.data.files.first();
this.post.thumbnail = file.getUrl(); // PROBLEM !! $this.post is undefined
});
}
});
},
},

Related

vuejs not setting data property from arrow function

I got this weird thing going on here:
I have this data property in vue
data() {
return {
currentLat: 'intial Lat',
currentLong: 'intial Long',
};
},
mounted() {
this.getCurrentLocation();
},
methods: {
getCurrentLocation() {
navigator.geolocation.getCurrentPosition((position) => {
this.currentLat = position.coords.latitude;
this.currentLong = position.coords.longitude;.
console.log(this.currentLat); this prints 41.2111
});
console.log(this.currentLat); this prints 'intial Lat'
},
},
this.currentLat not set in the mount
I dont understand what's happing here! it's so weird!
Here is an example of converting to a promise and using async/await:
async getCurrentLocation() {
const position = await new Promise(resolve => {
navigator.geolocation.getCurrentPosition(position => resolve(position))
});
this.currentLat = position.coords.latitude;
this.currentLong = position.coords.longitude;
console.log(this.currentLat); // shouldn't print initial value
},
Your code is valid, the callback arrow function is asynchronous (it's not executed immediately) and the call of console.log(this.currentLat); is synchronous which makes it to be executed before the callback context, the property is properly if you use it inside the template it will work fine
Set the values in a callback as follows:
<template>
<div id="app">{{ currentLat }} - {{ currentLong }}</div>
</template>
<script>
export default {
data() {
return {
currentLat: "intial Lat",
currentLong: "intial Long",
};
},
mounted() {
this.getCurrentLocation();
},
methods: {
getCurrentLocation() {
navigator.geolocation.getCurrentPosition(this.setCoordinates);
},
setCoordinates(position) {
this.currentLat = position.coords.latitude;
this.currentLong = position.coords.longitude;
},
},
};
</script>

Return value from dataPointSelection event in Apexcharts

I have a question related to this question and this answer but they don't solve my question completely. I'm using vue and apexcharts and I would like to return a value or update a variable from an event. Is it possible to return something instead of printing it in the console?
Something like this:
events: {
dataPointSelection: function (event, chartContext, config) {
this.active = this.series[config.seriesIndex];
}
}
The problem that I face is that "this" makes reference to the overall vue component and therefore "series" and "active" cannot be found.
Here is the code that gives me"TypeError: this.series is undefined" when I click on a point data. The series data I get from the parent component and it looks like this:
[{"name":"S-1","data":[[2.65,100], [6.67,100]]}, {"name":"S-2","data":[[0,50],[2.65,50]]}]
<script>
import VueApexCharts from 'vue-apexcharts';
export default {
name: "myGraph",
components: {
apexchart: VueApexCharts,
},
props: {
series: {}
},
data: () => ({
active: undefined,
chartOptions: {
chart: {
width: '100%',
animations: {
enabled: false
},
events: {
dataPointSelection: function (event, chartContext, config) {
this.active = this.series[config.seriesIndex];
}
}
},
tooltip: {
intersect: true,
shared: false
},
markers: {size: 1},
}
}),
}
}
</script>
The idea is that on dataPointSelection, it should activate that serie in order to access later on other information that will be store in that object.
The easiest way is to bind the event directly in the component
<apexchart type="bar" #dataPointSelection="dataPointSelectionHandler"></apexchart>
methods: {
dataPointSelectionHandler(e, chartContext, config) {
console.log(chartContext, config)
}
}
Another way is to use ES6 arrow functions in your chart configuration
computed: {
chartOptions: function() {
return {
chart: {
events: {
dataPointSelection: (e, chart, opts) => {
// you can call Vue methods now as "this" will point to the Vue instance when you use ES6 arrow function
this.VueDemoMethod();
}
}
},
}
}
}
I think this is simply what you are looking for
chart: {
type: 'area',
events: {
dataPointSelection(event, chartContext, config) {
console.log(config.config.series[config.seriesIndex])
console.log(config.config.series[config.seriesIndex].name)
console.log(config.config.series[config.seriesIndex].data[config.dataPointIndex])
}
}
}
if you need by the click, this is better
chart: {
type: 'area',
events: {
click(event, chartContext, config) {
console.log(config.config.series[config.seriesIndex])
console.log(config.config.series[config.seriesIndex].name)
console.log(config.config.series[config.seriesIndex].data[config.dataPointIndex])
}
}
}
source How to access value on dataPointSelection function of Apexchart
documentation events https://apexcharts.com/docs/options/chart/events/

How to make sure my directive runs a function?

I have this Vue code:
export default {
data: function() {
return {
'showPopup': false
}
},
components: {
'search-bar': SearchBarComponent,
},
mounted: function() {
$(this.$el).foundation();
},
updated: function() {
$(this.$el).foundation();
},
methods: {
clickOutsidePopup: function(event) {
console.log(event);
}
},
directives: {
clickoutside: {
bind (el) {
el.event = event => el.vm.$emit(el.expression, event)
el.addEventListener('click', el.stopProp)
document.body.addEventListener('click', event)
},
unbind(el) {
el.removeEventListener('click', el.stopProp)
document.body.removeEventListener('click', el.event)
},
stopProp(event) { event.stopPropagation() }
}
}
}
And inside the template I have this:
<div class="small-screen popup-container">
<div class="popup" v-show="showPopup" v-clickoutside="clickOutsidePopup">
<search-bar />
</div>
</div>
which will be shown/hidden if we click on this:
<span #click="showPopup = !showPopup">🔍</span>
My problem is that my directive does not execute clickOutsidePopup. When I click outside of my element? I was inspired by this: Detect click outside element
I managed to make it work with this directive code:
directives: {
clickoutside: {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
// here I check that click was outside the el and his childrens
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
}
}
added an id to the search button:
<span id="top-bar-search-icon" #click="showPopup = !sho wPopup">🔍</span>
and modified my method:
methods: {
clickOutsidePopup: function(event) {
if (event.target.id !== "top-bar-search-icon")
this.showPopup = false;
}
},

VueJS: Setting data initially based on http response

So I have a template .vue file:
<template>
<div id="app">
<textarea v-model="input" :value="input" #input="update"></textarea>
<div v-html="compiledMarkdown"></div>
</div>
</template>
<script>
var markdown = require('markdown').markdown;
export default {
name: 'app',
data() {
return {
input: '# Some default data'
}
},
mounted: function () {
this.$nextTick(function () {
this.$http.get(window.location.pathname + '/data').then((response) => {
this.input = response.body.markdown;
}) })
},
computed: {
compiledMarkdown: function() {
this.$http.post(window.location.pathname, {
"html": markdown.toHTML(this.input)}).then(function() {
},function() {
});
return markdown.toHTML(this.input);
}
},
methods: {
update: function(e) {
this.input = e.target.value
}
}
}
</script>
In the mounted function I am trying to set input equal to the response of an HTTP request, but when you view this file this.input is still the same as it was initially declared. How can I change this.input inside the compiledMarkdown function to be this.input in the mounted function. What other approaches might I take?
You can not call a async method from a computed property, you can use method or watcher to run asynchronous code, from docs
This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
You have to ran that relevant code when input changes, like following:
var app = new Vue({
el: '#app',
data: {
input: '# Some default data',
markdown : ''
},
methods: {
fetchSchoolData: function (schoolId) {
var url = this.buildApiUrl('/api/school-detail?schoolId=' + schoolId);
this.$http.get(url).then(response => {
this.schoolsListData = response.data;
}).catch(function (error) {
console.log(error);
});
},
},
mounted: function () {
this.$nextTick(function () {
this.$http.get(window.location.pathname + '/data').then((response) => {
this.input = response.body.markdown;
})
})
},
watch: {
// whenever input changes, this function will run
input: function (newInput) {
this.$http.post(window.location.pathname, {
"html": markdown.toHTML(this.input)}).then(function() {
},function() {
this.markdown = markdown.toHTML(this.input);
});
}
},
Have a look at my similar answer here.

multiple watchers same function vue.js

I have several watchers that should be calling the same function, is there way to list them all in once statement?
watch: {
'param.a' (nv) {
this.calc();
}
,'param.b' (nv) {
this.calc();
}
,'param.c' (nv) {
this.calc();
}
}
something along the lines of 'param.a,param.b,param.c' (nv) {...} ?
Edit: I should have clarified, this isn't the actual code, but I can't use a computed property.
Not sure why you can't use a computed property but you could add the watcher in the created hook like in the demo below or this fiddle.
I think a watch for array is not implemented in vue. Also there is a similar question at SO, just with Vue 1.x syntax. (There the watch is added in the mounted hook (previously ready) but I think you don't have to wait for DOM ready to add the watch. Anyway that would also work.)
My code is inspired by this github issue. Only changed to a mixin and to ES6 arrow function.
Vue.mixin({
methods: {
watchCollection(arr, cb) {
arr.forEach((val) => this.$watch(val, cb))
}
}
})
new Vue({
el: '#app',
/*watch: {
'param.a' (nv) {
this.calc();
}
,'param.b' (nv) {
this.calc();
}
,'param.c' (nv) {
this.calc();
}
},*/
/* // not suported
watch: {
['param.a', 'param.b', 'param.c'] : (nv) {
this.calc();
}
},*/
created() {
this.watchCollection(
['param.a', 'param.b', 'param.c'],
this.calc)
},
computed: {
resultComputed() {
return this.calc();
}
},
methods: {
calc() {
let a = parseInt(this.param.a);
let b = parseInt(this.param.b);
let c = parseInt(this.param.c);
this.result = a + b + c;
return this.result;
}
},
data() {
return {
param: {
a: 0,
b: 0,
c: 0
},
// msg: 'hello',
result: 0
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<input v-model="param.a" />
<input v-model="param.b" />
<input v-model="param.c" />{{result}} {{resultComputed}}
<!--{{msg}}-->
</div>