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);
}
}
Related
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();
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.xmlMKÄ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();
I have this in my Angular.js controller that downloads a CSV file:
var blob = new Blob([csvContent.join('')], { type: 'text/csv;charset=utf-8'});
var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
link.href = URL.createObjectURL(blob);
link.download = 'teams.csv';
link.click();
This works perfectly in Chrome but not in IE. A browser console log says:
HTML7007: One or more blob URLs were revoked by closing the blob for
which they were created. These URLs will no longer resolve as the data
backing the URL has been freed.
What does it mean and how can I fix it?
Try this using, this or useragent
if (navigator.appVersion.toString().indexOf('.NET') > 0)
window.navigator.msSaveBlob(blob, filename);
else
{
var blob = new Blob(['stringhere'], { type: 'text/csv;charset=utf-8' });
var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
link.href = URL.createObjectURL(blob);
link.download = 'teams.csv';
link.click();
}
IE won't allow you to open blobs directly. You have to use msSaveOrOpenBlob. There's also msSaveBlob
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
var objectUrl = URL.createObjectURL(blob);
window.open(objectUrl);
}
I needed to use a Blob to download a converted a base64 PNG image. I was able to successfully download the blob on IE11 with window.navigator.msSaveBlob
See the following msdn link:
http://msdn.microsoft.com/en-us/library/hh779016(v=vs.85).aspx
Specifically, you should call:
window.navigator.msSaveBlob(blobObject, 'msSaveBlob_testFile.txt');
where blobObject is a Blob created in the usual fashion.
Complete Solution for Chrome, Internet Explorer Firefox and Opera
There are lots of nice bits on this page, but I had to use a combination of a few things to get it all to work. Hopefully this helps you.
Use a button or link to trigger a function called download():
<button class="button-no save-btn" ng-click="download()">DOWNLOAD</button>
Put this in your controller:
$scope.download = function () {
// example shows a JSON file
var content = JSON.stringify($scope.stuffToPutInFile, null, " ");
var blob = new Blob([content], {type: 'application/json;charset=utf-8'});
if (window.navigator && window.navigator.msSaveBlob) {
// Internet Explorer workaround
$log.warn("Triggering download using msSaveBlob");
window.navigator.msSaveBlob(blob, "export.json");
} else {
// other browsers
$log.warn("Triggering download using webkit");
var url = (window.URL || window.webkitURL).createObjectURL(blob);
// create invisible element
var downloadLink = angular.element('<a></a>');
downloadLink.attr('href', url);
downloadLink.attr('download', 'export.json');
// make link invisible and add to the DOM (Firefox)
downloadLink.attr('style','display:none');
angular.element(document.body).append(downloadLink);
// trigger click
downloadLink[0].click();
}
};
What's your IE browser version? You need a modern browser or IE10+
http://caniuse.com/bloburls
Maybe you need some delay. What about with:
link.click();
setTimeout(function(){
document.body.createElementNS('http://www.w3.org/1999/xhtml', 'a');
URL.revokeObjectURL(link.href);
}, 100);
I needed to get the download feature to work in Chrome and IE11. I had good success with this code.
HTML
<div ng-repeat="attachment in attachments">
<a ng-click="openAttachment(attachment)" ng-href="{{attachment.fileRef}}">{{attachment.filename}}</a>
</div>
JS
$scope.openAttachment = function (attachment) {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(
b64toBlob(attachment.attachment, attachment.mimeType),
attachment.filename
);
}
};
Done it this way, working fine for me.
downloadFile(data) {
if (navigator.msSaveBlob) {
let blob = new Blob([data], {
"type": "text/csv;charset=utf8;"
});
navigator.msSaveBlob(blob, this.fileName);
}
else {
let blob = new Blob(['\ufeff' + data], { type: 'text/csv;charset=utf-8;' });
let $link = document.createElement("a");
let url = URL.createObjectURL(blob);
$link.setAttribute("target", "_blank");
$link.setAttribute("href", url);
$link.setAttribute("download", this.fileName);
$link.style.visibility = "hidden";
document.body.appendChild($link);
$link.click();
document.body.removeChild($link);
}
}
Try to use this instead :
var blob = file.slice(0, file.size);
Create polyfill method as below,had a variable filename since in my case download filename was static.This method will be called while blob function is not supported as in case of Internet explorer
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype,
'toBlob', {
value: function (callback, type, quality) {
var canvas = this;
setTimeout(function () {
var binStr = atob(canvas.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
var blob = new Blob([arr], {
type: 'image/png'
});
window.navigator.msSaveOrOpenBlob(blob, fileName);
});
}
});
}
try {
const blob = new Blob([res.body], {
type: res.headers.get('Content-Type'),
});
const file = new File([blob], this.getFileName(res), {
type: res.headers.get('Content-Type'),
});
saveAs(file);
} catch (err) {
var textFileAsBlob = new Blob([res.body], {
type: res.headers.get('Content-Type'),
});
window.navigator.msSaveBlob(textFileAsBlob, this.getFileName(res));
}
To get the file name. Use the below function.
getFileName(response: any) {
let name: string;
try {
const contentDisposition: string = response.headers.get(
'content-disposition'
);
const [, filename] = contentDisposition.split('filename=');
name = filename;
} catch (e) {
name = 'File_Name_Not_Specified_' + new Date();
}
return name;
}
This worked for me.
I'm currently working on fixing a CSV Export of a data table on a web application.
It's currently able to export on all needed browsers except Chrome when you click the export button.
I've been trying to figure it out for a while now and I'm resisting pulling my hair out.
The code below is my service that was working until recently. Any help is greatly appreciated.
svc.downloadContent =
(target, fileName, content) => {
if (!browserSvc.canDownloadFiles()) return;
// IE10
if (window.navigator.msSaveOrOpenBlob) {
const blob = new Blob([content], {type: 'text/csv'});
window.navigator.msSaveOrOpenBlob(blob, fileName);
// IE9
} else if (env.browser === 'Explorer') {
const frame = document.createElement('iframe');
document.body.appendChild(frame);
angular.element(frame).hide();
const cw = frame.contentWindow;
const cwDoc = cw.document;
cwDoc.open('text/csv', 'replace');
cwDoc.write(content);
cwDoc.close();
cw.focus();
cwDoc.execCommand('SaveAs', true, fileName);
document.body.removeChild(frame);
// Sane browsers
} else {
const blob = new Blob([content], {type: 'text/csv'});
const url = URL.createObjectURL(blob);
const a = angular.element(target);
const download = a.attr('download');
// If not already downloading ...
if (!download) {
a.attr('download', fileName);
a.attr('href', url);
// This must run in the next tick to avoid
// "$digest already in progress" error.
//$timeout(() => target.click());
try {
target.click();
// Clear attributes to prepare for next download.
a.attr('download', '');
a.attr('href', '');
} catch (e) {
console.error('csv-svc.js: e =', e);
}
}
}
I managed to figure this out just a couple minutes after posting my question. I needed to add an else if just for Chrome. However, I will post the fix and leave this up, in hopes that it may help someone else in the future.
else if (env.browser === 'Chrome') {
const blob = new Blob([content], {type: 'text/csv'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.style = 'visibility:hidden';
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
I have this in my Angular.js controller that downloads a CSV file:
var blob = new Blob([csvContent.join('')], { type: 'text/csv;charset=utf-8'});
var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
link.href = URL.createObjectURL(blob);
link.download = 'teams.csv';
link.click();
This works perfectly in Chrome but not in IE. A browser console log says:
HTML7007: One or more blob URLs were revoked by closing the blob for
which they were created. These URLs will no longer resolve as the data
backing the URL has been freed.
What does it mean and how can I fix it?
Try this using, this or useragent
if (navigator.appVersion.toString().indexOf('.NET') > 0)
window.navigator.msSaveBlob(blob, filename);
else
{
var blob = new Blob(['stringhere'], { type: 'text/csv;charset=utf-8' });
var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
link.href = URL.createObjectURL(blob);
link.download = 'teams.csv';
link.click();
}
IE won't allow you to open blobs directly. You have to use msSaveOrOpenBlob. There's also msSaveBlob
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
var objectUrl = URL.createObjectURL(blob);
window.open(objectUrl);
}
I needed to use a Blob to download a converted a base64 PNG image. I was able to successfully download the blob on IE11 with window.navigator.msSaveBlob
See the following msdn link:
http://msdn.microsoft.com/en-us/library/hh779016(v=vs.85).aspx
Specifically, you should call:
window.navigator.msSaveBlob(blobObject, 'msSaveBlob_testFile.txt');
where blobObject is a Blob created in the usual fashion.
Complete Solution for Chrome, Internet Explorer Firefox and Opera
There are lots of nice bits on this page, but I had to use a combination of a few things to get it all to work. Hopefully this helps you.
Use a button or link to trigger a function called download():
<button class="button-no save-btn" ng-click="download()">DOWNLOAD</button>
Put this in your controller:
$scope.download = function () {
// example shows a JSON file
var content = JSON.stringify($scope.stuffToPutInFile, null, " ");
var blob = new Blob([content], {type: 'application/json;charset=utf-8'});
if (window.navigator && window.navigator.msSaveBlob) {
// Internet Explorer workaround
$log.warn("Triggering download using msSaveBlob");
window.navigator.msSaveBlob(blob, "export.json");
} else {
// other browsers
$log.warn("Triggering download using webkit");
var url = (window.URL || window.webkitURL).createObjectURL(blob);
// create invisible element
var downloadLink = angular.element('<a></a>');
downloadLink.attr('href', url);
downloadLink.attr('download', 'export.json');
// make link invisible and add to the DOM (Firefox)
downloadLink.attr('style','display:none');
angular.element(document.body).append(downloadLink);
// trigger click
downloadLink[0].click();
}
};
What's your IE browser version? You need a modern browser or IE10+
http://caniuse.com/bloburls
Maybe you need some delay. What about with:
link.click();
setTimeout(function(){
document.body.createElementNS('http://www.w3.org/1999/xhtml', 'a');
URL.revokeObjectURL(link.href);
}, 100);
I needed to get the download feature to work in Chrome and IE11. I had good success with this code.
HTML
<div ng-repeat="attachment in attachments">
<a ng-click="openAttachment(attachment)" ng-href="{{attachment.fileRef}}">{{attachment.filename}}</a>
</div>
JS
$scope.openAttachment = function (attachment) {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(
b64toBlob(attachment.attachment, attachment.mimeType),
attachment.filename
);
}
};
Done it this way, working fine for me.
downloadFile(data) {
if (navigator.msSaveBlob) {
let blob = new Blob([data], {
"type": "text/csv;charset=utf8;"
});
navigator.msSaveBlob(blob, this.fileName);
}
else {
let blob = new Blob(['\ufeff' + data], { type: 'text/csv;charset=utf-8;' });
let $link = document.createElement("a");
let url = URL.createObjectURL(blob);
$link.setAttribute("target", "_blank");
$link.setAttribute("href", url);
$link.setAttribute("download", this.fileName);
$link.style.visibility = "hidden";
document.body.appendChild($link);
$link.click();
document.body.removeChild($link);
}
}
Try to use this instead :
var blob = file.slice(0, file.size);
Create polyfill method as below,had a variable filename since in my case download filename was static.This method will be called while blob function is not supported as in case of Internet explorer
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype,
'toBlob', {
value: function (callback, type, quality) {
var canvas = this;
setTimeout(function () {
var binStr = atob(canvas.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
var blob = new Blob([arr], {
type: 'image/png'
});
window.navigator.msSaveOrOpenBlob(blob, fileName);
});
}
});
}
try {
const blob = new Blob([res.body], {
type: res.headers.get('Content-Type'),
});
const file = new File([blob], this.getFileName(res), {
type: res.headers.get('Content-Type'),
});
saveAs(file);
} catch (err) {
var textFileAsBlob = new Blob([res.body], {
type: res.headers.get('Content-Type'),
});
window.navigator.msSaveBlob(textFileAsBlob, this.getFileName(res));
}
To get the file name. Use the below function.
getFileName(response: any) {
let name: string;
try {
const contentDisposition: string = response.headers.get(
'content-disposition'
);
const [, filename] = contentDisposition.split('filename=');
name = filename;
} catch (e) {
name = 'File_Name_Not_Specified_' + new Date();
}
return name;
}
This worked for me.