The HTML5 Drag and Drop API

Ed’s Note: This is an extract from the book Professional JavaScript for Web Developers, 3rd Edition by Nicholas C Zakas ©2012 John Wiley & Sons. For Source Code, Sample Chapters, the Author Forum and other resources, go to the book’s homepage on wrox.com.

Professional JavaScript for Web Developers, 3rd Edition Book Cover

Internet Explorer 4 first introduced JavaScript support for drag-and-drop functionality for web pages. At the time, only two items on a web page could initiate a system drag: an image or some text. When dragging an image, you simply held the mouse button down and then moved it; with text, you first highlighted some text and then you could drag it the same way as you would drag an image. In Internet Explorer 4, the only valid drop target was a text box. In version 5, Internet Explorer extended its drag-and-drop capabilities by adding new events and allowing nearly anything on a web page to become a drop target. Version 5.5 went a little bit further by allowing nearly anything to become draggable. (Internet Explorer 6 supports this functionality as well.) HTML5 uses the Internet Explorer drag-and-drop implementation as the basis for its drag-and-drop specification. Firefox 3.5, Safari 3+, and Chrome have also implemented native drag and drop according to the HTML5 spec.

Perhaps the most interesting thing about drag-and-drop support is that elements can be dragged across frames, browser windows, and sometimes, other applications. Drag-and-drop support in the browser allows you to tap into that functionality.

Drag-and-Drop Events

The events provided for drag and drop enable you to control nearly every aspect of a drag-and-drop operation. The tricky part is determining where each event is fired: some fire on the dragged item; others fire on the drop target. When an item is dragged, the following events fire (in this order):

  1. dragstart
  2. drag
  3. dragend

At the moment you hold a mouse button down and begin to move the mouse, the dragstart event fires on the item that is being dragged. The cursor changes to the no-drop symbol (a circle with a line through it), indicating that the item cannot be dropped on itself. You can use the ondragstart event handler to run JavaScript code as the dragging begins.

After the dragstart event fires, the drag event fires and continues firing as long as the object is being dragged. This is similar to mousemove, which also fires repeatedly as the mouse is moved. When the dragging stops (because you drop the item onto either a valid or an invalid drop target), the dragend event fires.

The target of all three events is the element that is being dragged. By default, the browser does not change the appearance of the dragged element while a drag is happening, so it’s up to you to change the appearance. Most browsers do, however, create a semitransparent clone of the element being dragged that always stays immediately under the cursor.

When an item is dragged over a valid drop target, the following sequence of events occurs:

  1. dragenter
  2. dragover
  3. dragleave or drop

The dragenter event (similar to the mouseover event) fires as soon as the item is dragged over the drop target. Immediately after the dragenter event fires, the dragover event fires and continues to fire as the item is being dragged within the boundaries of the drop target. When the item is dragged outside of the drop target, dragover stops firing and the dragleave event is fired (similar to mouseout). If the dragged item is actually dropped on the target, the drop event fires instead of dragleave. The target of these events is the drop target element.

Custom Drop Targets

When you try to drag something over an invalid drop target, you see a special cursor (a circle with a line through it) indicating that you cannot drop. Even though all elements support the drop target events, the default is to not allow dropping. If you drag an element over something that doesn’t allow a drop, the drop event will never fire regardless of the user action. However, you can turn any element into a valid drop target by overriding the default behavior of both the dragenter and the dragover events. For example, if you have a <div> element with an ID of "droptarget", you can use the following code to turn it into a drop target:

var droptarget = document.getElementById("droptarget");

EventUtil.addHandler(droptarget, "dragover", function(event){
    EventUtil.preventDefault(event);
});

EventUtil.addHandler(droptarget, "dragenter", function(event){
    EventUtil.preventDefault(event);
});

After making these changes, you’ll note that the cursor now indicates that a drop is allowed over the drop target when dragging an element. Also, the drop event will fire.

In Firefox 3.5+, the default behavior for a drop event is to navigate to the URL that was dropped on the drop target. That means dropping an image onto the drop target will result in the page navigating to the image file, and text that is dropped on the drop target results in an invalid URL error. For Firefox support, you must also cancel the default behavior of the drop event to prevent this navigation from happening:

EventUtil.addHandler(droptarget, "drop", function(event){
    EventUtil.preventDefault(event);
});

The dataTransfer object

Simply dragging and dropping isn’t of any use unless data is actually being affected. To aid in the transmission of data via a drag-and-drop operation, Internet Explorer 5 introduced the dataTransfer object, which exists as a property of event and is used to transfer string data from the dragged item to the drop target. Because it is a property of event, the dataTransfer object doesn’t exist except within the scope of an event handler for a drag-and-drop event. Within an event handler, you can use the object’s properties and methods to work with your drag-and-drop functionality. the dataTransfer object is now part of the working draft of HTML5.

The dataTransfer object has two primary methods: getData() and setData(). As you might expect, getData() is capable of retrieving a value stored by setData(). The first argument for setData(), and the only argument of getData(), is a string indicating the type of data being set: either "text" or "URL", as shown here:

//working with text
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");

//working with a URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

Even though Internet Explorer started out by introducing only "text" and "URL" as valid data types, HTML5 extends this to allow any MIME type to be specified. The values "text" and "URL" will be supported by HTML5 for backwards compatibility, but they are mapped to "text/plain" and "text/uri-list".

The dataTransfer object can contain exactly one value of each MIME type, meaning that you can store both text and a URL at the same time without overwriting either. The data stored in the dataTransfer object is available only until the drop event. If you do not retrieve the data in the ondrop event handler, the dataTransfer object is destroyed and the data is lost.

When you drag text from a text box, the browser calls setData() and stores the dragged text in the "text" format. Likewise, when a link or image is dragged, setData() is called and the URL is stored. It is possible to retrieve these values when the data is dropped on a target by using getData(). You can also call setData() manually during the dragstart event to store custom data that you may want to retrieve later.

There is a difference between data treated as text and data treated as a URL. When you specify data to be stored as text, it gets no special treatment whatsoever. When you specify data to be stored as a URL, however, it is treated just like a link on a web page, meaning that if you drop it onto another browser window, the browser will navigate to that URL.

Firefox through version 5 doesn’t properly alias "URL" to "text/uri-list" or "text" to "text/plain". It does, however, alias "Text" (uppercase T) to "text/plain". For best cross-browser compatibility of retrieving data from dataTransfer, you’ll need to check for two values for URLs and use "text" for plain text:

var dataTransfer = event.dataTransfer;

//read a URL
var url = dataTransfer.getData("url") ||dataTransfer.getData("text/uri-list");

//read text
var text = dataTransfer.getData("Text");

It’s important that the shortened data name be tried first, because Internet Explorer through version 10 doesn’t support the extended names and also throws an error when the data name isn’t recognized.

dropEffect and effectAllowed

The dataTransfer object can be used to do more than simply transport data to and fro; it can also be used to determine what type of actions can be done with the dragged item and the drop target. You accomplish this by using two properties: dropEffect and effectAllowed.

The dropEffect property is used to tell the browser which type of drop behaviors are allowed. This property has the following four possible values:

  • "none" — A dragged item cannot be dropped here. This is the default value for everything except text boxes.
  • "move" — The dragged item should be moved to the drop target.
  • "copy" — The dragged item should be copied to the drop target.
  • "link" — Indicates that the drop target will navigate to the dragged item (but only if it is a URL).

Each of these values causes a different cursor to be displayed when an item is dragged over the drop target. It is up to you, however, to actually cause the actions indicated by the cursor. In other words, nothing is automatically moved, copied, or linked without your direct intervention. The only thing you get for free is the cursor change. In order to use the dropEffect property, you must set it in the ondragenter event handler for the drop target.

The dropEffect property is useless, unless you also set the effectAllowed. This property indicates which dropEffect is allowed for the dragged item. The possible values are as follows:

  • "uninitialized" — No action has been set for the dragged item.
  • "none" — No action is allowed on the dragged item.
  • "copy" — Only "copy" is allowed.
  • "link" — Only "link" is allowed.
  • "move" — Only "move" is allowed.
  • "copyLink" — "copy" and "link" are allowed.
  • "copyMove" — "copy" and "move" are allowed.
  • "linkMove" — "link" and "move" are allowed.
  • "all" — All values are allowed.

This property must be set inside the ondragstart event handler.

Suppose that you want to allow a user to move text from a text box into a <div>. To accomplish this, you must set both dropEffect and effectAllowed to "move". The text won’t automatically move itself, because the default behavior for the drop event on a <div> is to do nothing. If you override the default behavior, the text is automatically removed from the text box. It is then up to you to insert it into the <div> to finish the action. If you were to change dropEffect and effectAllowed to "copy", the text in the text box would not automatically be removed.

Note: Firefox through version 5 has an issue with effectAllowed where the drop event may not fire when this value is set in code.

Draggability

By default, images, links, and text are draggable, meaning that no additional code is necessary to allow a user to start dragging them. Text is draggable only after a section has been highlighted, while images and links may be dragged at any point in time.

It is possible to make other elements draggable. HTML5 specifies a draggable property on all HTML elements indicating if the element can be dragged. Images and links have draggable automatically set to true, whereas everything else has a default value of false. This property can be set in order to allow other elements to be draggable or to ensure that an image or link won’t be draggable. For example:

<!-- turn off dragging for this image -->
<img src="smile.gif" draggable="false" alt="Smiley face">

<!-- turn on dragging for this element -->
<div draggable="true">...</div>

The draggable attribute is supported in Internet Explorer 10+, Firefox 4+, Safari 5+, and Chrome. Opera, as of version 11.5, does not support HTML5 drag and drop. In order for Firefox to initiate the drag, you must also add an ondragstart event handler that sets some information on the dataTransfer object.

Note: Internet Explorer 9 and earlier allow you to make any element draggable by calling the dragDrop() method on it during the mousedown event. Safari 4 and earlier required the addition of a CSS style -khtml-user-drag: element to make an element draggable.

Additional Members

The HTML5 specification indicates the following additional methods on the dataTransfer object:

  • addElement(element) — Adds an element to the drag operation. This is purely for data purposes and doesn’t affect the appearance of the drag operation. As of the time of this writing, no browsers have implemented this method.
  • clearData(format) — Clears the data being stored with the particular format. This has been implemented in Internet Explorer, Firefox 3.5+, Chrome, and Safari 4+.
  • setDragImage(element, x, y) — Allows you to specify an image to be displayed under the cursor as the drag takes place. This method accepts three arguments: an HTML element to display and the x- and y-coordinates on the image where the cursor should be positioned. The HTML element may be an image, in which case the image is displayed, or any other element, in which case a rendering of the element is displayed. Firefox 3.5+, Safari 4+, and Chrome all support this method.
  • types — A list of data types currently being stored. This collection acts like an array and stores the data types as strings such as "text". Internet Explorer 10+, Firefox 3.5+, and Chrome implemented this property.

You might also like...

Comments

About the author

Dan Maharry

Dan Maharry United Kingdom

Dan Maharry is the editor of DeveloperFusion. He has covered the latest in software development since the mid 90s. Not wishing to preach what he doesn't practice, Dan has also worked as a profes...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“XML is like violence - if it's not working for you, you're not using enough of it.”