Laden Sie eine Datei von jQuery.Ajax herunter

419

Ich habe eine Struts2-Aktion auf der Serverseite zum Herunterladen von Dateien.

<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>

Wenn ich jedoch die Aktion mit jQuery aufrufe:

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

In Firebug sehe ich, dass die Daten mit dem Binär-Stream abgerufen werden . Ich frage mich, wie ich das Fenster zum Herunterladen von Dateien öffnen kann, mit dem der Benutzer die Datei lokal speichern kann.

hguser
quelle
1
Ich habe es trotz des Plattformunterschieds als Duplikat markiert, da die Lösung meines Erachtens dieselbe ist (Sie können und müssen dies nicht über Ajax tun).
Pekka
1
Verwenden Sie also ohne Ajax einfach window.location = "download.action? para1 = value1 ...."?
Hguser

Antworten:

676

2019 Update moderner Browser

Dies ist der Ansatz, den ich jetzt mit ein paar Einschränkungen empfehlen würde:

  • Ein relativ moderner Browser ist erforderlich
  • Wenn erwartet wird, dass die Datei sehr groß ist , sollten Sie wahrscheinlich etwas Ähnliches wie beim ursprünglichen Ansatz (Iframe und Cookie) tun, da einige der folgenden Vorgänge wahrscheinlich Systemspeicher verbrauchen könnten, der mindestens so groß ist wie die heruntergeladene Datei und / oder eine andere interessante CPU Nebenwirkungen.

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 Ursprünglicher jQuery / iframe / Cookie-basierter Ansatz

Bluish hat diesbezüglich völlig Recht. Sie können dies nicht über Ajax tun, da JavaScript Dateien aus Sicherheitsgründen nicht direkt auf dem Computer eines Benutzers speichern kann. Leider zeigt das HauptfensterWenn Sie URL auf Ihren Dateidownload nur wenig Kontrolle darüber, wie die Benutzererfahrung beim Herunterladen von Dateien ist.

Ich habe jQuery File Download erstellt, das eine "Ajax-ähnliche" Erfahrung mit Dateidownloads mit OnSuccess- und OnFailure-Rückrufen ermöglicht, um eine bessere Benutzererfahrung zu erzielen. Werfen Sie einen Blick auf meinen Blog-Beitrag über das häufig auftretende Problem, das das Plugin löst, und einige Verwendungsmöglichkeiten sowie eine Demo von jQuery File Download in Aktion . Hier ist die Quelle

Hier ist eine einfache Anwendungsfall-Demo unter Verwendung der Plugin- Quelle mit Versprechungen. Die Demoseite enthält auch viele andere, bessere UX-Beispiele.

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

Abhängig davon, welche Browser Sie unterstützen müssen, können Sie möglicherweise https://github.com/eligrey/FileSaver.js/ verwenden, was eine explizitere Steuerung ermöglicht als die IFRAME-Methode, die jQuery File Download verwendet.

John Culviner
quelle
69
Ich liebe das, was Sie gebaut haben, aber ich vermute, dass Ihre Antwort hier etwas detaillierter sein sollte, um mehr StackOverFlow-Guthaben zu erhalten. Speziell darüber, wie Sie das Problem gelöst haben.
AnthonyVO
14
Es wäre schön, wenn Sie genau erwähnen würden, wie dieses "Plugin" die Einschränkung umgeht, anstatt uns zu zwingen, zu Ihrer Blog- / Plugin-Quelle zu gehen, um es zu sehen. Wird es beispielsweise stattdessen in einem Iframe veröffentlicht? Muss das Remote-Skript stattdessen die Datei speichern und eine URL zurückgeben?
Kevin B
2
@asgerhallas Sicher, aber das ist völlig nutzlos, wenn der Link weggeht.
Kevin B
26
Ich stimme zu, ein Blog ist ein weitaus besserer Ort, um ausführlich zu beschreiben, wie Sie Ihr Plugin verwenden und wie es funktioniert. Sie hätten aber zumindest einen kurzen Überblick darüber geben können, wie dieses Plugin das Problem löst. Dies löst beispielsweise das Problem, indem der Server ein Cookie setzt und Ihr Javascript kontinuierlich nach dem Cookie sucht, bis es existiert. Sobald es existiert, können wir davon ausgehen, dass der Download abgeschlossen ist. Mit dieser Art von Informationen könnte man leicht sehr schnell eine eigene Lösung entwickeln, und die Antwort hängt nicht mehr zu 100% von Ihrem Blog / Plugin / jquery ab und kann auf andere Bibliotheken angewendet werden.
Kevin B
1
Royi, so wie ich es verstehe, kann AJAX niemals Dateidownloads unterstützen, die zu einem Popup zum Herunterladen von Dateien führen, das auf der Festplatte gespeichert werden kann. Haben Sie einen Weg gefunden, den ich nicht kenne?
John Culviner
227

Niemand hat diese @ Pekka-Lösung gepostet ... also werde ich sie posten. Es kann jemandem helfen.

Sie müssen dies nicht über Ajax tun. Benutz einfach

window.location="download.action?para1=value1...."
bläulich
quelle
4
Schön ... da ich Probleme hatte, die Eingabeaufforderung für die Download-Datei zu handhaben und jquery ajax zu verwenden ... und diese Lösung funktioniert perfekt für mich ... + 1
swapnesh
45
Beachten Sie, dass der Server dafür einen Content-Disposition-Header-Wert von 'Anhang'
festlegen
21
Oder verwenden Sie alternativ, window.open(<url>, '_blank');um sicherzustellen, dass der Download Ihren aktuellen Browserinhalt nicht ersetzt (unabhängig vom Content-Disposition-Header).
Christopher King
4
Das Problem bei dieser Lösung besteht darin, dass Ihre Seite auf die Fehlerseite umgeleitet wird, wenn der Vorgang fehlschlägt / der Server einen Fehler zurückgibt. Um dies zu lösen, verwenden Sie die iFrame-Lösung
kofifus
4
Das eigentliche Problem bei dieser Lösung - Frage ist über POSTAnfrage.
Atomosk
35

Sie können mit HTML5

NB: Die zurückgegebenen Dateidaten MÜSSEN base64-codiert sein, da Sie keine JSON-Binärdaten codieren können

In meiner AJAXAntwort habe ich eine Datenstruktur, die so aussieht:

{
    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)
    }
}

Das bedeutet, dass ich Folgendes tun kann, um eine Datei über AJAX zu speichern

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

Die Funktion base64ToBlob wurde von hier übernommen und muss in Übereinstimmung mit dieser Funktion verwendet werden

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

Dies ist gut, wenn Ihr Server die zu speichernden Dateidaten speichert. Ich habe jedoch nicht ganz herausgefunden, wie man einen HTML4-Fallback implementieren würde

Luke Madhanga
quelle
1
Das a.click()scheint in Firefox nicht zu funktionieren ... Irgendeine Idee?
Bigpony
In einigen Browsern müssen Sie möglicherweise das azum Dom hinzufügen , damit dieser Code funktioniert, und / oder das revokeObjectURLTeil entfernen :document.body.appendChild(a)
Bigpony
hat meinen Tag gerettet (und möglicherweise auch einen Job :)) Kein JavaScript-Experte in irgendeiner Weise ... mehr Java-Typ. Ich habe jedoch keine Ahnung, warum ein einfaches "createObjectURL (neuer Blob ([atob (base64)])") nicht funktioniert! Es tut es einfach nicht, während jeder Instinkt sagt, dass es muss. grrr ...
apil.tamang
In der Zeile var bytechars = atob(base64)wird ein Fehler ausgegeben JavaScript runtime error: InvalidCharacterError. Ich verwende Chrome Version 75.0.3770.142, aber ich weiß nicht, was hier falsch ist.
Muflix
27

1. Framework-Agnostiker: Servlet-Download-Datei als Anhang

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

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

2. Struts2 Framework: Aktion zum Herunterladen der Datei als Anhang

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

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

Es ist besser, ein <s:a>Tag zu verwenden, das mit OGNL auf eine mit erstellte URL zeigt<s:url> Tag :

<!-- 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>

In den obigen Fällen, Sie müssen das schreiben Content-Disposition - Header an die Antwort , die Angabe , dass die Datei Bedarf heruntergeladen werden ( attachment) und nicht vom Browser geöffnet ( inline). Du brauchst den Inhaltstyp angeben und möchten möglicherweise den Dateinamen und die Länge hinzufügen (damit der Browser einen realistischen Fortschrittsbalken zeichnen kann).

Zum Beispiel beim Herunterladen einer 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...

Mit Struts2 (es sei denn, Sie verwenden die Aktion als Servlet, z. B. einen Hack für direktes Streaming ), müssen Sie nichts direkt in die Antwort schreiben. Die einfache Verwendung des Stream-Ergebnistyps und dessen Konfiguration in struts.xml funktioniert wie folgt: BEISPIEL

<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. Framework-Agnostiker (/ Struts2-Framework): Servlet (/ Action) öffnet Datei im Browser

Wenn Sie die Datei im Browser öffnen möchten, anstatt sie herunterzuladen, muss die Inhaltsdisposition auf Inline eingestellt sein , aber das Ziel kann nicht der aktuelle Speicherort des Fensters sein. Sie müssen auf ein neues Fenster abzielen, das mit Javascript erstellt wurde, <iframe>auf die Seite oder auf ein neues Fenster, das im laufenden Betrieb mit dem "diskutierten" target = "_ blank" erstellt wurde:

<!-- 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>
Andrea Ligios
quelle
2
Sir, Ihre Eingabe: "Content-Disposition", "inline; .... hat den Tag des armen Programmierers gerettet :)
Vedran Maricevic.
1
Dies ist die einzige Antwort, die "window.open" erwähnt (einer der Kommentare erwähnt es).
Andrew Koster
Es funktioniert nicht, wenn Sie viele Parameter haben, da Sie too long urlFehler erhalten.
Muflix
25

Der einfache Weg, um den Browser zum Herunterladen einer Datei zu veranlassen, besteht darin, die Anfrage wie folgt zu stellen:

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

Dies öffnet das Popup-Fenster zum Herunterladen des Browsers.

João Marcos
quelle
3
Vielen Dank, ich habe diese Lösung verwendet. Lief wie am Schnürchen. Wenn die Antwort keinen Blob enthält, erstellen Sie einfach einen neuen Blob.
fabio.sang
6
Eine bessere Version mit IE Handling Link
startet mit_R
Der Link von @startsWith_R hilft wirklich, wenn Sie mit IE11 arbeiten
alexventuraio
Danke, es hat bei mir funktioniert!
Zaki Mohammed
23

Ich habe wenig Funktion als Workaround-Lösung erstellt (inspiriert vom @ JohnCulviner-Plugin):

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

Demo mit Klickereignis:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
ndpu
quelle
Dadurch werden die Daten jedoch auf sehr seltsame Weise an den Server gesendet. Ich frage mich, ob es geändert werden könnte, um einen kompatiblen POST zu erstellen.
Shayne
16

Ich stand vor dem gleichen Problem und löste es erfolgreich. Mein Anwendungsfall ist dies.

" JSON-Daten an den Server senden und eine Excel-Datei empfangen. Diese Excel-Datei wird vom Server erstellt und als Antwort an den Client zurückgegeben. Laden Sie diese Antwort als Datei mit benutzerdefiniertem Namen im Browser herunter. "

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

Das obige Snippet macht nur folgendes

  • Posten eines Arrays als JSON auf dem Server mit XMLHttpRequest.
  • Nachdem wir den Inhalt als Blob (binär) abgerufen haben, erstellen wir eine herunterladbare URL, hängen sie an einen unsichtbaren "a" -Link an und klicken darauf. Ich habe hier eine POST-Anfrage gestellt. Stattdessen können Sie auch ein einfaches GET wählen. Wir können die Datei nicht über Ajax herunterladen, sondern müssen XMLHttpRequest verwenden.

Hier müssen wir einige Dinge auf der Serverseite sorgfältig einstellen. Ich habe einige Header in Python Django HttpResponse gesetzt. Sie müssen sie entsprechend einstellen, wenn Sie andere Programmiersprachen verwenden.

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

Da ich hier xls (excel) herunterlade, habe ich contentType auf über eins eingestellt. Sie müssen es entsprechend Ihrem Dateityp einstellen. Mit dieser Technik können Sie beliebige Dateien herunterladen.

Naren Yellavula
quelle
"Wir können die Datei nicht über Ajax herunterladen, müssen XMLHttpRequest verwenden". XMLHttpRequest ist per Definition AJAX. Ansonsten tolle Lösung für moderne Webbrowser. Für IE, das nicht unterstützt wird HTMLAnchorElement.download, denke ich darüber nach, es mit der proprietären msSaveOrOpenBlob- Methode zu kombinieren .
Tsahi Asher
15

Ok, basierend auf ndpus Code ist hier eine verbesserte (glaube ich) Version von 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();
}

Verwenden Sie dies so; -

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

Die Parameter werden als richtige Post-Parameter gesendet, als ob sie von einer Eingabe stammen, und nicht als json-codierte Zeichenfolge wie im vorherigen Beispiel.

CAVEAT: Seien Sie vorsichtig hinsichtlich des Potenzials einer variablen Injektion in diese Formen. Es gibt möglicherweise eine sicherere Möglichkeit, diese Variablen zu codieren. Alternativ erwägen Sie, ihnen zu entkommen.

Shayne
quelle
Dies ist ein funktionierendes Beispiel. Vielen Dank. Ist das ohne iframe aber ohne window.location möglich?
Marek Bar
Ich nehme an, Sie könnten das versteckte Formular einfach an den unteren Rand des DOM anhängen. Möglicherweise lohnt es sich auch, die Verwendung des Shadow-Doms zu erkunden, obwohl dies in älteren Browsern nicht unbedingt gut unterstützt wird.
Shayne
In diesem Code erhalte ich diesen Fehler. 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.
nichtig
Wie kann ich dieses Formular einer Modellklasse zuordnen? Ich habe: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) aber es funktioniert nicht ..
Bartex9
void: Das wäre wahrscheinlich eine Art Cross-Origin-Sicherheitsproblem. Das ist wahrscheinlich eine ganze Stapelüberlauffrage an und für sich. @ bartex9: Das hängt stark davon ab, welche Art von Framework Sie verwenden. Aber das Prinzip wäre, den Namen und den Pfad zu nehmen und zu speichern, während die Datei selbst in einen über das Internet zugänglichen Bereich des Dateisystems verschoben wird, oder etwas wie Amazon S3 für hohe Verfügbarkeit
Shayne
8

Hier ist was ich getan habe, reines Javascript und HTML. Habe es nicht getestet, aber das sollte in allen Browsern funktionieren.

Javascript-Funktion

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

Verwenden Sie nur Komponenten, die in allen Browsern unterstützt werden, keine zusätzlichen Bibliotheken.

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Hier ist mein serverseitiger JAVA Spring Controller Code.

@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
quelle
Es scheint, dass Ihr Ladeereignis nicht für Inhalte von Inhaltsdisposition-Anhängen aufgerufen wird (da nichts in den Iframe geladen wird). Wenn es für Sie funktioniert (Sie erhalten das console.log), veröffentlichen Sie bitte ein Beispiel
kofifus
Hier ist eine kurze Geige jsfiddle.net/y2xezyoj dies löst den Ladeereignisarsch aus, sobald die PDF-Datei in den Iframe geladen wird. Diese Geige wird nicht heruntergeladen, da sich der Schlüssel zum Herunterladen auf der Serverseite befindet. "Response.setHeader (" Content -disposition "," Anhang ; Dateiname = "+ Dateiname +" .xlsx ");"
Manukyanv07
1
Ja, es wird in diesem Fall funktionieren, aber wenn die Datei heruntergeladen wird,
dh
Sie haben völlig Recht. Das Ladeereignis wird direkt nach Abschluss der Serververarbeitung ausgelöst und beginnt mit dem Senden der Datei. Dies ist, wonach ich gesucht habe: 1- Blockieren Sie die Schaltfläche und zeigen Sie die Verarbeitung an, damit der Benutzer eine Rückmeldung erhalten kann, dass etwas passiert. 2 - Wenn der Server mit der Verarbeitung fertig ist und die Datei senden möchte 3- (Ladeereignis wird ausgelöst), wo ich die Schaltfläche entsperre und den Verarbeitungsspinner 4 entferne, wird dem Benutzer nun die Datei zum Speichern angezeigt, oder der Browser beginnt mit dem Herunterladen den definierten Download-Speicherort. Entschuldigung, mein Englisch.
manukyanv07
5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
EL missaoui habib
quelle
Könnten Sie Ihre Antwort erklären? Das würde anderen helfen zu verstehen, was Sie getan haben, damit sie Ihre Techniken auf ihre Situationen anwenden können.
Wai Ha Lee
2
Nur eine Warnung: Safari und IE unterstützen das downloadAttribut nicht, sodass Ihre Datei den Namen "Unbekannt" trägt
Yangshun Tay
4

In Rails mache ich das so:

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

Der Trick ist der window.location- Teil. Die Methode des Controllers sieht folgendermaßen aus:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
aarkerio
quelle
2
Kurze Frage, wird die Datei dadurch nicht zweimal generiert? Sobald Sie die Ajax-Anfrage gesendet haben. Dann leiten Sie die Seite auch auf dieselbe URL um. Wie können wir das beseitigen?
Coderhs
Nicht in meinem Fall. Ich habe es allerdings nur auf Chrome getestet.
Aarkerio
Wie coderhs bereits richtig angibt, wird die Aktion zweimal aufgerufen.
Sven
Es wird auch zweimal für mich gerufen.
CSquared
4

Verwenden window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open

Sie können diese Codezeile beispielsweise in einen Klick-Handler einfügen:

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

Es wird eine neue Registerkarte geöffnet (aufgrund des Fensternamens '_blank') und auf dieser Registerkarte wird die URL geöffnet.

Ihr serverseitiger Code sollte auch Folgendes haben:

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

Auf diese Weise sollte der Browser den Benutzer auffordern, die Datei auf der Festplatte zu speichern, anstatt ihnen nur die Datei anzuzeigen. Außerdem wird die gerade geöffnete Registerkarte automatisch geschlossen.

Andrew Koster
quelle
4

Ich versuche, eine CSV-Datei herunterzuladen und dann etwas zu tun, nachdem der Download abgeschlossen ist. Ich muss also eine entsprechende callbackFunktion implementieren .

Die Verwendung window.location="..."ist keine gute Idee, da ich das Programm nach Abschluss des Downloads nicht mehr bedienen kann. So etwas ändern Sie den Header, so dass es keine gute Idee ist.

fetchist eine gute Alternative , kann jedoch IE 11 nicht unterstützen . Und window.URL.createObjectURLnicht unterstützen kann IE 11.You beziehen kann dies .

Dies ist mein Code, er ähnelt dem Code von Shahrukh Alam. Sie sollten jedoch darauf achten, dass window.URL.createObjectURLmöglicherweise Speicherlecks auftreten. Sie können beziehen sich diese . Wenn die Antwort eingegangen ist, werden die Daten im Speicher des Browsers gespeichert. Bevor Sie auf den aLink klicken , wurde die Datei heruntergeladen. Dies bedeutet, dass Sie nach dem Download alles tun können.

$.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
quelle
4

So laden Sie eine Datei herunter, nachdem Sie sie von AJAX erhalten haben

Es ist praktisch, wenn die Datei für längere Zeit erstellt wurde und Sie PRELOADER anzeigen müssen

Beispiel beim Absenden eines Webformulars:

<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>

Die optionale Funktion ist auskommentiert, um das Beispiel zu vereinfachen.

Es müssen keine temporären Dateien auf dem Server erstellt werden.

Unter jQuery v2.2.4 OK. Bei der alten Version tritt ein Fehler auf:

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').
Mike S.
quelle
Um den Dateinamen von Content-Disposition zu erhalten, funktionierte diese Übereinstimmung für mich: filename.match(/filename=(.*)/)[1](ohne doppelte Anführungszeichen oder Fragezeichen) - regex101.com/r/2AsD4y/2 . Ihre Lösung war jedoch die einzige Lösung, die nach langem Suchen funktioniert hat.
jstuardo
3

Hinzufügen einiger weiterer Dinge zur obigen Antwort zum Herunterladen einer Datei

Unten finden Sie einen Java-Spring-Code, der ein Byte-Array generiert

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

Jetzt können Sie im Javascript-Code mit FileSaver.js eine Datei mit dem folgenden Code herunterladen

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

Das obige wird Datei herunterladen

Dario Nascimento
quelle
2

Ok, hier ist der Arbeitscode für die Verwendung von MVC und Sie erhalten Ihre Datei von einem Controller

Nehmen wir an, Sie müssen Ihr Byte-Array deklarieren und füllen. Sie müssen lediglich die Datei-Funktion verwenden (mit System.Web.Mvc).

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

und fügen Sie dann in derselben Steuerung diese 2 Funktionen hinzu

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

und dann können Sie Ihren Controller anrufen, um den Rückruf "Erfolg" oder "Fehler" herunterzuladen und zu erhalten

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

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
Yannick Richard
quelle
1

Ich habe einen Fix gefunden, der es Ihnen ermöglicht, einen Javascript-Aufruf zu verwenden, um den Download anzufordern und dann einen Rückruf zu erhalten, wenn der Download tatsächlich startet. Ich fand dies hilfreich, wenn der Link ein serverseitiges Skript ausführt, das ein wenig Zeit benötigt, um die Datei vor dem Senden zu erstellen. Sie können sie also darauf hinweisen, dass sie verarbeitet werden. Wenn die Datei dann endgültig gesendet wird, entfernen Sie diese Verarbeitungsbenachrichtigung. Aus diesem Grund wollte ich zunächst versuchen, die Datei über Ajax zu laden, damit ein Ereignis auftreten kann, wenn die Datei angefordert wird, und ein anderes, wenn der Download tatsächlich beginnt.

das js auf der Titelseite

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

der 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>

dann die andere Datei:

<!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>

Ich denke, es gibt eine Möglichkeit, Get-Daten mit js zu lesen, sodass dann kein PHP benötigt wird. aber ich weiß es nicht sofort und der Server, den ich benutze, unterstützt PHP, so dass dies für mich funktioniert. dachte, ich würde es teilen, falls es jemandem hilft.

Kit Ramos
quelle
0

Wenn Sie jQuery File Download verwenden möchten, beachten Sie dies bitte für IE. Sie müssen die Antwort zurücksetzen, sonst wird sie nicht heruntergeladen

    //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

Ihre Aktion kann ServletResponseAware für den Zugriff implementiert werdengetServletResponse()

Alireza Fattahi
quelle
0

Es ist sicher, dass Sie dies nicht über einen Ajax-Anruf tun können.

Es gibt jedoch eine Problemumgehung.

Schritte :

Wenn Sie form.submit () zum Herunterladen der Datei verwenden, können Sie Folgendes tun:

  1. Erstellen Sie einen Ajax-Aufruf vom Client zum Server und speichern Sie den Dateistream in der Sitzung.
  2. Wenn "Erfolg" vom Server zurückgegeben wird, rufen Sie form.submit () auf, um nur den in der Sitzung gespeicherten Dateistream zu streamen.

Dies ist hilfreich, wenn Sie entscheiden möchten, ob die Datei nach dem Erstellen von form.submit () heruntergeladen werden muss oder nicht. Beispiel: Es kann vorkommen, dass auf form.submit () eine Ausnahme auf der Serverseite auftritt und stattdessen Nach einem Absturz müssen Sie möglicherweise eine benutzerdefinierte Nachricht auf der Clientseite anzeigen. In diesem Fall kann diese Implementierung hilfreich sein.

Aman Srivastava
quelle
0

Es gibt eine andere Lösung zum Herunterladen einer Webseite in Ajax. Ich beziehe mich jedoch auf eine Seite, die zuerst verarbeitet und dann heruntergeladen werden muss.

Zuerst müssen Sie die Seitenverarbeitung vom Ergebnisdownload trennen.

1) Im Ajax-Aufruf werden nur die Seitenberechnungen durchgeführt.

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

       Funktion (Daten, Status) 
       {
            if (status == "success") 
            {
                / * 2) In der Antwort wird die Seite heruntergeladen, die die vorherigen Berechnungen verwendet. Dies kann beispielsweise eine Seite sein, auf der die Ergebnisse einer im Ajax-Aufruf berechneten Tabelle gedruckt werden. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }}               
       }}
);

// Zum Beispiel: in der CalculusPage.php

    if (! empty ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERT INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') WHERE id =". $ ID;
        ...
    }}

// Zum Beispiel: in der DownloadPage.php

    $ ID = $ _GET ["ID"];

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

    $ filename = "Export_Data.xls";
    Header ("Inhaltstyp: application / vnd.ms-excel");
    Header ("Content-Disposition: Inline; Dateiname = $ Dateiname");

    ...

Ich hoffe, diese Lösung kann für viele nützlich sein, so wie es für mich war.

Netluke
quelle
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);
        }
    });
});'
Shahrukh Alam
quelle
Nur-Code-Antworten sollten mindestens eine Mindestbeschreibung enthalten, in der erläutert wird, wie der Code funktioniert und warum er auf die Frage antwortet.
Roberto Caboni
0

Das ist, dass es in jedem Browser so gut funktioniert (ich verwende asp.net core)

            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
quelle
-1

Ich hatte lange mit diesem Problem zu kämpfen. Schließlich half mir eine elegante externe Bibliothek, die hier vorgeschlagen wurde.

Eerik Sven Puudist
quelle