Fork me on GitHub

HTML5 [dropzone]
<div dropzone="copy s:text/x-example">

Cross browser HTML5 drag and drop

Introduction

A small JavaScript library that provides an implementation of the proposed HTML5 dropzone attribute, and eases development of drag and drop the HTML5 way. Supports Chrome, Firefox, Safari and IE 10+.

Basic example

This sample is based on the example from the HTML5 spec. You can drag and drop fruits betweem the two lists. You can also use the platform keyboard modifiers to control whether elements are moved or copied between the lists, for example ctrl on windows will change from move to copy. Note that if you hit the link modifier (Alt on Windows) the drop is not allowed and the css classes are removed.

Drop your favorite fruits below, use ctrl on windows to control copy or move:

Implement ondragstart

To get this to work, 1st we need to setup a drag start handler to place some data into the DataTransfer object of the drag event, and we need a dragend handler which will fire when the user finishes dragging. This is just for example, typically you might prefer not to declare event handlers in markup :)

<ul ondragstart="dragStartHandler(event)" ondragend="dragEndHandler(event)">
  <li draggable="true" data-value="fruit-apple">Apples</li>
  <li draggable="true" data-value="fruit-orange">Oranges</li>
  <li draggable="true" data-value="fruit-pear">Pears</li>
</ul>

We provide some data keyed by the example type "text/x-example", we also specify that the user is allowed to move or copy the list items. We do the latter by specifying the effectAllowed. Note that if you tried to run this sample without the HTML5 dropzone library, it would not work on Internet Explorer at all.

var internalDNDType = 'text/x-example'; 
function dragStartHandler(event) {
  if (event.target instanceof HTMLLIElement) {
    // use the element's data-value="" attribute as the value to be moving:
    event.dataTransfer.setData(internalDNDType, event.target.dataset.value);
    // only allow the data to be copied or moved
    event.dataTransfer.effectAllowed = 'copyMove'; 
  } 
}

Define a [dropzone]

Now that the dragstart is set up, we need to specify a drop target. Typically you would need to do this by adding a bunch of event handlers to the node that can accept the drop, and cancelling the events if you decide that the element can take the drop given the data currently being dragged. With the dropzone attribute you can skip all of that.

<ol dropzone="move string:text/x-example"> </ol>

This dropzone specifies that the default operation is "move", and that we accept drags where the drag data kind is a string and the type is text/x-example. The operation can be specified by the user using platform specific keyboard modifiers, if none are specified the default operation will be "move". Finally define the drop and drag end handler, which reads the type that we set in dragstart and creates a new li element. The dragend handler uses the custom function getDropEffect() to test what drop effect the user selected. If the user did not complete the drop, or the used a keyboard modifier that specified linking then the drop is not allowed and the dropEffect is "none".

Handle drop and dragend

In the drop handler we examine the drag data item which is retrieved by type, and build a new list item based on it. On dragend we need to check whether the user specified a move or a copy, and remove the element from the source if appropriate.

      function dropHandler(event) {
var li = document.createElement('li'),
    target = event.target,
    data = event.dataTransfer.getData(internalDNDType);

  if (data == 'fruit-apple') {
    li.textContent = 'Apples';
  } else if (data == 'fruit-orange') {
    li.textContent = 'Oranges';
  } else if (data == 'fruit-pear') {
    li.textContent = 'Pears';
  } else {
    li.textContent = 'Unknown Fruit';
  }
        
  if (target instanceof HTMLLIElement) {
    target.parentElement.appendChild(li);
  } else {
    target.appendChild(li);
  }
}

function dragEndHandler(event) {
  // use the custom getDropEffect() method rather than the
  // native dropEffect property to get the correct value on IE
  var dropEffect = event.dataTransfer.getDropEffect();

  if (dropEffect === "move") {
    event.target.parentElement.removeChild(event.target);
  }
}

Using draggable

We can use the draggable function in place of the dragstart listener, this allows you to specify the effectAllowed and as many data types as you like, we can also hook up the dragend handler instead of defining the event handler in the markup.

    [].forEach.call(document.querySelectorAll(".sample1 li"), function(li) {
  draggable(li)
    .effectAllowed("copyMove")
    .setData("text/x-example", function() { return this.element.dataset.value; })
    .on("dragend", dragEndHandler);
});

document.querySelector(".sample1 [dropzone]").addEventListener("drop", dropHandler);

The effectAllowed function is called at drag start, you can supply either a string or a function that produces a string. The same is true of the setData functions 2nd parameter. Any function you supply is bound to an object where this.element is the thing passed to the draggable function.

Customising the drag ghost

The spec. provides a way to customise the drag image by using the setDragImage function on DataTransfer. Sadly this function does not exist at all on Internet Explorer and it can be quite clumsy to use elsewhere. Quite often we don't just want a simple clone of the node that is being dragged. The draggable object provides a way to provide a cross browser way (read also works on Internet Explorer) to customise the ghost image with the ghost() function. Here's a simple example, pick up the paper on the right to see a custom drag image.

I'm a bit of paper, pick me up and drag me !
No drag me !!!

The code to produce this is straighforward :

draggable("#paper")
  .setData("text/x-paper", "I'm a bit of paper")
  .ghost(customDragImage);

function customDragImage() {
    // clone the node being dragged (this.element)
    var clone = this.element.cloneNode(true);
    // give it a class - note that this should assume the clone is a child of the body
    clone.classList.add("clone");
    clone.innerHTML = "Thanks !";
    // return the node we want as the custom drag image
    return clone;   
}

As another more useful example, let's extend the fruits example from above so that users can select more than one fruit at a time. When a user drags any fruit, all selected fruits should be moved or copied. Without the ability to customise the drag image, it's very difficult to show the user what's getting dragged here.

Click on a fruit to select it.

Drop your favorite fruits below, use ctrl on windows to control copy or move:

To make this work we need to modify the setData function so that it encodes the values for all of the currently selected list items. We also need to provide a custom ghost element, this example clones all of the list items with the class "selected" and adds the to a list. Finally, the dragend function needs to remove all selected items rather than just the event target.

draggable(li)
  .effectAllowed("copyMove")
  .setData("text/x-example", function() { 
      return [].map.call(document.querySelectorAll("li.selected"), function(e) { 
        return e.innerHTML; 
      });
  })
  .ghost(function() {
      // custom ghost element function.
      var g = document.createElement("div"),
         ul = document.createElement("ul");

         g.appendChild(ul);
         g.classList.add("sample");
         g.classList.add("ghost");
         // clone all the selected elements and add them to our custom ghost element
         [].forEach.call(document.querySelectorAll(".li.selected"), function(item) {
           ul.appendChild(item.cloneNode(true));
         });

        return g;
  })
  .on("dragend", function(event) {
        var dropEffect = event.dataTransfer.getDropEffect();
        if (dropEffect === "move") {
           // remove all of the selected elements if the drop was a move
           [].forEach.call(document.querySelectorAll("li.selected"), function(item) {
             item.parentElement.removeChild(item);
           });
        }
  });

Working with Files and other applications

One of the most attractive features of HTML5 drag and drop (perhaps it's main one) is the ability to drag information from other applications or web pages into your web app, or drag from one web page to another, or drag from a web page and drop in a native application.

Working with Text

Drop text from another application (or this one) into the dropzone. The dropzone will look for text/plain or text/html represntations, so you can drag HTML fragments from other websites.

Working with files

Try the example here.