[javascript] How to save .xlsx data to file as a blob

I have a similar question to this question(Javascript: Exporting large text/csv file crashes Google Chrome):

I am trying to save the data created by excelbuilder.js's EB.createFile() function. If I put the file data as the href attribute value of a link, it works. However, when data is big, it crashes Chrome browser. Codes are like this:

//generate a temp <a /> tag
var link = document.createElement("a");
link.href = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + encodeURIComponent(data);
link.style = "visibility:hidden";
link.download = fileName;

document.body.appendChild(link);
link.click();
document.body.removeChild(link);

My codes to create the data using excelbuilder.js is like follows:

var artistWorkbook = EB.createWorkbook();
var albumList = artistWorkbook.createWorksheet({name: 'Album List'});

albumList.setData(originalData); 

artistWorkbook.addWorksheet(albumList);

var data = EB.createFile(artistWorkbook);

As suggested by the answer of the similar question (Javascript: Exporting large text/csv file crashes Google Chrome), a blob needs to be created.

My problem is, what is saved in the file isn't a valid Excel file that can be opened by Excel. The codes that I use to save the blob is like this:

var blob = new Blob(
    [data],
    {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,"}
);

// Programatically create a link and click it:
var a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = fileName;
a.click();

If I replace the [data] in the above codes with [Base64.decode(data)], the contents in the file saved looks more like the expected excel data, but still cannot be opened by Excel.

Thanks!

This question is related to javascript xlsx bloburls

The answer is


Solution for me.

Step: 1

<a onclick="exportAsExcel()">Export to excel</a>

Step: 2

I'm using file-saver lib.

Read more: https://www.npmjs.com/package/file-saver

npm i file-saver

Step: 3

let FileSaver = require('file-saver'); // path to file-saver

function exportAsExcel() {
    let dataBlob = '...kAAAAFAAIcmtzaGVldHMvc2hlZXQxLnhtbFBLBQYAAAAACQAJAD8CAADdGAAAAAA='; // If have ; You should be split get blob data only
    this.downloadFile(dataBlob);
}

function downloadFile(blobContent){
    let blob = new Blob([base64toBlob(blobContent, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')], {});
    FileSaver.saveAs(blob, 'report.xlsx');
}

function base64toBlob(base64Data, contentType) {
    contentType = contentType || '';
    let sliceSize = 1024;
    let byteCharacters = atob(base64Data);
    let bytesLength = byteCharacters.length;
    let slicesCount = Math.ceil(bytesLength / sliceSize);
    let byteArrays = new Array(slicesCount);
    for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        let begin = sliceIndex * sliceSize;
        let end = Math.min(begin + sliceSize, bytesLength);

        let bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, { type: contentType });
}

Work for me. ^^


I've found a solution worked for me:

const handleDownload = async () => {
   const req = await axios({
     method: "get",
     url: `/companies/${company.id}/data`,
     responseType: "blob",
   });
   var blob = new Blob([req.data], {
     type: req.headers["content-type"],
   });
   const link = document.createElement("a");
   link.href = window.URL.createObjectURL(blob);
   link.download = `report_${new Date().getTime()}.xlsx`;
   link.click();
 };

I just point a responseType: "blob"


This works as of: v0.14.0 of https://github.com/SheetJS/js-xlsx

/* generate array buffer */
var wbout = XLSX.write(wb, {type:"array", bookType:'xlsx'});
/* create data URL */
var url = URL.createObjectURL(new Blob([wbout], {type: 'application/octet-stream'}));
/* trigger download with chrome API */
chrome.downloads.download({ url: url, filename: "testsheet.xlsx", saveAs: true });

The answer above is correct. Please be sure that you have a string data in base64 in the data variable without any prefix or stuff like that just raw data.

Here's what I did on the server side (asp.net mvc core):

string path = Path.Combine(folder, fileName);
Byte[] bytes = System.IO.File.ReadAllBytes(path);
string base64 = Convert.ToBase64String(bytes);

On the client side, I did the following code:

const xhr = new XMLHttpRequest();

xhr.open("GET", url);
xhr.setRequestHeader("Content-Type", "text/plain");

xhr.onload = () => {
    var bin = atob(xhr.response);
    var ab = s2ab(bin); // from example above
    var blob = new Blob([ab], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;' });

    var link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = 'demo.xlsx';

    document.body.appendChild(link);

    link.click();

    document.body.removeChild(link);
};

xhr.send();

And it works perfectly for me.


Here's my implementation using the fetch api. The server endpoint sends a stream of bytes and the client receives a byte array and creates a blob out of it. A .xlsx file will then be generated.

return fetch(fullUrlEndpoint, options)
  .then((res) => {
    if (!res.ok) {
      const responseStatusText = res.statusText
      const errorMessage = `${responseStatusText}`
      throw new Error(errorMessage);
    }
    return res.arrayBuffer();
  })
    .then((ab) => {
      // BE endpoint sends a readable stream of bytes
      const byteArray = new Uint8Array(ab);
      const a = window.document.createElement('a');
      a.href = window.URL.createObjectURL(
        new Blob([byteArray], {
          type:
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        }),
      );
      a.download = `${fileName}.XLSX`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    })
    .catch(error => {
      throw new Error('Error occurred:' + error);
    });

try FileSaver.js library. it might help.

https://github.com/eligrey/FileSaver.js/