Envío de multiparte / formdata con jQuery.ajax

Tengo un problema al enviar un archivo a un script PHP del lado del servidor usando la función ajax de jQuery. Es posible obtener la lista de archivos con $('#fileinput').attr('files') pero ¿cómo es posible enviar estos datos al servidor? La matriz resultante ($_POST) en el php-script del lado del servidor es 0 (NULL) al utilizar la entrada de archivo.

Sé que es posible (aunque no encontré ninguna solución jQuery hasta ahora, solo código Prototye (http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html)).

Esto parece ser relativamente nuevo, así que no menciones que la carga de archivos sería imposible a través de XHR / Ajax, porque definitivamente está funcionando.

Necesito la funcionalidad en Safari 5, FF y Chrome sería bueno pero no son esenciales.

Mi código por ahora es:

$.ajax({
    url: 'php/upload.php',
    data: $('#file').attr('files'),
    cache: false,
    contentType: 'multipart/form-data',
    processData: false,
    type: 'POST',
    success: function(data){
        alert(data);
    }
});

preguntado el 22 de marzo de 11 a las 10:03

Lamentablemente, el uso del objeto FormData no funciona en IE <10. -

@GarciaWebDev supuestamente puede usar un polyfill con Flash para admitir la misma API. Verificar github.com/Modernizr/Modernizr/wiki/… para más información. -

Puede usar el $(':file') para seleccionar todos los archivos de entrada. Es un poco más simple. -

@RameshwarVyevhare Esa respuesta se publicó cinco años después de que se respondiera esta pregunta. Por favor, no haga preguntas similares solo para promover sus propias respuestas. -

13 Respuestas

Comenzando con Safari 5 / Firefox 4, es más fácil usar el FormData clase:

var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file-'+i, file);
});

Entonces ahora tienes un FormData objeto, listo para ser enviado junto con XMLHttpRequest.

jQuery.ajax({
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
});

Es imperativo que establezca el contentType opción de false, lo que obliga a jQuery a no agregar un Content-Type encabezado para usted, de lo contrario, faltará la cadena de límite. Además, debe dejar el processData bandera establecida en falso, de lo contrario, jQuery intentará convertir su FormData en una cadena, que fallará.

Ahora puede recuperar el archivo en PHP usando:

$_FILES['file-0']

(Solo hay un archivo, file-0, a menos que haya especificado el multiple atributo en su entrada de archivo, en cuyo caso, los números se incrementarán con cada archivo).

Usando el patrón de velas del Emulación FormData para navegadores antiguos

var opts = {
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
};
if(data.fake) {
    // Make sure no text encoding stuff is done by xhr
    opts.xhr = function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }
    opts.contentType = "multipart/form-data; boundary="+data.boundary;
    opts.data = data.toString();
}
jQuery.ajax(opts);

Crear FormData a partir de un formulario existente

En lugar de iterar manualmente los archivos, el objeto FormData también se puede crear con el contenido de un objeto de formulario existente:

var data = new FormData(jQuery('form')[0]);

Use una matriz nativa de PHP en lugar de un contador

Simplemente asigne el mismo nombre a los elementos de su archivo y termine el nombre entre paréntesis:

jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file[]', file);
});

$_FILES['file'] entonces será una matriz que contiene los campos de carga de archivos para cada archivo cargado. De hecho, recomiendo esto sobre mi solución inicial, ya que es más fácil de repetir.

Respondido 05 Oct 17, 21:10

Además, hay un Emulación FormData lo que hará que la migración de esta solución a navegadores antiguos sea bastante sencilla. Todo lo que tienes que hacer es comprobar data.fake y establecer el contentType propiedad manualmente, así como anular xhr para usar sendAsBinary(). - Rafael Schweikert

¿Podría dar más detalles sobre "no pareció funcionar"? ¿Qué has probado exactamente? La FormData emulación o la clase nativa? Al usar la emulación, el límite está en data.boundary así que agregarías xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }, a las opciones y uso "multipart/form-data; boundary="+data.boundary para contentType opción. - Rafael Schweikert

Esto usa application/x-www-form-urlencoded. ¿Hay alguna forma de usar multipart/form-data ¿en lugar de? - timmmm

@Timmmm No, usa multipart/form-data. Utilizando application/x-www-form-urlencoded no funcionaría. - Rafael Schweikert

@RoyiNamir Solo está documentado en codigo, Me temo que. - Rafael Schweikert

Mira mi código, hace el trabajo por mí.

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );

respondido 04 nov., 14:05

Esto funcionó perfectamente y es muy sencillo de implementar. - Jh62

Este script hace lo que necesito para enviar un formulario pequeño y está funcionando con una excepción ... está usando la URL de la acción de otro formulario en la misma página. Tengo la URL correcta en el script y ninguna URL en el formulario asociado. ¿Por qué haría este cambio y cómo puedo forzarlo a usar el correcto? - JAC

esto funcionó perfectamente bien si pones e.preventDefault(); antes de ajax. Gracias - Tayyab Hayat

Solo quería agregar un poco a la gran respuesta de Raphael. Aquí se explica cómo hacer que PHP produzca lo mismo $_FILES, independientemente de si utiliza JavaScript para enviar.

Formulario HTML:

<form enctype="multipart/form-data" action="/es/test.php" 
method="post" class="putImages">
   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />
</form>

PHP produce esto $_FILES, cuando se envía sin JavaScript:

Array
(
    [media] => Array
        (
            [name] => Array
                (
                    [0] => Galata_Tower.jpg
                    [1] => 518f.jpg
                )

            [type] => Array
                (
                    [0] => image/jpeg
                    [1] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/phpIQaOYo
                    [1] => /tmp/phpJQaOYo
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 258004
                    [1] => 127884
                )

        )

)

Si realiza una mejora progresiva, utilizando el JS de Raphael para enviar los archivos ...

var data = new FormData($('input[name^="media"]'));     
jQuery.each($('input[name^="media"]')[0].files, function(i, file) {
    data.append(i, file);
});

$.ajax({
    type: ppiFormMethod,
    data: data,
    url: ppiFormActionURL,
    cache: false,
    contentType: false,
    processData: false,
    success: function(data){
        alert(data);
    }
});

... esto es lo que PHP $_FILES La matriz se ve así, después de usar ese JavaScript para enviar:

Array
(
    [0] => Array
        (
            [name] => Galata_Tower.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpAQaOYo
            [error] => 0
            [size] => 258004
        )

    [1] => Array
        (
            [name] => 518f.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpBQaOYo
            [error] => 0
            [size] => 127884
        )

)

Esa es una buena matriz, y en realidad lo que algunas personas transforman $_FILES en, pero me parece útil trabajar con el mismo $_FILES, independientemente de si se utilizó JavaScript para enviar. Entonces, aquí hay algunos cambios menores en JS:

// match anything not a [ or ]
regexp = /^[^[\]]+/;
var fileInput = $('.putImages input[type="file"]');
var fileInputName = regexp.exec( fileInput.attr('name') );

// make files available
var data = new FormData();
jQuery.each($(fileInput)[0].files, function(i, file) {
    data.append(fileInputName+'['+i+']', file);
});

(Edición del 14 de abril de 2017: eliminé el elemento de formulario del constructor de FormData (), que solucionó este código en Safari).

Ese código hace dos cosas.

  1. Recupera el input nombre atributo automáticamente, haciendo el HTML más fácil de mantener. Ahora, mientras form tiene la clase putImages, todo lo demás se gestiona automáticamente. Eso es el input no es necesario que tenga un nombre especial.
  2. El formato de matriz que envía HTML normal es recreado por JavaScript en la línea data.append. Tenga en cuenta los corchetes.

Con estos cambios, enviar con JavaScript ahora produce exactamente lo mismo $_FILES matriz como envío con HTML simple.

Respondido 14 Abr '17, 18:04

Tuve el mismo problema con Safari. ¡Gracias por la pista! - hacer cosas

Acabo de construir esta función basándome en alguna información que leí.

Úselo como usar .serialize(), en su lugar solo pon .serializefiles();.
Trabajando aquí en mis pruebas.

//USAGE: $("#form").serializefiles();
(function($) {
$.fn.serializefiles = function() {
    var obj = $(this);
    /* ADD FILE TO PARAM AJAX */
    var formData = new FormData();
    $.each($(obj).find("input[type='file']"), function(i, tag) {
        $.each($(tag)[0].files, function(i, file) {
            formData.append(tag.name, file);
        });
    });
    var params = $(obj).serializeArray();
    $.each(params, function (i, val) {
        formData.append(val.name, val.value);
    });
    return formData;
};
})(jQuery);

Respondido 10 Oct 18, 18:10

Estaba tratando de que esto funcionara, pero parecía no reconocer serializefiles () como una función, a pesar de que esta definición estaba en la parte superior de la página. - Caído

eso funciona para mí muy bien. obteniendo datos con var data = $("#avatar-form").serializefiles(); enviando esto a través del parámetro de datos ajax y analizando con expreso formidable: form.parse(req, function(err, fields, files){ gracias por ese fragmento de código :) - SchurigH

Si su formulario está definido en su HTML, es más fácil pasar el formulario al constructor que iterar y agregar imágenes.

$('#my-form').submit( function(e) {
    e.preventDefault();

    var data = new FormData(this); // <-- 'this' is your form element

    $.ajax({
            url: '/my_URL/',
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',     
            success: function(data){
            ...

Respondido el 02 de junio de 14 a las 18:06

La respuesta de Devin Venable estaba cerca de lo que quería, pero quería uno que funcionara en varios formularios y usara la acción ya especificada en el formulario para que cada archivo vaya al lugar correcto.

También quería usar el método on () de jQuery para evitar usar .ready ().

Eso me llevó a esto: (reemplace formSelector con su selector jQuery)

$(document).on('submit', formSelecter, function( e ) {
        e.preventDefault();
    $.ajax( {
        url: $(this).attr('action'),
        type: 'POST',
        data: new FormData( this ),
        processData: false,
        contentType: false
    }).done(function( data ) {
        //do stuff with the data you got back.
    });

});

contestado el 23 de mayo de 17 a las 13:05

Si la entrada del archivo name indica una matriz y banderas multipley analiza todo el form con FormData, no es necesario iterativamente append() los archivos de entrada. FormData manejará automáticamente varios archivos.

$('#submit_1').on('click', function() {
  let data = new FormData($("#my_form")[0]);

  $.ajax({
    url: '/path/to/php_file',
    type: 'POST',
    data: data,
    processData: false,
    contentType: false,
    success: function(r) {
      console.log('success', r);
    },
    error: function(r) {
      console.log('error', r);
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="my_form">
  <input type="file" name="multi_img_file[]" id="multi_img_file" accept=".gif,.jpg,.jpeg,.png,.svg" multiple="multiple" />
  <button type="button" name="submit_1" id="submit_1">Not type='submit'</button>
</form>

Tenga en cuenta que un button type="button" se usa, no type="submit". Esto muestra que no hay dependencia en el uso submit para obtener esta funcionalidad.

La resultante $_FILES La entrada es así en las herramientas de desarrollo de Chrome:

multi_img_file:
  error: (2) [0, 0]
  name: (2) ["pic1.jpg", "pic2.jpg"]
  size: (2) [1978036, 2446180]
  tmp_name: (2) ["/tmp/phphnrdPz", "/tmp/phpBrGSZN"]
  type: (2) ["image/jpeg", "image/jpeg"]

Nota: Hay casos en los que algunas imágenes se cargarán bien cuando se carguen como un solo archivo, pero fallarán cuando se carguen en un conjunto de varios archivos. El síntoma es que PHP informa vacío $_POST y $_FILES sin que AJAX arroje ningún error. El problema ocurre con Chrome 75.0.3770.100 y PHP 7.0. Solo parece suceder con 1 de varias docenas de imágenes en mi conjunto de prueba.

Respondido 01 Jul 19, 21:07

Hoy en día ni siquiera necesitas jQuery :) buscar tabla de soporte de API

let result = fetch('url', {method: 'POST', body: new FormData(document.querySelector("#form"))})

Respondido 19 Jul 19, 19:07

En el contexto de la carga de archivos a través de fetch por favor vea compatibilidad: desarrollador.mozilla.org/en-US/docs/Web/API/… - Omar Tarik

@OmarTariq sí, tengo un enlace similar en mi respuesta)) - alex nikulin

UPS. ¿Cómo puedo extrañar eso? :-| - Omar Tarik

La clase FormData funciona, sin embargo, en iOS Safari (al menos en el iPhone) no pude usar la solución de Raphael Schweikert tal como está.

Mozilla Dev tiene una bonita página sobre la manipulación de objetos FormData.

Entonces, agregue un formulario vacío en algún lugar de su página, especificando el enctype:

<form enctype="multipart/form-data" method="post" name="fileinfo" id="fileinfo"></form>

Luego, cree el objeto FormData como:

var data = new FormData($("#fileinfo"));

y proceda como en El código de Rafael.

contestado el 23 de mayo de 17 a las 13:05

Tuve un problema con mis cargas de jquery ajax colgando silenciosamente en Safari y terminé haciendo un $ ('form-name'). Submit () condicional del navegador para Safari en lugar de la carga ajax que funciona en IE9 y FF18. Probablemente no sea una solución ideal para cargas múltiples, pero estaba haciendo esto para un solo archivo en un iframe desde un cuadro de diálogo de jquery, por lo que funcionó bien. - 111

Creo que vale la pena señalar un problema con el que me encontré hoy en relación con este problema: si se redirige la URL de la llamada ajax, entonces se puede perder el encabezado del tipo de contenido: 'multipart / form-data'.

Por ejemplo, estaba publicando en http://server.com/context?param=x

En la pestaña de red de Chrome vi el encabezado de varias partes correcto para esta solicitud, pero luego un redireccionamiento 302 a http://server.com/context/?param=x (observe la barra inclinada después del contexto)

Durante la redirección, se perdió el encabezado de varias partes. Asegúrese de que las solicitudes no se redirijan si estas soluciones no le funcionan.

respondido 17 mar '16, 22:03

Las versiones anteriores de IE no son compatibles con FormData (la lista completa de compatibilidad con el navegador para FormData está aquí: https://developer.mozilla.org/en-US/docs/Web/API/FormData).

O puede usar un complemento jquery (por ejemplo, http://malsup.com/jquery/form/#code-samples ) o puede usar una solución basada en IFrame para publicar datos de formularios de varias partes a través de ajax: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

Respondido 08 Feb 18, 10:02

Todas las soluciones anteriores se ven bien y son elegantes, pero el objeto FormData () no espera ningún parámetro, pero use append () después de crear una instancia, como lo que se escribió anteriormente:

formData.append (val.name, val.value);

respondido 08 mar '18, 13:03

  1. obtener el objeto de formulario por jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. ok, los datos son lo que quieres

Respondido 18 Jul 13, 11:07

$ ("# id") [0] devuelve primero ninguno vacío del formulario, ¿cómo se envía el formulario completo, incluidos todos de eso? - Mohammad-Hossein Jamali

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.