HTML5 Rocks

HTML5 Rocks

Integrating input[type="file"] with the Filesystem API

By Eric Bidelman at

Let's say you have a photo editing app and you'd like users to be able to drag in hundreds of photos and copy them into your app. Ok, what do you do?

Launch Demo

In a recent post, Eiji Kitamura highlighted a subtle, yet powerful new feature in the drag and drop APIs; the ability to drag in folders and retrieve them as HTML5 Filesystem API FileEntry and DirectoryEntry objects (done by accessing a new method on the DataTransferItem, .webkitGetAsEntry()).

What's remarkably cool about the .webkitGetAsEntry() extension is how elegant it makes importing files and entire folders. Once you have a FileEntry or DirectoryEntry from a drop event, it's a matter of using the Filesystem API's copyTo() to get it imported into your app.

An example of copying multiple dropped folders over to the filesystem:

var fs = null; // Cache filesystem for later.

// Not shown: setup drag and drop event listeners.
function onDrop(e) {
  e.preventDefault();
  e.stopPropagation();

  var items = e.dataTransfer.items;

  for (var i = 0, item; item = items[i]; ++i) { 
    var entry = item.webkitGetAsEntry();

    // Folder? Copy the DirectoryEntry over to our local filesystem.
    if (entry.isDirectory) {
      entry.copyTo(fs.root, null, function(copiedEntry) {
        // ...
      }, onError);
    }
  }
}

window.webkitRequestFileSystem(TEMPORARY, 1024 * 1204, function(fileSystem) {
  fs = fileSystem;
}, function(e) {
  console.log('Error', e);
});

Very nice! Again, the simplicity comes from integrating DnD with the Filesystem API calls.

Taking this one step further, we also have the ability to drag and drop a folder and/or files onto a normal <input type="file">, then access the entries as Filesystem directory or file entries. That is done through .webkitEntries:

<input type="file" multiple>

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

  var entries = e.target.webkitEntries; // Get all dropped items as FS API entries.

  [].forEach.call(entries, function(entry) {

    // Copy the entry into our local filesystem.
    entry.copyTo(fs.root, null, function(copiedEntry) {
      ...
    }, onError);

  });
}

document.querySelector('input[type="file"]').addEventListener('change', onChange);

I've put together a photo gallery demo to demonstrate these different techniques for importing files/folders.

Launch Demo

To learn more about the HTML5 Filesystem API, see Exploring the Filesystem APIs.

Drag and drop a folder onto Chrome now available

By Eiji Kitamura at

As web apps evolve, you might have found it handy to let users drag and drop files from the desktop onto the browser to edit, upload, share, etc. But unfortunately, we’ve been unable to drag and drop folders onto web pages. Luckily, beginning with Chrome 21, this issue will be addressed (already available in the Chrome dev channel).

Passing multiple files with drag and drop

Let’s look at a code sample of existing drag and drop.

<div id=”dropzone”></div>

var dropzone = document.getElementById('dropzone');
dropzone.ondrop = function(e) {
  var length = e.dataTransfer.files.length;
  for (var i = 0; i < length; i++) {
    var file = e.dataTransfer.files[i];
    ... // do whatever you want
  }
};

In this example, you can actually drag and drop a file or files from the desktop to your browser, but when you try to pass a folder, notice that it will be either rejected or treated as a File object resulting in a failure.

How to handle dropped folders

Chrome 21 allows you to drop a folder or multiple folders into the browser window. To handle these, you need to change the way you handle dropped objects.

<div id=”dropzone”></div>

var dropzone = document.getElementById('dropzone');
dropzone.ondrop = function(e) {
  var length = e.dataTransfer.items.length;
  for (var i = 0; i < length; i++) {
    var entry = e.dataTransfer.items[i].webkitGetAsEntry();
    if (entry.isFile) {
      ... // do whatever you want
    } else if (entry.isDirectory) {
      ... // do whatever you want
    }
  }
};

Notice that a big difference here is that you can treat a dropped object as Entry (FileEntry or DirectoryEntry) by using new function called getAsEntry (webkitGetAsEntry).
After obtaining access to the Entry object, you can use standard file handling methods that were introduced in the FileSystem API specification. For example, this example shows how you can detect if a dropped object is a file or a directory by examining the .isFile (or the .isDirectory) field.

For further information regarding the FileSystem API, read Exploring the FileSystem APIs, regarding new drag and drop capability, read this document. Of course, these features are open standards or are waiting to become one soon.

Don't Build Blobs, Construct Them

By Eric Bidelman at

Here's a heads up to all you BlobBuilder fans...it's going bye bye!

BlobBuilder is a handy API for creating Blobs (or Files) in JavaScript. It's been around since Chrome 8, FF 6, and IE 10 but has never shipped in Safari,...and likely never will. Recent spec changes to the File API include a new constructor for Blob, which essentially makes BlobBuilder irrelevant. In fact, Safari nightlies have already disabled it and Chrome will start to warn you in the console very soon.

For comparison, below is the same code using the deprecated BlobBuilder and the new Blob constructor. The snippet creates a stylesheet and appends it to the DOM.

BlobBuilder():

window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
                     window.MozBlobBuilder || window.MSBlobBuilder;
window.URL = window.URL || window.webkitURL;

var bb = new BlobBuilder();
bb.append('body { color: red; }');
var blob = bb.getBlob('text/css');

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = window.URL.createObjectURL(blob);

document.body.appendChild(link);

Blob():

window.URL = window.URL || window.webkitURL;

var blob = new Blob(['body { color: red; }'], {type: 'text/css'});

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = window.URL.createObjectURL(blob);
document.body.appendChild(link);

Handy! So now, instead of appending to a BlobBuilder, we can simply create the Blob from an array of data parts. The data parts can be different types (DOMString, ArrayBuffer, Blob) and in any order. For example:

var blob = new Blob(['1234567890', blob, arrayBuffer]);

Also note that the second object param is optional. For more info on these changes, see the MDN docs on Blob.

Debugging the Filesystem API

By Eric Bidelman at

The HTML5 Filesystem is a powerful API. With power, comes complexity. With complexity, comes more debugging frustrations. It's an unfortunate fact that the Chrome DevTools currently do not have support for the Filesystem API. It makes debugging it more difficult than it should be. By difficult, I mean being required to write code to list/remove files in the filesystem.

During my endeavors with the Filesystem API, I picked up a couple of tips along the way that I thought I'd pass along. Each tip has its own set of limitations, but using a combination of these will get you 90% of the way. Here are the top 5:

  1. Make sure you aren't running from file://. This is a sneaky one that a lot of people get bit by. Chrome imposes major security restrictions on file://. Many of the advanced file APIs (BlobBuilder, FileReader, Filesystem API,...) throw errors or fail silently if you run the app locally from file://. If you don't have a web server handy, Chrome can be started with the --allow-file-access-from-files flag to bypass this security restriction. Only use this flag for testing purposes.

  2. The dreaded SECURITY_ERR or QUOTA_EXCEEDED_ERR. This usually happens when attempting to write data but you're under the influence of #1. If that's not the case, than it's likely that you don't have quota. There are two types of quota that the filesystem can be opened with, TEMPORARY or PERSISTENT. If you're using the latter, the user needs to explicitly grant persistent storage to your app. See this post on how to do that.

  3. filesystem: URL FTW. It's handy to open the filesystem: URL for the root DirectoryEntry of your app's origin. What does that mean? For example, if your app lives on www.example.com, open filesystem:http://www.example.com/temporary/ in a new tab. Chrome will show a read-only list of the files/folders stored your app origin. For more info on filesystem: URLs, see http://www.html5rocks.com/en/tutorials/file/filesystem/#toc-filesystemurls.

  4. chrome://settings/cookies is your friend. This page allows you to nuke the data stored for an origin. That includes database storage, AppCache, cookies, localStorage, and stuff in the FileSystem API. Be forewarned though, it's an all or nothing thing. You can't remove just one file or pieces of data.

  5. Don't forget about error callbacks. The Filesystem API lives in an asynchronous world (unless you're using the sync version in Workers). Always use error callback in your API calls. They're optional, but you'll save yourself a lot of grief later on when things blow up.

Saving generated files on the client-side

By Eli Grey at

Have you ever wanted to add a Save as… button to a webapp? Whether you're making an advanced WebGL-powered CAD webapp and want to save 3D object files or you just want to save plain text files in a simple Markdown text editor, saving files in the browser has always been a tricky business. Usually when you want to save a file generated with JavaScript, you have to send the data to your server and then return the data right back with a Content-disposition: attachment header. This is less than ideal for webapps that need to work offline. The W3C File API includes a FileSaver interface, which makes saving generated data as easy as saveAs(data, filename), though unfortunately it will eventually be removed from the spec. I have written a JavaScript library called FileSaver.js, which implements FileSaver in all modern browsers. Now that it's possible to generate any type of file you want right in the browser, document editors can have an instant save button that doesn't rely on an online connection. When paired with the standard HTML5 canvas.toBlob() method, FileSaver.js lets you save canvases instantly and give them filenames, which is very useful for HTML5 image editing webapps. For browsers that don't yet support canvas.toBlob(), Devin Samarin and I wrote canvas-toBlob.js. Saving a canvas is as simple as running the following code:

canvas.toBlob(function(blob) {
  saveAs(blob, filename);
});

I have created a demo of FileSaver.js in action that demonstrates saving a canvas doodle, plain text, and rich text. Please note that saving with custom filenames is only supported in browsers that either natively support FileSaver or browsers like Google Chrome 14 dev and Google Chrome Canary, that support <a>.download or web filesystems via LocalFileSystem.

View FileSaver.js demo

How to construct files for saving

First off, you want to instantiate a BlobBuilder. The BlobBuilder API isn't supported in all current browsers, so I made BlobBuilder.js which implements it. The following example illustrates how to save an XHTML document with saveAs().

var bb = new BlobBuilder();
bb.append((new XMLSerializer).serializeToString(document));
var blob = bb.getBlob("application/xhtml+xml;charset=" + document.characterSet);
saveAs(blob, "document.xhtml");

Not saving textual data? You can append binary Blobs and ArrayBuffers to a BlobBuilder too! The following is an example of setting generating some binary data and saving it.

var bb = new BlobBuilder();
var buffer = new ArrayBuffer(8); // allocates 8 bytes
var data = new DataView(buffer);

// You can write (u)int8/16/32s and float32/64s to dataviews
data.setUint8 (0, 0x01);
data.setUint16(1, 0x2345);
data.setUint32(3, 0x6789ABCD);
data.setUint8 (7, 0xEF);

bb.append(buffer);
var blob = bb.getBlob("example/binary");
saveAs(blob, "data.dat");
// The contents of data.dat are <01 23 45 67 89 AB CD EF>

If you're generating large files, you can implement an abort button that aborts the FileSaver.

var filesaver = new FileSaver(blob, "video.webm");
abort_button.addEventListener("click", function() {
  filesaver.abort();
}, false);

Seek into local files with the File System API

By Seth Ladd at

If you have a File object (say, one stored using the FileSystem API), it's possible to seek into it and read chunks without reading the entire file into memory:

var url = "filesystem:http://example.com/temporary/myfile.zip";

window.webkitResolveLocalFileSystemURL(url, function(fileEntry) {
  fileEntry.file(function(file) {
    var reader = new FileReader();

    reader.onload = function(e) {
      var ab = e.target.result; // arrayBuffer containing bytes 0-10 of file.
      var uInt8Arr = new Uint8Array(ab);
      ...
    };

    var blob = file.webkitSlice(0, 10, "application/zip");  // mimetype is optional
    reader.readAsArrayBuffer(blob);
  }, errorHandler);
}, errorHandler);

Downloading resources in HTML5: a[download]

By Eric Bidelman at

Chrome now supports the HTML spec's new download attribute to a elements. When used, this attribute signifies that the resource it points to should be downloaded by the browser rather than navigating to it.

From Downloading Resources:

The download attribute, if present, indicates that the author intends the hyperlink to be used for downloading a resource. The attribute may have a value; the value, if any, specifies the default filename that the author recommends for use in labeling the resource in a local file system. There are no restrictions on allowed values, but authors are cautioned that most file systems have limitations with regard to what punctuation is supported in file names, and user agents are likely to adjust file names accordingly.

The attribute can furthermore be given a value, to specify the filename that user agents are to use when storing the resource in a file system. This value can be overridden by the Content-Disposition HTTP header's filename parameter.

For example, clicking the following link downloads the .png as "MyGoogleLogo.png" instead of navigating to its href value: download me. The markup is simple:

<a href="http://www.google.com/.../logo2w.png" download="MyGoogleLogo">download me</a>

The real benefit of a[download] will be when working with blob: URLs and filesystem: URLs URLs. It'll give users a way to download content created/modified within your app.

Full Demo

Browser support: only the current Chrome dev channel release (14.0.835.15+) supports this attribute.