52

I'm using the html5 events to enable both file and element drag-and-drop. I've attached the dragover event to the body and am using event delegations to show where a draggable can be dropped. My question is how can I tell if a file is being dragged vs. an element with draggable=true. I know I can detect the element being dragged via e.target. But, how can I tell if it is a file.

jquery is available.

Also, not talking about jquery-ui draggable here.

I'm starting to think maybe the only way to detect the file will be by exclusion and detecting the elements instead. If we're not dragging an element, assume it's a file. This will require extra work though as images and links are draggable by default, so I will have to add events to them or prevent them from dragging.

aepheus
  • 7,827
  • 7
  • 36
  • 51

4 Answers4

84

You can detect what is being dragged by inspecting dataTransfer.types. This behaviour is not (yet) consistent across browsers so you have to check for the existence of 'Files' (Chrome) and 'application/x-moz-file' (Firefox).

// Show the dropzone when dragging files (not folders or page
// elements). The dropzone is hidden after a timer to prevent 
// flickering to occur as `dragleave` is fired constantly.
var dragTimer;
$(document).on('dragover', function(e) {
  var dt = e.originalEvent.dataTransfer;
  if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))) {
    $("#dropzone").show();
    window.clearTimeout(dragTimer);
  }
});
$(document).on('dragleave', function(e) {
  dragTimer = window.setTimeout(function() {
    $("#dropzone").hide();
  }, 25);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="dropzone" style="border: 2px dashed black; background: limegreen; padding: 25px; margin: 25px 0; display: none; position">
   Drop files here!
</div>
 hover files here
Bouke
  • 11,768
  • 7
  • 68
  • 102
  • This works perfectly in Safari 5, Chrome 22, and FF 15. Thank you! PS That if/ternary conditional is slick. – Chad von Nau Oct 31 '12 at 00:20
  • any suggestions for IE10? – Björn Jan 04 '14 at 02:18
  • @Marcel it appears you have to handle `dragenter` and prevent the default to receive the other events (see [this answer and comment](http://stackoverflow.com/a/15171015/58107)). Can you confirm this? – Bouke Jan 04 '14 at 07:32
  • 1
    Firefox now includes `Files` now in addition to their browser-specific one, but stored in an object with numerical indexes whereas Chrome is a plain array. – bryc Aug 20 '14 at 18:33
  • `Array.prototype.indexOf.call(files, "Files")` works for me. – Tomáš Zato Nov 18 '15 at 23:11
  • 5
    @bouke, your code helped a lot! thanks! if any of you need to make the code work on IE10/IE11, use: `dt.types != null && ((dt.types.length && dt.types[0] === 'Files') || dt.types.contains('application/x-moz-file'))` as a condition for showing the dropzone. – ra00l Jan 19 '16 at 14:25
  • 2
    Why not just do `if(dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files')))`. This seems to work for all browsers – shinobi Jun 08 '16 at 12:59
  • This is a very clever solution because dragexit and dragstart events are not fired when dragging files from outside of the browser. Since dragging a file only fires dragenter, dragover and dragleave events, this is the only solution i came across the web after two hours of search. Many thanks! – İlter Kağan Öcal Aug 31 '17 at 14:39
  • Thanks for this useful piece of code. Question now is: when file is dropped (image, pdf), it replace the whole page by a document preview. How to prevent that and give the FileData to an input type='file'? Best – guillaumepotier Apr 05 '18 at 07:04
  • Bravo, worked perfectly for me. @aepheus if this answered your question, please mark this gentlemen's answer as the accepted one. – Chad Dec 11 '18 at 03:55
  • @shinobi if that is true what you say, you dont need to use contains, its a newer than indexOf, indexOf will work everywhere where contains works. `const hasFiles = ({dataTransfer: {types = []}}) => types.indexOf('Files') > -1` – The Fool Apr 21 '20 at 03:48
7

Further improvement of bouke's answer:

Since chrome calls dragleave of document on every dragenter foe each element, it can cause flickering of the dropzone, especially if there are many nested elements.

$(document).on('dragleave', function(e) {
    dragTimer = window.setTimeout(function() {
        $("#dropzone").hide();
        }, 25);
});

What I did to fix the issue for me is increasing the timeout a bit and adding clearTimeout before setting each timeout, since previously in some cases there would be more than one timeouts which are not cleared in the dragover event, since dragTimer stores only the latest one. The result version:

$(document).on('dragleave', function(e) {
    window.clearTimeout(dragTimer);
    dragTimer = window.setTimeout(function() {
        $("#dropzone").hide();
    }, 85);
});

btw, thanks for the idea! My other solution was an absolute pain :)

elanoism
  • 129
  • 2
  • 2
  • There is a better way to achieve this: ondragstart is called each time you enter a nested element, so you can keep a refernce counter. Found it here: https://stackoverflow.com/a/21002544/8277225 – Numbnut Mar 12 '20 at 21:16
4

I just use this to detect files in dragover event:

Array.prototype.indexOf.call(files, "Files")!=-1 // true if files
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
3

Use the function below to check if the drag source is an external file.

Tested on Windows 7 with:

  • Firefox version 39
  • Chrome version 44
  • Safari version 5.1.7
function isDragSourceExternalFile(dataTransfer){
    // Source detection for Safari v5.1.7 on Windows.
    if (typeof Clipboard != 'undefined') {
        if (dataTransfer.constructor == Clipboard) {
            if (dataTransfer.files.length > 0)
                return true;
            else
                return false;
        }
    }

    // Source detection for Firefox on Windows.
    if (typeof DOMStringList != 'undefined'){
        var DragDataType = dataTransfer.types;
        if (DragDataType.constructor == DOMStringList){
            if (DragDataType.contains('Files'))
                return true;
            else
                return false;
        }
    }

    // Source detection for Chrome on Windows.
    if (typeof Array != 'undefined'){
        var DragDataType = dataTransfer.types;
        if (DragDataType.constructor == Array){
            if (DragDataType.indexOf('Files') != -1)
                return true;
            else
                return false;
        }
    }
}

Example Usage with JQuery

$(document).on('dragover', function(e){
    var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
    console.log(IsFile);
});
Shannon Matthews
  • 9,649
  • 7
  • 44
  • 75