Scarica un file da jQuery.Ajax

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

  text/plain imageStream attachment;filename={fileName} 1024   

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 stream binario . Mi chiedo come aprire la finestra di download dei file con la quale l’utente può salvare il file localmente?

Bluish ha perfettamente ragione su questo, non puoi 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 al download del tuo file significa che hai poco controllo su quale sia l’esperienza dell’utente quando si verifica il download di un 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 blog post sul problema comune che il plugin risolve e alcuni modi per usarlo e anche una demo di Download di file jQuery in azione . Ecco la fonte

Ecco una demo del caso d’uso semplice che usa la fonte del plugin con promesse. La pagina demo include anche molti altri esempi di ‘migliori UX’.

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

A seconda di quali browser è necessario supportare, è ansible utilizzare https://github.com/eligrey/FileSaver.js/ che consente un controllo più esplicito rispetto al metodo IFRAME utilizzato da jQuery File Download.

Nessuno ha postato questa soluzione @ Pekka … quindi la pubblicherò. Può aiutare qualcuno.

Non puoi e non devi farlo attraverso Ajax. Basta usare

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

Puoi con HTML5

NB: i dati del file restituiti DEVONO essere codificati in base 64 perché non è ansible codificare i dati binari con JSON

Nella mia risposta AJAX ho una struttura dati simile 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}); }; 

Ciò è positivo se il server sta eseguendo il dumping del file da salvare. Tuttavia, non ho ancora capito come si possa implementare un fallback HTML4

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

   download   download 

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

   download   download 

Sarebbe meglio usare il punta con OGNL a un URL creato con il :

   value1  download 

Nei casi precedenti, è necessario scrivere l’intestazione Content-Disposition nella risposta , specificando che il file deve essere scaricato ( attachment ) e non aperto dal browser (in inline ). È necessario specificare anche il tipo di contenuto e si potrebbe desiderare di aggiungere il nome e la lunghezza del file (per aiutare il browser a disegnare una barra di avanzamento realistica).

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 l’azione come servlet, un hack per lo streaming diretto , per esempio), non devi scrivere nulla direttamente alla risposta; semplicemente usando il tipo di risultato Stream e configurandolo in struts.xml funzionerà: ESEMPIO

  application/zip attachment;filename="${fileName}" ${fileLength}  

3. Framework agnostic (/ Struts2 framework): file di apertura Servlet (/ Action) all’interno del browser

Se si desidera aprire il file all’interno del browser, invece di scaricarlo, la disposizione del contenuto deve essere impostata su inline , ma la destinazione non può essere la posizione corrente della finestra; devi scegliere come target una nuova finestra creata da javascript, un nella pagina o una nuova finestra creata al volo con il target “discussa” = “_ blank”:

   download    download    download  

Ho creato una piccola 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 = $("" ).appendTo("body"); } iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument; if (iframe_doc.document) { iframe_doc = iframe_doc.document; } iframe_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'); }); 

Ok, basato sul codice di ndpu, è 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 = $("" ).appendTo("body"); } iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument; if (iframe_doc.document) { iframe_doc = iframe_doc.document; } iframe_html = "
" Object.keys(data).forEach(function(key){ iframe_html += ""; }); iframe_html +="
"; iframe_doc.open(); iframe_doc.write(iframe_html); $(iframe_doc).find('form').submit(); }

Usa questo come questo;

 $('#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 anziché da una stringa codificata JSON come nell’esempio precedente.

CAVEAT: Siate cauti sul potenziale di iniezione variabile su quelle forms. Potrebbe esserci un modo più sicuro per codificare tali variabili. In alternativa, contempla di sfuggirle.

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

Posta i dati JSON sul server e ricevi un file excel. Il file excel viene creato dal server e restituito come risposta al client. Scarica tale 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 riportato sta solo seguendo

  • Pubblicare un array come JSON sul server usando XMLHttpRequest.
  • Dopo aver recuperato il contenuto come un blob (binario), stiamo creando un URL scaricabile e collegandolo al link “a” invisibile, quindi facendo clic su di esso. Ho fatto una richiesta POST qui. Invece, puoi anche fare un semplice GET. Non è ansible scaricare il file tramite Ajax, è necessario utilizzare XMLHttpRequest.

Qui abbiamo bisogno di impostare con cura alcune cose sul lato server. Ho impostato poche 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 regolato contentType su sopra uno. È necessario impostarlo in base al tipo di file. È ansible utilizzare questa tecnica per scaricare qualsiasi tipo di file.

Il modo semplice per fare in modo che il browser scarichi un file è di 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 popup di download del browser.

Ecco cosa ho fatto, puro javascript 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"); }); 

Usando solo componenti supportati in tutti i browser non ci sono librerie aggiuntive.

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

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

Aggiungere altre cose alla risposta sopra per scaricare un file

Di seguito è riportato un codice java spring che genera array di byte

 @RequestMapping(value = "/downloadReport", method = { RequestMethod.POST }) public ResponseEntity 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(bos.toByteArray(), respHeaders, HttpStatus.CREATED); } 

Ora in codice javascript utilizzando FileSaver.js, è ansible scaricare un file con il seguente codice

 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

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

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 

Ok, ecco il codice funzionante quando usi MVC e stai ricevendo il tuo file da un controller

diciamo che hai il tuo array di byte dichiarare e compilare, 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 quindi, nello stesso controller, aggiungi le 2 funzioni

 protected override void OnResultExecuting(ResultExecutingContext context) { CheckAndHandleFileResult(context); base.OnResultExecuting(context); } private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload"; ///  /// 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 ///  ///  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 quindi sarai in grado di chiamare il controller per scaricare e ottenere il callback “success” o “failure”

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

Se si desidera utilizzare il download di file jQuery, si prega di notare questo per IE. È necessario ripristinare la risposta o non verrà scaricato

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

L’azione può implementare ServletResponseAware per accedere a getServletResponse()

Ho trovato una soluzione che, anche se non utilizza effettivamente ajax, ti consente di utilizzare una chiamata javascript per richiedere il download e ottenere una richiamata all’avvio effettivo del download. Ho trovato utile questo se il collegamento esegue uno script lato server che richiede un po ‘di tempo per comporre il file prima di inviarlo. in modo da poter avvisare che sta elaborando, e quindi quando finalmente invia il file rimuove la notifica di elaborazione. è per questo che ho voluto provare a caricare il file tramite ajax per iniziare in modo che potessi avere un evento che si verifica quando il file viene richiesto e un altro quando inizia effettivamente il download.

la 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

  

poi l’altro file:

          

Penso che ci sia un modo per leggere ottenere i dati usando js, ​​quindi non sarebbe necessario alcun php. ma non lo so fuori mano e il server che sto usando supporta PHP così questo funziona per me. pensavo di condividerlo nel caso in cui aiutasse qualcuno.

È certo che non puoi farlo tramite la chiamata Ajax.

Tuttavia, c’è una soluzione alternativa.

Passi:

Se stai utilizzando form.submit () per scaricare il file, ciò che puoi fare è:

  1. Creare una chiamata Ajax da client a server e memorizzare il stream di file all’interno della sessione.
  2. Quando “successo” viene restituito dal server, chiama il tuo form.submit () per eseguire lo streaming del stream di file memorizzato nella sessione.

Questo è utile nel caso in cui si voglia decidere se il file deve essere scaricato dopo aver fatto form.submit (), ad esempio: può esserci un caso in cui su form.submit (), si verifica un’eccezione sul lato server e di arresto anomalo, potrebbe essere necessario mostrare un messaggio personalizzato sul lato client, in tal caso questa implementazione potrebbe essere di aiuto.