Save/download data generated in JavaScript

Because I was bored I wrote a HTML5 player for Magnatune: Magnatune Player Greattune Player [repo]. Besides learning jQuery, HTML5 audio and HTML Drag and Drop I also developed a method to save playlists to files.

Everything except the search is implemented in JavaScript, so the server never knows the playlist. Because I didn't want to send the playlist to the server just so the user can download it I investigated what methods there are to save data generated in JavaScript. I came up with the solution presented here. It does not use Flash or a echo server and is therefore supported in every recent browser except Internet Explorer before the version 10.

Feature test: Does this browser support the download attribute on anchor tags? (currently only Chrome)

Use any available BlobBuilder/URL implementation:

IE 10 has a handy navigator.msSaveBlob method. Maybe other browsers will emulate that interface? See MSDN about it.

Anyway, HMTL5 defines a very similar but more powerful window.saveAs function:

However, this is not supported by any browser yet. But there is a compatibility library called FileSaver.js (github) that adds this function to browsers that support Blobs (except Internet Exlorer).

Mime types that (potentially) don't trigger a download when opened in a browser:

Blobs and saveAs (or saveBlob)

Currently only IE 10 supports this, but I hope other browsers will also implement the saveAs/saveBlob method eventually.

I don't assign saveAs to navigator.saveBlob (or the other way around) because I cannot know at this point whether future implementations require these methods to be called with 'this' assigned to window (or naviagator) in order to work. E.g. console.log won't work when not called with this === console.

Blobs and object URLs

Currently WebKit and Gecko support BlobBuilder and object URLs.

Currently only Chrome (since 14-dot-something) supports the download attribute for anchor elements.

Now I need to simulate a click on the link. IE 10 has the better msSaveBlob method and older IE versions do not support the BlobBuilder interface and object URLs, so we don't need the MS way to build an event object here.

In other browsers I open a new window with the object URL. In order to trigger a download I have to use the generic binary data mime type "application/octet-stream" for mime types that browsers would display otherwise. Of course the browser won't show a nice file name here.

The timeout is probably not necessary, but just in case that some browser handle the click/window.open asynchronously I don't revoke the object URL immediately.

Using the filesystem API you could do something very similar. However, I think this is only supported by Chrome right now and it is much more complicated than this solution. And chrome supports the download attribute anyway.

data:-URLs

IE does not support URLs longer than 2048 characters (actually bytes), so it is useless for data:-URLs. Also it seems not to support window.open in combination with data:-URLs at all.

Note that encodeURIComponent produces UTF-8 encoded text. The mime type should contain the charset=UTF-8 parameter. In case you don't want the data to be encoded as UTF-8 you could use escape(data) instead.

Internet Explorer before version 10 does not support any of the methods above. If it is text data you could show it in an textarea and tell the user to copy it into a text file.

A small example using the sowSave function:

See how it looks like.

Update: The BlobBuilder interface is now deprecated. Instead the Blob interface is new constructible. So in very recent browsers one can write:

But for compatibility with older versions of Firefox, Chrome/WebKit and Opera one has to support the BlobBuilder interface anyway. See the HTML5 File API.

Comments

  1. Nice! Have you thought about using oWin.document.execCommand('SaveAs') for IE<10?

    http://stackoverflow.com/a/4458807/809536

    I've looked at your GitHub page, but couldn't see this code. Thoughts about starting a project?

    ReplyDelete
    Replies
    1. Ah nice! (Even though you can only save .txt and .html files this way.)
      I use this code in my "Greattune Player" (formerly known as Magnatune Player). This is not on github but on bitbucket (I like mercurial better than git so I use it if there is no good reason not to): https://bitbucket.org/panzi/greattune-player

      Delete
    2. Mmm, your code has stopped working with Chrome for us, I've had a message for a while that I ignored... probably that BlobBuilder was being deprecated. Have you worked on an update by any chance?

      Delete
    3. Found your new version here: https://bitbucket.org/panzi/greattune-player/src/0a70d90582d5eff379f4ad8801c2f534489d44bc/javascripts/magnatune.js?at=default

      I'm getting an exception in Chrome, tag undefined, it'll probably be a scoping error in my code

      Delete
    4. tag is a helper function which I wrote. I guess one does not really need it. Using jQuery's DOM building capabilities is concise enough.

      As for BlobBuilder: I didn't change the code in the article but I added an update notice at the bottom a while ago that mentions the deprecation of BlobBuilder and the new constructible Blobs.

      Delete
    5. I want to retrive hyperlinks . i am also using blob but after exporting excel the hyperlinks are not visible

      Delete
    6. I want to retrive hyperlinks . i am also using blob but after exporting excel the hyperlinks are not visible

      Delete
    7. I still don't understand what you want to do. What I wrote has nothing to do with hyperlinks or their visibility. Do you want to load a file from an url in JavaScript into a Blob? This is only possible from the same origin and is something completely different to what I described here anyway.

      Delete
  2. Very interesting, need to try this ASAP.

    ReplyDelete
  3. When I download the file in firefox, it saves it with a random name. Is there a fix?

    ReplyDelete
    Replies
    1. Sadly no. Only the download attribute and the saveAs method allow to define a file name, but currently only Chrome supports the download attribute and saveAs is currently only supported by IE 10. I think it is time for other browsers to catch up (Firefox and Opera).

      Delete
  4. Nice example, thanks!

    But, for real-word use: do I see it right that you'd need to maintain as many local playlists as the number of different browsers -- permuted by host environments -- you use?

    Now that web applications shift to the client side, and "client-only" apps shift to online, and thus browsers are more and more our default all-around platforms: is there any hope that the W3C finally recognizes the there is this entity there, the *local user* being in the center of all this, and not just a "visitor" any more, but really "The Client", and allow him/her to do real application work, rather than living in a crippled sandbox forever due to browser paranoia? :-/

    ReplyDelete
    Replies
    1. "do I see it right that you'd need to maintain as many local playlists as the number of different browsers -- permuted by host environments -- you use?"

      Because I store them in the browsers local storage, yes. But you can export the playlist to a file and import them in another browser. You can even drag and drop playlists between browsers (if both support HTML5 D'n'D).

      But I wouldn't at all call it browser paranoia. If any webpage could just access the users file system this could have devastating consequences (wiping the users HD, scanning for bank/credit card details etc.).

      Yes, there should be a way to do more, but in a secure manner. Mozilla is developing some such APIs for Firefox OS and they want to propose them as standard:
      https://developer.mozilla.org/en-US/docs/Web/Apps/Reference

      However, these APIs might be limited in another way: They aim at mobile platforms (mainly phones), not the desktop.

      Delete
  5. That is very helpful example, but what about file name? In IE you are using 'name' parameter, but for Chrome and FF not

    ReplyDelete
    Replies
    1. If you use a link then the download attribute can be used to set the file name (if the browser supports it). Chrome supports this attribute and so does Firefox in general. Though I'm not sure if it works in Firefox with object URLs, because in Firefox it only works if the downloaded file is from the same origin than the page. I don't know if object URLs count as such.

      Delete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. Thanks for this! Helped out a lot for exporting hundreds of data files

    ReplyDelete
  8. Its possible specify default download folder with javascript?

    ReplyDelete
    Replies
    1. Sorry, no. You don't know such things in browser JavaScript. Heck, you don't even know if the OS running the browser has the concept of folders.

      Delete
  9. (function () {
    var textFile = null;
    makeTextFile = function (text) {
    var data = new Blob([text], {type: 'text/html; charset=UTF-8'});
    if (textFile !== null) {
    window.URL.revokeObjectURL(TextFile);
    }
    textFile=window.URL.createObjectURL(data);
    return textFile;
    }
    mems="var sacsas='"+mems+"'"
    var link = document.getElementById('downloadlink');
    link.href = makeTextFile(mems);
    link.style.display = 'block';

    })();

    This code everytime writing new file, info.txt, info(1).txt, info(2).txt.... Please, now I can rewrite files in some, only one file? Thank You

    ReplyDelete
    Replies
    1. You can't really "write a file". You can only provide a BLOB to the user who can then save that file. It's your browser that renames it to "info (1).txt" in order to prevent name conflicts. It's just exactly the same as with other downloads.

      Delete
  10. sorry, meybe Yoy don't understand me... I want write new data in exist file info.txt, rewrite this file, with new data... I don't want write new, new, new files... I want changes data in one file info.txt... Everything in Crome Google, or Mozilla... Please, help me

    ReplyDelete
    Replies
    1. Do you mean you want to rewrite the file without user interaction? Without a new "Save As" dialog? That is not possible for security reasons.

      Delete
  11. This comment has been removed by the author.

    ReplyDelete
  12. I want REWRITE this file info.txt, not making new info (1,2,3,...).txt files

    ReplyDelete
    Replies
    1. That's not possible. You can only tell the browser that there is data that the user might want to save and that "info.txt" is the *suggested* file name. What happens after that is up to the browser and/or user.

      Delete
  13. thank you, I understand, but it is possible in IE

    ReplyDelete
    Replies
    1. Really? How? Do you have a link to the appropriate documentation on MSDN?

      Delete

Post a Comment

Popular posts from this blog

Reverse engineering a simple game archive

How to write a binary file format