21

How do I upload the content of a folder using JavaScript (client side)? The FileSystem API has not been adopted by browsers other than Chrome; I only get a File item with the name of the folder.

It should be possible, because Google Drive allows to drop a folder and all the content (folders and files) will be uploaded automatically.

Pang
  • 9,564
  • 146
  • 81
  • 122
Eduardo Alonso
  • 581
  • 1
  • 5
  • 9

6 Answers6

13

You can actually upload directories in all latest versions of Chrome, Firefox and Microsoft Edge. There are numerous working examples available to look at.

Here is a good, working example that I've previously used in a project

Quarklemotion Html5FileSelector

In addition, Dropzone JS also supports directory uploads as well and it works in Chrome, FF and Edge. I've just transitioned to using this in my own project.

Dropzone JS

These solutions recursively read the directory entries and list all of the files including their relative paths. If you want to rebuild the folder structure when uploading you will have to implement that using the relative paths and the appropriate algorithm.

CaseyC
  • 1,453
  • 14
  • 23
  • 2
    I wouldn't recommend Dropzone today. It seems to have fallen into abandonware, and will likely just get in your way more than help. There's nothing particularly challenging about uploading files. – Josh Noe Jan 03 '22 at 22:02
  • @JoshNoe I'm curious why you assume that Dropzone is "abandonware" and why you wouldn't recommend it? It's under active development and the last release was in September 2021, a few months ago. There may not be anything crazy challenging about uploading files this question is about more than uploading files. It's about recursively reading directories and uploading all of the files from a directory and any nested dirs. In 2017 this wasn't as trivial. I've provided a roll-your-own solution and then a lib (Dropzone) that does it for you out of the box. Did you have anything constructive to add? – CaseyC Jan 04 '22 at 23:57
  • I wouldn't recommend dropzone in 2022 for recursively reading directories because it's going to take one much longer to integrate dropzone than simply using `webkitdirectory` or one of the other answers here. Fair enough that it's not literally abandonware, but its documentation is full of dead links and obsolete code, and very limited at that. – Josh Noe Jan 05 '22 at 17:32
  • 1
    We use DropZone for a while, and original path is also retrieved when a folder is uploaded. Little adaptation on server side and it can also support folder tree. – Camille Apr 08 '22 at 09:30
13

It seems that Chrome and Firefox supports part of the filesystem API, but are not oficially supported.

This allows you to drop a folder and read all the content, here's the code i use on my app.

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

        // if directory support is available
        if(e.dataTransfer && e.dataTransfer.items)
        {
            var items = e.dataTransfer.items;
            for (var i=0; i<items.length; i++) {
                var item = items[i].webkitGetAsEntry();

                if (item) {
                  addDirectory(item);
                }
            }
            return;
        }

        // Fallback
        var files = e.target.files || e.dataTransfer.files;
        if (!files.length)
        {
            alert('File type not accepted');
            return;
        }

        processFile(files);
    }

    function addDirectory(item) {
        var _this = this;
        if (item.isDirectory) {
            var directoryReader = item.createReader();
            directoryReader.readEntries(function(entries) {
            entries.forEach(function(entry) {
                    _this.addDirectory(entry);
                });
            });
        } else {
            item.file(function(file){
                processFile([file],0);
            });
        }
    },
Shripad Krishna
  • 10,463
  • 4
  • 52
  • 65
Eduardo Alonso
  • 581
  • 1
  • 5
  • 9
  • if I want path of the each file(Beginning path from the uploaded folder) , what changes I have to do?. Because in my project I am sending each file to backend ,there I am creating same structure of the uploaded folder. – Avinash Jun 08 '21 at 17:50
  • 1
    @Avinash here is a script you can look at where I rebuilt the folder structure when uploading directories. This is open source https://github.com/caseychoiniere/duke-data-service-portal/blob/2e98af990b0dbbca15baa987ba4541055277f819/src/stores/mainStore.js#L837 – CaseyC Jun 15 '21 at 16:57
4

If you're okay using non-standard attributes, you can use the webkitdirectory property:

let picker = document.getElementById('picker');
let listing = document.getElementById('listing');

picker.addEventListener('change', e => {
  for (let file of Array.from(e.target.files)) {
    let item = document.createElement('li');
    item.textContent = file.webkitRelativePath;
    listing.appendChild(item);
  };
});
(Forked from <a href='https://codepen.io/simevidas'>Šime Vidas' Pen</a> for posting on Stack Overflow)<br><br>

<div class="picker"><input type="file" id="picker" name="fileList" webkitdirectory multiple></div>

<ul id="listing"></ul>
Matt
  • 2,953
  • 3
  • 27
  • 46
2
  • For implement the folder upload, you need apply both of JS side and server side:
  • At JS side I use these methods (from a teacher that posted at StackoverFlow - https://stackoverflow.com/a/47935286/11544097) to loop and file all file, folder content and subfolders:

    function makedir(entries) {
      const systems = entries.map(entry => traverse(entry, {}));
      return Promise.all(systems);
    
      async function traverse(entry, fs) {
        if (entry.isDirectory) {
          fs[entry.name] = {};
          let dirReader = entry.createReader();
          await new Promise((res, rej) => {
        dirReader.readEntries(async entries => {
          for(let e of entries) {
            await traverse(e, fs[entry.name]);
          }
          res();
        }, rej);
          });
        } else if (entry.isFile) {
          await new Promise((res, rej) => {
        entry.file(file => {
          fs[entry.name] = file;
          res();
        }, rej);
          });
        }
      return fs;
      }
    };
    
    function checkFile(obj){
        return obj instanceof File;
    };
    
    function exactFile(system_trees, relativePath, files){
        for (var i = 0; i < system_trees.length; i++){
            for (var property in system_trees[i]) {
                if (system_trees[i].hasOwnProperty(property)) {
                    if (checkFile(system_trees[i][property])){
                        system_trees[i][property]["relativePath"] = relativePath;
                        files.push(system_trees[i][property]);
                    } else {
                        files.concat(exactFile([system_trees[i][property]], (typeof relativePath !== 'undefined' && relativePath !== '' ? (relativePath + '/') : '') + property, files));
                    }
                }
            }
        }
        return files;
    };
    
    function readDropped(dT,_data) {
      const entries = [...dT.items].map(item => {
          return item.webkitGetAsEntry ? item.webkitGetAsEntry() : null;
        })
        .filter(entry => entry);
      if (entries.length) {
        makedir(entries).then(function(system_trees){
            var files = exactFile(system_trees, "", []);
            c(_data, files);
        }).catch(function(){
            var files = dT.files;
            c(_data, files);
        });
      } else {
            var files = dT.files;
            c(_data, files);
      }
    };
    
  • The 'dT' is drop event (e.originalEvent.dataTransfer) and '_data' that is Ajax upload submit form.
  • You also need a upload library or write it by yourself to send to file into server by ajax upload. In the code example above, I use formstone upload library (https://formstone.it/components/upload/)

  • At server side: You need to process the form data that was sent from JS side, al most case you need to process file by file with relative path info enclosed from the File object.

  • One more note, just set 'multiple' attribute at file upload input, NO need a 'webkitdirectory' or 'directory'. They caused the user can not choose the file (only folder allowed)

  • The result you will have the uploading system support all folder and files will keeping their structure like Google Drive :).

  • There is the tool (https://freetoolonline.com/zip-file.html) that I have wrote that supports folder upload based on the ideal I have explained above.

Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
Khoa Tran
  • 31
  • 4
0

We succesd to send a full tree of folder with DropZone. Folder as such are not uploaded, but each file will also send original path/local file system path. After is on server side where some work have to be done to properly handle it.

var dropzone = new Dropzone('my-div-class', {
    ...
    sending : function(file, xhr, formData) {
        // Add custom data to send with each file
        // file.fullPath contain relative path to filename from uploaded folder
        formData.append("originalFullPath", file.fullPath);
    }
})
Camille
  • 2,439
  • 1
  • 14
  • 32
-2

Unfortunately, only webkit-based browsers support the filesystem API at this point.

If you try to drop a folder in Google Drive using firefox or internet explorer, you will get an error message saying it's not supported.

iflp
  • 1,742
  • 17
  • 25