Eli Grey

Saving generated files on the client-side

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.

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
    , buffer = new ArrayBuffer(8) // allocates 8 bytes
    , data = new DataView(buffer)
;
// You can write uint8/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 = saveAs(blob, "video.webm");
abort_button.addEventListener("click", function() {
    filesaver.abort();
}, false);
  • http://about.me/andrewrabon Andrew

    Wow, this is all very useful. Thanks!

  • Eric Bidelman

    Love this! Just an FYI though, Chrome 13 no longer needs –unlimited-quota-for-files flags for the filesystem API to work. Also, –allow-file-access-from-files should only be used for testing as it allows access to file:// and is a security risk.

    • http://eligrey.com Eli Grey

      Thanks, I updated the post.

      • Eric Bidelman

        Whoa! Creepy fast sir. Creepy fast.

  • Eric Bidelman

    You may already know this, but a.download (http://developers.whatwg.org/links.html#downloading-resources) is now in Chrome Dev channel. It might be the better fallback to saving a named file than the FileSystem API. Although for the moment, both of these are only in Chrome :(

    • http://eligrey.com Eli Grey

      Wow, I didn’t know that! I’m going to look into adding that into FileSaver.js this instant. Also, I just experimented with it a bit and it looks like Chrome doesn’t follow the download property precisely when there isn’t a filename extension but a known media type is used (e.g. it saves foo.txt if the type is text/plain and the filename is only “foo”). I’d prefer if developers had more control over this. Other than that, this is perfect when combined with object URLs!

  • http://www.visual-experiments.com/ Henri

    Hi! Thanks a lot for both BlobBuilder and FileSaver! I’ve used them for my Google Chrome extension: 
    http://www.visual-experiments.com/2012/01/15/photosynth-webgl-viewer-with-html5-fileaver/

  • belltoy

    There is an error in Safari 5.1 for Mac:BlobBuilder.js:101
    RangeError: Maximum call stack size exceeded.

  • Pingback: Fájl mentés kliens oldalon | Farkas Máté