我在我的应用程序(使用ExtJS 4.2的单页应用程序)中使用<iframe>
(我知道,我知道......)来执行文件下载,因为它们包含大量数据并且可以使用而生成Excel文件(根据参数我们说的是20秒到20分钟)。
当前的状态是:当用户点击下载按钮时,他被Javascript(window.location.href = xxx
)“重定向”到执行导出的页面,但是因为它是在PHP中完成的,所以没有标题是发送后,浏览器不断加载页面,直到文件被下载。但它不是非常用户友好,因为没有任何东西显示它是否仍在加载,完成(文件下载除外)或失败(这导致页面实际重定向,可能使他失去他正在做的工作)。
所以我创建了一个停靠在右下角的小型非模态窗口,其中包含iframe以及一条小消息以确保用户放心。我需要的是能够检测它何时被加载并且能够区分两种情况:
但是我尝试了所有4个事件(W3Schools doc)并且没有曾经被解雇。我至少可以理解,如果它不是返回的HTML数据,它可能无法触发事件,但即使我强制错误返回文本数据,它也不会被触发。
如果有人知道这方面的解决方案,或者可能适合这里的替代系统,我全都耳朵!谢谢!
编辑:添加了iframe代码。我们的想法是获得一种比setTimeout
更好的方式来关闭它。
var url = 'http://mywebsite.com/my_export_route';
var ifr = $('<iframe class="dl-frame" src="'+url+'" width="0" height="0" frameborder="0"></iframe>');
ifr.appendTo($('body'));
setTimeout(function() {
$('.dl-frame').remove();
}, 3000);
答案 0 :(得分:7)
我想知道它是否需要在前端和后端代码中进行一些重大更改,但您是否考虑过使用AJAX?工作流程将是这样的:用户发送AJAX请求以启动文件生成,并且前端不断地从服务器轮询它的状态,当它完成时 - 向用户显示下载链接。我相信工作流程会更直接。
嗯,你也可以试试这个伎俩。在父窗口中为iframe的完整加载myOnLoadCallback
创建一个回调函数,然后使用parent.myOnLoadCallback()
从iframe中调用它。但是你仍然需要使用setTimeout
来处理服务器错误/连接超时。
最后一件事 - 你是怎么试图抓住iframe的事件的?也许它与浏览器有关。您是否尝试过直接在HTML属性中设置事件回调?喜欢
<iframe onload="done()" onerror="fail()"></iframe>
这是一个不好的做法,我知道,但有时需要完成工作快速,是吗?
<强>更新强>
好吧,我担心你需要用JS调试器度过漫长而痛苦的一天。 load
事件应工作。不过我还是有一些建议:
1)尝试在设置元素src
之前设置事件监听器。也许onload
事件如此之快,以至于它在创建元素和设置事件的回调之间滑动
2)同时尝试检查您的服务器代码是否与iframe很好地匹配。我做了一个简单的测试,试图从Dropbox下载PDF,尝试用你支持的路由替换我的URL。
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<iframe id="book"></iframe>
<button id="go">Request downloads!</button>
<script>
var bookUrl = 'https://www.dropbox.com/s/j4o7tw09lwncqa6/thinkpython.pdf';
$('#book').on('load', function(){
console.log('WOOT!', arguments);
});
$('#go').on('click', function(){
$('#book').attr('src', bookUrl);
});
</script>
更新2
3)另外,请查看浏览器调试器的“网络”选项卡,将src
设置为iframe时会发生什么情况,它应显示请求和服务器对标头的响应。
答案 1 :(得分:3)
我已经尝试过使用jQuery,但是你可以在这篇文章中看到它正常工作。
我做了一个工作示例here。
基本上就是这样:
<iframe src="http://www.example.com" id="myFrame"></iframe>
代码:
function test() {
alert('iframe loaded');
}
$('#myFrame').load(test);
在IE11上测试。
答案 2 :(得分:2)
也许你应该使用
$($('.dl-frame')[0].contentWindow.document).ready(function () {...})
答案 3 :(得分:2)
您可以使用以下脚本。它来自我的一个项目。
$("#reportContent").html("<iframe id='reportFrame' sandbox='allow-same-origin allow-scripts' width='100%' height='300' scrolling='yes' onload='onReportFrameLoad();'\></iframe>");
答案 4 :(得分:2)
我想我会给其他人发布的更合适的方法提供更多的hacky替代方法。如果您可以控制PHP下载脚本,也许只需在下载完成后输出javascript即可。或者可能重定向到运行javascript的html页面。 javascript运行,然后可以尝试在父框架中调用某些东西。什么工作取决于您的应用程序是否在同一个域中运行
相同的域框架只能使用框架javascript对象相互引用。所以它可能是这样的,在你的单页面应用程序中你可以有像
这样的东西window.downloadHasFinished=function(str){ //Global pollution. More unique name?
//code to be run when download has finished
}
对于你下载的php脚本,你可以在完成后输出这个html + javascript
<script>
if(parent && parent.downloadHasFinished)
parent.downloadHasFinished("if you want to pass a data. maybe export url?")
</script>
对于不同的域名,我们可以使用postMessage
。因此,在您的单页面应用程序中,它将类似于
$(window).on("message",function(e){
var e=e.originalEvent
if(e.origin=="http://downloadphp.anotherdomain.com"){ //for security
var message=e.data //data passed if any
//code to be run when download has finished
}
});
并在您的php下载脚本中,您可以输出此html + javascript
<script>
parent.postMessage("if you want to pass data",
"http://downloadphp.anotherdomain.com");
</script>
老实说,如果其他答案有效,你应该使用那些。我只是觉得这是一个有趣的选择,所以我发布了它。
答案 5 :(得分:2)
试试这个(模式)
$(function () {
var session = function (url, filename) {
// `url` : URL of resource
// `filename` : `filename` for resource (optional)
var iframe = $("<iframe>", {
"class": "dl-frame",
"width": "150px",
"height": "150px",
"target": "_top"
})
// `iframe` `load` `event`
.one("load", function (e) {
$(e.target)
.contents()
.find("html")
.html("<html><body><div>"
+ $(e.target)[0].nodeName
+ " loaded" + "</div><br /></body></html>");
alert($(e.target)[0].nodeName
+ " loaded" + "\nClick link to download file");
return false
});
var _session = $.when($(iframe).appendTo("body"));
_session.then(function (data) {
var link = $("<a>", {
"id": "file",
"target": "_top",
"tabindex": "1",
"href": url,
"download": url,
"html": "Click to start {filename} download"
});
$(data)
.contents()
.find("body")
.append($(link))
.addBack()
.find("#file")
.attr("download", function (_, o) {
return (filename || o)
})
.html(function (_, o) {
return o.replace(/{filename}/,
(filename || $(this).attr("download")))
})
});
_session.always(function (data) {
$(data)
.contents()
.find("a#file")
.focus()
// start 6 second `download` `session`,
// on `link` `click`
.one("click", function (e) {
var timer = 6;
var t = setInterval(function () {
$(data)
.contents()
.find("div")
// `session` notifications
.html("Download session started at "
+ new Date() + "\n" + --timer);
}, 1000);
setTimeout(function () {
clearInterval(t);
$(data).replaceWith("<span class=session-notification>"
+ "Download session complete at\n"
+ new Date()
+ "</span><br class=session-notification />"
+ "<a class=session-restart href=#>"
+ "Restart download session</a>");
if ($("body *").is(".session-restart")) {
// start new `session`,
// on `.session-restart` `click`
$(".session-restart")
.on("click", function () {
$(".session-restart, .session-notification")
.remove()
// restart `session` (optional),
// or, other `session` `complete` `callback`
&& session(url, filename ? filename : null)
})
};
}, 6000);
});
});
};
// usage
session("http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf", "ECMA_JS.pdf")
});
答案 6 :(得分:1)
您可以使用this库。您的代码片段类似于:
window.onload = function () {
rajax_obj = new Rajax('',
{
action : 'http://mywebsite.com/my_export_route',
onComplete : function(response) {
//This will only called if you have returned any response
// instead of file from your export script
// In your case 2
// Text data : Error message => Display message to user
}
});
}
然后,您可以在下载链接上点击rajax_obj.post()
。
<a href="javascript:rajax_obj.post()">Download</a>
注意:您应该在PHP脚本中添加一些标题,以便强制下载文件
header('Content-Disposition: attachment; filename="'.$file.'"');
header('Content-Transfer-Encoding: binary');
答案 7 :(得分:1)
关于你的评论是否有更好的方法来关闭它而不是setTimeout。您可以使用jQuery fadeOut选项或任何转换,并在“完整”回调中删除元素。下面是一个示例,您可以直接转储到一个小提琴,只需要引用jQuery。
我还在'load'事件的监听器中包含内容,直到iFrame被加载为最初询问的问题才进行淡入淡出。
// plugin your URL here
var url = 'http://jquery.com';
// create the iFrame, set attrs, and append to body
var ifr = $("<iframe>")
.attr({
"src": url,
"width": 300,
"height": 100,
"frameborder": 0
})
.addClass("dl-frame")
.appendTo($('body'))
;
// log to show its part of DOM
console.log($(".dl-frame").length + " items found");
// create listener for load
ifr.one('load', function() {
console.log('iframe is loaded');
// call $ fadeOut to fade the iframe
ifr.fadeOut(3000, function() {
// remove iframe when fadeout is complete
ifr.remove();
// log after, should no longer exist in DOM
console.log($(".dl-frame").length + " items found");
});
});
答案 8 :(得分:1)
试试这个:
注意 :您应该在同一个域中。
var url = 'http://mywebsite.com/my_export_route',
iFrameElem = $('body')
.append('<iframe class="dl-frame" src="' + url + '" width="0" height="0" frameborder="0"></iframe>')
.find('.dl-frame').get(0),
iDoc = iFrameElem.contentDocument || iFrameElem.contentWindow.document;
$(iDoc).ready(function (event) {
console.log('iframe ready!');
// do stuff here
});
答案 9 :(得分:1)
如果你从iframe进行文件下载,那么load事件就不会发生了:)我一周前这样做了。解决此问题的唯一方法是使用标记调用下载代理脚本,然后通过cookie返回该标记,然后加载该文件。分钟,你需要在页面上有一个setInterval,它会监视那个特定的cookie。
// Jst to clearyfy
var token = new Date().getTime(); // ticks
$('<iframe>',{src:"yourproxy?file=somefile.file&token="+token}).appendTo('body');
var timers = [];
timers[timers.length+1] = setInterval(function(){
var _index = timers.length+1;
var cookie = $.cooke(token);
if(typeof cookie != "undefined"){
// File has been downloaded
$.removeCookie(token);
clearInterval(_index);
}
},400);
在您的代理脚本中添加cookie,其名称设置为发送的字符串,以及令牌url参数。
答案 10 :(得分:1)
如果您控制生成excel的服务器中的脚本或您发送给iframe的任何内容,为什么不放置UID标志并将其存储在值为0的会话中,所以...当创建iframe时服务器和服务器调用脚本只需将UID标志设置为1,当脚本完成时(iframe将被加载)只需将其置于2。
然后你只需要一个计时器和一个定期的AJAX调用服务器来检查UID标志......如果它被设置为0,那么这个过程没有启动,如果它是1文件正在创建,最后如果是2,则该过程已经结束。
你怎么看?如果您需要有关此方法的更多信息,请询问。答案 11 :(得分:1)
您可以使用$(iframe).load(function() {...});
对于PDF文件或其他富媒体,您可以使用以下库: http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/
注意:您将需要JQuery UI
答案 12 :(得分:1)
我能想到两种解决方案。要么你有PHP将它发布到一个MySQL表,其中前端将从使用AJAX调用中获取信息来检查生成的进度。使用访问页面时生成的某些唯一键是多个人同时生成excel文件的理想选择。
另一个解决方案是使用nodejs&amp;然后在PHP中使用cURL或socket到nodejs服务发布excel文件的进度。然后,当在nodejs中从PHP接收更新时,您只需为正确的套接字编写excel文件的进度。这将削减一些浏览器支持。除非你使用外部库来为它提供几乎所有浏览器的websocket支持。版本
希望这个答案有所帮助。我去年有同样的问题。结束了AJAX轮询,即时发布PHP后期进度。