Question

I'm aware that jQuery's ajax method cannot handle downloads, and I do not want to add a jQuery plugin to do this.

I want to know how to send POST data with XMLHttpRequest to download a file.

Here's what I've tried:

var postData = new FormData();
postData.append('cells', JSON.stringify(output));

var xhr = new XMLHttpRequest();
xhr.open('POST', '/export/', true);
xhr.setRequestHeader("X-CSRFToken", csrftoken);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
    console.log(e);
    console.log(xhr);
}
xhr.send(postData);

I'm working with Django, and the file appears to be sending back to the client successfully. In the network tab in Chrome, I can see gibberish in the preview tab (which I expect). But I want to send back a zip file, not a text representation of the zip file. Here's the Django back end:

wrapper = FileWrapper(tmp_file)
response = HttpResponse(wrapper, content_type='application/zip')
response['Content-Disposition'] = "attachment; filename=export.zip"
response['Content-Length'] = tmp_file.tell()
return response

I've searched this for hours now without finding a proper example on how to do this with XMLHttpRequests. I don't want to create a proper html form with a POST action because the form data is rather large, and dynamically created.

Is there something wrong with the above code? Something I'm missing? I just don't know how to actually send the data to the client as a download.

Was it helpful?

Solution 2

UPDATE: this answer is not accurate anymore since the introduction of Blob API. Please refer to Steven's answer for details.


ORIGINAL ANSWER:

XHR request will not trigger file download. I can't find explicit requirement, but W3C doc on XMLHttpRequest doesn't describe any special reaction on content-disposition=attachment responses either

You could download file by window.open() in separate tab, if it was not POST request. Here it was suggested to use a hidden form with target=_blank

OTHER TIPS

If you set the XMLHttpRequest.responseType property to 'blob' before sending the request, then when you get the response back, it will be represented as a blob. You can then save the blob to a temporary file and navigate to it.

var postData = new FormData();
postData.append('cells', JSON.stringify(output));

var xhr = new XMLHttpRequest();
xhr.open('POST', '/export/', true);
xhr.setRequestHeader('X-CSRFToken', csrftoken);
xhr.responseType = 'blob';
xhr.onload = function (e) {
    var blob = e.currentTarget.response;
    var contentDispo = e.currentTarget.getResponseHeader('Content-Disposition');
    // https://stackoverflow.com/a/23054920/
    var fileName = contentDispo.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1];
    saveOrOpenBlob(blob, fileName);
}
xhr.send(postData);

And here's an example implementation of saveOrOpenBlob:

function saveOrOpenBlob(blob, fileName) {
    window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
    window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fs) {
        fs.root.getFile(fileName, { create: true }, function (fileEntry) {
            fileEntry.createWriter(function (fileWriter) {
                fileWriter.addEventListener("writeend", function () {
                    window.location = fileEntry.toURL();
                }, false);
                fileWriter.write(blob, "_blank");
            }, function () { });
        }, function () { });
    }, function () { });
}

If you don't care about having the browser navigate to the file when it's a viewable file type, then making a method that always saves directly to file is much simpler:

function saveBlob(blob, fileName) {
    var a = document.createElement('a');
    a.href = window.URL.createObjectURL(blob);
    a.download = fileName;
    a.dispatchEvent(new MouseEvent('click'));
}

download: function(){
    var postData = new FormData();
		var xhr = new XMLHttpRequest();
		xhr.open('GET', downloadUrl, true);
		xhr.responseType = 'blob';
		xhr.onload = function (e) {
			var blob = xhr.response;
			this.saveOrOpenBlob(blob);
		}.bind(this)
		xhr.send(postData);
 }

saveOrOpenBlob: function(blob) {
		var assetRecord = this.getAssetRecord();
		var fileName = 'Test.mp4'
		var tempEl = document.createElement("a");
    	document.body.appendChild(tempEl);
    	tempEl.style = "display: none";
        url = window.URL.createObjectURL(blob);
        tempEl.href = url;
        tempEl.download = fileName;
        tempEl.click();
		window.URL.revokeObjectURL(url);
	},

Try this it is working for me.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top