547

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

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

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

私が試した 1 つのアプローチは、multi-part応答を使用することです。そのため、空の 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"ダイアログを表示します。しかし、Internet ExplorerSafariでは失敗します。Internet Explorer は "load" イベントを発生させますが、ファイルをダウンロードしません。Safariはファイルを (間違った名前とコンテンツ タイプで) ダウンロードし、"load"イベントを発生させません。

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

私は何をすべきか?

4

24 に答える 24

487

考えられる解決策の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
    );
}
于 2010-11-12T20:54:14.573 に答える
28

非常に単純な (そして不十分な) 1 行の解決策は、window.onblur()イベントを使用して読み込みダイアログを閉じることです。もちろん、時間がかかりすぎて、ユーザーが何か他のこと (電子メールを読むなど) を行うことにした場合、読み込みダイアログは閉じます。

于 2011-06-04T17:25:51.117 に答える
18

このソリューションは非常にシンプルですが、信頼性があります。また、実際の進行状況メッセージを表示することができます (既存のプロセスに簡単にプラグインできます)。

処理するスクリプト (私の問題は、HTTP 経由でファイルを取得し、それらを ZIP として配信することでした) は、セッションにステータスを書き込みます。

ステータスは毎秒ポーリングされて表示されます。それだけです (OK、そうではありません。多くの詳細 (同時ダウンロードなど) を処理する必要がありますが、開始するのに適した場所です ;-))。

ダウンロードページ:

<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
?>
于 2010-09-10T22:16:21.867 に答える
15

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

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

于 2014-10-15T13:25:25.490 に答える
11

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

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

于 2013-03-26T13:46:08.573 に答える
4

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

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

次に、iframe/waiting/browserwindow の混乱全体をスキップできますが、非常にエレガントなソリューションが得られます。

于 2009-07-09T21:37:44.520 に答える
3

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

于 2009-07-09T21:48:31.360 に答える
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 イベントが発生します。その後、待機メッセージが閉じられ、ファイルのダウンロードが開始されます。Internet Explorer 7および Firefoxでテストされています。

于 2009-07-21T22:41:39.380 に答える
1

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

于 2009-07-09T20:54:38.490 に答える
0

問題は、ファイルの生成中に「待機中」のインジケーターを表示し、ファイルがダウンロードされると通常の状態に戻ることです。これを行うのが好きな方法は、非表示のiFrameを使用し、フレームのonloadイベントをフックして、ダウンロードの開始時にページに通知することです。

ただし、ファイルのダウンロード(添付ファイルヘッダートークンなど)の場合、InternetExplorerではonloadは起動しません。サーバーのポーリングは機能しますが、私は余分な複雑さが嫌いです。だからここに私がすることです:

  • 通常どおり、非表示のiFrameをターゲットにします。
  • コンテンツを生成します。2分で絶対タイムアウトでそれをキャッシュします。
  • JavaScriptリダイレクトを呼び出し元のクライアントに送り返し、基本的にジェネレーターページをもう一度呼び出します。:これにより、Internet Explorerで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'");
        }
    }
于 2009-09-13T04:00:53.837 に答える
0

ブロブを含むXMLHttpRequestがオプションでない場合は、新しいウィンドウでファイルを開き、そのウィンドウ本体に要素が間隔を置いて取り込まれているかどうかを確認できます。

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 an error page configured in your application
        // for failed requests, check for those DOM elements.
    }
}, 1000)
// Better if you specify the maximum number of intervals

于 2017-12-03T18:10:24.780 に答える
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);
        }
    },
于 2020-04-06T08:46:36.233 に答える
-2

ボタン/リンクがクリックされたときに iframe を作成し、これを body に追加します。

$('<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);
于 2014-05-27T09:45:07.107 に答える