我有一个页面,允许用户下载动态生成的文件.生成需要很长时间,所以我想显示一个"等待"指标.问题是,我无法弄清楚如何检测浏览器何时收到文件,所以我可以隐藏指标.
我正在以隐藏的形式发出请求,该请求POST到服务器,并针对其结果定位隐藏的iframe.这样我就不会用结果替换整个浏览器窗口.我在iframe上监听"加载"事件,希望在下载完成后它会触发.
我在文件中返回"Content-Disposition:attachment"标题,这会导致浏览器显示"保存"对话框.但浏览器不会在iframe中触发"加载"事件.
我尝试过的一种方法是使用多部分响应.因此它会发送一个空的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上失败了; IE触发"加载"事件但不下载文件,Safari下载文件(名称和内容类型错误),并且不会触发"加载"事件.
一种不同的方法可能是调用启动文件创建,然后轮询服务器直到它准备就绪,然后下载已经创建的文件.但我宁愿避免在服务器上创建临时文件.
有没有人有更好的主意?
一种可能的解决方案是在客户端使用JavaScript.
客户端算法:
生成随机唯一标记.
提交下载请求,并将令牌包含在GET/POST字段中.
显示"等待"指标.
启动一个计时器,每隔一秒左右,找一个名为"fileDownloadToken"的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 ); }
一个非常简单(和蹩脚)的单行解决方案是使用该window.onblur()
事件来关闭加载对话框.当然,如果花费太长时间并且用户决定做其他事情(比如阅读电子邮件),则加载对话框将关闭.
老线程,我知道......
但那些谷歌引领的人可能会对我的解决方案感兴趣.它非常简单,但又可靠.它可以显示真实的进度消息(并且可以轻松插入现有进程):
处理的脚本(我的问题是:通过http检索文件并将其作为zip传递)将状态写入会话.
每秒轮询和显示状态.这就是全部(好吧,不是.你必须处理很多细节[例如并发下载],但它是一个很好的起点;-)).
下载页面:
DOWNLOAD 1 DOWNLOAD 2 ...Please wait...
getstatus.php
的download.php
"pending","message"=>"Processing".$someinfo); session_write_close(); $processing=do_what_has_2Bdone(); session_start(); } $_SESSION['downloadstatus']=array("status"=>"finished","message"=>"Done"); //and spit the generated file to the browser ?>
我使用以下内容下载blob并在下载后撤消object-url.它适用于chrome和firefox!
function download(blob){ var url = URL.createObjectURL(blob); console.log('create ' + url); window.addEventListener('focus', window_focus, false); function window_focus(){ window.removeEventListener('focus', window_focus, false); URL.revokeObjectURL(url); console.log('revoke ' + url); } location.href = url; }
关闭文件下载对话框后,窗口会将焦点恢复,以便触发焦点事件.
基于Elmer的示例,我准备了自己的解决方案。使用定义的下载类单击元素后,可以在屏幕上显示自定义消息。我使用焦点触发器来隐藏消息。
的JavaScript
$(function(){$('.download').click(function() { ShowDownloadMessage(); }); }) function ShowDownloadMessage() { $('#message-text').text('your report is creating, please wait...'); $('#message').show(); window.addEventListener('focus', HideDownloadMessage, false); } function HideDownloadMessage(){ window.removeEventListener('focus', HideDownloadMessage, false); $('#message').hide(); }
的HTML
please wait...
现在,您应该实现任何要下载的元素:
Download report
要么
每次下载后,您都会看到报告正在创建的消息,请稍候...
我写了一个简单的JavaScript类,它实现了一种类似于在背叛答案中描述的技术.我希望这对某人有用.GitHub项目名为response-monitor.js
默认情况下,它使用spin.js作为等待指示符,但它还提供了一组用于实现自定义指标的回调.
支持JQuery但不是必需的.
值得注意的功能
简单集成
没有依赖
JQuery插件(可选)
Spin.js集成(可选)
用于监视事件的可配置回调
处理多个同时请求
服务器端错误检测
超时检测
跨浏览器
用法示例
HTML
Link 1 (Timeout: 30s) Link 2 (Timeout: 10s)
客户端(纯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});
带回调的客户端(JQuery)
//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; //ex: response-monitor_1419642741528 //this value is passed to the client through the ResponseMonitor.onResponse callback $cookieValue = 1; //for ex, "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文件夹.
如果您正在流式传输要动态生成的文件,并且已实现了实时的服务器到客户端消息传递库,则可以非常轻松地向客户端发出警报。
我喜欢并推荐的服务器到客户端消息传递库是Socket.io(通过Node.js)。服务器脚本完成后,生成正在传输的文件以供下载,该脚本中的最后一行可以向Socket.io发出消息,该消息将通知发送给客户端。在客户端上,Socket.io侦听服务器发出的传入消息,并允许您对其进行操作。与其他方法相比,使用此方法的好处是您可以在流完成后检测到“ true”完成事件。
例如,您可以在单击下载链接后显示忙碌指示器,流式传输文件,在流式传输脚本的最后一行中从服务器向Socket.io发送消息,在客户端上侦听通知,接收通知并通过隐藏忙碌指示器来更新您的UI。
我知道大多数阅读此问题答案的人可能没有这种类型的设置,但是我在自己的项目中使用了这种精确的解决方案来产生了很大的效果,并且效果很好。
Socket.io非常易于安装和使用。查看更多:http : //socket.io/