vue-grid-layout
vue-grid-layout copied to clipboard
Is there a way to use vuex on this package?
Like in the subject? Is there a way to do that? How are you updating layout on vuex?
I think this can be done like this:
// store
var store = new Vuex.Store({
state: {
layout: [
// your layout
]
},
mutations: {
UPDATE_LAYOUT(state, layout) {
state.layout = layout
}
},
getters: {
layout() {
return this.state.layout
}
}
})
In your component, simply make layout a computed variable, and add a getter and a setter:
computed: {
// ...
layout: {
get() {
return this.$store.getters.layout
},
set(newLayout) {
this.$store.commit('UPDATE_LAYOUT', newLayout)
}
}
}
Whenever the layout is changed
this.layout = someLayout
it will automatically update the layout in your vuex store.
Error: [vuex] Do not mutate vuex store state outside mutation handlers
Mutations on object should be only called from vuex I guess.. So every changing someLayout inside gridItem or/and gridLayout will efect in this error and the same we cannot get our Layout reactivly.
onLayoutUpdated(val) {
console.log(val)
console.log('onLayoutUpdated');
console.log(this.layout);
this.$store.commit('dashboard/setLayout', this.layout)
},
resize(val) {
console.log(val)
console.log('resize');
this.$store.commit('dashboard/setLayout', [])
},
move(val) {
console.log(val)
console.log('move');
this.$store.commit('dashboard/setLayout', [])
//this.layout2 = ;
},
moved(val) {
console.log(val)
console.log('moved');
this.$store.commit('dashboard/setLayout', [])
},
resized(val) {
console.log(val)
console.log('resized');
this.$store.commit('dashboard/setLayout', [])
},
Made like so but now When I shuffle One box with another the second one is mutating out of vuex. Andd When I try to put It out of parent Box The same situation.
Every time we are modyfing the position of box we should use mutation for it I think.
In Vuex we cant just make smth like Layout[i].x = newX; We should make new object or use Vue.set(array, property, newValue).
We could get It all working If we had all done by events and then we could call every change in vuex as well I guess. Or even better to make it reactiive with Vuex all time.
Hello All, I tried t use vuex with the package, and it works if you are not using strict mode. in strict mode, you get errors `
vue.esm.js?efeb:1743 Error: [vuex] Do not mutate vuex store state outside mutation handlers. at assert (vuex.esm.js?358c:97) at Vue.store._vm.$watch.deep (vuex.esm.js?358c:746) at Watcher.run (vue.esm.js?efeb:3235) at Watcher.update (vue.esm.js?efeb:3209) at Dep.notify (vue.esm.js?efeb:697) at Object.reactiveSetter [as y] (vue.esm.js?efeb:1014) at c (vue-grid-layout.min.js?dce4:1) at s (vue-grid-layout.min.js?dce4:1) at VueComponent.eval (vue-grid-layout.min.js?dce4:1) at Array.eval (vue.esm.js?efeb:1839) ` anybody with a solution?
If u disable it, It wont print this error, but still u will mutate out of the store what isnt good at all. I created my own grid layout for my uses based on this react-grid-layout.
@lrembacz can you share your gird? is this a case of wrong tool for the job,as I need to be able to keep track of layout in the store. I need to be able to save layout to db and recall as well. Are there better tools for this?
Unfortunately its not ready as a plugin or module so im not able to publish it. You can maybe try to update your component state layout with this plugin and then clone your object/array and then make mutation on your store only if its updated.
<grid-layout :layout="currentLayout"
v-if="currentLayout"
:col-num="12"
:row-height="30"
:is-draggable="draggable"
:is-resizable="resizable"
:vertical-compact="true"
:use-css-transforms="true"
@on-update="updateLayout"
>
<grid-item v-for="item in layout"
v-if="layout"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
>
<span class="text">{{item.i}}</span>
</grid-item>
</grid-layout>
data() {
return {
layout: []
}
},
watch: {
currentLayout(val) {
if (val) {
this.layout = JSON.parse(JSON.stringify(this.currentLayout));
}
},
},
computed: {
currentLayout: {
get() {
return this.$store.getters['dashboard/currentLayout'];
},
set(newLayout) {
this.$store.commit('dashboard/SET_CURRENT_LAYOUT', newLayout)
}
},
},
methods: {
updateLayout(newLayout) {
let filtered;
filtered = newLayout.map( (item) => { return { x: item.x, y: item.y, w: item.w, h: item.h, i: item.i }})
this.$store.commit('dashboard/SET_CURRENT_LAYOUT', filtered)
},
}
You can maybe try something like that but I havent tested it yet. We are cloning our layout without reactivity so we wont mutate state out of store. And after that we watch our store state and updating our component state on the change.
Hi @lrembacz , we don't really have vuex usage experience, would love if you could submit a PR. Even a sample project so we could test the problems you describe to try and fix them would be great!
Hi @gmsa , I dont rly have so much time to fix your whole Layout, but there are few mistakes I can see just like that.
https://github.com/jbaysolutions/vue-grid-layout/blob/master/src/GridLayout.vue#L242
At this line you are mutation prop layout directly and then watch this prop and you emiting event I dont rly understand this and its completly wrong I guess.
<template>
<div>
{{ layout }}
<br><br>
{{ layoutFromStore }}
<grid-layout
@layout-updated="layoutUpdatedEvent"
:layout="layout"
:col-num="12"
:row-height="30"
:is-draggable="true"
:is-resizable="true"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item v-for="item in layout"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
>
<!--@resize="resizeEvent"-->
<!--@move="moveEvent"-->
<!--@resized="resizedEvent"-->
<!--@moved="movedEvent"-->
<span class="text">{{item.i}}</span>
</grid-item>
</grid-layout>
</div>
</template>
<script>
import * as VueGridLayout from "vue-grid-layout";
var GridLayout = VueGridLayout.GridLayout;
var GridItem = VueGridLayout.GridItem;
export default{
data() {
return {
layout: []
}
},
created() {
this.layout = JSON.parse(JSON.stringify(this.layoutFromStore));
},
components: {
"GridLayout": GridLayout,
"GridItem": GridItem
},
watch: {
layoutFromStore(val) {
if (val) {
this.layout = JSON.parse(JSON.stringify(this.layoutFromStore));
}
},
},
computed: {
layoutFromStore: {
get() {
return this.$store.getters['dashboard/layout']
},
set(newLayout) {
this.$store.commit('dashboard/SET_LAYOUT', newLayout);
}
}
},
methods:{
layoutUpdatedEvent: function(newLayout){
let filtered;
filtered = newLayout.map( (item) => { return { x: item.x, y: item.y, w: item.w, h: item.h, i: item.i }})
this.$store.commit('dashboard/SET_LAYOUT', filtered);
}
}
}
</script>
With this code you can make it working partly, but Im not sure if your mutation of props won't make some changes to state and causes discrepancy of state.
As I can see you are using those events just to tell to user that state was changed. I would prefer to make changes to layout only with those events or u can just clone ur props state inside your layout and work on this local state, then with events you can mutate your main layout inside Vuex. I think its a good solution.
this.layout = JSON.parse(JSON.stringify(this.layoutFromStore));
This actually can clone object without reactivity.
The problem here is that the layout prop is an object that is passed by value, and vue-grid-layout modifies fields internally to that prop.
Changing props is bad practice using Vue, as far as I understand. A warning is generated if the entire prop is changed outright, but no warning is generated if internal values of the prop is changed, except when using vuex in strict mode.
EDIT: I've just now discovered that my solution is almost identical to the one @lrembacz posted over a year ago. So the rest of my post is highly redundant:
I have worked around it like so:
import _ from 'underscore';
...
export default {
data() {
// VueGridLayout modifies this when modifying the grid...
let layout = _.clone(
// mapGetters not ready yet...
this.$store.getters.getTabsGridLayout(this.tabID)
);
return {
layout,
}
},
computed: {
...mapGetters(['getTabsGridLayout']),
// We need this so we can watch it and determine if vuex's layout has changed so we can update our local layout data.
storeLayout() {
return this.getTabsGridLayout(this.tabID)
},
},
watch: {
storeLayout(val, oldVal) {
// This gets called even when nothing actually changed
// https://codepen.io/pmorch/pen/JQNLdm
if (JSON.stringify(val) == JSON.stringify(oldVal))
return;
// console.log('storeLayout changed', this.tabID);
this.layout = val;
}
},
methods: {
...mapActions(['setTabsGridLayoutAction']),
// This is called when layout has finished changing (on mouseup typically)
layoutUpdatedEvent: function(newLayout) {
this.setTabsGridLayoutAction({
tabID: this.tabID,
layout: newLayout
});
}
},
}
The idea is that a component that local data() originally based on the vuex state. vue-grid-layout can modify it as it does without causing vuex problems since this is local data. When storeLayout (the layout in the vuex store) changes the local layout in component-local data gets updated. Once layout is complete, we update the vuex store with the changed layout.
Using this approach, the vuex store only changes e.g. when resizing a GridItem is complete, it doesn't get updated while it is being dragged. But that was fine for me. Otherwise one could've watched the local layout and then updated the vuex store when it changed.
Just a comment, the _.clone inside data() can be avoided. You said mapGetters not ready yet .
If the purpose is to avoid errors on layout undefined, just put a condition on layout not null, so when layout will be ready, the grid will be rendered.
I find this cleaner:
<grid-layout v-if="layout">
....
data() {
return {
layout: null,
...
}
}
and obviously keep the watch on storeLayout as needed
@tonjo instead of setting layout to null why not just set it to [], an empty array. That means it won't break and it sets you up really well for dynamically adding and removing items.
@funkel1989 yes, definitely a better idea
One tip for everyone. Just one event "@layout-updated" not enough. You have to subscribe to breackpoint changed event if you using responsive layout. But still it doesn't work for me 100% on the smallest size screen, so I rendering one column without grid just simple for. Ugly but working.