Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

DnD API - Drop Effects

Chapter 72 17 mins

Learning outcomes:

  1. What are drop effects
  2. The dataTransfer.dropEffect property
  3. The dataTransfer.effectAllowed property
  4. Practical-level examples of using these

Introduction

In the last chapter, we covered what are 'dropzones' in the DnD API, in precise detail.

Now, in this chapter, our aim is to cover the idea of drop effects in the API. In particular, we'll see what's the main purpose behind using them, how to use them, how not to use them and much more on this road.

So wasting any more time, let's get to work!

What are drop effects?

Did you even notice the mouse pointer when you drag a draggable item to a dropzone.

By default, when we drag an item and hover it over any element, the browser shows us a given pointer to indicate what would happen when the item is dropped at this instant.

For some draggable items, it's limited to only one pointer, but for some it can be many. However, any element could have one of at most three different kinds of mouse pointers — not more than that.

The simplest example to demonstrate this would be to have a single draggable <div>, as shown below.

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

Try dragging this <div>.

As soon as you do so, you'll notice that a 🛇 icon is shown along with the <div> as it's dragged. This is the browser's way to tell you that the <body> element (or any other element in this case) couldn't accept this draggable element's drop.

When 🛇 is displayed, it means that the drop event won't fire at the end of the drag operation.

And similarly, if 🛇 isn't displayed, then this would simply mean one thing — drop would definitely get dispatched.

There are pointers other than BLOCK as well. Each serves to indicate that a certain operation would occur on the dragged element at the end of the drag operation.

In the DnD API, these 'operations' are called drop effects.

Altogether, there are four possible drop effects:

  1. None — the drop would have no effect.
  2. Move — the drop would have the effect of moving the dropped item from its original location, usually into the dropzone.
  3. Copy — the drop would have the effect of copying the dropped item and showing it inside the dropzone.
  4. Link — the drop would have the effect of creating some relationship between the dragged element and the dropzone.

What does the word 'drop effect' mean?

The term 'drop effect' merely relates to the item displayed to the user when performing a drag operation.

It doesn't have to do anything with animation/transition effectson the drop, as the term might suggest confuse some. The term simply means what effect would the drop have.

Will it move the dropped item, copy it, link with it, or have no effect at all?

Hopefully, the term 'drop effect' would be understood by now.

For elements made draggable and droppable manually, the pointer shown is usually for the one for copy. However, one extremely crucial thing to keep in mind is that the icon merely gives a visual clue as to what might happen if we were to drop the dragged item.

It doesn't do anything itself!

When you first come across drop effects in the DnD API, you might be tempted to think that they could configure the way a drop is made.

For instance, if the drop effect is move, you might think that dropping the dragged item would result in the item being literally copied from its original position into the dropzone.

This is NOT always the case. It might be sometimes — but not always!

Remember that drop effects are just a visual clue to the user as to what would happen on a drop effect — they're nothing more than this.

What this means is that, for instance, if you're shown a pointer for the effect 'move' or 'copy' while dragging an element over a dropzone, don't get tricked into thinking that dropping the element would actually move or copy it into the dropzone, respectively.

Nothing would happen by displaying a specific icon more than the fact that it would give a visual hint to the user for the end operation.

That's it!

To actually accomplish any specific effect for a custom DnD implementation, we have to write the respective code ourselves.

But before that, let's see how to customize the icon shown to the user as an item is dragged over a dropzone.

The dropEffect property

The dataTransfer object of the DragEvent interface holds numerous properties and methods essentially containing information related to the drag.

One of these properties, which of particular interest to us right now, is dropEffect.

dropEffect configures a dropzone by specifying what effect would be made as soon as an item is dragged-and-dropped over it.

It can have one of the following four values:

  1. 'none' — no effect. The drop event won't fire on the dropzone. The corresponding icon is shown.
  2. 'move' — the dragged item would be moved. The corresponding icon is shown.
  3. 'copy' — the dragged item would be copied. The corresponding icon is shown.
  4. 'link' — the dragged item would be linked. The corresponding icon is shown.

Assignment of any other value is ignored — in such a case, the property remains equal to 'none'.

Let's see a quick example...

Consider the code below:

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

<section></section>

As always, this is the HTML for our drag-and-drop implementation. One element is a draggable (you know which one) and the other is a droppable.

Now since we're just interested in showing a different pointer as <div> is dragged into <section> and not anything fancy, we won't be setting up handlers for dragstart or drop.

We'll simply use a single dragover handler (on <section>) and set dataTransfer.dropEffect to 'move'. This would make the respective changes to the pointer as soon as a drag is made over the dropzone:

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

sectionEle.ondragover = function(e) {
   e.dataTransfer.dropEffect = 'move';
   e.preventDefault(); // do you remember this?
}

Live Example

Simple, wasn't it?

Let's see the pointer for all drop effects in one single example.

All Drop Effects

One strange thing in regards to dropEffect is that by default for a custom dropzone, the property is equal to 'none'. This should mean that the dropzone accepts nothing. However, despite of dropEffect being 'none', the dropzone accepts everything!

This is definitely some kind of buggy implementation, and unfortunately, we can't do anything about it.

If we set dropEffect to 'none' explicitly, only then would the dropzone not accept any drops and behave as desired.

Moving on, keep in mind that dropEffect works only for dropzones. That is, setting it is sensible only on the dragenter, dragover, dragleave and drop events — all of which fire on a dropzone.

If you configure dropEffect on the dragstart event, it will be useless since the browser would anyways replace it with the default drop effect on dispatching dragover.

In certain browsers, such as Chrome, dropEffect is reset to 'none' inside the drop handler. This is actually a bug in these browsers.

Let's see a couple of examples of dropEffect before considering a full-blown example where we use the property to customize the action performed when a draggable element is dropped in one of two dropzones, depending on the dropzone.

Remember that the default feedback icon shown in a browser when a draggable item is brought over a dropzone is for the 'copy' effect, yet dropEffect returns 'none' when retrieved.

This is once again a conflicing thing, but web devs can't do anything for it!

Uptil now, we've just seen how the dropEffect property works. Let's now see how could we use it for a practical purpose.

Consider the HTML markup below:

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

<p>Move</p>
<section id="d1"></section>

<p>Copy</p>
<section id="d2"></section>

We have a single draggable <div> and two <section> elements, both of which would act as dropzones. #d1 is meant to move the <div> from its original location into itself while #d2 is meant to copy it and then place it in itself.

Let's set up the logic to accomplish this. First, for the draggable <div>:

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

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

As soon as the <div> is dragged, its reference is stored inside the global variable draggedEle. This is reset back to null when the drag operation ends with the dragend event.

Now over to configuring the first dropzone which is meant to move the dragged <div>:

var d1Ele = document.querySelector('#d1');

d1Ele.ondragover = function(e) {
   e.dataTransfer.dropEffect = 'move';
   e.preventDefault();
}

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

And after this, over to the second dropzone which is meant to copy the dragged <div>:

var d2Ele = document.querySelector('#d2');

d2Ele.ondragover = function(e) {
   e.dataTransfer.dropEffect = 'copy';
   e.preventDefault();
}

d2Ele.ondrop = function(e) {
   if (draggedEle) {
      var node = draggedEle.cloneNode();
      this.appendChild(node);
   }
}

Altogether, this is the following code:

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

var d1Ele = document.querySelector('#d2');
var d2Ele = document.querySelector('#d2');

// configuring <div>
divEle.ondragstart = function(e) { /* ... */ }
divEle.ondragend = function(e) { /* ... */ }

// configuring the first dropzone
d1Ele.ondragover = function(e) { /* ... */ }
d1Ele.ondrop = function(e) { /* ... */ }

// configuring the second dropzone
d2Ele.ondragover = function(e) { /* ... */ }
d2Ele.ondrop = function(e) { /* ... */ }

Live Example

The effectAllowed property

Another property on dataTransfer related to dropEffect and the icons displayed while an element is dragged is effectAllowed.

It is used to configure a draggable element, in contrast to dropEffect which is used to configure a dropzone.

The effectAllowed property specifies what drop effects are allowed to be made on the draggable element.

That is, 'can the element be moved, copied, linked, or both moved and copied, or moved and linked, or copied and linked, or all of them.'

The acceptable values are:

  1. 'all' — all effects are allowed.
  2. 'copy' — only the 'copy' effect is allowed.
  3. 'link' — only the 'link' effect is allowed.
  4. 'move' — only the 'move' effect is allowed.
  5. 'none' — no effect is allowed. The element can't be dropped over a dropzone.
  6. 'copyLink' — both the 'copy' and 'link' effects are allowed.
  7. 'copyMove' — both the 'copy' and 'move' effects are allowed.
  8. 'linkMove' — both the 'link' and 'move' effects are allowed.
As stated on MDN's official document for effetAllowed, on Internet Explorer the property is converted to all lowercase characters on assignment. So, if you assign 'linkMove' to effectAllowed, it would become 'linkmove' when you retrieve it later on.

Setting effectAllowed inside the handler for any event other than dragstart has no effect at all — the assignment is simply ignored.

This is because effectAllowed is meant to configure only draggable elements — not dropzones. Hence, setting it on events fired on a dropzone is useless since the property is not meant to configure dropzones. The property meant to configure dropzones, as we saw above, is dropEffect.

Consider the code below:

<div id="d1" draggable="true">Move</div>
<div id="d2" draggable="true">Copy</div>

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

We have two <div>s, one that would be moved and that would be copied as soon as dragged-and-dropped into the <section> dropzone.

Let's fetch and configure both these draggable <div>s first:

var draggedEle;
var d1Ele = document.querySelector('#d1');
var d2Ele = document.querySelector('#d2');

d1Ele.ondragstart = function(e) {
   e.dataTransfer.effectAllowed = 'move';
   draggedEle = this;
}

d2Ele.ondragstart = function(e) {
   e.dataTransfer.effectAllowed = 'copy';
   draggedEle = this;
}

Now over to configuring the <section> element:

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

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

sectionEle.ondrop = function(e) {
   if (draggedEle) {
      if (e.dataTransfer.effectAllowed === 'move') {
         this.appendChild(draggedEle);
      }
      else if (e.dataTransfer.effectAllowed === 'copy') {
         this.innerHTML += draggedEle.outerHTML;
      }
   }
}

The most interesting thing over here is the ondrop handler. In there, we first check whether draggedEle has a truthy value i.e. does it point to an element node. If it does, we further lay out two checks, this time on dataTransfer.effectAllowed.

If the property is equal to 'move', we move the element, or otherwise if it is equal to 'copy', we copy it. The logic to move or copy the draggable <div> is the same as in the last example we saw above.

This gives us another interesting DnD implementation to try out:

Live Example

Wasn't this simple, as well?

Moving on...

With this, we've successfully completed the tiring and extensive unit on the DnD API. Uh, it sure was a long unit!

We covered a lot of stuff starting with the very basics of setting up a custom drag-and-drop functionality on our webpages, what are 'draggable' elements, when do the events dragstart and dragend (and even drag) fire and how to use them for practical-level purposes and so on.

We also saw what are 'droppable' elements, how to make any given element droppable, when and how do the events dragenter, dragover, dragleave and drop fire and how to use each of these for practical-level purposes.

Let's also not forget about drag data. In this unit, we also learnt what is the drag data store, how to use it to store data for a drag operation that could be ended anywhere on the OS, not just the active webpage, how to retrieve data from it, and so on and so forth.

To boil it all down, by this point we are damn ready to code any sort of DnD functionality given to us, in minutes. We are well-versed with every single concept of the DnD API and therefore it should not be any difficult to code anything related to DnD.

It's time to test your skills and understanding on DnD.

Are you ready?

Let's take the JavaScript Drag and Drop API — Quiz.