我有一个页面,允许用户下载动态生成的文件。生成需要很长时间,所以我想显示一个“等待”指标。问题是,我无法弄清楚如何检测浏览器何时收到文件,因此我可以隐藏指示器。
我正在以隐藏的形式发出请求,该请求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下载文件(名称和内容类型错误),并且不会触发“加载”事件。
另一种方法可能是调用启动文件创建,然后轮询服务器直到它准备就绪,然后下载已经创建的文件。但我宁愿避免在服务器上创建临时文件。
有没有人有更好的主意?
答案 0 :(得分:412)
一个possible solution在客户端上使用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
);
}
答案 1 :(得分:27)
一个非常简单(和蹩脚)的单行解决方案是使用window.onblur()
事件来关闭加载对话框。当然,如果花费太长时间并且用户决定做其他事情(比如阅读电子邮件),则加载对话框将关闭。
答案 2 :(得分:12)
但谷歌引领的那些人可能会对我的解决方案感兴趣。 它非常简单,但又可靠。它可以显示真实的进度消息(并且可以轻松插入现有进程):
处理的脚本(我的问题是:通过http检索文件并将其作为zip传递)将状态写入会话。
每秒轮询并显示状态。这就是全部(好吧,不是。你必须处理很多细节[例如并发下载],但它是一个好的起点; - ))。
下载页面:
<a href="download.php?id=1" class="download">DOWNLOAD 1</a>
<a href="download.php?id=2" class="download">DOWNLOAD 2</a>
...
<div id="wait">
Please wait...
<div id="statusmessage"></div>
</div>
<script>
//this is jquery
$('a.download').each(function()
{
$(this).click(
function(){
$('#statusmessage').html('prepare loading...');
$('#wait').show();
setTimeout('getstatus()', 1000);
}
);
});
});
function getstatus(){
$.ajax({
url: "/getstatus.php",
type: "POST",
dataType: 'json',
success: function(data) {
$('#statusmessage').html(data.message);
if(data.status=="pending")
setTimeout('getstatus()', 1000);
else
$('#wait').hide();
}
});
}
</script>
getstatus.php
<?php
session_start();
echo json_encode($_SESSION['downloadstatus']);
?>
的download.php
<?php
session_start();
$processing=true;
while($processing){
$_SESSION['downloadstatus']=array("status"=>"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
?>
答案 3 :(得分:10)
我使用以下内容下载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;
}
关闭文件下载对话框后,窗口会重新调整焦点,以便触发焦点事件。
答案 4 :(得分:8)
我写了一个简单的JavaScript类,它实现了一种类似于bull answer中描述的技术。我希望这对某人有用。 GitHub项目名为response-monitor.js
默认情况下,它使用spin.js作为等待指标,但它也提供了一组回调来实现自定义指标。
支持JQuery,但不是必需的。
显着功能
使用示例
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});
带回调的客户端(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文件夹。
答案 5 :(得分:5)
如果您正在流式传输动态生成的文件,并且还实施了实时的服务器到客户端消息库,则可以非常轻松地向客户端发出警报。
我喜欢并推荐的服务器到客户端消息传递库是Socket.io(通过Node.js)。在完成服务器脚本生成正在下载的文件以供下载之后,该脚本中的最后一行可以向Socket.io发送一条消息,该消息向客户端发送通知。在客户端上,Socket.io侦听从服务器发出的传入消息,并允许您对它们进行操作。使用此方法优于其他方法的好处是,您可以在完成流式传输后检测到“真正的”完成事件。
例如,您可以在单击下载链接后显示忙碌指示符,流式传输文件,从流式脚本最后一行的服务器向Socket.io发送消息,在客户端上收听通知,通过隐藏忙碌指示符来接收通知并更新您的UI。
我意识到大多数人在阅读这个问题的答案时可能没有这种类型的设置,但我已经使用这个确切的解决方案在我自己的项目中产生了很大的效果,并且效果非常好。
Socket.io非常容易安装和使用。查看更多:http://socket.io/
答案 6 :(得分:5)
根据埃尔默的例子,我已经准备好了自己的解决方案。使用已定义的 download 类单击元素后,可以在屏幕上显示自定义消息。我已经使用焦点触发器来隐藏消息。
<强>的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 强>
<div id="message" style="display: none">
<div id="message-screen-mask" class="ui-widget-overlay ui-front"></div>
<div id="message-text" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-front ui-draggable ui-resizable waitmessage">please wait...</div>
</div>
现在你应该实现任何要下载的元素:
<a class="download" href="file://www.ocelot.com.pl/prepare-report">Download report</a>
或
<input class="download" type="submit" value="Download" name="actionType">
每次下载点击后,您会看到您的报告正在创建的消息,请稍候......
答案 7 :(得分:3)
当用户触发文件生成时,您只需为该“下载”分配一个唯一ID,并将用户发送到每隔几秒刷新(或使用AJAX检查)的页面。文件完成后,将其保存在相同的唯一ID下......
然后你可以跳过整个iframe / waiting / browserwindow混乱,但有一个非常优雅的解决方案。
答案 8 :(得分:3)
核心问题是Web浏览器没有取消页面导航时触发的事件,但有页面完成加载时触发的事件。直接浏览器事件之外的任何事情都将是利与弊的攻击。</ p>
有四种已知方法可用来检测何时开始下载浏览器:
a
属性的download
标记附加,并触发click事件。然后,现代的网络浏览器将为用户提供保存已检索文件的选项。这种方法有几个缺点:load
事件,但是,如果下载开始,则不会触发任何事件。然后,Javascript可以循环检测在Web服务器上设置Cookie的过程。这种方法有几个缺点:load
事件就会发生。这种方法有几个缺点:load
事件触发,则发生错误,请中止XHR请求并删除iframe。如果发生XHR progress
事件,则可能已在iframe中开始下载,中止XHR请求,等待几秒钟,然后删除iframe。这允许下载较大的文件而无需依赖服务器端cookie。这种方法有几个缺点:如果没有适当的内置Web浏览器事件,那么这里没有任何完美的解决方案。但是,根据您的用例,以上四种方法中的一种可能比其他方法更适合。
只要有可能,就立即将响应流传输到客户端,而不是先在服务器上生成所有内容,然后再发送响应。可以流式传输各种文件格式,例如CSV,JSON,XML,ZIP等。这实际上取决于寻找支持流式内容的库。在请求开始后立即流式传输响应时,检测下载的开始并不重要,因为下载将立即开始。
另一种选择是仅预先输出下载头,而不是等待首先生成所有内容。然后生成内容,最后开始发送给客户端。用户的内置下载器将耐心等待,直到数据开始到达为止。缺点是基础网络连接可能会超时(等待在客户端或服务器端)开始等待数据流。
答案 9 :(得分:3)
/**
* download file, show modal
*
* @param uri link
* @param name file name
*/
function downloadURI(uri, name) {
// <------------------------------------------ Do someting (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");
答案 10 :(得分:3)
我参加派对的时间已经很晚了但如果其他人想知道我的解决方案,我会把它放在这里:
我对这个确切的问题真的很困难,但我找到了一个使用iframe的可行解决方案(我知道,我知道。这很糟糕,但它适用于我遇到的一个简单问题)
我有一个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将永远不会将下载脚本识别为已加载&#39;。我知道这可能不一定会在浏览器收到文件时检测到,但您的问题听起来与我的相似。
答案 11 :(得分:3)
如果您不想在服务器上生成和存储文件,是否愿意存储状态,例如文件正在进行中,文件完整?您的“等待”页面可以轮询服务器以了解文件生成何时完成。你不确定浏览器是否开始下载,但你有信心。
答案 12 :(得分:2)
我刚才遇到了同样的问题。我的解决方案是使用临时文件,因为我已经生成了一堆临时文件。表格提交时:
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事件被触发。然后关闭等待消息,然后开始文件下载。在IE7和Firefox上测试过。
答案 13 :(得分:0)
问题是在生成文件时有一个“等待”指示符,然后在下载文件后恢复正常。我喜欢todo的方式是使用隐藏的iFrame并挂钩框架的onload事件,以便在下载开始时让我的页面知道。 IE中的但 onload不会触发文件下载(与附件标头令牌一样)。轮询服务器有效,但我不喜欢额外的复杂性。所以这就是我的工作:
免责声明,请勿在繁忙的网站上执行此操作,因为缓存可能会加起来。但实际上,如果您的网站忙于长时间运行的过程,那么无论如何都会让您失去线程。
这就是代码隐藏的样子,这就是你真正需要的。
public partial class Download : System.Web.UI.Page
{
protected System.Web.UI.HtmlControls.HtmlControl Body;
protected void Page_Load( object sender, EventArgs e )
{
byte[ ] data;
string reportKey = Session.SessionID + "_Report";
// Check is this page request to generate the content
// or return the content (data query string defined)
if ( Request.QueryString[ "data" ] != null )
{
// Get the data and remove the cache
data = Cache[ reportKey ] as byte[ ];
Cache.Remove( reportKey );
if ( data == null )
// send the user some information
Response.Write( "Javascript to tell user there was a problem." );
else
{
Response.CacheControl = "no-cache";
Response.AppendHeader( "Pragma", "no-cache" );
Response.Buffer = true;
Response.AppendHeader( "content-disposition", "attachment; filename=Report.pdf" );
Response.AppendHeader( "content-size", data.Length.ToString( ) );
Response.BinaryWrite( data );
}
Response.End();
}
else
{
// Generate the data here. I am loading a file just for an example
using ( System.IO.FileStream stream = new System.IO.FileStream( @"C:\1.pdf", System.IO.FileMode.Open ) )
using ( System.IO.BinaryReader reader = new System.IO.BinaryReader( stream ) )
{
data = new byte[ reader.BaseStream.Length ];
reader.Read( data, 0, data.Length );
}
// Store the content for retrieval
Cache.Insert( reportKey, data, null, DateTime.Now.AddMinutes( 5 ), TimeSpan.Zero );
// This is the key bit that tells the frame to reload this page
// and start downloading the content. NOTE: Url has a query string
// value, so that the content isn't generated again.
Body.Attributes.Add("onload", "window.location = 'binary.aspx?data=t'");
}
}
答案 14 :(得分:0)
如果您只想在显示下载对话框之前显示消息或加载程序gif,则快速解决方案是将消息放入隐藏容器中,当您单击生成要下载文件的按钮时,您可以容器可见。然后使用jquery或javascript来捕获按钮的focusout事件以隐藏包含消息的容器
答案 15 :(得分:0)
如果带有blob的Xmlhttprequest不是一个选项,那么你可以在新窗口中打开你的文件并检查eny元素是否以间隔填充在该窗体中。
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 error page configured your application for failed requests, check for those dom elemets
}
}, 1000)
//Better if you specify maximun no of intervals
&#13;
答案 16 :(得分:0)
“如何检测浏览器何时接收文件下载?”
该配置也遇到了同样的问题:
支柱1.2.9
jquery-1.3.2。
jquery-ui-1.7.1.custom
IE 11
java 5
我的Cookie解决方案:
-客户端
提交表单时,请调用javascript函数以隐藏页面并加载等待的微调框
Expression<func<T1,T2,bool>>
然后,调用一个函数,该函数每500毫秒检查一次cookie是否来自服务器。
function loadWaitingSpinner(){
... hide your page and show your spinner ...
}
如果找到cookie,则每500毫秒停止检查一次,使cookie过期并调用函数以返回页面并删除等待的微调器( removeWaitingSpinner())。如果您希望能够再次下载另一个文件,请务必使Cookie过期!
function checkCookie(){
var verif = setInterval(isWaitingCookie,500,verif);
}
-服务器端:
在服务器过程结束时,将cookie添加到响应中。当您的文件准备好下载时,该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 ...
}
我希望可以帮助别人!
答案 17 :(得分:0)
根据我的经验,有两种方法可以解决此问题:
答案 18 :(得分:0)
此Java / Spring示例检测到下载结束,此时它隐藏了“正在加载...”指示符。
方法:在JS端,设置Cookie的最长有效期限为2分钟,并每秒轮询一次Cookie 有效期限。然后,服务器端会使用更早的有效期限来覆盖此Cookie,即服务器进程的完成。一旦在JS轮询中检测到cookie过期,就会隐藏“正在加载...”。
JS边
function buttonClick() { // Suppose this is the handler for the button that starts
$("#loadingProgressOverlay").show(); // show loading animation
startDownloadChecker("loadingProgressOverlay", 120);
// Here you launch the download URL...
window.location.href = "myapp.com/myapp/download";
}
// This JS function detects the end of a download.
// It does timed polling for a non-expired Cookie, initially set on the
// client-side with a default max age of 2 min.,
// but then overridden on the server-side with an *earlier* expiration age
// (the completion of the server operation) and sent in the response.
// Either the JS timer detects the expired cookie earlier than 2 min.
// (coming from the server), or the initial JS-created cookie expires after 2 min.
function startDownloadChecker(imageId, timeout) {
var cookieName = "ServerProcessCompleteChecker"; // Name of the cookie which is set and later overridden on the server
var downloadTimer = 0; // reference to timer object
// The cookie is initially set on the client-side with a specified default timeout age (2 min. in our application)
// It will be overridden on the server side with a new (earlier) expiration age (the completion of the server operation),
// or auto-expire after 2 min.
setCookie(cookieName, 0, timeout);
// set timer to check for cookie every second
downloadTimer = window.setInterval(function () {
var cookie = getCookie(cookieName);
// If cookie expired (NOTE: this is equivalent to cookie "doesn't exist"), then clear "Loading..." and stop polling
if ((typeof cookie === 'undefined')) {
$("#" + imageId).hide();
window.clearInterval(downloadTimer);
}
}, 1000); // Every second
}
// These are helper JS functions for setting and retrieving a Cookie
function setCookie(name, value, expiresInSeconds) {
var exdate = new Date();
exdate.setTime(exdate.getTime() + expiresInSeconds * 1000);
var c_value = escape(value) + ((expiresInSeconds == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = name + "=" + c_value + '; path=/';
}
function getCookie(name) {
var parts = document.cookie.split(name + "=");
if (parts.length == 2 ) {
return parts.pop().split(";").shift();
}
}
Java / Spring服务器端
@RequestMapping("/download")
public String download(HttpServletRequest request, HttpServletResponse response) throws Exception {
//... Some logic for downloading, returning a result ...
// Create a Cookie that will override the JS-created Max-Age-2min Cookie
// with an earlier expiration (same name)
Cookie myCookie = new Cookie("ServerProcessCompleteChecker", "-1");
myCookie.setMaxAge(0); // this is immediate expiration,
// but can also add +3 sec. for any flushing concerns
myCookie.setPath("/");
response.addCookie(myCookie);
//... -- presumably the download is writing to the Output Stream...
return null;
}
答案 19 :(得分:0)
Primefaces也使用Cookie轮询
monitorDownload: function(start, complete, monitorKey) {
if(this.cookiesEnabled()) {
if(start) {
start();
}
var cookieName = monitorKey ? 'primefaces.download_' + monitorKey : 'primefaces.download';
window.downloadMonitor = setInterval(function() {
var downloadComplete = PrimeFaces.getCookie(cookieName);
if(downloadComplete === 'true') {
if(complete) {
complete();
}
clearInterval(window.downloadMonitor);
PrimeFaces.setCookie(cookieName, null);
}
}, 1000);
}
},
答案 20 :(得分:0)
如果您下载的文件已保存,而不是在文档中,则无法确定下载何时完成,因为它不在当前文档的范围内,而是在单独的进程中浏览器。
答案 21 :(得分:0)
我已经更新了以下参考代码。添加正确的下载 URL 链接并尝试一下。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
padding: 0;
margin: 0;
}
svg:not(:root) {
display: block;
}
.playable-code {
background-color: #f4f7f8;
border: none;
border-left: 6px solid #558abb;
border-width: medium medium medium 6px;
color: #4d4e53;
height: 100px;
width: 90%;
padding: 10px 10px 0;
}
.playable-canvas {
border: 1px solid #4d4e53;
border-radius: 2px;
}
.playable-buttons {
text-align: right;
width: 90%;
padding: 5px 10px 5px 26px;
}
</style>
<style type="text/css">
.event-log {
width: 25rem;
height: 4rem;
border: 1px solid black;
margin: .5rem;
padding: .2rem;
}
input {
width: 11rem;
margin: .5rem;
}
</style>
<title>XMLHttpRequest: progress event - Live_example - code sample</title>
</head>
<body>
<div class="controls">
<input class="xhr success" type="button" name="xhr" value="Click to start XHR (success)" />
<input class="xhr error" type="button" name="xhr" value="Click to start XHR (error)" />
<input class="xhr abort" type="button" name="xhr" value="Click to start XHR (abort)" />
</div>
<textarea readonly class="event-log"></textarea>
<script>
const xhrButtonSuccess = document.querySelector('.xhr.success');
const xhrButtonError = document.querySelector('.xhr.error');
const xhrButtonAbort = document.querySelector('.xhr.abort');
const log = document.querySelector('.event-log');
function handleEvent(e) {
if (e.type=='progress')
{log.textContent = log.textContent + `${e.type}: ${e.loaded} bytes transferred Received ${event.loaded} of ${event.total}\n`;
}
else if (e.type=='loadstart')
{
log.textContent = log.textContent + `${e.type}: started\n`;
}
else if (e.type=='error')
{
log.textContent = log.textContent + `${e.type}: error\n`;
}
else if (e.type=='loadend')
{
log.textContent = log.textContent + `${e.type}: completed\n`;
}
}
function addListeners(xhr) {
xhr.addEventListener('loadstart', handleEvent);
xhr.addEventListener('load', handleEvent);
xhr.addEventListener('loadend', handleEvent);
xhr.addEventListener('progress', handleEvent);
xhr.addEventListener('error', handleEvent);
xhr.addEventListener('abort', handleEvent);
}
function runXHR(url) {
log.textContent = '';
const xhr = new XMLHttpRequest();
var request = new XMLHttpRequest();
addListeners(request);
request.open('GET', url, true);
request.responseType = 'blob';
request.onload = function (e) {
var data = request.response;
var blobUrl = window.URL.createObjectURL(data);
var downloadLink = document.createElement('a');
downloadLink.href = blobUrl;
downloadLink.download ='download.zip';
downloadLink.click();
};
request.send();
return request
}
xhrButtonSuccess.addEventListener('click', () => {
runXHR('https://abbbbbc.com/download.zip');
});
xhrButtonError.addEventListener('click', () => {
runXHR('http://i-dont-exist');
});
xhrButtonAbort.addEventListener('click', () => {
runXHR('https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json').abort();
});
</script>
</body>
</html>
Return to post
参考:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/progress_event#live_example
答案 22 :(得分:0)
您可以依赖浏览器的缓存,并在文件加载到缓存时触发同一文件的第二次下载。
$('#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();
});
答案 23 :(得分:-2)
单击按钮/链接时创建iframe,并将其附加到正文。
$('<iframe />')
.attr('src', url)
.attr('id','iframe_download_report')
.hide()
.appendTo('body');
创建一个有延迟的iframe,并在下载后将其删除。
var triggerDelay = 100;
var cleaningDelay = 20000;
var that = this;
setTimeout(function() {
var frame = $('<iframe style="width:1px; height:1px;" class="multi-download-frame"></iframe>');
frame.attr('src', url+"?"+ "Content-Disposition: attachment ; filename="+that.model.get('fileName'));
$(ev.target).after(frame);
setTimeout(function() {
frame.remove();
}, cleaningDelay);
}, triggerDelay);