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.
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):
- dragstart
- drag
- 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:
- dragenter
- dragover
- 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.
Comments