gridstack / gridstack.js

Build interactive dashboards in minutes.
https://gridstackjs.com
MIT License
6.56k stars 1.28k forks source link

Vue3 demo issue with 2 grids #2214

Open Zen33 opened 1 year ago

Zen33 commented 1 year ago

Page url: https://gridstackjs.com/demo/vue3js_v-for.html

When I add two grid, then remove one of them, the rest could not draggable.

fredericrous commented 1 year ago

playing with the demo I could also find myself in a situation where 2 widgets overlap. A widget get's behind another and when I move the one at the top, the one beneath moves, 1 sec after, on the same spot I dropped my first widget

adumesny commented 1 year ago

I do not recommend v-for usage - see my Angular Copomnent explaination why for loops are only for very very simple cases IMO...

maybe someone can create a Vue version of my angular components and examples.

adumesny commented 1 year ago

I would love to have a high quality wrapper for Vue (and React) as I've now created one for Angular (what I use at work) - clearly keeping gridstack neutral (plain TS) as frameworks come and go....

I don't know Vue, but for more advanced things (multiple grids drag&drop, nested grids, dragging from toolbar to add/remove items) is it best to let gridstack do all the DOM manipulation as trying to sync between framework and GS becomes complex quickly. This is what I've done in the Angular wrapper - gridstack calls back using addFRemoveCB to have correct Angular component created instead of <div class="gridstack-item"> for example, but all dom dragging/reparenting/removing is done by gs. Content is created using framework.

The current React & Vue use the for loop which quickly falls appart IMO (I have the same for Angular but discourage for only the simplest things (display a grid from some data, with little modification by user)

thalida commented 1 year ago

Sharing my vue (vue3) solution in case it's helpful, and I'm also interested to see how other folks have rolled their own.

This gives full control to Gridstack to handle creating and removing items, I hook into the added event to programmatically add Vue 3 components to the content. So far this implementation works well with other Gridstack of features: responsive grid, drag in widgets (acceptWidgets), etc.

<script setup lang="ts">
import { h, onMounted, render, watchEffect } from 'vue'

let grid: GridStack | null = null;

onMounted(() => {
  grid = GridStack.init({
    margin: 12,
    cellHeight: 100,
    float: true,
    disableOneColumnMode: true,
    acceptWidgets: true,
    minRow: 1,
  })

  grid.on('added', function(event: Event, items: GridStackNode[]) {
    for (const item of items) {
      const itemEl = item.el as HTMLElement
      const itemElContent = itemEl.querySelector('.grid-stack-item-content') as HTMLElement

      const widgetId = item.id

      if (typeof widgetId === 'undefined') {
        continue
      }

      // dynamically render a vue component, and append it to the grid stack item content
      // https://vuejs.org/guide/extras/render-function.html
      const widgetNode = h(SpaceWidget, { widgetId })
      render(widgetNode, itemElContent)
    }
  });

  grid.on('removed', function(event, items) {
    for (const item of items) {
      const itemEl = item.el
      const itemElContent = itemEl.querySelector('.grid-stack-item-content')
      // Unmount the vue node from the item element
      // Calling render with null will allow vue to clean up the DOM, and trigger lifecycle hooks
      render(null, itemElContent)
    }
  });

  watchEffect(() => {
    grid?.load(<gridstack settings>)
  })
});
</script>
<template>
  <div class="grid-stack grow shrink-0 w-full h-full"></div>
</template>
adumesny commented 1 year ago

| This gives full control to Gridstack to handle creating and removing items

that's great and it would be great if you could add an running vue example like the other ones already posted... Also you could use GridStack.addRemoveCB to get called to create the dom elements without waiting for the added event to happen (base class could create the default which it currently doesn't). Question is do you need to also do something special when items are deleted for your components ? Angular does.

thalida commented 1 year ago

that's great and it would be great if you could add an running vue example like the other ones already posted...

I'll open a PR today with new demos using GridStack.addRemoveCB and the event based way I shared above. If that's not what you meant, let me know!

Question is do you need to also do something special when items are deleted for your components ? Angular does.

Yes! Good catch, thank you. I've updated my own code to handle remove, and will include it in the demos. The code above has also been edited to show remove flow.

adumesny commented 1 year ago

right, so instead of using added/removed events, we might want to use GridStack.addRemoveCB but in your case (unlike the Angualr wrapper I include) you want the 2 divs and only then do you add your component inside the content div. I was thinking we could extract the div creation out and have GS use it, or your code use it before adding Vue component inside content. that would require a new lib, but I can extract it out...

rocifier commented 1 year ago

@thalida I am trying to adapt your example for our dashboard. but the problem I am facing is that my manually rendered components do not have access to the rest of my app. in particular global functions we add to app.config.globalProperties or childchild components which try to access those. Do you know any way of instancing vue components like you are doing but with the full app scope available? I tried referencing named components which I registered using app.component(...) but it just says they are not found...

Edit: I found a way to hack it by writing createdWidgetVNode.appContext = this.appContext || null and first storing appContext in my parent component in setup() from getCurrentInstance()?.appContext. However the above code still doesn't work in the case where my component gets re-rendered. My component is in a keep-alive tab component and when I change to another tab and back, the tab becomes visible and it wants to re-render. All my gridstack widgets are rendered but are disconnected from the gridstack so they have no height and working layout anymore.

vela666 commented 1 year ago

分享我的 vue (vue3) 解决方案,以防它有帮助,而且我也有兴趣了解其他人如何推出自己的解决方案。

这为 Gridstack 提供了完全控制权来处理创建和删除项目,我挂钩该added事件以编程方式将 Vue 3 组件添加到内容中。到目前为止,这个实现与 Gridstack 的其他功能配合良好:响应式网格、拖入小部件 ( acceptWidgets) 等。

<script setup lang="ts">
import { h, onMounted, render, watchEffect } from 'vue'

let grid: GridStack | null = null;

onMounted(() => {
  grid = GridStack.init({
    margin: 12,
    cellHeight: 100,
    float: true,
    disableOneColumnMode: true,
    acceptWidgets: true,
    minRow: 1,
  })

  grid.on('added', function(event: Event, items: GridStackNode[]) {
    for (const item of items) {
      const itemEl = item.el as HTMLElement
      const itemElContent = itemEl.querySelector('.grid-stack-item-content') as HTMLElement

      const widgetId = item.id

      if (typeof widgetId === 'undefined') {
        continue
      }

      // dynamically render a vue component, and append it to the grid stack item content
      // https://vuejs.org/guide/extras/render-function.html
      const widgetNode = h(SpaceWidget, { widgetId })
      render(widgetNode, itemElContent)
    }
  });

  grid.on('removed', function(event, items) {
    for (const item of items) {
      const itemEl = item.el
      const itemElContent = itemEl.querySelector('.grid-stack-item-content')
      // Unmount the vue node from the item element
      // Calling render with null will allow vue to clean up the DOM, and trigger lifecycle hooks
      render(null, itemElContent)
    }
  });

  watchEffect(() => {
    grid?.load(<gridstack settings>)
  })
});
</script>
<template>
  <div class="grid-stack grow shrink-0 w-full h-full"></div>
</template>

This method specifies that the drag handle selector 'handle:'. handleClass' is invalid. How can I solve this problem

carum98 commented 1 month ago

@thalida I am trying to adapt your example for our dashboard. but the problem I am facing is that my manually rendered components do not have access to the rest of my app. in particular global functions we add to app.config.globalProperties or childchild components which try to access those. Do you know any way of instancing vue components like you are doing but with the full app scope available? I tried referencing named components which I registered using app.component(...) but it just says they are not found...

Edit: I found a way to hack it by writing createdWidgetVNode.appContext = this.appContext || null and first storing appContext in my parent component in setup() from getCurrentInstance()?.appContext. However the above code still doesn't work in the case where my component gets re-rendered. My component is in a keep-alive tab component and when I change to another tab and back, the tab becomes visible and it wants to re-render. All my gridstack widgets are rendered but are disconnected from the gridstack so they have no height and working layout anymore.

Hi @rocifier, did you find any solution to the keep-alive problem?