我有一个允许用户下载动态生成文件的页面。生成它需要很长时间,所以我想显示一个“等待”指示器。问题是,我不知道如何检测浏览器何时接收到文件,以便我可以隐藏指示器。
我请求一个隐藏的表单,它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”事件。
另一种方法可能是调用启动文件创建,轮询服务器直到准备就绪,然后下载已经创建的文件。但我宁愿避免在服务器上创建临时文件。
我该怎么办?
我在这个问题上有一个真正的斗争,但我发现了一个使用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将永远不会将下载脚本识别为“已加载”。我知道这可能并不一定是检测浏览器何时接收文件,但您的问题听起来与我的类似。
如果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
我在这个配置中遇到了同样的问题:
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);
我也遇到过同样的问题。我的解决方案是使用临时文件,因为我已经生成了一堆临时文件。提交表格时:
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上进行了测试。
我写了一个简单的JavaScript类,实现了一种类似于bulltorious回答中描述的技术。我希望它能对在座的人有用。
GitHub项目被称为response-monitor.js。
默认情况下,它使用spin.js作为等待指示器,但它也为自定义指示器的实现提供了一组回调。
jQuery是支持的,但不是必需的。
显著的特征
简单的集成
没有依赖关系
jQuery插件(可选)
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});
带有回调函数的客户端
// 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; // Example: response-monitor_1419642741528
// This value is passed to the client through the ResponseMonitor.onResponse callback
$cookieValue = 1; // For example, "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文件夹。