Hi I'm tired after trying each solution but not able to make it work.
my http call in angular is
$http({
method: 'GET',
url: API_URL + 'v1/file/' + candidateId + '/download',
headers: {
'authToken': AuthService.getToken(),
},
responseType: 'arraybuffer'
})
.then(function onSuccess(response) {
successCallback(response);
},
function onError(response) {
errorCallback(response);
});
and in Success of this code
vm.onSuccessDownloadResume = function(response) {
var blob = new Blob([response.data], {type: response.headers('content-type')});
var objectUrl = URL.createObjectURL(blob);
window.open(objectUrl);
};
I tried webkitURL.createObjectURL(blob), it's working fine for chrome only but URL.createObject is not at all working.
getting message
URL.createObjectURL() is not a function()
Thanks
There's some compatibility issues related to createObjectURL, you can see more here: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
And with Blob: https://developer.mozilla.org/en-US/docs/Web/API/BlobBuilder#Browser_compatibility
I had a similar issue, and this SO link helped me with a workaround: Blob constructor browser compatibility
I've copied this function and made some minor changes:
var NewBlob = function(data, datatype)
{
var out;
try {
out = new Blob([data], {type: datatype});
console.debug("case 1");
}
catch (e) {
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if (e.name == 'TypeError' && window.BlobBuilder) {
var bb = new BlobBuilder();
bb.append(data);
out = bb.getBlob(datatype);
console.debug("case 2");
}
else if (e.name == "InvalidStateError") {
// InvalidStateError (tested on FF13 WinXP)
out = new Blob([data], {type: datatype});
console.debug("case 3");
}
else {
// We're screwed, blob constructor unsupported entirely
console.debug("Errore");
}
}
return out;
}
Related
I am trying to add the functionality to download a file hosted in a server. To access the file I have to send the Authorization header, thus I have to send an XHR request to get the file from the server. Since the file content is in a variable, I have to create a data url to make it available as the href attribute of an anchor tag and click it programmatically to download the file.
It's working good in almost all the browser (Except IE11, for which I have written a separate code), but in iOS Safari (in some versions of iOS), it's giving errors. Here's the code that I am using -
var isBrowserIE = window.navigator && window.navigator.msSaveOrOpenBlob;
var dataHref = 'https://example.com/doc.pdf';
var xhr = new XMLHttpRequest();
xhr.open('GET', dataHref, true);
xhr.setRequestHeader('Content-Type', 'application/pdf');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.responseType = isBrowserIE ? 'blob' : 'arraybuffer';
xhr.onload = function (e) {
if (this.status == 200) {
//For IE11
if (isBrowserIE) {
// Create a new Blob object using the response data of the onload object
var blob = new Blob([this.response], { type: 'application/pdf' });
var bool = window.navigator.msSaveOrOpenBlob(blob, docName);
if (!bool) {
alert("Download failed, Please try again later");
}
} else {
var uInt8Array = new Uint8Array(this.response);
var i = uInt8Array.length;
var binaryString = new Array(i);
while (i--) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
var data = binaryString.join('');
var base64 = window.btoa(data);
var dataUrl = 'data:application/octet-stream;charset=utf-16le;base64,' + base64;
var element = document.createElement('a');
element.setAttribute('href', dataUrl);
element.setAttribute('download', 'doc.pdf');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
} else {
alert("Download failed, Please try again later");
closeWindow();
}
};
xhr.send();
Here's the possible error I am getting which is related -
Safari cannot open the page.<br><br>The error was: “Data URL decoding failed”.
Is there anything I missed which is the reason for this error? The error is occuring only in iPad 4 and iPad 5, but working in iPad mini and iPhone XR. Not sure why is it working in some versions of iOS devices and not in others.
So, I finally figured it out. Here's my final code with explanations in comments (Sorry for ES5 code, I needed to support IE11 and the current project is not using babel yet) -
/* exported DownloadHandler */
/* global Uint8Array*/
var DownloadHandler = (function() {
function isMobileDevice() {
return navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|Opera Mini|IEMobile/i);
}
function isChromeBrowser() {
return navigator.userAgent.match(/Crios|Chrome/i);
}
function isIEBrowser() {
return window.navigator && window.navigator.msSaveOrOpenBlob;
}
function isSafariBrowser() {
return navigator.userAgent.match(/Safari/i);
}
function getResponseType() {
// Both Desktop Chrome and IE supports blob properly
// Chrome also supports Data URI way, but it fails miserably when the file size is more than 2 MB (Not sure about the exact limit though).
if (isIEBrowser() || isChromeBrowser()) {
return 'blob';
} else if (isMobileDevice()) {
return 'arraybuffer';
}
return 'blob';
}
function getBlobUriFromResponse(response) {
var blob = new Blob([response], { type: 'application/pdf' });
var downloadUrl = URL.createObjectURL(blob);
return downloadUrl;
}
function getDataUriFromResponse(response) {
var uInt8Array = new Uint8Array(response);
var i = uInt8Array.length;
var binaryString = new Array(i);
while (i--) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
var data = binaryString.join('');
var base64 = window.btoa(data);
var dataUrl = 'data:application/octet-stream;charset=utf-16le;base64,' + base64;
return dataUrl;
}
function downloadFileUsingXHR(fileName, fileUrl, fileMimeType, requestType, headersList) {
var xhr = new XMLHttpRequest();
xhr.open(requestType, fileUrl, true);
xhr.setRequestHeader('Content-Type', fileMimeType);
for (var i = 0; i < headersList.length; i++) {
var header = headersList[i];
xhr.setRequestHeader(header.key, header.value);
}
xhr.responseType = getResponseType();
xhr.onload = function() {
if (this.status == 200) {
//For IE11
//IE uses blob with vendor specific code
if (isIEBrowser()) {
// Create a new Blob object using the response data of the onload object
var blob = new Blob([this.response], { type: fileMimeType });
var bool = window.navigator.msSaveOrOpenBlob(blob, fileName);
if (!bool) {
alert('Download failed, Please try again later');
}
} else {
var dataUrl;
if (this.responseType === 'blob') {
dataUrl = getBlobUriFromResponse(this.response);
} else {
dataUrl = getDataUriFromResponse(this.response);
}
var element = document.createElement('a');
// Safari doesn't work well with blank targets
if (!isSafariBrowser()) {
element.setAttribute('target', '_blank');
}
element.setAttribute('href', dataUrl);
element.setAttribute('download', fileName);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
} else {
alert('Download failed, Please try again later');
}
};
xhr.send();
}
return {
downloadFileUsingXHR: downloadFileUsingXHR
};
})();
Here's how to use the above code:
DownloadHandler.downloadFileUsingXHR('example.pdf', 'https://example.com/doc.pdf', 'application/pdf','GET',[{key:'Authorization',value:'Bearer ' + token}]);
I'll probably convert it into a library later and post a link here. I'll get the chance to refine the code too
So I'm working on a website that needs to make a call to the server and it returns a zip file, the thing is that I'm not entierly sure I'm doing everything right. The code looks kind of like this:
function download(){
if($('.download').hasClass('activeBtn')){
$.ajax({
type: 'GET',
url: someUrl,
contentType: 'application/zip',
dataType: 'text',
headers: {
'Api-Version': '3.4'
}
}).then(function (data) {
console.log(data); //Basically prints the byte array
//Here I should build the file and download it
});
}
}
As you can see I need to makeup the file with the byte array that is in the response, how can I do that?
An approach utilizing XMLHttpRequest(); check if a element has download property, if true, set download property to an objectURL; else, use window.open() with parameter objectURL of Blob response
function downloadFile(url, headers, filename) {
function handleFile(data) {
console.log(this.response || data);
var file = URL.createObjectURL(this.response || data);
filename = filename || url.split("/").pop();
var a = document.createElement("a");
// if `a` element has `download` property
if ("download" in a) {
a.href = file;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} else {
// use `window.open()` if `download` not defined at `a` element
window.open(file)
}
}
var request = new XMLHttpRequest();
request.responseType = "blob";
request.onload = handleFile;
request.open("GET", url);
for (var prop in headers) {
request.setRequestHeader(prop, headers[prop]);
}
request.send();
}
downloadFile("/path/to/resource/", {"x-content": "abc"}, "filename.zip")
jQuery version using fork of jquery-ajax-blob-arraybuffer.js
/**
*
* jquery.binarytransport.js
*
* #description. jQuery ajax transport for making binary data type requests.
* #version 1.0
* #author Henry Algus <henryalgus#gmail.com>
*
*/
// use this transport for "binary" data type
$.ajaxTransport("+binary", function(options, originalOptions, jqXHR){
// check for conditions and support for blob / arraybuffer response type
if (window.FormData && ((options.dataType && (options.dataType == 'binary'))
|| (options.data
&& ((window.ArrayBuffer && options.data instanceof ArrayBuffer)
|| (window.Blob && options.data instanceof Blob))))
)
{
return {
// create new XMLHttpRequest
send: function(headers, callback){
// setup all variables
var xhr = new XMLHttpRequest(),
url = options.url,
type = options.type,
async = options.async || true,
// blob or arraybuffer. Default is blob
dataType = options.responseType || "blob",
data = options.data || null,
username = options.username || null,
password = options.password || null;
xhr.addEventListener('load', function(){
var data = {};
data[options.dataType] = xhr.response;
// make callback and send data
callback(xhr.status
, xhr.statusText
, data
, xhr.getAllResponseHeaders());
});
xhr.open(type, url, async, username, password);
// setup custom headers
for (var i in headers ) {
xhr.setRequestHeader(i, headers[i] );
}
xhr.responseType = dataType;
xhr.send(data);
},
abort: function(){
jqXHR.abort();
}
};
}
});
function downloadFile(url, headers, filename) {
return $.ajax({
url:url,
dataType:"binary",
processData: false,
headers:headers
})
.then(function handleFile(data) {
console.log(this.response || data);
var file = URL.createObjectURL(this.response || data);
filename = filename || url.split("/").pop();
var a = document.createElement("a");
// if `a` element has `download` property
if ("download" in a) {
a.href = file;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} else {
// use `window.open()` if `download` not defined at `a` element
window.open(file)
}
})
}
downloadFile("/path/to/resource/", {"x-custom-header":"abc"}, "filename.zip");
Just have to download it, that's all
You can use <a> element, download attribute
$("<a>", {href: someUrl,
download: "filename.zip"
}).appendTo("body")[0].click()
Alternatively parse file using a library, e.g., zip.js, create multiple or single downloadable .zip from data contained within file.
Create an objectURL of each file, download each file using a element.
If download attribute is not available at browser, you can use data URI of file object with MIME type set to application/octet-stream to download file
I ended up using fetch api. For me its more clear whats goin on:
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(dwnloadModel)
})
.then((response) => {
response.blob().then((blob) => {
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', downloadUrl);
link.setAttribute('download', 'file');
link.style.display = 'none';
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(link.href);
document.body.removeChild(link);
})
});
I basicly take the blob from my response and put it to url, which i then append to dom as a 'a' tag and click it by js. Once is clicked i clean up the a tag and revoke the url. works fine for me.
Okay, I've read just about every Stack Overflow question on PDF downloading from a web service. None of them have helped me so far. I'm using this as a last ditch effort to try and get some answers. Basically, I'm making a GET request to the API, and need to get a dynamically generated PDF back. We've tried doing this with receiving a byte[] and now we're returning a stream with the content. The following is what we have in the web service controller:
var result = await resp.Content.ReadAsAsync<byte[]>();
var response = request.CreateResponse(HttpStatusCode.OK);
var dataStream = new MemoryStream(result);
response.Content = new StreamContent(dataStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "idcard.pdf";
var fileStream = new FileStream(#"c:\temp\temp.pdf", FileMode.Create);
fileStream.Write(result, 0, result.Length);
fileStream.Close();
return response;
The FileStream part is a test we were doing to see if saving the data to a temporary file worked and that the PDF can be saved. That part does work. Going to c:\temp and opening the idcard.pdf file works perfectly. One of the problems with that is it does it silently and the user wouldn't know it's there. We can tell them, but we'd really prefer the PDF to open in the browser by default and/or have it save through the browser so they knew something happened.
My Angular code looks like this:
.factory('memberIdCard', ['$http', function($http) {
var get = function() {
return $http({
method: 'GET',
url: '/Member/IdCard',
headers: {
accept: 'application/octet-stream'
},
responseType: 'arraybuffer',
transformResponse: function(data) {
var pdf;
console.log('data: ', data);
if (data) {
pdf = new Blob([data], {
type: 'application/pdf'
});
console.log('pdf: ', pdf);
}
return pdf;
}
})
}
return {
get: get
}
}]);
I have tried this part with $http and $resource and neither works. Now, in my controller:
$scope.printIdCard = function() {
memberIdCard.get().then(function(data) {
var pdf = data.data;
FileSaver.saveAs(pdf, 'idcard.pdf');
var pdfUrl = window.URL.createObjectURL(pdf);
$scope.pdfView = $sce.trustAsResourceUrl(pdfUrl);
window.open($scope.pdfView);
});
As a note, FileSaver is from angular-file-saver.
After all that, the new window opens, but there's an error that says: Failed to load PDF Document, and if you try and open it in Adobe Acrobat it has an error that says: Adobe Acrobat Reader DC could not open 'idcard.pdf' because it is either not a supported file type or because the file has been damaged (for example, it was sent as an email attachment and wasn't correctly decoded).
Any help would be greatly appreciated. I feel like I've done everything that was suggested in many of the other SO questions, but maybe I'm missing something I just haven't been able to see.
Thanks!
I did something similar for .xlsx files, but the concept is the same. Hope this can help you, its working for me.
I got the javascript code to download the file from another SO answer, which I can't link because I don't remember where it was.
My web api controller looks like this:
[Route("all")]
[HttpGet]
public HttpResponseMessage GetAll(HttpRequestMessage request)
{
HttpResponseMessage response = null;
MemoryStream stream = _exportService.CreateDataStream();
response = request.CreateResponse(HttpStatusCode.OK);
response.Content = new ByteArrayContent(stream.GetBuffer());
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.Add("content-type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
return response;
}
And the angular service:
(function (app) {
'use strict';
app.factory('exportService', exportService);
exportService.$inject = ['$q', '$http'];
function exportService($q, $http) {
var extension = '.xlsx';
var service = {
export: exportData
};
function exportData(event, fname){
var config = {
responseType: 'arraybuffer'
}
var path = 'api/export/'+event;
var deferred = $q.defer();
return $http.get(path, config).then(
function(response) {
var data = response.data;
var status = response.status;
var headers = response.headers();
var octetStreamMime = 'application/octet-stream';
var success = false;
var filename = fname + extension;
var contentType = headers['content-type'] || octetStreamMime;
try
{
// Try using msSaveBlob if supported
var blob = new Blob([data], { type: contentType });
if(navigator.msSaveBlob)
navigator.msSaveBlob(blob, filename);
else {
// Try using other saveBlob implementations, if available
var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if(saveBlob === undefined) throw "Not supported";
saveBlob(blob, filename);
}
success = true;
deferred.resolve();
} catch(ex)
{
}
if(!success)
{
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if(urlCreator)
{
// Try to use a download link
var link = document.createElement('a');
if('download' in link)
{
// Try to simulate a click
try
{
// Prepare a blob URL
var blob = new Blob([data], { type: contentType });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
// Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
success = true;
deferred.resolve();
} catch(ex) {
}
}
if(!success)
{
// Fallback to window.location method
try
{
var blob = new Blob([data], { type: octetStreamMime });
var url = urlCreator.createObjectURL(blob);
window.location = url;
success = true;
deferred.resolve();
} catch(ex) {
deferred.reject();
}
}
}
}
return deferred.promise;
},
function(error) {
return $q.reject(error);
});
}
return service;
}
})(angular.module('core.module'));
I want to download a pdf file for jquery ajax response. Ajax response contains pdf file data. I tried this solution. My code is given below but I always get a blank pdf.
$(document).on('click', '.download-ss-btn', function () {
$.ajax({
type: "POST",
url: 'http://127.0.0.1:8080/utils/json/pdfGen',
data: {
data: JSON.stringify(jsonData)
}
}).done(function (data) {
var blob = new Blob([data]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "Sample.pdf";
link.click();
});
});
jQuery has some issues loading binary data using AJAX requests, as it does not yet implement some HTML5 XHR v2 capabilities, see this enhancement request and this discussion
Given that, you have one of two solutions:
First solution, abandon JQuery and use XMLHTTPRequest
Go with the native HTMLHTTPRequest, here is the code to do what you need
var req = new XMLHttpRequest();
req.open("GET", "/file.pdf", true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
console.log(blob.size);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="Dossier_" + new Date() + ".pdf";
link.click();
};
req.send();
Second solution, use the jquery-ajax-native plugin
The plugin can be found here and can be used to the XHR V2 capabilities missing in JQuery, here is a sample code how to use it
$.ajax({
dataType: 'native',
url: "/file.pdf",
xhrFields: {
responseType: 'blob'
},
success: function(blob){
console.log(blob.size);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="Dossier_" + new Date() + ".pdf";
link.click();
}
});
I am newbie and most of the code is from google search. I got my pdf download working with the code below (trial and error play). Thank you for code tips (xhrFields) above.
$.ajax({
cache: false,
type: 'POST',
url: 'yourURL',
contentType: false,
processData: false,
data: yourdata,
//xhrFields is what did the trick to read the blob to pdf
xhrFields: {
responseType: 'blob'
},
success: function (response, status, xhr) {
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches !== null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
var linkelem = document.createElement('a');
try {
var blob = new Blob([response], { type: 'application/octet-stream' });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "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."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.target = "_blank";
a.click();
}
} else {
window.location = downloadUrl;
}
}
} catch (ex) {
console.log(ex);
}
}
});
You can do this with html5 very easily:
var link = document.createElement('a');
link.href = "/WWW/test.pdf";
link.download = "file_" + new Date() + ".pdf";
link.click();
link.remove();
For those looking a more modern approach, you can use the fetch API. The following example shows how to download a PDF file. It is easily done with the following code.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/pdf'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
})
I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.
Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following [link][1].
Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.
Try this:
$(document).on('click', '.download-ss-btn', function () {
$.ajax({
type: "POST",
url: 'http://127.0.0.1:8080/utils/json/pdfGen',
data: {
data: JSON.stringify(jsonData)
},
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}).done(function (data) {
let binaryString = window.atob(data);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
var blob = new Blob([data], {type: "application/pdf"});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "Sample.pdf";
link.click();
});
});
I have written a directive based on Scott's answer. You'd use it like so:
<button class="btn btn-success"
download-response="getData()"
download-success="getDataSuccess()"
download-error="getDataError()"
download-name="{{name}}.pdf"
download-backup-url="/Backup/File.pdf">
Save
</button>
Issue: the code below throws an error TypeError: Invalid calling object in IE11 on the first method (line: saveBlob(blob, filename);). Even though it falls back to other methods of downloading, it is my understanding that the saveMethod1 should work in IE11.
Here is the code:
'use strict';
// directive allows to provide a function to be executed to get data to be downloaded
// attributes:
// download-response - Required. Function to get data. It must return a promise. It must be declared on the $scope.
// download-success - Optional. Function to be executed if download-response function was successfully resolved. It must be declared on the $scope.
// download-error - Optional. Function to be executed if download-response function return a promise rejection. It must be declared on the $scope.
// download-mime - Optional. provide a mime type of data being downloaded. Defaulted to "application/octet-stream"
// download-name - Optional. name of the file to download. Defaulted to "download.bin"
// download-backup-url - in case browser does not support dynamic download, this url will be called to get the file
angular.module('app.directives')
.directive('downloadResponse', [ '$parse', '$timeout',
function ($parse, $timeout) {
function saveMethod1(data, filename, contentType) {
// Support for saveBlob method (Currently only implemented in Internet Explorer as msSaveBlob, other extension in case of future adoption)
var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if (saveBlob) {
// Save blob is supported, so get the blob as it's contentType and call save.
var blob = new Blob([data], { type: contentType });
saveBlob(blob, filename);
//console.log("SaveBlob Success");
} else {
throw 'saveBlob is not supported. Falling back to the next method';
}
}
function saveMethod2(data, filename, contentType, octetStreamMime) {
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
// Try to use a download link
var link = document.createElement("a");
var url;
if ("download" in link) {
// Prepare a blob URL
var blob = new Blob([data], { type: contentType });
url = urlCreator.createObjectURL(blob);
link.setAttribute("href", url);
// Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
//console.log("Download link Success");
} else {
// Prepare a blob URL
// Use application/octet-stream when using window.location to force download
var blob = new Blob([data], { type: octetStreamMime });
url = urlCreator.createObjectURL(blob);
window.location = url;
//console.log("window.location Success");
}
} else {
throw 'UrlCreator not supported. Falling back to the next method';
}
}
function saveMethod3(attrs) {
if (attrs.downloadBackupUrl && attrs.downloadBackupUrl != '') {
console.log('opening ' + attrs.downloadBackupUrl);
window.open('http://' + document.domain + attrs.downloadBackupUrl, '_blank');
} else {
throw 'Could not download a file using any of the available methods. Also you did not provide a backup download link. No more bullets left...';
}
}
return {
restrict: 'A',
scope: false,
link:function (scope, elm, attrs) {
var getDataHandler = $parse(attrs.downloadResponse);
elm.on('click', function() {
var promise = getDataHandler(scope);
promise.then(
function (data) {
if (attrs.downloadSuccess && attrs.downloadSuccess != '') {
var successHandler = $parse(attrs.downloadSuccess);
successHandler(scope);
}
var octetStreamMime = "application/octet-stream";
var filename = attrs.downloadName || "download.bin";
var contentType = attrs.downloadMime || octetStreamMime;
try {
saveMethod1(data, filename, contentType);
return;
} catch (e) {
console.log(e);
try {
saveMethod2(data, filename, contentType, octetStreamMime);
return;
} catch (e) {
console.log(e);
try {
saveMethod3(attrs);
return;
} catch (e) {
throw e;
}
throw e;
}
throw e;
}
},
function(data) {
if (attrs.downloadError && attrs.downloadError != '') {
var errorHandler = $parse(attrs.downloadError);
errorHandler(scope);
}
}
);
});
}
};
}
]);
Any help is greatly appreciated!
Problem:
I have resolved this issue. It appears that Internet Explorer 11 does not like aliases to the msSaveBlob function, demonstrated by the simplest examples:
// Succeeds
navigator.msSaveBlob(new Blob(["Hello World"]), "test.txt");
// Fails with "Invalid calling object"
var saveBlob = navigator.msSaveBlob;
saveBlob(new Blob(["Hello World"]), "test.txt");
So essentially the generic alias created to encapsulate saveBlob functionality, which should be permissible, prevents msSaveBlob from working as expected:
var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
Workaround:
So the work around is to test for msSaveBlob separately.
if(navigator.msSaveBlob)
navigator.msSaveBlob(blob, filename);
else {
// Try using other saveBlob implementations, if available
var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if(saveBlob === undefined) throw "Not supported";
saveBlob(blob, filename);
}
tl;dr
So the msSaveBlob method works, as long as you don't alias the function. Perhaps it's a security precaution - but then perhaps a security exception would have been more appropriate, though I think it is most likely a flaw, considering the source. :)