Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue3 demo issue with 2 grids #2214

Open
Zen33 opened this issue Feb 15, 2023 · 13 comments
Open

Vue3 demo issue with 2 grids #2214

Zen33 opened this issue Feb 15, 2023 · 13 comments

Comments

@Zen33
Copy link

Zen33 commented Feb 15, 2023

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
Copy link
Contributor

fredericrous commented Mar 17, 2023

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 adumesny added the Vue label Mar 17, 2023
@adumesny
Copy link
Member

adumesny commented Mar 17, 2023

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 adumesny changed the title Vue3 demo issue Vue3 demo issue with 2 grids Mar 17, 2023
@adumesny adumesny pinned this issue Apr 8, 2023
@adumesny
Copy link
Member

adumesny commented Apr 8, 2023

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
Copy link
Contributor

thalida commented May 30, 2023

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
Copy link
Member

adumesny commented May 30, 2023

| 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
Copy link
Contributor

thalida commented May 30, 2023

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
Copy link
Member

adumesny commented May 30, 2023

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
Copy link

rocifier commented Jun 28, 2023

@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
Copy link

vela666 commented Jul 16, 2023

分享我的 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
Copy link

carum98 commented Aug 13, 2024

@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?

@mateusas96
Copy link

@carum98 this solution worked for me.

onActivated(() => {
    grid = GridStack.init(gridOptions);
});

onDeactivated(() => {
    grid?.destroy(false); //  if false nodes and grid will not be removed from the DOM
});

@markWIMPER
Copy link

@carum98这个解决方案对我有用。

onActivated(() => {
    grid = GridStack.init(gridOptions);
});

onDeactivated(() => {
    grid?.destroy(false); //  if false nodes and grid will not be removed from the DOM
});

nice bro!

@carum98
Copy link

carum98 commented Oct 15, 2024

这个解决方案对我有用。

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants