Détecter quand le navigateur reçoit le téléchargement de fichiers


488

J'ai une page qui permet à l'utilisateur de télécharger un fichier généré dynamiquement. Cela prend beaucoup de temps à générer, je voudrais donc afficher un indicateur "d'attente". Le problème est que je ne sais pas comment détecter quand le navigateur a reçu le fichier, je peux donc masquer l'indicateur.

Je fais la demande sous une forme cachée, qui POSTE au serveur et cible une iframe cachée pour ses résultats. C'est ainsi que je ne remplace pas la totalité de la fenêtre du navigateur par le résultat. J'écoute un événement de "chargement" sur l'iframe, dans l'espoir qu'il se déclenche lorsque le téléchargement est terminé.

Je renvoie un en-tête "Content-Disposition: attachment" avec le fichier, ce qui fait que le navigateur affiche la boîte de dialogue "Enregistrer". Mais le navigateur ne déclenche pas un événement de "chargement" dans l'iframe.

Une approche que j'ai essayée utilise une réponse en plusieurs parties. Il enverrait donc un fichier HTML vide, ainsi que le fichier téléchargeable joint. Par exemple:

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

Cela fonctionne dans Firefox; il reçoit le fichier HTML vide, déclenche l'événement "charger", puis affiche la boîte de dialogue "Enregistrer" pour le fichier téléchargeable. Mais il échoue sur IE et Safari; IE déclenche l'événement "load" mais ne télécharge pas le fichier, et Safari télécharge le fichier (avec un nom et un type de contenu incorrects) et ne déclenche pas l'événement "load".

Une approche différente pourrait être de faire un appel pour démarrer la création du fichier, puis d'interroger le serveur jusqu'à ce qu'il soit prêt, puis de télécharger le fichier déjà créé. Mais je préfère éviter de créer des fichiers temporaires sur le serveur.

Quelqu'un a-t-il une meilleure idée?


4
Aucune version d'IE ne prend en charge multipart / x-mixed-replace.
EricLaw

Merci Eric - c'est bon à savoir. Je ne perdrai plus de temps avec cette approche.
JW.

Le seul moyen fiable semble être la notification push du serveur (SignalR pour les gens ASP.NET).
dudeNumber4

1
bennadel.com/blog/… - c'est une solution simple
Mateen

1
@mateen merci mec! c'est vraiment simple
Fai Zal Dong

Réponses:


451

Une solution possible utilise JavaScript sur le client.

L'algorithme client:

  1. Générez un jeton unique aléatoire.
  2. Soumettez la demande de téléchargement et incluez le jeton dans un champ GET / POST.
  3. Afficher l'indicateur "en attente".
  4. Démarrez une minuterie, et toutes les secondes environ, recherchez un cookie nommé "fileDownloadToken" (ou tout ce que vous décidez).
  5. Si le cookie existe et que sa valeur correspond au jeton, masquez l'indicateur "en attente".

L'algorithme du serveur:

  1. Recherchez le champ GET / POST dans la demande.
  2. S'il a une valeur non vide, supprimez un cookie (par exemple, "fileDownloadToken") et définissez sa valeur sur la valeur du jeton.

Code source du 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;
}

Exemple de code serveur (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();

Où:

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
Idée géniale, je l'ai utilisé comme cadre de base pour cette réponse sur le téléchargement de plusieurs fichiers avec jQuery / C #
Greg

7
Attention aux autres: si document.cookies n'inclut pas le downloadToken, vérifiez le chemin du cookie. Dans mon cas, j'ai dû définir le chemin sur "/" côté serveur (par exemple cookie.setPath ("/") en Java) même si le chemin était vide par défaut. Pendant un certain temps, j'ai pensé que le problème était la gestion spéciale des cookies de domaine «localhost» ( stackoverflow.com/questions/1134290/… ) mais ce n'était pas le problème à la fin. Peut-être que pour les autres, cela vaut la peine d'être lu.
jlpp

2
@bulltorious avant d'approfondir votre solution, je me demande si cela fonctionnera avec une demande de téléchargement de fichier interdomaine. Pensez-vous que ce sera le cas, ou que les restrictions des cookies le compromettront?
kiks73

5
Génial - il ne me serait pas venu à l'esprit en 100 ans que vous pourriez inclure des cookies dans le cadre du téléchargement d'un fichier. Je vous remercie!!
Freefaller

8
Comme d'autres l'ont souligné, cette solution ne résout qu'une partie du problème, l'attente que le serveur prépare l'heure du fichier. L'autre partie du problème, qui peut être considérable en fonction de la taille du fichier et de la vitesse de connexion, est le temps nécessaire pour obtenir le fichier entier sur le client. Et cela n'est pas résolu avec cette solution.
AsGoodAsItGets

27

Une solution très simple (et boiteuse) sur une ligne consiste à utiliser l' window.onblur()événement pour fermer la boîte de dialogue de chargement. Bien sûr, si cela prend trop de temps et que l'utilisateur décide de faire autre chose (comme lire des e-mails), la boîte de dialogue de chargement se fermera.


Il s'agit d'une approche simple qui est idéale pour se débarrasser d'une superposition de chargement pour un téléchargement de fichier qui a été déclenchée à l'aide de onbeforeunloadMerci.
wf4

5
Cela ne fonctionne pas dans tous les navigateurs (certains ne laissent pas / ne brouillent pas la fenêtre actuelle dans le cadre du flux de travail de téléchargement, par exemple Safari, certaines versions d'IE, etc.).
hiattp

4
Chrome et d'autres navigateurs de ce type téléchargent automatiquement les fichiers dans lesquels cette condition échouera.
Lucky

@Lucky, ce n'est que par défaut. Il est tout à fait possible qu'un utilisateur de Chrome spécifie où les téléchargements doivent être enregistrés et voit donc la boîte de dialogue
ESR

2
mauvaise idée parce que vous activez le flou sur tabchange, ou toute action en dehors de la fenêtre
Michael

14

vieux fil, je sais ...

mais ceux qui sont menés ici par google pourraient être intéressés par ma solution. c'est très simple, mais fiable. et il permet d'afficher de vrais messages de progression (et peut être facilement branché aux processus existants):

le script qui traite (mon problème était: récupérer des fichiers via http et les livrer sous forme de zip) écrit le statut dans la session.

l'état est interrogé et affiché toutes les secondes. c'est tout (ok, ce n'est pas le cas. vous devez vous occuper de beaucoup de détails [par exemple les téléchargements simultanés], mais c'est un bon point de départ ;-)).

la page de téléchargement:

    <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
mais si l'utilisateur a plusieurs fenêtres ou téléchargements ouverts? vous obtenez également ici un appel redondant vers le serveur
Yuki

3
Si vous avez plusieurs connexions d'un utilisateur, elles attendent toutes la fin des autres connexions car session_start () verrouille la session pour l'utilisateur et empêche tous les autres processus d'y accéder.
Honza Kuchař

2
vous n'avez pas besoin d'utiliser .each()pour les inscriptions aux événements. dis juste$('a.download').click()
robisrob

N'évaluez pas le code à l'intérieur setTimeout('getstatus()', 1000);. Utilisez directement la fn:setTimeout(getstatus, 1000);
Roko C. Buljan

11

J'utilise ce qui suit pour télécharger des objets blob et révoquer l'URL de l'objet après le téléchargement. ça marche en chrome et 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;
}

une fois la boîte de dialogue de téléchargement de fichier fermée, la fenêtre reprend son focus afin que l'événement focus soit déclenché.


A toujours le problème de changer de fenêtre et de revenir, ce qui entraînera la dissimulation du modal.
dudeNumber4

9
Les navigateurs comme Chrome qui téléchargent dans le bac inférieur ne brouillent / ne recentrent jamais la fenêtre.
Coleman

10

Sur la base de l'exemple d'Elmer, j'ai préparé ma propre solution. Une fois que les éléments ont cliqué avec la classe de téléchargement définie , il permet d'afficher un message personnalisé à l'écran. J'ai utilisé le déclencheur de focus pour masquer le message.

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>

Vous devez maintenant implémenter n'importe quel élément à télécharger:

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

ou

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

Après chaque téléchargement, vous verrez un message que votre rapport crée, veuillez patienter ...


2
Et si l'utilisateur clique sur la fenêtre?
Tom Roggero

c'est exactement ce que je cherchais, c'est beaucoup !!
Sergio

Le hide () ne sera pas appelé dans mon cas
Prashant Pimpale

8

J'ai écrit une classe JavaScript simple qui implémente une technique similaire à celle décrite dans la réponse bulltorious . J'espère que cela peut être utile à quelqu'un ici. Le projet GitHub s'appelle response-monitor.js

Par défaut, il utilise spin.js comme indicateur d'attente, mais il fournit également un ensemble de rappels pour la mise en œuvre d'un indicateur personnalisé.

JQuery est pris en charge mais n'est pas requis.

Caractéristiques notables

  • Intégration simple
  • Pas de dépendances
  • Plug-in JQuery (facultatif)
  • Intégration Spin.js (facultatif)
  • Rappels configurables pour la surveillance des événements
  • Gère plusieurs demandes simultanées
  • Détection d'erreurs côté serveur
  • Détection de timeout
  • Navigateur croisé

Exemple d'utilisation

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

//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 avec rappels (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);

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

Pour plus d'exemples, consultez le dossier d' exemples sur le référentiel.


5

Je suis très en retard à la fête, mais je mettrai cela ici si quelqu'un d'autre souhaite connaître ma solution:

J'ai eu un vrai problème avec ce problème précis mais j'ai trouvé une solution viable en utilisant des iframes (je sais, je sais. C'est terrible mais ça marche pour un problème simple que j'ai eu)

J'avais une page html qui a lancé un script php distinct qui a généré le fichier puis l'a téléchargé. Sur la page html, j'ai utilisé le jquery suivant dans l'en-tête html (vous devrez également inclure une bibliothèque 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>

Sur your_download_script.php, disposez des éléments suivants:

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

Pour décomposer cela, jquery lance d'abord votre script php dans un iframe. L'iframe est chargé une fois le fichier généré. Ensuite, jquery lance à nouveau le script avec une variable de demande indiquant au script de télécharger le fichier.

La raison pour laquelle vous ne pouvez pas effectuer le téléchargement et la génération de fichiers en une seule fois est due à la fonction php header (). Si vous utilisez header (), vous changez le script en autre chose qu'une page Web et jquery ne reconnaîtra jamais le script de téléchargement comme étant «chargé». Je sais que cela ne détecte pas nécessairement quand un navigateur reçoit un fichier, mais votre problème ressemblait au mien.


5

Si vous diffusez un fichier que vous générez de manière dynamique et que vous avez également implémenté une bibliothèque de messagerie serveur-client en temps réel, vous pouvez alerter votre client assez facilement.

La bibliothèque de messagerie serveur à client que j'aime et que je recommande est Socket.io (via Node.js). Une fois que votre script de serveur a terminé de générer le fichier en cours de streaming pour le téléchargement, votre dernière ligne dans ce script peut émettre un message vers Socket.io qui envoie une notification au client. Sur le client, Socket.io écoute les messages entrants émis par le serveur et vous permet d'agir sur eux. L'avantage d'utiliser cette méthode par rapport aux autres est que vous êtes en mesure de détecter un «vrai» événement de fin une fois la diffusion terminée.

Par exemple, vous pouvez afficher votre indicateur d'occupation après avoir cliqué sur un lien de téléchargement, diffuser votre fichier, émettre un message vers Socket.io à partir du serveur dans la dernière ligne de votre script de streaming, écouter le client pour une notification, recevoir la notification et mettez à jour votre interface utilisateur en masquant l'indicateur occupé.

Je me rends compte que la plupart des gens qui lisent les réponses à cette question peuvent ne pas avoir ce type de configuration, mais j'ai utilisé cette solution exacte avec grand effet dans mes propres projets et cela fonctionne à merveille.

Socket.io est incroyablement facile à installer et à utiliser. Voir plus: http://socket.io/


5

"Comment détecter quand le navigateur reçoit le téléchargement de fichiers?"
J'ai rencontré le même problème avec cette configuration:
struts 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1.custom
IE 11
java 5


Ma solution avec un cookie:
- Côté client:
lors de la soumission de votre formulaire, appelez votre fonction javascript pour masquer votre page et charger votre spinner en attente

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

Ensuite, appelez une fonction qui vérifiera toutes les 500 ms si un cookie provient du serveur.

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

Si le cookie est trouvé, arrêtez de vérifier toutes les 500 ms, expirez le cookie et appelez votre fonction pour revenir à votre page et supprimer le spinner en attente ( removeWaitingSpinner () ). Il est important de faire expirer le cookie si vous souhaitez pouvoir télécharger à nouveau un autre fichier!

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

- Côté serveur:
à la fin de votre processus serveur, ajoutez un cookie à la réponse. Ce cookie sera envoyé au client lorsque votre fichier sera prêt à être téléchargé.

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

J'espère aider quelqu'un!


Cela fonctionne parfaitement. Merci pour ce bel échantillon.
Sedat Kumcu

4

Lorsque l'utilisateur déclenche la génération du fichier, vous pouvez simplement attribuer un ID unique à ce "téléchargement" et envoyer l'utilisateur vers une page qui se rafraîchit (ou vérifie avec AJAX) toutes les quelques secondes. Une fois le fichier terminé, enregistrez-le sous ce même ID unique et ...

  • Si le fichier est prêt, faites le téléchargement.
  • Si le fichier n'est pas prêt, affichez la progression.

Ensuite, vous pouvez ignorer tout le désordre iframe / attente / browserwindow, tout en ayant une solution vraiment élégante.


Cela ressemble à l'approche des fichiers temporaires que j'ai mentionnée ci-dessus. Je pourrais faire quelque chose comme ça s'il s'avère que mon idée est impossible, mais j'espérais l'éviter.
JW.

3

Si vous ne souhaitez pas générer et stocker le fichier sur le serveur, êtes-vous prêt à stocker l'état, par exemple fichier en cours, fichier terminé? Votre page "en attente" pourrait interroger le serveur pour savoir quand la génération du fichier est terminée. Vous ne sauriez pas avec certitude si le navigateur a commencé le téléchargement, mais vous auriez une certaine confiance.


2

Je viens d'avoir exactement le même problème. Ma solution était d'utiliser des fichiers temporaires car je générais déjà un tas de fichiers temporaires. Le formulaire est soumis avec:

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

À la fin du script qui génère le fichier, j'ai:

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

Cela entraînera le déclenchement de l'événement de chargement sur l'iframe. Ensuite, le message d'attente est fermé et le téléchargement du fichier démarre. Testé sur IE7 et Firefox.


2

D'après mon expérience, il y a deux façons de gérer cela:

  1. Définissez un cookie de courte durée sur le téléchargement et demandez à JavaScript de vérifier en permanence son existence. Le seul problème réel est d'obtenir la durée de vie du cookie correctement - trop courte et le JS peut la manquer, trop longtemps et cela pourrait annuler les écrans de téléchargement pour d'autres téléchargements. L'utilisation de JS pour supprimer le cookie lors de la découverte résout généralement ce problème.
  2. Téléchargez le fichier à l'aide de fetch / XHR. Non seulement vous savez exactement quand le téléchargement du fichier se termine, mais si vous utilisez XHR, vous pouvez utiliser des événements de progression pour afficher une barre de progression! Enregistrez le blob résultant avec msSaveBlob dans IE / Edge et un lien de téléchargement ( comme celui-ci ) dans Firefox / Chrome. Le problème avec cette méthode est qu'iOS Safari ne semble pas gérer correctement le téléchargement des blobs - vous pouvez convertir le blob en une URL de données avec un FileReader et l'ouvrir dans une nouvelle fenêtre, mais c'est ouvrir le fichier, pas l'enregistrer.

2

Salutations, je sais que le sujet est ancien mais je laisse une solution que j'ai vue ailleurs et ça a marché:

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

Tu peux l'utiliser:

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

1

Si vous avez téléchargé un fichier, qui est enregistré, par opposition à être dans le document, il n'y a aucun moyen de déterminer quand le téléchargement est terminé, car il n'est pas dans la portée du document actuel, mais un processus distinct dans le navigateur.


8
Je dois préciser - je ne suis pas trop préoccupé par le moment où le téléchargement finalise . Si je peux identifier quand le téléchargement commence, ce serait suffisant.
. JW

0

La question est d'avoir un indicateur «d'attente» pendant la génération d'un fichier, puis de revenir à la normale une fois le fichier téléchargé. La façon dont j'aime faire cela consiste à utiliser un iFrame caché et à accrocher l'événement onload du cadre pour informer ma page lorsque le téléchargement démarre. MAIS onload ne se déclenche pas dans IE pour les téléchargements de fichiers (comme avec le jeton d'en-tête de pièce jointe). L'interrogation du serveur fonctionne, mais je n'aime pas la complexité supplémentaire. Voici donc ce que je fais:

  • Ciblez l'iFrame caché comme d'habitude.
  • Générez le contenu. Cachez-le avec un timeout absolu en 2 minutes.
  • Renvoyez une redirection javascript au client appelant, appelant essentiellement la page du générateur une deuxième fois. REMARQUE: cela provoquera le déclenchement de l'événement onload dans IE, car il se comporte comme une page standard.
  • Supprimez le contenu du cache et envoyez-le au client.

Avertissement, ne faites pas cela sur un site très fréquenté, car la mise en cache pourrait s'additionner. Mais vraiment, si vos sites qui ont occupé le long processus vous affameront de toute façon.

Voici à quoi ressemble le codebehind, c'est tout ce dont vous avez vraiment besoin.

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

Une solution rapide si vous souhaitez uniquement afficher un message ou un gif de chargeur jusqu'à ce que la boîte de dialogue de téléchargement s'affiche est de placer le message dans un conteneur caché et lorsque vous cliquez sur le bouton qui génère le fichier à télécharger, vous rendez le conteneur visible. Utilisez ensuite jquery ou javascript pour intercepter l'événement focusout du bouton pour masquer le conteneur qui contient le message


0

Si Xmlhttprequest avec blob n'est pas une option, vous pouvez ouvrir votre fichier dans une nouvelle fenêtre et vérifier si les éléments eny sont remplis dans ce corps de fenêtre avec intervalle.

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

Cet exemple Java / Spring détecte la fin d'un téléchargement, auquel cas il masque l'indicateur "Chargement ...".

Approche: Du côté JS, définissez un cookie avec un âge d'expiration maximal de 2 minutes et interrogez chaque seconde pour l' expiration des cookies . Ensuite, le côté serveur remplace ce cookie avec un âge d'expiration plus précoce - l'achèvement du processus serveur. Dès que l'expiration du cookie est détectée dans l'interrogation JS, "Loading ..." est masqué.

Côté 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();
    }
}

Côté serveur 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;
}

Le cookie est créé par le script JS mais il n'est pas mis à jour par le contrôleur, il conserve la valeur d'origine (0), comment puis-je mettre à jour la valeur du cookie sans rafraîchir la page?
Shessuky

C'est étrange - pouvez-vous vous assurer que le nom est exactement correct? Il écrasera le cookie si le nom correspond. Faites-moi savoir
gène b.

La valeur d'origine n'est pas 0. La valeur d'origine définie dans JS est de 2 min. La NOUVELLE valeur avec laquelle le serveur est censé modifier est 0.
gène b.

Faites-vous aussi ceci: myCookie.setPath("/"); response.addCookie(myCookie);
gène b.

J'ai compris (pour une raison quelconque) que je devais ajouter des cookies avant de faire response.getOutputStream (); (obtenir un flux de sortie de réponse pour ajouter des fichiers de téléchargement), il n'a pas été pris en compte lorsque je l'ai fait après cette étape
Shessuky

0

Primefaces utilise également l'interrogation des cookies

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

Créez un iframe lorsque le bouton / lien est cliqué et ajoutez-le au corps.

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

Créez un iframe avec retard et supprimez-le après le téléchargement.

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

Il manque des informations et ne résout pas le problème "quand cacher le chargement".
Tom Roggero
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.