Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag & Drop support of folders (Chrome >= 21) #587

Closed
wants to merge 9 commits into from

Conversation

tiff
Copy link

@tiff tiff commented Jul 9, 2012

Chrome 21 implemented the possibility for folder drag & drop.

This patch adds a "relativePath" property to each instance of plupload.File.

Example: http://protonet.github.com/plupload/examples/drag_and_drop.html
More details to the chrome implementation: http://wiki.whatwg.org/wiki/DragAndDropEntries
Chromium issue: http://code.google.com/p/chromium/issues/detail?id=99823

Also this patch makes sure that the default behavior within dragover is only prevented when the drag contains any files otherwise there will be problems when eg. the drop_element is a <textarea> and the user is dragging text around.

@tiff
Copy link
Author

tiff commented Jul 10, 2012

Wrote a blog post containing more infos: http://blog.protonet.info/post/26894439416/html5-drag-drop-files-and-folders

@Jellyfrog
Copy link

👍

@frankbolviken
Copy link

What's the progress on this one? We need this functionality.

@jayarjo
Copy link
Contributor

jayarjo commented Mar 31, 2013

Checking if those are indeed files that are being dragged is interesting. Will bear this in mind.

@Arcara
Copy link

Arcara commented Apr 23, 2013

Hi

I would like to know if the folder uploading function will be incorporated to plupload 2, and why it's not in the latest releases of plupload 1.5.7 although the code is here. I'm trying to add it to this one, but I'm not able to add the files of a folder to the plupload component.

Thank you very much,
Carlos

@jayarjo
Copy link
Contributor

jayarjo commented Apr 23, 2013

It is incorporated in Plupload 2.

Regarding this code here, it is good, but still has some flaws. So that we cannot merge it directly. But more importantly the code for our html5 runtime in 1.x branch is already quite big and hard to follow. We decided to not extend it any further. So, only bugfixes. Plupload 2 will get all new features.

@Arcara
Copy link

Arcara commented Apr 24, 2013

If not too much to ask, do you know approximately when it will be available in the main branch of plupload 2?
It's very useful and I'm hesitating between waiting it or programming me on the server side another way to upload files keeping the folders structure (ej: uploading a zip file and uncompress it).

Thank you very much for your attention and congratulations for your work, plupload is the best upload component that I know.

@jayarjo
Copy link
Contributor

jayarjo commented Apr 24, 2013

It is in main branch already. The latest binaries are always available at moxie repo, ubder bin/ folder.

@Arcara
Copy link

Arcara commented Apr 24, 2013

I'm seeing the code, and I see the calls to webkitGetAsEntry and else, but I don't see any reference of relativePath or any other way to know the path of each file (of course, this is null when drag&drop folder and its files). Can it be done at present?

Thank you very much

@jayarjo
Copy link
Contributor

jayarjo commented Apr 24, 2013

Dropped folders are recursively checked for files that fulfill filter requirements and files that fit are added. That's it. How you planned to use relativePath or where did you expect it to appear?

@Arcara
Copy link

Arcara commented Apr 24, 2013

Yes, when I drag & drop a folder with subfolders and files inside, all the files appear in plupload, but I need to know what is the folder structure of each one of the files to uploading in order to I can clone the whole folder structure (with its files) in my server.
So, I thought that each one of the files in the array 'uploader.files' had a property '.relativePath' or similar in that manner I could do something like this:

jQuery('#uploader').plupload('getUploader').bind('BeforeUpload', function (up, file) {
       up.settings.multipart_params = {'originalFileName':file.name,'totalSize':file.size,'relativePath':file.relativePath}
});

@jayarjo
Copy link
Contributor

jayarjo commented Apr 24, 2013

Makes sense. Also shouldn't require too much coding to add I guess, I'll check...

@jayarjo
Copy link
Contributor

jayarjo commented Apr 25, 2013

After some thinking I came to a conclusion that it is more confusing than useful, mainly because it is not supported across browsers. Not sure why you would want to rely on something like that?

@Arcara
Copy link

Arcara commented Apr 25, 2013

It would be very useful and the easiest way to be able to upload files from different folders without mixing them and preserving the original order (that sometimes is complex and shouldn't be lost).
In many cases is necessary preserve in the server the same folder structure that in the CD/DVD/USB/HDD of the computer (I.E: to separate docs from videos and photos, and inside each one, to maintain all the subfolders with the date or place in wich they were made).
At present and without this function, to preserve the folder structure, firstly I have to create manually, one by one, all the folder structure (it can be big), and later go with plupload to each folder for uploading all its files, and go on with the next folder.
Although at the moment it only works with Chrome, it would be the only but at the same time a great way to reach that objetive much more easily and quickly.
Of course, if anyone use other browser different than Chrome (and I hope firefox provide in the future something similar to webkitGetAsEntry), .relativePath would be null and would have no other option that put all files in the same target folder.

Thank you very much for your attention
Carlos

@scottshipman
Copy link

has this been resolved in v2 yet? I am not seeing any relativePath definition similar to relativePath = directory.fullPath.replace(/^//, "").replace(/(.+?)/?$/, "$1/");

I agree that a way to duplicate a folder/subfolder structure on upload is a valuable feature, even if only supported by Chrome.

@foaly-nr1
Copy link

Guys, until the new release is out – to take some pressure from Davit – you might want to use a workaround.

Source: http://www.i-do-this.com/blog/plupload-2-1-chrome-and-folder-support/57

<div id="drop-target">
    <input type="file" id="files" />
</div>
<button id="uploadfiles">Upload!</button>

<script src="//cdn.jsdelivr.net/plupload/2.1.2/plupload.full.min.js"></script>
var uploader, traverseFileTree, map = {};

// replace by your plupload setup, this is just an example
uploader = new plupload.Uploader({
    runtimes : 'html5',
    container: 'drop-target',
    drop_element: 'drop-target',
    browse_button : 'files',
    url : 'http://www.torrentplease.com/dropzone.php',
    init: {
        PostInit: function() {
            document.getElementById('uploadfiles').onclick = function() {
                uploader.start();
                return false;
            };
        },
        BeforeUpload: function (up, file) {
            // send relativePath along
            if(map[file.name] !== undefined) {
                up.setOption('multipart_params', {
                    relativePath: map[file.name].shift()
                });
            }
        }
    }
});
uploader.init();

// all relative paths are built here
traverseFileTree = function (item, path) {
    var dirReader = null;
    path = path || '';
    if (item.isFile) {
        item.file(function(file) {
            // careful here, could be several files of the same name
            // we assume files will be in the same order here than in plupload
            if(map[file.name] === undefined) {
                map[file.name] = [];
            }
            map[file.name].push(path);
        });
    } else if (item.isDirectory) {
        dirReader = item.createReader();
        dirReader.readEntries(function (entries) {
            var n = 0;
            for (n = 0; n < entries.length; n++) {
                traverseFileTree(entries[n], path + item.name + "/");
            }
        });
    }
};

// bind another handler to the drop event to build an object representing the folder structure
document.getElementById('drop-target').addEventListener('drop', function(e) {
    var items = e.dataTransfer.items, n, item;
    for(n = 0; n < items.length; n++) {
        item = items[n].webkitGetAsEntry();
        if(item) {
            traverseFileTree(item);
        }
    }
}, false);

@ddellacosta
Copy link

After looking into implementing a version of foaly-nr1's workaround above, I realized that there are indeed some issues that need to be addressed, and it's probably a bad idea to go too far down this road right now.

There doesn't seem to be any API for getting the path of a File that is widely supported by browsers. The webkitGetAsEntry() call in foaly-nr1's code (which provides an Entry) is a part of the FileSystem API, which is apparently dead ("Work on this document has been discontinued and it should not be referenced or used as a basis for implementation.") as of April 2014 (via html5rocks.com) and is obviously not well supported in browsers other than Chrome and Opera. The other API for handling files that does seem to be supported widely is the File API, but that API doesn't seem to support anything that would allow a developer to get ahold of the path.

Point being, it seems that it would be a bad idea to depend on this feature even if it is incorporated in plupload at this time, based on the state of the relevant web specifications. While I don't agree with jayarjo that having the feature itself would be confusing, even if it were not fully supported by vendors--clearly there is developer demand for this, and developers can choose to ignore anything they don't feel comfortable implementing for whatever reason--otherwise I would agree it shouldn't be incorporated because it doesn't seem likely to be supported in the future.

I'd love to be shown to be wrong here--but as far as I can tell, the deceased FileSystem API is the only API that provides path information for files in conjunction with the Drag-and-Drop API. It also follows, sadly, that foaly-nr1's workaround is probably not a good thing to rely upon even in Chrome or Opera, as there is no guarantee this API could be available in any future releases.

@ddellacosta
Copy link

Follow-up counterpoint from my boss, for your consideration:

The HTML5 standard being abandoned doesn't necessarily imply that the chrome filesystem api
will be abandoned. They've already gone beyond what the HTML5 standard was proposing:
https://developer.chrome.com/apps/app_storage

Here is a discussion where the chrome team is saying that their api will remain and be supported
as they use it for their apps, and internally as well:
https://groups.google.com/a/chromium.org/forum/#!topic/chromium-apps/k39Lb1VYWEI

@tsmgeek
Copy link

tsmgeek commented Mar 9, 2015

I have merged this code into my pluploader, we have this on a live system and works well.
There is a bug due to the 100 file limit in webkit for readEntries();
You have to iterate over it until entries.length is false then you can process all the found files.
Below is altered code in walkFileSystem() which works.

                        function walkFileSystem(directory, callback, error) {
                                if (!callback.pending) {
                                        callback.pending = 0;
                                }
                                if (!callback.files) {
                                        callback.files = [];
                                }

                                callback.pending++;

                                var reader = directory.createReader(),
                                relativePath = directory.fullPath.replace(/^\//, "").replace(/(.+?)\/?$/, "$1/");
                                var allentries = [];

                                function toArray(list) {
                                        return Array.prototype.slice.call(list || [], 0);
                                }
                                var readEntries = function(){
                                reader.readEntries(function(entries) {
                                        if(!entries.length){
                                                callback.pending--;
                                                plupload.each(allentries, function(entry) {
                                                        if (entry.isFile) {
                                                                callback.pending++;
                                                                entry.file(function(File) {
                                                                        File.relativePath = relativePath + File.name;
                                                                        callback.files.push(File);
                                                                        if (--callback.pending === 0) {
                                                                                callback(callback.files);
                                                                        }
                                                                }, error);
                                                        } else {
                                                                walkFileSystem(entry, callback, error);
                                                        }
                                                });
                                                if (callback.pending === 0) {
                                                        callback(callback.files);
                                                }
                                        }else{
                                                allentries = allentries.concat(toArray(entries));
                                                readEntries();
                                        }
                                }, error);
                                };

                                readEntries();
                        }

@mscheper
Copy link

More information about @tsmgeek's fix (since I was just about to submit essentially the same fix): The root cause of the issue is that the code isn't following the Basic Concepts of the DirectoryReader class. See this StackOverflow post for details.

@jayarjo
Copy link
Contributor

jayarjo commented Aug 20, 2015

relativePath is now being retrieved where supported and can be accessed on the File object.

@tsmgeek , @mscheper we do not rely on length property and check the folders recursively. Can you check the code here: FileDrop.

Since this has been implemented. I'll close this one.

@jayarjo jayarjo closed this Aug 20, 2015
@foaly-nr1
Copy link

Great news, thanks @jayarjo!

@pafgoncalves
Copy link

How to make that property be posted?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet