Виявити, коли браузер отримує завантаження файлів


487

У мене є сторінка, яка дозволяє користувачеві завантажувати динамічно генерований файл. Генерування потрібно тривалий час, тому я хотів би показати індикатор "очікування". Проблема полягає в тому, що я не можу зрозуміти, як виявити, коли браузер отримав файл, тому я можу приховати індикатор.

Я роблю запит у прихованій формі, яка надсилає POST на сервер, і націлює прихований кадр для його результатів. Це так, що я не замінюю все вікно браузера результатом. Я слухаю подію "завантаження" на iframe, сподіваючись, що вона запуститься, коли завантаження завершиться.

Я повертаю заголовок "Вміст-диспозиція: вкладення" з файлом, через який браузер відображає діалогове вікно "Зберегти". Але браузер не запускає події "завантаження" в 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", потім показує діалогове вікно "Зберегти" для завантажуваного файлу. Але це не вдається на IE і Safari; IE запускає подію "load", але не завантажує файл, а Safari завантажує файл (з неправильним ім'ям та типом вмісту) та не запускає подію "load".

Іншим підходом може бути заклик розпочати створення файлу, а потім опитувати сервер до готовності, а потім завантажити вже створений файл. Але я краще уникати створення тимчасових файлів на сервері.

Хтось має кращу ідею?


4
Жодна версія IE не підтримує multipart / x-змішану заміну.
EricLaw

Дякую Еріку - це добре знати. Я більше не витрачаю часу на такий підхід.
JW.

Єдиним надійним способом, здається, є повідомлення про натискання сервера (SignalR для людей з ASP.NET).
dudeNumber4

1
bennadel.com/blog/… - це просте рішення
Mateen

1
@mateen дякую чувак! це дуже просто
Fai Zal Dong

Відповіді:


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, перш ніж заглиблюватися у ваше рішення, мені цікаво, чи буде він працювати з запитом на завантаження файлів між доменними файлами. Ви думаєте, що це буде, чи обмеження щодо cookie будуть компрометувати це?
kiks73

5
Блискуче - мені не прийшло б у голову через 100 років, щоб ви могли включати файли cookie як частину завантаження файлів. Дякую!!
freefaller

8
Як зазначали інші, це рішення вирішує лише частину проблеми, чекаючи, коли сервер підготує файл до часу. Інша частина проблеми, яка може бути значною в залежності від розміру файлу та швидкості з'єднання, полягає в тому, скільки часу потрібно, щоб фактично отримати весь файл на клієнті. І це не вирішено цим рішенням.
AsGoodAsItGets

27

Дуже простим (і кульгавим) рішенням одного рядка є використання window.onblur()події для закриття діалогового вікна завантаження. Звичайно, якщо це займе занадто багато часу, і користувач вирішить зробити щось інше (наприклад, читання електронних листів), діалогове вікно завантаження закриється.


Це простий підхід, який ідеально підходить для позбавлення від завантажувального накладення для завантаження файлу, яке було запущено за допомогою onbeforeunloadДякую.
wf4

5
Це працює не у всіх браузерах (деякі не залишають / розмивають поточне вікно як частину робочого процесу завантаження, наприклад, Safari, деякі версії IE тощо).
hiattp

4
Chrome та інші переглядачі автоматично завантажують файли, коли ця умова не виконана.
Пощастило

@Lucky, це лише за замовчуванням. Цілком можливо, що користувач Chrome вкаже, куди слід зберігати завантаження, і отже побачить діалогове вікно
ESR

2
погана ідея, оскільки ви активуєте розмиття під час зміни табуляції чи будь-яку дію за вікном
Майкл

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
але якщо у користувача відкрито кілька вікон або завантажень? також ви отримуєте тут зайвий дзвінок на сервер
Yuki

3
Якщо у вас є декілька з'єднань від одного користувача, вони будуть чекати, коли інші з'єднання закінчаться, оскільки session_start () блокує сеанс для користувача та не дає усім іншим процесам отримати доступ до нього.
Honza Kuchař

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

Не вживайте коду всередині setTimeout('getstatus()', 1000);. Використовуйте fn безпосередньо:setTimeout(getstatus, 1000);
Roko C. Buljan

11

Я використовую наступне, щоб завантажити краплі і відкликати об’єкт-URL після завантаження. він працює в хромі та 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
Що робити, якщо користувач натисне вікно?
Том Роджеро

це саме те, що я шукав, багато Thaks !!
Серхіо

Шкура () не викликався в моєму випадку
Prashant Pimpale

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 буде надіслано клієнту, коли ваш файл буде готовий до завантаження.

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

Я сподіваюся допомогти комусь!


Це прекрасно працює. Дякуємо за цей прекрасний зразок.
Седат Кумку

4

Коли користувач запускає генерацію файлу, ви можете просто призначити унікальний ідентифікатор цьому "завантаженню" та відправити користувача на сторінку, яка оновлюється (або перевіряється за допомогою AJAX) кожні кілька секунд. Після закінчення файлу збережіть його під тим самим унікальним ідентифікатором і ...

  • Якщо файл готовий, виконайте завантаження.
  • Якщо файл не готовий, покажіть прогрес.

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


Це звучить як підхід до тимчасових файлів, про який я згадував вище. Я можу зробити щось подібне, якщо виявиться, що моя ідея неможлива, але я сподівався її уникнути.
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. Єдиною справжньою проблемою є отримання права життя файлів cookie - занадто короткий, і JS може пропустити його занадто довго, і це може скасувати екрани завантаження для інших завантажень. Використання JS для видалення файлу cookie після виявлення зазвичай це виправляє.
  2. Завантажте файл за допомогою fetch / XHR. Мало того, що ви точно знаєте, коли закінчується завантаження файлу, якщо ви використовуєте XHR, ви можете використовувати події прогресу, щоб показати панель прогресу! Збережіть отриману крапку за допомогою 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 не запускається в IE для завантаження файлів (наприклад, з маркером заголовка вкладення). Опитування сервера працює, але мені не подобається додаткова складність. Отже ось що я роблю:

  • Націлюйте на прихований iFrame як завжди.
  • Створення вмісту. Кешуйте його з абсолютним тайм-аутом за 2 хвилини.
  • Відправити переспрямування javascript назад до клієнта, що дзвонить, по суті викликаючи сторінку генератора вдруге. ПРИМІТКА: це призведе до запуску події завантаження в 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, щоб зловити подію фокусування кнопки, щоб приховати контейнер, який містить повідомлення


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 хв. Та опитуйте щосекунди на час закінчення терміну cookie . Потім сервер замінює цей файл cookie на більш ранньому терміні придатності - завершенні серверного процесу. Як тільки в опитуванні 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 Server Side

    @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, якщо назва збігається. Дайте мені знати
ген b.

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

Також ви це робите: myCookie.setPath("/"); response.addCookie(myCookie);
ген b.

Я зрозумів (чомусь), що я повинен додати файли cookie перед тим, як робити response.getOutputStream (); (отримуючи вихідний потік відповідей для додавання файлів для завантаження), він не був врахований, коли я це зробив після цього кроку
Шессукі

0

Primeface також використовує опитування файлів 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.