我有一个允许用户下载动态生成文件的页面。生成它需要很长时间,所以我想显示一个“等待”指示器。问题是,我不知道如何检测浏览器何时接收到文件,以便我可以隐藏指示器。
我请求一个隐藏的表单,它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”事件。
另一种方法可能是调用启动文件创建,轮询服务器直到准备就绪,然后下载已经创建的文件。但我宁愿避免在服务器上创建临时文件。
我该怎么办?
如果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
一种可能的解决方案是在客户机上使用JavaScript。
客户端算法:
生成一个随机的唯一令牌。
提交下载请求,并在GET/POST字段中包含令牌。
显示“等待”指示灯。
启动一个计时器,每隔一秒左右,查找一个名为“fileDownloadToken”的cookie(或任何您决定的cookie)。
如果cookie存在,并且它的值与令牌匹配,则隐藏“等待”指示器。
服务器算法:
在请求中查找GET/POST字段。
如果它有一个非空值,删除一个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
);
}
如果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