Adding a “physical” aspect to your websites that allows users to move elements around the screen is a nice option. This “drag and drop” behavior is especially useful on devices with touch interfaces.
The Drag and Drop API is probably the oldest feature I cover in this book. It was first implemented in Internet Explorer 5 back in 1999 (that’s about 150 Internet years ago) and has been adopted by other browsers for quite some time, although the effort to standardize it was only undertaken as part of the HTML5 movement. Unfortunately, Drag and Drop shows some signs of aging, being quite arcane and unintuitive at first.
By default the a and img elements can be dragged around the screen (I’ll get to other elements momentarily), but you have to set up a drop zone, an area that the elements can be dragged into. A drop zone is created when you attach two events to an element: dragover and drop. All that’s required of dragover is that you cancel its default behavior (for one of the arcane reasons I noted earlier, which you don’t need to worry about). All the hard work happens with the drop event.
That may sound a little confusing, so this example shows a very simple setup: The #target element has the dragover and drop event listeners attached to it, the callback function of dragover prevents the default behavior with preventDefault(), and the main action happens inside the callback function of the drop event.
var target = document.getElementById('target');
target.addEventListener('dragover', function (e) {
e.preventDefault();
}, false);
target.addEventListener('drop', function (e) {
// Do something
}, false);
All of the events in the Drag and Drop API create an object called dataTransfer, which has a series of relevant properties and methods. You want to access these when the drop event is fired. For img elements, you want to get the URL of the item, so for this you use the getData() method with a value of URL and then do something with it; in this example, I’ll create a new img element and pass the URL to the src attribute, making a copy of the existing one:
target.addEventListener('drop', function (e) {
e.preventDefault();
var newImg = document.createElement('img');
newImg.setAttribute('src', e.dataTransfer.getData('URL'));
e.currentTarget.appendChild(newImg);
}, false);
Note the use of preventDefault() again inside the function on the drop callback; using this is important, because in most (if not all) browsers the default behavior after dropping an item into a drop zone is to try to open its URL. This is part of Drag and Drop’s arcane behavior. All you really need to know is to use preventDefault() to stop this from happening.
You can see a simple example based on the previous code in the file drag-drop.html—just drag the image from its starting position to inside the box.
I said previously that, by default, only a and img elements are draggable, but you can make that true of any element in two steps. First, apply the true value to the draggable attribute of the element in question:
<div draggable="true" id="text">Drag Me</div>
Second, specify a datatype for the element. You do this with the dragstart event, using the setData() method of dataTransfer to apply a MIME type (in this case, text/plain) and a value (in this case, the text content of the element):
var txt = document.getElementById('txt');
txt.addEventListener('dragstart', function (e) {
e.dataTransfer.setData('text/plain', e.currentTarget.textContent);
}, false);
You can detect the type of file being dropped by using the contains() method, which is a child of the types object, itself a child of the dataTransfer object created in the callback function of the drop event. The method returns true or false if the string supplied in the argument matches a value in types; for example, to find out if a dropped element contains a plain text type, you would use this:
var foo = e.dataTransfer.types.contains('text/plain');
Using the contains() method means you can perform different actions on different files.
The example file drag-drop-2.html shows two elements, an img and a p, which can be dragged into the marked drop zone, creating a copy of each, and the following code shows how this is done: The contains() method detects if the element being dragged contains a URL; if it does, it must be an img, so it creates a new img element with the URL of the dropped element in the src attribute; if it doesn’t, it must be text, so it creates a new text node filled with the text of the dropped element.
target.addEventListener('drop', function (e) {
var smth;
e.preventDefault();
if (e.dataTransfer.types.contains('text/uri-list')) {
smth = document.createElement('img');
smth.setAttribute('src', e.dataTransfer.getData('URL'));
} else {
smth = document.createTextNode(e.dataTransfer.getData('Text'));
}
e.currentTarget.appendChild(smth);
}, false);
Although what I’ve described in this section is more than sufficient for you to use the Drag and Drop API, the API contains plenty more that I haven’t covered. If you’re interested, a number of extra events are available: dragenter and dragleave are events for the drop zone, and dragend and drag are fired on the draggable item.