Can Handsontable beforeRemoveRow be async? - vuejs2

Expectation:
beforeRemoveRow performs an async action (go to database) and then returns false/true. In case of 'false' I expect the row to NOT be removed from the table.
Behavior:
Even when I return 'false' from beforeRemoveRow, the row gets deleted.
Code:
Handsontable with Vue.js
<template>
<!-- ... -->
</template>
<script>
import { mapState } from 'vuex'
import hot from '#/components/hot/index'
import { url } from '#/constants/api'
export default {
computed: {
...mapState({
clientAccounts: state => state.clients.clientAccounts,
token: state => state.auth.token
})
},
data () {
return {
hotSettings: {
beforeRemoveRow: async (index) => {
const confirmed = confirm('Do you really want to delete the user?')
if (confirmed) {
try {
const result = await this.$store.dispatch({
type: 'deleteClientAccount',
id: this.clientAccounts[index].id,
token: this.token,
})
console.log('result', result)
return true
} catch (err) {
return false
}
} else {
return false
}
},
contextMenu: ['remove_row']
}
}
},
components: {
hot: hot
}
}
</script>
I am wondering if it has to do with the 'async' keyword?
When I remove the 'async' syntax and go with with promise .then it behaves as expected and does not delete the row. However, in this case it fails to perform the async action before deleting the row.
EDIT
Handsontable Support answered to this question in their forum:
"the hook is currently running synchronously and only by returning false you're able to cancel the action.
As I see the last comment suggested to make it work async, which is a good idea. However, currently, Handsontable work only on synchronous calls and it would be impossible to change only one hook."
That said, if anybody found a workaround without hook to prevent removing a row based on database validation, please share.

Since Handsontable is synchronous I had to insert my own function before Handsontable ever gets called.
I'm using the old Handsontable RemoveRow plugin (was removed in 1.11.0), which adds remove buttons next to each row. Obviously if you're not using the RemoveRow plugin this won't look exactly the same, but maybe it can give you some ideas.
Originally, the plugin calls Handsontable to remove the row immediately after the button is pressed:
$(div).on('mouseup', function () {
instance.alter("remove_row", row); // <---
});
I replaced that with a call to my own function:
$(div).on('mouseup', function () {
requestRemoveRow(instance, row); // <---
});
Now in my own function I can make an AJAX request with a callback. If successful, I'll proceed with the usual Handsontable alter call to remove the row:
function requestRemoveRow(handsontable, row) {
// Disable remove buttons during request to avoid race condition
$("th.htRemoveRow *").attr("disabled", true);
$.ajax({
url: "resource/" + row,
type: "DELETE"
})
.done(function() {
handsontable.alter("remove_row", row);
})
.always(function() {
// Enable remove buttons after request finishes
$("th.htRemoveRow *").removeAttr("disabled");
})
;
}

Related

Vue 2 / Nuxt 2 Emit From Axios With Dialog Confirm

i am using Vue 2 / nuxt to emit from a axios post* call which itself is called from a Buefy dialog confirm. The emit from this component will close the window / panel and then re-load the users.
If I call the axios request from the button, this works without any issues, but once being called from the dialog, it just don't work?
*most likely this will be updated to a delete request, just not gotten to that let
See code below:
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: () => {
this.removeUserFunc()
}
})
},
removeUserFunc() {
// console.log(that)
const that = this
// Build URL
const EndPoint = '/remove_user/' + this.id
this.$axios.post(EndPoint).then((res) => {
// User Remove Message
UserRemoved(this.$swal)
that.$parent.$emit('completed')
// console.log(this.$emit('complete'))
// // Emit 'completed' Message
console.log(that.$emit('completed'))
console.log(that)
}).catch((res) => {
console.log(res)
// Check For Errors
GeneralError(this.$swal)
})
}
I was thinking it was losing access to the correct this, so i was trying to pass that back in, but not sure that is the case?
I have also tried with await, while that sort of works? I think is firing the emit too fast, as it re-loads the users but it still includes the user that as just been deleted?
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: async() => {
this.removeUserFunc()
await this.$emit('completed')
}
})
},
The this keyword refers to the object the function belongs to, or the window object if the function belongs to no object.
Try to use .bind and use a ES5 function
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: function() {
this.removeUserFunc()
}.bind(this)
})
},

Vue js getters returns always empty

I have a vue 3 app with vuex, below is my store :
import VueX from 'vuex'
import ProductService from '#/services/ProductService'
export default new Vuex.Store({
state:{ products : [] },
getters : {
getProductFromSlug(state){
console.log(state) // <= state with products retrieved (please see first screen capture below)
console.log(state.products) // <== empty ??? (please see second screen capture)
return state.products.filter( product => {
return product.slug == 'my-slug'
})
}
},
actions:{
fetchProducts(context){
ProductService.getProducts().then( response => context.commit('setProducts', response.data) )
}
},
mutations: { setProducts(state, products) { this.state.products = products } }
})
From my component, i want to retrieve a product by slug :
...
created() {
store.dispatch('fetchProducts')
console.log(store.getters.productFromSlug)
}
First output of state ==>
Output of state.products ==>
Anyone can explain me please why i get empty values from the getProductFromSlug ?
Console output shouldn't be used as a reliable way of debugging. Here this problem is observed, an array is passed by reference to console, it doesn't reflect the current state of things.
The mistake here is that promises aren't chained correctly, this results in race condition. dispatch always returns a promise, asynchronous actions should always return a promise. Then it's not waited. It should be:
async created() {
await store.dispatch('fetchProducts')
console.log(store.getters.productFromSlug)
}
and
fetchProducts(context){
return ProductService.getProducts().then(...)
}

Returning a getters in a computed create a loop

I am calling inside the computed an action from the store to run it and after I am returning a getter, this will create a loop.
The HTML
{{loadedProjects}}
The computed
computed: {
loadedProjects() {
this.$store.dispatch("getProjects");
return this.$store.getters.loadedProjects;
}
}
The store
import Vuex from "vuex";
import axios from "axios";
const createStore = () => {
return new Vuex.Store({
state: {
loadedProjects: []
},
mutations: {
setProjects(state, projects) {
state.loadedProjects = projects
}
},
actions: {
getProjects(vuexContext) {
console.log("hello1")
return axios.get("THE API URL")
.then(res => {
console.log("hello2")
vuexContext.commit("setProjects", res.data);
})
.catch(e => console.log(e));
}
},
getters: {
loadedProjects(state) {
return state.loadedProjects;
}
}
});
};
export default createStore;
I expect to call my action to populate my state and after to return my state to render my data.
What is the point of using the store action that makes an API call inside the computed property ... maybe you want to trigger loadedProjects change ? ....computed property is not asynchronous so either way the return line will be executed before the you get the response... you might try vue-async-computed plugin OR just use the call on the created hook like you have done which is the better way and you don't have to use a computed property you can just {{ $store.getters.loadedProjects }} on your template
Computed properties should not have side effects (e.g. calling a store action, changing data, and so on). Otherwise it can happen that the triggered side effect could lead to a re-rendering of the component and possible re-fetching of the computed property. Thus, an infinite loop
I changed the code like that:
created: function () {
this.$store.dispatch("getProjects")
},
computed: {
loadedProjects() {
return this.$store.getters.loadedProjects
}
}
It is working now but I would like to know but I have that problem working inside the computed and also I wonder if it's the best solution. Any help????

Make Vue template wait for global object returned by AJAX call

I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.

Which Lifecycle hook after axios get but before DOM render

I'm trying to render my DOM, dependent on some data I'm returning from an axios get. I can't seem to get the timing right. The get is in the created hook, but there is a delay between the get and actually receiving the data. Basically if there is info in seller_id then I need to show the cancel button, otherwise don't. Here is my code:
this is in my created hook
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
and then this is the logic to show or hide the button. I've tried created, mounted, beforeUpdate, and updated all with no luck. I've also tried $nextTick but I can't get the timing correct. This is what I have currently:
beforeUpdate: function () {
// this.$nextTick(function () {
function sellerIdNotBlank() {
var valid = this.seller_id == '';
return !valid;
}
if(sellerIdNotBlank()){
this.show_cancel_button = true;
}
// })
},
First, it is pointless to get your data from backend and try to sync with Vue.js lifecycle methods. It never works.
Also, you should avoid beforeUpdate lifecycle event. It is often a code smell. beforeUpdate is to be used only when you have some DOM manipulations done manually and you need to adjust them again before Vue.js attempt to re-render.
Further, show_cancel_button is a very good candidate for a computed property. Here is how component will look:
const componentOpts = {
data() {
return {
seller_id: '',
// ... some more fields
};
},
created() {
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
},
computed: {
show_cancel_button() {
return this.seller_id !== '';
}
}
}