How to distinguish if a file or folder is being dragged prior to it being dropped?
Asked Answered
N

5

70

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.

Notebook answered 29/7, 2014 at 13:24 Comment(7)
I found the fit answer for my question here (#9535177) but it's 2012 year, and I can't find an actual info(Notebook
A minimal working example that clarifies or demonstrates the problem might help people provide quality answers.Humanitarianism
The meta questionManny
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.Flushing
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.Notebook
@KristinaKurshakova You should do the right thing and accept the answer below. Marco provided a high-quality answer.Flip
@KrisKu how can we detect if it's a file or a folder? That's what I'm looking for!Chap
C
121

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:

Congratulation answered 2/8, 2014 at 12:55 Comment(19)
As I write in my question, I need that info while dragging (dragover and dragenter events), not drop.Notebook
Answer edited. This is the best I can do. Im sorry, but it looks like you can't read files on drag events.Congratulation
Thanks, but also as I write event.dataTransfer.types[i] == "Files" is true for folder and file.Notebook
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.Congratulation
+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?Oligopoly
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.Congratulation
On Chrome 39.0.2171.99 for local files, you can view dataTransfer on drag events now, specifically ondragenter and ondragover.Boschbok
@Boschbok 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.Congratulation
@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 statementBoschbok
Seems you can now detect files and folders via the .webkitGetAsEntry() method on the file. developers.google.com/web/updates/2012/07/…Izanagi
That's only webkit by the wayCongratulation
While 4096*N (N>=0) is a generally used for directory size, other sizes may be present also (see this: superuser.com/a/142905)Fleer
Dropped an empty folder named test.jpeg in the second example provided, detected as a file.Finicking
@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 :\Congratulation
@MarcoBonelli There is one other way: FileReader does nothing for directories, no load or error events. But that works for small files only.Finicking
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.Excisable
event.dataTransfer?.types.includes('Files') works fine with TSHollis
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.Incendiarism
@Incendiarism 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.Congratulation
S
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

Sebastien answered 27/9, 2016 at 14:33 Comment(2)
Exactly what the OP is not looking for. Incorrect, unrelated, confusionary, browser-specific and jQuery based.Congratulation
Is there a way to do the same on Safari?Democrat
B
3

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);
    }

}
Bast answered 21/11, 2018 at 14:49 Comment(3)
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.Roland
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 ; )Rupert
For those using this method, if user is using a big file then RileReader approach can be very heavy on the client.Diplopia
S
3

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

Skiing answered 28/3, 2019 at 8:36 Comment(2)
works on Chrome 80 windows 10 not localhost 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 filesCoeternity
you can use this in conjunction with npm packages ... mime or mime-db or mime-types to get recognised extensions for the given mime typeArchaeology
E
0

This works for me. I filter by size of the file. The only problem would be a false positive for empty files (size = 0)

  <div class="flex align-items-center flex-column justify-content-center dragging-area"
                  (drop)="
                    dropHandler(
                      $event,
                      vm.message.id,
                      vm.allowedFileEndings,
                      vm.message.id
                    );
                    $event.preventDefault()
                  "
                  (dragover)="$event.preventDefault()"
                >
dropHandler(
    event: any,
    outboxMessageId: string,
    allowedFileEndings: string[],
    id: string,
  ) {
    const { files } = event.dataTransfer;
    const filesArray: File[] = [...files];
    const filesThatAreNotFolders = filesArray.filter(
      (value) => value.size !== 0,
    );
    if (filesThatAreNotFolders.length > 0) {
      // Do upload your file here for variable filesThatAreNotFolders 
    }
  }
Eley answered 12/6, 2024 at 15:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.