Mendeteksi ketika browser menerima unduhan file

488

Saya memiliki halaman yang memungkinkan pengguna untuk mengunduh file yang dihasilkan secara dinamis. Butuh waktu lama untuk menghasilkan, jadi saya ingin menunjukkan indikator "menunggu". Masalahnya adalah, saya tidak tahu cara mendeteksi kapan browser menerima file, jadi saya bisa menyembunyikan indikator.

Saya membuat permintaan dalam bentuk tersembunyi, yang POST ke server, dan menargetkan iframe tersembunyi untuk hasilnya. Ini jadi saya tidak mengganti seluruh jendela browser dengan hasilnya. Saya mendengarkan acara "memuat" di iframe, dengan harapan bahwa itu akan menyala ketika unduhan selesai.

Saya mengembalikan header "Content-Disposition: attachment" dengan file, yang menyebabkan browser menampilkan dialog "Simpan". Tetapi peramban tidak memecat acara "memuat" di iframe.

Salah satu pendekatan yang saya coba adalah menggunakan respons multi-bagian. Jadi itu akan mengirim file HTML kosong, serta file yang dapat diunduh terlampir. Sebagai contoh:

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

Ini berfungsi di Firefox; ia menerima file HTML kosong, menyalakan acara "memuat", lalu menampilkan dialog "Simpan" untuk file yang dapat diunduh. Tetapi gagal pada IE dan Safari; IE memecat acara "memuat" tetapi tidak mengunduh file, dan Safari mengunduh file (dengan nama dan tipe konten yang salah), dan tidak memecat acara "memuat".

Pendekatan yang berbeda mungkin untuk membuat panggilan untuk memulai pembuatan file, lalu polling server sampai siap, lalu unduh file yang sudah dibuat. Tapi saya lebih suka menghindari membuat file sementara di server.

Adakah yang punya ide yang lebih baik?

JW.
sumber
4
Tidak ada versi IE yang mendukung multipart / x-campur-ganti.
EricLaw
Terima kasih Eric - itu bagus untuk diketahui. Saya tidak akan membuang waktu lagi dengan pendekatan itu.
JW.
Hanya cara yang andal yang tampaknya menjadi pemberitahuan push server (SignalR for ASP.NET people).
dudeNumber4
1
bennadel.com/blog/… - ini adalah solusi sederhana
Mateen
1
@mateen terima kasih Bung! itu sangat sederhana
Fai Zal Dong

Jawaban:

451

Salah satu solusi yang mungkin menggunakan JavaScript pada klien.

Algoritme klien:

  1. Buat token unik acak.
  2. Kirim permintaan unduhan, dan sertakan token di bidang GET / POST.
  3. Tampilkan indikator "menunggu".
  4. Mulai pengatur waktu, dan setiap detik, cari cookie bernama "fileDownloadToken" (atau apa pun yang Anda putuskan).
  5. Jika ada cookie, dan nilainya sesuai dengan token, sembunyikan indikator "waiting".

Algoritma server:

  1. Cari bidang GET / POST dalam permintaan.
  2. Jika memiliki nilai yang tidak kosong, jatuhkan cookie (mis. "FileDownloadToken"), dan tetapkan nilainya ke nilai token.

Kode sumber klien (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;
}

Contoh kode 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();

Dimana:

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
    );
}
banteng
sumber
4
Ide luar biasa, saya menggunakannya sebagai kerangka dasar untuk jawaban ini tentang mengunduh banyak file dengan jQuery / C #
Greg
7
A menuju yang lain: jika document.cookies tidak termasuk unduhanToken, periksa jalur cookie. Dalam kasus saya, saya harus menetapkan path ke "/" di sisi server (misalnya cookie.setPath ("/") di Jawa) meskipun path kosong secara default. Untuk beberapa waktu saya pikir masalahnya adalah penanganan cookie domain 'localhost' khusus ( stackoverflow.com/questions/1134290/... ) tapi itu bukan masalah pada akhirnya. Mungkin itu untuk orang lain meskipun begitu layak dibaca.
jlpp
2
@Bulltorious sebelum membahas lebih dalam dengan solusi Anda, saya ingin tahu apakah ini akan berfungsi dengan permintaan unduhan file domain silang. Apakah Anda pikir itu akan, atau pembatasan cookie akan membahayakannya?
kiks73
5
Brilliant - 100 tahun tidak akan terlintas di benak saya bahwa Anda dapat memasukkan cookie sebagai bagian dari unduhan file. Terima kasih!!
terjun bebas
8
Seperti yang orang lain tunjukkan, solusi ini hanya menyelesaikan sebagian dari masalah, menunggu server menyiapkan waktu file. Bagian lain dari masalah, yang bisa sangat tergantung pada ukuran file dan kecepatan koneksi, adalah berapa lama yang dibutuhkan untuk benar-benar mendapatkan seluruh file pada klien. Dan itu tidak diselesaikan dengan solusi ini.
AsGoodAsItGets
27

Solusi satu baris yang sangat sederhana (dan lumpuh) adalah menggunakan window.onblur()acara tersebut untuk menutup dialog pemuatan. Tentu saja, jika terlalu lama dan pengguna memutuskan untuk melakukan sesuatu yang lain (seperti membaca email) dialog pemuatan akan ditutup.

birdman
sumber
Ini adalah pendekatan sederhana yang ideal untuk menyingkirkan overlay pemuatan untuk unduhan file yang dipicu menggunakan onbeforeunloadTerima kasih.
wf4
5
Ini tidak berfungsi di semua browser (beberapa tidak meninggalkan / mengaburkan jendela saat ini sebagai bagian dari alur kerja unduhan, misalnya Safari, beberapa versi IE, dll).
hiattp
4
Chrome dan peramban semacam itu mengunduh secara otomatis file di mana kondisi ini akan gagal.
Lucky
@ Beruntung itu hanya secara default. Sangat mungkin pengguna Chrome akan menentukan di mana unduhan harus disimpan dan karenanya melihat kotak dialog
ESR
2
ide buruk karena Anda mengaktifkan blur di tabchange, atau tindakan apa pun di luar jendela
Michael
14

utas lama, saya tahu ...

tetapi mereka, yang dipimpin di sini oleh google mungkin tertarik pada solusi saya. itu sangat sederhana, namun dapat diandalkan. dan memungkinkan untuk menampilkan pesan kemajuan nyata (dan dapat dengan mudah dicolokkan ke proses yang ada):

skrip yang memproses (masalah saya adalah: mengambil file melalui http dan mengirimkannya sebagai zip) menulis status ke sesi.

statusnya disurvei dan ditampilkan setiap detik. itu saja (ok, tidak. Anda harus mengurus banyak detail [mis. unduhan bersamaan], tapi ini tempat yang bagus untuk memulai ;-)).

halaman unduhan:

    <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']);
?>

unduh.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
    ?>
bytepirate
sumber
3
tetapi apakah pengguna memiliki banyak jendela atau unduhan terbuka? Anda juga mendapatkan panggilan berlebihan ke server
Yuki
3
Jika Anda memiliki beberapa koneksi dari satu pengguna, mereka semua akan menunggu koneksi lain berakhir karena session_start () mengunci sesi untuk pengguna dan mencegah semua proses lain untuk mengaksesnya.
Honza Kuchař
2
Anda tidak perlu menggunakan .each()untuk pendaftaran acara. katakan saja$('a.download').click()
robisrob
Jangan membuat kode di dalam setTimeout('getstatus()', 1000);. Gunakan fn secara langsung:setTimeout(getstatus, 1000);
Roko C. Buljan
11

Saya menggunakan yang berikut ini untuk mengunduh gumpalan dan mencabut url-objek setelah unduhan. ini bekerja di chrome dan 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;
}

setelah dialog unduhan file ditutup, jendela mendapatkan kembali fokusnya sehingga acara fokus dipicu.

Elmer
sumber
Masih memiliki masalah switching window dan kembali yang akan menyebabkan modal bersembunyi.
dudeNumber4
9
Browser seperti Chrome yang mengunduh ke baki bawah tidak pernah mengaburkan / memfokuskan kembali jendela.
Coleman
10

Berdasarkan contoh Elmer, saya sudah menyiapkan solusi sendiri. Setelah elemen diklik dengan kelas unduhan yang ditentukan, ia memungkinkan untuk menampilkan pesan khusus di layar. Saya telah menggunakan pemicu fokus untuk menyembunyikan pesan.

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>

Sekarang Anda harus menerapkan elemen apa pun untuk mengunduh:

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

atau

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

Setelah setiap klik unduhan, Anda akan melihat pesan yang dibuat oleh laporan Anda, harap tunggu ...

Jerzy Gebler
sumber
2
Bagaimana jika pengguna mengklik jendela?
Tom Roggero
ini persis apa yang saya cari, banyak terima kasih !!
Sergio
Hide () tidak dipanggil dalam kasus saya
Prashant Pimpale
8

Saya menulis kelas JavaScript sederhana yang menerapkan teknik yang mirip dengan yang dijelaskan dalam jawaban bulltorious . Semoga bermanfaat bagi seseorang di sini. Proyek GitHub disebut response-monitor.js

Secara default menggunakan spin.js sebagai indikator menunggu tetapi juga menyediakan serangkaian panggilan balik untuk penerapan indikator khusus.

JQuery didukung tetapi tidak diperlukan.

Fitur-fitur penting

  • Integrasi sederhana
  • Tidak ada ketergantungan
  • Plug-in JQuery (opsional)
  • Integrasi Spin.js (opsional)
  • Panggilan balik yang dapat dikonfigurasi untuk memantau acara
  • Menangani beberapa permintaan simultan
  • Deteksi kesalahan sisi server
  • Deteksi batas waktu
  • Lintas browser

Contoh penggunaan

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>

Klien (JavaScript biasa)

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

Klien (JQuery)

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

Klien dengan panggilan balik (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

Untuk lebih banyak contoh, periksa folder contoh di repositori.

Jorge Paulo
sumber
5

Saya sangat terlambat ke pesta tetapi saya akan menempatkan ini di sini jika orang lain ingin tahu solusi saya:

Saya memiliki perjuangan nyata dengan masalah yang tepat ini, tetapi saya menemukan solusi yang layak menggunakan iframe (saya tahu, saya tahu. Ini mengerikan tetapi bekerja untuk masalah sederhana yang saya punya)

Saya memiliki halaman html yang meluncurkan skrip php terpisah yang menghasilkan file dan kemudian mengunduhnya. Pada halaman html, saya menggunakan jquery berikut di header html (Anda juga harus menyertakan perpustakaan 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>

Di your_download_script.php, miliki yang berikut ini:

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

Untuk memecah ini, jquery pertama meluncurkan skrip php Anda dalam iframe. Iframe dimuat setelah file dibuat. Kemudian jquery meluncurkan skrip lagi dengan variabel permintaan yang memberitahu skrip untuk mengunduh file.

Alasan Anda tidak dapat melakukan pengunduhan dan pembuatan file sekaligus adalah karena fungsi php header (). Jika Anda menggunakan tajuk (), Anda mengubah skrip ke sesuatu selain halaman web dan jquery tidak akan pernah mengenali skrip unduhan sebagai 'dimuat'. Saya tahu ini mungkin tidak selalu mendeteksi ketika browser menerima file tetapi masalah Anda terdengar mirip dengan milikku.

Walker Boh
sumber
5

Jika Anda streaming file yang Anda hasilkan secara dinamis, dan juga memiliki perpustakaan perpesanan server-ke-klien yang realtime, Anda dapat memberi tahu klien Anda dengan mudah.

Perpustakaan perpesanan server-ke-klien yang saya sukai dan rekomendasikan adalah Socket.io (via Node.js). Setelah skrip server Anda selesai menghasilkan file yang sedang di-stream untuk mengunduh baris terakhir Anda di skrip itu dapat memancarkan pesan ke Socket.io yang mengirimkan pemberitahuan kepada klien. Pada klien, Socket.io mendengarkan pesan masuk yang dipancarkan dari server dan memungkinkan Anda untuk menindaklanjutinya. Manfaat menggunakan metode ini dibandingkan yang lain adalah Anda dapat mendeteksi acara selesai "benar" setelah streaming selesai.

Misalnya, Anda dapat menampilkan indikator sibuk Anda setelah tautan unduhan diklik, mengalirkan file Anda, mengirimkan pesan ke Socket.io dari server di baris terakhir skrip streaming Anda, dengarkan klien untuk pemberitahuan, terima pemberitahuan dan perbarui UI Anda dengan menyembunyikan indikator sibuk.

Saya menyadari sebagian besar orang membaca jawaban untuk pertanyaan ini mungkin tidak memiliki jenis pengaturan ini, tetapi saya telah menggunakan solusi tepat ini untuk efek yang besar dalam proyek saya sendiri dan itu bekerja dengan sangat baik.

Socket.io sangat mudah dipasang dan digunakan. Lihat lebih lanjut: http://socket.io/

Seni Geigel
sumber
5

"Bagaimana cara mendeteksi ketika browser menerima unduhan file?"
Saya menghadapi masalah yang sama dengan konfigurasi itu:
struts 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1.custom
IE 11
java 5


Solusi saya dengan cookie:
- Sisi klien:
Saat mengirimkan formulir Anda, panggil fungsi javascript Anda untuk menyembunyikan halaman Anda dan memuat spinner yang menunggu Anda

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

Kemudian, panggil fungsi yang akan memeriksa setiap 500 ms apakah cookie berasal dari server.

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

Jika cookie ditemukan, berhenti memeriksa setiap 500ms, kedaluwarsa cookie dan panggil fungsi Anda untuk kembali ke halaman Anda dan menghapus spinner yang menunggu ( removeWaitingSpinner () ). Penting untuk kedaluwarsa cookie jika Anda ingin dapat mengunduh file lain lagi!

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

- Sisi server:
Di akhir proses server Anda, tambahkan cookie ke respons. Cookie itu akan dikirim ke klien ketika file Anda siap untuk diunduh.

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

Saya berharap dapat membantu seseorang!

MB33
sumber
Ini bekerja dengan sempurna. Terima kasih atas sampel cantik ini.
Sedat Kumcu
4

Ketika pengguna memicu pembuatan file, Anda bisa menetapkan ID unik untuk "unduhan" itu, dan mengirim pengguna ke halaman yang menyegarkan (atau memeriksa dengan AJAX) setiap beberapa detik. Setelah file selesai, simpan di bawah ID unik yang sama dan ...

  • Jika file sudah siap, lakukan unduhan.
  • Jika file tidak siap, tunjukkan progresnya.

Kemudian Anda dapat melewati seluruh kekacauan iframe / waiting / browserwindow, namun memiliki solusi yang sangat elegan.

gahooa
sumber
Kedengarannya seperti pendekatan file sementara yang saya sebutkan di atas. Saya mungkin melakukan sesuatu seperti ini jika ternyata ide saya tidak mungkin, tetapi saya berharap untuk menghindarinya.
JW.
3

Jika Anda tidak ingin membuat dan menyimpan file di server, apakah Anda bersedia menyimpan statusnya, misal file-in-progress, file-complete? Halaman "menunggu" Anda dapat mengumpulkan informasi dari server untuk mengetahui kapan pembuatan file selesai. Anda tidak akan tahu pasti bahwa browser memulai unduhan tetapi Anda akan memiliki kepercayaan diri.

bmb
sumber
2

Saya baru saja mengalami masalah yang sama persis ini. Solusi saya adalah menggunakan file sementara karena saya sudah menghasilkan banyak file sementara. Formulir dikirimkan dengan:

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

Di akhir skrip yang menghasilkan file yang saya miliki:

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

Ini akan menyebabkan acara pemuatan di iframe dipecat. Kemudian pesan tunggu ditutup dan unduhan file akan dimulai. Diuji pada IE7 dan Firefox.

grom
sumber
2

Dalam pengalaman saya, ada dua cara untuk menangani ini:

  1. Tetapkan cookie berumur pendek pada unduhan, dan minta JavaScript untuk terus memeriksa keberadaannya. Hanya masalah nyata adalah memperbaiki cookie seumur hidup - terlalu pendek dan JS dapat melewatkannya, terlalu lama dan mungkin membatalkan layar unduhan untuk unduhan lainnya. Menggunakan JS untuk menghapus cookie setelah ditemukan biasanya memperbaiki ini.
  2. Unduh file menggunakan fetch / XHR. Anda tidak hanya tahu persis kapan unduhan file selesai, jika Anda menggunakan XHR Anda dapat menggunakan progres acara untuk menampilkan bilah progres! Simpan gumpalan yang dihasilkan dengan msSaveBlob di IE / Edge dan tautan unduhan ( seperti ini ) di Firefox / Chrome. Masalah dengan metode ini adalah bahwa iOS Safari tampaknya tidak menangani pengunduhan gumpalan dengan benar - Anda dapat mengubah gumpalan itu menjadi URL data dengan FileReader dan membukanya di jendela baru, tapi itu membuka file, bukan menyimpannya.
Sora2455
sumber
2

Salam, saya tahu bahwa topiknya sudah lama tetapi saya meninggalkan solusi yang saya lihat di tempat lain dan berhasil:

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

Anda bisa menggunakannya:

downloadURI("www.linkToFile.com", "file.name");
Manuel Larrota
sumber
1

Jika Anda telah mengunduh file, yang disimpan, sebagai lawan berada di dalam dokumen, tidak ada cara untuk menentukan kapan unduhan selesai, karena itu bukan dalam lingkup dokumen saat ini, tetapi merupakan proses terpisah di browser.

Diodeus - James MacFarlane
sumber
8
Saya harus mengklarifikasi - Saya tidak terlalu peduli dengan kapan pengunduhan selesai . Jika saya bisa mengidentifikasi kapan pengunduhan dimulai, itu sudah cukup.
JW.
0

Pertanyaannya adalah memiliki indikator 'menunggu' saat file dibuat dan kemudian kembali normal setelah file diunduh. Cara saya suka melakukan ini adalah menggunakan iFrame tersembunyi dan mengaitkan aktivitas muatan frame untuk memberi tahu halaman saya saat unduhan dimulai. TAPI onload tidak diaktifkan di IE untuk unduhan file (seperti dengan token header lampiran). Polling server berfungsi, tapi saya tidak suka kompleksitas ekstra. Jadi inilah yang saya lakukan:

  • Targetkan iFrame tersembunyi seperti biasa.
  • Buat konten. Cache dengan batas waktu absolut dalam 2 menit.
  • Kirim javascript redirect kembali ke klien panggilan, pada dasarnya memanggil halaman generator untuk kedua kalinya. CATATAN: ini akan menyebabkan peristiwa onload menyala di IE karena bertindak seperti halaman biasa.
  • Hapus konten dari cache dan kirimkan ke klien.

Penafian, jangan lakukan ini di situs yang sibuk, karena caching bisa bertambah. Tapi sungguh, jika situs Anda yang sibuk proses yang berjalan lama akan membuat Anda kelaparan utas.

Inilah yang terlihat seperti codebehind, yang benar-benar Anda butuhkan.

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

Solusi cepat jika Anda hanya ingin menampilkan pesan atau loader gif sampai dialog unduhan ditampilkan adalah meletakkan pesan dalam wadah tersembunyi dan ketika Anda mengklik tombol yang menghasilkan file yang akan diunduh, Anda membuat wadah tersebut terlihat. Kemudian gunakan jquery atau javascript untuk menangkap acara fokus tombol untuk menyembunyikan wadah yang berisi pesan

Mihaela Rada
sumber
0

Jika Xmlhttprequest dengan blob bukan opsi maka Anda dapat membuka file Anda di jendela baru dan memeriksa apakah ada elemen yang terisi dalam jendela itu dengan interval.

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

aniketKumkar
sumber
0

Contoh Java / Spring ini mendeteksi akhir suatu Unduhan, yang pada saat itu menyembunyikan indikator "Memuat ...".

Pendekatan: Di sisi JS, atur Cookie dengan Max Expiration Age of 2 min, dan polling setiap detik untuk cookie expiration . Kemudian sisi server menimpa cookie ini dengan usia kedaluwarsa yang lebih awal - penyelesaian proses server. Segera setelah cookie kedaluwarsa terdeteksi di polling JS, "Memuat ..." disembunyikan.

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

Sisi Server Java / Musim Semi

    @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;
}
gen b.
sumber
Cookie dibuat oleh skrip JS tetapi tidak diperbarui oleh controller, ia mempertahankan nilai asli (0), bagaimana saya bisa memperbarui nilai cookie tanpa menyegarkan halaman?
Shessuky
Itu aneh - dapat Anda memastikan nama yang tepat benar? Itu akan menimpa cookie jika namanya cocok. Beri tahu saya
gen b.
Nilai aslinya bukan 0. Nilai asli yang diatur dalam JS adalah 2 menit. Nilai BARU yang seharusnya diubah oleh server adalah 0.
gen b.
Juga, apakah Anda melakukan ini: myCookie.setPath("/"); response.addCookie(myCookie);
gen b.
Saya menemukan (untuk beberapa alasan), bahwa saya harus menambahkan cookie sebelum melakukan response.getOutputStream (); (mendapatkan aliran output respons untuk menambahkan file unduhan), itu tidak diperhitungkan ketika saya melakukannya setelah langkah itu
Shessuky
0

Primefaces juga menggunakan polling 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);
        }
    },
wutzebaer
sumber
-2

Buat iframe saat tombol / tautan diklik dan tambahkan ini ke badan.

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

Buat iframe dengan penundaan dan hapus setelah diunduh.

                            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);
Pir Abdul
sumber
Ini kurang informasi dan tidak menyelesaikan masalah "kapan harus menyembunyikan pemuatan".
Tom Roggero