3
votes

Gmail handles its drag-and-drop file attachment uploading in a few clever ways:

1) Dragging a file into the browser causes the dropzone to appear. The cursor displays feedback indicating whether you are in the dropzone or not (in windows, a red crossthrough circle is used if outside the dropzone). If you drop inside the window but outside of the dropzone, the drop is intercepted such that the browser's default behavior is prevented (usually navigating away to display the dragged file).

The most obvious way to attempt this is to set dragover handler on the BODY to make the dropzone appear and preventDefault, but what about the cursor change? Is there some way to use dataTransfer.effectAllowed='none'?

2) Dragging text from one part of the window into another part does not trigger the drag-and-drop handling (ie- the dropzone does not appear)--and the preventDefault mentioned in #1 does not kick in.

If I capture the dragover event on the BODY (from #1), then intra-window text dragging is prevented. How do they accomplish both of these at the same time? It seems like this is more complex than it at first might appear.

UPDATE:

I learned two related things while trying to completely address this:

1) It appears that IE will not even trigger the drop event handler if dropEffect='none'... so I decided to only set it to none if e.dataTransfer.types exists (which it does in Chrome & FF, but not in IE). The downside is that the cursor doesn't have the red crossthrough, but at least I can intercept the drop to prevent a navaway. Your best guess for determining whether it was a file drop in IE is if e.dataTransfer.getData('Text')==null. (In my case, I wanted to be able to receive drops of either files or text, so this is how I can tell the difference in IE.)

2) It was non-obvious how Gmail hides the dropzone when you leave the browser. If you use a pure dragleave event on the page, then dragging into any child will trigger the dragleave handler even though you didn't really leave the page. I then noticed that there's a delay in Gmail before the dropzone disappears, so I would guess they use a timer to hide the dropzone (which gets reset on something like dragover). But I've come up with an alternate solution that appears to work so far:

function areXYInside(e){  
        var w=e.target.offsetWidth;
        var h=e.target.offsetHeight;
        var x=e.offsetX;
        var y=e.offsetY;
        return !(x<0 || x>=w || y<0 || y>=h);
}

And then:

$("#page").bind('dragleave', function(e){
        if(this!=e.target) return false;
        if(!areXYInside(e)){
                hideBox();
        }                 
        return false;
});
1

1 Answers

3
votes

I believe they are setting dataTransfer.effectAllowedon dragover, depending on the dataTransfer.types attribute.

EDIT: I was wrong the first time, here are the actual values for types(in Chrome at least):
- dragging text ["text/html","text","text/plain"]
- dragging a file ["Files"]

Here is a short jsFiddle example to play around with.

You can check more info about drag and drop at w3c or mdc.

EDIT: I managed to implement the exact behavior on Chrome and FF (see here)