Access vue instance from handsontable - vue.js

I am trying to set a vuejs variable from within handsontable.
The vuejs variable:
this.dataChanged
in code block below is not available from handsontable settings, any idea how can I access it?
<template>
<div id="hot-container">
<HotTable :root="root" :settings="hotSettings"></HotTable>
</div>
</template>
<script>
export default {
data() {
return {
#vuejs variable i want to set from hot
dataChanged: false,
root: 'test-hot',
hotSettings: {
data: [{something: 0}],
afterChange: function(changes, src) {
if (src !== 'loadData') {
this.dataChanged = true
}
},
methods: {
saveChanges: function () {
if (this.dataChanged){
//save data
}
}
}

I faced this same problem... I found a workaround posted on GitHub like so..
This way you can access all Vue's data, methods, etc as you normally would.
data() {
return {
hotSettings: {
...
afterChange: this.afterChangeVue
...
}
}
},
methods: {
afterChangeVue(changes, source) {
console.log('changes, source => ', changes, source);
console.log('this.$store => ', this.$store);
},
Here is the link to the original thread: https://github.com/handsontable/vue-handsontable-official/issues/7#issuecomment-356190395

I ended up saving to a variable declared outside of vue - ie above the data () declaration
var myNewVar = 42
data() {
#can save to myNewVar from here

Just like #Rosdi_Kasim said, but much simpler with fat arrow function
data() {
return {
hotSettings: {
/*...*/
afterChange: (changes, source) => {
console.log('changes, source => ', changes, source);
console.log('this.$store => ', this.$store);
}
/*...*/
}
}
},
methods: {
}

Related

How do I make Vue 2 Provide / Inject API reactive?

I set up my code as follows, and I was able to update checkout_info in App.vue from the setter in SomeComponent.vue, but the getter in SomeComponent.vue is not reactive.
// App.vue
export default {
provide() {
return {
checkout_info: this.checkout_info,
updateCheckoutInfo: this.updateCheckoutInfo
}
},
data() {
return {
checkout_info: {},
}
},
methods: {
updateCheckoutInfo(key, value) {
this.checkout_info[key] = value
}
}
}
// SomeComponent.vue
export default {
inject: ['checkout_info', 'updateCheckoutInfo']
computed: {
deliveryAddress: {
get() { return this.checkout_info.delivery_address }, // <---- Not reactive??
set(value) { return this.updateCheckoutInfo('delivery_address', value) }
}
}
}
I found the answer after many hours of searching. You have to use Object.defineProperty to make it reactive. I'm not sure if this is the best approach, but this is a working example.
export default {
data() {
return {
checkout_info: {},
}
},
provide() {
const appData = {}
Object.defineProperty(appData, "checkout_info", {
enumerable: true,
get: () => this.checkout_info,
})
return {
updateCheckoutInfo: this.updateCheckoutInfo,
appData,
}
}
}
You can later access it via this.appData.checkout_info
This note from official documentation.
Note: the provide and inject bindings are NOT reactive. This is
intentional. However, if you pass down an observed object, properties
on that object do remain reactive.
I think this is the answer to your question.
source:
https://v2.vuejs.org/v2/api/#provide-inject
I would put the values in an object:
var Provider = {
provide() {
return {
my_data: this.my_data
};
},
data(){
const my_data = {
foo: '1',
fuu: '2'
};
return {
my_data;
}
}
}
var Child = {
inject: ['my_data'],
data(){
console.log(my_data.foo);
return {};
},
}
When are object properties they are reactive. I don't know if this is the correct solution but it works in my case.

vue-pdf doesn't refresh on src change

I'm using the latest vue-pdf package to display pdf files in my app. I built this component, PdfViewer:
<template>
<div class="fill-height pdf-container">
<template v-if="src && numberOfPages">
<pdf
v-for="page in numberOfPages"
:key="`${fileName}-${page}`"
:src="src"
:page="page"
/>
</template>
</div>
</template>
import { mapGetters } from 'vuex'
import pdf from 'vue-pdf'
export default {
props: {
fileName: {
type: String,
required: true
}
},
components: {
pdf
},
data() {
return {
src: null,
numberOfPages: 0
}
},
computed: {
...mapGetters({
getAttachments: 'questions/getAttachments'
})
},
methods: {
init() {
if (this.fileName) {
let url = this.getAttachments[this.fileName]
let loadingTask = pdf.createLoadingTask(url)
this.src = loadingTask
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
},
},
watch: {
fileName() {
this.init()
}
},
beforeMount() {
this.init()
}
}
Basically I'm receiving a fileName as a prop, then look for its URL in the object I receive in getAttachments getter. The file names are in different list component.
It works fine on the first run and the first file is loaded and displayed successfully. But once clicked on another file name - nothing being displayed. I do receive the file name prop and it does find the URL, but the file doesn't display. Even when I click on the file that has already been displayed - now it doesn't.
I thought maybe it has something to do with src and numberOfPages property, so I tried to reset them before loading the file:
init() {
if (this.fileName) {
this.src = null
this.numberOfPages = 0
let url = this.getAttachments[this.fileName]
let loadingTask = pdf.createLoadingTask(url)
this.src = loadingTask
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
}
Alas, same result. And in the console I see the following warning from pdf.worker.js: Warning: TT: invalid function id: 9
Have no idea what it means.
Any help, please?
EDIT
I tried to do that with async/await and forceUpdate:
async init() {
if (this.fileName) {
this.src = null
this.numberOfPages = 0
let url = this.getAttachments[this.fileName]
let loadingTask = await pdf.createLoadingTask(url)
await loadingTask.promise.then(pdf => {
this.src = url
this.numberOfPages = pdf.numPages
})
this.$forceUpdate()
}
}
That also didn't help. But I found out that once I change the passed fileName, the code does go to the init() method, but for some reason it skips the loadingTask.promise.then part, doesn't go in. I have to idea why.
Well, apparently there's some issue with vue-pdf library. Eventually I solved it by setting timeout when assigning fileName prop and re-rendering the component:
<PdfViewer v-if="selectedFileName" :fileName="selectedFileName" />
onFileNameSelected(fileName) {
this.selectedFileName = null
setTimeout(() => {
this.selectedFileName = fileName
}, 0)
}
And then in the PdfViewer component it's just:
created() {
this.src = pdf.createLoadingTask(this.getAttachments[this.fileName])
},
mounted() {
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
That did the trick for me, though feels kinda hacky.

Async changes to properties

New to vuejs.
I have a vue with the following script (code shortened):
export default {
mixins: [asyncStatuses],
props: {
value: { type: Object }
},
data() {
return {
statuses: []
};
},
computed: {
hasStatuses() {
return this.statuses && this.statuses.length > 0;
}
},
beforeMount() {
// This is an async call
this.getStatuses().then((response) => {
this.statuses = response.data.statuses;
});
}
};
In my .vue file, I do something like this:
<div v-if="hasStatuses">
<div>Show a list of statuses</div>
</div>
The problem is the <div> never shows up. The statuses are loading correctly. I put a debugger in the computed.hasStatuses but it never runs?
Can anyone explain to me how and why this is happening and how to fix it?
Thanks again!!
The code is setting self.statuses, but self is not defined.
self.statuses = response.data.statuses
Just use this.
this.statuses = response.data.statuses

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>