ブラウザがファイルのダウンロードを受信したときに検出する


488

ユーザーが動的に生成されたファイルをダウンロードできるページがあります。生成に時間がかかるので、「待機中」のインジケーターを表示したいと思います。問題は、ブラウザーがファイルを受信したことを検出する方法がわからないため、インジケーターを非表示にできることです。

サーバーにPOSTする非表示のフォームでリクエストを作成し、その結果の非表示のiframeをターゲットにしています。これは、ブラウザウィンドウ全体を結果で置き換えないためです。ダウンロードが完了したときに発生することを期待して、iframeで「ロード」イベントをリッスンします。

「Content-Disposition:attachment」ヘッダーをファイルとともに返します。これにより、ブラウザーに「保存」ダイアログが表示されます。ただし、ブラウザはiframeで「ロード」イベントを発生させません。

私が試したアプローチの1つは、マルチパート応答を使用することです。そのため、空の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」イベントを発生させ、ダウンロード可能なファイルの「保存」ダイアログを表示します。しかし、IEとSafariでは失敗します。IEは「load」イベントを起動しますが、ファイルをダウンロードしません。Safariはファイルをダウンロードし(名前とコンテンツタイプが間違っています)、「load」イベントを起動しません。

別の方法として、ファイルの作成を開始する呼び出しを行い、準備ができるまでサーバーをポーリングし、作成済みのファイルをダウンロードするという方法もあります。しかし、サーバー上に一時ファイルを作成することは避けたいです。

誰かがより良いアイデアを持っていますか?


4
IEのどのバージョンもmultipart / x-mixed-replaceをサポートしていません。
EricLaw 2009

エリックさん、ありがとうございます。そのアプローチでもう時間を無駄にすることはありません。
JW。

信頼できる方法は、サーバープッシュ通知(SignalR for ASP.NETの人々)だけのようです。
dudeNumber4 2015年

1
bennadel.com/blog/…- これはシンプルなソリューションです
Mateen

1
@mateenありがとうおい!とてもシンプル
Fai Zal Dong

回答:


451

考えられる解決策の 1つは、クライアントでJavaScriptを使用することです。

クライアントアルゴリズム:

  1. ランダムな一意のトークンを生成します。
  2. ダウンロードリクエストを送信し、GET / POSTフィールドにトークンを含めます。
  3. 「待機」インジケータを表示します。
  4. タイマーを開始し、毎秒、「fileDownloadToken」という名前のCookie(またはユーザーが決めたもの)を探します。
  5. Cookieが存在し、その値がトークンと一致する場合は、「待機」インジケーターを非表示にします。

サーバーアルゴリズム:

  1. リクエストでGET / POSTフィールドを探します。
  2. 空ではない値がある場合は、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
    );
}

4
素晴らしいアイデアです。jQuery/ C#を使用した複数のファイルのダウンロードに関するこの回答の基本フレームワークとして使用しました
Greg

7
他の人に向けて:document.cookiesにdownloadTokenが含まれていない場合は、Cookieパスを確認してください。私の場合、パスはデフォルトで空白でしたが、サーバー側でパスを「/」に設定する必要がありました(Javaではcookie.setPath( "/")など)。しばらくの間、問題は特別な「localhost」ドメインのCookie処理(stackoverflow.com/questions/1134290/…)だと思っていましたが、結局それは問題ではありませんでした。他の人にとっては一読の価値があるかもしれませんが。
jlpp 2014

2
@bulltoriousあなたのソリューションをより深く掘り下げる前に、クロスドメインのファイルダウンロードリクエストで動作するかどうかを考えます。それはそうなると思いますか、それともクッキーの制限はそれを危険にさらしますか?
kiks73 14

5
すばらしい-ファイルのダウンロードの一部としてCookieを含めることができたのは、100年後のことです。ありがとうございました!!
freefaller 2015

8
他の人が指摘したように、このソリューションはサーバーがファイル時間を準備するのを待つという問題の一部しか解決しません。問題のもう1つの部分は、ファイルのサイズと接続速度によってはかなり大きくなる可能性がありますが、実際にクライアントでファイル全体を取得するのにかかる時間です。そして、それはこのソリューションでは解決されません。
AsGoodAsItGet 2016年

27

非常に単純な(そして不完全な)1行のソリューションは、window.onblur()イベントを使用して読み込みダイアログを閉じることです。もちろん、時間がかかりすぎてユーザーが何か他のこと(メールの閲覧など)を行うことにした場合は、読み込みダイアログが閉じます。


これは、onbeforeunloadありがとうを使用してトリガーされたファイルダウンロードのロードオーバーレイを取り除くのに理想的なシンプルなアプローチです。
wf4

5
これはすべてのブラウザーで機能するわけではありません(Safari、一部のIEバージョンなど、ダウンロードワークフローの一部として現在のウィンドウを離れたりぼかしたりしないブラウザーもあります)。
hiattp 2014年

4
Chromeなどのブラウザは、この条件が失敗するファイルを自動ダウンロードします。
ラッキー

@Luckyはデフォルトでのみです。Chromeのユーザーがダウンロードを保存する場所を指定し、ダイアログボックスを表示することは完全に可能です
ESR

2
tabchangeまたはウィンドウの外のアクションでぼかしをアクティブにするため、悪い考え
Michael

14

古いスレッド、私は知っています...

しかし、グーグルによってここに導かれているそれらは私の解決策に興味があるかもしれません。非常にシンプルですが、信頼性があります。実際の進行状況メッセージを表示することができます(既存のプロセスに簡単にプラグインできます)。

処理するスクリプト(私の問題は、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
しかし、ユーザーが複数のウィンドウまたはダウンロードを開いている場合はどうなりますか?また、サーバーへの冗長な呼び出しが発生します
Yuki

3
1人のユーザーからの複数の接続がある場合、session_start()がユーザーのセッションをロックし、他のすべてのプロセスがアクセスできないようにするため、他の接続が終了するのをすべて待機します。
HonzaKuchař14年

2
.each()イベントの登録に使用する必要はありません。ただ言う$('a.download').click()
robisrob 2015年

内部のコードを評価しないでくださいsetTimeout('getstatus()', 1000);。直接FNを使用してください:setTimeout(getstatus, 1000);
ロコC. Buljan

11

以下を使用してblobをダウンロードし、ダウンロード後にオブジェクト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;
}

ファイルのダウンロードダイアログを閉じると、ウィンドウにフォーカスが戻り、フォーカスイベントがトリガーされます。


それでもウィンドウの切り替えと戻りの問題があり、モーダルが非表示になります。
dudeNumber4 2015年

9
下部トレイにダウンロードするChromeのようなブラウザーは、ウィンドウをぼかしたり、フォーカスを再調整したりすることはありません。
コールマン

10

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">

ダウンロードをクリックするたびに、レポートが作成しているメッセージが表示されますしばらくお待ちください...


2
ユーザーがウィンドウをクリックした場合はどうなりますか?
Tom Roggero、2015年

これはまさに私が探していたものです、本当にありがとうございます!!
Sergio

私の場合、hide()が呼び出されていません
Prashant Pimpale

8

強引な回答で説明されているのと同様の手法を実装する単純なJavaScriptクラスを作成しまし。私はそれがここの誰かに役立つことを願っています。GitHubプロジェクトはresponse-monitor.jsと呼ばれます

デフォルトでは、待機インジケーターとしてspin.jsを使用しますが、カスタムインジケーターを実装するための一連のコールバックも提供します。

JQueryはサポートされていますが、必須ではありません。

注目すべき機能

  • シンプルな統合
  • 依存関係なし
  • jQueryプラグイン(オプション)
  • Spin.js統合(オプション)
  • イベントを監視するための構成可能なコールバック
  • 複数の同時リクエストを処理します
  • サーバー側のエラー検出
  • タイムアウト検出
  • クロスブラウザ

使用例

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

私はパーティーに非常に遅れていますが、他の誰かが私の解決策を知りたい場合は、ここに置きます。

私はこの正確な問題に本当に苦労しましたが、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はダウンロードスクリプトが「ロード」されていると認識しません。ブラウザーがファイルを受信したときにこれが必ずしも検出されない可能性があることはわかっていますが、あなたの問題は私のように聞こえました。


5

動的に生成しているファイルをストリーミングしていて、サーバーからクライアントへのリアルタイムのメッセージングライブラリも実装されている場合は、クライアントに非常に簡単に警告できます。

私が気に入って推奨するサーバーからクライアントへのメッセージングライブラリは、Socket.io(Node.js経由)です。サーバースクリプトが完了した後、ダウンロード用にストリーミングされるファイルを生成すると、そのスクリプトの最後の行でメッセージをSocket.ioに送信して、クライアントに通知を送信できます。クライアントでは、Socket.ioはサーバーから送信された受信メッセージをリッスンし、ユーザーがそれらを操作できるようにします。他の方法よりもこの方法を使用する利点は、ストリーミングの完了後に「真の」終了イベントを検出できることです。

たとえば、ダウンロードリンクがクリックされた後にビジーインジケーターを表示し、ファイルをストリーミングし、ストリーミングスクリプトの最後の行でサーバーからSocket.ioにメッセージを送信し、クライアントで通知をリッスンし、通知を受け取ることができます。ビジーインジケーターを非表示にしてUIを更新します。

この質問への回答を読んでいるほとんどの人は、このタイプの設定がないかもしれないことに気づきますが、私はこの正確な解決策を自分のプロジェクトで大きな効果に使用しており、それは素晴らしい働きをします。

Socket.ioは、インストールと使用が非常に簡単です。詳細:http : //socket.io/


5

「ブラウザがファイルのダウンロードを受信したことを検出する方法」
私はその構成で同じ問題に直面しました:
struts 1.2.9
jquery-1.3.2。
jquery-ui-1.7.1.custom
IE 11
Java 5


Cookieを使用した私のソリューション:
-クライアント側:
フォームを送信するときに、JavaScript関数を呼び出してページを非表示にし、待機中のスピナーをロードします

function loadWaitingSpinner(){
... hide your page and show your spinner ...
}

次に、Cookieがサーバーから送信されているかどうかを500ミリ秒ごとにチェックする関数を呼び出します。

function checkCookie(){
    var verif = setInterval(isWaitingCookie,500,verif);
}

Cookieが見つかった場合は、500 ミリ秒ごとにチェックを停止し、Cookieを期限切れにして、関数を呼び出してページに戻り、待機中のスピナーを削除します(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がクライアントに送信されます。

Cookie waitCookie = new Cookie("waitingCookie", "done");
response.addCookie(waitCookie);

私は誰かを助けたいと思います!


それは完全に動作します。この美しいサンプルをありがとう。
Sedat Kumcu

4

ユーザーがファイルの生成をトリガーすると、その「ダウンロード」に一意のIDを割り当て、数秒ごとに更新(またはAJAXでチェック)するページにユーザーを送信できます。ファイルが完成したら、同じ一意のIDで保存して...

  • ファイルの準備ができている場合は、ダウンロードしてください。
  • ファイルの準備ができていない場合は、進行状況を表示します。

次に、iframe /待機/ブラウザウィンドウの混乱全体をスキップできますが、非常にエレガントなソリューションがあります。


これは、前述の一時ファイルによるアプローチのように聞こえます。私の考えが不可能であることがわかった場合、私はこのようなことをするかもしれませんが、それを避けたいと思っていました。
JW。

3

ファイルを生成してサーバーに保存したくない場合は、ステータスを保存しますか?たとえば、進行中のファイル、完了したファイルなど。「待機中」ページはサーバーをポーリングして、ファイル生成がいつ完了したかを知ることができます。ブラウザがダウンロードを開始したかどうかは確かにわかりませんが、ある程度の自信はあります。


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でテスト済み。


2

私の経験では、これを処理するには2つの方法があります。

  1. ダウンロードに有効期間の短いCookieを設定し、JavaScriptにその存在を継続的にチェックさせます。実際の問題は、Cookieの有効期間を正しく設定することだけです。短すぎるとJSがそれを逃し、長すぎると、他のダウンロードのダウンロード画面がキャンセルされる可能性があります。発見時にJSを使用してCookieを削除すると、通常これが修正されます。
  2. fetch / XHRを使用してファイルをダウンロードします。ファイルのダウンロードがいつ完了するか正確にわかるだけでなく、XHRを使用している場合は、進行状況イベントを使用して進行状況バーを表示できます。IE / EdgeのmsSaveBlobとFirefox / Chromeのダウンロードリンク(このような)を使用して、結果のblobを保存します。この方法の問題は、iOS Safariがblobのダウンロードを正しく処理していないように見えることです。FileReaderを使用してblobをデータURLに変換し、それを新しいウィンドウで開くことができますが、ファイルは開かれ、保存されません。

2

こんにちは、私はトピックが古いことを知っていますが、私は他の場所で見た解決策を残し、それはうまくいきました:

/**
 *  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");

1

ドキュメントにあるのではなく、保存されているファイルをダウンロードした場合、ダウンロードが現在のドキュメントの範囲ではなく、ブラウザーでの別個のプロセスであるため、ダウンロードがいつ完了したかを判断する方法はありません。


8
私は明確にすべき-私は"mはあまりにもダウンロードする際に関係していないが完了。私はちょうどその時、ダウンロード開始を識別することができた場合、それは十分だろう。
。JW

0

問題は、ファイルが生成されている間は「待機」インジケーターがあり、ファイルがダウンロードされると通常に戻ることです。私がこれを行うのが好きな方法は、非表示のiFrameを使用して、フレームのonloadイベントをフックし、ダウンロードが開始したときにページに通知することです。しかし onloadイベント(添付ファイルのヘッダートークンと同じように)ファイルのダウンロードのためにIEで火をしません。サーバーのポーリングは機能しますが、余分な複雑さが嫌いです。だからここに私がやっていることがあります:

  • 通常どおり、非表示のiFrameをターゲットにします。
  • コンテンツを生成します。2分以内に絶対タイムアウトでキャッシュします。
  • JavaScriptリダイレクトを呼び出し元のクライアントに送り返し、基本的にはジェネレーターページをもう一度呼び出します。注:通常のページのように動作するため、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'");
        }
    }

0

ダウンロードダイアログが表示されるまでメッセージまたはローダーgifのみを表示する場合の簡単な解決策は、メッセージを非表示のコンテナーに入れ、ダウンロードするファイルを生成するボタンをクリックすると、コンテナーが表示されるようにすることです。次に、jqueryまたはjavascriptを使用してボタンのfocusoutイベントをキャッチし、メッセージを含むコンテナーを非表示にします


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


0

このJava / Springの例は、ダウンロードの終了を検出し、その時点で「Loading ...」インジケーターを非表示にします。

アプローチ: JS側で、最大有効期限が2分のCookieを設定し、毎秒ポーリングして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;
}

CookieはJSスクリプトによって作成されますが、コントローラーによって更新されません。元の値(0)を維持します。ページを更新せずにCookie値を更新するにはどうすればよいですか?
シェスカキー

それは奇妙です-名前が正確に正しいことを確認できますか?名前が一致する場合、Cookieを上書きします。知らせてください
遺伝子b。

元の値は0ではありません。JSで設定された元の値は2分です。サーバはで変更することになっていることを新しい値は0である
遺伝子B。

また、これを行っていますか? myCookie.setPath("/"); response.addCookie(myCookie);
遺伝子b。

(なんらかの理由で)response.getOutputStream()を実行する前にCookieを追加する必要があることを理解しました(ダウンロードファイルを追加するための応答出力ストリームを取得する)、その手順の後で実行した場合は考慮されませんでした
シェスカキー

0

PrimefacesもCookieポーリングを使用しています

https://github.com/primefaces/primefaces/blob/32bb00299d00e50b2cba430638468a4145f4edb0/src/main/resources/META-INF/resources/primefaces/core/core.js#L458

    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);
        }
    },

-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);

これは情報が不足していて、「ロードを非表示にするタイミング」の問題を解決しません。
トムロジェロ2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.