2
\$\begingroup\$

I've come up with the following code to handle drag and drop operation. It's composed of a constructor function that returns an objects with methods to attach to event handlers.

Notes about this solution:

  • It's client responsibility to attach methods to event handlers;
  • specific CSS classes should be added to DOM elements;
  • class names follows BEM (block-element-modifier) convention, which I just learned;
  • It's intended to work with <LI> elements but it should work with other kinds;
  • Client must insert drop zones between draggable items;
  • Client is intended to handle move/copy operations;
  • It is assumed to run on modern browsers.

Comments requested to you:

  • Should I try to make the code more packaged and reusable?
  • I believe in onDrop method lies the more brittle code. How can I do better? For instance, if I intend to clone elements I must know element kind in advance.
  • I think of doing a state machine to reflect events timing/ordering but I'm not sure where to start.
  • It's common to make an object return event handlers like this, instead of attach to events itself? What I like about this is that I'm able to envolve them in decorators (which I've done and plan to post in a another thread).
  • here we have a constructor function but I return a new object inside of it. Should it be a normal function or should I stick to a constructor?

HTML sample

<ul class="dragAndDrop">
  <li class="dragAndDrop__drop-zone"></li>
  <li class="dragAndDrop__drag-source" data-id="1" draggable="true"></li>
  <li class="dragAndDrop__drop-zone"></li>
  <li class="dragAndDrop__drag-source" data-id="2" draggable="true"></li>
  <li class="dragAndDrop__drop-zone"></li>
</ul>

DragAndDrop.js

function DragAndDrop() {
  'use strict';

  /* all strings used are defined here */

  var props = {
    data: {
      id: 'data-id'
    },
    effects: {
      move: 'move'
    },
    mimeTypes: {
      xModel: 'application/x-model'
    },
    css: {
      dragAndDrop__dropZone: 'dragAndDrop__drop-zone',
      dragAndDrop__dropZoneDropHere: 'dragAndDrop__drop-zone--drop-here',
      dragAndDropActive: 'dragAndDrop--active'
    }
  };

  /* utility functions */

  function getContainer(element) {
    return Dom.findAncestor(element, 'dragAndDrop');
  }

  /* events */

  function onDragStart(event) {
    if (!event.target.attributes.draggable) return;

    var parent = getContainer(event.target);
    parent.classList.add(props.css.dragAndDropActive);

    var id = event.target.attributes[props.data.id].value;
    event.dataTransfer.effectAllowed = props.effects.move;
    event.dataTransfer.setData(props.mimeTypes.xModel, id);
  }

  function onDragOver(event) {
    if (event.target.classList.contains(props.css.dragAndDrop__dropZone) &&
      event.dataTransfer.types.some(function(item) {
        return item === props.mimeTypes.xModel;
      })) {
      event.target.classList.add(props.css.dragAndDrop__dropZoneDropHere);

      event.preventDefault();
      return false;
    }
  }

  function onDragLeave(event) {
    event.target.classList.remove(props.css.dragAndDrop__dropZoneDropHere);
  }

  function onDragEnd(event) {
    var parent = getContainer(event.target);
    parent.classList.remove(props.css.dragAndDropActive);
  }

  function onDrop(event) {
    var id = event.dataTransfer.getData(props.mimeTypes.xModel);
    event.target.classList.remove(props.css.dragAndDrop__dropZoneDropHere);

    event.target.insertAdjacentHTML('afterend', '<li class="dragAndDrop__drag-source" data-id="' + id + '" draggable="true">dropped </li><li class="dragAndDrop__drop-zone"></li>');

    event.preventDefault();
    return false;
  }

  return {
    onDragStart: onDragStart,
    onDragOver: onDragOver,
    onDragLeave: onDragLeave,
    onDragEnd: onDragEnd,
    onDrop: onDrop
  };
}

Usage:

function Program() {
  function run() {
    var demo = new DragAndDrop();

    document.addEventListener('dragstart', demo.onDragStart);
    document.addEventListener('dragover', demo.onDragOver);
    document.addEventListener('dragleave', demo.onDragLeave);
    document.addEventListener('dragend', demo.onDragEnd);
    document.addEventListener('drop', demo.onDrop);
  }

  this.run = run;
}

var main = new Program();
main.run();
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.