Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

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

Open
wants to merge 9 commits into from

8 participants

Christopher Blum Jellyfrog frankbolviken Davit Barbakadze Arcara Scott Shipman foaly-nr1 Dave Della Costa
Christopher Blum

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.

frankbolviken

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

Davit Barbakadze
Owner

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

Arcara

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

Davit Barbakadze
Owner

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

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.

Davit Barbakadze
Owner

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

Arcara

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

Davit Barbakadze
Owner

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

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}
});
Davit Barbakadze
Owner

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

Davit Barbakadze
Owner

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

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

Scott Shipman

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

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);
Dave Della Costa

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.

Dave Della Costa

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 15, 2011
  1. HTML5: Check whether actual files are being dragged/dropped before ca…

    Christopher Blum authored
    …ncelling the corresponding dom events
  2. Make the drag/drop stuff work in Firefox 3.6+

    Christopher Blum authored
  3. Move the 'Standard' comment to the correct line

    Christopher Blum authored
Commits on Apr 10, 2012
  1. Merge remote-tracking branch 'plupload/master'

    Christopher Blum authored
    Conflicts:
    	src/javascript/plupload.html5.js
Commits on Jul 6, 2012
  1. Christopher Blum
  2. Christopher Blum
  3. Christopher Blum

    Drag & drop example

    tiff authored
Commits on Aug 2, 2012
  1. Christopher Blum
Commits on Sep 24, 2012
  1. Christopher Blum

    Fix ios6 'image.jpg' issue

    tiff authored
This page is out of date. Refresh to see the latest.
89 examples/drag_and_drop.html
View
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Plupload - Drag &amp; drop example</title>
+ <style>
+ body {
+ font-family: Verdana, Geneva, sans-serif;
+ font-size: 13px;
+ color: #333;
+ background: url(bg.jpg);
+ width: 620px;
+ margin: 40px auto;
+ }
+ #drop-target {
+ border: 10px dashed #999;
+ text-align: center;
+ color: #999;
+ font-size: 20px;
+ width: 600px;
+ height: 300px;
+ line-height: 300px;
+ cursor: pointer;
+ }
+
+ #drop-target.dragover {
+ background: rgba(255, 255, 255, 0.4);
+ border-color: green;
+ }
+
+ #debug {
+ margin-top: 20px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="drop-target">Drop your files or folders (Chrome >= 21) here</div>
+
+ <div id="debug">No runtime found, your browser doesn't support HTML5 drag &amp; drop upload.</div>
+ <script type="text/javascript" src="../src/javascript/plupload.js"></script>
+ <script type="text/javascript" src="../src/javascript/plupload.html5.js"></script>
+ <script type="text/javascript">
+ // Custom example logic
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ var uploader = new plupload.Uploader({
+ runtimes : 'html5',
+ drop_element : 'drop-target',
+ browse_button : 'drop-target',
+ max_file_size : '10mb',
+ upload : "upload.php"
+ });
+
+ uploader.bind('Init', function(up, params) {
+ if (uploader.features.dragdrop) {
+ $('debug').innerHTML = "";
+
+ var target = $("drop-target");
+
+ target.ondragover = function(event) {
+ event.dataTransfer.dropEffect = "copy";
+ };
+
+ target.ondragenter = function() {
+ this.className = "dragover";
+ };
+
+ target.ondragleave = function() {
+ this.className = "";
+ };
+
+ target.ondrop = function() {
+ this.className = "";
+ };
+ }
+ });
+
+ uploader.bind('FilesAdded', function(up, files) {
+ for (var i in files) {
+ $('debug').innerHTML += '<div id="' + files[i].id + '">- ' + files[i].relativePath + ' (' + plupload.formatSize(files[i].size) + ')</div>';
+ }
+ });
+
+ uploader.init();
+ </script>
+ </body>
+</html>
91 src/javascript/plupload.html5.js
View
@@ -211,27 +211,80 @@
*/
init : function(uploader, callback) {
var features, xhr;
-
+
+ function hasFiles(dataTransfer) {
+ if (!dataTransfer || typeof(dataTransfer.files) === "undefined") {
+ return false;
+ }
+
+ var types = plupload.toArray(dataTransfer.types || []);
+ return types.indexOf("public.file-url") !== -1 || // Safari < 5
+ types.indexOf("application/x-moz-file") !== -1 || // Gecko < 1.9.2 (< Firefox 3.6)
+ types.indexOf("Files") !== -1 || // Standard
+ types.length === 0;
+ }
+
+ 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/");
+ reader.readEntries(function(entries) {
+ callback.pending--;
+ plupload.each(entries, 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);
+ }
+ }, error);
+ }
+
function addSelectedFiles(native_files) {
- var file, i, files = [], id, fileNames = {};
+ var file, i, files = [], id, fileName, fileNames = {};
// Add the selected files to the file queue
for (i = 0; i < native_files.length; i++) {
file = native_files[i];
-
+ fileName = file.fileName || file.name;
+
+ // Safari on iOS 6 will name each picture "image.jpg"
+ if (fileName === "image.jpg" && native_files.length > 1) {
+ fileName = "image_" + (i + 1) + ".jpg";
+ }
+
// Safari on Windows will add first file from dragged set multiple times
// @see: https://bugs.webkit.org/show_bug.cgi?id=37957
- if (fileNames[file.name]) {
+ if (fileNames[fileName]) {
continue;
}
- fileNames[file.name] = true;
+ fileNames[fileName] = true;
// Store away gears blob internally
id = plupload.guid();
html5files[id] = file;
// Expose id, name and size
- files.push(new plupload.File(id, file.fileName || file.name, file.fileSize || file.size)); // fileName / fileSize depricated
+ files.push(new plupload.File(id, fileName, file.fileSize || file.size, file.relativePath)); // fileName / fileSize deprecated
}
// Trigger FilesAdded event if we added any
@@ -380,7 +433,7 @@
// Get or create drop zone
dropInputElm = document.getElementById(uploader.id + "_drop");
- if (!dropInputElm) {
+ if (!dropInputElm && hasFiles(e.dataTransfer)) {
dropInputElm = document.createElement("input");
dropInputElm.setAttribute('type', "file");
dropInputElm.setAttribute('id', uploader.id + "_drop");
@@ -423,16 +476,30 @@
// Block browser default drag over
plupload.addEvent(dropElm, 'dragover', function(e) {
- e.preventDefault();
+ if (hasFiles(e.dataTransfer)) {
+ e.preventDefault();
+ }
}, uploader.id);
// Attach drop handler and grab files
plupload.addEvent(dropElm, 'drop', function(e) {
var dataTransfer = e.dataTransfer;
-
- // Add dropped files
- if (dataTransfer && dataTransfer.files) {
- addSelectedFiles(dataTransfer.files);
+
+ if (!hasFiles(dataTransfer)) {
+ return;
+ }
+
+ var items = dataTransfer.items || [], files = dataTransfer.files, firstEntry;
+ if (items[0] && items[0].webkitGetAsEntry && (firstEntry = items[0].webkitGetAsEntry())) {
+ // Experimental way of uploading entire folders (only supported by chrome >= 21)
+ walkFileSystem(firstEntry.filesystem.root, function(files) {
+ addSelectedFiles(files);
+ }, function() {
+ // Fallback to old way when error happens
+ addSelectedFiles(files);
+ });
+ } else {
+ addSelectedFiles(files);
}
e.preventDefault();
10 src/javascript/plupload.js
View
@@ -1603,7 +1603,7 @@
* @param {String} name File name.
* @param {Number} size File size in bytes.
*/
- plupload.File = function(id, name, size) {
+ plupload.File = function(id, name, size, relativePath) {
var self = this; // Setup alias for self to reduce code size when it's compressed
/**
@@ -1631,6 +1631,14 @@
self.size = size;
/**
+ * Relative path in original file system
+ *
+ * @property relativePath
+ * @type String
+ */
+ self.relativePath = relativePath || name;
+
+ /**
* Number of bytes uploaded of the files total size.
*
* @property loaded
Something went wrong with that request. Please try again.