68

I am trying to detect if a folder or a file is dragged in the dragover or dragenter events.

For example:

In the ondrop event, there is an argument called MouseEvent, which has a field named dataTransfer, where are listed files (.files) or items (.items), depending on the browser, and I can read that in both Chrome and Firefox. However, for the dragover and dragenter events those fields (.files and .items) are empty. The problem is that I need that information while dragging, not dropping.

NOTE: For both files and folders event.dataTransfer.types[i] === "Files" is true.

Background Research

I found the following answer to partially fit for my question:

WebKit, and hence Chrome, is quite restrictive on when you can call getData. You're not allowed to do it inside dragstart or dragover. I think this is the canonical bug.

But that answer is from 2012, and I can't find actual updated information on the topic, so I am looking for updated information on this.

Community
  • 1
  • 1
Kris Ku
  • 1,507
  • 1
  • 16
  • 32
  • 1
    I found the fit answer for my question here (http://stackoverflow.com/questions/9534677/html5-drag-and-drop-getdata-only-works-on-drop-event-in-chrome?rq=1) but it's 2012 year, and I can't find an actual info( – Kris Ku Jul 29 '14 at 19:17
  • 1
    A minimal working example that clarifies or demonstrates the problem might help people provide quality answers. – Marcin Aug 07 '14 at 18:36
  • 9
    [The meta question](http://meta.stackoverflow.com/questions/267993) – Peter Mortensen Aug 07 '14 at 18:54
  • 3
    My guess is you simply can't do this: what if I am dragging a file or folder over my browser window to something unrelated: I wouldn't want a random website to glean any information about the content of what I am dragging if it isn't the final target. – Mark Rotteveel Aug 08 '14 at 07:13
  • Yes! Info about content file should not be available while dragging due to security. But we know the type of dragging element (file or dom element). So, if many browsers does not support folder dnd, so we have right to know file or folder is it. – Kris Ku Aug 08 '14 at 08:55
  • 1
    @KristinaKurshakova You should do the right thing and accept the answer below. Marco provided a high-quality answer. – Robert May 03 '17 at 22:01
  • @KrisKu how can we detect if it's a file or a folder? That's what I'm looking for! – YTG Apr 08 '21 at 09:29

4 Answers4

118

TL;DR you can't.

If you're wondering why this question still hasn't got an accepted answer, you can read this meta question created by OP, and my answer.

File drag/drop in HTML5

I made some research in different pieces of documentation for this topic and tested it by myself on various browsers, so I decided to summarize all I know about drag and drop of files here.

Dragging

When you drag a file you can use some listeners, such as:

  • dragenter
  • dragover
  • dragend
  • dragleave

Given that these are drag events, the files property of event.dataTransfer will either have length == 0 or be empty (null).

You can't read files details in a drag event and you can't check if they are folders. This is not a bug, it's a security feature.

Imagine you could read files on a drag event: you would be able to read everything even if the user doesn't want to upload files to your site. It would make no sense, seriously. Imagine you are dragging a file from your desktop to another folder and you accidentally drag it through a web page: now the web page reads your file and stores your personal information on its server... that would be a huge security flaw.

However, you will still be able to detect whether the user is dragging files (and by files I mean folders too, because folders are files) or not by iterating over the array event.dataTransfer.types. You can create a function that checks if the drag event contains files, and then call it in the event handler.

Example:

function containsFiles(event) {
    if (event.dataTransfer.types) {
        for (var i=0; i<event.dataTransfer.types.length; i++) {
            if (event.dataTransfer.types[i] == "Files") {
                return true;
            }
        }
    }
    
    return false;
}

function handleDragEnter(e) {
    e.preventDefault();
    if (containsFiles(e)) {
        // The drag event contains files
        // Do something
    } else {
        // The drag event doesn't contain files
        // Do something else
    }
}

Dropping

When you drop a file into the drop <div> (or whatever element you're using as dropzone), you will use a listener for the event drop to read some file properties such as name, size, type and last modification date.

You can use euristics to try detecting if a file is a folder or not.

Heuristic #1

Use FileReader or webkitGetAsEntry() as suggested in this other answer. A FileReader instance will emit an error event if trying to read a folder (for example with reader.readAsBinaryString(e.dataTransfer.files[i]).

Problem: using FileReader effectively means reading the file. For large files, this can cause problems!

Heuristic #2

Check if the file has type === "" and its size is a multiple of 4096 (size % 4096 === 0).

Problem: this method doesn't give you absolute certainty that a file is a folder: it might be a file without extension and with a size of 0 or exactly N x 4096B. Furthermore, as noted in the comments below for example, not all platforms have folders with a .size multiple of 4096. For example, one user reports a size of 928 for a folder on macOS.

function handleDrop(e) {
    e.stopPropagation();
    e.preventDefault();

    var files = e.dataTransfer.files;

    for (var i = 0, f; f = files[i]; i++) { // iterate in the files dropped
        if (!f.type === "" && f.size % 4096 === 0) {
            // The file might be a folder
            // Do something
        } else {
            // The file is not a folder
            // Do something else
        }
    }
}

Working examples

Here are some working examples to see what I said above in action and test it by yourself. Before running them, make sure that your browser supports drag and drop features. Have fun:

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • As I write in my question, I need that info while dragging (dragover and dragenter events), not drop. – Kris Ku Aug 04 '14 at 10:10
  • 2
    Answer edited. This is the best I can do. Im sorry, but it looks like you **can't** read files on `drag` events. – Marco Bonelli Aug 04 '14 at 16:35
  • 1
    Thanks, but also as I write `event.dataTransfer.types[i] == "Files"` is true for folder and file. – Kris Ku Aug 05 '14 at 08:04
  • 26
    I know. My answer to your question is that you really **can't** check if a file is a folder on a drag event. I'm sorry. – Marco Bonelli Aug 05 '14 at 12:38
  • 4
    +1 Do you have sources for your two statements concerning the detection of folders? I don't think the folder size is guaranteed by any specification; it's rather a heuristic value, isn't it? – ComFreek Aug 08 '14 at 15:35
  • 2
    The system represents folders in blocks of 4KiB, and yes that's actually an heuristic value, there isn't an official method to distinguish a file from a folder. – Marco Bonelli Aug 08 '14 at 16:13
  • 5
    On Chrome 39.0.2171.99 for local files, you *can* view `dataTransfer` on drag events now, specifically `ondragenter` and `ondragover`. – bryc Jan 17 '15 at 22:00
  • @bryc actually, If you pay attantion and read my answer carefully, I'm saying: "the `files` property of `event.dataTransfer` will either have `length == 0` or be empty (`null`)". So, yes, **`dataTransfer` always exists, but `dataTransfer.files` doesn't**. – Marco Bonelli Jan 17 '15 at 22:21
  • @MarcoBonelli I am talking about a specific case of local files on Chrome where `dataTransfer.files` is available at the client side on drag events. That has nothing to contradict your statement – bryc Jan 17 '15 at 22:31
  • 1
    Seems you can now detect files and folders via the `.webkitGetAsEntry()` method on the file. https://developers.google.com/web/updates/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available – meleyal Jul 17 '15 at 09:45
  • That's only webkit by the way – Marco Bonelli Jul 17 '15 at 09:51
  • 1
    While 4096*N (N>=0) is a generally used for directory size, other sizes may be present also (see this: https://superuser.com/a/142905) – Dima Jul 10 '17 at 17:14
  • Dropped an empty folder named `test.jpeg` in the second example provided, detected as a file. – S.D. Feb 07 '18 at 13:23
  • @S.D. that's probably because of the "known issue" that folders can't really be recognized for sure without using some heuristic like the one I explained above :\ – Marco Bonelli Feb 07 '18 at 17:19
  • @MarcoBonelli There is one other way: `FileReader` does nothing for directories, no `load` or `error` events. But that works for small files only. – S.D. Feb 07 '18 at 17:24
  • 2
    I’m on macOS 10.15.6 and the `size` values for two different folders are `928` and `992`, so it definitely can’t be trusted. – meduz' Aug 30 '20 at 00:49
  • `event.dataTransfer?.types.includes('Files')` works fine with TS – acidjazz Apr 28 '21 at 16:13
  • 1
    Update from 2022: This answer is no longer correct, apparently browser behaviour has changed. `console.log([...e.dataTransfer.items].map(v => v.kind));` does indeed print `["file"]` during drag events. – Stefnotch Aug 08 '22 at 11:11
  • 1
    @Stefnotch this answer is still correct. The issue is not detecting whether files are being dragged or not. It is detecting whether those files being dragged are files or folders. This is still **not** possible. You will get `.kind === "file"` for both files and directories. – Marco Bonelli Mar 22 '23 at 15:23
3

This is work on Dropping -on drop event- (please note that this doesn't work on dragover event):

isDraggedItemIsFile = function(e) {
// handle FF
if (e.originalEvent.dataTransfer.files.length == 0) {
    return false;
}
// handle Chrome
if (e.originalEvent.dataTransfer.items) {
    if (typeof (e.originalEvent.dataTransfer.items[0].webkitGetAsEntry) == "function") {
        return e.originalEvent.dataTransfer.items[0].webkitGetAsEntry().isFile;
    } else if (typeof (e.originalEvent.dataTransfer.items[0].getAsEntry) == "function") {
        return e.originalEvent.dataTransfer.items[0].getAsEntry().isFile;
    }
}
return true;
};

$forms.on('drop', function(e) {
        if (isDraggedItemIsFile(e)) {
            // do something if file
        } else{
           // is directory
        }
    });

Tested on FF V49, Chrome V55, Edge V25

Anas
  • 711
  • 7
  • 24
2

For dropping, you can separate files from folders by using FileReader or with webkitGetAsEntry().

The webkitGetAsEntry() is not supported by IE11, so keep that in mind!

The code will look like:

 onDrop(event) {
    let files = event.dataTransfer ? event.dataTransfer.files : 'null';

    for(let i = 0, file; file = files[i]; i++) {
       var reader = new FileReader();

       reader.onload = function (e) {
           console.log('it is a file!');
       };
       reader.onerror = function (e) {
          console.log('it is a folder!');
       };

       reader.readAsText(file);
    }

}
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Kaloyan Stamatov
  • 3,894
  • 1
  • 20
  • 30
  • 1
    I think you might have missed the part of the question that describes how he needs this info during drag over and drag enter, the call to "webkitGetAsEntry()" returns null during theese two events. – TChadwick Jul 02 '19 at 21:41
  • But still very helpfull, because now I know how to identify real files in general. I'll use this in my uploader component, thanks a lot ; ) – Niwo Aug 17 '21 at 21:09
  • For those using this method, if user is using a big file then RileReader approach can be very heavy on the client. – Jorgu Nov 03 '22 at 18:16
2

I was able to get the entire Mimetype of the thing being dragged over my page. Mimetype appears to be blank for folders, so maybe you can distinguish it that way.

Partial code (extracted from React):

function handleDragOver(ev: DragEvent) {
    ev.preventDefault();
    ev.dataTransfer!.dropEffect = 'copy';
    console.log(Array.from(ev.dataTransfer.items).map(i => [i.kind,i.type].join('|')).join(', '));
}

document.addEventListener('dragover',handleDragOver);

Output looks like:

file|image/x-icon, file|image/jpeg, file|application/vnd.ms-excel

When I drag 3 files over my page.

Not sure if it only works on localhost, I haven't uploaded this anywhere yet, but it's totally working.

MDN docs on DataTransferItem

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • works on Chrome 80 windows 10 not localhost https://jsfiddle.net/greggman/rcwzeu2t/ but any file for which the OS doesn't know the mime type shows up as no mime-type so no way to distinguish folders from files – gman Mar 14 '20 at 17:02