O'Reilly logo

JavaScript Cookbook by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

7.7. Using the New HTML5 Drag-and-Drop

Problem

You want to incorporate the use of drag-and-drop into your web page, and allow your users to move elements from one place in the page to another.

Solution

Use the new HTML5 native drag-and-drop, which is an adaptation of the drag-and-drop technique supported in Microsoft Internet Explorer. Example 7-4 provides a demonstration.

Example 7-4. Using the new HTML5 drag-and-drop

<!DOCTYPE html>
<head>
<title>HTML5 Drag-and-Drop</title>
<style>
#drop
{
  width: 300px;
  height: 200px;
  background-color: #ff0000;
  padding: 5px;
  border: 2px solid #000000;
}
#item
{
    width: 100px;
    height: 100px;
    background-color: #ffff00;
    padding: 5px;
    margin: 20px;
    border: 1px dashed #000000;
}
*[draggable=true] {
  -moz-user-select:none;
  -khtml-user-drag: element;
  cursor: move;
}

*:-khtml-drag {
  background-color: rgba(238,238,238, 0.5);
}

</style>
<script>

function listenEvent(eventTarget, eventType, eventHandler) {
   if (eventTarget.addEventListener) {
      eventTarget.addEventListener(eventType, eventHandler,false);
   } else if (eventTarget.attachEvent) {
      eventType = "on" + eventType;
      eventTarget.attachEvent(eventType, eventHandler);
  } else {
     eventTarget["on" + eventType] = eventHandler;
  }
}

// cancel event
function  cancelEvent (event) {
   if (event.preventDefault) {
      event.preventDefault();
   } else {
      event.returnValue = false;
   }
}

// cancel propagation
function  cancelPropagation (event) {
   if (event.stopPropagation) {
      event.stopPropagation();
   } else {
      event.cancelBubble = true;
   }
}

window.onload=function() {
   var target = document.getElementById("drop");
   listenEvent(target,"dragenter",cancelEvent);
   listenEvent(target,"dragover", dragOver);
   listenEvent(target,"drop",function (evt) {
              cancelPropagation(evt);
              evt = evt || window.event;
              evt.dataTransfer.dropEffect = 'copy';
              var id = evt.dataTransfer.getData("Text");
              target.appendChild(document.getElementById(id));
              });

   var item = document.getElementById("item");
   item.setAttribute("draggable", "true");
   listenEvent(item,"dragstart", function(evt) {
               evt = evt || window.event;
               evt.dataTransfer.effectAllowed = 'copy';
               evt.dataTransfer.setData("Text",item.id);
               });

};

function dragOver(evt) {
  if (evt.preventDefault) evt.preventDefault();
  evt = evt || window.event;
  evt.dataTransfer.dropEffect = 'copy';
  return false;
}
</script>
</head>
<body>
<div>
<p>Drag the small yellow box with the dash border to the larger
red box with the solid border</p>
</div>
<div id="item" draggable="true">
</div>
<div id="drop">
</div>
</body>

Discussion

As part of the HTML5 specification, drag-and-drop has been implemented natively, though Opera doesn’t currently support drag-and-drop and it can be a bit tricky in Firefox, Chrome, Safari, and IE8. The example does not work with IE7, either.

Note

Currently, implementations of HTML5 drag-and-drop are not robust or consistent. Use with caution until the functionality has broader support.

Drag-and-drop in HTML5 is not the same as in previous implementations, because what you can drag is mutable. You can drag text, images, document nodes, files, and a host of objects.

To demonstrate HTML5 drag-and-drop at its simplest, we’ll explore how Example 7-4 works. First, to make an element draggable, you have to set an attribute, draggable, to true. In the example, the draggable element is given an identifier of item. Safari also requires an additional CSS style setting:

-khtml-user-drag: element;

The allowable values for -khtml-user-drag are:

element

Allows element to be dragged.

auto

Default logic to determine whether element is dragged (only images, links, and text can be dragged by default).

none

Element cannot be dragged.

Since I’m allowing a div element to be dragged, and it isn’t a link, image, or text, I needed to set -khtml-user-drag to element.

The element that can be dragged must have the draggable attribute set to true. It could be set in code, using setAttribute:

item.setAttribute("draggable","true");

However, IE doesn’t pick up the change in state; at least, it doesn’t pick up the CSS style change for a draggable object. Instead, it’s set directly on the object:

<div id="item" draggable="true">
</div>

The dragstart event handler for the same element is where we set the data being transferred with the drag operation. In the example, since I’m dragging an element node, I’m going to set the data type to “text” and provide the identifier of the element (accessible via target, which has the element context). You can specify the element directly, but this is a more complex operation. For instance, in Firefox, I could try the following, which is derived from the Mozilla documentation:

evt.dataTransfer.setData("application/x-moz-node",target);

and then try to process the element at the drop end:

var item = evt.dataTransfer.getData("application/x-moz-node");
target.appendChild(item);

However, the item gets set to a serialized form of the div element, not an actual reference to the div element. In the end, it’s easier just to set the data transfer to text, and transfer the identifier for the element, as shown in Example 7-4.

Note

You can also set several different kinds of data to be transferred, while your drop targets only look for specific types. There doesn’t have to be a one-to-one mapping. Also note that WebKit/Safari doesn’t handle the MIME types correctly unless you use getData and setData specifically.

The next part of the drag-and-drop application has to do with the drag receiver. In the example, the drag receiver is another div element with an identifier of “drop”. Here’s another instance where things get just a tad twisted.

There are a small group of events associated with HTML5 drag-and-drop:

dragstart

Drag event starts.

drag

During the drag operation.

dragenter

Drag is over the target; used to determine if target will accept drop.

dragover

Drag is over target; used to determine feedback to user.

drop

Drop occurs.

dragleave

Drag leaves target.

dragend

Drag operation ends.

The dragged item responds to dragstart, drag, and dragend. The target responds to dragenter, dragover, dragleave, and drop.

When the dragged object is over the target, if the target wants to signal it can receive the drop, it must cancel the event. Since we’re dealing with browsers that implement DOM Level 2 event handling, the event is canceled using the cancelEvent reusable function created in an earlier recipe. The code also returns false:

function dragOver(evt) {
  if (evt.preventDefault) evt.preventDefault();
  evt = evt || window.event;
  evt.dataTransfer.dropEffect = 'copy';
  return false;
}

In addition, the dragenter event must also be canceled, either using preventDefault or returning false in the inline event handler, as shown in the example.

Note

Both dragenter and dragover must be canceled if you want the target element to receive the drop event.

When the drop event occurs, the drop event handler function uses getData to get the identifier for the element being dragged, and then uses the DOM appendChild method to append the element to the new target. For a before and after look, Figure 7-1 shows the page when it’s loaded and Figure 7-2 shows the page after the drag-and-drop event.

Web page with drag-and-drop enabled; before drag-and-drop

Figure 7-1. Web page with drag-and-drop enabled; before drag-and-drop

Web page after drag-and-drop operation

Figure 7-2. Web page after drag-and-drop operation

I also had to cancel event propagation with the receiver element, though it’s not required in all browsers.

In the example, I use an anonymous function for the dragstart and drop event handler functions. The reason is that, as mentioned in Recipe 7.5, the attachEvent method supported by Microsoft does not preserve element context. By using an anonymous function, we can access the element in the other function scope, within the event handler functions.

I haven’t been a big fan of drag-and-drop in the past. The operation requires a rather complex hand-and-mouse operation, which limits the number of people who can take advantage of this interactive style. Even if you have no problems with fine motor skills, using drag-and-drop with a touch screen or mouse pad can be cumbersome. It’s also been extremely problematic to implement.

The current HTML5 specification details newly standardized drag-and-drop functionality, though not everyone is pleased by the capability. Peter-Paul Koch (PPK) of the well-known Quirksblog describes the current implementation of drag-and-drop in HTML5 as the “HTML5 drag-and-drop disaster”, and be aware that the article may not be safe for work). Why? According to PPK, there are several things wrong with the implementation: too many events, confusing implementation details, and inconsistencies.

One of the biggest concerns PPK mentions is the requirement that if an element is to receive a drop, when it receives either the dragenter or dragover event, it has to cancel the events.

I sympathize, but I understand some of the decisions that went into the current implementation of drag-and-drop in HTML5. For instance, we’re dealing with a legacy event handling model, so we can’t invent new event triggers just for drag-and-drop. If, by default, an element is not a valid drag-and-drop target, then the drag-and-drop operation must continue as another target is sought. If the dragged element enters or is over a valid target, though, by canceling the dragenter and dragover events, the target is signaling that yes, it is interested in being a target, and doing so using the only means available to it: by canceling the dragenter and dragover events.

PPK recommends using what he calls “old school” implementations of drag-and-drop. The only issue with those is they aren’t cross-browser friendly, and can be complicated to implement. They’re also not accessible, though accessibility can be added.

Gez Lemon wrote an article for Opera on using the WAI-ARIA (Web Accessibility Initiative–Accessible Rich Internet Applications) accessible drag-and-drop features. It’s titled “Accessible drag and drop using WAI-ARIA”. He details the challenges associated with making accessible drag-and-drop, as well as the two drag-and-drop properties associated with WAI-ARIA:

aria-grabbed

Set to true when element is selected for dragging.

aria-dropeffect

Effect that happens when source is released on drag target.

The aria-dropeffect has the following values:

copy

Source is duplicated and dropped on target.

move

Source is removed and moved to target.

reference

A reference or shortcut to source is created in target.

execute

Function is invoked, and source is input.

popup

Pop-up menu or dialog presented so user can select an option.

none

Target will not accept source.

In the article, Gez provides a working example using the existing drag-and-drop capability, and also describes how ARIA can be integrated into the new HTML5 drag-and-drop. The example provided in the article currently only works with Opera, which hasn’t implemented HTML5 drag-and-drop. Yet. But you can check out Figure 7-3 to see how drag-and-drop would look from a keyboard perspective.

What a keyboard-enabled drag-and-drop operation could look like

Figure 7-3. What a keyboard-enabled drag-and-drop operation could look like

Though old school drag-and-drop is complicated, several libraries, such as jQuery, provide this functionality, so you don’t have to code it yourself. Best of all, ARIA accessibility is being built into these libraries. Until the HTML5 version of drag-and-drop is widely supported, you should make use of one of the libraries.

In addition, as the code demonstrates, drag-and-drop can follow the old school or the new HTML5 method, and the libraries can test to see which is supported and provide the appropriate functionality. We can use the jQuery script.aculo.us, or Dojo drag-and-drop, now and in the future, and not worry about which implementation is used.

See Also

Safari’s documentation on HTML5 drag-and-drop can be found at http://developer.apple.com/mac/library/documentation/AppleApplications/Conceptual/SafariJSProgTopics/Tasks/DragAndDrop.html. Mozilla’s documentation is at https://developer.mozilla.org/en/Drag_and_Drop. The HTML5 specification section related to drag-and-drop can be accessed directly at http://dev.w3.org/html5/spec/Overview.html#dnd.

See Recipe 7.5 for more on the complications associated with advanced cross-browser event listening, and Recipes 7.3, 7.4, and 7.5 for descriptions of the advanced event listening functions. Recipe 12.15 covers setAttribute.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required