Определить, когда браузер получает файл загрузки
У меня есть страница, которая позволяет пользователю загружать динамически сгенерированный файл. Генерирование занимает много времени, поэтому я хотел бы показать индикатор "ожидания". Проблема в том, что я не могу понять, как определить, когда браузер получил файл, поэтому я могу скрыть индикатор.
Я делаю запрос в скрытой форме, которая отправляет на сервер и нацеливает скрытый 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".
Другой подход может заключаться в том, чтобы сделать вызов, чтобы начать создание файла, затем опросить сервер, пока он не будет готов, а затем загрузить уже созданный файл. Но я бы предпочел не создавать временные файлы на сервере.
У кого-нибудь есть идея получше?
24 ответа
Одно возможное решение использует JavaScript на клиенте.
Клиентский алгоритм:
- Создайте случайный уникальный токен.
- Отправьте запрос на загрузку и включите токен в поле GET/POST.
- Покажите индикатор ожидания.
- Запустите таймер, и каждую секунду или около того ищите файл cookie с именем fileDownloadToken (или что вы решите).
- Если файл cookie существует и его значение соответствует токену, скрыть индикатор "ожидания".
Алгоритм сервера:
- Ищите поле GET/POST в запросе.
- Если оно имеет непустое значение, удалите 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://stackru.com/a/1459794/59087
// See: http://shiflett.org/blog/2006/mar/server-name-versus-http-host
// See: http://stackru.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
);
}
Очень простое (и хромое) однострочное решение - использовать window.onblur()
событие, чтобы закрыть диалог загрузки. Конечно, если это займет слишком много времени и пользователь решит заняться чем-то другим (например, чтением писем), диалоговое окно загрузки закроется.
Основная проблема заключается в том, что в веб-браузере нет события, которое запускается при отмене навигации по странице, но есть событие, которое запускается, когда страница завершает загрузку. Все, что выходит за рамки прямого события в браузере, будет взломом со своими плюсами и минусами.
Существует четыре известных подхода к обнаружению начала загрузки браузера:
- Вызовите fetch(), получите весь ответ, прикрепите
a
тег сdownload
атрибут и вызвать событие щелчка. Современные веб-браузеры предложат пользователю возможность сохранить уже полученный файл. У этого подхода есть несколько недостатков:
- Весь большой двоичный объект данных хранится в ОЗУ, поэтому, если файл большой, он будет потреблять столько ОЗУ. Для небольших файлов это, вероятно, не является препятствием.
- Пользователь должен дождаться загрузки всего файла, прежде чем он сможет его сохранить. Они также не могут покинуть страницу, пока она не завершится.
- Встроенный загрузчик файлов в веб-браузер не используется.
- Междоменная выборка, вероятно, завершится ошибкой, если не установлены заголовки CORS.
- Используйте iframe + файл cookie на стороне сервера. Iframe запускает
load
событие, если страница загружается в iframe вместо начала загрузки, но не запускает никаких событий, если загрузка начинается. После этого установка cookie на веб-сервере может быть обнаружена Javascript в цикле. У этого подхода есть несколько недостатков:
- Сервер и клиент должны работать согласованно. Сервер должен установить куки. Клиент должен обнаружить куки.
- Междоменные запросы не смогут установить cookie.
- Существуют ограничения на количество файлов cookie, которые можно установить для одного домена.
- Не удается отправить настраиваемые заголовки HTTP.
- Используйте iframe с перенаправлением URL. Iframe запускает запрос, и как только сервер подготовит файл, он выгружает HTML-документ, который выполняет мета-обновление, на новый URL-адрес, который запускает загрузку через 1 секунду. В
load
Событие в iframe происходит при загрузке HTML-документа. У этого подхода есть несколько недостатков:
- Сервер должен поддерживать хранилище для загружаемого контента. Требуется задание cron или подобное для регулярной очистки каталога.
- Когда файл готов, сервер должен выгрузить специальный HTML-контент.
- Перед удалением iframe из модели DOM клиент должен угадать, когда iframe фактически сделал второй запрос к серверу и когда действительно началась загрузка. Этого можно избежать, просто оставив iframe в DOM.
- Не удается отправить настраиваемые заголовки HTTP.
- Используйте iframe + XHR. Iframe запускает запрос на загрузку. Как только запрос выполняется через iframe, выполняется идентичный запрос через XHR. Если
load
возникает событие iframe, произошла ошибка, прервите запрос XHR и удалите iframe. Если XHRprogress
событие возникает, значит загрузка, вероятно, началась в iframe, прервите запрос XHR, подождите несколько секунд и затем удалите iframe. Это позволяет загружать файлы большего размера, не полагаясь на cookie на стороне сервера. У этого подхода есть несколько недостатков:
- Для получения одной и той же информации сделано два отдельных запроса. Сервер может отличить XHR от iframe, проверяя входящие заголовки.
- Междоменный запрос XHR, вероятно, завершится ошибкой, если не заданы заголовки CORS. Однако браузер не узнает, разрешен CORS или нет, пока сервер не отправит обратно HTTP-заголовки. Если сервер ожидает отправки заголовков, пока данные файла не будут готовы, XHR может примерно определить, когда iframe начал загрузку, даже без CORS.
- Клиент должен угадать, когда действительно началась загрузка, чтобы удалить iframe из DOM. Этого можно избежать, просто оставив iframe в DOM.
- Не удается отправить настраиваемые заголовки в iframe.
Без соответствующего встроенного события веб-браузера здесь нет идеальных решений. Однако один из четырех вышеперечисленных методов, вероятно, подойдет лучше, чем другие, в зависимости от вашего варианта использования.
По возможности передавайте ответы клиенту на лету, вместо того, чтобы сначала создавать все на сервере, а затем отправлять ответ. Возможна потоковая передача файлов различных форматов, таких как CSV, JSON, XML, ZIP и т. Д. Это действительно зависит от поиска библиотеки, поддерживающей потоковый контент. При потоковой передаче ответа сразу после запуска запроса определение начала загрузки не имеет большого значения, потому что она начнется практически сразу.
Другой вариант - просто вывести заголовки загрузки вперед, а не ждать, пока сначала будет сгенерирован весь контент. Затем сгенерируйте контент и, наконец, начните отправлять клиенту. Встроенный загрузчик пользователя будет терпеливо ждать, пока не начнут поступать данные. Обратной стороной является то, что базовое сетевое соединение может истекать по таймауту, ожидая начала передачи данных (либо на стороне клиента, либо на стороне сервера).
Старая нить, я знаю...
но те, которые приводятся здесь 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
?>
На примере Элмера я подготовил собственное решение. После щелчка элементов с определенным классом загрузки, на экране можно отобразить собственное сообщение. Я использовал фокус триггера, чтобы скрыть сообщение.
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">
После каждого щелчка загрузки вы увидите сообщение о создании вашего отчета, пожалуйста, подождите...
Я использую следующее для загрузки 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;
}
после того, как диалоговое окно загрузки файла закрыто, окно возвращает ее фокус, так что происходит событие фокуса.
Приветствую, я знаю, что тема старая, но я оставляю решение, которое видел в другом месте, и оно сработало:
/**
* 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.2.9
JQuery-1.3.2.
JQuery-щ-1.7.1.custom
IE 11
Ява 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);
Я надеюсь помочь кому-то!
Я написал простой класс 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
Дополнительные примеры можно найти в папке примеров в хранилище.
Если вы создаете потоковую передачу файла, который вы генерируете динамически, а также внедрили библиотеку обмена сообщениями между серверами в реальном времени, вы можете довольно легко предупредить вашего клиента.
Библиотека обмена сообщениями между серверами, которую я люблю и рекомендую, - Socket.io (через Node.js). После того, как ваш серверный скрипт завершит генерацию файла, который передается для загрузки, ваша последняя строка в этом скрипте может отправить сообщение в Socket.io, которое отправит уведомление клиенту. На клиенте Socket.io прослушивает входящие сообщения, отправленные с сервера, и позволяет вам действовать с ними. Преимущество использования этого метода перед другими заключается в том, что вы можете обнаружить "истинное" событие завершения после завершения потоковой передачи.
Например, вы можете отобразить индикатор занятости после нажатия на ссылку для скачивания, передать файл в потоковом режиме, отправить сообщение на сервер Socket.io с последней строки вашего скрипта потоковой передачи, прослушать уведомление клиента на клиенте, получить уведомление и обновите свой интерфейс, скрыв индикатор занятости.
Я понимаю, что большинство людей, читающих ответы на этот вопрос, могут не иметь такой установки, но я использовал это точное решение с большим эффектом в своих собственных проектах, и оно прекрасно работает.
Socket.io невероятно прост в установке и использовании. Смотрите больше: http://socket.io/
Я очень поздно на вечеринку, но я поставлю это здесь, если кто-то еще хотел бы узнать мое решение:
У меня была настоящая борьба с этой точной проблемой, но я нашел жизнеспособное решение, используя 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 никогда не распознает скрипт загрузки как "загруженный". Я знаю, что это не обязательно обнаруживает, когда браузер получает файл, но ваша проблема звучит похоже на мою.
Когда пользователь запускает генерацию файла, вы можете просто назначить уникальный идентификатор для этой "загрузки" и отправить пользователя на страницу, которая обновляется (или проверяется с помощью AJAX) каждые несколько секунд. Как только файл будет завершен, сохраните его под тем же уникальным идентификатором и...
- Если файл готов, загрузите его.
- Если файл не готов, покажите прогресс.
Тогда вы можете пропустить весь беспорядок iframe /wait /browserwindow, но при этом получить действительно элегантное решение.
Если вы не хотите создавать и хранить файл на сервере, готовы ли вы сохранить статус, например, файл в процессе, файл завершен? Ваша "ожидающая" страница может опросить сервер, чтобы узнать, когда генерация файла завершена. Вы не могли бы точно знать, что браузер начал загрузку, но у вас будет некоторая уверенность.
По моему опыту, есть два способа справиться с этим:
- Установите для загрузки недолговечный файл cookie и пусть JavaScript постоянно проверяет его наличие. Единственная реальная проблема заключается в том, чтобы правильно определить время жизни cookie - слишком короткое, и JS может его пропустить, слишком долгое и может отменить экраны загрузки для других загрузок. Использование JS для удаления cookie при обнаружении обычно исправляет это.
- Загрузите файл с помощью fetch/XHR. Вы не только точно знаете, когда загрузка файла завершается, но и при использовании XHR вы можете использовать события прогресса, чтобы показать индикатор выполнения! Сохраните полученный BLOB- объект с помощью msSaveBlob в IE/Edge и ссылку для загрузки ( например, эту) в Firefox/Chrome. Проблема с этим методом заключается в том, что iOS Safari, похоже, не обрабатывает загрузку больших двоичных объектов правильно - вы можете преобразовать большой двоичный объект в URL-адрес данных с помощью FileReader и открыть его в новом окне, но при этом файл открывается, а не сохраняется.
У меня была точно такая же проблема. Моим решением было использовать временные файлы, так как я уже генерировал кучу временных файлов. Форма представляется вместе с:
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.
Вы можете полагаться на кеш браузера и запускать вторую загрузку того же файла, когда файл загружается в кеш.
$('#link').click(function(e) {
e.preventDefault();
var url = $(this).attr('href');
var request = new XMLHttpRequest();
request.responseType = "blob";
request.open("GET", url);
var self = this;
request.onreadystatechange = function () {
if (request.readyState === 4) {
var file = $(self).data('file');
var anchor = document.createElement('a');
anchor.download = file;
console.log(file);
console.log(request);
anchor.href = window.URL.createObjectURL(request.response);
anchor.click();
console.log('Completed. Download window popped up.');
}
};
request.send();
});
Если у вас есть загруженный файл, который сохраняется, а не находится в документе, невозможно определить, когда загрузка завершена, поскольку это не входит в область действия текущего документа, а представляет собой отдельный процесс в браузере.
Primefaces также использует опрос файлов cookie
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);
}
},
Вопрос заключается в том, чтобы во время генерации файла был индикатор ожидания, а после загрузки файл возвращался в нормальное состояние. Мне нравится делать это, используя скрытый 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'");
}
}
Быстрое решение, если вы хотите отображать только сообщение или gif-загрузчик до тех пор, пока не отобразится диалоговое окно загрузки, - это поместить сообщение в скрытый контейнер, и когда вы нажимаете кнопку, генерирующую файл для загрузки, вы делаете контейнер видимым. Затем используйте jquery или javascript, чтобы перехватить событие focusout кнопки, чтобы скрыть контейнер, содержащий сообщение.
Я обновил приведенный ниже справочный код. Добавьте правильный URL-адрес для загрузки и попробуйте это.
Ссылка:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/progress_event#live_example
Этот пример Java/Spring определяет конец загрузки, и в этот момент скрывается индикатор "Загрузка...".
Подход: на стороне JS установите Cookie с максимальным сроком действия 2 минуты и опрашивайте каждую секунду на предмет истечения срока действия cookie. Затем серверная сторона заменяет этот файл cookie более ранним сроком действия - завершением серверного процесса. Как только в JS-опросе обнаруживается истечение срока действия cookie, "Загрузка..." скрывается.
Сторона 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();
}
}
Серверная часть 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;
}
Если Xmlhttprequest с blob не является опцией, вы можете открыть свой файл в новом окне и проверить, заполняются ли элементы en y в этом теле окна с интервалом.
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
Создайте 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);