Pages

Thursday, August 4, 2016

Learning Meteor.js [Part 43]: Drag and drop support with interact.js and dragula.js

On a previous post I went over using interact.js to add drag and drop support when building a lunch order. Turns out something very similar is needed to build a recipe from ingredients. Luckily the ingredients list part can be reused (which supports dragging ingredient items) and all that remains is to hook up the drop target:
import interact from 'interact.js';

Template.recipeEditPart.onRendered(function() {
    var vm = this.viewModel;
    ko.applyBindings(vm, this.find('.recipeEditPart'));

    interact('.ingredients-content').dropzone({
        ondrop: function(event) {
            var ingredientId = $(event.relatedTarget).data('id');
            vm.addIngredient(ingredientId);
        }
    });
});

The UI would then render the list of ingredients from the observableArray, and will look like this:

Great. Now we need to allow the user to reorder the list items by using the little 'grab' icon on the left. I am sure it can be done with interact.js, but it is nowhere near as simple as dragula.js. Interact.js offers low level constructs to build pretty much anything with mouse and touch interactions, but it is harder to configure. Dragula.js is perhaps less powerful but it is very simple to use:
import dragula from 'dragula';

Template.recipeEditPart.onRendered(function() {
    ko.applyBindings(this.viewModel, this.find('.recipeEditPart'));

    setupSortableList($('.ingredients-list')[0], this.viewModel.ingredients);
});

function setupSortableList(htmlList, observableArray) {
    var drake = dragula([htmlList], {
        moves: function(el, container, handle) {
            return !!$(handle).parents('.list-item-handle').length;
        }
    }).on('drop', function (el) {
        var item = ko.dataFor(el);

        observableArray.remove(item);
        observableArray.splice($(el).index(), 0, item);

        drake.remove();
    });
}

Let's go over what is happening here:

  1. When meteor finishes rendering the template on screen it invokes the 'onCreated' method.
  2. Dragula is called with the html element whose child elements are draggable. In this case the ingredients list.
  3. An options object is passed with a 'moves' function. This is used to restrict the 'grab' icon as the only element that can initiate a drag (as opposed to the while list item).
  4. Hook up to the on 'drop' event, at this point we use knockout's ko.dataFor() to retrieve the view model associated with the list item. The information is used to modify the observable array of ingredients.
  5. A call is made to dragula to remove() the element that is being dragged. Since the UI will re-render based on the observable changes to the collection the dragged element is no longer needed and can be discarded from the DOM.

You can see the screen capture below for a sample of how the experience looks like. You can also visit the live demo or view the soure code.


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.