Related
So I am trying to download a zip file with an ajax call to my api. The api responds with a base 64 encoded byte array. Now for most of the downloads this is working just fine, but when the zip file gets too large it starts to fail in Chrome. Works fine in all other browsers. From what I have found on stack overflow this is a known issue in chrome, and people suggested using blobs. Thing is I am using blobs and still have the issue. Here is my code for handling the download. I use this to download pdf and zip files by passing in different values for contentType. Has anyone run into this issue before? Are there any work arounds or scripts I can add to the page that will remedy this problem?
// data is base 64 encoded byte array
function download(data, filename, contentType) {
var byteCharacters = atob(data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], { type: contentType });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
// IE / edge workaround
window.navigator.msSaveOrOpenBlob(blob, filename);
} else if (navigator.userAgent.indexOf("Firefox") > 0) {
var a = window.document.createElement('a');
a.download = filename;
a.style.cssText = "display: none";
a.target = "_blank";
// Append anchor to body.
document.body.appendChild(a);
a.href = "data:" + contentType + ";base64," + data;
a.click();
// Remove anchor from body
document.body.removeChild(a);
} else //Chrome, safari, etc
{
var a = document.createElement("a");
a.style = "display: none";
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}
}
The api responds with a base 64 encoded byte array
Why? I'm suggesting you to add a method which will respond with pure binary zip files.
When set xhr.responseType = "blob"; and just save it like you do it for Chrome.
If you want to keep your api as is, try to get rid of Array and fill in Uint8Array straightway.
UPD. I tested this on my Chrome 69 under Ubuntu 18.04 with 16gb RAM and I found that Chrome would not let me download much more than 1000 megabytes this way.
How large are file you are trying to download?
Check it by yourself (don't forget to make sure that nothing will block downloading):
function getBlob(size) {
let u8a = new Uint8Array(size);
for (var i = 0; i < size; i++) {
u8a[i] = 33 + Math.floor(Math.random() * 5)
}
return new Blob([u8a]);
}
function downloadFile(source, filename) {
if (source instanceof (File, Blob)) {
filename = source.name || filename;
source = URL.createObjectURL(source);
}
let link = document.createElement("a");
link.style.display = "none";
link.href = source;
link.download = filename || "blob.bin";
document.body.appendChild(link);
link.click();
setTimeout(function () {
document.body.removeChild(link);
URL.revokeObjectURL(source);
}, 3000);
}
Size (MB): <input id="in" type=number min=1 max=3000>
<button onclick="downloadFile(getBlob(1024*1024*(+document.getElementById('in').value)))">download</button>
Seems to be a bug. In the meantime of it being resolved here's what you could do.
Split the file into smaller parts, and make many ajax requests, then when everything has been downloaded reconstruct the original file client side by concatenating the parts. From there proceed as normal.
Problem with large blob download is mostly about memory leak.
Some additional code can solve this problem, I think.
Your blob is storing in memory, and if it very large is bad,
because browser process has limit of available memory. This can
solve indexedDB indexeddb example, You should add a blob to
indexedDB and then immideately remove from it. When you writing blob
to indexeddb, browser creates cached file at file system, and from
this moment your blob will be stored at file system, like a File.
And this action will free a lot of memory.
When you create
ObjectUrl, you will have an additional link of blob in memory, and
garbage collector will remove it, only on document unload event. So
you need to revokeObjectURL.
var previousBlob = null;
downloadFile(source, filename) {
if(previousBlob){
URL.revokeObjectURL(previousBlob);
previousBlob = null;
}
//.. do stuff
}
P.S.
IndexedDB helped me solve one bug of Chrome. In the Chrome browser you can't create 1000 blobs (totaly in all opened tabs), so I solve this problem by writing blobs to indexeddb.Because blobs became a files and limit of blobs don't sprad on it. You can see the result by you own yeys at firefox "about:memory" tool, is the best tool of prifiling memory (better then other browsers)
In addition to these solution methods above you can also try to set maxRequestLength property in the web.config.
<system.web>
...
<httpRuntime targetFramework="4.5.1" maxRequestLength="51200" executionTimeout="30" />
<!-- for 50 MB set maxRequestLength="51200"-->
</system.web>
Hope this helps...
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!
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!');
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.
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.
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>
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();
}
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.
Simple Solution!
<a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD">Click here</a>
Works in all Modern browsers.
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");
}
You can generate a data URI. However, there are browser-specific limitations.
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>
download
<button onclick="download('test.txt', 'Hello World!');">download</button>
</body>
</html>
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
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/
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????
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 :)
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();
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);
}
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"));
}
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
For simple files like 'txt' or'js' you can use the package fs-browsers.
It has nice and easy download and export methods for client-side which do not invole any server.
import { exportFile } from 'fs-browsers';
const onExportClick = (textToExport) => {
// Export to txt file
exportFile(textToExport);
}
If you want to change the name of the file, or even it's type you can do it easily with this:
import { exportFile } from 'fs-browsers';
const onExportClick = (textToExport) => {
// Export to js file called 'file.js'
exportFile(textToExport, { fileName: 'file.js' });
}
For more complex files you will need to involve a server as you said.
The package can also does that with excel files ('xls') if that is what you need.
import { exportFile, EXCEL_FILE } from 'fs-browsers';
const data = [{ "id": 5, "name": "John", "grade": 90, "age": 15 }, { "id": 7, "name": "Nick", "grade": 70, "age": 17 }];
const headings = ["Student ID", "Student Name", "Test Grade", "Student Age"];
exportFile(data, { type: EXCEL_FILE, headings: headings, fileName: 'grades.xls' });
Maybe in the future there eill be other kind of files too.
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!
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!');
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.
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.
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>
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();
}
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.
Simple Solution!
<a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD">Click here</a>
Works in all Modern browsers.
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");
}
You can generate a data URI. However, there are browser-specific limitations.
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>
download
<button onclick="download('test.txt', 'Hello World!');">download</button>
</body>
</html>
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
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/
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????
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 :)
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();
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);
}
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"));
}
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
For simple files like 'txt' or'js' you can use the package fs-browsers.
It has nice and easy download and export methods for client-side which do not invole any server.
import { exportFile } from 'fs-browsers';
const onExportClick = (textToExport) => {
// Export to txt file
exportFile(textToExport);
}
If you want to change the name of the file, or even it's type you can do it easily with this:
import { exportFile } from 'fs-browsers';
const onExportClick = (textToExport) => {
// Export to js file called 'file.js'
exportFile(textToExport, { fileName: 'file.js' });
}
For more complex files you will need to involve a server as you said.
The package can also does that with excel files ('xls') if that is what you need.
import { exportFile, EXCEL_FILE } from 'fs-browsers';
const data = [{ "id": 5, "name": "John", "grade": 90, "age": 15 }, { "id": 7, "name": "Nick", "grade": 70, "age": 17 }];
const headings = ["Student ID", "Student Name", "Test Grade", "Student Age"];
exportFile(data, { type: EXCEL_FILE, headings: headings, fileName: 'grades.xls' });
Maybe in the future there eill be other kind of files too.
I'm trying to find a clean and consistent approach for downloading the contents of a canvas as an image file.
For Chrome or Firefox, I can do the following
// Convert the canvas to a base64 string
var image = canvas.toDataURL();
image = image.replace(/^data:[a-z]*;/, 'data:application/octet-stream;');
// use the base64 string as the 'href' attribute
var download = $('<a download="' + filename + '" target="_blank" href="' + image + '">');
Since the above doesn't work in IE, I'm trying to build a Blob with the 'window.navigator.msSaveOrOpenBlob' function.
var image = canvas.toDataURL();
image = image.replace(/^data:[a-z]*;,/, '');
// Convert from base64 to an ArrayBuffer
var byteString = atob(image);
var buffer = new ArrayBuffer(byteString.length);
var intArray = new Uint8Array(buffer);
for (var i = 0; i < byteString.length; i++) {
intArray[i] = byteString.charCodeAt(i);
}
// Use the native blob constructor
blob = new Blob([buffer], {type: "image/png"});
// Download this blob
window.navigator.msSaveOrOpenBlob(blob, "test.png");
In the example above, do I really have to convert a canvas to base64, base64 to ArrayBuffer, and finally convert from base64 to blob? (Firefox has a 'canvas.toBlob' function, but again that's not available in IE). Also, this only works in IE10, not IE9.
Does anyone have any suggestions for a solution that will work in Chrome, Safari, Firefox, IE9, and IE10?
Yes you have to do all the extra footwork.
toBlob support in Chrome and FF is very recent, even though its been in the spec for several years. Recent enough that it wasn't even on the radar when MS made IE9 and 10.
Unfortunately MS has no intent of changing IE9 or IE10 canvas implementations, which means they will exist as they are for all eternity, with bugs and missing pieces. (IE10 canvas has several bugs that IE9 does not, that are fixed again in IE11. Like this gem. It's a real shamble.)
I'm trying to find a cross browser way to store data locally in HTML5. I have generated a chunk of data in a Blob (see MDN). Now I want to move this Blob to the actual filesystem and save it locally. I've found the following ways to achieve this;
Use the <a download> attribute. This works only in Chrome currently.
Microsoft introduces a saveAs function in IE 10 which will achieve this.
Open the Blob URL in the browser and save it that way.
None of these seems to work in Safari though. While (1) works in Chrome, (2) in IE and (3) in Firefox no one works in Safari 6. The download attribute is not yet implemented and when trying to open a blob using the URL Safari complains that URLs starting with blob: are not valid URLs.
There is a good script that encapsulates (1) and (3) called FileSaver.js but that does not work using the latest Safari version.
Is there a way to save Blobs locally in a cross browser fashion?
FileSaver.js has beed updated recently and it works on IE10, Safari5+ etc.
See: https://github.com/eligrey/FileSaver.js/#supported-browsers
The file name sucks, but this works for me in Safari 8:
window.open('data:attachment/csv;charset=utf-8,' + encodeURI(csvString));
UPDATE: No longer working in Safari 9.x
The only solution that I have come up with is making a data: url instead. For me this looks like:
window.open("data:image/svg+xml," + encodeURIComponent(currentSVGString));
Here data is the array buffer data coming from response while making http rest call in js. This works in safari, however there might me some issue in filename as it comes to be untitled.
var binary = '';
var bytes = new Uint8Array(data);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
var base64 = 'data:' + contentType + ';base64,' + window.btoa(binary);
var uri = encodeURI(base64);
var anchor = document.createElement('a');
document.body.appendChild(anchor);
anchor.href = uri;
anchor.download = fileName;
anchor.click();
document.body.removeChild(anchor);
Have you read this article? http://updates.html5rocks.com/2012/06/Don-t-Build-Blobs-Construct-Them
Relating to http://caniuse.com/#search=blob, blobs are possible to use in safari.
You should consturct a servlet which delivers the blob via standard http:// url, so you can avoid using blob: url. Just make a request to that url and build your blob.
Afterwards you can save it in your filesystem or local storage.
The download attribute is supported since ~safari 10.1, so currently this is the way to go.
This is the only thing that worked for me on safari.
var newWindow = window.open();
const blobPDF = await renderMapPDF(); // Your async stuff goes here
if (!newWindow) throw new Error('Window could not be opened.');
newWindow.location = URL.createObjectURL(blobPDF);