Callum Macrae

Making a sortable list in Vue

Last week, I wanted to represent an array as a reorderable list in Vue, where you could drag and drop the list elements and the array would be changed accordingly.

Here was my code:

<template>
  <ul>
    <li v-for="item in items">{{ item }}</li>    
  </ul>
</template>

<script>
  export default {
    data() {
      return {
        items: [1, 2, 3, 4]
      };
    } 
  };
</script>

I started off trying vue-sortable, but it didn't reorder the original array:

I dug into the source of vue-sortable to see if I could send a patch adding this functionality, and it turned out it wasn't that complicated a plugin. It basically just initiated Sortable to handle the sorting, which then handles the rest.

Writing my own directive seemed to be the answer. Adding a directive in Vue is really simple: you just call Vue.directive() with the name of your directive and an object containing lifecycle methods, and it gives you an HTML element to work on. The full documentation is here: Custom Directives - vue.js. Note that I'm using Vue 1.0, but it shouldn't be too tricky to adapt this for Vue 2.

To start with, we just register our component and get it to initiate Sortable, same as vue-sortable does:

import Vue from 'vue';
import Sortable from 'sortablejs';

Vue.directive('sortable', {
  update(options = {}) {
    Sortable.create(this.el, options);
  },
});

Once the directive has been added, we can call it the same way vue-sortable is called:

<ul v-sortable>
  <li v-for="item in items">{{ item }}</li>
</ul>

That seems simple enough, but only achieves what we had previously: in order to reorder the array, we're going to need to pass in an onUpdate function to Sortable, and accept the original array the list was generated from as a param, otherwise we can't access it.

import Vue from 'vue';
import Sortable from 'sortablejs';

Vue.directive('sortable', {
  params: ['sorting'],
  update(options = {}) {
    const sorting = this.params.sorting;
    if (sorting) {
      options.onUpdate = function sortableUpdate(e) {
        const deleted = sorting.splice(e.oldIndex, 1);
        sorting.splice(e.newIndex, 0, deleted[0]);
      };
    }

    Sortable.create(this.el, options);
  },
});

We're calling splice twice: the first time to remove the item from the array, and the second time to add the remove element back into the array in its new position.

Now we pass in items as a param, and now reordering the list items also reorders the array:

<ul v-sortable :sorting="items">
  <li v-for="item in items">{{ item }}</li>
</ul>

Success!

« Return to home