33

I create an Iframe on the fly and set as the url a page that downloads a binary file (xls, doc...). While files are downloading I show an animation. When does not, I hide it.

The problem is that Chrome does not know when the files are fully downloaded, that is when the iframe is completely loaded. I use the iframe property readyState to check the iframe state:

var iframe = document.createElement("iframe");
iframe.style.visibility = "hidden";
// I start a progress animation
window.setTimeout(showProgressAnimation, 1000);
// I start the file download
iframe.src ='GetFile.aspx?file=' + fileName;
document.body.appendChild(iframe);


function showProgressAnimation() {
   if (iframe.readyState == "complete" || iframe.readyState == "interactive") {
      // I stop the animation and show the page
      animation.style.display = 'none';
      progressBar.hide();
      $('#page').show();
   }
   else {
      // Chrome is always getting into this line
      window.setTimeout(showProgressAnimation, 1000);
   }
}

So the result is an infinite loop.

I've tried the following and it works in Firefox and Chrome but not when the contents are a binary file:

if ($.browser.mozilla || $.browser.webkit ) {
    iframe.onload = function showProgressAnimation() {
        animation.style.display = 'none';
        progressBar.hide();
        $('#page').show();
    }
}
// IE
else{
     window.setTimeout(showProgressAnimation, 1000);
}
anmarti
  • 5,045
  • 10
  • 55
  • 96
  • I know that this is sound simple, but try to remove the `if ($.browser.mozilla) {` from the code and try it again. At least to me this is working. The `onload` is what I use and make it work. – Aristos Dec 19 '12 at 13:25
  • 1
    why not `$("iframe").on("load",function () { animation.style.display = 'none'; progressBar.hide(); $('#page').show(); });` and does the console have something to say? – mplungjan Dec 19 '12 at 13:26
  • @Aristos I did it and doesn't work. Did you try with chrome? Question updated with the full cross browser code I have right now. – anmarti Dec 19 '12 at 13:40
  • yes on Chrome, I use chrome. Did you get any errors ? – Aristos Dec 19 '12 at 13:41
  • Do you see that you hide it with the `visible`, but you show it with the `display` ? This must be the same to work. Place an alert on the `onload` to double check that is not called. – Aristos Dec 19 '12 at 13:53

4 Answers4

16

You can use the onload to signaling the load of the iframe

here is a simple example that working

var iframe = document.createElement("iframe");
iframe.style.display = "none";
// this function will called when the iframe loaded
iframe.onload = function (){
  iframe.style.display = "block";    
  alert("loaded");
};
// set the src last.
iframe.src ='http://www.test.com';

// add it to the page.
document.getElementById("one").appendChild(iframe);

Tested here:
http://jsfiddle.net/48MQW/5/
With src loaded last.
http://jsfiddle.net/48MQW/24/

Aristos
  • 66,005
  • 16
  • 114
  • 150
  • 4
    Does not work in Chrome if url is a PDF or more accurately of mime type application/pdf. Works in IE 9, Windows Safari and FireFox. The versions are pretty much current for FF and Chrome. Safari is a few months old. Does work with http://www.test.com in all four. – Lee Meador May 17 '13 at 21:29
  • @LeeMeador Thank you for the note. If you have a solution for the non-working case, please share it with us. The pdf is probably handle by the plugin of adobe acrobat and maybe this is make the issue of not working. – Aristos May 17 '13 at 22:39
  • For the versions I have, it seems that FF and Chrome use their own code for PDF content. IE and Safari seem to use Acrobat plugins. – Lee Meador May 17 '13 at 22:47
  • Just revisiting this, I believe strongly that you need to assign the onload before you set the src. Also why wrap the function in brackets? – mplungjan May 18 '13 at 07:05
  • @mplungjan ok, I will do that, sounds logical. No special reason the wrap with brackets. – Aristos May 18 '13 at 08:50
  • 3
    Since onLoad event doesn't fire when downloading a file in Chrome, there's a technique that uses a cookie to check if it loaded or not. There's a plugin that implements that technique nicely: http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/ – Andrea Jan 07 '14 at 13:49
  • Does not work if content-disposition is attachment – Nick Aug 24 '22 at 04:36
  • @Nick because this is when you download a file - you can not know when you downloaded – Aristos Aug 26 '22 at 10:20
  • @Aristos Then this is not really an answer to the original question is it? – Nick Aug 30 '22 at 03:11
  • @Nick after 10 years, I really don't remember. Now I see it again, its confuse me also. What I have write here is a way to auto make an iframe and know when its loaded. Not when a file is downloaded. – Aristos Aug 30 '22 at 21:47
6

The downloadable file content doesn't trigger the readystatechange event handler or the onload event handler. This couse you can set a cookie in server side together the file content, and client side check this cookie periodically. For example:

server

response.cookie('fileDownloaded','true');
response.header('attachment','your-file-name.any');
//...write bytes to response...

client

var checker = setInterval(()=>{
    if(document.cookie.indexOf('fileDownloaded')>-1){
        alert('done');
        clearInterval(checker);
    }
},100);

Of course, you can use your framework to check the cookie value correctly, this is just a poc, not a safe cookie parser.

sarkiroka
  • 1,485
  • 20
  • 28
  • I use this solution while downloading binary file from php script and the document.cookie never gets the value set in php, developer tools show correct set-cookie header but document.cookie is never updated. – jcubic Feb 11 '18 at 17:19
  • 1
    `Set-Cookie:download=1518612276937` weird thing is that I have it in `cookie:` header in next request. It work like with httponly cookie but that option was not used. This is in Google Chrome. Maybe I'll ask another question. – jcubic Feb 14 '18 at 12:53
  • 2
    Found the reason in comment to [this question](https://stackoverflow.com/questions/1106377/detect-when-browser-receives-file-download), if you use this solution you need to set the path for the cookie, otherwise it will not work. – jcubic Feb 14 '18 at 12:59
  • But the cookie is set the same time when iframe completed loading (`iframe.contentDocumement.readyState == "complete"`) which just when first data is send to the client. (I'm using flush in php because otherwise I didn't get progrees in browser, only once at the end). – jcubic Feb 14 '18 at 13:08
1

Please try this - you are really mixing dom and jQuery from line to line

var tId;

function stopAnim() {
    // I stop the animation and show the page
    animation.hide();
    progressBar.hide();
    $('#page').show();
    clearInterval(tId);
}
var iframe = $("<iframe />");
iframe.css("visibility","hidden");

iframe.on("readystatechange",function() {
 if (this.readyState == "complete" || this.readyState == "interactive") {
   stopAnim();
 }
});
iframe.on("load",function() { // can possibly be deleted
 if (tId) {
   stopAnim();
 }
});

iframe.attr("src","GetFile.aspx?file=" + fileName);
$("body").append(iframe);
tId = setInterval(function() {
  // update progress here
}, 1000); // 
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • If the URL loads a PDF (application/pdf) this does not work in FireFox or Chrome (where it takes the on-load option) but does work in Windows Safari. It works in IE 9 but prints twice (once for "interactive" and once for "complete") however the printed page is blank. – Lee Meador May 17 '13 at 22:09
  • Why would an answer from 2012 be voted down in 2022? What kind of silliness is this? – mplungjan Aug 24 '22 at 06:27
0

Sarkiroka's solution worked for me. I have added an instance id to the cookie name to provide a thread safe solution:

const instanceId = "some uuid retrieved from client";

response.cookie(`fileDownloaded-${instanceId}`,'true', { path: '/', secure: true, maxAge: 90000});
response.header('attachment','your-file-name.any');
//...write bytes to response...

client

var checker = setInterval(()=>{
    if(document.cookie.indexOf(`fileDownloaded-${instanceId}`)>-1){
        alert('done');
        clearInterval(checker);
    }
},100);

Adriaan
  • 17,741
  • 7
  • 42
  • 75