On my web page when a user clicks the download button the Exports Action is called from the controller, an Excel file is generated and then returned to the user. Depending on the parameters passed to the action it sometimes takes some time for the file to be generated and returned to the user.
So, I want to show a busy indicator while waiting for the file. I went with Bootstrap spinner.
$('.content').on('click', 'a.export', function (e) {
e.preventDefault();
// show busy indicator
$('#divSpinner').css('display', 'inline-block');
var idSelect = '';
$('#ddlSelect option:selected').each(function () {
idSelect += $('#ddlSelect')[0].value;
});
var idSelect2 = '';
$('#ddlSelect2 option:selected').each(function () {
idSelect2 += $('#ddlSelect2')[0].value;
});
var url = 'Home/Exports?' + 'param1=' + idSelect + '¶m2=' + idSelect2;
//download file
window.location = url;
// hide busy indicator
$('#divSpinner').css('display', 'none');
});
Well this kind of works, but the problem is that $('#divSpinner').css('display', 'none'); gets hit immediately and the spinner hides before the file is actually available to the user for download (browser download/open window).
How can I rewrite the code to wait for window.location=url to finish and then hide the spinner?
EDIT: I also tried wrapping the Download part in an async function like this:
async function Download(url) {
let promise = new Promise((resolve, reject) => {
resolve(window.location = url);
});
let result = await promise; // wait until the promise resolves (*)
}
and calling it like this:
Download(url)
.then(function() {
$('#divSpinner').hide()
});
Download works, but still $('#divSpinner').hide()
get's hit before the file download is presented to the user. Isn't the whole point of async/await to wait for the result before continuing code execution??
EDIT2 (with "blob"):
async function Download(url) {
let promise = new Promise((resolve, reject) => {
resolve(filedownload(url));
});
let result = await promise;
}
function filedownload(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName")
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
};
req.send();
}
and calling it like this:
Download(url)
.then(function() {
$('#divSpinner').hide()
});
EDIT 3: I used @Nenad's solution from below, but I only changed the handling of the response header (otherwise I was getting NULL for filename at download - I used this solution https://stackoverflow.com/a/40940790/7975627):
function filedownload(urlToSend, resolve) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var filename = "";
var disposition = req.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
resolve(filename); // call resolve after download has finished.
};
req.send();
}