Introduction

In the last chapter, we explored all the details to configure a draggable element, including the events dragstart, drag, and dragend and saw an example for each one.

Now, what's ought to be covered in this chapter is very interesting. We'll see how to configure data for a given drag operation natively using the DnD API, as opposed to using some global variables, and then see the benefit of doing so.

In particular, we'll see how to set data of a specific format; how to retrieve it back; how to retrieve all the formats of data set; some very poorly-supported formats and much more.

What is drag data?

By default, every drag operation on a webpage has some piece of data associated with it.

Formally, we refer to this as the drag's data.

Drag's data is the data associated with a drag operation.

As we already know, there are three things that can accept drag operations by default.

  1. Links (given by <a>)
  2. Images
  3. Text selections

Only these three things are draggable out-of-the-box, and likewise the target of any drag operation, to start with.

Now for each of these, the browser sets some data automatically as soon as the drag operation begins on it:

  1. For a link, its drag data is the value of its href attribute.
  2. For an image, its drag data is the value of its src attribute.
  3. For a text selection, its drag data depends on the selection. It could be just a plain piece of text, or include the HTML tags of the selection made.

Apart from these three items nothing is draggable on a webpage, by default, and so doesn't store any kind of drag data to work with later on.

The developer has to accomplish all this manually. He/she has to set and then retrieve the data all manually.

Now before we could move onto see how to do this, it's important that we first get a better understanding of the benefit of setting drag data using the native solutions provided by the DnD API.

The dataTransfer property

As soon as a drag operation begins, i.e. the dragstart event gets fired on an element, we could configure its data using the dataTransfer property on the event's object.

The dataTransfer property doesn't directly do any of this magic — we have to call its setData() method in order to set given data.

But before dissecting the syntax of this method, let's first see what is the dataTransfer property.

On all drag event objects, there is a dataTransfer property that holds a DataTransfer object.

The dataTransfer property serves to hold information regarding the ongoing drag operation.

The DataTransfer interface provides methods to set, get and clear data from a given drag data store.

Morover, it also contains some properties to further define the nature of the drag such as 'would it cause a copy of the draggable item', or 'would it move the draggable item from its original position', and so on and so forth.

Below shown are all the properties and methods exposed by the DataTransfer interface.

Property/methodPurpose
dropEffectSpecifies which drag operation is accepted by the dropzone.
effectAllowedSpecifies which operation can be performed on the dragged item.
typesReturns an array holding all formats of data stored in the drag's data store.
clearData()Clears all data from the data store.
getData()Returns data for a given format.
setData()Sets data for a given format.
setDragImage()Provides an image to move along with a drag operation.

In the following chapters, even in this one, we'll cover some of these properties/methods on the dataTransfer object.

For now, we are mainly interested in setData(), getData() and clearData().

Let's explore each of these...

Setting drag data

The setData() method of the dataTransfer object (which is a property of a drag event's object) serves to set data in a given format on the drag's data store.

Here's the syntax of the method:

DataTransfer.prototype.setData(format, data)

format is a string specifying the type of data being stored. Usually, it is given as MIME type. data is simply the actual data to store, also given as a string.

There is another method on the dataTransfer object to set data, apart from setData(). It is called mozSetDataAt(). However, note that it has very poor cross-browser support — it's mainly available in Gecko-based browsers.

There are mainly three recognisable values for format as shown below:

  1. 'text/plain'
  2. 'text/uri-list'
  3. 'text/html'

Let's understand each one in detail...

'text/plain' is useful when you literally have text values to store, for example the id of an HTML element.

'text/uri-list' is useful when you have to store strings containing URLs. Although there's not a restriction to use it to store URLs, text/uri-list can be handy in some cases, like when we drag an element and then drop it into the address bar of the browser. Having some data of the format text/uri-list in the drag's data store would result in that value showing up in the address bar.

'text/html' is useful when we are dealing with strings that contain HTML. Note that text/html doesn't get any special treatment by the browser — the same data could also be stored using text/plain, but there are occassions when using both is a requirement as we shall see later on in this chapter.

In older browsers, 'text/plain' was instead written as 'Text' (with the value capitalized). This was to indicate that the format wasn't a true MIME type. However, in all modern browsers, 'text/plain' is the standard.

With these descriptions in mind, let's quickly consider an example of a DnD implementation whereby we set a piece of text on the drag's data store and then drag the element all the way into a document editor, and finally see the value output in the editor.

<div draggable="true">Drag and Drop</div>
var divEle = document.querySelector('div');

divEle.ondragstart = function(e) {
   e.dataTransfer.setData('text/plain', 'Working with dataTransfer!')
}

Try this example live in the link below.

Live Example

Open a text editor such as Wordpad on your OS and then try dragging the <div> into the editor, and dropping it there.

We've demonstrated this example in the video snippet below. On the left-hand side, we have the example shown above while on the right-hand side, we have the WordPad document editor:

See how the text set using setData() is copied onto the editor as soon as we drop the dragged element in there.

This is the power of setData() and the native drag store of the DnD API — we can set data for a given drag operation which itself could end anywhere on the OS.

If the place where the drag ends, in the OS, supports drop events and the associated data as well, it would perform the necessary processing on the data and display it.

Now let's work with 'text/uri-list'.

As stated before, text/uri-list is used when we need to store a fully-qualified URL for a drag operation.

By default, when we drag a link (given by <a>) in a browser, it automatically sets a value of type text/uri-list on the drag's data store pointing to the href attribute of the link.

Upon dropping an item onto the address bar of the browser, if its drag data contains a value of type text/uri-list, the browser puts the corresponding URL into the address bar.

Moreover, on supported browsers, if we drag such an item onto the panel showing all tabs, it causes the browser to open a new tab pointing to the URL.

In short, text/uri-list gets special treatment by browsers.

Time for an example.

In the code below, we have a <div> element with a data-href attribute set on it. This attribute holds a fully-qualified URL in it:

<div draggable="true" data-href="https://www.codeguage.com/">Div 1</div>

For the script, we configure the dragstart handler of this <div> element to store the value of its data-href attribute for the drag, using the format 'text/uri-list':

var divEle = document.querySelector('div');

divEle.ondragstart = function(e) {
   e.dataTransfer.setData('text/uri-list', this.getAttribute('data-href'));
}

In this way, if we drag and drop this <div> onto the browser's address bar, we'd get the data-href URL input there.

You could try this example below:

Live Example

For the last option for the format parameter, that is 'text/html', there is no special treatment given to this format in browsers, unlike 'text/uri-list'.

As one might think, dragging an item, that has data of type text/html set on it, onto the desktop of the OS would not create a new HTML file.

'text/html' and 'text/plain' are effectively the same. The main benefit of using the former could be seen only after we see how to store more than one value for a drag operation in the next section.

Setting multiple pieces of data

Using the setData() method, it's possible to store more than one data value for a drag operation.

To do so, we call the method multiple times, specifying each new value in each new call.

However, one thing to make sure is that although more than one value could be stored, it has to be of a distinct format when compared to the values already stored.

If it is of the same format as an existing value, then that value is overridden with this new value.

You could think of this behavior much like CSS styles — when we put a new style inside a selector, if that style already has a value, that value is replaced with this new one.

Let's quickly demonstrate all of this long discussion while at the same time also show an application of the text/html format.

window.ondragstart = function(e) {
   if (e.target.nodeType === 2) {
      e.dataTransfer.setData('text/html', e.target.innerHTML);
      e.dataTransfer.setData('text/plain', e.target.innerText);
   }
}

We start by handling dragstart on the window element. Inside the handler, we check whether the item being dragged is an HTML element. If it is, we do two things:

  1. Retrieve its innerHTML and add it on the drag's data store, under the format text/html.
  2. Retrieve its innerText and again add it to the drag's data store, but this time under the format text/plain.

Now here's what this does: if we drag and drop any HTML element to a location where text/html is understood, the respective drop handling mechanism will use the data associated with this format, which contains all the necessary tags and markup.

However, if text/html is not understood, then the data associated with text/plain is read and used instead.

Just as before, text/html isn't granted any special powers here — it's just that we could use it to specify a more important piece of information related to the drag of an HTML element, besides the plain text value.

All good uptil now?

Let's see whether you really understand everything uptil this point. Like really everything.

Write a script to configure the dragstart of all <a> elements such that:

  1. When they are dragged and dropped on any location that supports URLs, they are shown as a URL.
  2. When they are dragged and dropped on any location that doesn't suppport URLs but does support plain text, they are shown as follows: text (url), where text is the text with the <a> tag (excluding the markup) and url is its href's value.

For instance, given the following <a> element,

<a href="https://www.codeguage.com">CodeGuage</a>

if it is dragged onto the browser's address bar, after the desired drag configurations, it should get displayed as a URL — https://www.codeguage.com; otherwise if it's dragged, let's say, onto a text editor on the OS, then it should be displayed as plain text — CodeGuage (https://www.codeguage.com).

The solution is pretty simple.

We just need to set two separate data values on the drag data store for any <a> element. The first one would be of type text/uri-list while the second one would be of type text/plain.

If a given <a> element is dropped over a region supporting URLs, such as the browser's address bar, it would extract out the text/uri-list data from this element. Otherwise, if it supports plain text, it would extract out the text/plain data form this element.

Here's the code:

var aEleList = document.querySelectorAll('a');

function dragstartHandler(e) {
   var href = e.target.href;
   var innerText = e.target.innerText;
   e.dataTransfer.setData('text/uri-list', href);
   e.dataTransfer.setData('text/plain', innerText + ' (' + href + ')');
}

aEleList.forEach(function(ele) {
   ele.ondragstart = dragstartHandler;
})

First we retrieve all <a> elements in aEleList and then create a function dragstartHandler() to handle the dragstart event on each of these elements.

Inside this function, we retrieve the href and innerText of the dragged <a> element and use these to set the desired pieces of data on the drag data store.

Lastly, we handle the dragstart event on each element in aEleList, assigning it the handler function dragstartHandler().

The types property

The DataTransfer API is quite useful and extensive. In the sections above, we only covered one method of the interface i.e. setData().

Now, let's see one very handy property — types.

The types property of the dataTransfer object returns an array holding all the types of data set in the drag's data store (if there is any data, at all).

Each element of this array is a string, a better to say, a unique string.

types showcases the types of data stored for a drag operation. Since when setting data using setData(), a given format could only be used once, it turns out that types contains unique values.

Consider the code below:

<div draggable="true">Drag and Drop</div>

<section></section>

By now, you'd be much familiar with this basic HTML setup we use to demonstrate a drag-and-drop functionality.

Here's the script:

var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) {
   e.dataTransfer.setData('text/html', this.outerHTML);
   e.dataTransfer.setData('text/plain', this.innerText);
}

sectionEle.ondragover = function(e) { e.preventDefault(); }

sectionEle.ondrop = function(e) {
   console.log(e.dataTransfer.types.toString());
}

Live Example

There isn't much to explain here except for the fact that we set some data inside the ondragstart handler for the <div> and then log the types of data stored, inside the ondrop handler for the <section> dropzone.

Here's the log that gets made as soon as we drag and drop the <div> element into <section>.

text/plain,text/html

Piece of cake! Isn't it?

Talking about the practical applications of types, we could use it to check whether a dropzone can accept certain drops.

For instance, we may have a dropzone A that only accepts plain text data, a dropzone B that only accepts URLs, and a dropzone C that only accepts some custom data. Using types, we could configure each dropzone to check whether the drag's data store has any sort of data for the desired format.

Getting drag data

Getting data back from a drag's data store is possible through the getData() method on the same dataTransfer object we saw earlier on.

The method takes just one argument which is the format of the data to retrieve:

DataTransfer.prototype.getData(format)

format is a string specifying the type of data to retrieve. It ought to be one of the strings passed to setData() when storing data.

This argument can be said to filter out the desired data for us.

It's not possible to get all the data at once from the data store. We could only retrieve data for a particular format at a time.

To retrieve all the data, we have to repeatedly call getData() with all the desired formats.

Let's consider our same old drag-and-drop implementation setup:

<div draggable="true">Drag and Drop</div>

<section></section>

This time we store the HTML markup of the <div> element in the data store as soon as it's dragged and then retrieve it back when dropped in <section>, to reconstruct it from that markup:

var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) {
   e.dataTransfer.setData('text/html', this.outerHTML);
}

sectionEle.ondragover = function(e) { e.preventDefault(); }

sectionEle.ondrop = function(e) {
   this.innerHTML += e.dataTransfer.getData('text/html');
}

Live Example

In this example, the dropzone acts as if it makes copies of the dragged element. In all the previous chapters, we used appendChild() to show the dragged event in the dropzone when dropped. This had the consequence of removing the node from its original location.

Notice the += (augmented-assignment) operator inside the ondrop handler above. It is what enables each new drag-and-drop of the <div> over <section> to create a new element in there.

It's all about experimenting with the interfaces and utilities you already know to create different sorts of drag-and-drop functionalities.

This example could've been set up without using the native data store for drag operations, at all. We could've created a global variable to hold the HTML of the dragged element and then retrieve this variable back when the element is dropped over the <section> element. The whole point of using the native data store is to show you how to use it and give some possible use case for it.

Moving on, calling getData() with non-existing formats won't throw an error, as one may think. Rather, it would return ''.

The following code illustrates this:

<div draggable="true">Drag and Drop</div>

<section></section>
var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) {
   e.dataTransfer.setData('text/html', this.outerHTML);
}

sectionEle.ondragover = function(e) { e.preventDefault(); }

sectionEle.ondrop = function(e) {
   // getting a non-existent value
   console.log(e.dataTransfer.getData('text/random'));
}

Live Example

As soon as we drop the <div> into the <section> dropzone, the following log is made:

true

This confirms the fact that calling getData() with a non-existent format indeed returns ''.