Скачать файл с помощью AJAX-запроса
Я хочу отправить "запрос на загрузку ajax", когда я нажимаю на кнопку, поэтому я попытался следующим образом:
JavaScript:
var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();
download.php:
<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");
readfile("file.txt");
?>
но не работает, как ожидалось, как я могу это сделать? заранее спасибо
14 ответов
Обновление 27 апреля 2015 г.
В начало и на сцену HTML5 входит атрибут загрузки. Он поддерживается в Firefox и Chrome, и скоро появится в IE11. В зависимости от ваших потребностей вы можете использовать его вместо запроса AJAX (или использовать window.location
) пока файл, который вы хотите загрузить, находится в том же месте, что и ваш сайт.
Вы всегда можете сделать запрос AJAX /window.location
запасной вариант с использованием некоторого JavaScript для проверки download
поддерживается, и если нет, переключение на вызов window.location
,
Оригинальный ответ
У вас не может быть AJAX-запроса, открывающего приглашение на загрузку, поскольку вам физически нужно перейти к файлу, чтобы запросить загрузку. Вместо этого вы можете использовать функцию успеха, чтобы перейти к download.php. Это откроет приглашение на загрузку, но не изменит текущую страницу.
$.ajax({
url: 'download.php',
type: 'POST',
success: function() {
window.location = 'download.php';
}
});
Хотя это отвечает на вопрос, лучше просто использовать window.location
и полностью избежать запроса AJAX.
Чтобы браузер загрузил файл, вам нужно сделать такой запрос:
function downloadFile(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download=fileName;
link.click();
};
req.send();
}
Тебе вообще не нужен ajax для этого. Если вы просто установили "download.php" в качестве href для кнопки, или, если это не ссылка, используйте:
window.location = 'download.php';
Браузер должен распознавать двоичную загрузку и не загружать реальную страницу, а просто использовать файл как загрузку.
Кросс-браузерное решение, протестировано на Chrome, Firefox, Edge, IE11.
В DOM добавьте скрытый тег ссылки:
<a id="target" style="display: none"></a>
Затем:
var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = null;
var contentType = req.getResponseHeader("content-type");
// IE/EDGE seems not returning some response header
if (req.getResponseHeader("content-disposition")) {
var contentDisposition = req.getResponseHeader("content-disposition");
fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
} else {
fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
}
if (window.navigator.msSaveOrOpenBlob) {
// Internet Explorer
window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
} else {
var el = document.getElementById("target");
el.href = window.URL.createObjectURL(blob);
el.download = fileName;
el.click();
}
};
req.send();
Это возможно. Вы можете начать загрузку из ajax-функции, например, сразу после создания файла.csv.
У меня есть функция ajax, которая экспортирует базу данных контактов в файл.csv, и сразу после ее завершения она автоматически начинает загрузку файла.csv. Итак, после того как я получил responseText и все в порядке, я перенаправил браузер так:
window.location="download.php?filename=export.csv";
Мой файл download.php выглядит так:
<?php
$file = $_GET['filename'];
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=".$file."");
header("Content-Transfer-Encoding: binary");
header("Content-Type: binary/octet-stream");
readfile($file);
?>
Нет обновления страницы, и файл автоматически начинает загружаться.
ПРИМЕЧАНИЕ. - Проверено в следующих браузерах:
Chrome v37.0.2062.120
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Я предпочитаю Location.assign()
https://developer.mozilla.org/en-US/docs/Web/API/Location.assign
Для тех, кто ищет более современный подход, вы можете использовать fetch API
. В следующем примере показано, как загрузить файл электронной таблицы. Это легко сделать с помощью следующего кода.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
})
Я считаю, что этот подход намного легче понять, чем другие XMLHttpRequest
решения. Кроме того, он имеет синтаксис, аналогичныйjQuery
подход, без необходимости добавлять какие-либо дополнительные библиотеки.
Конечно, я бы посоветовал проверить, для какого браузера вы разрабатываете, поскольку этот новый подход не будет работать в IE. Вы можете найти полный список совместимых браузеров по следующей ссылке.
Важно: в этом примере я отправляю запрос JSON на сервер, прослушивающий данныйurl
. Этаurl
должен быть установлен, на моем примере я предполагаю, что вы знаете эту часть. Также обратите внимание на заголовки, необходимые для работы вашего запроса. Поскольку я отправляю JSON, я должен добавитьContent-Type
заголовок и установите для него application/json; charset=utf-8
, чтобы сообщить серверу, какой тип запроса он получит.
Решение @Joao Marcos работает для меня, но мне пришлось изменить код, чтобы он работал в IE, ниже, если как выглядит код
downloadFile(url,filename) {
var that = this;
const extension = url.split('/').pop().split('?')[0].split('.').pop();
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "blob";
req.onload = function (event) {
const fileName = `${filename}.${extension}`;
const blob = req.response;
if (window.navigator.msSaveBlob) { // IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
}
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
};
req.send();
},
Это решение не очень отличается от приведенного выше, но для меня оно работает очень хорошо, и я думаю, что оно чисто.
Я предлагаю кодировать base64 на стороне файлового сервера (base64_encode(), если вы используете PHP) и отправлять данные в кодировке base64 клиенту
На клиенте вы делаете это:
let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
let uri = URL.createObjectURL(blob);
let link = document.createElement("a");
link.download = THE_FILE_NAME,
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
Этот код помещает закодированные данные в ссылку и имитирует щелчок по ссылке, а затем удаляет ее.
Расшифровка имени файла из заголовка немного сложнее...
var filename = "default.pdf";
var disposition = req.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1)
{
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1])
filename = matches[1].replace(/['"]/g, '');
}
это работает для меня
var dataObj = {
somekey:"someValue"
}
$.ajax({
method: "POST",
url: "/someController/someMethod",
data: dataObj,
success: function (response) {
const blob = new Blob([response], { type: 'text/csv' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.csv";
document.body.appendChild(a);
a.click();
}
});
var req = $.ajax({
type: 'GET',
cache: false,
url: "ScaricaXmlbyLink",
data: data,
success: function (response) {
const blob = new Blob([response], { type: 'text/xml' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xml";
document.body.appendChild(a);
a.click();
}
});
Ваши потребности покрыты window.location('download.php');
Но я думаю, что вам нужно передать файл для загрузки, а не всегда загружать один и тот же файл, и именно поэтому вы используете запрос, один из вариантов - создать файл php так же просто, как showfile.php, и выполнить запрос, например
var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);
showfile.php
<?php
$file = $_GET["file"]
echo $file;
где file - это имя файла, переданное через Get или Post в запросе, а затем просто перехватывает ответ в функции
if(ajaxRequest.readyState == 4){
var file = ajaxRequest.responseText;
window.location = 'downfile.php?file=' + file;
}
}
Есть другое решение для загрузки веб-страницы в ajax. Но я имею в виду страницу, которая должна быть сначала обработана, а затем загружена.
Для начала нужно отделить обработку страницы от загрузки результатов.
1) В вызове ajax выполняются только расчеты страницы.
$.post ("CalculusPage.php", {calculusFunction: true, ID: 29, данные1: "a", данные2: "b" }, функция (данные, статус) { если (статус == "успех") { /* 2) В ответ загружается страница, которая использует предыдущие расчеты. Например, это может быть страница, которая печатает результаты таблицы, вычисленной в вызове ajax. */ window.location.href = DownloadPage.php+"?ID="+29; } }); // Например: в CalculusPage.php if (!empty($_POST["calculusFunction"])) { $ID = $_POST["ID"]; $query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID; ... } // Например: в DownloadPage.php $ID = $_GET["ID"]; $sede = "SELECT * FROM ExamplePage WHERE id = ".$ID; ... $ Имя_файла = "Export_Data.xls"; заголовок ("Content-Type: application/vnd.ms-excel"); заголовок ("Content-Disposition: inline; filename=$filename"); ...
Я надеюсь, что это решение может быть полезным для многих, как и для меня.