Along with an army of JavaScript APIs, HTML 5 comes with a Drag and Drop (DnD) API that brings native DnD support to the browser making it much easier to code up.
HTML 5 DnD is based on Microsoft’s original implementation which was available as early as Internet Explorer 5! Now currently supported in IE, Firefox 3.5 and Safari 4.
We’re going to look how to implement DnD and review some of the issues (or hoops we have to jump through) with the current implementations now.
My First Drag and Drop
DnD requires only a few things to work:
Something to drag
A drop target
JavaScript event handlers on the target to tell the browser it can drop
The following elements are draggable by default: elements and elements (with an href).
If you want to drag a different element, you need to set the draggable attribute to true. That’s according to the spec, but to get it to work in Safari and Firefox there’s a little more that you need to do, which we’ll come on to.
If you want to skip the code walk through, here’s the simple drag and drop (source code)
Here’s our example markup:
Basic Drag and Drop#drop {
min-height: 100px;
width: 200px;
border: 3px dashed #ccc;
margin: 10px;
padding: 10px;
}
p {
margin: 3px 0;
}
Note: the included h5utils.js script is a small library to trigger HTML 5 elements and to give me cross browser event binding.
Currently the elements can be dragged, but they can’t be dropped (this is the browser’s default).
To drop elements we need to:
Tell the browser that the element can be dragged over a specific point
Handle the drop event
To tell the browser we can drop in this element, all we have to do is cancel the dragover event. However, since IE behaves differently, we need to do the same thing for the dragenter event.
Here’s the code for basic drop handling:
var drop = document.querySelector('#drop');
// Tells the browser that we *can* drop on this target
addEvent(drop, 'dragover', cancel);
addEvent(drop, 'dragenter', cancel);
addEvent(drop, 'drop', function (event) {
// stops the browser from redirecting off to the text.
if (event.preventDefault) {
event.preventDefault();
}
this.innerHTML += '
' + event.dataTransfer.getData('Text') + '
';return false;
});
function cancel(event) {
if (event.preventDefault) {
event.preventDefault();
}
return false;
}
Note for this code, I’m using addEvent where you’d normally see element.addEventListener, it’s from the h5utils.js library to support IE.
In the JavaScript we are:
Searching for the drop target in the DOM using document.querySelector (which returns the first result only)
When dragover event is fired (when the user drags the element over another), it will trigger the function called ‘cancel’ (at the bottom of the code above) which will prevent the default browser action
Do the same for dragenter to support IE
Bind the drop event, and within there grab some data about what was dropped.
dragover & dragenter
By cancelling this event, we’re telling the browser this element that we’re over is the one you can release and drop upon.
I’m still not entirely sure why there’s a difference here, but Firefox and friends needs preventDefault on the event, and IE requires the return false. Note that the examples out in the wild that use inline JavaScript (yuk, bad), the preventDefault is supposed to be implicit, so you won’t see it in the code.
drop
To show what was dropped, we need to do two things:
Cancel the default browser action. This is to ensure that if we drop a link, the browser doesn’t go off and surf to that location
Get the contents out of the dataTransfer object
The cancelling of the browser default is, again, done using the preventDefault and return false.
If we don’t set the dataTransfer data manually (which I’ll show you later), the default key is set to Text. So this might be the value of an href of an element, or it might be src address.
Doing More with Drag’n Drop
There’s a number of extra bits in the spec other than just dragging images.
Here’s a few bits that I can see being useful:
Being able to drag any element
More complex data types, other than text
Set a different drag icon/image when dragging the element
Dragging Anything
If we change the to
The HTML 5 spec says it should be as simple as adding the following attributes to the markup of the elements in question:
draggable="true"
However, this doesn’t work completely for Safari or Firefox.
For Safari you need to add the following style to the element:
[draggable=true] {
-khtml-user-drag: element;
}
This will start working in Safari, and as you drag it will set a default, empty value with the dataTransfer object. However, Firefox won’t allow you to drag the element unless you manually set some data to go with it.
To solve this, we need a dragstart event handler, and we’ll give it some data to be dragged around with:
var dragItems = document.querySelectorAll('[draggable=true]');
for (var i = 0; i < dragItems.length; i++) {
addEvent(dragItems[i], 'dragstart', function (event) {
// store the ID of the element, and collect it on the drop later on
event.dataTransfer.setData('Text', this.id);
});
}
More Complex Data Types
You’ve already seen how we can use dataTransfer.setData(format, string) to associate some data in the example above.
You can also use this key/value pair to store more data, but you’re limited to storing strings only.
One way I would get around this is to have a data lookup, where the key of the lookup may be the ID of the element, and then I can de-reference the data on the drop event.
For example:
var people = {
rem : {
name : "Remy Sharp",
blog : "http://remysharp.com"
},
brucel : {
name : "Bruce Lawson",
blog : "http://brucelawson.co.uk"
}
// etc...
}
var dragItems = document.querySelectorAll('[draggable=true]');
for (var i = 0; i < dragItems.length; i++) {
addEvent(dragItems[i], 'dragstart', function (event) {
// store the ID of the element, and collect it on the drop later on
event.dataTransfer.setData('Text', this.id);
});
}
addEvent(drop, 'drop', function (event) {
// stops the browser from redirecting off to the text.
if (event.preventDefault) {
event.preventDefault();
}
var person = people[event.dataTransfer.getData('Text')];
this.innerHTML += '
';return false;
});
Obviously this isn’t the most ideal situation, but there’s also the possibility to set other content types in the drag data, which should produce some interesting apps in the future.
Drag Icon
Along with several other options with the dragstart event, you can also set a drag image, i.e. what you see under your cursor.
You can create a DOM fragment, and then associate the fragment with the dataTransfer using setDragImage(element, x, y).
So we can add the following to our example to use a custom drag image:
var dragIcon = document.createElement('img');
dragIcon.src = 'http://twitter.com/api/users/profile_image/twitter?size=mini';
Which creates an image element, and how within the dragstart event, we’ll set the drag image:
addEvent(dragItems[i], 'dragstart', function (event) {
// store the ID of the element, and collect it on the drop later on
event.dataTransfer.setData('Text', this.id);
event.dataTransfer.setDragImage(dragIcon, -10, -10);
return false;
});
This sets the custom drag icon 10 pixels below the cursor, as seen in this example: drag and drop with custom image (source code)
Native Drag
There’s lots of Drag and Drop JavaScript libraries available, but what I’d like to see, is native DnD support falling back to library based. However, I know for a fact that some libraries, including jQuery, construct a custom event when passed in to the event handler which means, currently, you can’t use the dataTransfer object, so you’ll have to rely on binding the events yourself. I’m sure this will change soon.
There’s lots more in Drag and Drop but this should be enough to go and play with now!
As for me, I'll be updating the Drag and Drop demos on HTML5demos.com to see if I can add ARIA support and capture a screencast of it running. Watch this space!
Further Reader