How to implement the Vue.draggable to be swappable

@tookapic

Vue.draggable is very useful UI component. It is a nice tool to create draggable items and represents nice sort animations. But sometimes you might want to use a swappable component instead of sortable one. If so this article will help you perhaps.

Why do I need to swap instead of sorting?

I want to store the sorted date persistence. I will store it to the database, but the data structure is not I wanted. the sort methods move several array index. for example, if I drag the first index element to last index element. The result is the following.

// sort
Before: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
After:  [2, 3, 4, 5, 6, 7, 8, 9, 10, 1]

Otherwise, my expected result is the following.

// swap
Before: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
After:  [10, 2, 3, 4, 5, 6, 7, 8, 9, 1]

Maybe, the expected result depends on the database structure, so I recommend to consult with server-side engineers if you will use Vue.draggable and store the order to your database.

Vue.draggable doesn't have swap feature

One day, some guy created an issue to ask the maintainer to add swap function to Vue.draggable. But the answer was NO.

swap feature #466

Actually, Sortable.js has swap plugin but current Vue.draggable version v2.21.0 doesn't have a swap feature in fact.

Hack the Vue.draggable

Anyway, I implemented an admin tool with Vue.draggable to swap the item orders, but I notice it reordered items and the data structure was not expected by a server-side engineer. So I hacked the component as the following codes.

<template>
  <table>
    <draggable v-model="items" tag="tbody" :move="handleMove" @end="handleDragEnd" :options="{animation:500}">
      <tr class="movable" v-for="item in items" :key="item.id">
        <td>{{ item.id }}</td><td>{{ item.name }}</td><td>{{ item.age }}</td>
      </tr>
    </draggable>
  </table>
</template>

<script>
import draggable from "vuedraggable";

export default {
  components: {
    draggable,
  },
  data() {
    const items = [
      { id: 1, name: "Bianka Effertz", age: 37 },
      { id: 2, name: "Mckayla Bogan", age: 20 },
      { id: 3, name: "Estevan Mann", age: 17 },
      { id: 4, name: "Cloyd Ziemann", age: 55 }
    ]
    return { items }
  },

  methods: {
    handleDragEnd() {
      this.$toast.show('dragEnd')

      this.futureItem = this.items[this.futureIndex]
      this.movingItem = this.items[this.movingIndex]
      const _items = Object.assign([], this.items)
      _items[this.futureIndex] = this.movingItem
      _items[this.movingIndex] = this.futureItem

      this.items = _items
    },
    handleMove(e) {
      const { index, futureIndex } = e.draggedContext
      this.movingIndex = index
      this.futureIndex = futureIndex
      return false // disable sort
    }
}
</script>

demo is here

Let describe the codes

Vue.draggable has move property through the argument path to Sortable.js. It has move event fired when the item is moved. so we can pass the handler function for the event. And the event is required to return false, -1 or 1. It means as the following.

  • return false; — for cancel
  • return -1; — insert before target
  • return 1; — insert after target

So I change return false every the onMove event fired, as a result, the sorting function is disabled but I can catch the dragged event context contains target index and future Index. Now we have known to requirement data, so we can implement the Vue.draggable to be swappable.

recap

By some chance, we have more smart methods Vue.draggable to be swappable. But It's the one way If you can't find the solution.