Descargar un archivo por jQuery.Ajax

420

Tengo una acción 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 jQuery:

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

En Firebug veo que los datos se recuperan con la secuencia binaria . Me pregunto cómo abrir la ventana de descarga de archivos con la que el usuario puede guardar el archivo localmente.

Hguser
fuente
1
Lo marqué como un duplicado a pesar de la diferencia de plataforma, porque hasta donde puedo ver, la solución es la misma (no puede y no necesita hacer esto a través de Ajax).
Pekka
1
entonces, sin ajax, simplemente use window.location = "download.action? para1 = value1 ...."?
hguser

Respuestas:

676

Actualización de navegadores modernos 2019

Este es el enfoque que ahora recomendaría con algunas advertencias:

  • Se requiere un navegador relativamente moderno
  • Si se espera que el archivo sea muy grande , probablemente debería hacer algo similar al enfoque original (iframe y cookie) porque algunas de las operaciones a continuación podrían consumir memoria del sistema al menos tan grande como el archivo que se descarga y / u otra CPU interesante efectos secundarios.

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

Enfoque basado en jQuery / iframe / cookie original de 2012

Bluish tiene toda la razón sobre esto, no puede hacerlo a través de Ajax porque JavaScript no puede guardar archivos directamente en la computadora de un usuario (por motivos de seguridad). Desafortunadamente, señalar la URL de la ventana principal en la descarga de su archivo significa que tiene poco control sobre la experiencia del usuario cuando se produce una descarga de archivo.

He creado jQuery Descarga de archivos que permite una "Ajax como" experiencia con las descargas de archivos completos con onSuccess y onFailure devoluciones de llamada para proporcionar una mejor experiencia de usuario. Echa un vistazo a mi blog sobre el problema común que resuelve el complemento y algunas formas de usarlo y también un demostración de jQuery File Download en acción . Aquí esta la fuente

Aquí hay una demostración de caso de uso simple usando la fuente del complemento con promesas. La página de demostración incluye muchos otros ejemplos de 'mejor UX'.

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

John Culviner
fuente
69
Me encanta lo que construiste, pero sospecho que para obtener más crédito StackOverFlow, tu respuesta aquí debería contener un poco más de detalle. Específicamente sobre cómo resolvió el problema.
AnthonyVO
14
Sería bueno que mencionaras exactamente cómo este "complemento" supera la limitación, en lugar de obligarnos a ir a la fuente de tu blog / complemento para verlo. por ejemplo, ¿se está publicando en un iframe? ¿requiere en su lugar el script remoto para guardar el archivo y devolverle una url?
Kevin B
2
@asgerhallas Claro, pero eso es completamente inútil si dicho enlace desaparece.
Kevin B
26
Estoy de acuerdo, un blog es un lugar mucho mejor para colocar una larga descripción de cómo usar su complemento y cómo funciona. pero al menos podría haber dado una breve descripción de cómo este complemento resuelve el problema. Por ejemplo, esto resuelve el problema haciendo que el servidor configure una cookie y que su javascript busque continuamente la cookie hasta que exista. Una vez que existe, podemos suponer que la descarga se ha completado. Con ese tipo de información, uno podría implementar su propia solución muy rápidamente, y la respuesta ya no se basa 100% en su blog / plugin / jquery y puede aplicarse a otras bibliotecas.
Kevin B
1
Royi, según tengo entendido, AJAX nunca puede soportar descargas de archivos que resulten en una ventana emergente de descarga de archivos para guardar en el disco. ¿Has encontrado una forma que desconozco?
John Culviner
228

Nadie publicó esta solución de @ Pekka ... así que la publicaré. Puede ayudar a alguien.

No necesita hacer esto a través de Ajax. Solo usa

window.location="download.action?para1=value1...."
azulado
fuente
44
Agradable ... ya que estaba luchando con el manejo del archivo de descarga y usando jquery ajax ... y esta solución funciona perfectamente para mí ... + 1
swapnesh
45
Tenga en cuenta que esto requiere que el servidor establezca un valor de encabezado de disposición de contenido de 'archivo adjunto'; de lo contrario, el navegador redirigirá (y mostrará) el contenido de la respuesta
brichins
21
O utilice alternativamente window.open(<url>, '_blank');para asegurarse de que la descarga no reemplace el contenido actual de su navegador (independientemente del encabezado Content-Disposition).
Christopher King
44
El problema con esta solución es que si la operación falla / el servidor devuelve un error, su página será redirigida a la página de error. Para resolver eso, use la solución iFrame
kofifus
44
El verdadero problema con esta solución: la pregunta es sobre la POSTsolicitud.
Atomosk
35

Puedes con HTML5

NB: los datos de archivo devueltos DEBEN estar codificados en base64 porque no puede codificar JSON datos binarios

En mi AJAXrespuesta, 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 aquí y debe usarse de conformidad 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 datos de archivos para guardar. Sin embargo, aún no he resuelto cómo se implementaría un respaldo HTML4

Luke Madhanga
fuente
1
El a.click()no parece funcionar en Firefox ... ¿Alguna idea?
bigpony
En algunos navegadores, es posible que deba agregar el adom para que este código funcione y / o eliminar la revokeObjectURLparte:document.body.appendChild(a)
bigpony
me salvó el día (y posiblemente un trabajo también :)) No soy un experto en javascript en ninguna medida ... más java guy. Sin embargo, no tengo idea de por qué un simple "createObjectURL (new Blob ([atob (base64)])))" no funciona. Simplemente no lo hace, mientras que todo instinto dice que debe hacerlo. grrr ...
apil.tamang
en la línea var bytechars = atob(base64)arroja un error JavaScript runtime error: InvalidCharacterError. Estoy usando Chrome Versión 75.0.3770.142 pero no sé, qué está mal aquí.
Muflix
27

1. Marco independiente: archivo de descarga de servlet como archivo 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 archivo 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 la <s:a>etiqueta apuntando con OGNL a una URL creada con la <s:url>etiqueta:

<!-- 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 Content-Disposition en la respuesta , especificando que el archivo debe ser descargado ( attachment) y no abierto por el navegador ( 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 la Acción como un Servlet, un truco para la transmisión directa , por ejemplo), no necesita escribir directamente nada en la respuesta; simplemente usando el tipo de resultado Stream y configurándolo en struts.xml funcionará: EJEMPLO

<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 agnóstico (/ 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 establecerse en línea , pero el destino no puede ser la ubicación actual de la ventana; debe orientar una nueva ventana creada por javascript, una <iframe>en la página o una nueva ventana creada sobre la marcha con el "discutido" target = "_ blank":

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>
Andrea Ligios
fuente
2
Señor, su opinión: "Content-Disposition", "en línea; .... salvó el día del pobre codificador :)
Vedran Maricevic.
1
Esta es la única respuesta que menciona "window.open" (uno de los comentarios lo menciona).
Andrew Koster,
No funciona si tiene muchos parámetros, porque obtendrá un too long urlerror.
Muflix
25

La manera simple de hacer que el navegador descargue un archivo es hacer la solicitud de esta 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.

João Marcos
fuente
3
Gracias, usé esta solución. Trabajado como un encanto. Además, si no obtiene un blob de la respuesta, simplemente cree un nuevo Blob.
fabio.sang
66
Una mejor versión con IE manejando enlace
comienza con_R
El enlace de @startsWith_R realmente ayuda si estás trabajando con IE11
alexventuraio
Gracias funcionó para mí!
Zaki Mohammed
23

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

Demostración con evento de clic:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
ndpu
fuente
Sin embargo, eso envía los datos de una manera muy extraña al servidor. Me pregunto si podría modificarse para crear POST compatible.
Shayne
16

Enfrenté el 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. Ese archivo de Excel es creado por el servidor y devuelto como respuesta al cliente. Descargue esa respuesta como un archivo con 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 anterior solo hace lo siguiente

  • Publicar una matriz como JSON en el servidor utilizando XMLHttpRequest.
  • Después de buscar contenido como un blob (binario), estamos creando una URL descargable y adjuntándola a un enlace invisible "a" y luego haciendo clic en ella. Hice una solicitud POST aquí. En cambio, también puedes optar por un GET simple. No podemos descargar el archivo a través de Ajax, debemos usar XMLHttpRequest.

Aquí necesitamos establecer cuidadosamente algunas cosas en el lado del servidor. Configuré algunos encabezados en Python Django HttpResponse. Debe configurarlos en consecuencia si usa otros lenguajes de programación.

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

Desde que descargo xls (excel) aquí, ajusté contentType al anterior. Debe configurarlo de acuerdo con su tipo de archivo. Puede usar esta técnica para descargar cualquier tipo de archivo.

Naren Yellavula
fuente
"No podemos descargar el archivo a través de Ajax, debemos usar XMLHttpRequest". XMLHttpRequest es AJAX por definición. De lo contrario, es una excelente solución para los navegadores web modernos. Para IE, que no es compatible HTMLAnchorElement.download, estoy pensando en combinarlo con el método patentado msSaveOrOpenBlob .
Tsahi Asher
15

Ok, basado en el código de ndpu heres 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();
}

Use esto así; -

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

Los parámetros se envían como parámetros posteriores apropiados como si vinieran de una entrada en lugar de una cadena codificada json como en el ejemplo anterior.

PRECAUCIÓN: 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.

Shayne
fuente
Este es un ejemplo de trabajo. Gracias. ¿Es posible hacerlo sin iframe pero sin window.location?
Marek Bar
Supongo que podría agregar el formulario oculto a la parte inferior del DOM. Posiblemente también valga la pena explorar el uso de Shadow dom, aunque eso no es necesariamente compatible con navegadores antiguos.
Shayne
En este código obtengo este error. 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.
nulo
¿Cómo puedo asignar este formulario a alguna clase de modelo? Tengo: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) pero no funciona ..
bartex9
nulo: Eso probablemente sería algún tipo de problema de seguridad de origen cruzado. Esa es probablemente una pregunta de desbordamiento de pila completa en sí misma. @ bartex9: Eso dependería en gran medida del tipo de marco que esté utilizando. Pero el principio sería tomar el nombre y la ruta y almacenarlo, mientras empuja el archivo en un área accesible del sitio del sistema de archivos, o algo así como Amazon S3 para alta disponibilidad
Shayne
8

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

Al usar solo componentes que son compatibles con todos los navegadores, no se necesitan bibliotecas adicionales.

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Aquí está mi código de 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();
        }
    }
    }
manukyanv07
fuente
parece que su evento de carga no se llama para contenido adjunto de disposición de contenido (porque no se carga nada en el iframe), si funciona para usted (obtiene el console.log) por favor publique una muestra
kofifus
Aquí hay un violín rápido jsfiddle.net/y2xezyoj que activa el evento de carga tan pronto como el archivo pdf se carga en el iframe ... este violín no se descarga porque la clave para la descarga está en el lado del servidor "response.setHeader (" Contenido -disposición "," archivo adjunto ; nombre de archivo = \ "" + nombre de archivo + ".xlsx \" ");"
manukyanv07
1
sí, funcionará en ese caso, pero si el archivo se descarga, es decir, el servidor envía Content-Disposition: adjunto, entonces el evento de carga no se activará, lo que fue mi punto
kofifus
Tiene toda la razón, el evento de carga se dispara justo después de que el servidor haya terminado de procesar y comience a enviar el archivo. Esto es lo que estaba buscando, 1- bloquear el botón y mostrar el procesamiento para que el usuario pueda tener una retroalimentación de que las cosas están sucediendo. 2 - Luego, cuando el servidor termina de procesar y está a punto de enviar el archivo 3- (se dispara el evento de carga) donde desbloqueo el botón y elimino el spinner de procesamiento 4 - el usuario ahora aparece con el archivo guardado o el navegador comienza a descargarlo La ubicación de descarga definida. Perdon por mi inglés.
manukyanv07
5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
EL missaoui habib
fuente
¿Podrías explicar tu respuesta? Eso ayudaría a otros a comprender lo que has hecho para que puedan aplicar tus técnicas a sus situaciones.
Wai Ha Lee
2
Solo una advertencia: Safari e IE no admiten el downloadatributo, por lo que su archivo terminará teniendo el nombre "Desconocido"
Yangshun Tay
4

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
aarkerio
fuente
2
Pregunta rápida, ¿esto no generará el archivo dos veces? Una vez que envíe la solicitud ajax. Luego, hace que la página redirija a la misma URL también. ¿Cómo podemos eliminar eso?
coderhs
No en mi caso Sin embargo, solo lo probé en Chrome.
aarkerio
Como los codificadores ya dicen correctamente, la acción se llama dos veces.
Sven
También me llaman dos veces.
CSquared
4

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

Por ejemplo, puede poner esta línea de código en un controlador de clics:

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

Se abrirá una nueva pestaña (debido al nombre de la ventana '_blank') y esa pestaña abrirá la URL.

Su código del lado del servidor también debería tener algo como esto:

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

Y de esa manera, el navegador debe solicitar al usuario que guarde el archivo en el disco, en lugar de solo mostrarle el archivo. También cerrará automáticamente la pestaña que acaba de abrir.

Andrew Koster
fuente
4

Intento descargar un archivo CSV y luego hacer algo después de que la descarga haya finalizado. Entonces necesito implementar una callbackfunción apropiada .

Usar window.location="..."no es una buena idea porque no puedo operar el programa después de terminar la descarga. Algo como esto, cambia el encabezado para que no sea una buena idea.

fetches una buena alternativa, sin embargo, no puede soportar IE 11 . Y window.URL.createObjectURLno puede soportar IE 11. Puedes referir esto .

Este es mi código, es similar al código de Shahrukh Alam. Pero debe tener cuidado de que window.URL.createObjectURLtal vez cree pérdidas de memoria. Puedes referir esto . Cuando llegue la respuesta, los datos se almacenarán en la memoria del navegador. Entonces, antes de hacer clic en el aenlace, el archivo se ha descargado. Significa que puede hacer cualquier cosa después de la descarga.

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

Cómo DESCARGAR un archivo después de recibirlo por AJAX

Es conveniente cuando el archivo se crea durante mucho tiempo y necesita mostrar PRELOADER

Ejemplo al enviar un formulario 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>

Opcional funcional se comenta para simplificar el ejemplo.

No es necesario crear archivos temporales en el servidor.

En jQuery v2.2.4 OK. Habrá un error en la versión anterior:

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
fuente
Para obtener el nombre del archivo de Content-Disposition, esta coincidencia funcionó para mí: filename.match(/filename=(.*)/)[1](sin las comillas dobles o el signo de interrogación) - regex101.com/r/2AsD4y/2 . Sin embargo, su solución fue la única solución que funcionó después de buscar mucho.
jstuardo
3

Agregar algunas cosas más a la respuesta anterior para descargar un archivo

A continuación se muestra un código java spring que genera una matriz de bytes

@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 código javascript usando 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

dario nascimento
fuente
2

Ok, aquí está el código de trabajo al usar MVC y estás obteniendo tu archivo de un controlador

supongamos que tiene su conjunto de bytes declarar y llenar, lo único que debe 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 estas 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 de "éxito" o "falla"

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

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

Encontré una solución que, si bien en realidad no está usando ajax, le permite usar una llamada de JavaScript para solicitar la descarga y luego recibir una devolución de llamada cuando la descarga realmente comienza. Esto me pareció útil si el enlace ejecuta un script del lado del servidor que toma un poco de tiempo para componer el archivo antes de enviarlo. para que pueda alertarlos de que se está procesando, y luego, cuando finalmente envíe el archivo, elimine esa notificación de procesamiento. Por eso, para empezar, 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>

entonces 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 los datos usando js para que no se necesite php. pero no lo sé de la mano y el servidor que estoy usando es compatible con php, así que esto funciona para mí. pensé en compartirlo en caso de que ayude a alguien.

Kit Ramos
fuente
0

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 accedergetServletResponse()

Alireza Fattahi
fuente
0

Es cierto que no puede 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:

  1. Cree una llamada ajax del cliente al servidor y almacene la secuencia de archivos dentro de la sesión.
  2. Una vez que se devuelva el "éxito" del servidor, llame a su form.submit () para simplemente transmitir la secuencia del archivo almacenado en la sesión.

Esto es útil en caso de que desee decidir si el archivo debe descargarse o no después de crear form.submit (), por ejemplo: puede haber un caso en el que en form.submit (), se produzca una excepción en el lado del servidor y, en su lugar de bloqueo, es posible que deba mostrar un mensaje personalizado en el lado del cliente, en tal caso, esta implementación podría ayudar.

Aman Srivastava
fuente
0

Hay otra solución para descargar una página web en ajax. Pero me refiero a una página que primero debe procesarse y luego descargarse.

Primero debe separar el procesamiento de la página de la descarga de resultados.

1) Solo los cálculos de la página se realizan en la llamada ajax.

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

       función (datos, estado) 
       {
            if (estado == "éxito") 
            {
                / * 2) En la respuesta se descarga la página que usa los cálculos anteriores. Por ejemplo, esta puede ser una página que imprime los resultados de una tabla calculada en la llamada ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Por ejemplo: en CalculusPage.php

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

        $ query = "INSERTAR EN LA PÁGINA DE EJEMPLO (data1, data2) VALORES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') DONDE id =". $ ID;
        ...
    }

// Por ejemplo: en el DownloadPage.php

    $ ID = $ _GET ["ID"];

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

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

    ...

Espero que esta solución pueda ser útil para muchos, como lo fue para mí.

netluke
fuente
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
fuente
Las respuestas de solo código deben tener al menos una descripción mínima que explique cómo funciona el código y por qué responde a la pregunta.
Roberto Caboni
0

Eso funciona muy bien en cualquier navegador (estoy 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'));                 

        }
Marinpietri
fuente
-1

Luché con este problema durante mucho tiempo. Finalmente, una elegante biblioteca externa sugerida aquí me ayudó.

Eerik Sven Puudist
fuente