我有一个页面,允许用户下载动态生成的文件。生成需要很长时间,所以我想显示一个“等待”指示器。问题是,我不知道如何检测浏览器何时收到文件,以便隐藏指示器。
我正在请求一个隐藏的表单,它会POST到服务器,并以隐藏的 iframe 为目标。这是,所以我不会用结果替换整个浏览器窗口。我在 iframe 上监听“加载”事件,希望下载完成后它会触发。
我返回一个Content-Disposition: attachment带有文件的“”标题,这会导致浏览器显示“保存”对话框。但是浏览器不会在 iframe 中触发“加载”事件。
Content-Disposition: attachment
我尝试过的一种方法是使用multi-part响应。所以它会发送一个空的 HTML 文件,以及附加的可下载文件。
multi-part
例如:
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 文件,触发 “加载” 事件,然后显示可下载文件的 “保存” 对话框。但它在Internet Explorer和Safari上失败;Internet Explorer 触发“加载”事件,但不下载文件, Safari 下载 文件(名称和内容类型错误)并且不触发 “加载” 事件。
另一种方法可能是调用开始文件创建,轮询服务器直到它准备好,然后下载已经创建的文件。但我宁愿避免在服务器上创建临时文件。
我该怎么办?
一种可能的解决方案是在客户端上使用 JavaScript。
客户端算法:
服务器算法:
客户端源代码(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 ); }