vue.jsvuejs2vuetify.jssortablejsvuedraggable

How do I make a two dimensional grid with Vue.Draggable?


I am using Vue2 with Vuetify and I have a two dimensional grid of buttons.

<v-row>
  <v-col cols="6" v-for="item in itemList" :key="item.id">
    <v-btn color="black--text">
      {{ item.text }}
    </v-btn>
  </v-col>
</v-row>

I want to use Vue.Draggable to enable drag-and-drop reordering. This is what I tried.

<v-row>
  <v-col cols="6" v-for="item in itemList" :key="item.id">
    <draggable v-model="itemList" group="myGroup">
      <v-btn color="black--text">
        {{ item.text }}
      </v-btn>
    </draggable>
  </v-col>
</v-row>

This does not work as I would like. For one thing, when I drag any button to a new spot, the first button in the list disappears. For another, the hole left from the moved button does not fill until after the move is complete, which makes it look awkward. Additionally, the transition effects just don't look right. I also tried moving the draggable components outside of the v-row, but that makes the entire grid one draggable unit. What I would like is something that resembles the grid example here: https://sortablejs.github.io/Sortable/#grid. The code is given for the example below that, but not the grid example and I can't seem to replicate it.

I have been struggling to find information about the Sortable or Draggable packages. Is there an official documentation somewhere that I'm missing? The wiki on GitHub just has four miscellaneous articles, two of which are about a decade old and the documentation directories only have deprecated functionality. That only leaves the readme on each repo. Is that all there is? I want to make sure there's not a more robust source to reference.


Solution

  • Currently, the draggable is inside the v-for, so a new instance is created for each element. Change it so the draggable wraps the element with the v-for, so you have one instance handling the whole list.

    Since draggable renders its own HTML tag, you can have it render the v-row using the tag prop (otherwise you'll have a div between v-row and v-col, which breaks the grid).

    This gives you something like:

      <draggable v-model="itemList" group="myGroup" tag="v-row">
        <v-col cols="6" v-for="item in itemList" :key="item.id">
          ...
        </v-col>
      </draggable>
    

    Have a look at it in the snippet:

    const colors = ['hotpink', 'cadetblue', 'magenta', 'yellow', 'green']
    
    new Vue({
      el: '#app',
      components: {
        draggable: vuedraggable
      },
      vuetify: new Vuetify(),
      data() {
        return {
          itemList: Array(5).fill().map((_,i) => ({id: i, text: 'Button '+i, color: colors[i%colors.length]})),
        }
      }
    })
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
    
    <div id="app">
      <v-app>
        <v-main>
          <v-container>
              <draggable tag="v-row" v-model="itemList" group="myGroup">
                <v-col cols="6" v-for="item in itemList" :key="item.id" :style="{backgroundColor: item.color}">
                  <v-btn color="black--text">
                    {{ item.text }}
                  </v-btn>
    
                </v-col>
              </draggable>
          </v-container>
        </v-main>
      </v-app>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
    
    <script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>