检测浏览器何时接收文件下载


487

我有一个页面,允许用户下载动态生成的文件。生成需要很长时间,因此我想显示一个“等待”指示。问题是,我不知道如何检测浏览器何时收到文件,因此可以隐藏指示器。

我正在以隐藏的形式发出请求,该请求会发布到服务器,并以隐藏的iframe作为结果。这样一来,我就不会用结果替换整个浏览器窗口。我在iframe上侦听“加载”事件,希望下载完成后将触发该事件。

我随文件返回一个“ Content-Disposition:附件”标头,这将导致浏览器显示“保存”对话框。但是浏览器不会在iframe中触发“加载”事件。

我尝试的一种方法是使用多部分响应。因此它将发送一个空的HTML文件以及附加的可下载文件。例如:

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

这在Firefox中有效;它接收到空的HTML文件,触发“加载”事件,然后显示可下载文件的“保存”对话框。但是它在IE和Safari上失败;IE会触发“加载”事件,但不会下载文件,而Safari会下载文件(具有错误的名称和内容类型),并且不会触发“加载”事件。

一种不同的方法可能是调用开始文件创建,然后轮询服务器直到服务器就绪,然后下载已创建的文件。但是我宁愿避免在服务器上创建临时文件。

有谁有更好的主意吗?


4
IE版本不支持multipart / x-mixed-replace。
EricLaw

谢谢Eric-很高兴知道。我不会再花时间在这种方法上。
JW。

唯一可靠的方法似乎是服务器推送通知(适用于ASP.NET的SignalR)。
dudeNumber4 2015年

1
bennadel.com/blog/... -这是一个简单的解决方案
MATEEN

1
@mateen谢谢老兄!真的很简单
Fai Zal Dong

Answers:


451

一种可能的解决方案是在客户端上使用JavaScript。

客户端算法:

  1. 生成随机的唯一令牌。
  2. 提交下载请求,并将令牌包含在GET / POST字段中。
  3. 显示“正在等待”指示器。
  4. 启动一个计时器,然后大约每秒钟查找一个名为“ fileDownloadToken”(或您决定的内容)的cookie。
  5. 如果cookie存在,并且其值与令牌匹配,则隐藏“等待”指示符。

服务器算法:

  1. 在请求中查找GET / POST字段。
  2. 如果它具有非空值,则删除一个cookie(例如“ fileDownloadToken”),并将其值设置为令牌的值。

客户端源代码(JavaScript):

function getCookie( name ) {
  var parts = document.cookie.split(name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

function expireCookie( cName ) {
    document.cookie = 
        encodeURIComponent(cName) + "=deleted; expires=" + new Date( 0 ).toUTCString();
}

function setCursor( docStyle, buttonStyle ) {
    document.getElementById( "doc" ).style.cursor = docStyle;
    document.getElementById( "button-id" ).style.cursor = buttonStyle;
}

function setFormToken() {
    var downloadToken = new Date().getTime();
    document.getElementById( "downloadToken" ).value = downloadToken;
    return downloadToken;
}

var downloadTimer;
var attempts = 30;

// Prevents double-submits by waiting for a cookie from the server.
function blockResubmit() {
    var downloadToken = setFormToken();
    setCursor( "wait", "wait" );

    downloadTimer = window.setInterval( function() {
        var token = getCookie( "downloadToken" );

        if( (token == downloadToken) || (attempts == 0) ) {
            unblockSubmit();
        }

        attempts--;
    }, 1000 );
}

function unblockSubmit() {
  setCursor( "auto", "pointer" );
  window.clearInterval( downloadTimer );
  expireCookie( "downloadToken" );
  attempts = 30;
}

服务器代码示例(PHP):

$TOKEN = "downloadToken";

// Sets a cookie so that when the download begins the browser can
// unblock the submit button (thus helping to prevent multiple clicks).
// The false parameter allows the cookie to be exposed to JavaScript.
$this->setCookieToken( $TOKEN, $_GET[ $TOKEN ], false );

$result = $this->sendFile();

哪里:

public function setCookieToken(
    $cookieName, $cookieValue, $httpOnly = true, $secure = false ) {

    // See: http://stackoverflow.com/a/1459794/59087
    // See: http://shiflett.org/blog/2006/mar/server-name-versus-http-host
    // See: http://stackoverflow.com/a/3290474/59087
    setcookie(
        $cookieName,
        $cookieValue,
        2147483647,            // expires January 1, 2038
        "/",                   // your path
        $_SERVER["HTTP_HOST"], // your domain
        $secure,               // Use true over HTTPS
        $httpOnly              // Set true for $AUTH_COOKIE_NAME
    );
}

4
很棒的主意,我将其用作此答案的基本框架,该答案涉及使用jQuery / C#下载多个文件
Greg

7
请注意:如果document.cookies不包含downloadToken,请检查cookie路径。就我而言,即使默认情况下路径为空白,我也必须在服务器端将路径设置为“ /”(例如,Java中的cookie.setPath(“ /”))。一段时间以来,我认为问题出在特殊的“本地主机”域Cookie处理(stackoverflow.com/questions/1134290/…),但这最终不是问题。尽管对其他人来说值得一读,但也许对其他人而言。
jlpp 2014年

2
@bulltorious在更深入地解决您的解决方案之前,我想知道它是否适用于跨域文件下载请求。您是否认为它会这样做,否则Cookie限制会危害它?
kiks73

5
辉煌-在100年内,我不会想到您可以将Cookie包含在文件下载中。谢谢!!
freefaller

8
正如其他人指出的那样,该解决方案仅解决了部分问题,即等待服务器准备文件时间。问题的另一部分,取决于文件的大小和连接速度,可能是相当多的,它是在客户端上实际获取整个文件所花费的时间。而这种解决方案无法解决。
AsGoodAsItGet's

27

一个非常简单(且la脚)的单行解决方案是使用该window.onblur()事件关闭加载对话框。当然,如果花费的时间太长,并且用户决定执行其他操作(例如阅读电子邮件),则会关闭加载对话框。


这是一种简单的方法,非常适合摆脱使用“ onbeforeunload谢谢” 触发的文件下载的加载覆盖。
wf4

5
并非在所有浏览器中都有效(某些浏览器不会将当前窗口保留/模糊为下载工作流程的一部分,例如Safari,某些IE版本等)。
hiattp 2014年

4
Chrome和其他此类浏览器会自动下载文件,这种情况将失败。
幸运2015年

@Lucky这是默认设置。Chrome的用户完全有可能指定下载的保存位置,因此请参阅对话框
ESR 2016年

2
这是个坏主意,因为您激活了tabchange上的模糊效果或窗口外的任何操作
Michael

14

旧线程,我知道...

但是那些由谷歌领导的人可能对我的解决方案感兴趣。它非常简单,但可靠。并且可以显示真实的进度消息(并且可以轻松地插入到现有流程中):

处理的脚本(我的问题是:通过http检索文件并将其作为zip交付)将状态写入会话。

状态会每秒轮询并显示一次。多数民众赞成(好的,不是。您必须处理很多细节(例如,并发下载),但这是开始;-)的好地方)。

下载页面:

    <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
但是如果用户有多个窗口或打开的下载?你也会在这里接到服务器的多余电话
Yuki

3
如果您有一个用户的多个连接,则它们将全部等待其他连接终止,因为session_start()会为用户锁定会话并阻止所有其他进程访问它。
HonzaKuchař14年

2
您不需要.each()用于事件注册。只是说$('a.download').click()
robisrob

不要在里面评估代码setTimeout('getstatus()', 1000);。直接使用fn:setTimeout(getstatus, 1000);
Roko C. Buljan

11

我使用以下内容下载Blob,并在下载后撤消了对象URL。它适用于Chrome和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;
}

关闭文件下载对话框后,窗口将重新获得焦点,从而触发焦点事件。


仍然存在切换窗口和返回的问题,这将导致模式隐藏。
dudeNumber4 2015年

9
Chrome浏览器等下载到底部托盘中的浏览器永远不会模糊/重新聚焦窗口。
Coleman

10

基于Elmer的示例,我准备了自己的解决方案。元素单击后定义下载类,可以在屏幕上显示自定义消息。我用焦点触发器来隐藏消息。

的JavaScript

$(function(){$('.download').click(function() { ShowDownloadMessage(); }); })

function ShowDownloadMessage()
{
     $('#message-text').text('your report is creating, please wait...');
     $('#message').show();
     window.addEventListener('focus', HideDownloadMessage, false);
}

function HideDownloadMessage(){
    window.removeEventListener('focus', HideDownloadMessage, false);                   
    $('#message').hide();
}

的HTML

<div id="message" style="display: none">
    <div id="message-screen-mask" class="ui-widget-overlay ui-front"></div>
    <div id="message-text" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-front ui-draggable ui-resizable waitmessage">please wait...</div>
</div>

现在,您应该实现任何要下载的元素:

<a class="download" href="file://www.ocelot.com.pl/prepare-report">Download report</a>

要么

<input class="download" type="submit" value="Download" name="actionType">

每次下载后,您都会看到报告正在创建的消息,请稍候...


2
如果用户单击窗口怎么办?
汤姆·罗杰罗

这正是我想要的,很多东西!
塞尔吉奥

在我的情况下,hide()并未得到调用
Prashant Pimpale,

8

我编写了一个简单的JavaScript类,该类实现的技术类似于在顽强的回答中描述的技术。我希望它对这里的人有用。GitHub项目称为response-monitor.js

默认情况下,它使用spin.js作为等待指示器,但它也提供了一组回调以实现自定义指示器。

支持JQuery,但不是必需的。

显着特点

  • 简单整合
  • 没有依赖
  • jQuery插件(可选)
  • Spin.js集成(可选)
  • 用于监视事件的可配置回调
  • 处理多个同时请求
  • 服务器端错误检测
  • 超时检测
  • 跨浏览器

用法示例

的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>

客户端(纯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

客户端(JQuery)

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

带有回调的客户端(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);

服务器(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

有关更多示例,请检查存储库上的examples文件夹。


5

我参加聚会很晚,但是如果其他人想知道我的解决方案,我会在这里提出:

我确实遇到了这个确切的问题,但是我找到了使用iframe的可行解决方案(我知道,我知道。这很糟糕,但是它可以解决我遇到的一个简单问题)

我有一个html页面,它启动了一个单独的php脚本,该脚本生成了文件,然后下载了该文件。在html页面上,我在html标头中使用了以下jquery(您还需要包括一个jquery库):

<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>

在your_download_script.php上,具有以下内容:

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*
}

为了解决这个问题,jquery首先在iframe中启动您的php脚本。生成文件后,即会加载iframe。然后,jquery再次使用请求变量启动脚本,告诉该脚本下载文件。

一劳永逸地无法完成下载和文件生成的原因是由于php header()函数。如果使用header(),则将脚本更改为网页之外的其他内容,并且jquery将永远不会将下载脚本识别为“正在加载”。我知道这不一定能检测到浏览器何时接收文件,但是您的问题听起来与我的相似。


5

如果您正在流式传输要动态生成的文件,并且已实现了实时的服务器到客户端消息传递库,则可以非常轻松地向客户端发出警报。

我喜欢并推荐的服务器到客户端消息传递库是Socket.io(通过Node.js)。服务器脚本完成后,生成正在传输的文件以供下载,该脚本中的最后一行可以向Socket.io发出消息,该消息将通知发送给客户端。在客户端上,Socket.io侦听服务器发出的传入消息,并允许您对其进行操作。与其他方法相比,使用此方法的好处是您能够在流完成后检测到“ true”完成事件。

例如,您可以在单击下载链接后显示忙碌指示器,流式传输文件,在流式传输脚本的最后一行中从服务器向Socket.io发送消息,在客户端上侦听通知,接收通知并通过隐藏忙碌指示器来更新您的UI。

我意识到大多数阅读此问题答案的人可能没有这种类型的设置,但是我在自己的项目中使用了这种精确的解决方案来产生巨大的效果,并且效果很好。

Socket.io非常易于安装和使用。查看更多:http : //socket.io/


5

“如何检测浏览器何时接收文件下载?”
我在配置时遇到了同样的问题:
struts 1.2.9
jquery-1.3.2。
jquery-ui-1.7.1.custom
IE 11
Java 5


我的Cookie解决方案:
-客户端:
提交表单时,请调用javascript函数以隐藏页面并加载等待的微调框

function loadWaitingSpinner(){
... hide your page and show your spinner ...
}

然后,调用一个函数,该函数每500毫秒检查一次cookie是否来自服务器。

function checkCookie(){
    var verif = setInterval(isWaitingCookie,500,verif);
}

如果找到cookie,则每500毫秒停止检查一次,使cookie过期并调用函数以返回页面并删除等待的微调器(removeWaitingSpinner())。如果您希望能够再次下载另一个文件,则使cookie过期很重要!

function isWaitingCookie(verif){
    var loadState = getCookie("waitingCookie");
    if (loadState == "done"){
        clearInterval(verif);
        document.cookie = "attenteCookie=done; expires=Tue, 31 Dec 1985 21:00:00 UTC;";
        removeWaitingSpinner();
    }
}
    function getCookie(cookieName){
        var name = cookieName + "=";
        var cookies = document.cookie
        var cs = cookies.split(';');
        for (var i = 0; i < cs.length; i++){
            var c = cs[i];
            while(c.charAt(0) == ' ') {
                c = c.substring(1);
            }
            if (c.indexOf(name) == 0){
                return c.substring(name.length, c.length);
            }
        }
        return "";
    }
function removeWaitingSpinner(){
... come back to your page and remove your spinner ...
}

-服务器端:
在服务器过程结束时,将cookie添加到响应中。当您的文件准备好下载时,该cookie将发送到客户端。

Cookie waitCookie = new Cookie("waitingCookie", "done");
response.addCookie(waitCookie);

我希望可以帮助别人!


非常完美。感谢您提供的美丽示例。
Sedat Kumcu '19

4

当用户触发文件生成时,您只需为该“下载”分配一个唯一的ID,然后将用户发送到每隔几秒钟刷新一次(或用AJAX检查)的页面。文件完成后,将其保存在相同的唯一ID下并...

  • 如果文件已准备好,请进行下载。
  • 如果文件尚未准备好,请显示进度。

然后,您可以跳过整个iframe / waiting / browserwindow混乱,但有一个非常优雅的解决方案。


这听起来像我上面提到的临时文件方法。如果事实证明我的想法是不可能的,那么我可能会做这样的事情,但我希望避免这样做。
JW。

3

如果您不想在服务器上生成和存储文件,是否愿意存储状态,例如文件进行中,文件已完成?您的“等待”页面可能会轮询服务器以了解文件生成完成的时间。您可能不确定浏览器是否已开始下载,但您会有所信心。


2

我只是有这个完全相同的问题。我的解决方案是使用临时文件,因为我已经生成了一堆临时文件。提交的表格包括:

var microBox = {
    show : function(content) {
        $(document.body).append('<div id="microBox_overlay"></div><div id="microBox_window"><div id="microBox_frame"><div id="microBox">' +
        content + '</div></div></div>');
        return $('#microBox_overlay');
    },

    close : function() {
        $('#microBox_overlay').remove();
        $('#microBox_window').remove();
    }
};

$.fn.bgForm = function(content, callback) {
    // Create an iframe as target of form submit
    var id = 'bgForm' + (new Date().getTime());
    var $iframe = $('<iframe id="' + id + '" name="' + id + '" style="display: none;" src="about:blank"></iframe>')
        .appendTo(document.body);
    var $form = this;
    // Submittal to an iframe target prevents page refresh
    $form.attr('target', id);
    // The first load event is called when about:blank is loaded
    $iframe.one('load', function() {
        // Attach listener to load events that occur after successful form submittal
        $iframe.load(function() {
            microBox.close();
            if (typeof(callback) == 'function') {
                var iframe = $iframe[0];
                var doc = iframe.contentWindow.document;
                var data = doc.body.innerHTML;
                callback(data);
            }
        });
    });

    this.submit(function() {
        microBox.show(content);
    });

    return this;
};

$('#myForm').bgForm('Please wait...');

在生成文件的脚本末尾,我有:

header('Refresh: 0;url=fetch.php?token=' . $token);
echo '<html></html>';

这将导致触发iframe上的加载事件。然后关闭等待消息,然后将开始文件下载。在IE7和Firefox上测试。


2

以我的经验,有两种方法可以解决此问题:

  1. 在下载中设置一个短暂的Cookie,并让JavaScript不断检查其存在。唯一真正的问题是正确设置Cookie的生存期-太短而JS可能会错过它,太长了,它可能会取消其他下载的下载屏幕。发现时使用JS删除cookie通常可以解决此问题。
  2. 使用fetch / XHR下载文件。您不仅确切地知道文件下载的完成时间,而且如果您使用XHR,还可以使用进度事件来显示进度栏!使用IE / Edge中的msSaveBlob保存生成的blob,并在Firefox / Chrome中使用下载链接(类似此链接)进行保存。这种方法的问题在于,iOS Safari似乎无法正确处理blob的下载-您可以使用FileReader将blob转换为数据URL,然后在新窗口中将其打开,但这将打开文件,而不是将其保存。

2

问候,我知道这个话题很久了,但是我留下了一个我在其他地方看到的解决方案,它的工作原理是:

/**
 *  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'));
}

您可以使用它:

downloadURI("www.linkToFile.com", "file.name");

1

如果您下载的是已保存的文件,而不是保存在文档中,则无法确定何时完成下载,因为它不在当前文档的范围内,而是在浏览器中的单独过程。


8
我要澄清-我的“M不太关心当下载完成,如果我可以只识别下载开始的时候,这将是足够的。
。JW

0

问题是在生成文件时有一个“等待”指示,然后在下载文件后恢复正常。我喜欢这样做的方法是使用隐藏的iFrame并挂接框架的onload事件,以便在下载开始时让我的页面知道。BUT onload不会在IE中触发以进行文件下载(例如带有附件标头令牌)。轮询服务器可以工作,但是我不喜欢这种额外的复杂性。所以这是我的工作:

  • 像往常一样定位隐藏的iFrame。
  • 生成内容。在2分钟内以绝对超时对其进行缓存。
  • 将javascript重定向发送回调用方客户端,实际上是第二次调用生成器页面。注意:这将导致onload事件在IE中触发,因为它的作用类似于常规页面。
  • 从缓存中删除内容并将其发送给客户端。

免责声明,请勿在繁忙的站点上执行此操作,因为可能会增加缓存。但是,实际上,如果您的站点长时间运行很忙,无论如何都会使您的线程饿死。

这是背后代码的样子,这是您真正需要的。

public partial class Download : System.Web.UI.Page
{
    protected System.Web.UI.HtmlControls.HtmlControl Body;

    protected void Page_Load( object sender, EventArgs e )
    {
        byte[ ] data;
        string reportKey = Session.SessionID + "_Report";

        // Check is this page request to generate the content
        //    or return the content (data query string defined)
        if ( Request.QueryString[ "data" ] != null )
        {
            // Get the data and remove the cache
            data = Cache[ reportKey ] as byte[ ];
            Cache.Remove( reportKey );

            if ( data == null )                    
                // send the user some information
                Response.Write( "Javascript to tell user there was a problem." );                    
            else
            {
                Response.CacheControl = "no-cache";
                Response.AppendHeader( "Pragma", "no-cache" );
                Response.Buffer = true;

                Response.AppendHeader( "content-disposition", "attachment; filename=Report.pdf" );
                Response.AppendHeader( "content-size", data.Length.ToString( ) );
                Response.BinaryWrite( data );
            }
            Response.End();                
        }
        else
        {
            // Generate the data here. I am loading a file just for an example
            using ( System.IO.FileStream stream = new System.IO.FileStream( @"C:\1.pdf", System.IO.FileMode.Open ) )
                using ( System.IO.BinaryReader reader = new System.IO.BinaryReader( stream ) )
                {
                    data = new byte[ reader.BaseStream.Length ];
                    reader.Read( data, 0, data.Length );
                }

            // Store the content for retrieval              
            Cache.Insert( reportKey, data, null, DateTime.Now.AddMinutes( 5 ), TimeSpan.Zero );

            // This is the key bit that tells the frame to reload this page 
            //   and start downloading the content. NOTE: Url has a query string 
            //   value, so that the content isn't generated again.
            Body.Attributes.Add("onload", "window.location = 'binary.aspx?data=t'");
        }
    }

0

如果您只想在显示下载对话框之前仅显示消息或加载程序gif,则快速解决方案是将消息放入隐藏的容器中,然后单击生成要下载文件的按钮,即可使该容器可见。然后使用jquery或javascript捕获按钮的focusout事件,以隐藏包含消息的容器


0

如果不建议使用带有Blob的Xmlhttprequest,则可以在新窗口中打开文件,并检查eny元素是否以间隔填充在该窗口主体中。

var form = document.getElementById("frmDownlaod");
 form.setAttribute("action","downoad/url");
 form.setAttribute("target","downlaod");
 var exportwindow = window.open("", "downlaod", "width=800,height=600,resizable=yes");
 form.submit();

var responseInterval = setInterval(function(){
	var winBody = exportwindow.document.body
	if(winBody.hasChildNodes()) // or 'downoad/url' === exportwindow.document.location.href
	{
		clearInterval(responseInterval);
		// do your work
		// if there is error page configured your application for failed requests, check for those dom elemets 
	}
}, 1000)
//Better if you specify maximun no of intervals


0

这个Java / Spring示例检测下载的结束,此时它隐藏了“正在加载...”指示符。

方法:在JS端,设置Cookie的最大有效期限为2分钟,并每秒轮询一次Cookie的有效期限。然后,服务器端会使用更早的版本覆盖此Cookie到期期限,即服务器进程的完成。一旦在JS轮询中检测到cookie到期,就会隐藏“正在加载...”。

JS边

function buttonClick() { // Suppose this is the handler for the button that starts
    $("#loadingProgressOverlay").show();  // show loading animation
    startDownloadChecker("loadingProgressOverlay", 120);
    // Here you launch the download URL...
    window.location.href = "myapp.com/myapp/download";
}

// This JS function detects the end of a download.
// It does timed polling for a non-expired Cookie, initially set on the 
// client-side with a default max age of 2 min., 
// but then overridden on the server-side with an *earlier* expiration age 
// (the completion of the server operation) and sent in the response. 
// Either the JS timer detects the expired cookie earlier than 2 min. 
// (coming from the server), or the initial JS-created cookie expires after 2 min. 
function startDownloadChecker(imageId, timeout) {

    var cookieName = "ServerProcessCompleteChecker";  // Name of the cookie which is set and later overridden on the server
    var downloadTimer = 0;  // reference to timer object    

    // The cookie is initially set on the client-side with a specified default timeout age (2 min. in our application)
    // It will be overridden on the server side with a new (earlier) expiration age (the completion of the server operation), 
    // or auto-expire after 2 min.
    setCookie(cookieName, 0, timeout);

    // set timer to check for cookie every second
    downloadTimer = window.setInterval(function () {

        var cookie = getCookie(cookieName);

        // If cookie expired (NOTE: this is equivalent to cookie "doesn't exist"), then clear "Loading..." and stop polling
        if ((typeof cookie === 'undefined')) {
            $("#" + imageId).hide();
            window.clearInterval(downloadTimer);
        }

    }, 1000); // Every second
}

// These are helper JS functions for setting and retrieving a Cookie
function setCookie(name, value, expiresInSeconds) {
    var exdate = new Date();
    exdate.setTime(exdate.getTime() + expiresInSeconds * 1000);
    var c_value = escape(value) + ((expiresInSeconds == null) ? "" : "; expires=" + exdate.toUTCString());
    document.cookie = name + "=" + c_value + '; path=/';
}

function getCookie(name) {
    var parts = document.cookie.split(name + "=");
    if (parts.length == 2 ) {
        return parts.pop().split(";").shift();
    }
}

Java / Spring服务器端

    @RequestMapping("/download")
    public String download(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //... Some logic for downloading, returning a result ...

        // Create a Cookie that will override the JS-created Max-Age-2min Cookie 
        // with an earlier expiration (same name)
        Cookie myCookie = new Cookie("ServerProcessCompleteChecker", "-1");
        myCookie.setMaxAge(0); // this is immediate expiration, 
                               // but can also add +3 sec. for any flushing concerns
        myCookie.setPath("/");
        response.addCookie(myCookie);
        //... -- presumably the download is writing to the Output Stream...
        return null;
}

cookie是由JS脚本创建的,但未由控制器更新,它保持原始值(0),如何在不刷新页面的情况下更新cookie值?
Shessuky

真奇怪-你可以确保这个名字究竟是否正确?如果名称匹配,它将覆盖cookie。让我知道
基因b。

原始值不为0。在JS中设置的原始值为2分钟。新值,该服务器假定与修改是0
B基因。

另外,您是否正在这样做: myCookie.setPath("/"); response.addCookie(myCookie);
基因b。

我发现(出于某种原因)我应该在执行response.getOutputStream()之前添加cookie (获取响应输出流以附加下载文件),在执行此步骤后我没有考虑到它
Shessuky

0

Primefaces也使用Cookie轮询

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);
        }
    },

-2

单击按钮/链接时创建一个iframe,并将其附加到正文中。

                  $('<iframe />')
                 .attr('src', url)
                 .attr('id','iframe_download_report')
                 .hide()
                 .appendTo('body'); 

延迟创建iframe,并在下载后将其删除。

                            var triggerDelay =   100;
                            var cleaningDelay =  20000;
                            var that = this;
                            setTimeout(function() {
                                var frame = $('<iframe style="width:1px; height:1px;" class="multi-download-frame"></iframe>');
                                frame.attr('src', url+"?"+ "Content-Disposition: attachment ; filename="+that.model.get('fileName'));
                                $(ev.target).after(frame);
                                setTimeout(function() {
                                    frame.remove();
                                }, cleaningDelay);
                            }, triggerDelay);

缺少信息,也无法解决“何时隐藏加载”问题。
Tom Roggero 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.