Wykryj, kiedy przeglądarka odbierze pobieranie pliku


487

Mam stronę, która pozwala użytkownikowi pobrać plik generowany dynamicznie. Generowanie zajmuje dużo czasu, więc chciałbym pokazać wskaźnik „czekania”. Problem polega na tym, że nie mogę ustalić, jak wykryć, kiedy przeglądarka otrzyma plik, więc mogę ukryć wskaźnik.

Zgłaszam żądanie w ukrytej formie, które POST wysyła do serwera i kieruje wyniki do ukrytej ramki iframe. Dzieje się tak, dlatego nie zastępuję całego okna przeglądarki wynikiem. Słucham zdarzenia „load” w ramce iframe, w nadziei, że zadziała po zakończeniu pobierania.

Z plikiem zwracam nagłówek „Content-Disposition: załącznik”, co powoduje, że przeglądarka wyświetla okno dialogowe „Zapisz”. Ale przeglądarka nie uruchamia zdarzenia „load” w ramce iframe.

Jednym ze sposobów, które wypróbowałem, jest użycie odpowiedzi wieloczęściowej. Wyśle więc pusty plik HTML, a także załączony plik do pobrania. Na przykład:

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

Działa to w przeglądarce Firefox; otrzymuje pusty plik HTML, uruchamia zdarzenie „ładuj”, a następnie wyświetla okno dialogowe „Zapisz” dla pliku do pobrania. Ale zawodzi w IE i Safari; IE uruchamia zdarzenie „ładuj”, ale nie pobiera pliku, a Safari pobiera plik (z niewłaściwą nazwą i typem zawartości) i nie uruchamia zdarzenia „ładuj”.

Innym podejściem może być wywołanie w celu rozpoczęcia tworzenia pliku, następnie odpytanie serwera, aż będzie on gotowy, a następnie pobranie już utworzonego pliku. Ale wolałbym unikać tworzenia plików tymczasowych na serwerze.

Czy ktoś ma lepszy pomysł?


4
Żadna wersja IE nie obsługuje wieloczęściowego / x-mieszanego-zamień.
EricLaw

Dzięki Eric - dobrze wiedzieć. Dzięki temu podejściu nie będę marnować więcej czasu.
JW.

Jedynym niezawodnym sposobem wydaje się być powiadomienie push serwera (SignalR dla osób ASP.NET).
dudeNumber4

1
bennadel.com/blog/… - to proste rozwiązanie
Mateen

1
@mateen dzięki stary! to naprawdę proste
Fai Zal Dong

Odpowiedzi:


451

Jedno możliwe rozwiązanie wykorzystuje JavaScript na kliencie.

Algorytm klienta:

  1. Wygeneruj losowy unikalny token.
  2. Prześlij żądanie pobrania i dołącz token w polu GET / POST.
  3. Pokaż wskaźnik „oczekiwania”.
  4. Uruchom licznik, a co sekundę szukaj pliku cookie o nazwie „fileDownloadToken” (lub cokolwiek innego).
  5. Jeśli plik cookie istnieje, a jego wartość pasuje do tokena, ukryj wskaźnik „oczekiwania”.

Algorytm serwera:

  1. Poszukaj pola GET / POST w żądaniu.
  2. Jeśli ma niepustą wartość, upuść plik cookie (np. „FileDownloadToken”) i ustaw jego wartość na wartość tokena.

Kod źródłowy klienta (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;
}

Przykładowy kod serwera (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();

Gdzie:

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
Niesamowity pomysł, użyłem go jako podstawowej struktury dla tej odpowiedzi na temat pobierania wielu plików za pomocą jQuery / C #
Greg

7
Uwaga dla innych: jeśli document.cookies nie zawiera downloadToken, sprawdź ścieżkę do pliku cookie. W moim przypadku musiałem ustawić ścieżkę na „/” po stronie serwera (np. Cookie.setPath („/”) w Javie), mimo że domyślnie ścieżka była pusta. Przez pewien czas myślałem, że problemem jest specjalna obsługa plików cookie w domenie „localhost” ( stackoverflow.com/questions/1134290/… ), ale to nie był problem. Być może dla innych, choć tak warte przeczytania.
jlpp

2
@ bulltorious zanim przejdziemy do głębi swojego rozwiązania, zastanawiam się, czy będzie ono działać z żądaniem pobrania pliku między domenami. Czy uważasz, że tak, czy ograniczenia związane z plikami cookie zagrażają temu?
kiks73

5
Genialne - w ciągu 100 lat nie przyszło mi do głowy, że możesz dołączyć pliki cookie w ramach pobierania pliku. Dziękuję Ci!!
freefaller

8
Jak zauważyli inni, to rozwiązanie rozwiązuje tylko część problemu, czekając, aż serwer przygotuje czas na plik. Inną częścią problemu, która może być znaczna w zależności od rozmiaru pliku i szybkości połączenia, jest czas potrzebny na pobranie całego pliku na kliencie. I to nie rozwiązuje tego rozwiązania.
AsGoodAsItGets

27

Bardzo prostym (i kiepskim) rozwiązaniem jednoliniowym jest użycie window.onblur()zdarzenia do zamknięcia okna ładowania. Oczywiście, jeśli potrwa to zbyt długo, a użytkownik zdecyduje się zrobić coś innego (np. Czytanie wiadomości e-mail), okno ładowania zostanie zamknięte.


Jest to proste podejście, które idealnie nadaje się do pozbycia się nakładki ładującej dla pobierania pliku, które zostało uruchomione za pomocą funkcji onbeforeunloadDziękujemy.
wf4

5
Nie działa to we wszystkich przeglądarkach (niektóre nie opuszczają / nie rozmazują bieżącego okna w ramach przepływu pracy pobierania, np. Safari, niektóre wersje IE itp.).
hiattp

4
Chrome i inne tego typu przeglądarki automatycznie pobierają pliki, których spełnienie nie powiedzie się.
Lucky

@ Szczęście, że jest tylko domyślnie. Jest całkowicie możliwe, że użytkownik Chrome określi, gdzie pliki do pobrania powinny być zapisywane, i dlatego zobaczy okno dialogowe
ESR

2
zły pomysł, ponieważ aktywujesz rozmycie przy zmianie tabulacji lub jakąkolwiek akcję za oknem
Michael

14

stary wątek, wiem ...

ale te, które są tu prowadzone przez Google, mogą być zainteresowane moim rozwiązaniem. jest bardzo prosty, ale niezawodny. i umożliwia wyświetlanie rzeczywistych komunikatów o postępach (i można je łatwo podłączyć do istniejących procesów):

skrypt, który przetwarza (moim problemem było: pobieranie plików przez http i dostarczanie ich jako zip) zapisuje status do sesji.

status jest odpytywany i wyświetlany co sekundę. to wszystko (ok, to nie. musisz zadbać o wiele szczegółów [np. jednoczesne pobieranie], ale jest to dobre miejsce, aby zacząć ;-)).

strona pobierania:

    <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
ale jeśli użytkownik ma otwarte wiele okien lub plików do pobrania? dostaniesz tutaj również nadmiarowe połączenie z serwerem
Yuki,

3
Jeśli masz wiele połączeń od jednego użytkownika, wszyscy będą czekać na zakończenie innych połączeń, ponieważ session_start () blokuje sesję dla użytkownika i uniemożliwia dostęp do niego wszystkim innym procesom.
Honza Kuchař

2
nie musisz używać .each()do rejestracji zdarzeń. po prostu powiedz$('a.download').click()
robisrob,

Nie sprawdzaj kodu w środku setTimeout('getstatus()', 1000);. Użyj fn bezpośrednio:setTimeout(getstatus, 1000);
Roko C. Buljan

11

Korzystam z poniższych, aby pobrać obiekty BLOB i odwołać adres URL obiektu po pobraniu. działa w Chrome i 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;
}

po zamknięciu okna pobierania pliku okno odzyskuje fokus, co powoduje wyzwolenie zdarzenia fokusa.


Nadal występuje problem z przełączaniem okna i zwracaniem, co spowoduje ukrycie modalu.
dudeNumber4

9
Przeglądarki takie jak Chrome, które pobierają do dolnej tacy, nigdy nie rozmazują / nie zmieniają ostrości okna.
Coleman,

10

Na podstawie przykładu Elmera przygotowałem własne rozwiązanie. Po kliknięciu elementów ze zdefiniowaną klasą pobierania pozwala wyświetlić niestandardowy komunikat na ekranie. Użyłem skupienia wyzwalacza aby ukryć wiadomość.

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>

Teraz powinieneś zaimplementować dowolny element do pobrania:

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

lub

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

Po każdym kliknięciu pobierania zobaczysz komunikat, który tworzy raport, proszę czekać ...


2
Co się stanie, jeśli użytkownik kliknie okno?
Tom Roggero

to jest dokładnie to, czego szukałem, bardzo dużo !!
Sergio

Hide

8

Napisałem prostą klasę JavaScript, która implementuje technikę podobną do tej opisanej w żartobliwej odpowiedzi . Mam nadzieję, że może się tu przydać. Projekt GitHub nazywa się response-monitor.js

Domyślnie używa spin.js jako wskaźnika oczekiwania, ale zapewnia również zestaw zwrotnych implementacji niestandardowego wskaźnika.

JQuery jest obsługiwany, ale nie wymagany.

Ważne funkcje

  • Prosta integracja
  • Bez zależności
  • Wtyczka JQuery (opcjonalnie)
  • Integracja ze Spin.js (opcjonalnie)
  • Konfigurowalne wywołania zwrotne do monitorowania zdarzeń
  • Obsługuje wiele jednoczesnych żądań
  • Wykrywanie błędów po stronie serwera
  • Wykrywanie przekroczenia limitu czasu
  • Przeglądarka

Przykładowe użycie

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>

Klient (zwykły 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

Klient (JQuery)

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

Klient z wywołaniami zwrotnymi (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);

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

Aby uzyskać więcej przykładów, sprawdź folder przykładów w repozytorium.


5

Jestem spóźniony na przyjęcie, ale umieszczę to tutaj, jeśli ktoś inny chciałby poznać moje rozwiązanie:

Naprawdę zmagałem się z tym dokładnie problemem, ale znalazłem realne rozwiązanie przy użyciu ramek iframe (wiem, wiem. To okropne, ale działa na prosty problem, który miałem)

Miałem stronę HTML, która uruchomiła osobny skrypt php, który wygenerował plik, a następnie go pobrał. Na stronie html użyłem następującej jquery w nagłówku html (musisz również dołączyć bibliotekę 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>

Na your_download_script.php:

function downloadFile($file_path) {
    if (file_exists($file_path)) {
        header('Content-Description: File Transfer');
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename=' . basename($file_path));
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file_path));
        ob_clean();
        flush();
        readfile($file_path);
        exit();
    }
}


$_SESSION['your_file'] = path_to_file; //this is just how I chose to store the filepath

if (isset($_REQUEST['download']) && $_REQUEST['download'] == 'yes') {
    downloadFile($_SESSION['your_file']);
} else {
    *execute logic to create the file*
}

Aby rozwiązać ten problem, jquery najpierw uruchamia skrypt php w ramce iframe. Ramka iframe jest ładowana po wygenerowaniu pliku. Następnie jquery ponownie uruchamia skrypt ze zmienną żądania, która nakazuje skryptowi pobranie pliku.

Przyczyną niemożności pobrania i wygenerowania pliku za jednym razem jest funkcja php header (). Jeśli używasz header (), zmieniasz skrypt na coś innego niż strona internetowa i jquery nigdy nie rozpoznaje skryptu pobierania jako „załadowanego”. Wiem, że niekoniecznie wykrywa to, kiedy przeglądarka odbiera plik, ale twój problem brzmiał podobnie do mojego.


5

Jeśli przesyłasz strumieniowo plik, który generujesz dynamicznie, a także masz zaimplementowaną bibliotekę wiadomości między serwerami w czasie rzeczywistym, możesz dość łatwo ostrzec swojego klienta.

Biblioteczną komunikację między serwerami a klientami, którą lubię i polecam, to Socket.io (via Node.js). Po zakończeniu skryptu serwera generowanie pliku przesyłanego strumieniowo w celu pobrania ostatniej linii w tym skrypcie może wysłać komunikat do Socket.io, który wysyła powiadomienie do klienta. Na kliencie Socket.io nasłuchuje przychodzących wiadomości emitowanych z serwera i umożliwia działanie na nich. Zaletą korzystania z tej metody w porównaniu z innymi jest to, że po zakończeniu przesyłania strumieniowego można wykryć „prawdziwe” zdarzenie zakończenia.

Na przykład możesz pokazać wskaźnik zajętości po kliknięciu łącza pobierania, przesłać strumieniowo plik, wysłać wiadomość do Socket.io z serwera w ostatnim wierszu skryptu przesyłania strumieniowego, nasłuchiwać na kliencie powiadomienia, odbierać powiadomienia i zaktualizuj interfejs użytkownika, ukrywając wskaźnik zajętości.

Zdaję sobie sprawę, że większość osób czytających odpowiedzi na to pytanie może nie mieć tego typu konfiguracji, ale użyłem tego dokładnego rozwiązania z wielkim efektem w moich własnych projektach i działa on cudownie.

Socket.io jest niezwykle łatwy w instalacji i obsłudze. Zobacz więcej: http://socket.io/


5

„Jak wykryć, kiedy przeglądarka otrzymuje pobieranie pliku?”
Z tą konfiguracją napotkałem ten sam problem:
struts 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1.custom
IE 11
java 5


Moje rozwiązanie z plikiem cookie:
- po stronie klienta: przy
przesyłaniu formularza wywołaj funkcję javascript, aby ukryć stronę i załadować oczekujący spinner

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

Następnie wywołaj funkcję, która będzie sprawdzać co 500 ms, czy plik cookie pochodzi z serwera.

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

Jeśli plik cookie zostanie znaleziony, przestań sprawdzać co 500 ms , wygaś plik cookie i wywołaj swoją funkcję, aby wrócić do strony i usunąć oczekujące pokrętło (removeWaitingSpinner () ). Ważne jest, aby wygasnąć plik cookie, jeśli chcesz ponownie pobrać kolejny plik!

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

- Strona serwera:
pod koniec procesu serwera dodaj plik cookie do odpowiedzi. Ten plik cookie zostanie wysłany do klienta, gdy plik będzie gotowy do pobrania.

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

Mam nadzieję, że komuś pomogę!


Działa idealnie. Dzięki za tę piękną próbkę.
Sedat Kumcu

4

Gdy użytkownik uruchomi generowanie pliku, możesz po prostu przypisać unikalny identyfikator do tego „pobierania” i wysłać użytkownika na stronę, która odświeża (lub sprawdza za pomocą AJAX) co kilka sekund. Po zakończeniu pliku zapisz go pod tym samym unikalnym identyfikatorem i ...

  • Jeśli plik jest gotowy, wykonaj pobieranie.
  • Jeśli plik nie jest gotowy, pokaż postęp.

Następnie możesz pominąć cały bałagan iframe / waiting / browserwindow, ale mieć naprawdę eleganckie rozwiązanie.


To brzmi jak podejście do plików tymczasowych, o którym wspomniałem powyżej. Mógłbym zrobić coś takiego, jeśli okaże się, że mój pomysł jest niemożliwy, ale miałem nadzieję tego uniknąć.
JW.

3

Jeśli nie chcesz generować i przechowywać pliku na serwerze, czy chcesz zapisać status, np. Plik w toku, plik kompletny? Twoja strona „oczekiwania” może sondować serwer, aby dowiedzieć się, kiedy generowanie pliku zostało zakończone. Nie wiedziałbyś na pewno, że przeglądarka rozpoczęła pobieranie, ale byłbyś pewien.


2

Właśnie miałem ten sam problem. Moim rozwiązaniem było użycie plików tymczasowych, ponieważ generowałem już kilka plików tymczasowych. Formularz jest przesyłany z:

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

Na końcu skryptu generującego plik mam:

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

Spowoduje to uruchomienie zdarzenia ładowania w ramce iframe. Następnie komunikat oczekiwania zostanie zamknięty, a następnie rozpocznie się pobieranie pliku. Testowane na IE7 i Firefox.


2

Z mojego doświadczenia wynikają dwa sposoby:

  1. Ustaw krótkotrwały plik cookie podczas pobierania i JavaScript stale sprawdzaj jego istnienie. Jedynym prawdziwym problemem jest uzyskanie prawidłowego okresu istnienia pliku cookie - zbyt krótki, a JS może go przegapić, zbyt długo i może anulować ekrany pobierania innych pobrań. Użycie JS do usunięcia pliku cookie po wykryciu zwykle to rozwiązuje.
  2. Pobierz plik za pomocą fetch / XHR. Nie tylko wiesz dokładnie, kiedy kończy się pobieranie pliku, ale jeśli używasz XHR, możesz użyć zdarzeń postępu, aby wyświetlić pasek postępu! Zapisz wynikowy obiekt blob za pomocą msSaveBlob w IE / Edge i łącza pobierania ( takiego jak ten ) w Firefox / Chrome. Problem z tą metodą polega na tym, że iOS Safari nie obsługuje poprawnie pobierania obiektów blob - możesz przekonwertować obiekt blob na adres URL danych za pomocą FileReadera i otworzyć go w nowym oknie, ale to otwiera plik, a nie zapisuje.

2

Pozdrowienia, wiem, że temat jest stary, ale zostawiam rozwiązanie, które widziałem gdzie indziej i zadziałało:

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

Możesz tego użyć:

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

1

Jeśli masz pobrany plik, który jest zapisywany, a nie jest w dokumencie, nie ma sposobu, aby określić, kiedy pobieranie jest ukończone, ponieważ nie jest to zakres bieżącego dokumentu, ale osobny proces w przeglądarce.


8
Powinienem wyjaśnić - nie martwię się zbytnio, kiedy pobieranie się zakończy . Gdybym tylko mógł określić, kiedy pobieranie się rozpocznie, to by było na tyle.
JW.

0

Pytanie polega na tym, aby wskaźnik „oczekiwania” pojawiał się podczas generowania pliku, a następnie powracał do normalnego stanu po pobraniu pliku. Lubię to robić za pomocą ukrytej ramki iFrame i zaczepić zdarzenie onload ramki, aby moja strona wiedziała, kiedy pobieranie się rozpocznie. ALE onload nie uruchamia się w IE dla pobierania plików (jak w przypadku tokena nagłówka załącznika). Sondowanie serwera działa, ale nie lubię dodatkowej złożoności. Oto co robię:

  • Jak zwykle celuj w ukrytą ramkę iFrame.
  • Wygeneruj treść. Buforuj go z absolutnym limitem czasu w 2 minuty.
  • Wyślij przekierowanie javascript z powrotem do wywołującego klienta, zasadniczo wzywając stronę generatora po raz drugi. UWAGA: spowoduje to uruchomienie zdarzenia onload w IE, ponieważ działa jak zwykła strona.
  • Usuń zawartość z pamięci podręcznej i wyślij ją do klienta.

Oświadczenie, nie rób tego na ruchliwej stronie, ponieważ buforowanie może się sumować. Ale tak naprawdę, jeśli twoje witryny, które są zajęte długim procesem, i tak pozbawią Cię wątków.

Oto, jak wygląda ludzkość, i to wszystko, czego naprawdę potrzebujesz.

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

Szybkim rozwiązaniem, jeśli chcesz wyświetlić wiadomość lub program ładujący do momentu wyświetlenia okna pobierania, jest umieszczenie wiadomości w ukrytym kontenerze, a po kliknięciu przycisku generującego plik do pobrania, kontener staje się widoczny. Następnie użyj jquery lub javascript, aby złapać zdarzenie focusout przycisku, aby ukryć kontener zawierający wiadomość


0

Jeśli Xmlhttprequest z obiektem blob nie jest opcją, możesz otworzyć plik w nowym oknie i sprawdzić, czy elementy eny są zapełniane w tym oknie z przerwami.

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

Ten przykład Java / Spring wykrywa koniec pobierania, w którym to momencie ukrywa wskaźnik „Ładowanie ...”.

Podejście: po stronie JS ustaw plik cookie o maksymalnym wieku wygaśnięcia wynoszącym 2 minuty i sprawdzaj co sekundę, czy plik cookie wygasł . Następnie strona serwera zastępuje ten plik cookie z wcześniejszym wiekiem ważności - zakończeniem procesu serwera. Po wykryciu wygaśnięcia pliku cookie w sondowaniu JS „Ładowanie ...” jest ukryte.

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

Strona Java / Spring Server

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

Plik cookie jest tworzony przez skrypt JS, ale nie jest aktualizowany przez kontroler, zachowuje oryginalną wartość (0), jak mogę zaktualizować wartość pliku cookie bez odświeżania strony?
Shessuky,

To dziwne - można zapewnić, że nazwa jest dokładnie poprawne? Zastąpi plik cookie, jeśli nazwa będzie zgodna. Daj mi znać
gen b.

Pierwotna wartość nie wynosi 0. Oryginalna wartość ustawiona w JS wynosi 2 min. NOWA wartość, którą serwer ma modyfikować, to 0.
gen b.

Czy robisz to również: myCookie.setPath("/"); response.addCookie(myCookie);
gen b.

Doszedłem do wniosku (z jakiegoś powodu), że powinienem dodać pliki cookie przed wykonaniem response.getOutputStream (); (pobieranie strumienia wyjściowego odpowiedzi w celu dołączenia pobranych plików), nie został wzięty pod uwagę, gdy zrobiłem to po tym kroku
Shessuky

0

Primefaces również korzysta z pobierania plików 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

Utwórz element iframe po kliknięciu przycisku / łącza i dołącz go do treści.

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

Utwórz element iframe z opóźnieniem i usuń go po pobraniu.

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

Brakuje informacji i nie rozwiązuje problemu „kiedy ukryć ładowanie”.
Tom Roggero
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.