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

我请求一个隐藏的表单,它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”事件。

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

我该怎么办?


当前回答

您可以依赖浏览器的缓存,并在文件加载到缓存时触发同一文件的第二次下载。

$('#link').click(function(e) {
    e.preventDefault();

    var url = $(this).attr('href');
    var request = new XMLHttpRequest();
    request.responseType = "blob";
    request.open("GET", url);

    var self = this;
    request.onreadystatechange = function () {
        if (request.readyState === 4) {
            var file = $(self).data('file');
            var anchor = document.createElement('a');
            anchor.download = file;
            console.log(file);
            console.log(request);
            anchor.href = window.URL.createObjectURL(request.response);
            anchor.click();
            console.log('Completed. Download window popped up.');
        }
    };
    request.send();
});

其他回答

我在这个配置中遇到了同样的问题:

Struts 1.2.9 jQuery 1.3.2。 jQuery UI 1.7.1.custom Internet Explorer 11 Java 5

我用饼干的解决方案:

客户端:

在提交表单时,调用JavaScript函数隐藏页面并加载等待的旋转器

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

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

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

如果找到cookie,停止每500毫秒检查一次,使cookie过期并调用函数返回页面并删除等待spinner (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);

如果XMLHttpRequest和一个blob不是一个选项,那么你可以在一个新窗口中打开你的文件,并检查是否有任何元素被填充到该窗口体中。

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 an error page configured in your application // for failed requests, check for those DOM elements. } }, 1000) // Better if you specify the maximum number of intervals

来自其他地方的有效解决方案:

/**
 *  download file, show modal
 *
 * @param uri link
 * @param name file name
 */
function downloadURI(uri, name) {
// <------------------------------------------       Do something (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");

您可以依赖浏览器的缓存,并在文件加载到缓存时触发同一文件的第二次下载。

$('#link').click(function(e) {
    e.preventDefault();

    var url = $(this).attr('href');
    var request = new XMLHttpRequest();
    request.responseType = "blob";
    request.open("GET", url);

    var self = this;
    request.onreadystatechange = function () {
        if (request.readyState === 4) {
            var file = $(self).data('file');
            var anchor = document.createElement('a');
            anchor.download = file;
            console.log(file);
            console.log(request);
            anchor.href = window.URL.createObjectURL(request.response);
            anchor.click();
            console.log('Completed. Download window popped up.');
        }
    };
    request.send();
});

我在这个问题上有一个真正的斗争,但我发现了一个使用iframes的可行解决方案。这很糟糕,但它适用于我遇到的一个简单问题。)

我有一个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将永远不会将下载脚本识别为“已加载”。我知道这可能并不一定是检测浏览器何时接收文件,但您的问题听起来与我的类似。