Introduction

Back in the days when IE5 was about to be shipped and HTML5 was already a recent technology, IE launched a new API to enable native drag-and-drop functionality. The developer could use this API to customise things as they are dragged and dropped.

With newer and newer versions of the browser, Microsoft kept on adding more and more bulk to this API ultimately leading to the API finding itself all the way in the HTML5 specification and hence other browsers implementing it.

Strictly speaking, the implementation of IE for its drag-and-drop functionality was reverse-engineered by Ian Hickson while writing its spec for the HTML5's specification. This specification was then followed by other web browsers, such as Google Chrome, Firefox, Safari, Opera, etc. (although not very strictly).

In these early days, the implementation of DnD was extremely inconsistent across various browsers and buggy in each one separately. Some features worked in some browsers; some didn't.

However, today, at the time of this writing, all major web browsers support basic drag-and-drop functionality.

Anyways, coming back to DnD, it's good to know a bit about this, arguably irrelevant, history just in case you ever want to know about the contributions of IE to web technologies — well, IE has quite many of them!

At the end of this chapter, we'll show you the exact browser support for DnD as of today and also give ways to overcome some inconsistencies.

Let's dive into the practical work.

Getting familiar

We'll start off by understanding the default behavior of browsers these days regarding drag-and-drop functionality.

Anything that could be dragged on a webpage is called draggable.

By default, only the following items are draggable on a webpage:

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

Nothing else is draggable.

So uptil this point, we know that not everything is capable of being dragged by default in web browsers. Only three things are: links, images and text selections.

That's it!

Now once something is picked up and dragged, it eventually has to be put somewhere, or better to say, dropped somewhere.

This gives birth to another concept:

Any element where we can drop stuff is called a dropzone.

A dropzone is also referred to as a drop region, droppable region, droppable element, destination element, or simply a droppable.

Once again, not everything on a webpage is a dropzone.

Only the following are considered dropzones by default:

  1. Text input boxes (given by <input type="text">)
  2. Textarea boxes (given by <textarea>)
  3. Any element with contenteditable set to "true".

Nothing else!

So uptil this point, it's clear that a dropzone is an element that accepts drops, and that not everything is a dropzone by default — only the ones listed above enjoy this facility.

This means that we could drag an image (which is draggable by default) and drop it into a <textarea> element (which is droppable by default).

The consequence of doing so is interesting...

When we drop an <img> inside a text box (<textarea> in this case), the src of the image is put inside the text box.

You can check this yourself below:



Try dragging and then dropping the image into the text box. As soon as you do so, you notice the box filled up with the src of the image.

This was about images — what about other draggable stuff?

Let's define the consequence for all of them:

  1. When a link is dropped onto any droppable element, its href attribute is copied onto the textbox.
  2. When a text selection is dropped over a textbox, the selected text is put inside the box. If the selected text was from a textbox itself then that text is removed from the original textbox, by default. However, by pressing the Ctrl key, we could change this and prevent the text from being removed.

All these can be seen in the live previews below:

To boil it all down, web browsers provide quite a lot of drag-and-drop functionality by default. But generally, all this is not very frequently used.

How many times do you remember dragging text from an input field into another field and dropping it there with the Ctrl key pressed? You would more than often copy the text and paste it into the desired field, isn't it so?

How many times do you drag links into textboxes? Or for that matter, how many times do you drag images into textboxes? Once in a month, once in a year? Or maybe never?

One thing known for sure is that these out-of-the-box DnD facilities provided by web browsers aren't very handy...

OK, let's be fair — they aren't handy at all!

Tapping into this default mechanism is utterly necessary, and the whole point of this unit, if you wish to create useful DnD experiences for the end user.

In the discussion that follows, we'll see how to modify the default behavior of the browser on the DnD functionality, and create a custom DnD functionality.

The draggable attribute

The first step to configure DnD functionality is the draggable HTML attribute.

Let's see what it does...

As the name suggests,

The draggable HTML attribute makes an element draggable.

This is the simplest of all definitions.

By default, as we stated earlier, not all elements on a webpage are draggable. However, by setting the draggable attribute to "true", we can make any one draggable.

Shown below is an example:

<div>Drag and Drop</div>
div {
   backgrond-color: #fff493; /* light tint of yellow */
   padding: 50px;
   display: inline-block
}

We have a <div> element with some CSS styles applied to give it a large clickable area.

Drag and Drop

Try dragging this <div>, by pressing the mouse button down while the pointer is over it and then moving the mouse while the button is held down. You'll most probably end up making a text selection — this is because we can't drag this element.

Let's go forward and add the draggable attribute:

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

Here's the new output:

Drag and Drop

Try dragging it now — you'll see the magic of draggable.

As we drag the <div>, we see a translucent, replica image of the <div> moving along with the pointer. This confirms that the <div> is draggable.

draggable ain't a Boolean attribute!

The draggable attribute is not a Boolean attribute in HTML, unlike many other attributes. That is, we couldn't set it as follows:

<div draggable>This is wrong</div>

It must have a value.

<div draggable="true">This is draggable</div>
<div draggable="false">This is not draggable</div>

"true" means that the element is draggable, while "false" means that the element is not draggable.

So now if you're asked to reason about the purpose of the draggable attribute, you'd have an answer rightaway:

draggable is required to make an element draggable.

Without draggable, we couldn't manually configure the drag-and-drop behavior of an HTML element — we would never be able to drag it likewise any sort of drag-and-drop configurations would go ignored (unless that element is draggable by default)

Setting draggable="true" on links or images is redundant since these elements are draggable by default. However, you could use the attribute to turn off this feature for these elements as you like.

The dragstart event

After setting the draggable attribute on an element, step two is to listen to the dragstart event on it.

Let's see what is dragstart...

The moment we begin to drag a draggable element, (by moving the pointer over it while the mouse button is held down) it receives a dragstart event.

Subsequent actions can obviously be then made by the ondragstart property or by assigning a listener function for the event using the addEventListener() method.

But what action could we possibly do inside the handler for dragstart?

Could you think of any?

Well, we could set some data for the respective drag operation.

For instance, if we drag a <div>, maybe we could save its reference in a global variable and then retrieve the value of this variable later on when we drop the <div> in a dropzone.

The question is why do we even need consider dragstart? Can't we implement DnD functionality without it.

We could, but only for elements, that are draggable by default. For other elements, we couldn't do anything useful without listening to the dragstart event. But why? Let's see...

Suppose you drag a <div> element (which was obviously made draggable using the draggable attribute), and then drop it to a dropzone.

What would happen at this point? Yes, we haven't shown you how to work with dropzones, but let's imagine for the moment that the dropzone is fully configured. What would happen?

Well, if we did not listen to the dragstart event, we woudn't have specified the data associated with the current drag. This would've meant that the moment we dropped the <div> into the dropzone, our application would've had no clue regarding what is being dropped — there would've been no data at all to work with!

Moreover, as we'll see later on, of all the events that get dispatched on the dropzone, none of them has any property to refer to the element being dragged and dropped.

OK, great! So we don't know which element was dropped on the dropzone by any event that fires on it... Is this some kind of a joke?

All this simply means one thing: to set data associated with a drag operation, we need to set it manually as soon as the drag operation begins, which is marked by the dragstart event.

Keep in mind, though, that it's not just dragstart where we could set data for the ongoing drag operation. We could do this inside the drag event's handler as well, which we shall explore in the JavaScript Drag and Drop — Draggable Elements chapter.

But sensibly speaking, dragstart represents the start of a drag event, and it makes much more sense to set the data at this point, rather than when the drag has already begun. Moreover, drag tends to fire continuously, which would make the data be set again and again — something clearly unnecessary and inefficient!

Here's an example of handling dragstart:

<div draggable="true">Drag and Drop</div>
The CSS is same here as in the example above.
var draggedEle;
var divEle = document.querySelector('div');

divEle.ondragstart = function(e) {
   draggedEle = e.target;
}

First we create a global variable draggedEle to hold a reference to the dragged element later on.

Next, we select the <div> element in the variable divEle and then assign a handler to its dragstart event. Inside the handler, we save the event's target inside draggedEle.

At least for now, you won't find this code snippet useful at all.

After all, we could've assigned divEle to draggedEle inside the handler, instead of e.target, or simply just removed the handler since we already have a reference to the <div> element.

However, once we add more draggable <div>s, which we would very soon in this chapter, you'd see the benefit of using e.target inside the ondragstart handler and assigning it to the global var draggedEle.

Anyways, so the code above, gets the variable draggedEle filled up with the reference of <div> as soon as it's dragged.

We could confirm this using a very simple console log statement:

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

divEle.ondragstart = function(e) {
   draggedEle = e.target;
console.log(draggedEle === divEle); // true }

Live Example

As soon as we drag the <div>, true is logged in the console. This means that yes, draggedEle is exactly the same element as divEle.

Perfect!

It's time to finally make this code snippet sensible by creating and configuring a dropzone.

The dragover event

As stated before, not every element on a webpage is considered a dropzone, by default.

Only the following meet enjoy this feature:

  1. Text input boxes (given by <input type="text">)
  2. Textarea boxes (given by <textarea>)
  3. Any element with contenteditable set to "true".

Fortunately, we can make any element a dropzone using the DnD API.

The first step in doing so is to handle the dragover event on the respective element.

So what is dragover?

The dragover event fires repeatedly on an element after short intervals as soon as a draggable element is dragged over it. The event only stops firing when the pointer leaves the element.

By default, the browser cancels further events from popping up in its native dragover handling mechanism. We can, and in all cases need to, prevent this default action by using the preventDefault() method on the event object, or any other property to accomplish this prevention.

An interesting question at this point would be as follows: which event are we interested in handling which the default dragover mechanism of the browser would otherwise prevent?

You know what: letting the browser continue on with its default nature of preventing subsequent events in dragover would keep the most important event from firing i.e. drop.

We'll see drop in the next section, in detail. If the drop event gets prevented, we'd never be able to implement a custom drag-and-drop functionality using this DnD API.

No seriously — never!

In short, it's utterly important to use preventDefault() inside the dragover handler or any other way of cancelling the event's default action.

Other ways of cancelling an event's default action are to return a falsey value from its handler, or to set its returnValue property to false.

Doing so enables the subsequent drop event to fire on the element, and thus transforms the element into a dropzone.

Going back with our last example, let's now add a new HTML element to act as the dropzone. We'll use a <section> element, but you could use any element you like (such as a <div>).

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

<p>Dropzone</p>
<section></section>
div { /* same as above */ }

section {
   min-height: 200px;
   background-color: #eee;
   max-width: 500px;
}
Drag and Drop


Dropzone:

With the element in place, let's give it a handler for dragover and turn it into a dropzone:

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

divEle.ondragstart = function(e) {
   draggedEle = e.target;
}

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

Now, the <section> element has become a dropzone. The last thing left it to handle its drop event and perform some DOM changes right in there.

The drop event

It won't be completely wrong to say that the most important event of DnD is drop.

The drop event fires on a dropzone, as soon as an element is dropped over it. The dropping action is done by releasing the mouse button after a drag operation while the pointer is still over the dropzone.

Keep in mind that for drop to occur on any element, it's a MUST to cancel the default behavior of the browser for the dragover event on that element. Omitting this step would simply keep drop from firing!

One mistake could ruin it all! OK, it's not that dramatic.

Inside the handler for drop, we could do numerous things.

In our case, we'll remove the dragged <div> element from where it is and instead place it inside this dropzone.

But how will we do this?

One way is to use removeChild() to remove this node from its original position and then use appendChild() to add it inside <section>.

But we could shorten this set of statements using a very simple trick. Calling appendChild() on an existing node automatically removes it from its original place — no need to do the removal manually.

Alright, so here's the ondrop handler for our example:

/* ... */

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

sectionEle.ondrop = function(e) {
   this.appendChild(draggedEle);
}
For simplicity, we've shortened the code here to just show the handlers for the dropzone. You'll, however, obviously need to include everything in your code.

As soon as a draggable element is dropped onto this element, the drop event gets fired and consequently the draggable element is moved from its original position to inside this dropzone.

Live Example

Simple, isn't it?

This completes our basic drag-and-drop implementation.

We have a draggable <div> which can be dragged-and-dropped into a <section> dropzone, at which point it's moved from its original location to inside this <section> element.

This is just the start of exploring the DnD API. In the coming chapters, we'll consider each of the configurations — such as making an element draggable and making an element droppable — in more precise detail.

A more complex example

In the example above, we just had one <div> element to be dragged and dropped onto <section>. However, what's more interesting is to have many of them, each one capable of being dragged and dropped onto the dropzone.

Let's see how to implement this functionality.

First of all note that no major changes would be required to be made in order to implement this. We'll just need to create multiple <divs> and assign each one the same dragstart handler that we gave to the single <div> in our last example.

Doesn't seem complicated, right?

To start with, let's set up the HTML. Previously we had one <div>, now we'll have many of them — four, to be precise:

<div draggable="true">Div 1</div>
<div draggable="true">Div 2</div>
<div draggable="true">Div 3</div>
<div draggable="true">Div 4</div>

<p>Dropzone</p>
<section></section>

The CSS is the same as before. Just to put a little spice in this example, we give a different background color to each <div> element:

<div draggable="true">Div 1</div>
<div draggable="true" style="background-color: #f5deb3;">Div 2</div>
<div draggable="true" style="background-color: #ffb5b5;">Div 3</div>
<div draggable="true" style="background-color: #aaaaff;">Div 4</div>

<p>Dropzone</p>
<section></section>

For the scripting part, we first fetch the list of all these <div> elements inside the variable divEleList, and remove the old divEle variable:

var draggedEle;
var divEleList = document.querySelectorAll('div');
var sectionEle = document.querySelector('section');

Then, we assign a ondragstart handler to each element in divEleList. This handler would be the same so we create a named function for it, and then pass this assign this function to each element's ondragstart property.

var draggedEle;
var divEleList = document.querySelectorAll('div');
var sectionEle = document.querySelector('section');

function dragstartHandler(e) {
   draggedEle = e.target;
}

divEleList.forEach(function(e) {
   e.ondragstart = dragstartHandler;
})

Now before we go any further, notice one thing.

In the HTML above, we gave each <div> element the draggable attribute manually. Wouldn't it be a good idea if we could set this in the loop while we iterate over each <div> element.?

In this way, we won't have to manually write down the attribute for each element.

So first let's clean up our old HTML and transfer the draggable-setting logic inside forEach():

<div>Div 1</div>
<div style="background-color: #f5deb3;">Div 2</div>
<div style="background-color: #ffb5b5;">Div 3</div>
<div style="background-color: #aaaaff;">Div 4</div>

<p>Dropzone</p>
<section></section>
var draggedEle;
var divEleList = document.querySelectorAll('div');
var sectionEle = document.querySelector('section');

function dragstartHandler(e) {
   draggedEle = e.target;
}

divEleList.forEach(function(e) {
e.draggable = true; e.ondragstart = dragstartHandler; })

With this done, let's resume from where we left.

So now that each <div> element has an ondragstart handler, we just need to configure <section> based on the same logic we used earlier on.

Here's it:

var draggedEle;
var divEleList = document.querySelectorAll('div');
var sectionEle = document.querySelector('section');

/* ... */

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

sectionEle.ondrop = function(e) {
   this.appendChild(draggedEle);
}

Alright, time for the real action! Let's try this example live.

Live Example

It's only in this example that our old ondragstart handler makes sense.

Previously, we just had one <div> element and so saving its reference inside the global variable draggedEle (inside the ondragstart handler) was meaningless, since we already had a global variable divEle pointing to the <div>.

However, now the scenario is different. We have four <div > elements, each with the same ondragstart handler. We don't know which one causes the handler to fire and so use the target of the event's object to confirm this, and ultimately assign its value to the global variable draggedEle.

To summarise all this long discussion, any given set of elements can be given a custom drag-and-drop functionality.

This is accomplished in a sequence of steps:

  1. Firstly, we set the draggable HTML attribute on the element to the value "true". This makes the element draggable i.e. it could be dragged using the mouse pointer.
  2. Then we handle the dragstart event on this draggable element. Inside the handler, we usually configure some data related to the ongoing drag.
  3. Next, we configure an element into a dropzone by handling its dragover event and preventing the default action in there.
  4. Finally, we handle the drop event on this dropzone where we perform the required changes to the DOM, such as moving an element from its location into the dropzone.

This is a very basic implementation of a custom drag-and-drop functionality.

More complicated workflows can be made by listening to a larger number of events and setting data associated with the drag using specialized methods made for the task.

In the upcoming chapters, we'll cover all these details one-by-one. For now, it's important that you get firm on the very basics of DnD taught in this chapter.

Once these concepts are peaches and cream for you, only then consider moving on the next chapters. Otherwise, you'll find yourself not able to fully comprehend every single concept in this DnD API.