XLSX client save from backend api response: binary string, application/octet-stream - javascript

I need to save XLSX file on frontend that im getting from XHR to api.
In api response i got this headers:
Content-Type: application/octet-stream
content-disposition: attachment; filename = OrdersList-253CREATED0.xlsx
And part of response body:
PKõzMdocProps/core.xml­MKÄ0ïý!÷vVd-mQÅ++ÞB:¶Åæ$Úõßí®Å£ÇÉû¼ä\ïåHÞѺA«²$¥Ðí º>6xE×uB[ÜZmÐú -å*Ú{o
'zÜ%!V!yÑVrFÛáâwYDÏ[î9l±Ytôè+ùwe+¥y³ã,hàwÀ߬G+Ý9YȽj¦dÊg.lÄàéîöa^>ó\ ¤uDHy²Â"÷Øà(üÁ~%»üêºÙÐ:KÙ*fYÌ.,-²¼8ËKøÕ?9£¶õe8Kd{ ...
In my code, I tried many options:
1) replace type: "application/octet-stream" to application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
2) creating blob/file without s2ab function like this Blob([binary_string], ...)
let binary_string = response;
// Download using blob:
let ab = s2ab(binary_string),
blob = new Blob(
[ab],
{type: "application/octet-stream"}
);
let downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = 'test-blob.xlsx';
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
// Download using file
let FileSaver = require('file-saver'),
ab = s2ab(binary_string),
file = new File(
[ab],
"test-file.xlsx",
{type: "application/octet-stream"});
FileSaver.saveAs(file);
// s2ab function
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
Why i cant just use iframe, or a[download]? Because authorization header is required.
The file from response is correct, it opens when loading via postman, but when im trying to save it via js from XHR response it always corrupted.
I would be very grateful for the help :)

Im solved the problem.
In this projects im using react, so ofc im used axios for ajax requests.
I think the problem in some axios settings or with axios itself;
Working code example on vanilla js:
var request = new XMLHttpRequest();
request.open('GET', `http://localhost/api/get_xlsx`, true);
request.setRequestHeader('Token', 'user_auth_token_needed_in_my_app');
request.responseType = 'blob';
request.onload = function(e) {
if (this.status === 200) {
var blob = this.response;
if(window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, 'test.xlsx');
}
else{
var downloadLink = window.document.createElement('a');
var contentTypeHeader = request.getResponseHeader("Content-Type");
downloadLink.href = window.URL.createObjectURL(new Blob([blob], {
type: contentTypeHeader }));
downloadLink.download = 'test.xlsx';
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
}
};
request.send();

Related

CSV file from server URL not downloading

I'm messing with a piece of code to download a CSV from server. this is what I'm trying:
downloadCSVTemp(){
let name = 'report.csv';
const response = api.getReportCSV()
.then((res)=>{
const blob = new Blob([res], { type: "data:text/csv;charset=utf-8," });
const blobURL = window.URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.download = name;
anchor.href = blobURL;
anchor.dataset.downloadurl = ["text/csv", anchor.download, anchor.href].join(
":"
);
anchor.click();
});
}
getReportCSV(){
return axios.get("/export/assessments/");
}
I intend to download a csv file from server url by an axios call, But this code is downloading an csv file which can't be opend by the browser & full of garbage data. What's the problem here ?
The res argument contains Axios response object. When creating the blob, you should use res.data:
new Blob([res.data], { type: "data:text/csv;charset=utf-8," });

how to get mime type from content-type

The thing is axios calls return files. sometimes xlsx, sometimes plain txt.
In javascript, as soon as I get them, i force download it via blob.
Something like this:
var headers = response.headers;
var blob = new Blob([response.data], {
type: headers['content-type']
});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "report.xlsx";
link.click();
As you see, I got something like this: link.download = "report.xlsx" . What I want is to replace xlsx with dynamic mime type so that sometimes it's report.txt and sometimes it's report.xlsx.
How do I do that from content-type?
You can get the file extension using the content type of headers.
Use this Javascript library - node-mime
You just want to pass your headers['content-type'], it will give you the file extension which you need to set for download name.
var ctype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
console.log(mime.getExtension(ctype));
<script src="https://wzrd.in/standalone/mime#latest"></script>
Example: In your case,
var headers = response.headers;
var blob = new Blob([response.data], {
type: headers['content-type']
});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "report." + mime.getExtension(headers['content-type']);
link.click();
Incomplete list of MIME types from Mozilla Developers.
What is the backend of your application? I used this in C# (.NET Core) to get the content type of a file then set it as a header in the response:
public string GetContentType (string filePath) {
var contentTypeProvider = new FileExtensionContentTypeProvider();
string contentType;
if( !contentTypeProvider.TryGetContentType( filePath, out contentType ) ) {
contentType = "application/octet-stream";
};
return contentType;
}
Edit: modified OP code to handle content type dynamically:
var headers = response.headers;
var responseType = headers['content-type'];
var fileType = "text/plain";
var fileName = "report.txt";
if ( responseType == "application/octet-stream" ) {
fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
fileName = "report.xlsx";
}
var blob = new Blob([response.data], {
type: fileType
});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();

Write Byte Array to a file JavaScript

I have Java REST webservice that returns documents as byte array, I need to write JavaScript code to get the webservice's response and write it to a file in order to download that file as PDF Kindly see a screen shot of the webservice's response and see my sample code this code downloads a corrupted PDF file.
var data = new FormData();
data.append('PARAM1', 'Value1');
data.append('PARAM2', 'Value2');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'SERVICEURL');
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Basic " + btoa("username:password"));
xhr.onload = function() {
console.log('Response text = ' + xhr.responseText);
console.log('Returned status = ' + xhr.status);
var arr = [];
arr.push(xhr.responseText);
var byteArray = new Uint8Array(arr);
var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(byteArray, { type: 'application/octet-stream' }));
a.download = "tst.pdf";
// Append anchor to body.
document.body.appendChild(a)
a.click();
// Remove anchor from body
document.body.removeChild(a)
};
xhr.send(data);
Since you are requesting a binary file you need to tell XHR about that otherwise it will use the default "text" (UTF-8) encoding that will interpret pdf as text and will mess up the encoding. Just assign responseType property a value of 'blob' or the MIME type of pdf
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob'; // tell XHR that the response will be a pdf file
// OR xhr.responseType = 'application/pdf'; if above doesn't work
And you will access it using response property and not responseText.
So you will use arr.push(xhr.response); and it will return you a Blob.
If this doesn't work, inform me will update another solution.
Update:
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob'; // tell XHR that the response will be a pdf file
xhr.onload = function() {
var blob = this.response;
var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = "tst.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};

Handling stream from server and displaying the file in browser

I'm trying to find the global solution for handling strem files from the server.
My response object is angular http Response
My object has differnet _body for images/docs etc.
images is a long string of ##$.. and docs are json
I've been looking for solutions over the web, and got to implement this:
let contentType = resData.headers.get("Content-Type");
let blob = new Blob([resData.arrayBuffer()], {type: contentType});
let url = window.URL.createObjectURL(blob);
window.open('url: ', url);
This code downloads a file that has content-type of octet-stream
but all other files are not displayed in the browser.
My main goal is to have the same behavior if would have put in the URL the API that returns a stream, and the browser knows how to handle it (images are shown in browser, files that browser doesn't support are automatically downloaded etc.)
This is the code for the request.
return Observable.create(observer => {
let xhr = new XMLHttpRequest();
xhr.open(Object.keys(RequestMethod).find(k => RequestMethod[k] === url.method), url.url, true);
const shift =
xhr.setRequestHeader('Content-type', 'application/json');
xhr.responseType = (Object.keys(ResponseContentType).find(k => ResponseContentType[k] === url.responseType)).toLocaleLowerCase();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
let blob = new Blob([xhr.response], {type: body.MimeType});
observer.next({blob: blob, fileName: body.FileName});
observer.complete();
} else {
observer.error(xhr.response);
}
}
}
xhr.send(url.getBody());
This is the code for the special handling of each mimeType
handleAttachmentItem(resData) {
let blob = resData.blob;
const fileName = resData.fileName;
if (blob.type.includes('image')) {
let b64Response = window.URL.createObjectURL(blob);
let outputImg = document.createElement('img');
outputImg.src = b64Response;
let w = window.open();
w.document.write('<html><head><title>Preview</title><body style="background: #0e0e0e">');
w.document.write(outputImg.outerHTML);
} else if (blob.type.includes('text')) {
let url = window.URL.createObjectURL(blob);
window.open(url);
} else {
let a = document.createElement("a");
document.body.appendChild(a);
let url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
}
}

How to download excel file by using Spring response

I'm trying to download excel file by using Jasper Report 6.2.2
Here is my Spring Controller:
#RequestMapping(value = "/downloadExcel", method = RequestMethod.POST)
#ResponseBody
public void downloadMyReportExcelFile(#RequestBody ExcelFilter excelFilter, HttpServletResponse response) {
try {
reportExportBo.downloadReportFile(response, excelFilter);
} catch (Throwable e) {
LOGGER.error("Unknown error at REST Service", e);
}
}
and also here is my downloadReportFile method codes:
#Override
public void downloadReportFile(HttpServletResponse response, ExcelFilter excelFilter) {
List<myClassObject> myObjectList= objectRecordBo.myData(excelFilter);
InputStream is = this.getClass().getClassLoader().getResourceAsStream("/my_reports.jrxml");
ExcelExporter exporter = new ExcelExporter();
String fileName = "my_exported_report.xls";
JasperDesign jd = JRXmlLoader.load(is);
JasperReport jr = JasperCompileManager.compileReport(jd);
JasperPrint jprint = JasperFillManager.fillReport(jr, null, new JRBeanCollectionDataSource(myObjectList));
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
JRXlsExporter xlsExporter = new JRXlsExporter();
xlsExporter.setExporterInput(new SimpleExporterInput(jprint));
xlsExporter.setExporterOutput(new SimpleOutputStreamExporterOutput(response.getOutputStream()));
SimpleXlsReportConfiguration xlsReportConfiguration = new SimpleXlsReportConfiguration();
xlsReportConfiguration.setOnePagePerSheet(false);
xlsReportConfiguration.setRemoveEmptySpaceBetweenRows(true);
xlsReportConfiguration.setDetectCellType(false);
xlsReportConfiguration.setWhitePageBackground(false);
xlsExporter.setConfiguration(xlsReportConfiguration);
xlsExporter.exportReport();
my_reports.jrxml is suitable for myObjectList, columns and variables are same.
Also here is my javascript function;
function downloadService(url, paramData, fileName, $http) {
return $http.post(url, paramData, {responseType:'Content-Type'}).then(function (response) {
var blob = new Blob([response.data], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
var objectUrl = URL.createObjectURL(blob);
var a = document.createElement("a");
a.style = "display: none";
a.href = objectUrl;
a.download = fileName + '.xls' ;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(objectUrl);
}, 100);
}, function (response) {
//TODO
});
}
After calling downloadService method, i got excel downloaded but it is not readable
What do i wrong?
EDITED:
By the way when i'm using in html side;
<a style="float:right; " href="service/downloadExcel">{{ 'EXPORT_EXCEL' | translate}}</a>
and Spring controller is GET and no any #RequestBody, it works fine. But I need to pass parameters with JSON Object, so i can not use it.
I solved my problem.
First I set Header and Content Type of Response, before JasperDesign.
...
if (list != null && list.size() > 0) {
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
JasperDesign jd = JRXmlLoader.load(reportStream);
JasperReport jr = JasperCompileManager.compileReport(jd);
...
Also I updated my ajax service as;
...
return $http.post(newUrl, paramData, {responseType: 'arraybuffer'}).then(function (response) {
var blob = new Blob([response.data], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8'});
....
Thanks for helping guys...
Here is the another solution(javascript side) might be helpful for others:
var request = new XMLHttpRequest();
request.open('POST', url, true); // update the url
request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
request.responseType = 'blob';
var fileName = "simulation_results.xlsx"; // file name
request.onload = function (e) {
if (this.status === 200) {
var blob = this.response;
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, fileName);
} else {
var downloadLink = window.document.createElement('a');
var contentTypeHeader = request.getResponseHeader("Content-Type");
downloadLink.href = window.URL.createObjectURL(new Blob([blob], {
type: contentTypeHeader
}));
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
}
};
request.send(JSON.stringify(data)); // request data

Categories