Envío de multipart / formdata con jQuery.ajax

563

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, $('#fileinput').attr('files')pero ¿cómo es posible enviar estos Datos al servidor? La matriz resultante ( $_POST) en el script php del lado del servidor es 0 ( NULL) cuando se usa la entrada de archivo.

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

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

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);
    }
});
zoku
fuente
Lamentablemente, usar el objeto FormData no funciona en IE <10.
Alejandro García Iglesias
@GarciaWebDev supuestamente puede usar un polyfill con Flash para admitir la misma API. Visite github.com/Modernizr/Modernizr/wiki/… para obtener más información.
yuxhuang
Posible duplicado .
Raphael Schweikert
3
Puede usar $(':file')para seleccionar todos los archivos de entrada. Es solo un poco más simple.
Shahar
@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.
Ryan P

Respuestas:

882

Comenzando con Safari 5 / Firefox 4, es más fácil usar la FormDataclase:

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

Entonces ahora tiene un FormDataobjeto, 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 imprescindible que establezca la contentTypeopción false, forzando a jQuery a no agregar un Content-Typeencabezado, de lo contrario, la cadena de límite no se incluirá. Además, debe dejar el processDataindicador establecido en falso, de lo contrario, jQuery intentará convertirlo FormDataen una cadena, lo que fallará.

Ahora puede recuperar el archivo en PHP usando:

$_FILES['file-0']

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

Uso de la 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 desde 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 a sus elementos de archivo el mismo nombre 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 simple iterar.

Raphael Schweikert
fuente
2
Además, existe una emulación FormData que hará que portar esta solución a navegadores antiguos sea bastante simple. Todo lo que tiene que hacer es verificar data.fakey establecer la contentTypepropiedad manualmente, así como anular xhr para usar sendAsBinary().
Raphael Schweikert
13
Entonces, ¿no estás usando jQueryni la FormDataclase y me preguntas en el contexto de una pregunta específica para jQuery y una respuesta específica para usar FormData? Lo siento, pero no creo que pueda ayudarte allí ...
Raphael Schweikert
3
Esto usa application/x-www-form-urlencoded. ¿Hay alguna forma de usar multipart/form-dataen su lugar?
Timmmm
44
@Timmmm No, lo usa multipart/form-data. Usar application/x-www-form-urlencodedno funcionaría.
Raphael Schweikert
55
@RoyiNamir Solo está documentado en código , me temo.
Raphael Schweikert
51

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

Formulario HTML:

<form enctype="multipart/form-data" action="/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, utilice Raphael's JS 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);
    }
});

... así es $_FILEScomo se ve la matriz de PHP , 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 variedad, y en realidad es en lo que algunas personas se transforman $_FILES, pero creo que es útil trabajar con lo mismo $_FILES, independientemente de si se usó JavaScript para enviar. Entonces, aquí hay algunos cambios menores al 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 corrigió este código en Safari).

Ese código hace dos cosas.

  1. Recupera el inputatributo de nombre automáticamente, haciendo que el HTML sea más fácil de mantener. Ahora, siempre que formla clase haya puesto imágenes, todo lo demás se encargará automáticamente. Es decir, la inputnecesidad no tiene ningún 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, el envío con JavaScript ahora produce exactamente la misma $_FILESmatriz que el envío con HTML simple.

ajmicek
fuente
Tuve el mismo problema con Safari. ¡Gracias por la pista!
medoingthings
49

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();
  } );
Asad Malik
fuente
Esto funcionó perfectamente y es muy sencillo de implementar.
Jh62
45

Acabo de construir esta función basada en alguna información que leí.

Utilizarlo como usar .serialize(), sólo hay que poner en su lugar .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);
evandro777
fuente
2
Estaba tratando de hacer 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.
Fallenreaper
1
eso me funciona bien. obtener datos al var data = $("#avatar-form").serializefiles();enviar esto a través del parámetro de datos ajax y analizar con formidable express: form.parse(req, function(err, fields, files){gracias por ese fragmento de código :)
SchurigH
23

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){
            ...
Devin Venable
fuente
9

La respuesta de Devin Venable fue cercana a lo que quería, pero quería una que funcionara en múltiples formularios y utilizara 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.
    });

});
Karl Henselin
fuente
2

Hoy en día ni siquiera necesita jQuery :) fetch API support table

let result = fetch('url', {method: 'POST', body: new FormData(document.querySelector("#form"))})
Alex Nikulin
fuente
En el contexto de la carga de archivos mediante fetch, consulte compatibilidad: developer.mozilla.org/en-US/docs/Web/API/…
Omar Tariq
@OmarTariq sí, tengo un enlace similar en mi respuesta))
Alex Nikulin
Ups ¿Cómo puedo perder esa:-|
Omar Tariq
1

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 buena 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 proceder como en el código de Raphael .

topkara
fuente
Tuve un problema con mis cargas jquery ajax silenciosamente colgadas en Safari y terminé haciendo un navegador condicional $ ('form-name'). Submit () 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 jquery, por lo que funcionó bien.
glifo
1

Creo que vale la pena señalar un problema que encontré hoy relacionado con este problema: si se redirige la url para la llamada ajax, entonces se puede perder el encabezado para content-type: '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 multiparte correcto para esta solicitud, pero luego un redireccionamiento 302 a http://server.com/context/?param=x (tenga en cuenta la barra diagonal después del contexto)

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

James
fuente
1

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

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

szatti1489
fuente
1

Si la entrada del archivo nameindica una matriz y marcas multiple, y analiza todo formcon FormData, no es necesario iterar append()los archivos de entrada. FormDatamanejará automáticamente múltiples 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 button type="button"se utiliza un regular , no type="submit". Esto muestra que no hay dependencia en el uso submitpara obtener esta funcionalidad.

La $_FILESentrada resultante 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 que algunas imágenes se cargarán bien cuando se cargan como un solo archivo, pero fallarán cuando se carguen en un conjunto de múltiples archivos. El síntoma es que PHP informa vacío $_POSTy $_FILESsin 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.

OXÍGENO
fuente
0

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

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

sudip
fuente
0

PHP

<?php 
    var_dump( $_FILES ); 
?>

jQuery y formulario HTML

$( "#form" ).bind( "submit", function (e) {

    e.preventDefault();

    $.ajax({

        method: "post",
        url: "process.php",

        data: new FormData( $(this) ), // $(this) = formHTML element

        cache: false,

        /* upload */
        contentType: false,
        processData: false

    });

} )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<form id="form" >
    File: <input type="file" name="upload" />
    <input type="submit" value="Submit" />
</form>

antílope
fuente
-1
  1. obtener objeto de formulario por jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. ok, los datos son tu deseo
usuario1909226
fuente
$ ("# id") [0] devuelve primero ninguno vacío <input type = "file" /> del formulario, ¿cómo envía el formulario completo, incluidos todos los <input type = "file" />?
Mohammad-Hossein Jamali