I have a page that allows the user to download a dynamically-generated file. It takes a long time to generate, so I\'d like to show a \"waiting\" indicator. The problem is,
I'm very late to the party but I'll put this up here if anyone else would like to know my solution:
I had a real struggle with this exact problem but I found a viable solution using iframes (I know, I know. It's terrible but it works for a simple problem that I had)
I had an html page that launched a separate php script that generated the file and then downloaded it. On the html page, i used the following jquery in the html header (you'll need to include a jquery library as well):
<script>
$(function(){
var iframe = $("<iframe>", {name: 'iframe', id: 'iframe',}).appendTo("body").hide();
$('#click').on('click', function(){
$('#iframe').attr('src', 'your_download_script.php');
});
$('iframe').load(function(){
$('#iframe').attr('src', 'your_download_script.php?download=yes'); <!--on first iframe load, run script again but download file instead-->
$('#iframe').unbind(); <!--unbinds the iframe. Helps prevent against infinite recursion if the script returns valid html (such as echoing out exceptions) -->
});
});
</script>
On your_download_script.php, have the following:
function downloadFile($file_path) {
if (file_exists($file_path)) {
header('Content-Description: File Transfer');
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=' . basename($file_path));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file_path));
ob_clean();
flush();
readfile($file_path);
exit();
}
}
$_SESSION['your_file'] = path_to_file; //this is just how I chose to store the filepath
if (isset($_REQUEST['download']) && $_REQUEST['download'] == 'yes') {
downloadFile($_SESSION['your_file']);
} else {
*execute logic to create the file*
}
To break this down, jquery first launches your php script in an iframe. The iframe is loaded once the file is generated. Then jquery launches the script again with a request variable telling the script to download the file.
The reason that you can't do the download and file generation all in one go is due to the php header() function. If you use header(), you're changing the script to something other than a web page and jquery will never recognize the download script as being 'loaded'. I know this may not necessarily be detecting when a browser receives a file but your issue sounded similar to mine.
A quick solution if you only want to display a message or a loader gif until the download dialog is displayed is to put the message in a hidden container and when you click on the button that generate the file to be downloaded you make the container visible. Then use jquery or javascript to catch the focusout event of the button to hide the container that contain the message
/**
* download file, show modal
*
* @param uri link
* @param name file name
*/
function downloadURI(uri, name) {
// <------------------------------------------ Do someting (show loading)
fetch(uri)
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = name;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
// <---------------------------------------- Detect here (hide loading)
alert('File detected'));
})
.catch(() => alert('An error sorry'));
}
You can use it:
downloadURI("www.linkToFile.com", "file.name");
Primefaces uses cookie polling, too
https://github.com/primefaces/primefaces/blob/32bb00299d00e50b2cba430638468a4145f4edb0/src/main/resources/META-INF/resources/primefaces/core/core.js#L458
monitorDownload: function(start, complete, monitorKey) {
if(this.cookiesEnabled()) {
if(start) {
start();
}
var cookieName = monitorKey ? 'primefaces.download_' + monitorKey : 'primefaces.download';
window.downloadMonitor = setInterval(function() {
var downloadComplete = PrimeFaces.getCookie(cookieName);
if(downloadComplete === 'true') {
if(complete) {
complete();
}
clearInterval(window.downloadMonitor);
PrimeFaces.setCookie(cookieName, null);
}
}, 1000);
}
},