{"id":440,"date":"2011-07-15T16:53:14","date_gmt":"2011-07-15T20:53:14","guid":{"rendered":"http:\/\/eligrey.com\/blog\/?p=440"},"modified":"2011-07-15T16:53:14","modified_gmt":"2011-07-15T20:53:14","slug":"saving-generated-files-on-the-client-side","status":"publish","type":"post","link":"https:\/\/eligrey.com\/blog\/saving-generated-files-on-the-client-side\/","title":{"rendered":"Saving generated files on the client-side"},"content":{"rendered":"<p>Have you ever wanted to add a <q>Save as&hellip;<\/q> button to a webapp? Whether you&#8217;re making an advanced <a href=\"https:\/\/developer.mozilla.org\/en\/WebGL\">WebGL<\/a>-powered <abbr title=\"computer-aided design\">CAD<\/abbr> 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.<\/p>\n<p>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 <code>Content-disposition: attachment<\/code> header. This is less than ideal for webapps that need to work offline. The W3C File API includes a <a href=\"http:\/\/www.w3.org\/TR\/file-writer-api\/#the-filesaver-interface\"><code>FileSaver<\/code> interface<\/a>, which makes saving generated data as easy as <code>saveAs(data, filename)<\/code>, though unfortunately it will eventually be removed from the spec.<\/p>\n<p>I have written a JavaScript library called <a href=\"https:\/\/github.com\/eligrey\/FileSaver.js\">FileSaver.js<\/a>, which implements <code>FileSaver<\/code> in all modern browsers. Now that it&#8217;s possible to generate any type of file you want right in the browser, document editors can have an instant save button that doesn&#8217;t rely on an online connection. When paired with the standard HTML5 <a href=\"http:\/\/www.w3.org\/TR\/html5\/the-canvas-element.html\"><code>canvas.toBlob()<\/code><\/a> 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&#8217;t yet support <code>canvas.toBlob()<\/code>, <a href=\"https:\/\/github.com\/eboyjr\">Devin Samarin<\/a> and I wrote <a href=\"https:\/\/github.com\/eligrey\/canvas-toBlob.js\">canvas-toBlob.js<\/a>. Saving a canvas is as simple as running the following code:<\/p>\n<pre lang=\"javascript\">canvas.toBlob(function(blob) {\n    saveAs(blob, filename);\n});\n<\/pre>\n<p>I have created a <a href=\"http:\/\/eligrey.com\/demos\/FileSaver.js\/\">demo<\/a> 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 <code>FileSaver<\/code> or browsers like <a href=\"http:\/\/www.chromium.org\/getting-involved\/dev-channel\">Google Chrome 14 dev<\/a> and <a href=\"http:\/\/tools.google.com\/dlpage\/chromesxs\">Google Chrome Canary<\/a>, that support <a href=\"http:\/\/developers.whatwg.org\/links.html#downloading-resources\">&lt;a&gt;.download<\/a> or web filesystems via <a href=\"http:\/\/www.w3.org\/TR\/file-system-api\/#using-localfilesystem\"><code>LocalFileSystem<\/code><\/a>.<\/p>\n<h2>How to construct files for saving<\/h2>\n<p>First off, you want to instantiate a <a href=\"https:\/\/developer.mozilla.org\/en\/DOM\/Blob\"><code>Blob<\/code><\/a>. The <code>Blob<\/code> API isn&#8217;t supported in all current browsers, so I made <a href=\"https:\/\/github.com\/eligrey\/Blob.js\">Blob.js<\/a> which implements it. The following example illustrates how to save an XHTML document with <code>saveAs()<\/code>.<\/p>\n<pre lang=\"javascript\">saveAs(\n      new Blob(\n          [(new XMLSerializer).serializeToString(document)]\n        , {type: \"application\/xhtml+xml;charset=\" + document.characterSet}\n    )\n    , \"document.xhtml\"\n);\n<\/pre>\n<p>Not saving textual data? You can save multiple binary <code>Blob<\/code>s and <a href=\"https:\/\/developer.mozilla.org\/en\/JavaScript_typed_arrays\"><code>ArrayBuffer<\/code>s<\/a> to a <code>Blob<\/code> as well! The following is an example of setting generating some binary data and saving it.<\/p>\n<pre lang=\"javascript\" escaped=\"true\">var\n      buffer = new ArrayBuffer(8) \/\/ allocates 8 bytes\n    , data = new DataView(buffer)\n;\n\/\/ You can write uint8\/16\/32s and float32\/64s to dataviews\ndata.setUint8 (0, 0x01);\ndata.setUint16(1, 0x2345);\ndata.setUint32(3, 0x6789ABCD);\ndata.setUint8 (7, 0xEF);\n\nsaveAs(new Blob([buffer], {type: \"example\/binary\"}), \"data.dat\");\n\/\/ The contents of data.dat are &lt;01 23 45 67 89 AB CD EF&gt;<\/pre>\n<p>If you&#8217;re generating large files, you can implement an abort button that aborts the <code>FileSaver<\/code>.<\/p>\n<pre lang=\"javascript\">var filesaver = saveAs(blob, \"video.webm\");\nabort_button.addEventListener(\"click\", function() {\n    filesaver.abort();\n}, false);<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Have you ever wanted to add a Save as&hellip; button to a webapp? Whether you&#8217;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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[45,76,90],"class_list":["post-440","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-file-api","tag-html5","tag-javascript"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pfpUD-76","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/posts\/440","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/comments?post=440"}],"version-history":[{"count":0,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/posts\/440\/revisions"}],"wp:attachment":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/media?parent=440"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/categories?post=440"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/tags?post=440"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}