Detect when browser receives file download

2018-12-31 00:39发布

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 can't figure out how to detect when the browser has received the file, so I can hide the indicator.

I'm making the request in a hidden form, which POSTs to the server, and targets a hidden iframe for its results. This is so I don't replace the entire browser window with the result. I listen for a "load" event on the iframe, in the hope that it will fire when the download is complete.

I return a "Content-Disposition: attachment" header with the file, which causes the browser to show the "Save" dialog. But the browser doesn't fire a "load" event in the iframe.

One approach I tried is using a multi-part response. So it would send an empty HTML file, as well as the attached downloadable file. For example:

Content-type: multipart/x-mixed-replace;boundary="abcde"

--abcde
Content-type: text/html

--abcde
Content-type: application/vnd.fdf
Content-Disposition: attachment; filename=foo.fdf

file-content
--abcde

This works in Firefox; it receives the empty HTML file, fires the "load" event, then shows the "Save" dialog for the downloadable file. But it fails on IE and Safari; IE fires the "load" event but doesn't download the file, and Safari downloads the file (with the wrong name and content-type), and doesn't fire the "load" event.

A different approach might be to make a call to start the file creation, then poll the server until it's ready, then download the already-created file. But I'd rather avoid creating temporary files on the server.

Does anyone have a better idea?

16条回答
萌妹纸的霸气范
2楼-- · 2018-12-31 01:02

old thread, i know...

but those, that are lead here by google might be interested in my solution. it is very simple, yet reliable. and it makes it possible to display real progress messages (and can be easily plugged in to existing processes):

the script that processes (my problem was: retrieving files via http and deliver them as zip) writes the status to the session.

the status is polled and displayed every second. thats all (ok, its not. you have to take care of a lot of details [eg concurrent downloads], but its a good place to start ;-)).

the downloadpage:

    <a href="download.php?id=1" class="download">DOWNLOAD 1</a>
    <a href="download.php?id=2" class="download">DOWNLOAD 2</a>
    ...
    <div id="wait">
    Please wait...
    <div id="statusmessage"></div>
    </div>
    <script>
//this is jquery
    $('a.download').each(function()
       {
        $(this).click(
             function(){
               $('#statusmessage').html('prepare loading...');
               $('#wait').show();
               setTimeout('getstatus()', 1000);
             }
          );
        });
    });
    function getstatus(){
      $.ajax({
          url: "/getstatus.php",
          type: "POST",
          dataType: 'json',
          success: function(data) {
            $('#statusmessage').html(data.message);
            if(data.status=="pending")
              setTimeout('getstatus()', 1000);
            else
              $('#wait').hide();
          }
      });
    }
    </script>

getstatus.php

<?php
session_start();
echo json_encode($_SESSION['downloadstatus']);
?>

download.php

    <?php
    session_start();
    $processing=true;
    while($processing){
      $_SESSION['downloadstatus']=array("status"=>"pending","message"=>"Processing".$someinfo);
      session_write_close();
      $processing=do_what_has_2Bdone();
      session_start();
    }
      $_SESSION['downloadstatus']=array("status"=>"finished","message"=>"Done");
//and spit the generated file to the browser
    ?>
查看更多
心情的温度
3楼-- · 2018-12-31 01:06

When the user triggers the generation of the file, you could simply assign a unique ID to that "download", and send the user to a page which refreshes (or checks with AJAX) every few seconds. Once the file is finished, save it under that same unique ID and...

  • If the file is ready, do the download.
  • If the file is not ready, show the progress.

Then you can skip the whole iframe/waiting/browserwindow mess, yet have a really elegant solution.

查看更多
高级女魔头
4楼-- · 2018-12-31 01:07

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

查看更多
公子世无双
5楼-- · 2018-12-31 01:10

A very simple (and lame) one line solution is to use the window.onblur() event to close the loading dialog. Of course, if it takes too long and the user decides to do something else (like reading emails) the loading dialog will close.

查看更多
萌妹纸的霸气范
6楼-- · 2018-12-31 01:10

I wrote a simple JavaScript class that implements a technique similar to the one described in bulltorious answer. I hope it can be useful to someone here. The GitHub project is called response-monitor.js

By default it uses spin.js as the waiting indicator but it also provides a set of callbacks for implementation of a custom indicator.

JQuery is supported but not required.

Notable features

  • Simple integration
  • No dependencies
  • JQuery plug-in (optional)
  • Spin.js Integration (optional)
  • Configurable callbacks for monitoring events
  • Handles multiple simultaneous requests
  • Server-side error detection
  • Timeout detection
  • Cross browser

Example usage

HTML

<!-- the response monitor implementation -->
<script src="response-monitor.js"></script>

<!-- optional JQuery plug-in -->
<script src="response-monitor.jquery.js"></script> 

<a class="my_anchors" href="/report?criteria1=a&criteria2=b#30">Link 1 (Timeout: 30s)</a>
<a class="my_anchors" href="/report?criteria1=b&criteria2=d#10">Link 2 (Timeout: 10s)</a>

<form id="my_form" method="POST">
    <input type="text" name="criteria1">
    <input type="text" name="criteria2">
    <input type="submit" value="Download Report">
</form>

Client (plain JavaScript)

//registering multiple anchors at once
var my_anchors = document.getElementsByClassName('my_anchors');
ResponseMonitor.register(my_anchors); //clicking on the links initiates monitoring

//registering a single form
var my_form = document.getElementById('my_form');
ResponseMonitor.register(my_form); //the submit event will be intercepted and monitored

Client (JQuery)

$('.my_anchors').ResponseMonitor();
$('#my_form').ResponseMonitor({timeout: 20});

Client with callbacks (JQuery)

//when options are defined, the default spin.js integration is bypassed
var options = {
    onRequest: function(token){
        $('#cookie').html(token);
        $('#outcome').html('');
        $('#duration').html(''); 
    },
    onMonitor: function(countdown){
        $('#duration').html(countdown); 
    },
    onResponse: function(status){
        $('#outcome').html(status==1?'success':'failure');
    },
    onTimeout: function(){
        $('#outcome').html('timeout');
    }
};

//monitor all anchors in the document
$('a').ResponseMonitor(options);

Server (PHP)

$cookiePrefix = 'response-monitor'; //must match the one set on the client options
$tokenValue = $_GET[$cookiePrefix];
$cookieName = $cookiePrefix.'_'.$tokenValue; //ex: response-monitor_1419642741528

//this value is passed to the client through the ResponseMonitor.onResponse callback
$cookieValue = 1; //for ex, "1" can interpret as success and "0" as failure

setcookie(
    $cookieName,
    $cookieValue,
    time()+300,            // expire in 5 minutes
    "/",
    $_SERVER["HTTP_HOST"],
    true,
    false
);

header('Content-Type: text/plain');
header("Content-Disposition: attachment; filename=\"Response.txt\"");

sleep(5); //simulate whatever delays the response
print_r($_REQUEST); //dump the request in the text file

For more examples check the examples folder on the repository.

查看更多
谁念西风独自凉
7楼-- · 2018-12-31 01:11

i use the following to download blobs and revoke the object-url after the download. it works in chrome and firefox!

function download(blob){
    var url = URL.createObjectURL(blob);
    console.log('create ' + url);

    window.addEventListener('focus', window_focus, false);
    function window_focus(){
        window.removeEventListener('focus', window_focus, false);                   
        URL.revokeObjectURL(url);
        console.log('revoke ' + url);
    }
    location.href = url;
}

after the file download dialog is closed, the window gets her focus back so the focus event is triggered.

查看更多
登录 后发表回答