How to Build Drag and Drop blocks using Vue-js

On my last project I was asked to build an interface where people could create and manage a travel plan, adding places to visit (articles in the website) and combining them with a date block and a text block. The interface also allows to sort the blocks in order to arrange them at the user will.

In the past, I would address the drag and drop UI effect with the jQuery UI library. Although it was possible to handle everything with jQuery UI and jQuery itself, the code got messy almost all the times.

But, because I currently handle all my UI needs with Vue.js, jQuery is not an option anymore.

Approaching the project moment where I would start to address the drag and drop Interaction with Vue.js, I searched for already made Vue.js components that would allow me to finish this job quickly. As always there are a lot of ready to use plugins and components, most of them based on other JavaScript libraries that implement the HTML5 drag and drop API.

One of them that capture my attention was the Shopify’s Draggable library. It looks very solid and complete. I found this article in Medium where Ali Kamalizade was creating a custom directive to integrate the Draggable library in the Vue.js environment.

It looked so simple that I preferred to skip the ready to use approach and jump into building my own directive v-sortable. I also decided to follow this approach as I would need to sync the DOM manipulation done in the Vue.js component with the Vuex store, where the blocks list state was stored.

Instead of using the Draggable library I decided to use the Sortable library which seems to be more actively maintained.

Sortable — is a JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery required. Supports Meteor, AngularJS, React, Polymer, Vue, Ember, Knockout and any CSS library, e.g. Bootstrap.

It all starts with installing the library into your project:

$ npm install sortablejs --save

My sortable Vue directive

Vue already comes with a default set of directives like v-model and v-for, but it allows you to create your own custom directives. This is special because it makes it possible to completely abstract the js library used to do the drag&drop heavy lifting from our own project code. For example, I started the implementation with the Shopify’s Draggable lib and in the end, I replaced by SortableJs, by just tweaking the custom directive. All the other code on my componenents and store remained untouched.

So, the goal here is to write some code that allows the use of the v-sortable="options" directive on my own component and let the drag&drop magic happens.

import Sortable from 'sortablejs/modular/sortable.core.esm.js';
const SortableDirective = {
    inserted( el, binding, vnode ) {
        let options = binding.value;
        options.onUpdate = (e) => vnode.data.on.sorted( e );
        const sortable = Sortable.create( el, binding.value );
    }
};
export default SortableDirective;

This custom directive accepts an options object which contains the options to configure the sortable library. I’m also hooking on the onUpdate event and sending it to the component so it would be possible to trigger the change on my Vuex store, thus keeping the DOM and the store in sync. This is important so that the user could save the travel plan after reordering the blocks at his own will.

This is image title

Component usage

Now that we have a directive, this is how I’m using it on my project component’s template:

<template>

...

<div v-sortable="$options.sortOptions" @sorted="handleSorted">
    <component v-for="item in blocks" :key="item.uid" :block="item" :is="blockType(item)"></component>
</div>

...

</template>

and the script:

<script>
...

import Sortable from '../directives/sortable';

const sortOptions = {
    draggable: ‘.js-sortable-block’,
    handle: ‘.js-drag-handle’
};

export default {
    sortOptions,
    directives: { Sortable },
    methods: {
        handleSorted( event ) { 
            this.$store.commit('sortBlocks', event );
        }
    },
    computed: {
       blocks() {
           return this.$store.state.blocks;
       }
    }
}

</script>

At this point the blocks wrapped by div.js-sortable-block are sortable. The user is able to grab each block by the handle .js-drag-handle and move it around the list.

The last piece of code we need is to mutate the state of the blocks on the Vuex store. After each user’s drag and drop interaction, the component gets the event sorted which triggers the component’s method handleSorted which commits a mutation sortBlocks on my Vuex store:

state: {
    blocks: [],
},
mutations: { 
    sortBlocks( state, e ) {
         if( e.oldIndex === e.newIndex ) {
             return;
         }
         let temp = state.blocks.splice( e.oldIndex, 1 );
         state.blocks.splice( e.newIndex, 0, temp[0] );
    }
}

The mutation changes the blocks indexes so it keeps the interaction in sync with the blocks state.

This is how I manage to integrate a ready-to-use Sortable JavaScript library with my Vue project. The custom directive v-sortable abstracts the complexity of the library from my own project and makes it possible to reuse it on other projects without having to go deep into the components code and look for the specifics.

I think you agree that we live in good times, where something like a drag & drop interaction is addressed easily.

Thank you for reading!

#vue-js #vue #javascript

How to Build Drag and Drop blocks using Vue-js
17.05 GEEK