Domanda

Ho una pagina che consente all'utente di scaricare un file generati dinamicamente. Ci vuole molto tempo per generare, quindi mi piacerebbe mostrare un indicatore di "attesa". Il problema è che non riesco a capire come rilevare quando il browser ha ricevuto il file, in modo da poter nascondere l'indicatore.

Sto facendo la richiesta in una forma nascosta, quali posti al server, e si rivolge a un iframe nascosto per i suoi risultati. Questa è quindi non sostituire l'intera finestra del browser con il risultato. Ascolto per un evento "carico" sul iframe, nella speranza che possa sparare quando il download è completato.

torno un "Content-Disposition: attachment" intestazione con il file, che fa sì che il browser per visualizzare la finestra di dialogo "Salva". Ma il browser non viene generato un evento di "carico" nel iframe.

Un approccio che ho provato è usare una risposta multi-parte. Quindi sarebbe inviare un file HTML vuoto, così come il file scaricabile in allegato. Ad esempio:

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

Questo funziona in Firefox; riceve il file HTML vuoto, genera l'evento "carico", poi mostra la finestra di dialogo "Save" per il file scaricabile. Ma non riesce su IE e Safari; IE genera l'evento "carico", ma non scarica il file, e Safari scarica il file (con il nome sbagliato e Content-Type), e non genera l'evento "carico".

Un approccio diverso potrebbe essere quello di effettuare una chiamata per avviare la creazione del file, quindi interroga il server fino a quando è pronto, quindi scaricare il file già creato. Ma io preferirei evitare di creare i file temporanei sul server.

Qualcuno ha un'idea migliore?

È stato utile?

Soluzione

possibile soluzione utilizza JavaScript sul client.

L'algoritmo di client:

  1. Genera un token univoco casuale.
  2. Invia la richiesta di download, e includono il token in un campo GET / POST.
  3. Visualizza l'indicatore "in attesa".
  4. avviare un timer, e ogni secondo o giù di lì, cercare un cookie denominato "fileDownloadToken" (o qualunque cosa tu decida).
  5. Se il cookie esiste, e il suo valore corrisponde il token, nascondere l'indicatore di "attesa".

L'algoritmo del server:

  1. Cercare il campo GET / POST nella richiesta.
  2. Se ha un valore non vuoto, goccia un cookie (ad esempio "fileDownloadToken"), e impostare il valore al valore del token.

il codice sorgente del client (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;
}

codice del server di esempio (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();

Dove:

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

Altri suggerimenti

Una soluzione di una linea molto semplice (e zoppo) è quello di utilizzare l'evento window.onblur() per chiudere la finestra di caricamento. Naturalmente, se ci vuole troppo tempo e l'utente decide di fare qualcosa di diverso (come le email lettura) la finestra di caricamento si chiude.

thread vecchio, lo so ...

ma quelli, che sono portato qui da Google potrebbe essere interessato a mia soluzione. è molto semplice, ma affidabile. e consente di visualizzare i messaggi reali progressi (e può essere facilmente collegato a processi esistenti):

lo script che elabora (il mio problema era: il recupero di file via http e consegnarli come zip) scrive lo stato della sessione

.

lo stato viene interrogato e visualizzato ogni secondo. Questo è tutto (. ok, la sua non si deve prendere cura di un sacco di dettagli [es] download simultanei, ma è un buon punto di partenza; -)).

il di Download:

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

Io uso il seguente per scaricare blob e revocare l'oggetto-url dopo il download. funziona in Chrome e 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;
}

dopo la finestra di download del file viene chiuso, la finestra ottiene la sua attenzione torna così l'evento di messa a fuoco viene attivato.

Ho scritto una semplice classe JavaScript che implementa una tecnica simile a quella descritta nel bulltorious risposta . Spero che possa essere utile a qualcuno qui. Il progetto si chiama GitHub risposta-monitor.js

Per impostazione predefinita utilizza spin.js come l'indicatore di attesa ma fornisce anche un set di callback per l'attuazione di un indicatore personalizzato.

JQuery è supportato ma non richiesta.

Le caratteristiche notevoli

  • Integrazione semplice
  • Non ci sono dipendenze
  • JQuery plug-in (opzionale)
  • Spin.js integrazione (opzionale)
  • callback configurabili per gli eventi di monitoraggio
  • Gestisce più richieste simultanee
  • il rilevamento degli errori lato server
  • rilevazione Timeout
  • Cross browser

Utilizzo Esempio

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>

Client (plain 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

Client (JQuery)

$('.my_anchors').ResponseMonitor();
$('#my_form').ResponseMonitor({timeout: 20});

Client con callback (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);

Server (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

Per ulteriori esempi controllare il href="https://github.com/witstep/response-monitor.js/tree/master/examples" rel="nofollow noreferrer"> cartella sulla repository.

In base a esempio di Elmer Ho preparato la mia soluzione. Dopo elementi click con definito Scarica di classe che permette di mostrare un messaggio personalizzato sullo schermo. Ho usato messa a fuoco grilletto per nascondere il messaggio.

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>

Ora si dovrebbe realizzare qualsiasi elemento per scaricare:

<a class="download" href="file://www.ocelot.com.pl/prepare-report">Download report</a>

o

<input class="download" type="submit" value="Download" name="actionType">

Dopo ogni Download fa clic si vedrà il messaggio il vostro rapporto è la creazione in corso ...

Quando l'utente attiva la generazione del file, si potrebbe semplicemente assegnare un ID univoco a quella "download", e inviare l'utente a una pagina che rinfresca (o assegni con AJAX) ogni pochi secondi. Una volta che il file è finito, salvarlo con lo stesso ID univoco e ...

  • Se il file è pronto, fare il download.
  • Se il file non è pronto, mostrare i progressi.

Poi si può saltare l'intero iframe / in attesa / browserwindow pasticcio, ma avere una soluzione davvero elegante.

Se non si desidera generare e memorizzare il file sul server, siete disposti a memorizzare lo stato, ad esempio, file-in-progress, file completo? Il tuo "attesa" pagina potrebbe interrogare il server per sapere quando la generazione del file è completo. Non si sa per certo che il browser ha iniziato il download, ma si sarebbe avere una certa fiducia.

Sono molto in ritardo alla festa, ma metto questo qui se qualcun altro vorrebbe sapere la mia soluzione:

ho avuto una vera e propria lotta con questo problema esatto, ma ho trovato una soluzione praticabile utilizzando iframe (lo so, lo so. E 'terribile, ma funziona per un semplice problema che ho avuto)

ho avuto una pagina html che ha lanciato uno script PHP separato che ha generato il file e poi scaricato. Nella pagina HTML, ho usato il seguente jquery nell'intestazione html (è necessario includere una libreria jQuery così):

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

Il your_download_script.php, hanno la seguente:

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*
}

Per rompere questo giù, jquery prima lancia lo script php in un iframe. L'IFRAME viene caricato una volta che viene generato il file. Poi jquery lancia lo script di nuovo con una variabile di richiesta di raccontare lo script per scaricare il file.

La ragione per cui non si può fare il download e generazione di file tutto in una volta è dovuta alla intestazione funzione PHP (). Se si utilizza header (), si sta modificando lo script per qualcosa di diverso da una pagina web e jquery non riconoscerà mai lo script download come viene 'caricato'. So che questo potrebbe non essere necessariamente rilevare quando un browser riceve un file, ma il problema sembrava simile alla mia.

Ho appena avuto questo stesso problema. La mia soluzione era quella di utilizzare i file temporanei dal momento che stava generando una serie di file temporanei già. Il modulo viene inviato con:

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

Alla fine dello script che genera il file che ho:

header('Refresh: 0;url=fetch.php?token=' . $token);
echo '<html></html>';

Questo farà sì che l'evento carico sul iframe per essere licenziato. Quindi il messaggio di attesa viene chiusa e il download del file avrà inizio. Testato su IE7 e Firefox.

Se si dispone di scaricare un file, che viene salvato, invece di essere nel documento, non c'è modo per determinare quando il download è completato, dato che non è nel campo di applicazione del documento corrente, ma un processo separato in il browser.

"Come rilevare quando il browser riceve il download di file?"
Ho affrontato lo stesso problema con che configurazione:
puntoni 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1.custom
IE 11
java 5


La mia soluzione con un biscotto:
- lato client:
Al momento della presentazione del modulo, chiamare la funzione JavaScript per nascondere la tua pagina e caricare il filatore di attesa

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

Poi, chiamare una funzione che controllerà ogni 500ms se un cookie è venuta da server.

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

Se non si trova il cookie, interrompere il controllo ogni 500ms, scade il cookie e chiamare la funzione di tornare alla tua pagina e rimuovere l'ogiva in attesa ( removeWaitingSpinner () ). E 'importante per scadere il cookie, se si vuole essere in grado di scaricare di nuovo un altro file!

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 ...
}

- lato server:
Alla fine del processo server, aggiungere un cookie alla risposta. Questo cookie viene inviato al client quando il file sarà pronto per il download.

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

Spero di aiutare qualcuno!

Il problema è quello di avere un indicatore di ‘attesa’, mentre viene generato un file e poi tornare alla normalità una volta che il file sta scaricando. Il modo in cui mi piace todo questo sta usando un iframe nascosto e agganciare evento onload della cornice di lasciare la mia pagina di download sapere quando si avvia. MA OnLoad non si attiva in IE per download di file (come con l'intestazione Token allegato). Polling il server funziona, ma mi piacciono la complessità supplementare. Così qui è quello che faccio:

  • Obiettivo del iframe nascosto, come al solito.
  • generare il contenuto. Cache con un timeout assoluto in 2 minuti.
  • Invia un reindirizzamento JavaScript torna a il client chiamante, in sostanza, chiamando il pagina generatore una seconda volta. NOTA:. Questo farà sì che l'evento onload al fuoco in IE perché sta comportando come una pagina normale
  • Rimuovere il contenuto dalla cache e inviarlo al cliente.

di responsabilità, non fare questo su un sito occupato, per la memorizzazione nella cache potrebbe aggiungere fino. Ma in realtà, se i siti che occupato il processo di lunga esecuzione moriranno di fame si di fili in ogni modo.

Ecco ciò che il codebehind sembra, che è tutto ciò che ha realmente bisogno.

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'");
        }
    }

Una soluzione rapida se desideri solo per visualizzare un messaggio o un GIF caricatore fino a quando viene visualizzata la finestra di download è di mettere il messaggio in un contenitore nascosto e quando si fa clic sul pulsante che genera il file da scaricare si effettua la contenitore visibile. Quindi utilizzare jQuery o JavaScript per catturare l'evento focusOut del pulsante per nascondere il contenitore che contiene il messaggio

Se XMLHttpRequest con blob non è un'opzione allora si può aprire il file in una nuova finestra e verificare se gli elementi eny vengono popolati in quel corpo finestra con intervallo.

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

Crea un iframe quando il pulsante / link viene cliccato e aggiungere questo al corpo.

                  $('<iframe />')
                 .attr('src', url)
                 .attr('id','iframe_download_report')
                 .hide()
                 .appendTo('body'); 

Crea un iframe con ritardo e cancellarlo dopo il download.

                            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);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top