Phát hiện khi trình duyệt nhận tệp tải xuống


488

Tôi có một trang cho phép người dùng tải xuống một tệp được tạo động. Phải mất một thời gian dài để tạo, vì vậy tôi muốn hiển thị một chỉ báo "chờ". Vấn đề là, tôi không thể tìm ra cách phát hiện khi trình duyệt nhận được tệp, vì vậy tôi có thể ẩn chỉ báo.

Tôi đang thực hiện yêu cầu ở dạng ẩn, gửi POST đến máy chủ và nhắm mục tiêu iframe ẩn cho kết quả của nó. Điều này là do đó tôi không thay thế toàn bộ cửa sổ trình duyệt bằng kết quả. Tôi lắng nghe một sự kiện "tải" trên iframe, với hy vọng nó sẽ kích hoạt khi quá trình tải xuống hoàn tất.

Tôi trả lại tiêu đề "Xử lý nội dung: tệp đính kèm" với tệp, khiến trình duyệt hiển thị hộp thoại "Lưu". Nhưng trình duyệt không kích hoạt sự kiện "tải" trong iframe.

Một cách tiếp cận tôi đã thử là sử dụng phản hồi đa phần. Vì vậy, nó sẽ gửi một tệp HTML trống, cũng như tệp có thể tải xuống được đính kèm. Ví dụ:

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

Điều này hoạt động trong Firefox; nó nhận được tệp HTML trống, kích hoạt sự kiện "tải", sau đó hiển thị hộp thoại "Lưu" cho tệp có thể tải xuống. Nhưng nó thất bại trên IE và Safari; IE kích hoạt sự kiện "tải" nhưng không tải xuống tệp và Safari tải xuống tệp (với tên và loại nội dung sai) và không kích hoạt sự kiện "tải".

Một cách tiếp cận khác có thể là thực hiện cuộc gọi để bắt đầu tạo tệp, sau đó thăm dò máy chủ cho đến khi sẵn sàng, sau đó tải xuống tệp đã được tạo. Nhưng tôi muốn tránh tạo các tệp tạm thời trên máy chủ.

Có ai có một ý tưởng tốt hơn?


4
Không có phiên bản IE nào hỗ trợ nhiều phần / x-hỗn hợp thay thế.
EricLaw

Cảm ơn Eric - thật tốt khi biết điều đó. Tôi sẽ không lãng phí thêm thời gian với cách tiếp cận đó.
JW.

Chỉ có cách đáng tin cậy dường như là thông báo đẩy máy chủ (SignalR cho ASP.NET folks).
dudeNumber4

1
bennadel.com/blog/ Kẻ - đây là một giải pháp đơn giản
Mateen

1
@mateen cảm ơn anh bạn! nó thực sự đơn giản
Fai Zal Dong

Câu trả lời:


451

Một giải pháp có thể sử dụng JavaScript trên máy khách.

Thuật toán máy khách:

  1. Tạo mã thông báo duy nhất ngẫu nhiên.
  2. Gửi yêu cầu tải xuống và bao gồm mã thông báo trong trường GET / POST.
  3. Hiển thị chỉ báo "chờ".
  4. Bắt đầu hẹn giờ và cứ sau một giây, hãy tìm một cookie có tên "fileD DownloadToken" (hoặc bất cứ điều gì bạn quyết định).
  5. Nếu cookie tồn tại và giá trị của nó khớp với mã thông báo, hãy ẩn chỉ báo "chờ".

Thuật toán máy chủ:

  1. Tìm trường GET / POST trong yêu cầu.
  2. Nếu nó có giá trị không trống, hãy bỏ cookie (ví dụ: "fileDoadToken") và đặt giá trị của nó thành giá trị của mã thông báo.

Mã nguồn của máy khách (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;
}

Mã máy chủ ví dụ (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();

Ở đâu:

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
Ý tưởng tuyệt vời, tôi đã sử dụng nó làm khung cơ bản cho câu trả lời này về việc tải xuống nhiều tệp bằng jQuery / C #
Greg

7
Hướng dẫn cho người khác: nếu document.cookies không bao gồm downloadToken, hãy kiểm tra đường dẫn cookie. Trong trường hợp của tôi, tôi đã phải đặt đường dẫn thành "/" ở phía máy chủ (ví dụ: cookie.setPath ("/") trong Java) mặc dù đường dẫn mặc định bị trống. Đôi khi tôi nghĩ rằng vấn đề là việc xử lý cookie tên miền 'localhost' đặc biệt ( stackoverflow.com/questions/1134290/ mẹo ) nhưng cuối cùng thì đó không phải là vấn đề. Có thể là cho những người khác mặc dù rất đáng đọc.
jlpp

2
@bulltorious trước khi đi sâu hơn với giải pháp của bạn, tôi tự hỏi liệu nó có hoạt động với yêu cầu tải xuống tệp tên miền chéo hay không. Bạn có nghĩ rằng nó sẽ, hoặc hạn chế cookie sẽ thỏa hiệp nó?
kiks73

5
Rực rỡ - điều đó đã không xảy ra với tôi trong 100 năm qua rằng bạn có thể bao gồm cookie như là một phần của tải xuống tệp. Cảm ơn bạn!!
rơi tự do

8
Như những người khác đã chỉ ra, giải pháp này chỉ giải quyết được một phần của vấn đề, chờ máy chủ chuẩn bị thời gian tập tin. Phần khác của vấn đề, có thể là đáng kể tùy thuộc vào kích thước của tệp và tốc độ kết nối, là mất bao lâu để thực sự có được toàn bộ tệp trên máy khách. Và điều đó không được giải quyết với giải pháp này.
AsoodAsItGets

27

Một giải pháp một dòng rất đơn giản (và khập khiễng) là sử dụng window.onblur()sự kiện để đóng hộp thoại tải. Tất nhiên, nếu mất quá nhiều thời gian và người dùng quyết định làm một việc khác (như đọc email), hộp thoại tải sẽ đóng lại.


Đây là một cách tiếp cận đơn giản, lý tưởng để loại bỏ lớp phủ tải cho tải xuống tệp được kích hoạt bằng onbeforeunloadCảm ơn.
wf4

5
Điều này không hoạt động trong tất cả các trình duyệt (một số không rời / làm mờ cửa sổ hiện tại như một phần của quy trình tải xuống, ví dụ Safari, một số phiên bản IE, v.v.).
hiattp

4
Chrome và các trình duyệt khác như vậy tự động tải xuống các tệp trong đó điều kiện này sẽ không thành công.
May mắn

@Lucky đó chỉ là mặc định. Người dùng Chrome hoàn toàn có thể chỉ định nơi lưu các bản tải xuống và do đó sẽ thấy hộp thoại
ESR

2
ý tưởng tồi vì bạn kích hoạt làm mờ trên tabchange hoặc bất kỳ hành động nào bên ngoài cửa sổ
Michael

14

chủ đề cũ, tôi biết ...

nhưng những người được dẫn đến đây bởi google có thể quan tâm đến giải pháp của tôi. nó rất đơn giản, nhưng đáng tin cậy và nó cho phép hiển thị các thông báo tiến trình thực (và có thể dễ dàng cắm vào các quy trình hiện có):

tập lệnh xử lý (vấn đề của tôi là: truy xuất tệp qua http và gửi chúng dưới dạng zip) ghi trạng thái vào phiên.

trạng thái được thăm dò và hiển thị mỗi giây. đó là tất cả (ok, không phải vậy. bạn phải quan tâm rất nhiều chi tiết [ví dụ: tải xuống đồng thời], nhưng đó là một nơi tốt để bắt đầu ;-)).

trang tải về:

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

tải về

    <?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
Nhưng nếu người dùng có nhiều cửa sổ hoặc tải xuống mở? bạn cũng nhận được ở đây một cuộc gọi dự phòng đến máy chủ
Yuki

3
Nếu bạn có nhiều kết nối từ một người dùng, tất cả họ sẽ chờ kết nối khác kết thúc vì session_start () khóa phiên cho người dùng và ngăn tất cả các quy trình khác truy cập vào nó.
Honza Kuchař

2
bạn không cần phải sử dụng .each()để đăng ký sự kiện. chỉ cần nói$('a.download').click()
robisrob

Đừng mã eval bên trong setTimeout('getstatus()', 1000);. Sử dụng fn trực tiếp:setTimeout(getstatus, 1000);
Roko C. Buljan

11

tôi sử dụng cách sau để tải xuống các đốm màu và thu hồi url đối tượng sau khi tải xuống. nó hoạt động trong chrome và 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;
}

sau khi hộp thoại tải xuống tệp được đóng lại, cửa sổ sẽ lấy lại tiêu điểm của cô ấy để sự kiện lấy nét được kích hoạt.


Vẫn có vấn đề chuyển đổi cửa sổ và trở lại sẽ làm cho phương thức ẩn.
dudeNumber4

9
Các trình duyệt như Chrome tải xuống khay dưới cùng không bao giờ làm mờ / lấy nét lại cửa sổ.
Coleman

10

Dựa trên ví dụ của Elmer tôi đã chuẩn bị giải pháp của riêng mình. Sau khi các phần tử nhấp vào với lớp tải xuống được xác định, nó cho phép hiển thị thông báo tùy chỉnh trên màn hình. Tôi đã sử dụng kích hoạt tập trung để ẩn tin nhắn.

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>

Bây giờ bạn nên thực hiện bất kỳ yếu tố nào để tải xuống:

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

hoặc là

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

Sau mỗi lần tải xuống, bạn sẽ thấy thông báo mà báo cáo của mình đang tạo, vui lòng đợi ...


2
Nếu người dùng nhấp vào cửa sổ thì sao?
Tom Roggero

đây chính xác là những gì tôi đang tìm kiếm, rất nhiều !!
Sergio

Ẩn () không được gọi trong trường hợp của tôi
Prashant Pimpale

8

Tôi đã viết một lớp JavaScript đơn giản thực hiện một kỹ thuật tương tự như một mô tả trong câu trả lời nhảm nhí . Tôi hy vọng nó có thể hữu ích cho ai đó ở đây. Dự án GitHub được gọi là answer-Monitor.js

Theo mặc định, nó sử dụng spin.js làm chỉ báo chờ nhưng nó cũng cung cấp một bộ các cuộc gọi lại để thực hiện một chỉ báo tùy chỉnh.

JQuery được hỗ trợ nhưng không bắt buộc.

Các tính năng đáng chú ý

  • Tích hợp đơn giản
  • Không phụ thuộc
  • Trình cắm JQuery (tùy chọn)
  • Tích hợp Spin.js (tùy chọn)
  • Cuộc gọi lại có thể cấu hình để theo dõi các sự kiện
  • Xử lý nhiều yêu cầu đồng thời
  • Phát hiện lỗi phía máy chủ
  • Hết thời gian phát hiện
  • Qua trình duyệt

Ví dụ sử dụng

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>

Máy khách (JavaScript đơn giản)

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

Máy khách (JQuery)

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

Máy khách có cuộc gọi lại (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);

Máy chủ (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

Để biết thêm ví dụ kiểm tra thư mục ví dụ trên kho lưu trữ.


5

Tôi đến bữa tiệc rất muộn nhưng tôi sẽ đưa nó lên đây nếu có ai muốn biết giải pháp của tôi:

Tôi đã có một cuộc đấu tranh thực sự với vấn đề chính xác này nhưng tôi đã tìm thấy một giải pháp khả thi bằng cách sử dụng iframe (tôi biết, tôi biết. Thật khủng khiếp nhưng nó hoạt động cho một vấn đề đơn giản mà tôi gặp phải)

Tôi đã có một trang html khởi chạy một tập lệnh php riêng tạo tệp và sau đó tải xuống. Trên trang html, tôi đã sử dụng jquery sau trong tiêu đề html (bạn cũng cần bao gồm thư viện 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>

Trên your_doad_script.php, có các mục sau:

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

Để phá vỡ điều này, trước tiên jquery khởi chạy tập lệnh php của bạn trong iframe. Khung nội tuyến được tải sau khi tệp được tạo. Sau đó, jquery khởi chạy lại tập lệnh với biến yêu cầu thông báo tập lệnh để tải tập tin.

Lý do bạn không thể thực hiện tải xuống và tạo tệp tất cả trong một lần là do hàm php title (). Nếu bạn sử dụng tiêu đề (), bạn đang thay đổi tập lệnh thành một thứ khác ngoài trang web và jquery sẽ không bao giờ nhận ra tập lệnh tải xuống là 'được tải'. Tôi biết điều này có thể không nhất thiết phải được phát hiện khi trình duyệt nhận được một tệp nhưng vấn đề của bạn có vẻ giống với tôi.


5

Nếu bạn đang truyền phát một tệp mà bạn đang tạo động và cũng có thư viện nhắn tin máy chủ đến máy khách thời gian thực được triển khai, bạn có thể cảnh báo khách hàng của mình khá dễ dàng.

Thư viện nhắn tin từ máy chủ đến máy khách mà tôi thích và giới thiệu là Socket.io (thông qua Node.js). Sau khi tập lệnh máy chủ của bạn hoàn tất việc tạo tệp đang được truyền phát để tải xuống dòng cuối cùng của bạn trong tập lệnh đó có thể phát ra một thông báo tới Socket.io để gửi thông báo cho khách hàng. Trên máy khách, Socket.io lắng nghe các tin nhắn đến được phát ra từ máy chủ và cho phép bạn hành động trên chúng. Lợi ích của việc sử dụng phương pháp này so với các phương pháp khác là bạn có thể phát hiện sự kiện kết thúc "thật" sau khi truyền phát xong.

Ví dụ: bạn có thể hiển thị chỉ báo bận của mình sau khi nhấp vào liên kết tải xuống, truyền phát tệp của bạn, phát thông báo đến Socket.io từ máy chủ trong dòng cuối cùng của tập lệnh phát trực tuyến, nghe thông báo trên máy khách để nhận thông báo, nhận thông báo và cập nhật giao diện người dùng của bạn bằng cách ẩn chỉ báo bận.

Tôi nhận ra hầu hết mọi người đọc câu trả lời cho câu hỏi này có thể không có kiểu thiết lập này, nhưng tôi đã sử dụng giải pháp chính xác này để mang lại hiệu quả tuyệt vời trong các dự án của riêng tôi và nó hoạt động rất tuyệt vời.

Socket.io cực kỳ dễ cài đặt và sử dụng. Xem thêm: http://socket.io/


5

"Làm cách nào để phát hiện khi trình duyệt nhận tệp tải xuống?"
Tôi gặp vấn đề tương tự với cấu hình đó:
struts 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1. Tùy chỉnh
IE 11
java 5


Giải pháp của tôi với cookie:
- Phía khách hàng:
Khi gửi biểu mẫu của bạn, hãy gọi chức năng javascript của bạn để ẩn trang của bạn và tải spinner chờ đợi của bạn

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

Sau đó, gọi một chức năng sẽ kiểm tra cứ sau 500ms xem cookie có đến từ máy chủ không.

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

Nếu cookie được tìm thấy, hãy dừng kiểm tra sau mỗi 500ms, hết hạn cookie và gọi chức năng của bạn để quay lại trang của bạn và xóa spinner chờ đợi ( removeWaitingSpinner () ). Điều quan trọng là hết hạn cookie nếu bạn muốn có thể tải xuống một tệp khác một lần nữa!

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

- Phía máy chủ:
Khi kết thúc quá trình máy chủ của bạn, hãy thêm cookie vào phản hồi. Cookie đó sẽ được gửi cho khách hàng khi tệp của bạn sẽ sẵn sàng để tải xuống.

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

Tôi hy vọng sẽ giúp được ai đó!


Nó hoạt động hoàn hảo. Cảm ơn mẫu đẹp này.
Sedat Kumcu

4

Khi người dùng kích hoạt việc tạo tệp, bạn chỉ cần gán một ID duy nhất cho "tải xuống" đó và gửi người dùng đến một trang làm mới (hoặc kiểm tra bằng AJAX) cứ sau vài giây. Khi tệp kết thúc, hãy lưu nó dưới cùng một ID duy nhất đó và ...

  • Nếu tập tin đã sẵn sàng, hãy tải xuống.
  • Nếu tập tin chưa sẵn sàng, hãy hiển thị tiến trình.

Sau đó, bạn có thể bỏ qua toàn bộ iframe / Wait / browserwindow mess, nhưng vẫn có một giải pháp thực sự thanh lịch.


Nghe có vẻ như cách tiếp cận tập tin tạm thời tôi đã đề cập ở trên. Tôi có thể làm điều gì đó như thế này nếu hóa ra ý tưởng của tôi là không thể, nhưng tôi đã hy vọng tránh được nó.
JW.

3

Nếu bạn không muốn tạo và lưu trữ tệp trên máy chủ, bạn có sẵn sàng lưu trữ trạng thái, ví dụ: tệp đang xử lý, tệp hoàn tất không? Trang "chờ" của bạn có thể thăm dò máy chủ để biết khi nào việc tạo tệp hoàn tất. Bạn sẽ không biết chắc chắn rằng trình duyệt đã bắt đầu tải xuống nhưng bạn có chút tự tin.


2

Tôi chỉ có vấn đề chính xác như vậy. Giải pháp của tôi là sử dụng các tệp tạm thời vì tôi đã tạo ra một loạt các tệp tạm thời. Mẫu này được gửi với:

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

Ở phần cuối của tập lệnh tạo tập tin tôi có:

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

Điều này sẽ khiến sự kiện tải trên iframe được kích hoạt. Sau đó, thông báo chờ được đóng lại và quá trình tải xuống tệp sẽ bắt đầu. Đã thử nghiệm trên IE7 và Firefox.


2

Theo kinh nghiệm của tôi, có hai cách để xử lý việc này:

  1. Đặt cookie tồn tại trong thời gian ngắn khi tải xuống và để JavaScript liên tục kiểm tra sự tồn tại của nó. Chỉ có vấn đề thực sự là có được thời gian tồn tại của cookie - quá ngắn và JS có thể bỏ lỡ nó, quá lâu và nó có thể hủy màn hình tải xuống cho các bản tải xuống khác. Sử dụng JS để xóa cookie khi phát hiện thường khắc phục điều này.
  2. Tải xuống tệp bằng cách tìm nạp / XHR. Bạn không chỉ biết chính xác khi nào quá trình tải xuống tệp kết thúc, nếu bạn sử dụng XHR, bạn có thể sử dụng các sự kiện tiến trình để hiển thị thanh tiến trình! Lưu blob kết quả với msSaveBlob trong IE / Edge và liên kết tải xuống ( như cái này ) trong Firefox / Chrome. Vấn đề với phương pháp này là iOS Safari dường như không xử lý việc tải xuống các đốm màu ngay - bạn có thể chuyển đổi blob thành URL dữ liệu bằng FileReader và mở nó trong một cửa sổ mới, nhưng đó là mở tệp, không lưu tệp.

2

Xin chào, tôi biết rằng chủ đề này đã cũ nhưng tôi để lại một giải pháp mà tôi thấy ở nơi khác và nó đã hoạt động:

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

Bạn co thể sử dụng no:

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

1

Nếu bạn đã tải xuống một tệp, được lưu, trái ngược với tài liệu, không có cách nào để xác định khi nào quá trình tải xuống hoàn tất, vì nó không nằm trong phạm vi của tài liệu hiện tại, mà là một quy trình riêng trong trình duyệt.


8
Tôi nên làm rõ - Tôi không quá quan tâm khi quá trình tải xuống hoàn tất . Nếu tôi chỉ có thể xác định khi nào quá trình tải xuống bắt đầu, thế là đủ.
JW.

0

Câu hỏi là có chỉ báo 'chờ' trong khi tệp được tạo và sau đó trở lại bình thường sau khi tệp được tải xuống. Cách tôi muốn làm điều này là sử dụng một iFrame ẩn và móc sự kiện tải của khung để cho trang của tôi biết khi bắt đầu tải xuống. NHƯNG onload không kích hoạt trong IE để tải tệp (như với mã thông báo tiêu đề đính kèm). Thăm dò máy chủ hoạt động, nhưng tôi không thích sự phức tạp thêm. Vì vậy, đây là những gì tôi làm:

  • Nhắm mục tiêu iFrame ẩn như bình thường.
  • Tạo nội dung. Cache nó với thời gian chờ tuyệt đối trong 2 phút.
  • Gửi một chuyển hướng javascript trở lại máy khách đang gọi, về cơ bản gọi trang trình tạo lần thứ hai. LƯU Ý: điều này sẽ khiến sự kiện onload phát sinh trong IE vì nó hoạt động như một trang thông thường.
  • Xóa nội dung khỏi bộ đệm và gửi cho khách hàng.

Từ chối trách nhiệm, không làm điều này trên một trang web bận rộn, vì bộ nhớ đệm có thể tăng lên. Nhưng thực sự, nếu các trang web của bạn bận rộn quá trình chạy dài sẽ bỏ đói các chủ đề.

Đây là những gì các codebehind trông giống như, đó là tất cả những gì bạn thực sự cần.

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

Một giải pháp nhanh nếu bạn chỉ muốn hiển thị thông báo hoặc gif của trình tải cho đến khi hộp thoại tải xuống được hiển thị là đặt tin nhắn vào một hộp chứa ẩn và khi bạn nhấp vào nút tạo tệp để tải xuống, bạn sẽ thấy hộp chứa. Sau đó, sử dụng jquery hoặc javascript để bắt sự kiện tập trung của nút để ẩn vùng chứa có chứa thông báo


0

Nếu Xmlhttprequest với blob không phải là một tùy chọn thì bạn có thể mở tệp của mình trong cửa sổ mới và kiểm tra xem các phần tử eny có được nhập vào phần thân cửa sổ đó không.

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

Ví dụ Java / Spring này phát hiện phần cuối của Tải xuống, tại thời điểm đó, nó ẩn chỉ báo "Đang tải ...".

Cách tiếp cận: Về phía JS, đặt Cookie với Tuổi hết hạn tối đa là 2 phút và thăm dò ý kiến ​​mỗi giây khi hết hạn cookie . Sau đó, phía máy chủ ghi đè cookie này với tuổi hết hạn sớm hơn - hoàn tất quy trình máy chủ. Ngay khi phát hiện hết hạn cookie trong bỏ phiếu JS, "Đang tải ..." sẽ bị ẩn.

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

Phía máy chủ Java / Spring

    @RequestMapping("/download")
    public String download(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //... Some logic for downloading, returning a result ...

        // Create a Cookie that will override the JS-created Max-Age-2min Cookie 
        // with an earlier expiration (same name)
        Cookie myCookie = new Cookie("ServerProcessCompleteChecker", "-1");
        myCookie.setMaxAge(0); // this is immediate expiration, 
                               // but can also add +3 sec. for any flushing concerns
        myCookie.setPath("/");
        response.addCookie(myCookie);
        //... -- presumably the download is writing to the Output Stream...
        return null;
}

Cookie được tạo bởi tập lệnh JS nhưng nó không được cập nhật bởi bộ điều khiển, nó duy trì giá trị ban đầu (0), làm cách nào tôi có thể cập nhật giá trị cookie mà không cần làm mới trang?
Shessuky

Điều đó thật lạ - bạn có thể đảm bảo tên chính xác không? Nó sẽ ghi đè lên cookie nếu tên phù hợp. Hãy cho tôi biết
gen b.

Giá trị ban đầu không bằng 0. Giá trị ban đầu được đặt trong JS là 2 phút. Giá trị MỚI mà máy chủ được yêu cầu sửa đổi là 0.
gen b.

Ngoài ra, bạn đang làm điều này: myCookie.setPath("/"); response.addCookie(myCookie);
gen b.

Tôi đã tìm ra (vì một số lý do), rằng tôi nên thêm cookie trước khi thực hiện answer.getOutputStream (); (nhận luồng đầu ra phản hồi để nối các tệp tải xuống), nó không được tính đến khi tôi thực hiện sau bước đó
Shessuky

0

Primefaces cũng sử dụng bỏ phiếu cookie

https://github.com/primefaces/primefaces/blob/32bb00299d00e50b2cba430638468a4145f4edb0/src/main/resource/META-INF/resource/primefaces/core/core.js#45

    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

Tạo iframe khi nút / liên kết được nhấp và nối phần này vào phần thân.

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

Tạo một iframe có độ trễ và xóa nó sau khi tải xuống.

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

Điều này là thiếu thông tin và nó không giải quyết được vấn đề "khi nào cần ẩn tải".
Tom Roggero
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.