5

Problem

I have following code snippet that is used to get file information during file drag and drop upload:

var files = event.dataTransfer.files;

for (var i = 0; i < files.length; i++) {
    var file = files[i];

    // I need notDirectory(file) function.
    notDirectory(file).then(function(file) {
      output.innerHTML += 
           `<p>
              Name: ${file.name}</br> 
              Size: ${file.size} bytes</br> 
              Type: ${file.type}</br> 
              Modified Date: ${file.lastModifiedDate}
            </p>`;
    });
}

I did research and found that Firefox does not support directory uploads, but allows client to drag and drop them to drop area.

Question

How can I filter out directories from upload handler in Firefox?

Update

You can find working sample here: https://jsfiddle.net/gevorgha/exs3ta25/

Please consider that I need it to work on the latest stable Firefox version - 46.0.1 without enabling extra preferences from browser, because I don't want to ask users to enable preference to make upload work properly.

gevorg
  • 4,835
  • 4
  • 35
  • 52
  • Hello, where exactly did you read that? I either don't quite understand about which part you're talking or at least for me it is working? – Gynteniuxas May 29 '16 at 18:57
  • I want to know which files are directories. Currently I don't know how to get that info from File API. – gevorg May 29 '16 at 19:02
  • 1
    Firefox nightly 45+ supports directory upload – guest271314 May 29 '16 at 19:21
  • 1
    @gevorg See http://stackoverflow.com/questions/36842425/select-drop-files-and-or-folders-to-be-parsed/ – guest271314 May 29 '16 at 20:38
  • Thanks, so for nightly builds it supports directory uploads. My question would be is there any workaround for latest stable Firefox version to detect directories? Because on stable version that feature is disabled by default or do I miss something? – gevorg May 29 '16 at 21:20

3 Answers3

2

My question would be is there any workaround for latest stable Firefox version to detect directories? Because on stable version that feature is disabled by default or do I miss something?

Directory upload was not enabled by default at firefox 47, where tried html, javascript at stacksnippets, jsfiddle.

See Firefox 42 for developers Interfaces/APIs/DOM

The Directory interface has been experimentally extended (bug 1177688). The two members Directory.path and Directory.getContents can be exposed by setting the dom.input.dirpicker preference to true.

A workaround to detect folder upload could include

  1. Use <input type="file"> with directory and allowdirs attributes set, possibly including multiple attribute, see Note, for drag and drop or user selection on click of container;
  2. Open prefs.js or about:config and set dom.input.dirpicker preference to Boolean true;
  3. Use modified version of "Code example" for "1. File input" at Directory Upload Demo;
  4. To check if upload is a Directory and not a single File object, use if with condition (filesAndDirs[0] && filesAndDirs[0].constructor.name === "Directory") or (filesAndDirs[0] instanceof Directory) inside of .then(function(filesAndDirectories){}) at chained to .getFilesAndDirectories();
  5. Substitute <label> element for <div> element as parent of <input type="file">. Adjust css of input type="file" to fill parent droppable container and set opacity to 0; adjust css at parent element of input type="file" to display text at :before pseudo-element, over input type="file" child element.

See also New API for directory picking and drag-and-drop.

Note, approach was tried at firefox 47, where both directories and individual files were successfully uploaded.

var dropArea = document.getElementById("dropArea");
var output = document.getElementById("result");
var ul = output.querySelector("ul");

function dragHandler(event) {
  event.stopPropagation();
  event.preventDefault();

  dropArea.className = "area drag";
}

function filesDroped(event) {
  event.stopPropagation();
  event.preventDefault();
  dropArea.className = "area";

  var uploadFile = function(file, path) {
    // handle file uploading
    console.log(file, path);
    var filesInfo = `<li>
                        Name: ${file.name}</br> 
                        Size: ${file.size} bytes</br> 
                        Type: ${file.type}</br> 
                        Modified Date: ${file.lastModifiedDate}
                      </li>`;
    ul.innerHTML += `${filesInfo}`;
  };

  var iterateFilesAndDirs = function(filesAndDirs, path) {
    for (var i = 0; i < filesAndDirs.length; i++) {
      if (typeof filesAndDirs[i].getFilesAndDirectories === 'function') {
        var path = filesAndDirs[i].path;

        // this recursion enables deep traversal of directories
        filesAndDirs[i].getFilesAndDirectories()
          .then(function(subFilesAndDirs) {
            // iterate through files and directories in sub-directory
            iterateFilesAndDirs(subFilesAndDirs, path);
          });
      } else {
        uploadFile(filesAndDirs[i], path);
      }
    }
  };
  if ("getFilesAndDirectories" in event.target) {
    event.target.getFilesAndDirectories()
      .then(function(filesAndDirs) {
        // if directory
        var dir = filesAndDirs;
        if (dir[0] && dir[0].constructor.name === "Directory") {
          
          console.log(dir);
          var directoryInfo = `<li>
                        Directory Name: ${dir[0].name}</br> 
                        Path: ${dir[0].path}
                      </li>`;
          ul.innerHTML += `${directoryInfo}`;
          alert("isDirectory:true");
        }
        iterateFilesAndDirs(dir, "/");
      })

  } else {
    // do webkit stuff
  }
}

dropArea.addEventListener("dragover", dragHandler);
dropArea.addEventListener("change", filesDroped);
input[type="file"] {
  width: 98%;
  height: 180px;
}

label[for="file"] {
    width: 98%;
  height: 180px;
}

.area {
  display:block;
  border: 5px dotted #ccc;
  text-align: center;
}

.area:after {
  display: block;
  border:none;
  white-space: pre;
  /*content: "Drop your files here!\aOr click to select files";*/
  position: relative;
  left: 0%;
  top: -75px;
  text-align:center;
}

.drag {
  border: 5px dotted green;
  background-color: yellow;
}

#result ul {
  list-style: none;
  margin-top: 20px;
}

#result ul li {
  border-bottom: 1px solid #ccc;
  margin-bottom: 10px;
}
<label id="dropArea" class="area">
  <input id="file" type="file" allowdirs directory webkitdirectory/>
</label>
<output id="result">
  <ul></ul>
</output>

jsfiddle https://jsfiddle.net/exs3ta25/31/

guest271314
  • 1
  • 15
  • 104
  • 177
  • Thank you for your great input on question, however none of these works on [the latest stable firefox version - 46.0.1](https://www.mozilla.org/en-US/firefox/releases/) without enabling extra settings from browser. – gevorg May 30 '16 at 08:48
  • 1
    @gevorg Note, drag and drop was not returning expected results at firefox. Firefox provides two `` fields for selection when `allowdirs` attribute is set; when file is dropped into `Choose folder...` the directory was not recursively iterated; adjusted stacksnippets and jsfiddle for user click selection on either `Choose files...` or `Choose folder...` `input` – guest271314 Jun 05 '16 at 15:28
2

Solution

I came up with following dirty workaround that works on Firefox version - 46.0.1

It uses FileReader API to check if uploaded file is directory or not.

Code

<div id="dropArea" style="border: 1px solid; padding: 50px; text-align: center;">
    Drop your files here!
</div>

<script>
    // Get target elements.
    var dropArea = document.getElementById("dropArea");

    // To be defined.
    function notDirectory(file) {
        return new Promise(function(resolve, reject) {
            var reader = new FileReader();

            // Can read files, but not directories.
            reader.onprogress = function(event) {
                if ('progress' === event.type) {
                    resolve(file);
                    reader.abort();
                }
            };

            // Wait for result.
            reader.readAsDataURL(file);
        });
    }

    // Attach drop listener.
    dropArea.addEventListener("drop", function (event) {
        // Stop propagation.
        event.stopPropagation();
        event.preventDefault();

        // Loop files to filter out directories and print files.
        var files = event.dataTransfer.files;
        for (var i = 0; i < files.length; i++) {
            var file = files[i];

            // I need notDirectory(file) function.
            notDirectory(file).then(function(file) {
                // Print info.
                console.log({
                    name: file.name,
                    size: file.size,
                    type: file.type
                });
            });
        }
    });

    // Attach drag move listener.
    dropArea.addEventListener("dragover", function (event) {
        // Stop propagation.
        event.stopPropagation();
        event.preventDefault();
    });
</script>

Related Links

gevorg
  • 4,835
  • 4
  • 35
  • 52
  • 1
    Note, `FileReader()` constructor does not expect a parameter to be passed – guest271314 May 30 '16 at 13:51
  • Interesting approach. You could probably use `if(file.size === 0 && file.type === "" && file.name !== "")` – guest271314 May 30 '16 at 14:02
  • Dangerous, e.g. `__init__.py` file in python will match that case, because it is empty in 99% cases. – gevorg May 30 '16 at 14:05
  • But `file.name` is not empty for directories, what will it give? – gevorg May 30 '16 at 14:07
  • `name` should be name of directory; `mozFullPath` will provide full path to directory. Your approach is appears to return expected result as to your requirement – guest271314 May 30 '16 at 14:09
  • I was asking what for should I add `if(file.size === 0 && file.type === "" && file.name !== "")`, because I don't see case that it will exclude? – gevorg May 30 '16 at 14:12
  • Was not able to retrieve `.mozFullPath`, though it is visible at `console` https://jsfiddle.net/exs3ta25/29/ – guest271314 May 30 '16 at 14:45
  • Found reason cannot access `.mozFullPath` http://stackoverflow.com/questions/25765655/is-mozfullpath-in-firefox-during-file-upload-a-security-risk#comment40292389_25765655 – guest271314 May 30 '16 at 14:55
  • Yep, we need to enable privilege for it. – gevorg May 30 '16 at 14:56
1

You can read a little about this in here (including Firefix). Here is a function (not made by me but I edited maybe a half of it) that detects whether drop is file or folder and shows its name, size and what type (tested in FireFox):

function handleDrop(e)
{
    e.stopPropagation();
    e.preventDefault();
    var filesInfo = "";

    var files = e.dataTransfer.files,
        folder;

    for (var i = 0, f; f = files[i]; i++)
    {
        if (!f.type && f.size % 4096 == 0)
            folder = true;
        else
            folder = false;

        filesInfo += 'Name: ' +  f.name;
        filesInfo += '<br>Size: ' +  f.size + ' bytes';
        filesInfo += '<br>Type: ' +  (folder ? 'folder ' : 'file');

        output.innerHTML = filesInfo;
    }
}

I hope that's what you were looking for. Good luck!

Gynteniuxas
  • 7,035
  • 18
  • 38
  • 54
  • Thank you for looking into this, but this code will also mark as directory e.g. **LICENSE** file with 4096 bytes or any other file with unknown type and 4096 byte size. Not 100% accurate method. – gevorg May 29 '16 at 20:07
  • Damn, it is true. Sorry about with invalid function. :/ – Gynteniuxas May 30 '16 at 09:30