Move Data and DOM Elements using Drag & Drop

By Robert Gravelle

The dataTransfer object is a read-only Event property that holds data about the Drag & Drop operation and allows you to send data along with a dragged element. Back in the HTML 5 Drag & Drop Basics article, I only alluded to the dataTransfer property. Today, we're going to continue from where we left of and learn how the dataTransfer object can be used to shuttle data and move document elements from one part of the page to another.

A Simple Transfer

The simplest use of Drag & Drop is to respond to the ondrop event. The HTML 5 Drag & Drop Basics article provided some code to set a message in the Drop target and change its background color:

  addEvent(drop, 'drop', function (e) {
    if (e.preventDefault) e.preventDefault(); // stops the browser from redirecting off to the text.
    this.innerHTML = "<strong>Done!</strong>";
    this.style.backgroundColor="#CCCC00"; 
    
    return false;
  });

Using the Drag & Drop event's dataTransfer property, it is possible to supply additional information to the Drop Target element. For instance, we could have a message that is associated to the draggable object. To demonstrate, we'll assign the value of "Thanks for the ride!" to the draggableText element. This is done via the dataTransfer's setData() method. It accepts two string arguments that together make up a key and value pair, just like a HashMap.

The Event.dataTransfer's setData() method must be called in the draggable object's ondragstart event. The following code snippet shows the code for that. In the draggableText's ondragstart event handler, we simply set its data to the dragText variable, which holds the "Thanks for the ride!" string. In the draggableBall's case, we can't store it as a DOM element because only strings are allowed. However, we can still put it's ID to good use:

  addEvent(draggableText, 'dragstart', function (e) {
    e.dataTransfer.setData('Text', dragText);
  });
	
  addEvent(draggableBall, 'dragstart', function (e) {
    e.dataTransfer.setData('Text', this.id);
  });

In the Drop Target's ondrop event handler, we can now retrieve the data using the Event.dataTransfer.getData() counterpart method and take the appropriate action based on the contents of the 'Text' key. In following example, the dragged item is deposited into the Drop Target, a common goal of Drag & Drop operations. It also demonstrates how you can either replace the contents of the Drop Target or append to them. In the former case, setting the innerHTML property works nicely. Appending the element is a little more involved, as we must copy the original before we can append it. This is accomplished using the DOM cloneNode() method. Passing it a value of false tells it to do a shallow copy, which is fine in this case because the ball has no child nodes. Finally, the appendChild() method adds the copied node to the Drop Target:

  addEvent(drop, 'drop', function (e) {
    cancel(e); // stops the browser from redirecting off to the text.
    var data = e.dataTransfer.getData('Text');
    if (data == draggableBall.id) {
      this.appendChild( draggableBall.cloneNode(false) );    
    } else {
      this.innerHTML = data;
    }
		
    return false;
  });

A Word of Warning about Copying Elements

A simple fact of copying elements is that they are identical in every way - like twins with the same name! It's not recommended to make exact copies of elements because ID's should always be unique. Observe these three copied "ball" elements:

<body>
  <h1 align="center">A Basic Drag & Drop Demo</h1>
    <a draggable="true" id="text" href="#">This is a draggable item</a>
  <img width="50" height="50" draggable="true" src="ball.gif" alt="ball (2K)" id="ball">
  <div id="drop">
    <img width="50" height="50" draggable="true" src="ball.gif" alt="ball (2K)" id="ball">
    <img width="50" height="50" draggable="true" src="ball.gif" alt="ball (2K)" id="ball">
    <img width="50" height="50" draggable="true" src="ball.gif" alt="ball (2K)" id="ball">
  </div>
</body>

Depending on what you want to do with the copies, you may want to assign unique IDs. This will be a one liner one day, once the CSS3 "*:last-child" selector is recognized by the DOM querySelector() function. Until then, the solution is to use querySelectorAll() instead and retrieve the last element in the returned collection using collection.length - 1 as the index. What we can select is all of the image elements whose ID begins with "ball", using "img[id^='ball']" as the selector. You always have to be careful when parsing numbers from a string that you don't make too many assumptions about the location of the number. The parseInt() function is only good where the number is at the beginning of the string. Usually, it's better to use the more exacting match() function, which supports the use of regular expressions.

Once we have the last ID (or zero if there aren't any yet), we can use setAttribute() to change the ID from "ball" to something like "ball_99" where the 99 is the incremented ID number:

if (data == draggableBall.id) {
  var ballCopy = draggableBall.cloneNode(false)
  var ballElements = this.querySelectorAll( "img[id^='ball']" );
  var lastId = 0;
  if (ballElements.length) {
    var lastBallElement = ballElements[ballElements.length - 1];
    lastId = lastBallElement.id.match(/d+/);
  }
  ballCopy.setAttribute("id", "ball_" + ++lastId); 
  this.appendChild( ballCopy );    
}

Here's what we get using the above code:

<body>
  <h1 align="center">A Simple dataTransfer Demo</h1>
    <a draggable="true" id="text" href="#">This is a draggable item</a>
  <img width="50" height="50" draggable="true" src="ball.gif" alt="ball (2K)" id="ball">
  <div id="drop">
    <img id="ball_1" alt="ball (2K)" src="ball.gif" draggable="true" height="50" width="50">
    <img id="ball_2" alt="ball (2K)" src="ball.gif" draggable="true" height="50" width="50">
    <img id="ball_3" alt="ball (2K)" src="ball.gif" draggable="true" height="50" width="50">
  </div>
</body>

For some reasons, the ID is the first attribute now, but that's of no consequence.

Using Drag & Drop to Move Elements

Allowing an infinite number of element drags can get monotonous! More often, moving the object might be what you really are looking for. In that case, no modifications are required to the above code. Rather, what we need to do is add code in the draggable elements' ondragend events to remove the originals. Again, the required code for confirming a successful move differs slightly, depending on whether you are replacing or appending elements. For replacing, the Drop Target's innerHTML will do the trick because it will either contain the draggable element or it won't! In the case of appended objects, it really depends on what the maximum number of drag operations is. Obviously, for moving an element, one copy is all it takes:

  addEvent(draggableText, 'dragend', function (e) {
    if (drop.innerHTML == dragText) e.target.parentNode.removeChild(this);
  });
  
  addEvent(draggableBall, 'dragend', function (e) {
    if (drop.getElementsByTagName('img').length) this.parentNode.removeChild(this);
  });

Here are today's demo files.

Conclusion

Today we saw how the dataTransfer property of the Drag & Drop Event can be used to associate data with DOM elements as well as help copy and move them from one part of the page to another. In a future article, we'll learn how our drop targets can receive data transferred by content objects dragged into the window from other browser windows, and even other applications.



Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.
  •