Question

I have recently contributed some code to Moodle which uses some of the capabilities of HTML5 to allow files to be uploaded in forms via drag and drop from the desktop (the core part of the code is here: https://github.com/moodle/moodle/blob/master/lib/form/dndupload.js for reference).

This is working well, except for when a user drags a folder / directory instead of a real file. Garbage is then uploaded to the server, but with the filename matching the folder.

What I am looking for is an easy and reliable way to detect the presence of a folder in the FileList object, so I can skip it (and probably return a friendly error message as well).

I've looked through the documentation on MDN, as well as a more general web search, but not turned up anything. I've also looked through the data in the Chrome developer tools and it appears that the 'type' of the File object is consistently set to "" for folders. However, I'm not quite convinced this is the most reliable, cross-browser detection method.

Does anyone have any better suggestions?

Was it helpful?

Solution

You cannot rely on file.type. A file without an extension will have a type of "". Save a text file with a .jpg extension and load it into a file control, and its type will display as image/jpeg. And, a folder named "someFolder.jpg" will also have its type as image/jpeg.

Try to read the file with a FileReader. If a directory is dragged in, the FileReader will raise the error event:

var reader = new FileReader();
reader.onload = function (e) {
    // it's a file
};
reader.onerror = function (e) {
    // it's a directory
};
reader.readAsText(file);

Happily, in IE11, when a directory is dropped, the e.dataTransfer.files collection is empty.

Chrome exposes an additional property in e.dataTransfer called items containing a collection of DataTransferItem objects. On each of these objects, you can call item.webkitGetAsEntry(), which returns an Entry object. The Entry object has properties isDirectory and isFile:

// Chrome only
if (e.dataTransfer.items && e.dataTransfer.items.length) {
   [].forEach.call(e.dataTransfer.items, function(item) {
      var entry = item.webkitGetAsEntry();
      if (entry && entry.isFile) {
         var file = item.getAsFile(); // same as object in e.dataTransfer.files[]
         // do something with the file
      }
   }
}

Interestingly, in my experimentation, every folder I've looked at has had its File.size % 4096 as zero. However, of course, some files will have that as well. File.size is not a reliable indicator of whether a file is actually a folder.

OTHER TIPS

I also ran into this problem and below is my solution. Basically, I took have a two pronged approach:

(1) check whether the File object's size is large, and consider it to be a genuine file if it is over 1MB (I'm assuming folders themselves are never that large). (2) If the File object is smaller than 1MB, then I read it using FileReader's 'readAsArrayBuffer' method. Successful reads call 'onload' and I believe this indicates the file object is a genuine file. Failed reads call 'onerror' and I consider it a directory. Here is the code:

var isLikelyFile = null;
if (f.size > 1048576){ isLikelyFile = false; }
else{
    var reader = new FileReader();
    reader.onload = function (result) { isLikelyFile = true; };
    reader.onerror = function(){ isLikelyFile = false; };
    reader.readAsArrayBuffer(f);
}
//wait for reader to finish : should be quick as file size is < 1MB ;-)
var interval = setInterval(function() {
    if (isLikelyFile != null){
        clearInterval(interval);
        console.log('finished checking File object. isLikelyFile = ' + isLikelyFile);
    }
}, 100);

I tested this in FF 26, Chrome 31, and Safari 6 and three browsers call 'onerror' when attempting to read directories. Let me know if anyone can think of a use case where this fails.

I proposing calling FileReader.readAsBinaryString on the File object. In Firefox, this will raise an Exception when the File is a Directory. I only do this if the File meets the conditions proposed by gilly3.

Please see my blog post at http://hs2n.wordpress.com/2012/08/13/detecting-folders-in-html-drop-area/ for more details.

Also, version 21 of Google Chrome now supports dropping folders. You can easily check if the dropped items are folders, and also read their contents.

Unfortunately, I don´t have any (client-side) solution for older Chrome versions.

One other note is that type is "" for any file that has an unknown extension. Try uploading a file named test.blah and the type will be empty. AND... try dragging and dropping a folder named test.jpg - type will be set to "image/jpeg". To be 100% correct, you can't depend on type solely (or if at all, really).

In my testing, folders have always been of size 0 (on FF and Chrome on 64-bit Windows 7 and under Linux Mint (Ubuntu essentially). So, my folder check is just checking if size is 0 and it seems to work for me in our environment. We also don't want 0-byte files uploaded either so if it's 0 byte the message comes back as "Skipped - 0 bytes (or folder)"

FYI, this post will tell you how to use dataTransfer API in Chrome to detect file type: http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top