DEV Community

loading...

Draggable table row with VueJS, Vuetify and SortableJS

Andrey Gaisinskii
Front-end developer with a passion for creativity and simplicity in his work. Mostly post about VueJS/NuxtJS related problems or tips.
・2 min read

TLDR At the bottom of the article you will find a link to the GitHub repo.

Prologue

Recently I have had to implement draggable rows for a vuetify table and right off the bat started searching in the docs and the webs for a solution. And came across this article written by Abhay Wawale and some other answers on StackOverflow & CodePen.

The article is great, but I was concerned about manipulating the DOM directly. As for me it is a bit hacky.

My Solution

Instead of accessing the item slot in v-data-table I decided to mess around with the body slot. Basically body slot is your <tbody> html tag and it is very nice since we need a tag prop for vuedraggable component which is a wrapper for SortableJS.

    <v-data-table>
      <template v-slot:body="props">
        <draggable
          :list="props.items"
          tag="tbody"
        >
          <!-- the row will go here -->
        </draggable>
      </template>
    </v-data-table>
Enter fullscreen mode Exit fullscreen mode

Now to the rows, you just have to loop through the array of items that you pass into your v-data-table items prop. Those items will be exposed in the body slot and can be accessed as follows:

          <tr
            v-for="(item, index) in props.items"
            :key="index"
          >
            <td> {{ item.your-property-here }} </td>
          </tr>
Enter fullscreen mode Exit fullscreen mode

Let's add some actual data and make our table more fancy:

    <v-data-table
      :headers="tableHeaders"
      :items="tableItems"
      :loading="loading"
      item-key="id"
      :show-select="false"
      :disable-pagination="true"
      :hide-default-footer="true"
      class="page__table"
    >
      <template v-slot:body="props">
        <draggable
          :list="props.items"
          tag="tbody"
        >
          <tr
            v-for="(user, index) in props.items"
            :key="index"
          >
            <td>
              <v-icon
                small
                class="page__grab-icon"
              >
                mdi-arrow-all
              </v-icon>
            </td>
            <td> {{ index + 1 }} </td>
            <td> {{ user.id }} </td>
            <td> {{ user.name }} </td>
            <td> {{ user.username }} </td>
            <td> {{ user.email }} </td>
            <td> {{ user.website }} </td>
            <td>
              <v-icon
                small
                @click="editUser(user.id)"
              >
                mdi-pencil
              </v-icon>
            </td>
          </tr>
        </draggable>
      </template>
    </v-data-table>
Enter fullscreen mode Exit fullscreen mode

Our table will look like this and draggable rows are working!

Our table!

Thanks for reading!

Code can be found here and live preview here

Discussion (8)

Collapse
lambertbeekhuis profile image
Lambert Beekhuis

Very interesting post. One question though: how to handle a 'drop'? What is triggered when you actually 'drop' something. If I make the list in a computed property with a getter and setter, no change is detected as the :list option using splice is used. When using the draggable :value option, it is not working...

So, how to handle it 'after the drop'?

Collapse
lambertbeekhuis profile image
Lambert Beekhuis

A solution that works for me is adding the event-handler
@end="endDrag(props.items)"
in the element

Thanks, great!

Collapse
eugenman profile image
Eugen

For example:
@end ="isDragging = false"

Collapse
appurist profile image
Paul / Appurist

This post just saved my bacon on an important project. I had been back and forth on many alternatives, none of which was the combination needed, until I saw this article. Having the full source code example, especially PageTable.vue, along with your working example was instrumental and so extremely helpful. Very much appreciated that you took the time to document this solution here.

Collapse
andynoir profile image
Andrey Gaisinskii Author

I'm so glad that I could help you. Thanks for kind words!

Collapse
juretopolak profile image
juretopolak

Shouldn't be data reactive with that I mean that the order of data should also change in the array?
This is the case with vuedraggable when using div or ul/li tags, but is not with this example using v-data-table.

dev-to-uploads.s3.amazonaws.com/i/...

Collapse
andynoir profile image
Andrey Gaisinskii Author

I'm not an expert in Vue/VueDevTools/Vuetify nor VueDraggable, but my question is: Why should it be reactive ? Because what we are doing is that we are using a wrapper library of SortableJS inside vuetify table component, there are no direct binding between vue/vuetify table component and VueDraggable library. We pass our array or items into vuetify table and then VueDraggable does its magic. If you want to do something upon table change you can use update event of VueDraggable, something like this:

   <v-data-table
      :headers="tableHeaders"
      :items="homePage.coupons"
      :loading="loading"
      item-key="id"
      :show-select="false"
      :disable-pagination="true"
      :hide-default-footer="true"
      class="page__table"
    >
      <template v-slot:body="props">
        <draggable
          :list="props.items"
          :handle="'.handle'"
          tag="tbody"
          @update="handleTableUpdate(props.items)"
        >
          <!-- other html here -->
        </draggable>
      </template>
    </v-data-table>
Enter fullscreen mode Exit fullscreen mode
Collapse
jonmalave profile image
Jon Malave

Thanks so much! This was extremely helpful! I didn't think Vue Draggable was possible on Vuetify's Table Component until I found this.