Why does AJAX output comes with wrong encoding? - javascript

I'm getting a file from a server with AJAX (Angular).The file is a simple XLSX document, sent like this:
ob_start();
$file = \PHPExcel_IOFactory::createWriter($xls, 'Excel2007');
$file->save('php://output');
$response->setContent(ob_get_clean());
$response->headers->replace(array(
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition' => 'attachment;filename=file.xlsx"'
));
When I make a request from frontend, I use Accept header too. Then I save the file with angular-file-saver using FileSaver.js and Blob.js.
But the received file is corrupt and I can't open it in Excel: it's size is (for example) 12446 bytes, but Chrome's DevTools Network tab shows responses Content-Length header as 7141 bytes.
How can I solve this problem?
UPD:
I'm sending a request like this:
$http.get(baseURL + '/' + entity + '/export/?' + condition + sort, {
headers: {'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8'}
});
and downloading file just like this:
var data = new Blob([response.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'});
FileSaver.saveAs(data, 'file.xlsx');

The way I got around the problem was using plain JS AJAX, instead of AngularJS. (There might be a problem with AngularJS and JQuery handling binary responses.)
This should work:
var request = new XMLHttpRequest();
request.open('GET', 'http://yourserver/yourpath', true);
request.responseType = 'blob';
request.onload = function (e) {
if (this.status === 200) {
var blob = this.response;
if (window.navigator.msSaveOrOpenBlob) {
var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
window.navigator.msSaveBlob(blob, fileNamePattern.exec(request.getResponseHeader("content-disposition"))[1]);
} else {
var downloadLink = window.document.createElement('a');
var contentTypeHeader = request.getResponseHeader("Content-Type");
var b = new Blob([blob], { type: contentTypeHeader });
downloadLink.href = window.URL.createObjectURL(b);
var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
downloadLink.download = fileNamePattern.exec(request.getResponseHeader("content-disposition"))[1];
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(b);
}
}
};
request.send();
Code is based on this and this.
FYI, I found that new Blob([response.data], ...) returns almost double the size of response.data when response.data is not returned as blob, but text/plain or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. To get around it, you need to pass it an array of bytes instead:
var i, l, d, array;
d = this.result;
l = d.length;
array = new Uint8Array(l);
for (var i = 0; i < l; i++){
array[i] = d.charCodeAt(i);
}
var b = new Blob([array], {type: 'application/octet-stream'});
window.location.href = URL.createObjectURL(b);
Code is from here.
Anyways, since the AJAX response is not correct using AngularJS, you won't get a valid xlsx file this way. You need to go with vanilla JS.

Related

Use the base64 preview of the binary data response (zip file) in angularjs

I always get this error in the downloaded zip file C:\Users\me\Downloads\test.zip: Unexpected end of archive
My current code is:
var blob = new Blob([data], { // data here is the binary content
type: 'octet/stream',
});
var zipUrl = window.URL.createObjectURL(blob);
var fileName = orderNo;
fileName += '.zip';
downloadFile(null, fileName, null, zipUrl, null); // just creates a hidden anchor tag and triggers the download
The response of the call is a binary (I think). Binary Content Here
But the preview is a base64. Base64 Content. And it is the correct one. The way I verify it is by using this fiddle.
You can refer to the screenshot of the network here
I put the base64 content in this line var sampleBytes = base64ToArrayBuffer(''); And the zip downloaded just opens fine.
Things I have tried so far.
Adding this headers to the GET call
var headers = {
Accept: 'application/octet-stream',
responseType: 'blob',
};
But I get Request header field responseType is not allowed by Access-Control-Allow-Headers in preflight response.
We're using an already ajax.service.js in our AngularJS project.
From this answer
var blob = new Blob([yourBinaryDataAsAnArrayOrAsAString], {type: "application/octet-stream"});
var fileName = "myFileName.myExtension";
saveAs(blob, fileName);
There are other things that I have tried that I have not listed. I will edit the questions once I find them again
But where I'm current at right now. The preview is correct base64 of the binary file. Is it possible to use that instead of the binary? (If it is I will not find the other methods that I've tested) I tried some binary to base64 converters but they don't work.
So I just went and ditched using the ajax.service.js, that we have, for this specific call.
I used the xhr snippet from this answer. I just added the headers necessary for our call: tokens and auth stuff.
And I used this code snippet for the conversion thing.
And the code looks like this:
fetchBlob(url, function (blob) {
// Array buffer to Base64:
var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(blob)));
var blob = new Blob([base64ToArrayBuffer(base64)], {
type: 'octet/stream',
});
var zipUrl = window.URL.createObjectURL(blob);
var fileName = orderNo;
fileName += ' Attachments ';
fileName += moment().format('DD-MMM-YYYY');
fileName += '.zip';
downloadFile(null, fileName, null, zipUrl, null); // create a hidden anchor tag and trigger download
});
function fetchBlob(uri, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', uri, true);
xhr.responseType = 'arraybuffer';
var x = AjaxService.getAuthHeaders();
xhr.setRequestHeader('auth_stuff', x['auth_stuff']);
xhr.setRequestHeader('token_stuff', x['token_stuff']);
xhr.setRequestHeader('Accept', 'application/octet-stream');
xhr.onload = function (e) {
if (this.status == 200) {
var blob = this.response;
if (callback) {
callback(blob);
}
}
};
return xhr.send();
};
function base64ToArrayBuffer(base64) {
var binaryString = window.atob(base64);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
};
return bytes;
}

No file was sent to SAP Leonardo Image Feature Extraction API using FormData

I'm testing SAP Leonardo Image Feature Extraction API (https://sandbox.api.sap.com/ml/featureextraction/inference_sync). I have the base64 string of the image and I want to transform it to a file object and zip it, then to send the zipped image file to this API using XMLHttpRequest. But the response text is "Service requires a list of (zipped) images".
I attach my HTTP request header and parameters in below screenshots.
Although we see a messy code in parameters, the zipped file Download here is created successfully.
If you cannot download the zipped file, please refer to the screenshot below.
Everything seems to be fine. However, the response text is as below with status 400.
My javascript code is shown below. What is wrong? It drives me crazy...
dataURItoBlob: function(dataURI, fileName) {
//convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
//separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
//write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var blob = new Blob([ia], {encoding:"UTF-8",type:mimeString});
//A Blob() is almost a File() - it's just missing the two properties below which we will add
blob.lastModifiedDate = new Date();
blob.name = fileName + '.' + mimeString.split('/')[1];
return blob;
},
onSubmit: function(oEvent) {
var oImage = this.getView().byId('myImage');
//oImage.getSrc() : 'data:image/png;base64,iVBORw0KGgo...'
var imageFile = this.dataURItoBlob(oImage.getSrc(), 'myImage');
var zip = new JSZip();
zip.file(imageFile.name, imageFile);
zip.generateAsync({
type:"blob",
compression: 'DEFLATE', // force a compression for this file
compressionOptions: {
level: 6,
},
}).then(function(content) {
//saveAs(content, "hello.zip");
// start the busy indicator
var oBusyIndicator = new sap.m.BusyDialog();
oBusyIndicator.open();
var formData = new FormData();
formData.append('files', content, 'myImage.zip');
var xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
oBusyIndicator.close();
//navigator.notification.alert(this.responseText);
console.log(this.responseText);
}
});
//setting request method
//API endpoint for API sandbox
//Destionation '/SANDBOX_API' in HCP is configured as 'https://sandbox.api.sap.com'
var api = "/SANDBOX_API/ml/featureextraction/inference_sync";
xhr.open("POST", api);
//adding request headers
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.setRequestHeader("Accept", "application/json");
//API Key for API Sandbox
xhr.setRequestHeader("APIKey", "yQd5Oy785NkAIob6g1eNwctBg4m1LGQS");
//sending request
xhr.send(formData);
});
},
I fix this issue by myself. I put my solution just for others' information. It's very easy and only below code needs to be removed before sending request. I have no idea why. Please suggest if you know the reason. Thanks in advance!
xhr.setRequestHeader("Content-Type", "multipart/form-data");
Best Regards,
Shelwin Wei

Problems downloading big file(max 15 mb) on google chrome

I have a downloading problem in Google Chrome.
I am using Ruby 2.2, Rails 4.2, AngularJS 1.2.
We dont have a database here. Everything we are getting through API. The file which we are trying to download is around 7 mb. It gives us "Failed: Network Error". Though it works fine on Firefox.
From the API we are getting binary data in JSON. We are parsing it. And then:
send_data response_fields["attachment"], type: response_fields["mimeType"], disposition: 'attachment', filename: params[:filename]
As we are using AngularJS, we are catching that value in AngularJS Controller and then converting it as:
var str = data;
var uri = "data:" + mimeType + ";base64," + str;
var downloadLink = document.createElement("a");
downloadLink.href = uri;
downloadLink.download = filename;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
This works in Firefox & even Chrome for smaller file size. Not sure why it is giving error for bigger size on Chrome.
Any suggestions?
Thanks.
This is an almost duplicate of these questions 1 and 2, but since they do deal particularly with the canvas element, I'll rewrite a more global solution here.
This problem is due to a size limit chrome has set in the anchor (<a>) download attribute. I'm not quite sure why they did it, but the solution is pretty easy.
Convert your dataURI to a Blob, then create an ObjectURL from this Blob, and pass this ObjectURL as the anchor's download attribute.
// edited from https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
function dataURIToBlob(dataURI) {
var binStr = atob(dataURI.split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len),
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
return new Blob([arr], {
type: mimeString
});
}
var dataURI_DL = function() {
var dataURI = this.result;
var blob = dataURIToBlob(dataURI);
var url = URL.createObjectURL(blob);
var blobAnchor = document.getElementById('blob');
var dataURIAnchor = document.getElementById('dataURI');
blobAnchor.download = dataURIAnchor.download = 'yourFile.mp4';
blobAnchor.href = url;
dataURIAnchor.href = dataURI;
stat_.textContent = '';
blobAnchor.onclick = function() {
requestAnimationFrame(function() {
URL.revokeObjectURL(url);
})
};
};
// That may seem stupid, but for the sake of the example, we'll first convert a blob to a dataURI...
var start = function() {
stat_.textContent = 'Please wait while loading...';
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
status.textContent = 'converting';
var fr = new FileReader();
fr.onload = dataURI_DL;
fr.readAsDataURL(this.response);
};
xhr.open('GET', 'https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4?dl=0');
xhr.send();
confirm_btn.parentNode.removeChild(confirm_btn);
};
confirm_btn.onclick = start;
<button id="confirm_btn">Start the loading of this 45Mb video</button>
<span id="stat_"></span>
<br>
<a id="blob">blob</a>
<a id="dataURI">dataURI</a>
And a jsfiddle version for FF, since they don't allow the downloadattribute from stack-snippets...

Generate a blob with grunt which will be available to JS in var

I need to embed a Flash .swf on the page and am unable use the normal way of setting the src or data attribute to the swf url - don't ask :s. So, I'm doing an ajax request for the swf, converting to a blob and then generating a blob url which I set as the swf src. Then I realised that as I'm building with Grunt, there may be a way to just write the swf file into the code as a blob in a var, and avoid the ajax request completely. Here's the code with the ajax request:
function createFlashMovie(blobUrl){
var obj = document.createElement("object");
obj.setAttribute("width", "800");
obj.setAttribute("height", "600");
obj.setAttribute("type", "application/x-shockwave-flash");
obj.setAttribute("data", blobUrl);
document.body.appendChild(obj);
}
function onAjaxLoad(oResponse){
blobUrl = window.URL.createObjectURL(oResponse);
createFlashMovie(blobUrl);
};
//do the xhr request for a.swf
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
onAjaxLoad(this.response);
}
}
xhr.open('GET', '//theserver.com/a.swf');
xhr.responseType = 'blob';
xhr.send();
...but I'm sure it must be possible to have something like this which is replaced by grunt to have the blob already available when it runs, and go straight to creating the blob url without the xhr request:
var theBlob = new Blob(["GRUNT_WRITES_THIS_IN_FROM_FILE"], {type: "application/x-shockwave-flash"});
Well, grunt is at its core just a Node program, so you can use any node command in your Gruntfile or tasks definitions. And it seems that Node's http.request would be perfect for your needs.
So:
add a custom task in your Gruntfile (http://gruntjs.com/creating-tasks#custom-tasks)
that uses http.request to download your swf (https://docs.nodejitsu.com/articles/HTTP/clients/how-to-create-a-HTTP-request)
update your code to use the local swf
I found a solution. Convert your swf file to be a base64-encoded string. At the moment I'm doing this separately and then pasting it into the source JS, but it can be automated at build time with grunt. Then in the page script create a var to store it as a dataURI:
var swfAsDataUri = "data:application/x-shockwave-flash;base64,BIG_LONG_CHUNK_OF_DATA_THAT_IS_YOUR_ENCODED_SWF_FILE__GRUNT_CAN_WRITE_THIS_IN_AT_BUILD_TIME";
Create a blob from the data url, and then create an object url from the blob:
//function taken from http://stackoverflow.com/questions/27159179/how-to-convert-blob-to-file-in-javascript
dataURLToBlob = function(dataURL) {
var BASE64_MARKER = ';base64,';
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {type: contentType});
};
var blobUrl = window.URL.createObjectURL( dataURLToBlob(swfAsDataUri) );
You can then use the object url as the src data for the flash movie's object tag when it's embedded:
function createFlashMovie(blobUrl){
var obj = document.createElement("object");
obj.setAttribute("width", "800");
obj.setAttribute("height", "600");
obj.setAttribute("type", "application/x-shockwave-flash");
obj.setAttribute("data", blobUrl); //use the object url here
document.body.appendChild(obj);
}
...and there you have it, no additional http request for the swf file.

handling csv response in backbonejs

I am posting some data to server through Backbone.js and server sends a csv file as response. As Backbone.js handles only json format can some body tell me how to handle this case, so that i would be able to download the csv file gracefully.
object = {};
object.c1 = formObj.c1
hash = {
success: function(model, response, options) {
},
error: function(model, response, options) {
return console.log(response);
}
};
model = new P.models.mine(object);
model.doSomething(object, hash);
It always comes to error part.
The ideal way to handle this would be to change your back end code to return JSON, or create another route that returns JSON. Since you are asking this question I'm assuming that isn't an option for you.
Basically you are going to have to parse the CSV on the client side:
https://stackoverflow.com/a/1293163/944006 - Should get you started.
If you are asking to download a csv file, then just pointing the browser at the location should prompt the user for download. You cannot prompt a file download through ajax(for good reason), but there are ways to tiptoe around this limitation:
https://stackoverflow.com/a/9970672/944006
You could also use plain javascript rather than Backbone.js. Believe me this is the best way.
Here is some code:
var xhr = new XMLHttpRequest();
xhr.open('POST', Urls.download, true);
xhr.responseType = 'blob';
xhr.setRequestHeader("Authorization", "Bearer " + access_token);
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.onload = function (e) {
if (this.status == 200) {
var blob = new Blob([this.response], { type: 'application/vnd.ms-excel' });
var downloadUrl = URL.createObjectURL(blob);
var a = document.createElement("a");
a.id = "a" + new Date().getTime();
a.setAttribute("data-bypass", "");
a.href = downloadUrl;
a.download = "list_" + new Date().getTime() + ".xlsx";
document.body.appendChild(a);
a.click();
} else {
alert('Unable to download excel.')
}
};
xhr.send(JSON.stringify(this.obj));

Categories

Resources