367

I've been fiddling with WebGL lately, and have gotten a Collada reader working. Problem is it's pretty slow (Collada is a very verbose format), so I'm going to start converting files to a easier to use format (probably JSON). I already have the code to parse the file in JavaScript, so I may as well use it as my exporter too! The problem is saving.

Now, I know that I can parse the file, send the result to the server, and have the browser request the file back from the server as a download. But in reality the server has nothing to do with this particular process, so why get it involved? I already have the contents of the desired file in memory. Is there any way that I could present the user with a download using pure JavaScript? (I doubt it, but might as well ask...)

And to be clear: I am not trying to access the filesystem without the users knowledge! The user will provide a file (probably via drag and drop), the script will transform the file in memory, and the user will be prompted to download the result. All of which should be "safe" activities as far as the browser is concerned.

[EDIT]: I didn't mention it upfront, so the posters who answered "Flash" are valid enough, but part of what I'm doing is an attempt to highlight what can be done with pure HTML5... so Flash is right out in my case. (Though it's a perfectly valid answer for anyone doing a "real" web app.) That being the case it looks like I'm out of luck unless I want to involve the server. Thanks anyway!

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
Toji
  • 33,927
  • 22
  • 105
  • 115
  • 9
    You could consider changing the accepted answer, there seems to be a purely HTML way now – Pekka Jan 23 '13 at 10:59

18 Answers18

331

Simple solution for HTML5 ready browsers...

function download(filename, text) {
    var pom = document.createElement('a');
    pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    pom.setAttribute('download', filename);

    if (document.createEvent) {
        var event = document.createEvent('MouseEvents');
        event.initEvent('click', true, true);
        pom.dispatchEvent(event);
    }
    else {
        pom.click();
    }
}

Usage

download('test.txt', 'Hello world!');
OrangeDog
  • 36,653
  • 12
  • 122
  • 207
Matěj Pokorný
  • 16,977
  • 5
  • 39
  • 48
  • 2
    If you know the source url that you want to download this is the best solution! – sidonaldson Aug 16 '13 at 14:58
  • 40
    The ability to set the file-name makes this a winner. – Omn Dec 21 '13 at 00:50
  • 2
    Pretty cool. Here's a JSFiddle of it in action: http://jsfiddle.net/jjorsett/VkRL6/ – John Jorsett May 07 '14 at 20:14
  • 9
    This method worked in Chrome until the latest update which I recieved a few days ago (35.0.1916.114 m). Now it works partially in that the filename and extension no longer work (it always names the file download.txt regardless of what is passed.) – Sevin7 May 27 '14 at 11:03
  • download attribute is only supported fully by Chrome. Firefox only supports download attribute if URL meets `same origin` policy requirements. I suppose data urls don't meet this requirement since you can technically get the data from anywhere. – vogomatix Aug 13 '14 at 15:20
  • @Yassir Ennazk's answer below works fine even in the latest Chrome. – Alexander Amelkin Nov 12 '14 at 11:55
  • Works like a charm in Chrome – slott Apr 10 '15 at 15:24
  • 1
    Won't work in current versions of IE or Safari because they don't support the download attribute. http://caniuse.com/#feat=download – ccnokes Apr 27 '15 at 16:51
  • 7
    I have Chrom 42.0.2311.90 , and this is workingfor me with the expected filename. – Saurabh Kumar Apr 30 '15 at 20:50
  • @Fiona And [this](http://stackoverflow.com/a/18197341/2438165), slightly different version? I don't have IE11... – Matěj Pokorný Jul 10 '15 at 07:51
  • @ Matěj Pokorný For IE11, we could resort to iframe: – Fiona Jul 15 '15 at 06:26
  • Note that the entire `if` `else` statement is not necessary if you simply put the save button inside the `a` tag. This probably increases browser compatibility as well. – jobukkit Sep 21 '16 at 15:47
  • 4
    If there a limit on the amount of data that can be included? – Kaspar Lee Oct 21 '16 at 18:12
  • @Druzion -- *"If there a limit on the amount of data that can be included?"* -- I'd be interested in that as well, I have 1GB + data here that I need to offer for downloading (otherwise, with small data, I would simply do this through a server). – John Weisz Feb 01 '17 at 15:55
  • 2
    @Druzion Firefox: ∞, Edge: 4 GB, Chrome: [only 2 MB](https://craignicol.wordpress.com/2016/07/19/excellent-export-and-the-chrome-url-limit/)? – Matěj Pokorný Jul 16 '17 at 16:45
  • @sidonaldson - How can I use a URL to download the file from instead pushing data stream? Eg. I have pre-signed URL from S3 that I want to use to download the files. How can I replace the "hello world" with the file that my s3 url pushes? Appericiate response – Swanidhi May 22 '19 at 12:30
  • @Swanidhi this isn't the solution you want to download a file from S3 try this one https://stackoverflow.com/a/19230609/628700 – sidonaldson May 22 '19 at 17:09
  • event.initEvent is now deprecated – mrblue6 Feb 22 '23 at 17:32
274

OK, creating a data:URI definitely does the trick for me, thanks to Matthew and Dennkster pointing that option out! Here is basically how I do it:

1) get all the content into a string called "content" (e.g. by creating it there initially or by reading innerHTML of the tag of an already built page).

2) Build the data URI:

uriContent = "data:application/octet-stream," + encodeURIComponent(content);

There will be length limitations depending on browser type etc., but e.g. Firefox 3.6.12 works until at least 256k. Encoding in Base64 instead using encodeURIComponent might make things more efficient, but for me that was ok.

3) open a new window and "redirect" it to this URI prompts for a download location of my JavaScript generated page:

newWindow = window.open(uriContent, 'neuesDokument');

That's it.

Cedric Reichenbach
  • 8,970
  • 6
  • 54
  • 89
Nøk
  • 2,764
  • 2
  • 15
  • 2
  • 37
    If you want to avoid using a popup, which can get blocked, you can set the `location.href` to the content. `location.href = uriContent`. – Alex Turpin Oct 03 '11 at 16:41
  • 12
    Hi i tried this but it downloads the file as .part file. How can i set the filetype? – Sedat Başar Nov 15 '11 at 08:09
  • 2
    The data uri scheme supports setting the mime type, wikipedia's article has some examples http://en.wikipedia.org/wiki/Data_URI_scheme#Examples – Boushley Mar 23 '12 at 03:51
  • 7
    @SedatBaşar Data URIs don't support setting a file name, you have to rely on the browser setting an appropriate extension using the mime type. But if the mime type can rendered by the browser it won't download it, but it will display it. There are some other ways to do this, but neither work in IE<10. – panzi May 19 '12 at 18:01
  • 1
    @panzi: What are those other ways you are referring to? – Randomblue Aug 23 '12 at 20:31
  • 7
    IE does not really sopport data URIs and Firefox seems to save the files with a random name. – nylund Feb 01 '13 at 08:34
  • 27
    I think we're making this more difficult than it need be. Open your JS console on this page and put it `location.href = "data:application/octet-stream," + encodeURIComponent(jQuery('#answer-4551467 .post-text').first().text());` and it will save the content of @Nøk's answer to a file. I don't have IE to test it, but it works for webkit. – Bruno Bronosky Mar 25 '13 at 21:10
  • 1
    @SedatBaşar See this answer below: http://stackoverflow.com/questions/2897619/using-html5-javascript-to-generate-and-save-a-file/18197511#18197511 – Omn Dec 21 '13 at 00:52
  • 1
    @BrunoBronosky: IE has some restrictions, eg. "For security reasons, data URIs are restricted to downloaded resources. Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements." See: https://msdn.microsoft.com/en-us/library/cc848897(v=vs.85).aspx – westor Jul 08 '15 at 15:31
  • @BrunoBronosky Doesnt prompt to save file in Safari – fdrv Apr 05 '16 at 09:34
  • @BrunoBronosky I _do_ have IE to test it in, and it does not work. It's nice if we don't have to support IE, but unfortunately, corporations still exist :D – Charles Wood Jul 19 '17 at 14:08
  • I tried this approach in chrome, it's not working, the new _blank window opens and disappears in the same time, tried to change the url using win.window.location.href but no luck! is there any way to embed PDF data or save as PDF file? – Khaled AbuShqear Nov 01 '17 at 09:29
  • Worked for me in Chrome inisitally, then Chrome started blocking it, saying the page had attempted to download multiple files automatically. Besides of which I need a bigger length limit, not really the question here. – enigment Jul 29 '18 at 15:35
84

HTML5 defined a window.saveAs(blob, filename) method. It isn't supported by any browser right now. But there is a compatibility library called FileSaver.js that adds this function to most modern browsers (including Internet Explorer 10+). Internet Explorer 10 supports a navigator.msSaveBlob(blob, filename) method (MSDN), which is used in FileSaver.js for Internet Explorer support.

I wrote a blog posting with more details about this problem.

Eli Grey
  • 35,104
  • 14
  • 75
  • 93
panzi
  • 7,517
  • 5
  • 42
  • 54
  • 1
    What about blocking popups? Behaviour of this library is similiar to @Nøk's solution. Plain text in Firefox is opened in a new. Only Chrome tries to save it, but it changes the extension (I need a dotfile with no extension). – ciembor Aug 19 '12 at 00:21
  • 1
    @ciembor the (object url+)download attribute variant (which I use with chrome) lets you set a filename. It works for me in chrome. – panzi Aug 19 '12 at 19:29
  • 1
    @ciembor aha and a popup is not blocked if a click directly caused it. – panzi Aug 19 '12 at 19:29
  • The FileSaver interface is now supported in the latest version of Chrome. – Justin Bull Oct 04 '12 at 21:54
  • @JustinBull Not the latest stable version, I presume? – panzi Oct 05 '12 at 22:24
  • @DustyJ: I did link to that very lib in my answer. – panzi Jan 10 '13 at 18:26
  • 6
    FileSaver.js supports IE now – Eli Grey Feb 01 '13 at 20:47
  • 18
    W3C says:Work on this document has been discontinued and it should not be referenced or used as a basis for implementation. – WaiKit Kung May 02 '14 at 10:27
  • @WaiKitKung Bummer. :/ – panzi May 02 '14 at 14:35
  • 1
    `window.saveAs` is not in the current draft: http://www.w3.org/TR/2014/CR-html5-20140731/browsers.html#the-window-object – Ciro Santilli OurBigBook.com Aug 23 '14 at 20:36
  • How can I use it with mimeType `application/zip`? Or do I have to use octet-stream? – k102 Jan 19 '16 at 07:47
  • You can whatever mime type on the blob you want (see Yassir Ennazk's answer). You just need to generate the ZIP file yourself in JavaScript. I don't know if there is an existing ZIP implementation in JavaScript anywhere. – panzi Jan 19 '16 at 19:42
  • File API is now supported in pretty much all major browsers https://caniuse.com/#feat=fileapi – SeanMC Jan 14 '19 at 15:18
61

Saving large files

Long data URIs can give performance problems in browsers. Another option to save client-side generated files, is to put their contents in a Blob (or File) object and create a download link using URL.createObjectURL(blob). This returns an URL that can be used to retrieve the contents of the blob. The blob is stored inside the browser until either URL.revokeObjectURL() is called on the URL or the document that created it is closed. Most web browsers have support for object URLs, Opera Mini is the only one that does not support them.

Forcing a download

If the data is text or an image, the browser can open the file, instead of saving it to disk. To cause the file to be downloaded upon clicking the link, you can use the the download attribute. However, not all web browsers have support for the download attribute. Another option is to use application/octet-stream as the file's mime-type, but this causes the file to be presented as a binary blob which is especially user-unfriendly if you don't or can't specify a filename. See also 'Force to open "Save As..." popup open at text link click for pdf in HTML'.

Specifying a filename

If the blob is created with the File constructor, you can also set a filename, but only a few web browsers (including Chrome & Firefox) have support for the File constructor. The filename can also be specified as the argument to the download attribute, but this is subject to a ton of security considerations. Internet Explorer 10 and 11 provides its own method, msSaveBlob, to specify a filename.

Example code

var file;
var data = [];
data.push("This is a test\n");
data.push("Of creating a file\n");
data.push("In a browser\n");
var properties = {type: 'text/plain'}; // Specify the file's mime-type.
try {
  // Specify the filename using the File constructor, but ...
  file = new File(data, "file.txt", properties);
} catch (e) {
  // ... fall back to the Blob constructor if that isn't supported.
  file = new Blob(data, properties);
}
var url = URL.createObjectURL(file);
document.getElementById('link').href = url;
<a id="link" target="_blank" download="file.txt">Download</a>
Steve Eynon
  • 4,979
  • 2
  • 30
  • 48
bcmpinc
  • 3,202
  • 29
  • 36
  • 1
    Can I show a dialog(popup) to specify a folder (directory) to save the file? – Calvin Feb 26 '16 at 01:18
  • @Calvin: I've updated the answer to explain how to force a download and provide a filename. For IE, I believe you can use `msSaveBlob` to open the "Save as" dialog. For other browsers, your only option is to manually choose "Save as" from the context menu of the download link. – bcmpinc Feb 27 '16 at 14:56
  • 1
    @Jek-fdrv: Only the Blob-urls work in Safari. The download attribute and File constructor are not supported by Safari, so you cannot force a download, meaning that the blob will probably be opened in the browser itself, and you cannot specify a filename. For the given example, you should still be able to download the file with Safari using the link's context menu. – bcmpinc Apr 07 '16 at 21:31
  • @bcmpinc http://stackoverflow.com/questions/36444507/how-to-show-save-file-dialog-in-safari – fdrv Apr 08 '16 at 01:34
  • This is a very helpful and informative answer. One more thing that may help people like me: after setting `document.getElementById('link').href = url;`, your code can go ahead and fire the link by using the element's `.click()` method. – LarsH Sep 17 '18 at 13:39
  • Initially I thought `URL.createObjectURL` would just create a massively long data URI which would be no improvement, yet after reading the docs, it appears that this function stores the big object in the browser's memory and returns a short URL that is a pointer to the object. Nice solution! – Simon East Oct 28 '21 at 09:12
  • 1
    @DaveF `URL.createObjectURL` is supported by ALL modern browsers, according to your link. And it doesn't appear to be deprecated. – Simon East Oct 28 '21 at 09:29
41
function download(content, filename, contentType)
{
    if(!contentType) contentType = 'application/octet-stream';
        var a = document.createElement('a');
        var blob = new Blob([content], {'type':contentType});
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.click();
}
Yassir Ennazk
  • 6,970
  • 4
  • 31
  • 37
  • 3
    What is the effect of does the contentType? What is it used for? – Uri Jun 08 '14 at 10:41
  • 3
    This one works fine even in the latest Chrome, unlike @Matěj Pokorný's answer. Thanks. – Alexander Amelkin Nov 12 '14 at 11:53
  • 2
    This doesn't work for me on FF36 or IE11. If I replace `a.click` with code using `document.createEvent()` as Matěj Pokorný suggested, it works on FF but not IE. I haven't tried Chrome. – Peter Hull Mar 10 '15 at 13:23
24

Take a look at Doug Neiner's Downloadify which is a Flash based JavaScript interface to do this.

Downloadify is a tiny JavaScript + Flash library that enables the generation and saving of files on the fly, in the browser, without server interaction.

Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • 6
    For most people, this is probably the answer that they'll need. So even though it doesn't meet my specific requirements (as explained above) I'm marking it as the accepted answer. – Toji May 24 '10 at 16:16
  • 2
    @Toji ah, I see. Maybe re-ask and re-phrase under the `HTML 5` banner and tag accordingly? That would be likely to attract those users who know about that specific field (still a comparably small crowd right now, I suppose). I'm pretty sure it can be done in HTML 5 but I have no idea how. – Pekka May 24 '10 at 16:19
  • 1
    Has the http://www.downloadify.info/ Downloadify domain been purchased/transferred, and if so is there a new location? The present site seems completely unrelated to the answer given. – Christos Hayward May 27 '11 at 15:01
  • @Jonathan yeah, the site went down. The source is still available here: https://github.com/dcneiner/Downloadify – Pekka May 27 '11 at 15:04
  • downloadify was compiled for online use, what about offline use that don't need internet connectifity? – Ariona Rian Feb 19 '12 at 17:30
  • @Ariona I'm not sure. I don't know where you got the "for online use" bit from though? Have you tried using it offline? – Pekka Feb 19 '12 at 18:45
  • @Pekka, i moan downloadify won't run if we called the file in file:// . so we need to use it on the internet. if you testing it right on your computer it won't work. see downloadify note at github. – Ariona Rian Feb 23 '12 at 04:58
  • @Ariona ah, I see. I imagine that is due to Flash's or the browser's restrictions. There may be no way to do this via `file://` URLs, although you could try using a `data:` URI as pointed out below – Pekka Feb 23 '12 at 08:39
  • 18
    This doesn't answer the *Using HTML5...* - titled question. – User Aug 29 '12 at 20:39
  • 5
    @Ixx well to be fair, that was added after the answer was posted. Still, you're right. The answer below should be accepted – Pekka Jan 23 '13 at 10:58
  • @ArionaRian, or for anyone interested in local testing, you can use it locally if you're browsing the file via localhost, instead of file://. A local HTTP server would make this happen. You can use Apache, or Python's SimpleHTTPServer, for example. – Lawrence Weru Jun 30 '14 at 18:14
  • @Toji please read answers and make a new accepted answer, because this answer is mostly unwanted one today. – T.Todua Sep 01 '16 at 11:18
  • 1
    So happy to look back on this 7 years later and have this be the answer for [statistically] nobody. (Because no one has Flash enabled anymore.) – Bruno Bronosky Jul 19 '17 at 16:53
  • @BrunoBronosky haha, yeah! – Pekka Jul 19 '17 at 16:56
20

Simple Solution!

<a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD">Click here</a>

Works in all Modern browsers.

T.Todua
  • 53,146
  • 19
  • 236
  • 237
  • Hi, do you know how to specify the "download" attribute behavior using window.open or another javascript function ? – yucer Aug 31 '16 at 18:57
  • 2
    @yucer There is no download attribute (or any attribute for that matter) with `window.open()`. It's irrelevant. You could use this method and force a `.click()` on it, but watch the timing as Firefox doesn't like it if you call `.click()` before letting the element attach to the body. – Brad Nov 03 '16 at 22:18
  • But sadly all spaces get deleted. In my case thats real bad since I want to download source code for a SVG file. – frankenapps Oct 02 '17 at 12:41
  • spaces are preserved if you use encodeURIComponent(content) – Mike Redrobe Aug 01 '18 at 19:56
  • cannot choose the name in firefox, but chrome works – Bhikkhu Subhuti Feb 27 '20 at 06:35
14

I've used FileSaver (https://github.com/eligrey/FileSaver.js) and it works just fine.
For example, I did this function to export logs displayed on a page.
You have to pass an array for the instanciation of the Blob, so I just maybe didn't write this the right way, but it works for me.
Just in case, be careful with the replace: this is the syntax to make this global, otherwise it will only replace the first one he meets.

exportLogs : function(){
    var array = new Array();

    var str = $('#logs').html();
    array[0] = str.replace(/<br>/g, '\n\t');

    var blob = new Blob(array, {type: "text/plain;charset=utf-8"});
    saveAs(blob, "example.log");
}
Razakhel
  • 732
  • 13
  • 34
  • 2
    FileSaver is great, here's a IE Shim for pre-IE10 function preIE10SaveAs: (filename, filecontent, mimetype) { var w = window.open(); var doc = w.document; doc.open( mimetype,'replace'); doc.charset = "utf-8"; doc.write(filecontent); doc.close(); doc.execCommand("SaveAs", null, filename); } – aqm Mar 20 '14 at 10:21
  • A warning about @aqm's shim: It executes script tags. – GameFreak Apr 19 '17 at 18:42
  • Also, it may be desirable to put `w.close();` after the `execCommand` – GameFreak Apr 19 '17 at 18:43
11

You can generate a data URI. However, there are browser-specific limitations.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • 1
    This is interesting. I'll look into it more when I get a chance. Thanks! – Toji May 24 '10 at 16:15
  • @quantumpotato, actually generating the URL is a little tricky to explain. All the nitty gritty is in [RFC 2397](http://tools.ietf.org/html/rfc2397). You can use tools like [this](http://dopiaza.org/tools/datauri/) for testing. Then, for your real app, you can search for a data URI or base 64 library for your language. If you don't find it, feel free to ask a follow up question. Some of the browser-specific limitations are given in the Wikipedia [article](http://en.wikipedia.org/wiki/Data_URI). For example, IE limits the length and type (e.g. not text/html). – Matthew Flaschen Jan 01 '12 at 06:51
  • Generating data URLs isn't that tricky: `"data:text/plain;charset=UTF-8,"+encodeURIComponent(text)` But yes, IE limits the size of data URLs to an unusable amount and it does not support them for things like `window.open(...)` or iframes (I think). – panzi Jan 10 '13 at 18:28
  • @panzi, it's not as easy as that either. For one thing, that's not the right MIME type for either Collada or JSON. – Matthew Flaschen Jan 11 '13 at 23:49
  • too non-informative answer. please, add the specifications for browsers, if you mention them. – T.Todua Aug 26 '16 at 11:36
10

try

let a = document.createElement('a');
a.href = "data:application/octet-stream,"+encodeURIComponent('"My DATA"');
a.download = 'myFile.json';
a.click(); // we not add 'a' to DOM so no need to remove

If you want to download binary data look here

Update

2020.06.14 I upgrade Chrome to 83.0 and above SO snippet stop works (due to sandbox security restrictions) - but JSFiddle version works - here

Community
  • 1
  • 1
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
9

I found two simple approaches that work for me. First, using an already clicked a element and injecting the download data. And second, generating an a element with the download data, executing a.click() and removing it again. But the second approach works only if invoked by a user click action as well. (Some) Browser block click() from other contexts like on loading or triggered after a timeout (setTimeout).

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript">
      function linkDownload(a, filename, content) {
        contentType =  'data:application/octet-stream,';
        uriContent = contentType + encodeURIComponent(content);
        a.setAttribute('href', uriContent);
        a.setAttribute('download', filename);
      }
      function download(filename, content) {
        var a = document.createElement('a');
        linkDownload(a, filename, content);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      }
    </script>
   </head>
  <body>
    <a href="#" onclick="linkDownload(this, 'test.txt', 'Hello World!');">download</a>
    <button onclick="download('test.txt', 'Hello World!');">download</button>
  </body>
</html>
maikel
  • 1,669
  • 18
  • 15
  • 2
    You could also insert the `a` into the document (possibly with `"display:none"`), click it, and then remove it. – Teepeemm May 02 '14 at 20:02
  • 1
    would this work in browsers where download attribute isn't supported like even modern ie and safari.. http://caniuse.com/#feat=download – Muhammad Umer Oct 08 '14 at 17:54
  • 1
    Just tested Safari 5.0 under wine. The first version works, the second not. I tested IE 8 (wine) as well and it doesn't work. – maikel Oct 10 '14 at 08:44
6

Here is a link to the data URI method Mathew suggested, it worked on safari, but not well because I couldn't set the filetype, it gets saved as "unknown" and then i have to go there again later and change it in order to view the file...

http://www.nihilogic.dk/labs/canvas2image/

Dennkster
  • 2,161
  • 2
  • 15
  • 13
4

You can use localStorage. This is the Html5 equivalent of cookies. It appears to work on Chrome and Firefox BUT on Firefox, I needed to upload it to a server. That is, testing directly on my home computer didn't work.

I'm working up HTML5 examples. Go to http://faculty.purchase.edu/jeanine.meyer/html5/html5explain.html and scroll to the maze one. The information to re-build the maze is stored using localStorage.

I came to this article looking for HTML5 JavaScript for loading and working with xml files. Is it the same as older html and JavaScript????

Jeanine
  • 73
  • 1
4

As previously mentioned the File API, along with the FileWriter and FileSystem APIs can be used to store files on a client's machine from the context of a browser tab/window.

However, there are several things pertaining to latter two APIs which you should be aware of:

  • Implementations of the APIs currently exist only in Chromium-based browsers (Chrome & Opera)
  • Both of the APIs were taken off of the W3C standards track on April 24, 2014, and as of now are proprietary
  • Removal of the (now proprietary) APIs from implementing browsers in the future is a possibility
  • A sandbox (a location on disk outside of which files can produce no effect) is used to store the files created with the APIs
  • A virtual file system (a directory structure which does not necessarily exist on disk in the same form that it does when accessed from within the browser) is used represent the files created with the APIs

Here are simple examples of how the APIs are used, directly and indirectly, in tandem to do this:

BakedGoods*

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});

Using the raw File, FileWriter, and FileSystem APIs

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);

Though the FileSystem and FileWriter APIs are no longer on the standards track, their use can be justified in some cases, in my opinion, because:

  • Renewed interest from the un-implementing browser vendors may place them right back on it
  • Market penetration of implementing (Chromium-based) browsers is high
  • Google (the main contributer to Chromium) has not given and end-of-life date to the APIs

Whether "some cases" encompasses your own, however, is for you to decide.

*BakedGoods is maintained by none other than this guy right here :)

Kevin
  • 2,617
  • 29
  • 35
4

You can use this to save text and other data:

function downloadFile(name, data) {
    let a = document.createElement("a");
    if (typeof a.download !== "undefined") a.download = name;
    a.href = URL.createObjectURL(new Blob([data], {
        type: "application/octet-stream"
    }));
    a.dispatchEvent(new MouseEvent("click"));
}

This function will create an Anchor element, set the name via .download (if supported), assign a url (.href) created from an object (URL.createObjectURL), in this case a Blob object, and dispatch a click event. In short: it's as if you're clicking a download link.

Example code

downloadFile("textfile.txt", "A simple text file");
downloadFile(
    "circle.svg",
    `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
        <circle cx="50" cy="50" r="42" />
    </svg>`
);
downloadFile(
    "utf8string.txt",
    new Uint8Array([85, 84, 70, 45, 56, 32, 115, 116, 114, 105, 110, 103]) // "UTF-8 string"
);

This function also accepts File, Blob and MediaSource:

function downloadFile(name, data) {
    if (!(data instanceof File || data instanceof Blob || data instanceof MediaSource)) {
        return downloadFile(name, new Blob([data], {
            type: "application/octet-stream"
        }));
    }

    let a = document.createElement("a");
    if (typeof a.download !== "undefined") a.download = name;
    a.href = URL.createObjectURL(data);
    a.dispatchEvent(new MouseEvent("click"));
}

Or you could use two functions:

function downloadFile(name, data) {
    return downloadObject(new Blob([data], {
        type: "application/octet-stream"
    }));
}

function downloadObject(name, object) {
    let a = document.createElement("a");
    if (typeof a.download !== "undefined") a.download = name;
    a.href = URL.createObjectURL(object);
    a.dispatchEvent(new MouseEvent("click"));
}
User
  • 317
  • 2
  • 8
2

This thread was invaluable to figure out how to generate a binary file and prompt to download the named file, all in client code without a server.

First step for me was generating the binary blob from data that I was saving. There's plenty of samples for doing this for a single binary type, in my case I have a binary format with multiple types which you can pass as an array to create the blob.

saveAnimation: function() {

    var device = this.Device;
    var maxRow = ChromaAnimation.getMaxRow(device);
    var maxColumn = ChromaAnimation.getMaxColumn(device);
    var frames = this.Frames;
    var frameCount = frames.length;

    var writeArrays = [];


    var writeArray = new Uint32Array(1);
    var version = 1;
    writeArray[0] = version;
    writeArrays.push(writeArray.buffer);
    //console.log('version:', version);


    var writeArray = new Uint8Array(1);
    var deviceType = this.DeviceType;
    writeArray[0] = deviceType;
    writeArrays.push(writeArray.buffer);
    //console.log('deviceType:', deviceType);


    var writeArray = new Uint8Array(1);
    writeArray[0] = device;
    writeArrays.push(writeArray.buffer);
    //console.log('device:', device);


    var writeArray = new Uint32Array(1);
    writeArray[0] = frameCount;
    writeArrays.push(writeArray.buffer);
    //console.log('frameCount:', frameCount);

    for (var index = 0; index < frameCount; ++index) {

      var frame = frames[index];

      var writeArray = new Float32Array(1);
      var duration = frame.Duration;
      if (duration < 0.033) {
        duration = 0.033;
      }
      writeArray[0] = duration;
      writeArrays.push(writeArray.buffer);

      //console.log('Frame', index, 'duration', duration);

      var writeArray = new Uint32Array(maxRow * maxColumn);
      for (var i = 0; i < maxRow; ++i) {
        for (var j = 0; j < maxColumn; ++j) {
          var color = frame.Colors[i][j];
          writeArray[i * maxColumn + j] = color;
        }
      }
      writeArrays.push(writeArray.buffer);
    }

    var blob = new Blob(writeArrays, {type: 'application/octet-stream'});

    return blob;
}

The next step is to get the browser to prompt the user to download this blob with a predefined name.

All I needed was a named link I added in the HTML5 that I could reuse to rename the initial filename. I kept it hidden since the link doesn't need display.

<a id="lnkDownload" style="display: none" download="client.chroma" href="" target="_blank"></a>

The last step is to prompt the user to download the file.

var data = animation.saveAnimation();
var uriContent = URL.createObjectURL(data);
var lnkDownload = document.getElementById('lnkDownload');
lnkDownload.download = 'theDefaultFileName.extension';
lnkDownload.href = uriContent;
lnkDownload.click();
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
tgraupmann
  • 71
  • 4
2

When testing the "ahref" method, I found that the web developer tools of Firefox and Chrome gets confused. I needed to restart the debugging after the a.click() was issued. Same happened with the FileSaver (it uses the same ahref method to actually make the saving). To work around it, I created new temporary window, added the element a into that and clicked it there.

    function download_json(dt) {
        var csv = ' var data = ';
        csv += JSON.stringify(dt, null, 3);

        var uricontent = 'data:application/octet-stream,' + encodeURI(csv);

        var newwin = window.open( "", "_blank" );
        var elem = newwin.document.createElement('a');
        elem.download = "database.js";
        elem.href = uricontent;
        elem.click();
        setTimeout(function(){ newwin.close(); }, 3000);
        
    }
Peeter Vois
  • 66
  • 1
  • 4
1

Here is a tutorial to export files as ZIP:

Before getting started, there is a library to save files, the name of library is fileSaver.js, You can find this library here. Let's get started, Now, include the required libraries:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.4/jszip.min.js"  type="text/javascript"></script>
<script type="text/javascript" src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.js" ></script>

Now copy this code and this code will download a zip file with a file hello.txt having content Hello World. If everything thing works fine, this will download a file.

<script type="text/javascript">
    var zip = new JSZip();
    zip.file("Hello.txt", "Hello World\n");
    zip.generateAsync({type:"blob"})
    .then(function(content) {
        // see FileSaver.js
        saveAs(content, "file.zip");
    });
</script>

This will download a file called file.zip. You can read more here: http://www.wapgee.com/story/248/guide-to-create-zip-files-using-javascript-by-using-jszip-library

Ilyas karim
  • 4,592
  • 4
  • 33
  • 47