3

I have been looking for a way to initiate a file download of data that only exists within the code executing on the user's browser (as opposed to initiating a download from the server), and indicate the file name in the process.

A search of StackOverflow turns up a number of promising posts that don't quite do what I need.

This answer is not IE-compatible and doesn't allow specifying a file name anyway.

This question provides a few ways to initiate downloads, but the answers that allow specifying a file name are not IE compatible and require user interaction, while other answers don't allow specifying a file name.

Is there a way to initiate a file download from JavaScript that:

  • Downloads client-side data
  • Does not require the user to manually initiate the download
  • Allows specifying the filename of the downloaded file
Community
  • 1
  • 1
JLRishe
  • 99,490
  • 19
  • 131
  • 169

1 Answers1

2

After much searching around and trying approaches from various places, I was able to come up with the following.

The tryAnchorDownload() approach should work on modern versions of FireFox and Chrome, and is a cleaned-up version of code provided in this forum post.

The trySaveAsDownload() approach should theoretically work on any major modern browser, though Safari may not respect the specified file name. It requires the FileSaver.js library and some browsers may need the Blob.js polyfill.

You can call the main function here like this:

initiateFileDownload(bytes, "myFile.txt");

where bytes is a Uint8Array of the file's bytes.

function byteArrayToBase64(bytes) {
    var chArray = Array.prototype.map.call(bytes, 
                     function (byte) { return String.fromCharCode(byte); });

    return window.btoa(chArray.join(""));
}

var octetStreamMimeType = "application/octet-stream";

function tryAnchorDownload(fileBytes, fileName) {
    var aElement = document.createElement("a"),
        event;

    if ("download" in aElement) {
        aElement.setAttribute("download", fileName);
        aElement.href = "data:" + octetStreamMimeType + 
                        ";base64," + byteArrayToBase64(fileBytes);

        document.body.appendChild(aElement);
        event = document.createEvent("MouseEvents");
        event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0,
                             false, false, false, false, 0, null);
        aElement.dispatchEvent(event);
        document.body.removeChild(aElement);

        return true;
    }

    return false;
}

function trySaveAsDownload(fileBytes, fileName) {
    var blob;

    if (window.saveAs) {
        blob = new Blob([fileBytes], { type: octetStreamMimeType });

        saveAs(blob, fileName);

        return true;
    }

    return false;
}

// fileBytes is a Uint8Array
function initiateFileDownload(fileBytes, fileName) {
    return tryAnchorDownload(fileBytes, fileName) ||
           trySaveAsDownload(fileBytes, fileName);
}

A more thorough (and probably more efficient) alternative to the byteArrayToBase64() function above can be found on MDN.

JLRishe
  • 99,490
  • 19
  • 131
  • 169