56

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...

Gup3rSuR4c
  • 9,145
  • 10
  • 68
  • 126
  • As noted below this is an issue with the WHATWG spec. I've filed a bug here - https://www.w3.org/Bugs/Public/show_bug.cgi?id=23486 – broofa Oct 12 '13 at 11:54
  • I have to agree with PPK (thx for the link in your own answer) - just don't use "html5 native DnD". Go "retro vanilla", just use mouse down/move/up, as discussed in this question https://stackoverflow.com/questions/18425089/simple-drag-and-drop-code – mathheadinclouds Jun 26 '20 at 03:50
  • FireFox does seem to have dataTransfer issues that other browsers don't have. See here: https://bugzilla.mozilla.org/show_bug.cgi?id=580928 – ADJenks Jul 02 '20 at 23:57

10 Answers10

97

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.

Gup3rSuR4c
  • 9,145
  • 10
  • 68
  • 126
  • 1
    +1, this is insanely stupid that https://jsfiddle.net/crl/rguLw8z2/1/ can't work on other browsers than FF – caub Nov 16 '15 at 12:33
  • 1
    If they made the data available this would essentially violate the javascript sandbox principle. So I don't think you should sarcastically be calling them geniuses when in fact they found a way to enable this feature, whilst at the same time protecting user data. – Pasha Skender Aug 21 '16 at 12:53
  • 3
    @LorenShqipognja, while I haven't used the DnD API again in the years since this post, and the original project that spawned this post is also gone, I still stand by my sarcasm because the spec is still wrong. You're given methods to read and write data, but as much as you write, you can't always read, especially in very crucial moments to force different results as needed. The whole data portion of the spec is pointless if you have to "hack" your way with LocalStorage, data attributes, or something else rather than the API providing the[ working] functionality. But, I digress. – Gup3rSuR4c Aug 23 '16 at 20:42
  • 1
    i was going insane trying to figure out why my datatransfer was not getting set – iedoc Jul 25 '17 at 16:50
  • 1
    2018, still struggling with this. Is there a new API that we an use? – Yana Agun Siswanto Oct 22 '18 at 10:29
  • What a time to be alive – Henrik Feb 02 '19 at 18:59
  • I've recently hit this issue. Thank you for the explanation! Something I seem to be missing though: Can we not just set event.whatever_field = "foo" so that it's accessible during `dragover`? I've done this in my application and it seems to work. But I'm afraid this may break at some point in the future... – Setzer22 Oct 07 '19 at 09:37
18

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.

Jens Fiederer
  • 420
  • 4
  • 7
  • 7
    Do you have a link to the WHATWG discussion that took place around this? I'd like to learn more about their reasoning. – broofa Oct 12 '13 at 11:33
  • 2
    please quote your sources – caub Nov 16 '15 at 12:34
  • But `dragend` is fired on the item being dragged, right? You shouldn't need to protect it against itself, should you? – Svante Oct 15 '17 at 18:38
  • And they couldn't have used the tried-and-true same-origin policy? Even across different windows data should be accessible within the same domain. – Luke Nov 09 '17 at 00:00
  • There was no source. I was working on a problem involving drag and drop and simply had a thought while reading this. Sorry for not mentioning this earlier. – Jens Fiederer Feb 07 '18 at 15:05
  • 1
    WHATWG section 6.10.8 "Security risks in the drag-and-drop model" has the reasoning: https://html.spec.whatwg.org/multipage/dnd.html#security-risks-in-the-drag-and-drop-model – Martin Lisowski Apr 26 '23 at 09:46
  • Thanks! Do you happen to know if there is a quick way to check whether that item existed in Mar 2013? – Jens Fiederer Apr 27 '23 at 13:33
15
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.

Roland
  • 151
  • 1
  • 2
  • 1
    I could not drag from HTML in a WebBrowser object inside WPF to c#. This fixes it. However, IE11 on its own definitely allows the drag of "text/plain". Same code in the WebBrowser control is no good. I looked for settings on the object, and the whole browser feature settings rigmarole but only this worked. – Dirk Bester Jul 25 '14 at 13:25
  • This was an issue for me in Safari Webkit when building a GUI using Java..nice analysis!. – repzero Apr 08 '18 at 03:55
6

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

ansiart
  • 2,563
  • 2
  • 23
  • 41
  • 4
    Hmmm, that is definitely an interesting way around it. Wonder how it scales with the other browsers though. Regardless, the DnD spec needs to be fixed in my opinion. – Gup3rSuR4c Dec 28 '12 at 22:42
  • 4
    it needs to be fixed a lot. And I worry all the workarounds we've built up to this point, will break when browsers actually realize how poorly they've implemented their dnd systems, and what not to follow in the spec (like this current stupid issue) – ansiart Dec 28 '12 at 23:32
  • This is what I came up with also as the one and only solution to check on dragover (or dragenter) to see if the drop target will allow a drop based upon the data that is being dragged. Ugly and counterintuitive but want to be able to reject and accept certain dragged content. – Youp Bernoulli Mar 14 '18 at 08:09
3

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.

RoshanZ
  • 31
  • 1
2

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);
}
Nate Bosscher
  • 397
  • 2
  • 10
0

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.

0

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!

Siong Thye Goh
  • 3,518
  • 10
  • 23
  • 31
james
  • 1
  • 1
0

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

Peter Nixey
  • 16,187
  • 14
  • 79
  • 133
0

I also faced with this problem but no way worked for me when I set the data in ondrag function and get it back it gives the data but when try in other events like dragover and drop it dose not work. I went this way maybe its not a proper answer but may help some one

let selectedId = 0;
function onDrag(params) {
    // event.dataTransfer.setData("text",params);
    selectedId = params
}
function onDragOver(params) {
    // event.preventDefault()
    // const data = event.dataTransfer.getData("text");
    let id = selectedId
}