HTML5 DnD dataTransfer setData or getData not working in every browser except Firefox

JavascriptJqueryHtmlDrag and-Drop

Javascript Problem Overview


Consider this JSFiddle. It works fine in Firefox (14.0.1), but fails in Chrome (21.0.1180.75), Safari (?) and Opera(12.01?) on both Windows (7) and OS X (10.8). As far as I can tell the issue is with either the setData() or getData() methods on the dataTransfer object. Here's the relevant code from the JSFiddle.

var dragStartHandler = function (e) {
    e.originalEvent.dataTransfer.effectAllowed = "move";
    e.originalEvent.dataTransfer.setData("text/plain", this.id);
};

var dragEnterHandler = function (e) {
    //  dataTransferValue is a global variable declared higher up.
    //  No, I don't want to hear about why global variables are evil,
    //  that's not my issue.
    dataTransferValue = e.originalEvent.dataTransfer.getData("text/plain");
    
    console.log(dataTransferValue);
};

As far as I can tell this should work perfectly fine and if you look at the console while dragging an item you will see the id written out, which means that it's finding the element just fine and grabbing it's id attribute. The question is, is it just not setting the data or not getting the data?

I'd appreciate suggestions because after a week of working on this with three attempts and some 200+ versions, I'm starting to loose my mind. All I know is it used to work back in version 60 or so and that specific code hasn't changed at all...

Actually, one of the major differences between 6X and 124 is that I changed the event binding from live() to on(). I don't think that's the issue, but I've come to see a couple failures from Chrome when it comes to DnD while working on this. This has been debunked. The event binding method has no effect on the issue.

UPDATE

I've created a new JSFiddle that strips out absolutely everything and just leaves the event binding and handlers. I tested it with jQuery 1.7.2 and 1.8 with both on() and live(). The issue persisted so I dropped down a level and removed all frameworks and used pure JavaScript. The issue still persisted, so based on my testing it's not my code that's failing. Instead it appears that Chrome, Safari and Opera are all implementing either setData() or getData() off spec or just failing for some reason or another. Please correct me if I'm wrong.

Anyway, if you take a look at the new JSFiddle you should be able to replicate the issue, just look at the console when you're dragging over an element designated to accept a drop. I've gone ahead and opened a ticket with Chromium. In the end I may still be doing something wrong, but I simply don't know how else to do DnD at this point. The new JSFiddle is as stripped down as it can get...

Javascript Solutions


Solution 1 - Javascript

Ok, so after a bit more digging around, I found that the problem actually isn't with Chrome, Safari, and Opera. What gave it away was that Firefox was supporting it and I just couldn't say the other browsers are failing, since that's something I'd normally accept for IE.

The real cause of the issue is the DnD specification itself. According to the spec for the drag, dragenter, dragleave, dragover and dragend events the drag data store mode is protected mode. What is protected mode you ask? It is:

> For all other events. The formats and kinds in the drag data store > list of items representing dragged data can be enumerated, but the > data itself is unavailable and no new data can be added.

That translates to, "you have no access to the data that you set, not even in read only mode! Go f@&# yourself.". Really? Who'se the genius that came up with this?

Now, to get around that limitation you have few choices, and I'm only going to outline two that I've come up with. Your first one is to use an evil global variable and pollute the global namespace. Your second choice is to use the HTML5 localStorage API to perform the EXACT same functionality that the DnD API should have provided to begin with!

If you go down this route, which I have, you're now implementing two HTML5 APIs not because you want to, but because you have to. Now I'm starting to appreciate PPK's rant about the disaster that the HTML5 DnD API is.

The bottom line is this, the spec needs to be changed to allow for access to the stored data even if it's only in read only mode. In my case, with this JSFiddle, I'm actually using the dragenter as a way to look ahead at the drop zone and verify that I should allow a drop to occur or not.

In this case Mozilla apparently opted out of full compliance with the spec which is why my JSFiddle was working just fine in it. It just so happens that this is the one time I fully support not supporting the full specification.

Solution 2 - Javascript

There is a reason for the "protected" bit....drag/drop can span completely different windows, and they didn't want somebody to be able to implement a "listener" DIV that would eavesdrop on the content of everything that was dragged over it (and maybe send those contents by AJAX to some spy server in Elbonia). Only the DROP area (which is more clearly under the user's control) gets the full scoop.

Annoying, but I can see why it might be considered necessary.

Solution 3 - Javascript

var dragStartHandler = function (e) {
    e.originalEvent.dataTransfer.effectAllowed = "move";
    e.originalEvent.dataTransfer.setData("text/plain", this.id);
};

The problem is with the "text/plain". The standard specification in MSDN documentation for setData is just "text" (without the /plain). Chrome accepts the /plain, but IE does not, in any version I tried.

I struggled with the same problem for several weeks, trying to figure out why my "drop" events weren't firing properly in IE while they did in CHrome. It was because the dataTransfer data hadn't been properly loaded.

Solution 4 - Javascript

I know you already answered this, but this is a useful thread -- I just wanted to add an addendum here -- if you're setting the data yourself, you can always add the data into the field itself (ugly I know), but it prevents having to re-create functionality:

For instance, if setting your own custom data:

  dataTransfer.setData('mycustom/whatever', 'data');

append the data as a new data entry, and iterate:

  dataTransfer.setData('mycustom/whatever/data/{a json encoded string even}');

querying:

// naive webkit only look at the datatransfer.types
if (dataTransfer.types.indexOf('mycustom/whatever') >= 0) {
	
	var dataTest = 'mycustom/whatever/data/';
	
	// loop through types and create a map
	for (var i in types) {

		if (types[i].substr(0, dataTest.length) == dataTest) {

			// shows:
			// {a json encoded string even}
			console.log('data:', types[i].substr(dataTest.length));

			return; // your custom handler
		}
	}
}

tested in chrome only

Solution 5 - Javascript

Something also worth noting is that if you leave the execution chain using a timeout, the dataTransfer object won't have your data anymore. e.g.

function dropEventHandler(event){
    var dt = event.dataTransfer.getData("text/plain"); // works
    var myEvent = event;

    setTimeout(function(){
       var dt = myEvent.dataTranfer.getData("text/plain"); // null
    }, 1);
}

Solution 6 - Javascript

I was getting same error for below code:

> event.originalEvent.dataTransfer.setData("text/plain", > event.target.getAttribute('id'));

I Changed code to:

> event.originalEvent.dataTransfer.effectAllowed = "move"; > event.originalEvent.dataTransfer.setData("text", event.target.getAttribute('id'));

And it worked for me.

Solution 7 - Javascript

I came across this post because I was having a similar experience with Chrome's dataTransfer.setData() and dataTransfer.getData() functions.

I had code that looked something like this:

HTML:
<div ondragstart="drag(event)" ondrop="newDrop(event)"></div>

JAVASCRIPT:
function drag(ev) {
    ev.dataTransfer.setData("text", ev.target.id);
}
function newDrop(ev){
    var itemDragged = ev.dataTransfer.getData("text");
    var toDropTo = ev.target.id;
}

The code worked perfectly fine in Internet Explorer but when it was tested in Chrome, I was unable to get values set in my dataTransfer object (set in drag function) using the dataTransfer.getData() function in the newDrop function. I was also unable to get the id value from the statement ev.target.id.

After some digging around on the web, I discovered that I was suppose to use the event parameters currentTarget property rather than the events target property. Updated code looked something like this:

JAVASCRIPT:
function drag(ev) {
    ev.dataTransfer.setData("text", ev.currentTarget.id);
}
function newDrop(ev){
    var itemDragged = ev.dataTransfer.getData("text");
    var toDropTo = ev.currentTarget.id;
}

With this change I was able to use the dataTransfer.setData() and dataTransfer.getData() functions in chrome as well as internet explorer. I have not tested anywhere else and I am not sure why this worked. Hope this helps and hopefully someone can give an explanation.

Solution 8 - Javascript

I was working on a website testing with Firefox.

In WAMP on my laptop, code like the OP's worked. However, when I moved the website to HOSTMONSTER, it didn't work there.

I followed Joshua Espana's answer, and it resolved my problem.

failed:

ev.dataTransfer.setData("text/plain", ev.target.id);

worked:

 ev.dataTransfer.setData("text", ev.currentTarget.id);

Thank, Joshua!

Solution 9 - Javascript

Anything pased to the dataTransfer only becomes available on ondrop events but ONLY on ondrop events (I believe this is a security consideration to prevent data being exposed to nefarious elements during a drag).

If you try adding an ondrop handler you should see the data exposed. Well at least you would if there weren't for one final trick...

To get the drop event to fire you need call .preventDefault on the dragover event or it prevents the drop event from firing

HTML (Angular)


<div (dragstart)="handleDragStart($event)"
     (dragover)="handleDragover($event)"
     (dragend)="handleDragEnd($event)"
     (drop)="handleDrop($event)">
  <div class="sortItem">item 1</div>
  <div class="sortItem">item 2</div>
  <div class="sortItem" draggable="true">Draggable</div>
  <div class="sortItem">item 4</div>
</div>

Handlers (Typescript)

  handleDragStart(event: DragEvent){
    event.dataTransfer?.setData("text", '{"some": "data"}')
    console.log('dragstart data:', event.dataTransfer?.getData("text"))
  }

  handleDragover(event: DragEvent) {
    console.log('dragover data:', event.dataTransfer?.getData("text") || 'none')
    event.preventDefault()
  }

  handleDragEnd(event: DragEvent) {
    console.log('drag end data:', event.dataTransfer?.getData("text") || 'none')
  }

  handleDrop(event: DragEvent) {
    console.log('drag drop data:', event.dataTransfer?.getData("text") || 'none')
  }

Output from the above


If you drop the item INSIDE the container with the ondrop handler

enter image description here

If you don't cancel the dragover event...

enter image description here


If you drop the item OUTSIDE container with the ondrop handler

enter image description here

If you don't cancel the dragover event...

enter image description here

Other relevant SO questions

SO: Data only available on drop

SO: Drop event not firing

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGup3rSuR4cView Question on Stackoverflow
Solution 1 - JavascriptGup3rSuR4cView Answer on Stackoverflow
Solution 2 - JavascriptJens FiedererView Answer on Stackoverflow
Solution 3 - JavascriptRolandView Answer on Stackoverflow
Solution 4 - JavascriptansiartView Answer on Stackoverflow
Solution 5 - JavascriptNate BosscherView Answer on Stackoverflow
Solution 6 - JavascriptRoshanZView Answer on Stackoverflow
Solution 7 - JavascriptJoshua EspanaView Answer on Stackoverflow
Solution 8 - JavascriptjamesView Answer on Stackoverflow
Solution 9 - JavascriptPeter NixeyView Answer on Stackoverflow