Rileva quando il browser riceve il download del file


488

Ho una pagina che consente all'utente di scaricare un file generato 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, quindi posso nascondere l'indicatore.

Sto effettuando la richiesta in una forma nascosta, che invia al server e indirizza un iframe nascosto per i suoi risultati. Questo è quindi non sostituisco l'intera finestra del browser con il risultato. Ascolto un evento "load" sull'iframe, nella speranza che si attivi al termine del download.

Restituisco un'intestazione "Disposizione contenuto: allegato" con il file, che fa sì che il browser mostri la finestra di dialogo "Salva". Ma il browser non genera un evento "load" nell'iframe.

Un approccio che ho provato è l'utilizzo di una risposta in più parti. Quindi invierebbe un file HTML vuoto, così come il file scaricabile allegato. Per 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 "load", quindi mostra la finestra di dialogo "Salva" per il file scaricabile. Ma fallisce su IE e Safari; IE genera l'evento "load" ma non scarica il file e Safari scarica il file (con nome e tipo di contenuto errati) e non attiva l'evento "load".

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

Qualcuno ha un'idea migliore?


4
Nessuna versione di IE supporta multipart / x-mixed-replace.
EricLaw,

Grazie Eric, è bello saperlo. Non perderò altro tempo con questo approccio.
JW.

L'unico modo affidabile sembra essere la notifica push del server (SignalR per persone ASP.NET).
dudeNumber4,

1
bennadel.com/blog/… - questa è una soluzione semplice
Mateen,

1
@mateen grazie amico! è davvero semplice
Fai Zal Dong il

Risposte:


451

Una possibile soluzione utilizza JavaScript sul client.

L'algoritmo client:

  1. Genera un token univoco casuale.
  2. Invia la richiesta di download e includi il token in un campo GET / POST.
  3. Mostra l'indicatore "in attesa".
  4. Avvia un timer e, all'incirca ogni secondo, cerca un cookie chiamato "fileDownloadToken" (o qualunque cosa tu decida).
  5. Se il cookie esiste e il suo valore corrisponde al token, nascondi l'indicatore "in attesa".

L'algoritmo del server:

  1. Cerca il campo GET / POST nella richiesta.
  2. Se ha un valore non vuoto, elimina un cookie (ad esempio "fileDownloadToken") e imposta il suo valore sul valore del token.

Codice sorgente 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;
}

Esempio di codice server (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
    );
}

4
Fantastica idea, l'ho usato come framework di base per questa risposta sul download di più file con jQuery / C #
Greg

7
Un avvertimento per gli altri: se document.cookies non include downloadToken, controlla il percorso dei cookie. Nel mio caso, ho dovuto impostare il percorso su "/" sul lato server (ad esempio cookie.setPath ("/") in Java) anche se il percorso era vuoto per impostazione predefinita. Per qualche tempo ho pensato che il problema fosse la speciale gestione dei cookie di dominio "localhost" ( stackoverflow.com/questions/1134290/… ) ma alla fine non era questo il problema. Potrebbe essere quello per gli altri, anche se vale la pena leggere.
jlpp,

2
@bulltorious prima di approfondire la tua soluzione, mi chiedo se funzionerà con una richiesta di download di file tra domini. Pensi che lo farà, o le restrizioni sui cookie lo comprometteranno?
kiks73,

5
Fantastico - non mi sarebbe mai venuto in mente in 100 anni che potresti includere i cookie come parte di un download di file. Grazie!!
caduta libera

8
Come altri hanno sottolineato, questa soluzione risolve solo una parte del problema, l'attesa che il server prepari l'ora del file. L'altra parte del problema, che può essere considerevole a seconda della dimensione del file e della velocità di connessione, è il tempo impiegato per ottenere effettivamente l'intero file sul client. E questo non è risolto con questa soluzione.
AsGoodAsIt Ottiene il

27

Una soluzione molto semplice (e zoppa) su una riga è quella di utilizzare l' window.onblur()evento per chiudere la finestra di dialogo di caricamento. Naturalmente, se impiega troppo tempo e l'utente decide di fare qualcos'altro (come leggere le e-mail), la finestra di caricamento verrà chiusa.


Questo è un approccio semplice che è l'ideale per sbarazzarsi di un overlay di caricamento per un download di file che è stato attivato utilizzando onbeforeunloadGrazie.
wf4,

5
Questo non funziona in tutti i browser (alcuni non lasciano / sfocano la finestra corrente come parte del flusso di lavoro di download, ad esempio Safari, alcune versioni di IE, ecc.).
hiattp,

4
Chrome e altri browser di questo tipo scaricano automaticamente i file in cui questa condizione fallirà.
Lucky

@Lucky che è solo per impostazione predefinita. È del tutto possibile che un utente di Chrome specifichi dove salvare i download e quindi vedrà la finestra di dialogo
ESR

2
pessima idea perché attivi la sfocatura sullo scambio di schede o qualsiasi azione fuori dalla finestra
Michael

14

vecchio thread, lo so ...

ma quelli che sono guidati qui da Google potrebbero essere interessati alla mia soluzione. è molto semplice, ma affidabile. e consente di visualizzare messaggi di avanzamento reale (e può essere facilmente collegato a processi esistenti):

lo script che elabora (il mio problema era: recuperare i file tramite http e consegnarli come zip) scrive lo stato nella sessione.

lo stato viene interrogato e visualizzato ogni secondo. questo è tutto (ok, no. Devi occuparti di molti dettagli [ad esempio download simultanei], ma è un buon punto di partenza ;-)).

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

3
ma se l'utente ha più finestre o download aperti? anche qui arriva una chiamata ridondante al server
Yuki,

3
Se si dispone di più connessioni da un utente, saranno tutte in attesa della fine di altre connessioni poiché session_start () blocca la sessione per l'utente e impedisce a tutti gli altri processi di accedervi.
Honza Kuchař,

2
non è necessario utilizzare .each()per le registrazioni degli eventi. dì solo$('a.download').click()
robisrob il

Non valutare il codice all'interno setTimeout('getstatus()', 1000);. Usa direttamente il fn:setTimeout(getstatus, 1000);
Roko C. Buljan il

11

utilizzo quanto segue per scaricare BLOB e revocare l'URL dell'oggetto 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 chiusura della finestra di dialogo per il download del file, la finestra torna allo stato attivo, quindi viene attivato l'evento focus.


Ha ancora il problema di cambiare la finestra e tornare, il che farà nascondere il modale.
dudeNumber4,

9
Browser come Chrome che vengono scaricati nel vassoio inferiore non sfocano / rifocano mai la finestra.
Coleman,

10

Sulla base dell'esempio di Elmer, ho preparato la mia soluzione. Dopo che gli elementi hanno fatto clic con una classe di download definita , consente di mostrare un messaggio personalizzato sullo schermo. Ho usato la messa a fuoco trigger di a 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 dovresti implementare qualsiasi elemento da 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 , vedrai il messaggio che sta creando il tuo rapporto, attendi ...


2
Cosa succede se l'utente fa clic sulla finestra?
Tom Roggero,

questo è esattamente quello che stavo cercando, grazie mille !!
Sergio,

Nel mio caso, hide () non viene chiamato
Prashant Pimpale l'

8

Ho scritto una semplice classe JavaScript che implementa una tecnica simile a quella descritta nella risposta bulltorious . Spero che possa essere utile a qualcuno qui. Viene chiamato il progetto GitHub response-monitor.js

Di default usa spin.js come indicatore di attesa ma fornisce anche una serie di callback per l'implementazione di un indicatore personalizzato.

JQuery è supportato ma non richiesto.

Caratteristiche notevoli

  • Integrazione semplice
  • Nessuna dipendenza
  • Plug-in JQuery (opzionale)
  • Integrazione Spin.js (opzionale)
  • Callback configurabili per il monitoraggio degli eventi
  • Gestisce più richieste simultanee
  • Rilevamento errori lato server
  • Rilevamento del timeout
  • Browser incrociato

Esempio di utilizzo

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 (JavaScript semplice)

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

Cliente (JQuery)

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

Client con richiamate (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 altri esempi, controlla la cartella degli esempi nel repository.


5

Sono in ritardo alla festa, ma lo metterò qui se qualcun altro desidera conoscere la mia soluzione:

Ho avuto una vera lotta con questo esatto problema ma ho trovato una soluzione praticabile usando iframe (lo so, lo so. È 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 (dovrai includere anche una libreria 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>

Su your_download_script.php, disporre di quanto segue:

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 scomporlo, jquery avvia prima il tuo script php in un iframe. L'iframe viene caricato una volta generato il file. Quindi jquery avvia nuovamente lo script con una variabile di richiesta che dice allo script di scaricare il file.

Il motivo per cui non è possibile eseguire il download e la generazione di file tutto in una volta è dovuto alla funzione php header (). Se usi header (), stai cambiando lo script in qualcosa di diverso da una pagina web e jquery non riconoscerà mai lo script di download come "caricato". So che questo potrebbe non rilevare necessariamente quando un browser riceve un file, ma il tuo problema sembrava simile al mio.


5

Se stai eseguendo lo streaming di un file che stai generando in modo dinamico e hai anche implementato una libreria di messaggistica da server a client in tempo reale, puoi avvisare il tuo client abbastanza facilmente.

La libreria di messaggistica da server a client che mi piace e raccomando è Socket.io (tramite Node.js). Al termine dello script del server, la generazione del file in streaming per il download dell'ultima riga in quello script può emettere un messaggio su Socket.io che invia una notifica al client. Sul client, Socket.io ascolta i messaggi in arrivo emessi dal server e consente di agire su di essi. Il vantaggio dell'utilizzo di questo metodo rispetto ad altri è che si è in grado di rilevare un evento di finitura "vero" al termine dello streaming.

Ad esempio, è possibile mostrare l'indicatore di occupato dopo aver fatto clic su un collegamento per il download, eseguire lo streaming del file, emettere un messaggio su Socket.io dal server nell'ultima riga dello script di streaming, ascoltare sul client una notifica, ricevere la notifica e aggiorna la tua UI nascondendo l'indicatore di occupato.

Mi rendo conto che la maggior parte delle persone che leggono le risposte a questa domanda potrebbe non avere questo tipo di installazione, ma ho usato questa soluzione esatta con grande efficacia nei miei progetti e funziona meravigliosamente.

Socket.io è incredibilmente facile da installare e utilizzare. Scopri di più: http://socket.io/


5

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


La mia soluzione con un cookie:
- Lato client:
quando invii il modulo, chiama la tua funzione javascript per nascondere la tua pagina e caricare il tuo spinner in attesa

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

Quindi, chiama una funzione che controllerà ogni 500ms se un cookie proviene dal server.

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

Se viene trovato il cookie, interrompi il controllo ogni 500 ms, scade il cookie e chiama la tua funzione per tornare alla tua pagina e rimuovere lo spinner in attesa ( removeWaitingSpinner () ). È importante far scadere il cookie se si desidera poter 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:
al termine del processo del server, aggiungi un cookie alla risposta. Quel cookie verrà inviato al client quando il file sarà pronto per il download.

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

Spero di aiutare qualcuno!


Funziona perfettamente. Grazie per questo bellissimo campione.
Sedat Kumcu,

4

Quando l'utente attiva la generazione del file, è possibile semplicemente assegnare un ID univoco a quel "download" e inviarlo a una pagina che si aggiorna (o verifica con AJAX) ogni pochi secondi. Una volta terminato il file, salvalo con lo stesso ID univoco e ...

  • Se il file è pronto, esegui il download.
  • Se il file non è pronto, mostra l'avanzamento.

Quindi puoi saltare l'intero pasticcio iframe / waiting / browserwindow, ma hai una soluzione davvero elegante.


Sembra l'approccio per file temporanei che ho menzionato sopra. Potrei fare qualcosa del genere se la mia idea fosse impossibile, ma speravo di evitarlo.
JW.

3

Se non si desidera generare e archiviare il file sul server, si desidera archiviare lo stato, ad es. File in corso, file completo? La tua pagina "in attesa" potrebbe eseguire il polling del server per sapere quando la generazione del file è completa. Non sapresti per certo che il browser ha avviato il download ma avresti una certa sicurezza.


2

Ho appena avuto lo stesso identico problema. La mia soluzione era quella di utilizzare i file temporanei poiché stavo già generando un sacco di file temporanei. Il modulo è 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 ho:

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

Ciò causerà l'attivazione dell'evento di caricamento sull'iframe. Quindi il messaggio di attesa viene chiuso e verrà avviato il download del file. Testato su IE7 e Firefox.


2

Nella mia esperienza, ci sono due modi per gestirlo:

  1. Imposta un cookie di breve durata durante il download e fai in modo che JavaScript verifichi continuamente la sua esistenza. L'unico vero problema è rendere la durata dei cookie giusta - troppo breve e JS può perderlo, troppo a lungo e potrebbe annullare le schermate di download per altri download. L'uso di JS per rimuovere il cookie al momento della scoperta di solito risolve questo problema.
  2. Scarica il file usando fetch / XHR. Non solo sai esattamente quando termina il download del file, se usi XHR puoi anche utilizzare gli eventi progress per mostrare una barra di avanzamento! Salvare il BLOB risultante con msSaveBlob in IE / Edge e un collegamento per il download ( come questo ) in Firefox / Chrome. Il problema con questo metodo è che iOS Safari non sembra gestire correttamente il download di BLOB: è possibile convertire il BLOB in un URL di dati con un FileReader e aprirlo in una nuova finestra, ma ciò sta aprendo il file, non salvandolo.

2

Saluti, so che l'argomento è vecchio ma lascio una soluzione che ho visto altrove e ha funzionato:

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

Puoi usarlo:

downloadURI("www.linkToFile.com", "file.name");

1

Se hai scaricato un file, che viene salvato, invece di essere nel documento, non c'è modo di determinare quando il download è completo, dal momento che non rientra nell'ambito del documento corrente, ma un processo separato nel browser.


8
Dovrei chiarire: "Non mi preoccupo troppo del completamento del download . Se potessi solo identificare quando inizia il download, sarebbe sufficiente.
JW.

0

La domanda è avere un indicatore di "attesa" durante la generazione di un file e poi tornare alla normalità una volta scaricato il file. Il modo in cui mi piace fare questo è usare un iFrame nascosto e agganciare l'evento onload del frame per far sapere alla mia pagina quando inizia il download. MA onload non si attiva in IE per i download di file (come con il token dell'intestazione dell'allegato). Il polling del server funziona, ma non mi piace la complessità aggiuntiva. Quindi ecco cosa faccio:

  • Scegli come target l'iFrame nascosto.
  • Genera il contenuto. Inseriscilo nella cache con un timeout assoluto in 2 minuti.
  • Invia un reindirizzamento JavaScript al client chiamante, essenzialmente richiamando una seconda volta la pagina del generatore. NOTA: questo farà sì che l'evento onload si attivi in ​​IE perché si comporta come una normale pagina.
  • Rimuovere il contenuto dalla cache e inviarlo al client.

Disclaimer, non farlo su un sito occupato, a causa della memorizzazione nella cache potrebbe sommarsi. Ma davvero, se i tuoi siti che occupano il lungo processo ti faranno morire di fame comunque.

Ecco come appare il codebehind, che è tutto ciò di cui hai veramente 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'");
        }
    }

0

Una soluzione rapida se si desidera visualizzare solo un messaggio o una gif del caricatore fino a quando non viene visualizzata la finestra di dialogo di download è quella di mettere il messaggio in un contenitore nascosto e quando si fa clic sul pulsante che genera il file da scaricare, si rende visibile il contenitore. Quindi utilizzare jquery o javascript per catturare l'evento focusout del pulsante per nascondere il contenitore che contiene il messaggio


0

Se Xmlhttprequest con BLOB non è un'opzione, è possibile aprire il file in una nuova finestra e verificare se gli elementi eny vengono popolati in quel corpo di 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


0

Questo esempio Java / Spring rileva la fine di un download, a quel punto nasconde l'indicatore "Caricamento in corso ...".

Approccio: sul lato JS, impostare un cookie con un'età massima di scadenza di 2 minuti e eseguire il polling ogni secondo per la scadenza dei cookie . Quindi il lato server sovrascrive questo cookie con una scadenza precedente : il completamento del processo del server. Non appena viene rilevata la scadenza del cookie nel polling JS, "Caricamento in corso ..." viene nascosto.

JS Side

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

Lato server 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;
}

Il cookie viene creato dallo script JS ma non viene aggiornato dal controller, mantiene il valore originale (0), come posso aggiornare il valore del cookie senza aggiornare la pagina?
Shessuky,

È strano - puoi assicurarti che il nome sia esattamente corretto? Sovrascriverà il cookie se il nome corrisponde. Fammi sapere
gene b.

Il valore originale non è 0. Il valore originale impostato in JS è 2 min. Il NUOVO valore che il server dovrebbe modificare è 0.
gene b.

Inoltre, stai facendo questo: myCookie.setPath("/"); response.addCookie(myCookie);
gene b.

Ho capito (per qualche motivo) che avrei dovuto aggiungere i cookie prima di fare response.getOutputStream (); (ottenere il flusso di output della risposta per aggiungere file di download), non è stato preso in considerazione quando l'ho fatto dopo quel passaggio
Shessuky

0

Primefaces utilizza anche il polling dei 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

Crea un iframe quando fai clic sul pulsante / collegamento e aggiungi questo al corpo.

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

Crea un iframe in ritardo ed eliminalo 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);

Mancano informazioni e non risolvono il problema "quando nascondere il caricamento".
Tom Roggero,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.