動的に生成されたファイルをユーザーがダウンロードできるようにするページがあります。生成には長い時間がかかるので、「待機中」のインジケーターを表示したいのですが。問題は、ブラウザがファイルを受信したことを検出する方法がわからないため、インジケータを非表示にできることです。
私はリクエストを隠しフォームで行っています。それはサーバーにPOSTし、隠されたiframeをターゲットにします。これは、ブラウザウィンドウ全体を結果に置き換えないためです。ダウンロードが完了したときに発生することを期待して、私はiframeの "load"イベントをリッスンします。
ファイルに「Content-Disposition:attachment」ヘッダーを返すと、ブラウザに「保存」ダイアログが表示されます。しかし、ブラウザは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では失敗します。 IEは "load"イベントを発生させますがファイルをダウンロードしません、そしてSafariはファイルを(間違った名前とcontent-typeで)ダウンロードし、そして "load"イベントを発生させません。
別の方法としては、ファイルの作成を開始するために呼び出しを行い、準備ができるまでサーバーをポーリングしてから、作成済みのファイルをダウンロードするという方法があります。しかし、サーバー上に一時ファイルを作成するのは避けたいと思います。
誰かがより良い考えを持っていますか?
1つの 可能な解決策 では、クライアントで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行の解決策は、ロード中のダイアログを閉じるためにwindow.onblur()
イベントを使用することです。もちろん、時間がかかりすぎてユーザーが何か他のことをすることにした場合(Eメールを読むように)、ロードダイアログは閉じます。
古いスレッド、私は知っている...
しかし、グーグルによってここに導かれているものは私の解決策に興味があるかもしれません。それは非常にシンプルでありながら信頼性があります。そしてそれは本当の進捗メッセージを表示することを可能にします(そして既存のプロセスに簡単に接続することができます):
処理するスクリプト(私の問題は、http経由でファイルを取得してZipとして配信することでした)がセッションにステータスを書き込みます。
ステータスは1秒ごとにポーリングされて表示されます。これで全部です(そう、そうではありません。あなたは多くの詳細を気にかけなければなりません[例えば同時ダウンロード]、しかしそれは始めるのに良い場所です;-)).
ダウンロードページ:
<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
?>
bLOBをダウンロードし、ダウンロード後にobject-urlを取り消すには、次のようにします。それはクロムと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;
}
ファイルダウンロードダイアログが閉じられると、ウィンドウはフォーカスを取り戻し、フォーカスイベントが発生します。
私はbulltorious answer で説明されているものに似たテクニックを実装する簡単なJavaScriptクラスを書きました。それがここの誰かに役立つことを願っています。 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 フォルダを確認してください。
動的に生成しているファイルをストリーミングしており、リアルタイムのサーバー間メッセージングライブラリも実装されている場合は、クライアントに非常に簡単に警告できます。
私が好きでお勧めするサーバーからクライアントへのメッセージングライブラリはSocket.ioです(Node.js経由)。あなたのサーバースクリプトがダウンロードのためにストリーミングされているファイルを生成し終えた後に、そのスクリプトのあなたの最後の行はクライアントに通知を送るSocket.ioにメッセージを出すことができます。クライアントでは、Socket.ioはサーバーから送信された着信メッセージをリッスンし、ユーザーがそれらを処理できるようにします。この方法を他の方法よりも使用することの利点は、ストリーミングが終了した後に「本当の」終了イベントを検出できることです。
たとえば、ダウンロードリンクをクリックした後にビジーインジケータを表示したり、ファイルをストリーミングしたり、ストリーミングスクリプトの最後の行でサーバーからSocket.ioにメッセージを送信したり、クライアントの通知を聞いたり、通知を受け取ることができます。ビジーインジケータを非表示にしてUIを更新します。
私はこの質問に対する答えを読んでいる人のほとんどがこの種の設定を持っていないかもしれないことを理解していますが、私は自分のプロジェクトで大きな効果を得るためにこの厳密な解決策を使いました。
Socket.ioは、インストールと使用が非常に簡単です。もっと見る: http://socket.io/
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
<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">
ダウンロードをクリックするたびに、レポートが作成中のメッセージが表示されます。しばらくお待ちください...
私はパーティーに非常に遅れています、しかし私が他の誰かが私の解決策を知りたいと思うならば、私はこれをここに置きます:
私はこの正確な問題に真剣に取り組んでいましたが、iframeを使った実行可能な解決策を見つけました(私は知っています、それはひどいのですがそれは私が持っていた単純な問題に対して働きます)
私はファイルを生成しそれをダウンロードした別のphpスクリプトを起動するhtmlページを持っていました。 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()を使用している場合は、スクリプトをWebページ以外のものに変更しているため、jqueryはダウンロードスクリプトを「ロード済み」と認識することはありません。ブラウザがファイルを受信したことを必ずしも検出しているとは限りませんが、あなたの問題は私のものと同じように聞こえました。
ユーザーがファイルの生成を開始したら、その「ダウンロード」に一意のIDを割り当てるだけで、数秒ごとに更新(またはAJAXで確認)するページにユーザーを送信できます。ファイルが完成したら、同じ一意のIDで保存してください。
それからあなたは全体のiframe/waiting/browserwindow混乱をスキップすることができます、まだ本当に優雅な解決策を持っています。
ファイルを生成してサーバーに保存したくない場合は、ステータスを保存しても構いませんか。進行中のファイル、ファイル完了?あなたの「待っている」ページは、ファイル生成がいつ完了したかを知るためにサーバをポーリングすることができます。ブラウザがダウンロードを開始したことを確信できないかもしれませんが、ある程度自信があるでしょう。
私はちょうどこれとまったく同じ問題を抱えていました。一時ファイルをたくさん生成していたので、私の解決策は一時ファイルを使うことでした。フォームは次のものと共に送信されます。
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でテスト済み。
ダウンロードダイアログが表示されるまでメッセージまたはローダーgifのみを表示したい場合の簡単な解決策は、メッセージを隠しコンテナに入れ、ダウンロードするファイルを生成するボタンをクリックするとコンテナを表示することです。次に、jqueryまたはjavascriptを使用してボタンのfocusoutイベントをキャッチし、メッセージを含むコンテナを非表示にします。
問題は、ファイルが生成されている間は「待機中」のインジケータが表示され、ファイルがダウンロードされると通常の状態に戻ることです。私がこれをするのが好きな方法は、隠されたiFrameを使って、ダウンロードがいつ始まるかを私のページに知らせるためにフレームのonloadイベントをフックすることです。 BUTonloadは、ファイルのダウンロード時にIEで起動しません(添付ファイルのヘッダートークンの場合と同様)。サーバーのポーリングは機能しますが、私は余分な複雑さを嫌います。だからここに私がしていることです:
免責事項、キャッシングが追加される可能性があるため、忙しいサイトでこれを行わないでください。しかし、実際には、長時間実行されているプロセスでビジー状態になっているサイトがとにかくスレッドを使い果たしてしまう場合があります。
これが、背後にあるコードがどのようなものかを示したものです。
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'");
}
}
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
ドキュメント内にあるのではなく保存されたファイルをダウンロードした場合、それは現在のドキュメントの範囲内ではなく、ブラウザ内の別のプロセスであるため、ダウンロードがいつ完了したかを判断する方法はありません。
「ブラウザがファイルのダウンロードを受信したことを検出するにはどうすればよいですか?」
その設定でも同じ問題に直面しました。
支柱1.2.9
jquery-1.3.2。
jquery-ui-1.7.1.custom
IE 11
Java 5
クッキーを使った私の解決策:
- クライアント側:
フォームを送信するときは、JavaScript関数を呼び出してページを非表示にし、待機中のスピナーをロードしてください。
function loadWaitingSpinner(){
... hide your page and show your spinner ...
}
次に、クッキーがサーバーから来ているかどうかを500msごとにチェックする関数を呼び出します。
function checkCookie(){
var verif = setInterval(isWaitingCookie,500,verif);
}
クッキーが見つかった場合は、500ミリ秒ごとにチェックを中止し、クッキーを期限切れにして関数を呼び出してページに戻り、待機中のスピナーを削除します(removeWaitingSpinner())。別のファイルをもう一度ダウンロードできるようにするには、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 ...
}
- サーバー側:
サーバープロセスの最後に、レスポンスにcookieを追加してください。あなたのファイルがダウンロードの準備ができているときそのクッキーはクライアントに送られるでしょう。
Cookie waitCookie = new Cookie("waitingCookie", "done");
response.addCookie(waitCookie);
私は誰かを助けたいと思っています!