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

我请求一个隐藏的表单,它post到服务器,并以一个隐藏的iframe作为其结果的目标。这样我就不会用结果替换整个浏览器窗口。我监听iframe上的“load”事件,希望它在下载完成时触发。

我将文件返回一个“Content-Disposition: attachment”头,这将导致浏览器显示“Save”对话框。但是浏览器不会在iframe中触发“load”事件。

我尝试过的一种方法是采用多部分回答。所以它会发送一个空的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文件,触发“load”事件,然后显示可下载文件的“Save”对话框。但它在ie和Safari浏览器上无法运行;Internet Explorer触发“load”事件,但它不下载文件,而Safari下载文件(带有错误的名称和内容类型),但不触发“load”事件。

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

我该怎么办?


当前回答

我也遇到过同样的问题。我的解决方案是使用临时文件,因为我已经生成了一堆临时文件。提交表格时:

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上的load事件被触发。然后关闭等待消息,然后开始文件下载。它在Internet Explorer 7和Firefox上进行了测试。

其他回答

我也遇到过同样的问题。我的解决方案是使用临时文件,因为我已经生成了一堆临时文件。提交表格时:

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上的load事件被触发。然后关闭等待消息,然后开始文件下载。它在Internet Explorer 7和Firefox上进行了测试。

问题是在生成文件时有一个“等待”指示器,然后在文件下载后恢复正常。我喜欢这样做的方式是使用一个隐藏的iFrame和钩子帧的onload事件,让我的页面知道什么时候开始下载。

但是onload不会在Internet Explorer中触发文件下载(就像使用附件头令牌一样)。轮询服务器是可行的,但我不喜欢这种额外的复杂性。这就是我所做的:

目标隐藏iFrame像往常一样。 生成内容。用 2分钟内绝对超时。 发送JavaScript重定向回 调用客户端,本质上调用 第二次生成页面。注意:这将导致在Internet Explorer中触发onload事件,因为它就像一个普通页面一样。 从缓存中删除内容并 发送给客户端。

免责声明:不要在繁忙的站点上这样做,因为缓存会增加。但实际上,如果您的站点非常繁忙,那么长时间运行的进程无论如何都会耗尽您的线程。

下面是隐藏代码的样子,这是您真正需要的。

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

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

如果您只想在显示下载对话框之前显示消息或加载器GIF图像,那么一个快速的解决方案是将消息放在一个隐藏的容器中,当您单击生成要下载的文件的按钮时,您将使容器可见。

然后使用jQuery或JavaScript捕获按钮的focusout事件,以隐藏包含消息的容器。

根据我的经验,有两种方法可以解决这个问题:

Set a short-lived cookie on the download, and have JavaScript continually check for its existence. Only real issue is getting the cookie lifetime right - too short and the JavaScript can miss it, too long and it might cancel the download screens for other downloads. Using JavaScript to remove the cookie upon discovery usually fixes this. Download the file using fetch/XHR. Not only do you know exactly when the file download finishes, if you use XHR you can use progress events to show a progress bar! Save the resulting blob with msSaveBlob in Internet Explorer or Edge and a download link (like this one) in Firefox and Chrome. The problem with this method is that iOS Safari doesn't seem to handle downloading blobs right - you can convert the blob into a data URL with a FileReader and open that in a new window, but that's opening the file, not saving it.