SortableJS / Vue.Draggable

Vue drag-and-drop component based on Sortable.js
https://sortablejs.github.io/Vue.Draggable/
MIT License
20.17k stars 2.9k forks source link

When using vuedraggable inside vuetify v-data-table two slots breaking the tbody-tr-td structure #1212

Open wcywin opened 1 year ago

wcywin commented 1 year ago

Github repo link:

https://github.com/wcywin/nuxt-3-vuetify-3-vuedraggable-v-data-table

Netlify link:

https://cerulean-tanuki-4c3a9e.netlify.app/

Step-by-step scenario

  1. When using Vuetify 3 and its v-data-table
  2. The draggable wrapper has to land inside the tbody slot of v-data-table
  3. the draggable itself has an additional slot of '#item'
  4. The table ends up heaving either 2x tbody or 2x tr and therefore is breaking the table structure of tbody-tr-td
  5. Therefore the UI breaks.

Actual Solution

  1. In Vuedraggable 2, the draggable component did not have a named slot inside, so only one tag in html appeared.
  2. In order to work in vuetify 3, we need to use the v-table component.
  3. Not using v-data-table we are losing its default functionality including headers and pagination.

Expected Solution

  1. The solution would be to allow the Draggable component to be a 'transparent' div, or its item slot to be a 'transparent' template, or to enable the item slot to render multiple children (as it shows an error of only 1 child to be rendered in the slot - but not accepting the template tag).
fabioselau077 commented 11 months ago

Same issue here... Any solutions?

You have example using v-table?

RajeevSiewnath commented 9 months ago

Yeah I managed to work around this fairly easy and with quite little effort

The issue is actually the double rendered tbody that comes from the <template #body={ items } /> slot. This was never an issue in Vuetify2 because the body slot allowed you to defined the tbody tag yourself as well. That would look something like this:

<v-data-table :headers="[]" :items="items">
  <template #body="{ items }">
    <draggable :list="[...items]" item-key="id" tag="tbody">
      <template #item="{ element: field }">
        <tr class="v-data-table__tr v-data-table__tr--clickable" @click="onRowClick(field)">
          <td class="v-data-table__td v-data-table-column--align-start">
            {{ item.whateverValue }}
          </td>
        </tr>
      </template>
    </draggable>
  </template>
</v-data-table>

This will render the tbody twice though and will mess up the table structure. But in vue3 when you add the following snippet in an onMounted callback of the component that renders the table it should look good and work all again.

onMounted(() => {
  const sortableTbody = document.querySelector('.v-table__wrapper > table > tbody > tbody')
  const tbody = document.querySelector('.v-table__wrapper > table > tbody')
  tbody!.parentNode!.append(sortableTbody!)
  tbody!.remove()
})

Keep in mind you have to take the whole draggable DOM node otherwise it won't of course work. That's why in the draggable component I have set the tag to tbody to exactly replace the tbody that is automatically placed there by Vuetify.

Small disclaimer: I have only used this in Vue3 not with Nuxt3, there might be some complications with the whole SSR part, but I'm sure you can get around that by executing the onMount part only client side.

Hope this helps!