Определить, когда браузер получает файл загрузки


488

У меня есть страница, которая позволяет пользователю загружать динамически генерируемый файл. Генерирование занимает много времени, поэтому я хотел бы показать индикатор «ожидания». Проблема в том, что я не могу понять, как определить, когда браузер получил файл, поэтому я могу скрыть индикатор.

Я делаю запрос в скрытой форме, которая отправляет на сервер и нацеливает скрытый iframe для его результатов. Поэтому я не заменяю все окно браузера результатом. Я слушаю событие загрузки в iframe, в надежде, что оно сработает после завершения загрузки.

Я возвращаю заголовок «Content-Disposition: attachment» вместе с файлом, в результате чего браузер отображает диалоговое окно «Save». Но браузер не запускает событие загрузки в iframe.

Один из подходов, которые я попробовал, - это использование ответа из нескольких частей. Таким образом, он отправит пустой HTML-файл, а также прикрепленный загружаемый файл. Например:

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

Это работает в Firefox; он получает пустой HTML-файл, запускает событие «load», а затем показывает диалоговое окно «Save» для загружаемого файла. Но это не работает в IE и Safari; IE запускает событие «load», но не загружает файл, а Safari загружает файл (с неправильным именем и типом содержимого) и не запускает событие «load».

Другой подход может заключаться в том, чтобы позвонить, чтобы начать создание файла, затем опросить сервер, пока он не будет готов, а затем загрузить уже созданный файл. Но я бы предпочел не создавать временные файлы на сервере.

У кого-нибудь есть идея получше?


4
Ни одна версия IE не поддерживает multipart / x-mixed-replace.
EricLaw

Спасибо, Эрик, это приятно знать. Я не буду больше тратить время на такой подход.
JW.

Единственным надежным способом, по-видимому, является push-уведомление сервера (SignalR для людей ASP.NET).
dudeNumber4

1
bennadel.com/blog/… - это простое решение
Mateen

1
@Mateen спасибо, чувак! это действительно просто
Фай Зал Донг

Ответы:


451

Одно из возможных решений использует JavaScript на клиенте.

Клиентский алгоритм:

  1. Создайте случайный уникальный токен.
  2. Отправьте запрос на загрузку и включите токен в поле GET / POST.
  3. Покажите индикатор ожидания.
  4. Запустите таймер, и каждую секунду или около того ищите файл cookie с именем fileDownloadToken (или все, что вы решите).
  5. Если файл cookie существует и его значение соответствует токену, скрыть индикатор «ожидания».

Алгоритм сервера:

  1. Ищите поле GET / POST в запросе.
  2. Если оно имеет непустое значение, удалите cookie (например, «fileDownloadToken») и установите его значение равным значению токена.

Исходный код клиента (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;
}

Пример кода сервера (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();

Куда:

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
Потрясающая идея, я использовал его в качестве основы для этого ответа о загрузке нескольких файлов с помощью jQuery / C #
Грег

7
Внимание другим: если document.cookies не включает downloadToken, проверьте путь к cookie. В моем случае мне пришлось установить путь к «/» на стороне сервера (например, cookie.setPath («/») в Java), хотя путь был пустым по умолчанию. Некоторое время я думал, что проблема заключалась в особой обработке cookie-файлов домена «localhost» ( stackoverflow.com/questions/1134290/… ), но это не было проблемой в конце. Может быть, что для других, хотя так стоит прочитать.
Jlpp

2
@bulltorious прежде чем углубляться в ваше решение, я хотел бы знать, будет ли оно работать с запросом на загрузку междоменного файла. Как вы думаете, это будет, или ограничения куки-файлов скомпрометируют это?
kiks73

5
Гениально - через 100 лет мне бы не пришло в голову, что вы можете включить куки в загрузку файла. Спасибо!!
freefaller

8
Как уже отмечали другие, это решение решает только часть проблемы, ожидая, пока сервер подготовит время файла. Другая часть проблемы, которая может быть значительной в зависимости от размера файла и скорости соединения, заключается в том, сколько времени требуется, чтобы фактически получить весь файл на клиенте. И это не решается с этим решением.
AsGoodAsItGets

27

Очень простое (и неудачное) однострочное решение - использовать window.onblur()событие, чтобы закрыть диалог загрузки. Конечно, если это займет слишком много времени и пользователь решит заняться чем-то другим (например, чтением писем), диалог загрузки закроется.


Это простой подход, который идеально подходит для избавления от оверлея загрузки для загрузки файла, которая была инициирована с помощью onbeforeunloadСпасибо.
wf4

5
Это работает не во всех браузерах (некоторые не оставляют / размывают текущее окно как часть рабочего процесса загрузки, например Safari, некоторые версии IE и т. Д.).
hiattp

4
Chrome и другие подобные браузеры автоматически загружают файлы, где это условие не выполняется.
Счастливчик

@ Лаки, это только по умолчанию. Вполне возможно, что пользователь Chrome укажет, где следует сохранять загрузки, и, следовательно, увидит диалоговое окно
ESR

2
плохая идея, потому что вы активируете размытие на tabchange или любое действие за окном
Michael

14

старая нить, я знаю ...

но те, которые приводятся здесь Google, могут быть заинтересованы в моем решении. это очень просто, но надежно. и это позволяет отображать сообщения о реальном прогрессе (и может быть легко подключен к существующим процессам):

сценарий, который обрабатывает (моя проблема заключалась в том, чтобы: извлекать файлы через http и доставлять их в формате zip) записывает состояние в сеанс.

статус опрашивается и отображается каждую секунду. вот и все (хорошо, это не так. вам нужно позаботиться о многих деталях [например, одновременных загрузках], но это хорошее место для начала ;-)).

страница загрузки:

    <a href="download.php?id=1" class="download">DOWNLOAD 1</a>
    <a href="download.php?id=2" class="download">DOWNLOAD 2</a>
    ...
    <div id="wait">
    Please wait...
    <div id="statusmessage"></div>
    </div>
    <script>
//this is jquery
    $('a.download').each(function()
       {
        $(this).click(
             function(){
               $('#statusmessage').html('prepare loading...');
               $('#wait').show();
               setTimeout('getstatus()', 1000);
             }
          );
        });
    });
    function getstatus(){
      $.ajax({
          url: "/getstatus.php",
          type: "POST",
          dataType: 'json',
          success: function(data) {
            $('#statusmessage').html(data.message);
            if(data.status=="pending")
              setTimeout('getstatus()', 1000);
            else
              $('#wait').hide();
          }
      });
    }
    </script>

getstatus.php

<?php
session_start();
echo json_encode($_SESSION['downloadstatus']);
?>

download.php

    <?php
    session_start();
    $processing=true;
    while($processing){
      $_SESSION['downloadstatus']=array("status"=>"pending","message"=>"Processing".$someinfo);
      session_write_close();
      $processing=do_what_has_2Bdone();
      session_start();
    }
      $_SESSION['downloadstatus']=array("status"=>"finished","message"=>"Done");
//and spit the generated file to the browser
    ?>

3
но если у пользователя есть несколько окон или загрузки открыты? Также вы получаете здесь избыточный вызов на сервер
Юки

3
Если у вас есть несколько подключений от одного пользователя, все они будут ожидать завершения других подключений, потому что session_start () блокирует сеанс для пользователя и препятствует доступу к нему всех других процессов.
Хонза Кучарж

2
вам не нужно использовать .each()для регистрации событий. просто скажи$('a.download').click()
робисроб

Не проверяйте код внутри setTimeout('getstatus()', 1000);. Используйте fn напрямую:setTimeout(getstatus, 1000);
Roko C. Buljan

11

Я использую следующее для загрузки BLOB-объектов и отзыва URL-адреса объекта после загрузки. это работает в Chrome и 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;
}

после того, как диалоговое окно загрузки файла закрыто, окно возвращает ее фокус, так что происходит событие фокуса.


По-прежнему существует проблема переключения окна и возврата, что приведет к скрытию модального окна.
dudeNumber4

9
Браузеры, такие как Chrome, которые загружаются в нижний лоток, никогда не размывают и не перефокусируют окно.
Коулман

10

На примере Элмера я подготовил собственное решение. После щелчка элементов с определенным классом загрузки это позволяет показывать пользовательское сообщение на экране. Я использовал фокус триггера, чтобы скрыть сообщение.

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>

Теперь вы должны реализовать любой элемент для загрузки:

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

или

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

После каждого клика на загрузку вы увидите сообщение о создании вашего отчета, пожалуйста подождите ...


2
Что если пользователь щелкнет окно?
Том Роггеро

это именно то, что я искал, спасибо большое !!
Серхио

Hide

8

Я написал простой класс JavaScript, который реализует технику, аналогичную описанной в бредовом ответе . Я надеюсь, что это может быть полезно для кого-то здесь. Проект GitHub называется response-monitor.js

По умолчанию он использует spin.js в качестве индикатора ожидания, но также предоставляет набор обратных вызовов для реализации пользовательского индикатора.

JQuery поддерживается, но не требуется.

Известные особенности

  • Простая интеграция
  • Нет зависимостей
  • Плагин JQuery (необязательно)
  • Интеграция Spin.js (необязательно)
  • Настраиваемые обратные вызовы для мониторинга событий
  • Обрабатывает несколько одновременных запросов
  • Обнаружение ошибок на стороне сервера
  • Тайм-аут обнаружения
  • Кросс-браузер

Пример использования

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>

Клиент (простой 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

Клиент (JQuery)

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

Клиент с обратными вызовами (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);

Сервер (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

Дополнительные примеры можно найти в папке примеров в хранилище.


5

Я очень поздно на вечеринку, но я поставлю это здесь, если кто-то еще хотел бы узнать мое решение:

У меня была настоящая борьба с этой конкретной проблемой, но я нашел жизнеспособное решение, используя iframes (я знаю, я знаю. Это ужасно, но это работает для простой проблемы, которая у меня была)

У меня была HTML-страница, которая запустила отдельный скрипт php, который сгенерировал файл, а затем загрузил его. На html-странице я использовал следующий jquery в html-заголовке (также необходимо включить библиотеку 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>

На your_download_script.php имейте следующее:

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


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

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

Чтобы разобраться в этом, jquery сначала запускает ваш php-скрипт в iframe. Iframe загружается после создания файла. Затем jquery снова запускает скрипт с переменной запроса, сообщающей скрипту о загрузке файла.

Причина, по которой вы не можете выполнять загрузку и генерацию файлов одновременно, связана с функцией php header (). Если вы используете header (), вы изменяете скрипт на что-то иное, чем веб-страница, и jquery никогда не распознает загружаемый скрипт как «загруженный». Я знаю, что это не обязательно обнаруживает, когда браузер получает файл, но ваша проблема звучит похоже на мою.


5

Если вы создаете потоковую передачу файла, который вы генерируете динамически, а также внедрили библиотеку обмена сообщениями между серверами в реальном времени, вы можете довольно легко предупредить вашего клиента.

Библиотека обмена сообщениями между серверами, которую я люблю и рекомендую, - Socket.io (через Node.js). После того, как ваш серверный скрипт завершит генерацию файла, который передается для загрузки, ваша последняя строка в этом скрипте может отправить сообщение в Socket.io, которое отправит уведомление клиенту. На клиенте Socket.io прослушивает входящие сообщения, отправленные с сервера, и позволяет вам действовать с ними. Преимущество использования этого метода перед другими заключается в том, что вы можете обнаружить «истинное» событие завершения после завершения потоковой передачи.

Например, вы можете отобразить индикатор занятости после нажатия на ссылку для скачивания, отправить файл в потоковом режиме, отправить сообщение на сервер Socket.io в последней строке вашего скрипта потоковой передачи, прослушать уведомление клиента, получить уведомление и обновите свой интерфейс, скрыв индикатор занятости.

Я понимаю, что большинство людей, читающих ответы на этот вопрос, могут не иметь такой установки, но я использовал это точное решение с большим эффектом в своих собственных проектах, и оно прекрасно работает.

Socket.io невероятно прост в установке и использовании. Смотрите больше: http://socket.io/


5

"Как определить, когда браузер получает загрузку файла?"
Я столкнулся с той же проблемой с этим конфигом:
struts 1.2.9
jquery-1.3.2.
jquery-ui-1.7.1. пользовательский
IE 11
java 5


Мое решение с cookie:
- Клиентская сторона:
при отправке формы вызовите функцию javascript, чтобы скрыть страницу и загрузить ожидающий счетчик

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

Затем вызовите функцию, которая будет каждые 500 мс проверять, приходит ли файл cookie с сервера.

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

Если cookie-файл найден, прекратите проверку каждые 500 мс, прекратите использование cookie-файла и вызовите свою функцию, чтобы вернуться на свою страницу и удалить ожидающий счетчик ( removeWaitingSpinner () ). Важно, чтобы срок действия файла cookie истек, если вы хотите снова загрузить другой файл!

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

- Серверная сторона:
в конце вашего серверного процесса добавьте куки-файл в ответ. Этот файл cookie будет отправлен клиенту, когда ваш файл будет готов к загрузке.

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

Я надеюсь помочь кому-то!


Работает отлично. Спасибо за этот прекрасный образец.
Седат Кумчу

4

Когда пользователь запускает генерацию файла, вы можете просто назначить уникальный идентификатор для этой «загрузки» и отправить пользователя на страницу, которая обновляется (или проверяется с помощью AJAX) каждые несколько секунд. Как только файл будет завершен, сохраните его под тем же уникальным идентификатором и ...

  • Если файл готов, сделайте загрузку.
  • Если файл не готов, покажите прогресс.

Тогда вы можете пропустить весь беспорядок iframe / wait / browserwindow, но при этом получить действительно элегантное решение.


Это похоже на подход временного файла, который я упоминал выше. Я мог бы сделать что-то подобное, если окажется, что моя идея невозможна, но я надеялся избежать этого.
JW.

3

Если вы не хотите генерировать и хранить файл на сервере, готовы ли вы сохранить статус, например, файл в процессе, файл завершен? Ваша страница ожидания может опросить сервер, чтобы узнать, когда генерация файла завершена. Вы не могли бы знать наверняка, что браузер начал загрузку, но у вас будет некоторая уверенность.


2

У меня была точно такая же проблема. Моим решением было использовать временные файлы, так как я уже генерировал кучу временных файлов. Форма представляется вместе с:

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

В конце сценария, который генерирует файл, который я имею:

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

Это вызовет событие загрузки в iframe. Затем сообщение ожидания закрывается и начинается загрузка файла. Протестировано на IE7 и Firefox.


2

По моему опыту, есть два способа справиться с этим:

  1. Установите для загрузки недолговечный cookie-файл и постоянно проверяйте его наличие на JavaScript. Единственная реальная проблема заключается в том, чтобы правильно определить срок жизни куки - слишком короткий, и JS может пропустить его, слишком долго, и это может отменить экраны загрузки для других загрузок. Использование JS для удаления cookie при обнаружении обычно исправляет это.
  2. Загрузите файл, используя fetch / XHR. Мало того, что вы точно знаете, когда заканчивается загрузка файла, если вы используете XHR, вы можете использовать события прогресса, чтобы показать индикатор выполнения! Сохраните полученный BLOB- объект с помощью msSaveBlob в IE / Edge и ссылкой для загрузки ( например, этой ) в Firefox / Chrome. Проблема этого метода заключается в том, что iOS Safari, похоже, не справляется с загрузкой больших двоичных объектов - вы можете преобразовать большой двоичный объект в URL-адрес данных с помощью FileReader и открыть его в новом окне, но это открывает файл, а не сохраняет его.

2

Привет, я знаю, что тема старая, но я оставляю решение, которое я видел в другом месте, и оно работало:

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

Вы можете использовать это:

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

1

Если у вас есть загруженный файл, который сохраняется, а не находится в документе, невозможно определить, когда загрузка завершена, поскольку это не входит в область действия текущего документа, а представляет собой отдельный процесс в браузере.


8
Я должен уточнить - меня не слишком волнует, когда загрузка завершится . Если бы я мог просто определить, когда начинается загрузка, этого было бы достаточно.
JW.

0

Вопрос заключается в том, чтобы во время генерации файла был индикатор ожидания, а затем, когда файл загружается, он возвращается в нормальное состояние. Мне нравится делать это, используя скрытый iFrame и перехватывая событие onload фрейма, чтобы сообщить моей странице, когда начинается загрузка. НО onload не запускается в IE при загрузке файлов (как с токеном заголовка вложения). Опрос сервера работает, но мне не нравится дополнительная сложность. Итак, вот что я делаю:

  • Нацельтесь на скрытый iFrame как обычно.
  • Генерация контента. Кэшируйте это с абсолютным таймаутом в 2 минуты.
  • Отправьте перенаправление javascript обратно вызывающему клиенту, по сути, повторно вызывая страницу генератора. ПРИМЕЧАНИЕ: это вызовет событие onload в IE, потому что оно действует как обычная страница.
  • Удалите содержимое из кэша и отправьте его клиенту.

Отказ от ответственности, не делайте этого на загруженном сайте, потому что кеширование может сложиться. Но на самом деле, если ваши сайты, которые заняты длительным процессом, в любом случае лишат вас потоков.

Вот как выглядит код, и это все, что вам действительно нужно.

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

Быстрое решение, если вы хотите отображать только сообщение или gif-загрузчик до тех пор, пока не отобразится диалоговое окно загрузки, - это поместить сообщение в скрытый контейнер, и когда вы нажимаете кнопку, генерирующую файл для загрузки, вы делаете контейнер видимым. Затем используйте jquery или javascript, чтобы перехватить событие focusout кнопки, чтобы скрыть контейнер, содержащий сообщение.


0

Если Xmlhttprequest с blob не является опцией, вы можете открыть свой файл в новом окне и проверить, заполняются ли элементы eny в этом теле окна с интервалом.

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

Этот пример Java / Spring определяет конец загрузки, после чего скрывает индикатор «Загрузка ...».

Подход: на стороне JS установите Cookie с максимальным сроком действия 2 мин и опрашивайте каждую секунду для истечения срока действия куки . Затем на стороне сервера переопределяет это печенье с ранее сроком действия - завершением процесса сервера. Как только в опросе JS обнаруживается истечение срока действия cookie, «Загрузка ...» скрывается.

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

    @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 создается сценарием JS, но не обновляется контроллером, он сохраняет исходное значение (0). Как я могу обновить значение файла cookie, не обновляя страницу?
Шессуки

Это странно - вы можете гарантировать , что имя точно правильно? Он перезапишет cookie, если имя совпадает. Дайте мне знать
ген б.

Исходное значение не равно 0. Исходное значение, установленное в JS, составляет 2 мин. НОВОЕ значение, которое сервер должен изменить, - 0.
ген b.

Кроме того, вы делаете это: myCookie.setPath("/"); response.addCookie(myCookie);
ген б.

Я понял (по некоторым причинам), что я должен добавить куки, прежде чем делать response.getOutputStream (); (получение потока вывода ответа для добавления загружаемых файлов), когда я сделал это после этого шага, это не было учтено
Shessuky

0

Primefaces также использует опрос cookie

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

    monitorDownload: function(start, complete, monitorKey) {
        if(this.cookiesEnabled()) {
            if(start) {
                start();
            }

            var cookieName = monitorKey ? 'primefaces.download_' + monitorKey : 'primefaces.download';
            window.downloadMonitor = setInterval(function() {
                var downloadComplete = PrimeFaces.getCookie(cookieName);

                if(downloadComplete === 'true') {
                    if(complete) {
                        complete();
                    }
                    clearInterval(window.downloadMonitor);
                    PrimeFaces.setCookie(cookieName, null);
                }
            }, 1000);
        }
    },

-2

Создайте iframe при нажатии кнопки / ссылки и добавьте его в тело.

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

Создайте iframe с задержкой и удалите его после загрузки.

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

Это не хватает информации, и это не решает проблему «когда скрывать загрузку».
Том Роггеро
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.