Scarica un file di jQuery.Ajax


420

Ho un'azione Struts2 sul lato server per il download di file.

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

Tuttavia, quando chiamo l'azione usando jQuery:

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

in Firebug vedo che i dati vengono recuperati con il flusso binario . Mi chiedo come aprire la finestra di download del file con la quale l'utente può salvare il file localmente?



1
L'ho contrassegnato come duplicato nonostante la differenza di piattaforma, perché per quanto posso vedere la soluzione è la stessa (non è possibile e non è necessario farlo tramite Ajax).
Pekka,

1
quindi, senza Ajax, basta usare window.location = "download.action? para1 = value1 ...."?
hguser,

Risposte:


676

Aggiornamento dei browser moderni 2019

Questo è l'approccio che ora consiglierei con alcune avvertenze:

  • È richiesto un browser relativamente moderno
  • Se si prevede che il file sia molto grande, è probabile che si debba fare qualcosa di simile all'approccio originale (iframe e cookie) perché alcune delle operazioni seguenti potrebbero consumare memoria di sistema almeno grande quanto il file da scaricare e / o altra CPU interessante effetti collaterali.

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 Approccio originale basato su jQuery / iframe / Cookie

Bluish ha perfettamente ragione, non è possibile farlo tramite Ajax perché JavaScript non può salvare i file direttamente sul computer di un utente (per motivi di sicurezza). Sfortunatamente puntare l' URL della finestra principale sul download del file significa che hai poco controllo su ciò che l'esperienza dell'utente è quando si verifica un download di file.

Ho creato jQuery File Download che consente un'esperienza "Ajax like" con download di file completi di callback OnSuccess e OnFailure per offrire un'esperienza utente migliore. Dai un'occhiata al mio post sul blog sul problema comune che il plugin risolve e su alcuni modi per usarlo e anche una demo di jQuery File Download in azione . Ecco la fonte

Ecco una semplice demo del caso d'uso usando la fonte del plugin con le promesse. La pagina demo include anche molti altri esempi di "UX migliori".

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

A seconda dei browser che devi supportare, potresti essere in grado di utilizzare https://github.com/eligrey/FileSaver.js/ che consente un controllo più esplicito rispetto al metodo IFRAME utilizzato dal download del file jQuery.


69
Adoro quello che hai costruito, ma sospetto che per ottenere più credito StackOverFlow la tua risposta qui dovrebbe contenere un po 'più di dettaglio. In particolare su come hai risolto il problema.
AnthonyVO,

14
sarebbe bello se menzionassi esattamente come questo "plug-in" aggira la limitazione, piuttosto che costringerci ad andare al tuo blog / fonte di plug-in per vederlo. per esempio, sta invece postando su un iframe? richiede invece lo script remoto per salvare il file e restituire un URL ad esso?
Kevin B,

2
@asgerhallas Certo, ma è completamente inutile se detto link scompare.
Kevin B,

26
Sono d'accordo, un blog è un posto molto migliore per inserire una lunga descrizione di come utilizzare il plugin e come funziona. ma avresti potuto almeno dare una breve panoramica di come questo plugin risolve il problema. Ad esempio, questo risolve il problema facendo impostare al server un cookie e facendo in modo che il tuo javascript cerchi continuamente il cookie fino a quando non esiste. Una volta che esiste, possiamo presumere che il download sia completo. Con quel tipo di informazioni si potrebbe facilmente implementare la propria soluzione molto rapidamente e la risposta non si basa più sul 100% sul tuo blog / plugin / jquery e può essere applicata ad altre librerie.
Kevin B,

1
Royi, come ho capito AJAX non può mai supportare i download di file che si traducono in un popup di download di file da salvare su disco. Hai trovato un modo di cui non sono a conoscenza?
John Culviner,

228

Nessuno ha pubblicato questa soluzione di @ Pekka ... quindi la posterò. Può aiutare qualcuno.

Non è necessario farlo tramite Ajax. Basta usare

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

4
Bello ... mentre stavo lottando per gestire il prompt del download del file e usare jquery ajax..e questa soluzione funziona perfettamente per me .. + 1
swapnesh

45
Si noti che ciò richiede che il server imposti un valore di intestazione Content-Disposition di "allegato", altrimenti il ​​browser reindirizzerà (e visualizzerà) il contenuto della risposta
brichins

21
Oppure, in alternativa, utilizzare window.open(<url>, '_blank');per assicurarsi che il download non sostituisca il contenuto del browser corrente (indipendentemente dall'intestazione Content-Disposition).
Christopher King,

4
Il problema con questa soluzione è che se l'operazione non riesce / il server restituisce un errore, la tua pagina verrà reindirizzata alla pagina di errore. Per risolverlo, usa la soluzione iFrame
kofifus il

4
Il vero problema con questa soluzione: la domanda riguarda la POSTrichiesta.
Atomosk,

35

Puoi farlo con HTML5

NB: I dati del file restituiti DEVONO essere codificati in base64 perché non è possibile codificare JSON dati binari

Nella mia AJAXrisposta ho una struttura di dati che assomiglia a questa:

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

Ciò significa che posso fare quanto segue per salvare un file tramite 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);
}

La funzione base64ToBlob è stata presa da qui e deve essere utilizzata in conformità con questa funzione

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

Questo è utile se il tuo server sta scaricando i file da salvare. Tuttavia, non ho ancora capito come implementare un fallback HTML4


1
Il a.click()non sembra al lavoro in Firefox ... Qualche idea?
bigpony,

In alcuni browser potrebbe essere necessario aggiungere il adom al fine di far funzionare questo codice e / o rimuovere la revokeObjectURLparte:document.body.appendChild(a)
bigpony

mi ha salvato la giornata (e forse anche un lavoro :)) Non sono un esperto javascript sotto ogni punto di vista ... altro ragazzo java. Tuttavia, non ho idea del perché un semplice "createObjectURL (new Blob ([atob (base64)])" "non funziona! Semplicemente no, mentre tutto l'istinto dice che deve. grrr ...
apil.tamang

alla linea var bytechars = atob(base64)genera un errore JavaScript runtime error: InvalidCharacterError. Sto usando la versione 75.0.3770.142 di Chrome, ma non so cosa c'è che non va.
Muflix,

27

1. Agnostico del framework: file di download servlet come allegato

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

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

2. Struts2 Framework: azione che scarica il file come allegato

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

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

Sarebbe meglio usare il <s:a>tag che punta con OGNL a un URL creato con il <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>

Nei casi precedenti, è necessario scrivere l' intestazione Content-Disposition nella risposta , specificando che il file deve essere scaricato ( attachment) e non aperto dal browser ( inline). È necessario specificare il tipo di contenuto troppo, e si consiglia di aggiungere il nome del file e la lunghezza (per aiuto il browser disegnando un ProgressBar realistico).

Ad esempio, quando si scarica un 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...

Con Struts2 (a meno che tu non stia usando Action come Servlet, ad esempio un hack per lo streaming diretto ), non è necessario scrivere direttamente nulla nella risposta; semplicemente usando il tipo di risultato Stream e configurandolo in struts.xml funzionerà: ESEMPIO

<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 agnostico (/ Struts2 framework): file di apertura servlet (/ azione) all'interno del browser

Se si desidera aprire il file all'interno del browser, anziché scaricarlo, la disposizione del contenuto deve essere impostata su in linea , ma la destinazione non può essere la posizione corrente della finestra; devi scegliere come target una nuova finestra creata da JavaScript, una <iframe>nella pagina o una nuova finestra creata al volo con il "discusso" target = "_ vuoto":

<!-- 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
Signore, il suo contributo: "Content-Disposition", "inline; .... ha salvato la povera giornata del programmatore :)
Vedran Maricevic.

1
Questa è l'unica risposta che menziona "window.open" (uno dei commenti lo menziona).
Andrew Koster,

Non funziona se si hanno molti parametri, perché si otterrà un too long urlerrore.
Muflix,

25

Il modo più semplice per fare in modo che il browser scarichi un file è fare la richiesta in questo modo:

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

Questo apre il pop-up del download del browser.


3
Grazie, ho usato questa soluzione. Ha funzionato come un fascino. Inoltre, se non si ottiene un BLOB dalla risposta, è sufficiente creare un nuovo BLOB.
fabio.sang

6
Una versione migliore con collegamento di
inizia con

Il link da @startsWith_R aiuta davvero se stai lavorando con IE11
alexventuraio

Grazie ha funzionato per me!
Zaki Mohammed,

23

Ho creato poca funzione come soluzione alternativa (ispirata al plugin @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();
}

Demo con evento click:

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

Questo però invia i dati in un modo molto strano al server. Mi chiedo se potrebbe essere modificato per creare POST conformi?
Shayne,

16

Ho affrontato lo stesso problema e risolto con successo. Il mio caso d'uso è questo.

" Pubblica i dati JSON sul server e ricevi un file Excel. Quel file Excel viene creato dal server e restituito come risposta al client. Scarica quella risposta come file con nome personalizzato nel browser "

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

Lo snippet sopra sta semplicemente facendo ciò che segue

  • Pubblicazione di un array come JSON sul server mediante XMLHttpRequest.
  • Dopo aver recuperato il contenuto come BLOB (binario), stiamo creando un URL scaricabile e lo alleghiamo a un collegamento "a" invisibile, quindi facciamo clic su di esso. Ho fatto una richiesta POST qui. Invece, puoi anche fare un semplice GET. Non è possibile scaricare il file tramite Ajax, è necessario utilizzare XMLHttpRequest.

Qui dobbiamo impostare con attenzione alcune cose sul lato server. Ho impostato alcune intestazioni in Python Django HttpResponse. È necessario impostarli di conseguenza se si utilizzano altri linguaggi di programmazione.

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

Da quando ho scaricato xls (Excel) qui, ho modificato contentType su uno sopra. È necessario impostarlo in base al tipo di file. È possibile utilizzare questa tecnica per scaricare qualsiasi tipo di file.


"Non è possibile scaricare il file tramite Ajax, è necessario utilizzare XMLHttpRequest". XMLHttpRequest è AJAX per definizione. Altrimenti ottima soluzione per i moderni browser web. Per IE, che non supporta HTMLAnchorElement.download, sto pensando di combinarlo con il metodo proprietario msSaveOrOpenBlob .
Tsahi Asher,

15

Ok, basato sul codice di ndpu ecco una versione migliorata (credo) di 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();
}

Usa questo in questo modo; -

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

I parametri vengono inviati come parametri post appropriati come se provenissero da un input piuttosto che come una stringa codificata json come nell'esempio precedente.

CAVEAT: Diffidare del potenziale di iniezione variabile su quelle forme. Potrebbe esserci un modo più sicuro per codificare quelle variabili. In alternativa, contempla l'evasione.


Questo è un esempio funzionante. Grazie. È possibile farlo senza iframe ma senza window.location?
Marek Bar,

Suppongo che potresti semplicemente aggiungere il modulo nascosto in fondo al DOM. Probabilmente vale la pena esplorare anche l'uso di Shadow dom, sebbene non sia necessariamente ben supportato sui browser più vecchi.
Shayne,

In questo codice viene visualizzato questo errore. 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.
Annullato il

Come posso associare questo modulo ad una classe di modello? Ho: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) ma non funziona ..
bartex9,

void: sarebbe probabilmente una sorta di problema di sicurezza tra le origini. Questa è probabilmente un'intera domanda di overflow dello stack in sé e per sé. @ bartex9: Dipenderebbe molto dal tipo di framework che stai usando. Ma il principio sarebbe quello di prendere il nome e il percorso e archiviarlo, mentre si spinge il file stesso in un'area accessibile dal web del file system, o qualcosa come Amazon S3 per l'alta disponibilità
Shayne

8

Ecco cosa ho fatto, javascript puro e html. Non l'ho provato ma questo dovrebbe funzionare in tutti i browser.

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

Utilizzando solo componenti supportati in tutti i browser non sono necessarie librerie aggiuntive.

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Ecco il codice del controller JAVA Spring sul lato server.

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

sembra che il tuo evento load non sia chiamato per il contenuto dell'allegato Content-disposition (perché non viene caricato nulla nell'iframe), se funziona per te (ottieni console.log) per favore
invia

Ecco un violino rapido jsfiddle.net/y2xezyoj che attiva il carico dell'evento di caricamento non appena il file pdf viene caricato nell'iframe .. questo violino non viene scaricato perché la chiave per il download si trova sul lato server "response.setHeader (" Contenuto -disposition "," allegato ; nomefile = \ "" + nomefile + ".xlsx \" ");"
manukyanv07,

1
sì, funzionerà in quel caso, ma se il file viene scaricato, cioè il server invia Content-Disposition: allegato, quindi l'evento load non si attiverà quale era il mio punto
kofifus

Hai pienamente ragione caricamento evento viene generato subito dopo che il server ha terminato l'elaborazione inizia a inviare il file. Questo è quello che stavo cercando, 1- blocca il pulsante e mostra l'elaborazione in modo che l'utente possa avere un feedback sul fatto che le cose stanno accadendo. 2 - Quindi al termine dell'elaborazione del server e in procinto di inviare il file 3- (l'evento di caricamento viene attivato) in cui sblocco il pulsante e rimuovo lo spinner di elaborazione 4 - l'utente viene ora visualizzato con il file di salvataggio o il browser inizia a scaricarlo in il percorso di download definito. Scusa per il mio inglese.
manukyanv07,

5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

Potresti spiegare la tua risposta? Ciò aiuterebbe gli altri a capire cosa hai fatto in modo da poter applicare le tue tecniche alle loro situazioni.
Wai Ha Lee,

2
Solo un avvertimento: Safari e IE non supportano l' downloadattributo, quindi il tuo file finirà per avere il nome "Sconosciuto"
Yangshun Tay,

4

In Rails, lo faccio in questo modo:

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

Il trucco è la parte window.location . Il metodo del controller è simile a:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end

2
Domanda veloce, questo non genererà il file due volte? Una volta inviata la richiesta Ajax. Quindi fai reindirizzare la pagina allo stesso URL. Come possiamo eliminarlo?
programmatori

Non nel mio caso. L'ho provato solo su Chrome però.
aarkerio,

Come i programmatori affermano già correttamente, l'azione viene chiamata due volte.
Sven,

Viene chiamato due volte anche per me.
CSquared

4

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

Ad esempio, puoi inserire questa riga di codice in un gestore di clic:

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

Si aprirà una nuova scheda (a causa del nome della finestra "_blank") e quella scheda aprirà l'URL.

Il codice sul lato server dovrebbe anche avere qualcosa del genere:

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

E in questo modo, il browser dovrebbe richiedere all'utente di salvare il file su disco, invece di mostrargli semplicemente il file. Inoltre chiuderà automaticamente la scheda che ha appena aperto.


4

Provo a scaricare un file CSV e poi faccio qualcosa dopo che il download è finito. Quindi devo implementare una callbackfunzione appropriata .

L'uso window.location="..."non è una buona idea perché non riesco a far funzionare il programma dopo aver terminato il download. Qualcosa del genere, cambia intestazione in modo che non sia una buona idea.

fetchè una buona alternativa, tuttavia non può supportare IE 11 . E window.URL.createObjectURLnon può supportare IE 11. Puoi fare riferimento a questo .

Questo è il mio codice, è simile al codice di Shahrukh Alam. Ma dovresti fare attenzione che window.URL.createObjectURLforse crei perdite di memoria. Puoi fare riferimento a questo . Quando la risposta è arrivata, i dati verranno archiviati nella memoria del browser. Quindi, prima di fare clic sul acollegamento, il file è stato scaricato. Significa che puoi fare qualsiasi cosa dopo il download.

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

Come SCARICARE un file dopo averlo ricevuto da AJAX

È utile quando il file viene creato a lungo e devi mostrare PRELOADER

Esempio quando si invia un modulo Web:

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

Funzionalità facoltativa è commentata per semplificare l'esempio.

Non è necessario creare file temporanei sul server.

Su jQuery v2.2.4 OK. Si verificherà un errore nella versione precedente:

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

Per ottenere il nome del file da Content-Disposition, questa corrispondenza ha funzionato per me: filename.match(/filename=(.*)/)[1](senza virgolette doppie o punto interrogativo) - regex101.com/r/2AsD4y/2 . Tuttavia, la tua soluzione è stata l'unica soluzione che ha funzionato dopo aver cercato molto.
jstuardo,

3

Aggiungendo alcune altre cose alla risposta sopra per scaricare un file

Di seguito è riportato un codice sorgente Java che genera array di byte

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

Ora nel codice JavaScript utilizzando FileSaver.js, è possibile scaricare un file con il codice seguente

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

Quanto sopra scaricherà il file


2

Ok, ecco il codice di lavoro quando si utilizza MVC e si ottiene il file da un controller

supponiamo che tu abbia il tuo array di byte dichiarato e popolato, l'unica cosa che devi fare è usare la funzione File (usando System.Web.Mvc)

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

e poi, nello stesso controller, aggiungi quelle 2 funzioni

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

e poi sarai in grado di chiamare il tuo controller per scaricare e ottenere il callback "successo" o "fallimento"

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

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });

1

Ho trovato una soluzione che, sebbene non stia effettivamente utilizzando Ajax, ti consente di utilizzare una chiamata JavaScript per richiedere il download e quindi ottenere una richiamata quando il download inizia effettivamente. L'ho trovato utile se il collegamento esegue uno script lato server che richiede un po 'di tempo per comporre il file prima di inviarlo. così puoi avvisarli che è in fase di elaborazione, quindi quando finalmente invia il file rimuovi quella notifica di elaborazione. ed è per questo che volevo provare a caricare il file tramite Ajax per cominciare, in modo che potessi avere un evento accadere quando il file è richiesto e un altro quando inizia effettivamente il download.

js in prima pagina

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

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

quindi l'altro file:

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

Penso che ci sia un modo per leggere ottenere dati usando js, ​​quindi non sarebbe necessario php. ma non lo so di persona e il server che sto usando supporta php quindi questo funziona per me. ho pensato di condividerlo nel caso in cui aiuti qualcuno


0

Se si desidera utilizzare il download del file jQuery, tenere presente quanto segue per IE. Devi reimpostare la risposta o non verrà scaricata

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

La tua azione può implementare ServletResponseAware per accederegetServletResponse()


0

È certo che non è possibile farlo tramite la chiamata Ajax.

Tuttavia, esiste una soluzione alternativa.

Passaggi:

Se si utilizza form.submit () per il download del file, è possibile:

  1. Crea una chiamata Ajax dal client al server e archivia il flusso di file all'interno della sessione.
  2. Quando "esito positivo" viene restituito dal server, chiamare form.submit () per eseguire lo streaming del flusso di file archiviato nella sessione.

Questo è utile nel caso in cui si desideri decidere se il file deve essere scaricato o meno dopo aver effettuato form.submit (), ad esempio: può esserci un caso in cui su form.submit (), si verifica un'eccezione sul lato server e invece di crash, potrebbe essere necessario mostrare un messaggio personalizzato sul lato client, in tal caso questa implementazione potrebbe aiutare.


0

esiste un'altra soluzione per scaricare una pagina Web in Ajax. Ma mi riferisco a una pagina che deve prima essere elaborata e quindi scaricata.

Per prima cosa devi separare l'elaborazione della pagina dal download dei risultati.

1) Nella chiamata ajax vengono effettuati solo i calcoli della pagina.

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

       funzione (dati, stato) 
       {
            if (status == "successo") 
            {
                / * 2) Nella risposta viene scaricata la pagina che utilizza i calcoli precedenti. Ad esempio, questa può essere una pagina che stampa i risultati di una tabella calcolata nella chiamata ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Ad esempio: in CalculusPage.php

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

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

// Ad esempio: in DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELEZIONA * DA ExamplePage DOVE id =". $ ID;
    ...

    $ Nomefile = "Export_Data.xls";
    header ("Content-Type: application / vnd.ms-excel");
    header ("Content-Disposition: inline; nomefile = $ nomefile");

    ...

Spero che questa soluzione possa essere utile per molti, come è stata per me.


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

Le risposte solo al codice dovrebbero avere almeno una descrizione minima che spieghi come funziona il codice e perché risponde alla domanda.
Roberto Caboni,

0

Funziona così bene su qualsiasi browser (sto usando 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'));                 

        }

-1

Ho lottato con questo problema per molto tempo. Finalmente un'elegante biblioteca esterna suggerita qui mi ha aiutato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.