0

I've used a part from a post here on SO ( I can't remember it though ) to parse files and folders on Chrome, but I cannot get it to work on Firefox (and to be honest I haven't tried it on others, though I think it doesn't work on safari either).

Here's the 2 directives, ngDrop and input.

angular.module('myApp').directive("ngDrop", function($rootScope) {
    var link = function($scope, elements, attr, ngModel) {

        var parseInput = function(event) {
            var list = [];
            $scope.count = 0;

            var toParse = [];
            for (var i = 0; i < event.dataTransfer.items.length; i++) {
                toParse.push(event.dataTransfer.items[i].webkitGetAsEntry());
            }

            var traverse_directory = function(entry) {
                var reader = entry.createReader();
                // Resolved when the entire directory is traversed
                return new Promise(function executer(resolve_directory) {
                    var iteration_attempts = [];
                    (function read_entries() {
                        // According to the FileSystem API spec, readEntries() must be called until
                        // it calls the callback with an empty array.  Seriously??
                        reader.readEntries(function(entries) {
                            if (!entries.length) {
                                // Done iterating this particular directory
                                resolve_directory(Promise.all(iteration_attempts));
                            } else {
                                // Add a list of promises for each directory entry.  If the entry is itself
                                // a directory, then that promise won't resolve until it is fully traversed.
                                iteration_attempts.push(Promise.all(entries.map(function(entry) {
                                    if (entry.isFile) {
                                        list.push(entry);
                                        return entry;
                                    } else {
                                        return traverse_directory(entry);
                                    }
                                })));
                                // Try calling readEntries() again for the same dir, according to spec
                                read_entries();
                            }
                        });
                    })();
                });
            };

            var updateNgModel = function() {
                var files = [], count = 0;
                for (var i = 0; i < list.length; i++) {
                    list[i].file(function(file) {
                        files.push(file);
                        count++;
                        if (count === list.length) {
                            ngModel.$setViewValue(files);
                        }
                    });
                }
            };

            for (var j = 0; j < toParse.length; j++) {
                if (toParse[j].isFile) {
                    list.push(toParse[j]);
                } else if (toParse[j].isDirectory) {
                    $scope.count++;
                    traverse_directory(toParse[j]).then(function() {
                        $scope.count--;
                        if ($scope.count == 0) {
                            updateNgModel();
                        }
                    });
                }
            }
            if ($scope.count == 0) {
                updateNgModel();
            }
        }

        elements[0].ondrop = function(event) {
            event.stopPropagation();
            event.preventDefault();

            // ... styling

            parseInput(event);
        };

        elements[0].ondragover = function(event) {
            event.preventDefault();
        };
    };

    return {
        restrict: 'A',
        require:"^ngModel",
        link: link
    };
});

// select file on input
angular.module('myApp').directive("input", function($rootScope) {
    var link = function($scope, elements, attr, ngModel) {
        if (attr.type && attr.type.toLowerCase() === 'file') {
            elements[0].onchange = function(event) {
                var list = event.__files_ || (event.target && event.target.files);
                var files = [];
                for (var i = 0; i < list.length; i++) {
                    files.push(list[i]);
                }
                ngModel.$setViewValue(files);
            };
        }
    };

    return {
        restrict: 'E',
        require:"^ngModel",
        link: link
    };
});

About the implementation, this is how I use them :

<div class="dropzone" ng-model="files" ng-drop>
    <input type="file" ng-model="files" webkitdirectory multiple>
    <h2><i class="fa fa-upload"></i> Drop Images Here !</h2>
    <div>Or just click to select files.</div>
</div>

Both of the directives are primarily used to fill the ngModel.

Here's a plunkr

Now when I drag/drop in FF : TypeError: event.dataTransfer.items is undefined and when I select : TypeError: list is null

What can I change to get it to work on both Chrome and Firefox, and why not, also other browsers at the same time ?

Romain
  • 3,586
  • 7
  • 31
  • 52
  • Not certain about `angularjs` parts; [this answer](http://stackoverflow.com/a/36828612) should resolve reading directories and files portions of Question. – guest271314 Apr 28 '16 at 23:52

2 Answers2

0

What can I change to get it to work on both Chrome and Firefox

Note, firefox does not currently support directory attribute at input type="file" element to read uploaded folders; .webkitGetAsEntry() is not a firefox supported method .

Try using <input type="file"> with multiple, webkitdirectory attributes set; <input type="radio"> for user to select folder or files upload to toggle webkitdirectory attribute at input type="file" element. Can't select files when webkitdirectory is present on <input type="file" /> , Allow chrome to select both/either directory or files .

window.onload = function() {
  var results = document.getElementById("results");
  var dropped = document.getElementById("filesDropped");
  var file = document.getElementById("file");
  var files = document.getElementById("files");
  var folder = document.getElementById("folder");
  document.getElementById("type").onchange = function(e) {
    file[(files.checked ? "remove" : "set") 
         + "Attribute"]("webkitdirectory", true)
  }

  file.onchange = function(e) {
    results.innerHTML = "";
    e.preventDefault();
    e.stopImmediatePropagation();
    var files = e.target.files;
    dropped.innerHTML = files.length;
    for (var i = 0; i < files.length; i++) {
      if (/image/.test(files[i].type)) {
        (function(j) {
          var img = new Image;
          img.onload = function() {
            console.log(files[j]);
            var figure = document.createElement("figure");
            var figcaption = document.createElement("figcaption");
            figcaption.innerHTML = files[j].name;
            figure.appendChild(figcaption);
            figure.appendChild(img);
            results.appendChild(figure);
            URL.revokeObjectURL(url);
          }
          var url = URL.createObjectURL(files[j]);
          img.src = url;
        }(i))
      } else {
        console.log(files[i].type, files[i])
      }
    }
    results.style.width = width;
  }
}
#dropzone {
  padding: 0px;
  margin: 0px;
  width: 400px;
  height: 300px;
  border: 2px dotted green;
}
#dropzone:hover {
  border: 4px dotted blue;
}
#dropzone input {
  width: 400px !important;
  height: 300px !important;
  opacity: 0;
}
#results {
  position: relative;
  display: block;
  width: auto;
  min-height: 50px;
}
#filesDropped:after {
  content: " Files dropped:";
}
figure,
figcaption {
  display: block;
  position: relative;
  width: 100%;
}
<span id="filesDropped"></span>
<div id="results"></div>
<br>
<span id="type">Drop files <input id="files" name="type" type="radio"> folder <input checked id="folder" name="type" type="radio">:</span>
<div id="dropzone">
  <input id="file" type="file" multiple webkitdirectory/>
</div>
Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
  • I already do all that. I guess there's no other solution than waiting for Firefox to support the directory support. I hoped for a tweak that'd allow me to parse a dropped directory. Anyway thanks. – Romain Apr 29 '16 at 08:00
  • @RomainFournereau nightly 45+ supports `.getFilesAndDirectories()`; see http://superuser.com/questions/909112/does-firefox-support-folder-upload – guest271314 Apr 29 '16 at 14:11
  • But FF 46 stable doesn't support it yet ? – Romain Apr 29 '16 at 14:37
  • Simplest approach have been able to utilize as a workaround for both webkit and firefox is toggling `webkitdirectory` attribute; see also https://bugzilla.mozilla.org/show_bug.cgi?id=1164310 – guest271314 Apr 29 '16 at 15:11
0

Nightly 45+ supports directory upload. See Does Firefox support folder upload?

window.onload = function() {
  document.querySelector("input").onchange = function(e) {

    var uploadFile = function(file, path) {
      // handle file uploading
      console.log(file, path)
    };

    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 e.target) {
      e.target.getFilesAndDirectories()
        .then(function(filesAndDirs) {
          iterateFilesAndDirs(filesAndDirs, "/");
        })
    } else {
      // do webkit stuff
    }
  }
}
<input type="file" webkitdirectory allowdirs directory />

plnkr http://plnkr.co/edit/DSUeZiW4JjvxmRrFnqN0?p=preview

Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177