A user account is required in order to edit this wiki, but we've had to disable public user registrations due to spam.

To request an account, ask an autoconfirmed user on Chat (such as one of these permanent autoconfirmed members).

DragAndDropEntries

From WHATWG Wiki
Jump to navigation Jump to search

Proposing exposing dropped files/folders as {File,Directory}Entry defined in FileSystem API [1] for better folders/files drag-and-drop support (this proposal was originally proposed on whatwg mailing list).

[1] File API: Directories and System http://www.w3.org/TR/file-system-api/ [2] Original proposal email: http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-November/033814.html

Background

Many sites have 'upload your files' feature, like for your photo images. HTML5 allows you to do this via <input type="file" multiple> or drag-and-drop feature, but the current solution does not provide clean solution for cases with folders, files/folder mixed cases, or folders with subfolders cases.

Proposing API

Add a new field 'entries' to <input type=files> element [1] and a new accessor method 'getAsEntry()' to DataTransferItem object[2]. Populate the field with or return an 'Entry' object defined in FileSystem API upon file selection or drop events.

Since we didn't use to have a good interface to represent directories/folders, all webapps could access for file drag-and-drop was one or multiple files given as File objects.

On the other hand, this API proposes returning Entry instances [3], which can represent either file or directory. Via the Entry interface webapps can easily distinguish if a dropped entity is a file or directory by examining '.isFile' or '.isDirectory' field of the entry. If the entry is a directory, webapps can recursively walk over nested files and directories included in the directory by calling '.readEntries()' method of the directory reader for the entry.

This approach allows webapps to directly interact with the local folder structure, and also allows them to control the enumerating part so that the apps can show nice progress meter if they want.

Benefits

  • Enables folder drag-and-drop: Most obvious and direct advantage of this API is it enables folder/directories drag-and-drop both in <input type=file> and HTML5 native drag-and-drop, which was purely not possible in the current HTML5 specification.
  • Enables folders/files mixtured drag-and-drop: Similarly, since Entry interface can represent files and directories, the API naturally enables more advanced mixed cases in drag-and-drop, like multiple folders, multiple folders and files and so on.
  • Gives webapps full control of when and what to traverse: Unlike previous proposals for folder drag-and-drop[4], this approach does not require full-traversal of the dropped folder(s) by the user agent, but instead simply exposes an interface with which webapps can asynchronously interact with the dropped folder(s). This gives the webapps a greater control of when and what to traverse directories, or when to stop the traversal, or how to show a neat progress indicator to the users. This also means that users no longer need to worry about sluggish browser behavior when s/he has wrongly dropped a directory which contains a millions of nested files/directories.
  • Gives webapps direct read-only access to the user's local file tree: Since the API exposes a selected user's local folder with its complete tree structure, it opens up various interesting possibilities to webapps which was not possible without accessing directory structures. E.g. importing a user's Photos folders with album-like subfolders to a photo-album app, importing a user's source code tree to a web-based IDE, showing a nice file browser on a dropped media drive in a cover-flow like interface etc.

HTMLInputElement.entries

 interface HTMLInputElement : HTMLElement {
    // allows scripts to access the element's selected files as an array of Entry defined in FileSystem API.
    // On getting, if the IDL attribute applies, it must return a Entry array that represents the current selected files and/or folders.
    // If the IDL attribute does not apply, then it must instead return null.
    // Unless the multiple attribute is set, there must be no more than one file in the list of selected files.
    readonly attribute Entry[] entries;
 }

DataTransferItem.getAsEntry()

 interface DataTransferItem {
   // Like DataTransferItem.getAsFile(), getAsEntry() method runs following steps:
   // 1. If the DataTransferItem object is not in the read/write mode or the read-only mode, return null and abort these steps.
   // 2. If the drag data item kind is not File, then return null and abort these steps.
   // 3. Return a new Entry object representing the actual file or directory represented by the DataTransferItem object.
   Entry getAsEntry();
 };

Isolated FileSystem

The Entry exposed by this feature is created in an Isolated filesystem, which is read-only, isolated and transient. The filesystem only contains the dropped items and can only be accessed by the site where the drag-and-drop event has happened. Webapps cannot access any other files or folders outside the filesystem.

Restrictions

  • The Entry in isolated filesystem does not support toURL() or is not resolvable by resolveFileSystemURL(), since its filesystem is transient and has limited life time (while toURL URL's do not work as live references like Blob URLs). The dropped entries are only accessible while the app has live instance for the entries.
  • The Entry's fullPath does not (and should not) expose the file's full platform path as it could contain a sensitive information. Instead it MAY contain a relative path to the top directory that the user dropped.

Example

Assume a user has following files and folders in his/her 'Photos' folder:

   /Users/username/Photos/trip/1.jpg
   /Users/username/Photos/trip/2.jpg
   /Users/username/Photos/trip/3.jpg
   /Users/username/Photos/halloween/2011/a.jpg
   /Users/username/Photos/halloween/2011/b.jpg
   /Users/username/Photos/halloween/2012/plan.jpg
   /Users/username/Photos/tokyo/1.jpg
   /Users/username/Photos/tokyo/2.jpg

When the user drag-and-drops or selects the two folders, trip, halloween and one photo file tokyo/1.jpg, the webapp will get following entries via 'entries' field of the input element or via DataTransferItem.getAsEntry():

   DirectoryEntry for 'trip',
   DirectoryEntry for 'halloween'
   FileEntry for '1.jpg'

Via the new 'entries' field the app can access children files or subfolders in the selected entries, and can properly organize and process pictures using the local folder structure. Even if the dropped directory is on a slow removable media, or it contains tons of subdirectories and files, the app can recursively traverse each of them while showing a nice progress information, or can show an interactive file tree which lets the user freely expand or select some of the contents.

We can think of similar interesting usage scenarios like local-cloud sync app or bulk 'importer', e.g. importing local source directory to cloud IDE etc.

Example Code (Drag and Drop)

  var droptarget = document.getElementById('droptarget');
 
  droptarget.addEventListener('drop', function (e) {
    e.stopPropagation();
    e.preventDefault();

    var items = e.dataTransfer.items;
    for (var i = 0; i < items.length; ++i) { 
      if (items[i].kind != "file")
         continue;
 
      var entry = items[i].webkitGetAsEntry();    // WebKit/chromium needs 'webkit' prefix (since Chrome 21)
  
     // import the dropped files/folders into temporary filesystem
     entry.copyTo(temporaryFs.root, entry.name, successCallback, errorCallback);
  });

Example Code (Input)

 <input type="file" multiple />
  var input = document.querySelector("input[type='file']");
 
  input.addEventListener('change', function (e) {
    e.stopPropagation();
    e.preventDefault();
 
    var entries = e.target.webkitEntries;        // WebKit/chromium needs 'webkit' prefix (since Chrome 22)
 
    for (var i = 0; i < entries.length; ++i) { 
      if (entries[i].isDirectory) {
        traverseDirectoryTree(entry);
      } else {
        showFileEntry(entries[i]);
      }
  });

References