8

I've been able to write JavaScript to cause the browser to download a file from a remote server using code like this:

var iframe = document.createElement("iframe");
iframe.style.display = "none";
iframe.src = "filename.zip"
document.body.appendChild(iframe);

Which works great. However, now I have a different situation where the contents of the file are stored in a string in my JavaScript on the browser side and I need to trigger a download of that file. I've tried replacing the third line above with this, where 'myFileContents' is the string containing the actual bytes of the file:

iframe.src = "data:application/octet-stream;base64," + Base64.encode(myFileContents);

This gets the file downloaded, but the file name is lost. In Chrome the file name is just 'download'. Also I've read that there are limitations to the file size allowed in some browser versions.

Is there a way to achieve this? Using JQuery would be OK. The solution needs to support any file type - zip, pdf, csv, png, jpg, xls, etc...

Mathijs Flietstra
  • 12,900
  • 3
  • 38
  • 67
Sean N.
  • 963
  • 2
  • 10
  • 23

1 Answers1

10

In some newer browsers you can use the new HTML5 download attribute on the a tag to achieve this:

var a = document.createElement('a');
a.download = "filename.txt";
a.href = "data:application/octet-stream;base64," + Base64.encode(myFileContents);
a.click();

For a future solution you could look into the HTML5 FileSystem API, but this API is not currently supported in most of the major browsers. It might not be of much use to you except for that it might provide you with another way to store the files locally if you would be OK with that. But it doesn't store the files on the users locally accessible file system, you would have to develop your own browser based interface for your users to interact with the files. Downloading the files from the HTML5 file system to the users local file system would in any case again be done using the new download attribute on an a tag, which would then refer to a location in the HTML5 file system instead of referring to an online location.

To do this with an iframe element you would have to somehow set the Content-Disposition request header on the iframe to inline; filename="filename.txt" using client side JavaScript, I don't think it is possible to do this, most likely because of security issues. If you really don't have any other option, you could kill the download speed performance by sending the string to the server using AJAX and then downloading it from there again with the right request headers set.

Mathijs Flietstra
  • 12,900
  • 3
  • 38
  • 67
  • Thanks. I did find another post where someone mentioned the trick of posting the file contents and filename to the web server, and then having it return the full response with Content-disposition header. An example implementation is here: https://code.google.com/p/download-data-uri/. I was trying to avoid that method because of performance issues by the redundant transmission. I'll try out the 'a.download' trick and look into what browsers support it. Thanks. – Sean N. May 09 '13 at 14:49
  • Your solution works very well in Chrome, but does not currently work in any other browsers. I'm accepting this answer until a better suggestion is made. I was thinking there might still be a way to use the iframe trick with some sort of fake ajax post and fake response, but I wouldn't know how to do this. – Sean N. May 22 '13 at 20:02
  • 4
    Instead of `a.click()` you can use `var clickEvent = document.createEvent("MouseEvent"); clickEvent.initEvent("click", true, true); a.dispatchEvent(clickEvent); ` to make it work in Firefox & Chrome – Sean N. May 22 '13 at 21:57
  • this now doesn't work with Chrome 65 https://developers.google.com/web/updates/2018/02/chrome-65-deprecations – Paulius Dragunas Mar 23 '18 at 18:11