javascript - files - Descarga un archivo por jQuery.Ajax
jquery ajax download file (16)
Agregando algunas cosas más a la respuesta anterior para descargar un archivo
A continuación se muestra un código de primavera de Java que genera byte Array
@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);
}
Ahora en el código javascript que usa FileSaver.js, puede descargar un archivo con el siguiente código
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);
Lo anterior descargará el archivo.
Tengo una acción de Struts2 en el lado del servidor para la descarga de archivos.
<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>
Sin embargo cuando llamo a la acción usando el jQuery:
$.post(
"/download.action",{
para1:value1,
para2:value2
....
},function(data){
console.info(data);
}
);
en Firebug veo que los datos se recuperan con el flujo binario . Me pregunto cómo abrir la ventana de descarga de archivos con la que el usuario puede guardar el archivo localmente.
En Rails, lo hago de esta manera:
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));
}
});
}
El truco es la parte window.location . El método del controlador se ve así:
# GET /files/{:id}/download_file/
def download_file
send_file(@file.file,
:disposition => ''attachment'',
:url_based_filename => false)
end
Encontré una solución que, si bien en realidad no usa ajax, le permite usar una llamada de javascript para solicitar la descarga y luego obtener una devolución de llamada cuando la descarga realmente comienza. Encontré esto útil si el enlace ejecuta una secuencia de comandos del lado del servidor que tarda un poco en componer el archivo antes de enviarlo. para que pueda alertarles que está procesando, y luego, cuando finalmente envíe el archivo, elimine esa notificación de procesamiento. Es por eso que quería intentar cargar el archivo a través de ajax para poder tener un evento cuando se solicita el archivo y otro cuando realmente comienza a descargarse.
el js en la portada
function expdone()
{
document.getElementById(''exportdiv'').style.display=''none'';
}
function expgo()
{
document.getElementById(''exportdiv'').style.display=''block'';
document.getElementById(''exportif'').src=''test2.php?arguments=data'';
}
el 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>
luego el otro archivo:
<!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>
Creo que hay una manera de leer obtener datos usando js, por lo que no se necesitaría php. pero no lo sé de antemano y el servidor que estoy usando soporta php, así que esto funciona para mí. Pensé en compartirlo en caso de que ayude a alguien.
Es cierto que no puedes hacerlo a través de la llamada Ajax.
Sin embargo, hay una solución.
Pasos
Si está utilizando form.submit () para descargar el archivo, lo que puede hacer es:
- Cree una llamada ajax del cliente al servidor y almacene la secuencia de archivos dentro de la sesión.
- Una vez que el servidor devuelva el "éxito", llame a su formulario.submit () para simplemente transmitir la secuencia de archivos almacenada en la sesión.
Esto es útil en caso de que desee decidir si es necesario descargar o no el archivo después de hacer form.submit (), por ejemplo: puede haber un caso en el que en form.submit (), se produce una excepción en el lado del servidor y en su lugar Si falla, es posible que deba mostrar un mensaje personalizado en el lado del cliente, en caso de que esta implementación pueda ayudar.
Esto es lo que hice, javascript puro y html. No lo probé pero esto debería funcionar en todos los navegadores.
Función 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 componentes que son compatibles con todos los navegadores, no hay bibliotecas adicionales.
Aquí está el código de mi controlador JAVA Spring del lado del servidor.
@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();
}
}
}
He creado una pequeña función como solución alternativa (inspirada en el complemento @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 click evento:
$(''#someid'').on(''click'', function() {
ajax_download(''/download.action'', {''para1'': 1, ''para2'': 2}, ''dataname'');
});
La forma simple de hacer que el navegador descargue un archivo es hacer la solicitud de la siguiente manera:
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();
}
Esto abre la ventana emergente de descarga del navegador.
Me enfrenté al mismo problema y lo resolví con éxito. Mi caso de uso es este.
" Publique datos JSON en el servidor y reciba un archivo de Excel. El servidor crea ese archivo de Excel y se devuelve como respuesta al cliente. Descargue esa respuesta como un archivo con un nombre personalizado en el navegador "
$("#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));
});
El fragmento de arriba está haciendo lo siguiente.
- Publicar una matriz como JSON en el servidor utilizando XMLHttpRequest.
- Después de obtener el contenido como un blob (binario), estamos creando una URL descargable y adjuntándola al enlace "a" invisible, luego hacemos clic en él. Hice una solicitud POST aquí. En su lugar, puede ir por un simple GET también. No podemos descargar el archivo a través de Ajax, debemos usar XMLHttpRequest.
Aquí tenemos que establecer cuidadosamente algunas cosas en el lado del servidor. Puse algunos encabezados en Python Django HttpResponse. Debe configurarlos en consecuencia si utiliza otros lenguajes de programación.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Desde que descargué xls (excel) aquí, ajusté contentType a uno superior. Necesita configurarlo de acuerdo a su tipo de archivo. Puedes usar esta técnica para descargar cualquier tipo de archivos.
Ok, aquí está el código de trabajo cuando se usa MVC y está obteniendo su archivo de un controlador
Digamos que usted tiene que declarar y rellenar su matriz de bytes, lo único que necesita hacer es usar la función de Archivo (usando System.Web.Mvc)
byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");
Y luego, en el mismo controlador, agregue esas 2 funciones.
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);
}
y luego podrá llamar a su controlador para descargar y obtener la devolución de llamada "exitosa" o "fallida"
$.fileDownload(mvcUrl(''name of the controller''), {
httpMethod: ''POST'',
successCallback: function (url) {
//insert success code
},
failCallback: function (html, url) {
//insert fail code
}
});
Ok, basado en el código de ndpu aquí hay una versión mejorada (creo) de 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 esto de esta manera;
$(''#someid'').on(''click'', function() {
ajax_download(''/download.action'', {''para1'': 1, ''para2'': 2});
});
Los parámetros se envían como parámetros de publicación adecuados, como si procedieran de una entrada en lugar de una cadena codificada en json, como se muestra en el ejemplo anterior.
AVISO: tenga cuidado con el potencial de inyección variable en esas formas. Puede haber una forma más segura de codificar esas variables. Alternativamente contempla escapar de ellos.
Si desea utilizar la descarga de archivos jQuery, tenga en cuenta esto para IE. Necesita restablecer la respuesta o no se descargará
//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
Su acción puede implementar ServletResponseAware
para acceder a getServletResponse()
Nadie publicó esta solución de @ Pekka ... así que la publicaré. Puede ayudar a alguien.
No puedes y no necesitas hacer esto a través de Ajax. Solo usa
window.location="download.action?para1=value1...."
1. Framework agnostic: Servlet descargando archivo como adjunto
<!-- with JS -->
<a href="javascript:window.location=''downloadServlet?param1=value1''">
download
</a>
<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>
2. Struts2 Framework: Acción descargando archivo como adjunto
<!-- with JS -->
<a href="javascript:window.location=''downloadAction.action?param1=value1''">
download
</a>
<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>
Sería mejor usar <s:a>
etiqueta <s:a>
apunta con OGNL a una URL creada con <s:url>
etiqueta <s:url>
:
<!-- without JS, with Struts tags: THE RIGHT WAY -->
<s:url action="downloadAction.action" var="url">
<s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>
En los casos anteriores, debe escribir el encabezado de Disposición de contenido en la respuesta , especificando que el archivo debe descargarse ( attachment
) y no debe abrirse con el navegador (en inline
). También debe especificar el Tipo de contenido , y es posible que desee agregar el nombre y la longitud del archivo (para ayudar al navegador a dibujar una barra de progreso realista).
Por ejemplo, al descargar 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 menos que esté utilizando el Action como un Servlet, un hack para la transmisión directa , por ejemplo), no necesita escribir directamente nada en la respuesta; simplemente utilizando el tipo de resultado Stream y configurándolo en struts.xml funcionará: EXAMPLE
<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 agnostic (/ Struts2 framework): Servlet (/ Action) abriendo el archivo dentro del navegador
Si desea abrir el archivo dentro del navegador, en lugar de descargarlo, la disposición de Contenido debe configurarse en línea , pero el destino no puede ser la ubicación de la ventana actual; debe apuntar a una nueva ventana creada por javascript, un <iframe>
en la página, o una nueva ventana creada sobre la marcha con el objetivo "discutido" = "_ en blanco":
<!-- 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>
Puedes con HTML5
NB: los datos del archivo devueltos DEBEN estar codificados en base64 porque no puede codificar JSON datos binarios
En mi respuesta AJAX
tengo una estructura de datos que se ve así:
{
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)
}
}
Eso significa que puedo hacer lo siguiente para guardar un archivo a través de 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 función base64ToBlob se tomó de here y debe usarse de acuerdo con esta función
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});
};
Esto es bueno si su servidor está volcando los archivos para ser guardados. Sin embargo, no he descubierto cómo implementar un respaldo HTML4
Bluish tiene toda la razón al respecto, no puede hacerlo a través de Ajax porque JavaScript no puede guardar archivos directamente en la computadora de un usuario (por razones de seguridad). Desafortunadamente, apuntar la URL de la ventana principal a la descarga de su archivo significa que tiene poco control sobre la experiencia del usuario cuando se produce la descarga de un archivo.
Creé jQuery File Download que permite una experiencia "similar a Ajax" con descargas de archivos completas con las devoluciones de llamada de OnSuccess y OnFailure para proporcionar una mejor experiencia de usuario. Eche un vistazo a la publicación de mi blog sobre el problema común que resuelve el complemento y algunas formas de usarlo, así como una demostración de descarga de archivos jQuery en acción . Aquí está la source
Aquí hay una demostración de caso de uso simple que usa la source del complemento con promesas. La página de demostración incluye muchos otros ejemplos, ''UX mejor'' también.
$.fileDownload(''some/file.pdf'')
.done(function () { alert(''File download a success!''); })
.fail(function () { alert(''File download failed!''); });
Dependiendo de los navegadores que necesite admitir, puede usar https://github.com/eligrey/FileSaver.js/ que permite un control más explícito que el método IFRAME que utiliza la descarga de archivos jQuery.
function downloadURI(uri, name)
{
var link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
}