Скачать файл от jQuery.Ajax

420

У меня есть действие Struts2 на стороне сервера для загрузки файлов.

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

Однако, когда я вызываю действие, используя jQuery:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

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

hguser
источник
1
Я пометил его как дубликат, несмотря на разницу в платформе, потому что, насколько я вижу, решение одно и то же (вы не можете и не должны делать это через Ajax).
Пекка
1
Итак, без ajax, просто используйте window.location = "download.action? para1 = value1 ...."?
hguser

Ответы:

676

2019 обновление современных браузеров

Такой подход я бы сейчас рекомендовал с несколькими оговорками:

  • Требуется относительно современный браузер
  • Если ожидается, что файл будет очень большим, вы, вероятно, должны сделать что-то похожее на оригинальный подход (iframe и cookie), потому что некоторые из приведенных ниже операций могут, вероятно, потреблять системную память, по крайней мере, такую ​​же, как загружаемый файл и / или другой интересный процессор побочные эффекты.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .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 = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Оригинальный подход jQuery / iframe / Cookie

Bluish абсолютно прав в этом, вы не можете сделать это через Ajax, потому что JavaScript не может сохранять файлы напрямую на компьютер пользователя (из соображений безопасности). К сожалению, указывая главное окно URL-адреса при загрузке файла означает, что у вас мало контроля над тем, что происходит с пользователем при загрузке файла.

Я создал jQuery File Download, который позволяет "Ajax-подобный" опыт загрузки файлов в комплекте с обратными вызовами OnSuccess и OnFailure, чтобы обеспечить лучшее взаимодействие с пользователем. Взгляните на мой пост в блоге об общей проблеме, которую решает плагин, и о некоторых способах ее использования, а также о демонстрации загрузки файла jQuery в действии . Вот источник

Вот простая демонстрация варианта использования с использованием исходного кода плагина с обещаниями. Демонстрационная страница включает в себя множество других, «лучше» UX примеров.

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

В зависимости от того, какие браузеры вам нужно поддерживать, вы можете использовать https://github.com/eligrey/FileSaver.js/, который обеспечивает более явный контроль, чем метод IFRAME, который использует jQuery File Download.

Джон Кульвинер
источник
69
Мне нравится то, что вы создали, но я подозреваю, что для получения большего кредита от StackOverFlow ваш ответ должен содержать немного больше деталей. Конкретно о том, как вы решили проблему.
AnthonyVO
14
было бы неплохо, если бы вы упомянули, как именно этот «плагин» преодолевает ограничение, а не заставляли нас заходить в ваш блог / плагин, чтобы увидеть его. например, вместо этого отправлять в iframe? вместо этого требуется удаленный скрипт, чтобы сохранить файл и вернуть ему URL?
Кевин Б.
2
@asgerhallas Конечно, но это совершенно бесполезно, если указанная ссылка исчезнет.
Кевин Б
26
Я согласен, блог - гораздо лучшее место для размещения подробного описания того, как использовать ваш плагин и как он работает. но вы могли бы хотя бы дать краткий обзор того, как этот плагин решает проблему. Например, это решает проблему, когда сервер устанавливает cookie, а ваш JavaScript постоянно ищет cookie, пока он не существует. Если он существует, мы можем предположить, что загрузка завершена. С такой информацией можно легко быстро развернуть собственное решение, и ответ больше не зависит на 100% от вашего блога / плагина / jquery и может быть применен к другим библиотекам.
Кевин Б.
1
Ройи, насколько я понимаю, AJAX никогда не может поддерживать загрузку файлов, в результате чего всплывающее окно загрузки файлов сохраняется на диск. Вы нашли способ, о котором я не знаю?
Джон Кульвинер
228

Никто не опубликовал это решение @ Пекки ... поэтому я опубликую его. Это может помочь кому-то.

Вам не нужно делать это через Ajax. Просто используйте

window.location="download.action?para1=value1...."
голубоватый
источник
4
Хороший ... как я боролся с обработкой приглашения к загрузке файла и использованием jquery ajax ... и это решение отлично работает для меня .. + 1
swapnesh
45
Обратите внимание, что для этого необходимо, чтобы сервер установил значение заголовка Content-Disposition равным «attachment», в противном случае браузер перенаправит (и отобразит) содержимое ответа
brichins
21
Или же используйте, window.open(<url>, '_blank');чтобы гарантировать, что загрузка не заменит ваш текущий контент браузера (независимо от заголовка Content-Disposition).
Кристофер Кинг
4
Проблема с этим решением заключается в том, что в случае сбоя операции / сервер возвращает ошибку, ваша страница будет перенаправлена ​​на страницу ошибки. Чтобы решить эту проблему, используйте решение iFrame
kofifus
4
Реальная проблема с этим решением - вопрос о POSTзапросе.
Атомоск
35

Вы можете с HTML5

Примечание: возвращаемые данные файла ДОЛЖНЫ быть закодированы в base64, поскольку вы не можете JSON кодировать двоичные данные.

В моем AJAXответе у меня есть структура данных, которая выглядит следующим образом:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

Это означает, что я могу сделать следующее, чтобы сохранить файл через AJAX

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

Функция base64ToBlob была взята отсюда и должна использоваться в соответствии с этой функцией

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

Это хорошо, если ваш сервер создает дамп файловых данных для сохранения. Тем не менее, я не совсем понял, как можно реализовать запасной вариант HTML4

Люк Мадханга
источник
1
a.click()Не похоже на работу в светлячок ... Любая идея?
bigpony
В некоторых браузерах вам может понадобиться добавить в adom для того, чтобы этот код работал и / или удалить revokeObjectURLчасть:document.body.appendChild(a)
bigpony
спас мой день (и, возможно, работу тоже :)) Не эксперт по javascript ни в коем случае ... больше ява парень. Однако я понятия не имею, почему простой «createObjectURL (new Blob ([atob (base64)])))» не работает! Это просто не так, в то время как все инстинкты говорят, что должны. grrr ...
apil.tamang
в строке var bytechars = atob(base64)выдает ошибку JavaScript runtime error: InvalidCharacterError. Я использую Chrome версии 75.0.3770.142, но я не знаю, что здесь не так.
Muflix,
27

1. Независимость от фреймворка: загрузка файла сервлетом в виде вложения

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: действие загрузки файла в виде вложения

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

Было бы лучше использовать <s:a>тег, указывающий с OGNL на URL, созданный с <s:url>тегом:

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

В вышеупомянутых случаях вам нужно записать заголовок Content-Disposition в ответ , указав, что файл должен быть загружен ( attachment) и не открыт браузером ( inline). Вам необходимо указать тип контента , и вы можете добавить имя и длину файла (чтобы браузер мог нарисовать реалистичный индикатор выполнения).

Например, при загрузке ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

В Struts2 (если вы не используете Action в качестве сервлета, например, хак для прямой потоковой передачи ), вам не нужно напрямую что-либо записывать в ответ; просто используя тип результата потока и его настройку в struts.xml будет работать: Пример

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Независимость от фреймворка (/ Struts2 framework): открытие файла сервлета (/ Action) внутри браузера

Если вы хотите открыть файл в браузере, вместо загрузки его, Content-disposition должен быть установлен в встроено , но целью не может быть текущее расположение окна; Вы должны настроить таргетинг на новое окно, созданное с помощью javascript, <iframe>на странице или новое окно, созданное на лету с «обсужденным» target = "_ blank":

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>
Андреа Лигиос
источник
2
Сэр, Ваш вклад: "Содержание-Диспозиция", "встроенный; .... спас день бедного кодировщика :)
Ведран Маричевич.
1
Это единственный ответ, который упоминает «window.open» (один из комментариев упоминает это).
Эндрю Костер
Это не работает, если у вас много параметров, потому что вы получите too long urlошибку.
Muflix
25

Простой способ заставить браузер загружать файл - сделать запрос следующим образом:

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

Откроется всплывающее окно загрузки браузера.

Жоао Маркос
источник
3
Спасибо, я использовал это решение. Работал как шарм. Кроме того, если вы не получили блоб из ответа, просто создайте новый блоб.
fabio.sang
6
Лучшая версия со ссылкой для
начинается с
Ссылка от @startsWith_R действительно помогает, если вы работаете с IE11
alexventuraio
Спасибо, это сработало для меня!
Заки Мохаммед
24

Я создал небольшую функцию как обходное решение (вдохновленный плагином @JohnCulviner):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Демо с событием клика:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
НДПУ
источник
Это отправляет данные очень странным способом на сервер. Интересно, можно ли это изменить, чтобы создать совместимый POST?
Шейн
16

Я столкнулся с той же проблемой и успешно решил ее. Мой вариант использования это.

« Отправьте данные JSON на сервер и получите файл Excel. Этот файл Excel создается сервером и возвращается как ответ клиенту. Загрузите этот ответ как файл с пользовательским именем в браузере »

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

Вышеуказанный фрагмент просто делает следующее

  • Отправка массива в виде JSON на сервер с использованием XMLHttpRequest.
  • После извлечения содержимого в виде двоичного объекта (двоичного объекта) мы создаем загружаемый URL-адрес и прикрепляем его к невидимой ссылке «а», а затем нажимаем на нее. Я сделал запрос POST здесь. Вместо этого вы можете пойти на простой GET тоже. Мы не можем скачать файл через Ajax, мы должны использовать XMLHttpRequest.

Здесь нам нужно тщательно настроить несколько вещей на стороне сервера. Я установил несколько заголовков в Python Django HttpResponse. Вам необходимо установить их соответствующим образом, если вы используете другие языки программирования.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Так как я загружаю xls (excel) здесь, я настроил contentType выше одного. Вам нужно установить его в соответствии с вашим типом файла. Вы можете использовать эту технику для загрузки любых файлов.

Нарен Йеллавула
источник
«Мы не можем загрузить файл через Ajax, мы должны использовать XMLHttpRequest». XMLHttpRequest является AJAX по определению. В противном случае отличное решение для современных веб-браузеров. Для IE, который не поддерживает HTMLAnchorElement.download, я думаю объединить его с фирменным методом msSaveOrOpenBlob .
Цахи Ашер
15

Хорошо, на основе кода ndpu вот улучшенная (я думаю) версия ajax_download; -

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Используйте это как это; -

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

Параметры отправляются как правильные параметры после ввода, как если бы они поступали с входных данных, а не как строка в кодировке json, как в предыдущем примере.

ПРЕДУПРЕЖДЕНИЕ: будьте осторожны с возможностью переменного введения на этих формах. Там может быть более безопасный способ кодирования этих переменных. В качестве альтернативы созерцайте избежать их.

Шэйн
источник
Это рабочий пример. Спасибо. Можно ли сделать это без iframe, но без window.location?
Marek Bar
Я полагаю, вы могли бы просто добавить скрытую форму в нижней части DOM. Также возможно стоит изучить использование Shadow dom, хотя это не всегда хорошо поддерживается в старых браузерах.
Шейн
В этом коде я получаю эту ошибку. Uncaught SecurityError: Blocked a frame with origin "http://foo.bar.com" from accessing a frame with origin "null". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
аннулировано
Как я могу сопоставить эту форму с некоторым классом модели? У меня есть: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) но это не работает ..
bartex9
void: Скорее всего, это будет проблема безопасности из разных источников. Это, вероятно, вопрос о переполнении стека сам по себе. @ bartex9: Это сильно зависит от того, какую платформу вы используете. Но принцип будет заключаться в том, чтобы взять имя и путь и сохранить их, одновременно помещая сам файл в область файловой системы, доступную через Интернет, или что-то вроде amazon S3 для обеспечения высокой доступности
Шейн,
8

Вот что я сделал, чистый JavaScript и HTML. Не проверял, но это должно работать во всех браузерах.

Функция Javascript

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Использование только компонентов, которые поддерживаются во всех браузерах, без дополнительных библиотек.

введите описание изображения здесь введите описание изображения здесь

Вот мой код контроллера JAVA Spring на стороне сервера.

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
manukyanv07
источник
кажется, что ваше событие загрузки не вызывается для содержимого вложения Content-disposition (потому что в iframe ничего не загружается), если оно работает для вас (вы получаете console.log),
пожалуйста, опубликуйте
Вот быстрая скрипка jsfiddle.net/y2xezyoj, которая запускает задницу события загрузки, как только файл PDF загружается в iframe. Эта скрипка не загружается, поскольку ключ для загрузки находится на стороне сервера "response.setHeader (" Content -disposition "," attachment ; filename = \ "" + fileName + ".xlsx \" ");"
manukyanv07
1
да, это будет работать в этом случае, но если файл загружен, то есть сервер отправляет Content-Disposition: attachment, то событие загрузки не сработает, что было моей
целью
Вы совершенно правы, событие загрузки запускается сразу после того, как сервер завершил обработку, и начинается отправка файла. Это то, что я искал: 1 - заблокируйте кнопку и покажите обработку, чтобы пользователь мог узнать, что происходит. 2 - Затем, когда сервер завершит обработку и собирается отправить файл 3- (событие загрузки запущено), где я разблокирую кнопку и удалю счетчик обработки 4 - пользователь теперь выскакивает файл сохранения или браузер начинает загружать его в определенное место загрузки. Извините мой английский.
manukyanv07
5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
Эль Миссауи Хабиб
источник
Не могли бы вы объяснить свой ответ? Это помогло бы другим понять, что вы сделали, чтобы они могли применять ваши методы в своих ситуациях.
Вай Ха Ли
2
Просто предупреждение: Safari и IE не поддерживают downloadатрибут, поэтому ваш файл в конечном итоге будет иметь имя «Неизвестно»
Яншун Тай
4

В Rails я делаю это так:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

Хитрость в части window.location . Метод контроллера выглядит так:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
aarkerio
источник
2
Быстрый вопрос, это не сгенерирует файл дважды? После того как вы отправите запрос AJAX. Затем вы также перенаправляете страницу на тот же URL. Как мы можем устранить это?
coderhs
Не в моем случае. Я только проверял это на Chrome все же.
Ааркерио
Поскольку кодеры уже заявляют правильно, действие вызывается дважды.
Свен
Мне тоже звонят дважды.
CSquared
4

Используйте window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open

Например, вы можете поместить эту строку кода в обработчик кликов:

window.open('/file.txt', '_blank');

Откроется новая вкладка (из-за имени окна _blank), и эта вкладка откроет URL.

Ваш код на стороне сервера также должен иметь что-то вроде этого:

res.set('Content-Disposition', 'attachment; filename=file.txt');

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

Эндрю Костер
источник
4

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

Использование window.location="..."не является хорошей идеей, потому что я не могу управлять программой после окончания загрузки. Как-то так, измени заголовок, чтобы он не был хорошей идеей.

fetchхорошая альтернатива, однако она не может поддерживать IE 11 . И window.URL.createObjectURLне может поддерживать IE 11. Вы можете сослаться на это .

Это мой кодекс, он похож на кодекс Шахрукха Алама. Но вы должны позаботиться о том, чтобы, window.URL.createObjectURLвозможно, создать утечки памяти. Вы можете сослаться на это . После получения ответа данные будут сохранены в памяти браузера. Поэтому, прежде чем нажать на aссылку, файл был загружен. Это означает, что вы можете сделать что-нибудь после загрузки.

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);

    //Do something after download 
    ...

    }
}).then(after_download)
}
kyakya
источник
4

Как ЗАГРУЗИТЬ файл после получения AJAX

Это удобно, когда файл создается долго и нужно показывать PRELOADER

Пример при отправке веб-формы:

<script>
$(function () {
    $('form').submit(function () {
        $('#loader').show();
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serialize(),
            dataType: 'binary',
            xhrFields: {
                'responseType': 'blob'
            },
            success: function(data, status, xhr) {
                $('#loader').hide();
                // if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
                //     var reader = new FileReader();
                //     reader.readAsText(data);
                //     reader.onload = function() {alert(reader.result);};
                //     return;
                // }
                var link = document.createElement('a'),
                    filename = 'file.xlsx';
                // if(xhr.getResponseHeader('Content-Disposition')){//filename 
                //     filename = xhr.getResponseHeader('Content-Disposition');
                //     filename=filename.match(/filename="(.*?)"/)[1];
                //     filename=decodeURIComponent(escape(filename));
                // }
                link.href = URL.createObjectURL(data);
                link.download = filename;
                link.click();
            }
        });
        return false;
    });
});
</script>

Необязательный функционал закомментирован для упрощения примера.

Не нужно создавать временные файлы на сервере.

На jQuery v2.2.4 ОК. На старой версии будет ошибка:

Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
Майк С
источник
Чтобы получить имя файла из Content-Disposition, это совпадение сработало для меня: filename.match(/filename=(.*)/)[1](без двойных кавычек или знака вопроса) - regex101.com/r/2AsD4y/2 . Тем не менее, ваше решение было единственным решением, которое сработало после многих поисков.
jstuardo
3

Добавив еще несколько вещей к ответу выше для скачивания файла

Ниже приведен код Java Spring, который генерирует массив байтов.

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Теперь в коде JavaScript, используя FileSaver.js, можете скачать файл с кодом ниже

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

Выше будет скачать файл

Дарио Насименто
источник
2

Итак, вот рабочий код при использовании MVC, и вы получаете свой файл из контроллера

Допустим, у вас есть объявление и заполнение вашего байтового массива, единственное, что вам нужно сделать, это использовать функцию File (используя System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

а затем, в том же контроллере, добавить 2 функции

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

и тогда вы сможете позвонить своему контроллеру, чтобы загрузить и получить ответный звонок «успех» или «сбой»

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
Янник Ричард
источник
1

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

JS на первой странице

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

фрейм

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

тогда другой файл:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

Я думаю, что есть способ прочитать данные с использованием js, поэтому никакой php не понадобится. но я не знаю это от руки, и сервер, который я использую, поддерживает php, так что это работает для меня. думал, что поделюсь этим на случай, если это кому-нибудь поможет.

Кит Рамос
источник
0

Если вы хотите использовать jQuery File Download, обратите внимание на это для IE. Вам нужно сбросить ответ или он не будет загружен

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Ваше действие может быть реализовано ServletResponseAware для доступаgetServletResponse()

Алиреза Фаттахи
источник
0

Несомненно, что вы не можете сделать это с помощью вызова Ajax.

Тем не менее, есть обходной путь.

Шаги:

Если вы используете form.submit () для загрузки файла, вы можете сделать следующее:

  1. Создайте ajax-вызов от клиента к серверу и сохраните поток файлов внутри сеанса.
  2. После «успеха», возвращаемого с сервера, вызовите вашу форму.submit (), чтобы просто передать поток файлов, сохраненный в сеансе.

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

Аман Шривастава
источник
0

есть другое решение для загрузки веб-страницы в 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»);

    ...

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

netluke
источник
0
The HTML Code:-

'<button type="button" id="GetFile">Get File!</button>'


The jQuery Code:-

'$('#GetFile').on('click', function () {
    $.ajax({
        url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            var a = document.createElement('a');
            var url = window.URL.createObjectURL(data);
            a.href = url;
            a.download = 'myfile.pdf';
            document.body.append(a);
            a.click();
            a.remove();
            window.URL.revokeObjectURL(url);
        }
    });
});'
Шахрух Алам
источник
Ответы только с кодом должны содержать как минимум минимальное описание, объясняющее, как работает код и почему он отвечает на вопрос.
Роберто Кабони
0

Вот так все отлично работает в любом браузере (я использую ядро ​​asp.net)

            function onDownload() {

  const api = '@Url.Action("myaction", "mycontroller")'; 
  var form = new FormData(document.getElementById('form1'));

  fetch(api, { body: form, method: "POST"})
      .then(resp => resp.blob())
      .then(blob => {
          const url = window.URL.createObjectURL(blob);
        $('#linkdownload').attr('download', 'Attachement.zip');
          $('#linkdownload').attr("href", url);
          $('#linkdownload')
              .fadeIn(3000,
                  function() { });

      })
      .catch(() => alert('An error occurred'));



}
 
 <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
 
 
 
 <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
 
 
 <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
 
 

        function onDownload() {
            const api = '@Url.Action("myaction", "mycontroller")'; 
            //form1 is your id form, and to get data content of form
            var form = new FormData(document.getElementById('form1'));

            fetch(api, { body: form, method: "POST"})
                .then(resp => resp.blob())
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    $('#linkdownload').attr('download', 'Attachments.zip');
                    $('#linkdownload').attr("href", url);
                    $('#linkdownload')
                        .fadeIn(3000,
                            function() {

                            });
                })
                .catch(() => alert('An error occurred'));                 

        }
Marinpietri
источник
-1

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

Ээрик Свен Пуудист
источник