Tarayıcının dosya indirmesini ne zaman aldığını algıla


488

Kullanıcı dinamik olarak oluşturulan bir dosyayı indirmek için izin veren bir sayfam var. Üretilmesi uzun zaman alıyor, bu yüzden bir "bekleme" göstergesi göstermek istiyorum. Sorun şu ki, tarayıcı dosyayı aldığında nasıl tespit edeceğimi bulamıyorum, böylece göstergeyi gizleyebilirim.

Ben sunucuya POSTs ve sonuçları için gizli bir iframe hedefleyen gizli bir formda istek yapıyorum. Bu yüzden tüm tarayıcı penceresini sonuçla değiştirmem. İndirme tamamlandığında patlayacağını ümit ederek iframe'de bir "load" olayı dinliyorum.

Dosyayla birlikte bir "Content-Disposition: attachment" üstbilgisi döndürüyorum, bu da tarayıcının "Kaydet" iletişim kutusunu göstermesine neden oluyor. Ancak tarayıcı iframe'de "yükleme" etkinliğini başlatmaz.

Denediğim bir yaklaşım çok parçalı bir yanıt kullanmaktır. Böylece boş bir HTML dosyası ve ekli indirilebilir dosya gönderir. Örneğin:

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

Bu Firefox'ta çalışır; boş HTML dosyasını alır, "load" olayını tetikler, ardından indirilebilir dosya için "Kaydet" iletişim kutusunu gösterir. Ancak IE ve Safari'de başarısız oluyor; IE "load" olayını tetikler ancak dosyayı indirmez ve Safari dosyayı (yanlış ad ve içerik türüyle) indirir ve "load" olayını başlatmaz.

Farklı bir yaklaşım, dosya oluşturmayı başlatmak için bir çağrı yapmak, daha sonra hazır olana kadar sunucuyu yoklamak ve daha önce oluşturulmuş dosyayı indirmek olabilir. Ancak sunucuda geçici dosyalar oluşturmaktan kaçınmayı tercih ederim.

Daha iyi bir fikri olan var mı?


4
IE'nin hiçbir sürümü multipart / x-mixed-replace özelliğini desteklemez.
EricLaw

Teşekkürler Eric - bunu bilmek güzel. Bu yaklaşımla daha fazla zaman kaybetmeyeceğim.
JW.

Sadece güvenilir bir yol sunucu push bildirim (ASP.NET millet için SignalR) gibi görünüyor.
dudeNumber4

1
bennadel.com/blog/… - bu basit bir çözüm
Mateen

1
mateen teşekkürler dostum! gerçekten basit
Fai Zal Dong

Yanıtlar:


451

Olası çözümlerden biri istemcide JavaScript kullanır.

İstemci algoritması:

  1. Rastgele benzersiz bir simge oluşturun.
  2. İndirme isteğini gönderin ve jetonu bir GET / POST alanına ekleyin.
  3. "Bekleyen" göstergesini gösterin.
  4. Bir zamanlayıcı başlatın ve her saniyede bir "fileDownloadToken" (veya ne karar verirseniz verin) adında bir çerez arayın.
  5. Çerez varsa ve değeri jetonla eşleşiyorsa, "bekleme" göstergesini gizleyin.

Sunucu algoritması:

  1. İstekte GET / POST alanını bulun.
  2. Boş olmayan bir değeri varsa, bir çerez bırakın (örn. "FileDownloadToken") ve değerini belirtecin değerine ayarlayın.

İstemci kaynak kodu (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;
}

Örnek sunucu kodu (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();

Nerede:

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
Harika bir fikir, bunu jQuery / C # ile birden fazla dosya indirme hakkında bu cevap için temel bir çerçeve olarak kullandım
Greg

7
Diğerleri için bir uyarı: document.cookies indirilen dosyayı içermiyorsa, çerez yolunu kontrol edin. Benim durumumda, yol varsayılan olarak boş olsa bile sunucu tarafında "/" yolunu ayarlamak zorunda kaldım (örn. Java'da cookie.setPath ("/")). Bir süredir sorunun özel 'localhost' alan adı tanımlama bilgisi işlemesi ( stackoverflow.com/questions/1134290/… ) olduğunu düşündüm ama sonunda sorun bu değildi. Belki de okumaya değer olsa diğerleri için olabilir.
jlpp

2
@bulltorious çözümünüzü daha derinlemesine incelemeden önce, bir etki alanları arası dosya indirme isteği ile çalışıp çalışmayacağını merak ediyorum. Sence çerez mi yoksa çerez kısıtlamaları onu tehlikeye atacak mı?
kiks73

5
Harika - 100 yıl içinde bir dosya indirme işleminin parçası olarak çerezleri dahil etmek benim için olmazdı. Teşekkür ederim!!
freefaller

8
Diğerlerinin de belirttiği gibi, bu çözüm sorunun sadece bir kısmını çözer ve sunucunun dosya zamanını hazırlamasını bekler. Dosyanın boyutuna ve bağlantı hızına bağlı olarak önemli olabilecek sorunun diğer kısmı, dosyanın tamamını istemciye almanın ne kadar sürdüğüdür. Ve bu çözüm ile çözülmedi.
AsGoodAsIt

27

Çok basit (ve topal) bir satır çözümü, window.onblur()yükleme iletişim kutusunu kapatmak için olayı kullanmaktır . Elbette, çok uzun sürerse ve kullanıcı başka bir şey yapmaya karar verirse (e-postaları okumak gibi) yükleme iletişim kutusu kapanır.


Bu, onbeforeunloadTeşekkürler kullanılarak tetiklenen bir dosya yüklemesi için bir yükleme katmanından kurtulmak için ideal olan basit bir yaklaşımdır .
wf4

5
Bu, tüm tarayıcılarda çalışmaz (bazıları, geçerli pencereyi indirme iş akışının bir parçası olarak bırakmaz / bulanıklaştırmaz, örneğin Safari, bazı IE sürümleri, vb.).
hiattp

4
Chrome ve diğer tarayıcılar, bu koşulun başarısız olacağı dosyaları otomatik olarak indirir.
Şanslı

@ Şans sadece varsayılan olarak. Chrome'un kullanıcı indirme kaydedileceği yeri belirtin ve dolayısıyla iletişim kutusu göreceksiniz tamamen mümkündür
ESR

2
kötü fikir çünkü tabchange bulanıklığı veya pencere dışında herhangi bir eylem etkinleştirir
Michael

14

eski iplik, biliyorum ...

ancak google tarafından yönetilenler benim çözümümle ilgilenebilir. çok basit ama güvenilir. ve gerçek ilerleme mesajlarının görüntülenmesini mümkün kılar (ve mevcut süreçlere kolayca takılabilir):

işleyen komut dosyası (benim sorunum: dosyaları http yoluyla almak ve zip olarak teslim etmek) durumu oturuma yazar.

durum yoklanır ve her saniye görüntülenir. hepsi bu (Tamam, onun değil. [örneğin eşzamanlı indirme] ayrıntılar bir sürü bakmak zorunda, ama onun ;-) başlatmak için iyi bir yer).

indirme sayfası:

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

indir.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
ancak kullanıcının birden fazla penceresi veya indirmesi açıksa? Ayrıca sunucuya gereksiz bir çağrı
Yuki

3
Bir kullanıcıdan birden çok bağlantınız varsa, session_start () kullanıcı için oturumu kilitlediğinden ve diğer tüm işlemlerin ona erişmesini engellediğinden, bunların tümü diğer bağlantıların sona ermesini bekler.
Honza Kuchař

2
.each()olay kayıtları için kullanmanıza gerek yoktur . just say$('a.download').click()
robisrob

İçerideki kodu değerlendirmeyin setTimeout('getstatus()', 1000);. Doğrudan fn kullanın:setTimeout(getstatus, 1000);
Roko C. Buljan

11

ben lekeler indirmek ve indirdikten sonra nesne-url iptal etmek için aşağıdaki kullanın. krom ve firefox'ta çalışıyor!

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

dosya indirme iletişim kutusu kapatıldıktan sonra, odak odağı tetiklenecek şekilde pencere odağını geri alır.


Hala mod değiştirme ve modal gizlemek neden döndürme sorunu vardır.
dudeNumber4

9
Chrome gibi alt tepsiye indirilen tarayıcılar pencereyi asla bulanıklaştırmaz / odaklamaz.
Coleman

10

Elmer'in örneğine dayanarak kendi çözümümü hazırladım. Elemanlar tanımlı indirme sınıfına tıkladıktan sonra ekranda özel mesaj gösterilmesini sağlar. Mesajı gizlemek için odak tetikleyicisini kullandım .

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>

Şimdi indirmek için herhangi bir öğeyi uygulamalısınız:

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

veya

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

Her indirme tıklamasından sonra raporunuzun oluşturduğu mesajı göreceksiniz , lütfen bekleyin ...


2
Kullanıcı pencereyi tıklarsa ne olur?
Tom Roggero

Bu tam olarak ne aradığını, çok thaks !!
Sergio

Benim durumumda gizle () çağrılmıyor
Prashant Pimpale

8

Zorlayıcı cevapta tarif edilene benzer bir tekniği uygulayan basit bir JavaScript sınıfı yazdım . Umarım burada birisi için faydalı olabilir. GitHub projesine response-monitor.js adı verilir

Varsayılan olarak spin.js kullanır olarak bekleme göstergesi olarak , ancak özel bir göstergenin uygulanması için bir dizi geri arama da sağlar.

JQuery desteklenir, ancak zorunlu değildir.

Dikkate değer özellikler

  • Basit entegrasyon
  • Bağımlılık yok
  • JQuery eklentisi (isteğe bağlı)
  • Spin.js Entegrasyonu (isteğe bağlı)
  • Olayları izlemek için yapılandırılabilir geri aramalar
  • Birden fazla eşzamanlı isteği işler
  • Sunucu tarafı hata tespiti
  • Zaman aşımı tespiti
  • Çapraz tarayıcı

Örnek kullanım

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>

İstemci (düz 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

İstemci (JQuery)

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

Geri aramalara sahip istemci (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);

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

Daha fazla örnek için depodaki örnekler klasörünü kontrol edin .


5

Partiye çok geç kaldım ama başka biri benim çözümümü bilmek isterse buraya koyacağım:

Bu sorunla gerçek bir mücadele yaşadım ama iframe'leri kullanarak uygun bir çözüm buldum (biliyorum, biliyorum. Korkunç ama yaşadığım basit bir problem için çalışıyor)

Dosyayı oluşturan ve daha sonra indirilen ayrı bir php betiği başlatan bir html sayfası vardı. Html sayfasında, html başlığında aşağıdaki jquery kullandım (bir jquery kitaplığı da eklemeniz gerekir):

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

Download_script.php dosyasında aşağıdakilere sahip olun:

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

Bunu bozmak için jquery ilk önce bir iframe içinde php betiğinizi başlatır. İframe, dosya oluşturulduktan sonra yüklenir. Ardından jquery, komut dosyasını dosyayı indirmesini söyleyen bir istek değişkeni ile yeniden başlatır.

İndirme ve dosya oluşturma işlemlerini tek seferde yapamamanızın nedeni php header () işlevidir. Header () kullanırsanız, komut dosyasını web sayfasından başka bir şeye değiştirirsiniz ve jquery indirme komut dosyasını hiçbir zaman 'yüklü' olarak tanımaz. Bir tarayıcı bir dosya aldığında bunun algılanmayabileceğini biliyorum ama sorununuz benimkine benziyordu.


5

Dinamik olarak oluşturduğunuz bir dosyayı akışa alıyorsanız ve aynı zamanda gerçek zamanlı bir sunucudan istemciye mesajlaşma kitaplığı uyguladıysanız, istemcinizi kolayca uyarabilirsiniz.

Sevdiğim ve önerdiğim sunucudan istemciye mesajlaşma kütüphanesi Socket.io'dur (Node.js aracılığıyla). Sunucu komut dosyanız bittikten sonra, bu komut dosyasındaki son satırınızı indirmek için akış halinde olan dosyayı oluşturma Socket.io'ya istemciye bildirim gönderen bir ileti gönderebilir. İstemcide Socket.io, sunucudan yayılan gelen iletileri dinler ve bunlara göre hareket etmenizi sağlar. Bu yöntemi diğerlerine göre kullanmanın yararı, akış tamamlandıktan sonra bir "gerçek" bitiş olayını tespit edebilmenizdir.

Örneğin, bir indirme bağlantısı tıklandıktan sonra meşgul göstergenizi gösterebilir, dosyanızı aktarabilir, akış komut dosyanızın son satırındaki sunucudan Socket.io'ya bir mesaj gönderebilir, bir bildirim için istemciyi dinleyebilir, bildirimi alabilirsiniz ve meşgul göstergesini gizleyerek kullanıcı arayüzünüzü güncelleyin.

Bu sorunun cevabını okuyan çoğu insanın bu tür bir kurulum olmayabilir, ancak bu kesin çözümü kendi projelerimde büyük etki yaratmak için kullandım ve harika çalışıyor.

Socket.io kurulumu ve kullanımı inanılmaz derecede kolaydır. Daha fazlasını görün: http://socket.io/


5

"Tarayıcı dosya indirmeyi ne zaman alır?"
Aynı yapılandırma ile aynı sorunla karşılaştım:
struts 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1.custom
IE 11
java 5


Bir çerezle çözümüm:
- İstemci tarafı:
Formunuzu gönderirken, sayfanızı gizlemek ve bekleyen çeviricinizi yüklemek için javascript işlevinizi arayın

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

Ardından, sunucudan bir çerez gelip gelmediğini her 500 ms'de bir kontrol edecek bir işlev çağırın.

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

Çerez bulunursa, 500ms'de bir kontrol etmeyi bırakın, çerezi sona erdirin ve sayfanıza geri dönmek için işlevinizi çağırın ve bekleyen döndürücüyü kaldırın ( removeWaitingSpinner () ). Başka bir dosyayı tekrar indirebilmek istiyorsanız çerezin süresinin dolması önemlidir!

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

- Sunucu tarafı:
Sunucu işleminizin sonunda yanıta bir çerez ekleyin. Dosyanız indirilmeye hazır olduğunda bu çerez istemciye gönderilir.

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

Birisine yardım etmeyi umuyorum!


Mükemmel çalışıyor. Bu güzel örnek için teşekkürler.
Sedat Kumcu

4

Kullanıcı dosyanın oluşturulmasını tetiklediğinde, bu "indirmeye" benzersiz bir kimlik atayabilir ve kullanıcıyı birkaç saniyede bir yenilenen (veya AJAX ile kontrol eden) bir sayfaya gönderebilirsiniz. Dosya bittikten sonra, aynı benzersiz kimlik altında kaydedin ve ...

  • Dosya hazırsa, indirme işlemini yapın.
  • Dosya hazır değilse, ilerlemeyi gösterin.

Sonra tüm iframe / bekleme / tarayıcı penceresini atlayabilir, ancak gerçekten zarif bir çözüme sahip olabilirsiniz.


Yukarıda bahsettiğim geçici dosya yaklaşımı gibi geliyor. Fikrim imkansız olduğu ortaya çıkarsa böyle bir şey yapabilirdim, ama bundan kaçınmayı umuyordum.
JW.

3

Dosyayı sunucuda oluşturmak ve depolamak istemiyorsanız, durumu, örneğin, devam eden dosya, dosya tamamlandı gibi depolamak ister misiniz? "Bekleyen" sayfanız, sunucunun dosya oluşturma işleminin ne zaman tamamlandığını bilmesini sağlayabilir. Tarayıcının indirmeyi başlattığından emin olmazsınız, ancak biraz güveniniz olur.


2

Ben de aynı problemi yaşadım. Benim çözümüm zaten bir sürü geçici dosya oluşturduğumdan beri geçici dosyaları kullanmaktı. Form şu şekilde gönderilir:

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

Dosyayı oluşturan komut dosyasının sonunda:

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

Bu, iframe'deki load olayının tetiklenmesine neden olur. Ardından bekleme mesajı kapatılır ve dosya indirme işlemi başlar. IE7 ve Firefox'ta test edildi.


2

Deneyimlerime göre, bununla başa çıkmanın iki yolu vardır:

  1. İndirme işleminde kısa ömürlü bir çerez ayarlayın ve JavaScript'in varlığını sürekli kontrol etmesini sağlayın. Yalnızca gerçek sorun çerez ömrünü doğru yapmaktır - çok kısa ve JS çok uzun süre kaçırabilir ve diğer indirmeler için indirme ekranlarını iptal edebilir. Keşif üzerine çerezi kaldırmak için JS kullanmak genellikle bunu düzeltir.
  2. Getirme / XHR kullanarak dosyayı indirin. Sadece dosya indirme işleminin ne zaman bittiğini bilmekle kalmaz, XHR kullanıyorsanız ilerleme çubuğunu göstermek için ilerleme olaylarını kullanabilirsiniz! Sonuçta ortaya çıkan bloğu IE / Edge'de msSaveBlob ve Firefox / Chrome'da bir indirme bağlantısı (bunun gibi ) ile kaydedin . Bu yöntemin sorunu, iOS Safari'nin blob indirmeyi doğru işlemediği görülüyor - blob'u bir FileReader ile veri URL'sine dönüştürebilir ve yeni bir pencerede açabilirsiniz, ancak bu dosyayı kaydetmez.

2

Selamlar, konunun eski olduğunu biliyorum ama başka bir yerde gördüğüm bir çözüm bıraktım ve işe yaradı:

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

Bunu kullanabilirsiniz:

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

1

Kaydedilen bir dosyayı indirdiyseniz, belgede olmanın aksine, geçerli belgenin kapsamında değil, tarayıcıda ayrı bir işlem olduğundan, indirme işleminin ne zaman tamamlandığını belirlemenin bir yolu yoktur.


8
Ben Açıklamak - Ben "m de indirme sırasında ile ilgilenmiyorum tamamlanıncaya . Sadece zamanını indir başlar belirleyebilir, bu yeterli olacaktır.
. JW

0

Soru, bir dosya oluşturulurken bir 'bekleme' göstergesine sahip olmak ve ardından dosya indirildikten sonra normale dönmektir. Bunu yapmaktan hoşlandığım yol, gizli bir iFrame kullanmak ve indirme başladığında sayfamı bilgilendirmek için çerçevenin onload olayını bağlamaktır. AMA yükleme işlemi IE'de dosya indirmeleri için başlatılmaz (ek başlık jetonu gibi). Sunucu yoklama çalışıyor, ancak ekstra karmaşıklığı sevmiyorum. İşte yaptığım şey:

  • Gizli iFrame'i her zamanki gibi hedefleyin.
  • İçeriği oluşturun. 2 dakika içinde mutlak bir zaman aşımı ile önbellekleyin.
  • Bir javascript yönlendirmesi çağıran istemciye geri gönderilir, temelde jeneratör sayfası ikinci kez çağrılır. Not: Bu normal bir sayfa gibi davranıyor çünkü onload olay IE'de tetiklenmesine neden olur.
  • İçeriği önbellekten kaldırın ve istemciye gönderin.

Yasal Uyarı, yoğun bir sitede bunu yapmayın, çünkü önbelleğe alma ekleyebilir. Ama gerçekten, eğer uzun süren süreç meşgul siteleri siteleriniz zaten konu açlıktan.

İşte kod arkası neye benziyor, gerçekten ihtiyacınız olan şey bu.

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

Karşıdan yükleme iletişim kutusu görüntülenene kadar yalnızca bir ileti veya yükleyici gif'i görüntülemek istiyorsanız hızlı bir çözüm, iletiyi gizli bir kapsayıcıya koymaktır ve indirilecek dosyayı oluşturan düğmeyi tıklattığınızda kapsayıcı görünür hale gelir. Ardından, iletiyi içeren kapsayıcıyı gizlemek için düğmenin focusout olayını yakalamak için jquery veya javascript kullanın


0

Bloblu Xmlhttprequest bir seçenek değilse, dosyanızı yeni pencerede açabilir ve eny öğelerinin bu pencere gövdesinde aralıklarla doldurulup doldurulmadığını kontrol edebilirsiniz.

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

Bu Java / Spring örneği bir İndirme işleminin sonunu algılar ve bu noktada "Yükleniyor ..." göstergesini gizler.

Yaklaşım: JS tarafında, Maksimum Sona Erme Yaşı 2 dakika olan bir Çerez ayarlayın ve çerezin sona ermesi için her saniye anket yapın . Sonra sunucu tarafı bu çerezi daha önce geçersiz kılar sona erme yaşıyla (sunucu işleminin tamamlanması) . JS yoklamasında çerezin geçerliliği sona erdiğinde, "Yükleniyor ..." gizlenir.

JS Side

function buttonClick() { // Suppose this is the handler for the button that starts
    $("#loadingProgressOverlay").show();  // show loading animation
    startDownloadChecker("loadingProgressOverlay", 120);
    // Here you launch the download URL...
    window.location.href = "myapp.com/myapp/download";
}

// This JS function detects the end of a download.
// It does timed polling for a non-expired Cookie, initially set on the 
// client-side with a default max age of 2 min., 
// but then overridden on the server-side with an *earlier* expiration age 
// (the completion of the server operation) and sent in the response. 
// Either the JS timer detects the expired cookie earlier than 2 min. 
// (coming from the server), or the initial JS-created cookie expires after 2 min. 
function startDownloadChecker(imageId, timeout) {

    var cookieName = "ServerProcessCompleteChecker";  // Name of the cookie which is set and later overridden on the server
    var downloadTimer = 0;  // reference to timer object    

    // The cookie is initially set on the client-side with a specified default timeout age (2 min. in our application)
    // It will be overridden on the server side with a new (earlier) expiration age (the completion of the server operation), 
    // or auto-expire after 2 min.
    setCookie(cookieName, 0, timeout);

    // set timer to check for cookie every second
    downloadTimer = window.setInterval(function () {

        var cookie = getCookie(cookieName);

        // If cookie expired (NOTE: this is equivalent to cookie "doesn't exist"), then clear "Loading..." and stop polling
        if ((typeof cookie === 'undefined')) {
            $("#" + imageId).hide();
            window.clearInterval(downloadTimer);
        }

    }, 1000); // Every second
}

// These are helper JS functions for setting and retrieving a Cookie
function setCookie(name, value, expiresInSeconds) {
    var exdate = new Date();
    exdate.setTime(exdate.getTime() + expiresInSeconds * 1000);
    var c_value = escape(value) + ((expiresInSeconds == null) ? "" : "; expires=" + exdate.toUTCString());
    document.cookie = name + "=" + c_value + '; path=/';
}

function getCookie(name) {
    var parts = document.cookie.split(name + "=");
    if (parts.length == 2 ) {
        return parts.pop().split(";").shift();
    }
}

Java / Spring Sunucu Tarafı

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

Çerez JS betiği tarafından oluşturulur, ancak denetleyici tarafından güncellenmez, orijinal değeri (0) korur, sayfayı yenilemeden çerez değerini nasıl güncelleyebilirim?
Shessuky

Bu garip - adın tam olarak doğru olduğundan emin olabilir misiniz ? Ad eşleşirse çerezin üzerine yazılır. Bilmeme izin ver
gen b.

Orijinal değer 0 değil. JS'de ayarlanan orijinal değer 2 dakikadır. Sunucunun değiştirmesi gereken YENİ değer 0'dır.
gen b.

Ayrıca, bunu yapıyor musunuz: myCookie.setPath("/"); response.addCookie(myCookie);
gen b.

Ben yapmadan önce çerezleri eklemek gerekir ki (nedense) anladım response.getOutputStream (); (karşıdan yükleme dosyalarını eklemek için yanıt çıktı akışı alma), bu adımdan sonra bunu yaptığımda dikkate alınmadı
Shessuky

0

Primefaces çerez tanımlama bilgisini de kullanır

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

Düğme / bağlantı tıklatıldığında bir iframe oluşturun ve bunu gövdeye ekleyin.

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

Gecikmeli bir iframe oluşturun ve indirdikten sonra silin.

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

Bu bilgi eksiktir ve "yükleme ne zaman gizlenir" sorununu çözmez.
Tom Roggero
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.