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



1
Я позначав це як дублікат, незважаючи на різницю платформи, тому що, наскільки я бачу, рішення однакове (Ви не можете і не потрібно робити це через Ajax).
Pekka

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

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

Bluish цілком прав щодо цього, ви не можете зробити це через Ajax, оскільки JavaScript не може зберігати файли безпосередньо на комп'ютері користувача (з погляду безпеки). На жаль, вказівка URL-адреси головного вікна під час завантаження файлу означає, що ви мало контролюєте, який досвід користувач має, коли відбувається завантаження файлу.

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

227

Ніхто не публікував це рішення @ Pekka ... тому я його опублікую. Це може комусь допомогти.

Вам не потрібно робити це через Ajax. Просто використовуйте

window.location="download.action?para1=value1...."

4
Приємно ... як я боровся з обробкою запиту на завантаження файлів та використанням jquery ajax..і це рішення ідеально підходить для мене .. + 1
swapnesh

45
Зауважте, що для цього потрібно, щоб сервер встановив заголовок Content-Disposition "вкладення", інакше веб-переглядач перенаправить на (і відобразить) вміст відповіді
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()це не працює у firefox ... Будь-яка ідея?
bigpony

У деяких браузерах вам може знадобитися додати aдо dom, щоб цей код працював та / або revokeObjectURLdocument.body.appendChild(a)
видаляв

врятував мій день (а можливо, і роботу :)) Не експерт з JavaScript за будь-якими заходами ... більше хлопець Java Однак я не маю поняття, чому простий "createObjectURL (новий Blob ([atob (base64)]))" не працює! Це просто ні, тоді як всі інстинкти говорять, що треба. grrr ...
apil.tamang

на лінії var bytechars = atob(base64)він видає помилку JavaScript runtime error: InvalidCharacterError. Я використовую Chrome версії 75.0.3770.142, але я не знаю, що тут не так.
Муфлікс

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: завантаження файлу в якості вкладення

<!-- 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 як сервлет, хак для прямого потокового передачі ), вам не потрібно нічого прямо писати у відповідь; просто використовувати тип результату Stream і конфігурувати його в 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): файл відкриття сервлета (/ дії) всередині браузера

Якщо ви хочете відкрити файл всередині браузера, замість того, щоб завантажувати його, вміст диспозиції повинен бути встановлений як вбудований , але ціль не може бути поточним розташуванням вікна; ви повинні орієнтуватися на нове вікно, створене 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помилку.
Муфлікс

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
Дякую, я використав це рішення. Працював як шарм. Крім того, якщо ви не отримаєте краплі від відповіді, просто створіть новий Blob.
fabio.sang

6
Краща версія з посиланням на
startWith_R

Посилання від @startsWith_R дійсно допомагає, якщо ви працюєте з IE11
alexventuraio

Дякую, це працювало на мене!
Закі Мухаммед

23

Я створив невелику функцію як вирішення способу (натхненний плагіном @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?
Shayne

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-адресу та приєднуємо її до невидимого посилання "a" та натискаємо його. Я зробив тут запит 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, як у попередньому прикладі.

CAVEAT: Будьте обережні щодо потенціалу змінних ін'єкцій у цих формах. Можливо, є більш безпечний спосіб кодування цих змінних. Крім того, роздумуйте про їх втечу.


Це робочий приклад. Дякую. Чи можна це зробити без iframe, але без window.location?
Marek Bar

Я припускаю, що ви могли просто додати приховану форму до нижньої частини DOM. Також можливо варто вивчити використання дому Shadow, хоча це не обов’язково добре підтримується у старих браузерах.
Shayne

У цьому коді я отримую цю помилку. 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: Це, ймовірно, буде якоюсь проблемою безпеки перехресного походження. Thats, ймовірно, цілий стек переповнює питання в і про себе. @ bartex9: Це сильно залежатиме від того, яку структуру ви використовуєте. Але принцип полягав би в тому, щоб взяти ім’я та шлях і зберегти це, під час виштовхування самого файлу у доступну для веб-області файлової системи або щось на зразок amazon S3 для високої доступності
Shayne

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

здається, що ваша подія завантаження не викликається для вмісту вкладеного файлу Content-Disposition (тому що нічого не завантажено в iframe), якщо воно працює для вас (ви отримуєте console.log) pls опублікуйте зразок
kofifus

Ось швидкий скрипт jsfiddle.net/y2xezyoj, це запускає жопу події завантаження, як тільки файл pdf завантажується в iframe. Ця скрипка не завантажує, оскільки ключ для завантаження знаходиться на стороні сервера "response.setHeader (" Вміст -disposition "," вкладення ; ім'я файлу = \ "" + fileName + ".xlsx \" ");"
manukyanv07

1
так, він буде працювати в цьому випадку, але якщо файл буде завантажений, тобто сервер надсилає Content-Disposition: attachment, тоді подія завантаження не запуститься, що було моєю точкою
kofifus

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

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

2
Швидке запитання, чи не створить цей файл двічі? Як тільки ви надішліть запит на ajax. Потім ви також переспрямовуєте сторінку на ту саму URL-адресу. Як ми можемо це усунути?
кодери

Не в моєму випадку. Я проте протестував його лише на Chrome.
aarkerio

Як кодери вже констатують правильно, дія викликається двічі.
Свен

Для мене це дзвонять двічі.
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)
}

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, який генерує байтовий масив

@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';
}

iframe

<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. Після повернення "успіху" з сервера зателефонуйте до form.submit (), щоб просто передати потоковий файл, збережений у сеансі.

Це корисно у випадку, коли ви хочете вирішити, чи потрібно завантажувати файл після створення form.submit (), наприклад: може бути випадок, коли на form.submit () на сервері відбувається виняток, а замість цього про збої, можливо, вам доведеться показати користувацьке повідомлення на стороні клієнта, в такому випадку ця реалізація може допомогти.


0

є ще одне рішення для завантаження веб-сторінки в ajax. Але я маю на увазі сторінку, яку спочатку треба обробити, а потім завантажити.

Спочатку потрібно відокремити обробку сторінки від завантаження результатів.

1) У виклику ajax проводяться лише розрахунки сторінки.

$ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"},

       функція (дані, статус) 
       {
            if (статус == "успіх") 
            {
                / * 2) У відповідь завантажується сторінка, яка використовує попередні обчислення. Наприклад, це може бути сторінка, яка друкує результати таблиці, обчислені під час виклику ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Наприклад: у CalculusPage.php

    if (! порожній ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "ВСТАВИТЬ INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "", "". $ _ POST ["data2"]. "") WHERE id = ". $ ID;
        ...
    }

// Наприклад: у програмі DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * FROM ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    заголовок ("Content-Type: application / vnd.ms-excel");
    заголовок ("Content-Disposition: inline; filename = $ filename");

    ...

Я сподіваюся, що це рішення може бути корисним для багатьох, як і для мене.


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

        }

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.