Invio multipart / formdata con jQuery.ajax


563

Ho un problema con l'invio di un file a uno script PHP sul lato server usando la funzione ajax di jQuery. È possibile ottenere l'elenco dei file con, $('#fileinput').attr('files')ma come è possibile inviare questi dati al server? L'array risultante ( $_POST) sul php-script sul lato server è 0 ( NULL) quando si utilizza l'input di file.

So che è possibile (anche se finora non ho trovato soluzioni jQuery, solo il codice Prototye ( http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html ) ).

Questo sembra essere relativamente nuovo, quindi per favore non menzionare il caricamento di file sarebbe impossibile tramite XHR / Ajax, perché sicuramente funziona.

Ho bisogno che le funzionalità di Safari 5, FF e Chrome siano belle ma non essenziali.

Il mio codice per ora è:

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

Purtroppo l'utilizzo dell'oggetto FormData non funziona su IE <10.
Alejandro García Iglesias,

@GarciaWebDev presumibilmente è possibile utilizzare un polyfill con Flash per supportare la stessa API. Scopri github.com/Modernizr/Modernizr/wiki/… per maggiori informazioni.
yuxhuang,


3
È possibile utilizzare $(':file')per selezionare tutti i file di input. È solo un po 'più semplice.
Shahar,

@RameshwarVyevhare Quella risposta è stata pubblicata cinque anni dopo la risposta a questa domanda. Per favore, non trollare domande simili solo per promuovere le tue risposte.
Ryan P,

Risposte:


882

A partire da Safari 5 / Firefox 4, è più facile usare la FormDataclasse:

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

Quindi ora hai un FormDataoggetto, pronto per essere inviato insieme a 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);
    }
});

È indispensabile impostare l' contentTypeopzione su false, forzando jQuery a non aggiungere Content-Typeun'intestazione per te, altrimenti la stringa di confine mancherà da essa. Inoltre, devi lasciare il processDataflag impostato su false, altrimenti jQuery proverà a convertire il tuo FormDatain una stringa, il che fallirà.

Ora puoi recuperare il file in PHP usando:

$_FILES['file-0']

(Esiste un solo file, a file-0meno che non sia stato specificato l' multipleattributo nell'input del file, nel qual caso i numeri aumenteranno con ogni file.)

Utilizzo dell'emulazione FormData per browser meno recenti

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

Crea FormData da un modulo esistente

Invece di iterare manualmente i file, l'oggetto FormData può anche essere creato con il contenuto di un oggetto modulo esistente:

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

Usa un array nativo PHP invece di un contatore

Basta nominare gli elementi del file allo stesso modo e terminare il nome tra parentesi:

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

$_FILES['file']sarà quindi un array contenente i campi di caricamento dei file per ogni file caricato. In realtà lo consiglio sulla mia soluzione iniziale in quanto è più semplice iterare.


2
Inoltre, esiste un'emulazione FormData che renderà abbastanza semplice il porting di questa soluzione su browser meno recenti. Tutto quello che devi fare è controllare data.fakee impostare la contentTypeproprietà manualmente, oltre a sostituire xhr da usare sendAsBinary().
Raphael Schweikert,

13
Quindi non stai usando jQueryné la FormDataclasse e mi stai ponendo nel contesto di una domanda specifica per jQuery e una risposta specifica per l'utilizzo di FormData? Mi dispiace ma non credo di poterti aiutare lì ...
Raphael Schweikert,

3
Questo usa application/x-www-form-urlencoded. C'è un modo di usare multipart/form-datainvece?
Timmmm,

4
@Timmmm No, utilizza multipart/form-data. L'uso application/x-www-form-urlencodednon funzionerebbe.
Raphael Schweikert,

5
@RoyiNamir È solo documentato nel codice , temo.
Raphael Schweikert,

51

Volevo solo aggiungere un po 'alla grande risposta di Raffaello. Ecco come fare in modo che PHP produca lo stesso $_FILES, indipendentemente dal fatto che tu usi JavaScript per inviare.

Modulo 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 questo $_FILES, quando inviato senza 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
                )

        )

)

Se esegui un miglioramento progressivo, utilizza Raphael's JS per inviare i file ...

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

... ecco $_FILEScome si presenta l'array di PHP , dopo aver usato JavaScript per inviare:

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
        )

)

È un array piacevole, e in realtà ciò che alcune persone si trasformano $_FILES, ma trovo utile lavorare con lo stesso $_FILES, indipendentemente dal fatto che JavaScript sia stato utilizzato per l'invio. Quindi, ecco alcune piccole modifiche a 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);
});

(Modifica del 14 aprile 2017: ho rimosso l'elemento del modulo dal costruttore di FormData () - che ha corretto questo codice in Safari.)

Quel codice fa due cose.

  1. Recupera inputautomaticamente l'attributo name, rendendo l'HTML più gestibile. Ora, fintanto che formla classe ha messo immagini, tutto il resto è curato automaticamente. Cioè, la inputnecessità non ha alcun nome speciale.
  2. Il formato dell'array inviato dall'HTML normale viene ricreato da JavaScript nella riga data.append. Nota le parentesi.

Con queste modifiche, l'invio con JavaScript ora produce esattamente lo stesso $_FILESarray dell'invio con HTML semplice.


Ha avuto lo stesso problema con Safari. Grazie per il suggerimento!
medoingthings

49

Guarda il mio codice, fa il lavoro per me

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

Funzionava perfettamente ed è molto semplice da implementare.
Jh62

45

Ho appena creato questa funzione in base ad alcune informazioni che ho letto.

Usalo come usando .serialize(), invece basta mettere .serializefiles();.
Lavorando qui nei miei test.

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

2
Stavo cercando di farlo funzionare, ma sembrava non riconoscere serializefiles () come una funzione, nonostante questa definizione andasse in cima alla pagina.
Fallenreaper,

1
per me funziona bene. ottenere dati con l' var data = $("#avatar-form").serializefiles();invio tramite parametro di dati form.parse(req, function(err, fields, files){
Ajax

23

Se il modulo è definito nel codice HTML, è più semplice passare il modulo al costruttore piuttosto che iterare e aggiungere immagini.

$('#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){
            ...

9

La risposta di Devin Venable era vicina a ciò che volevo, ma ne volevo uno che avrebbe funzionato su più moduli e che usasse l'azione già specificata nel modulo in modo che ogni file andasse nel posto giusto.

Volevo anche usare il metodo on () di jQuery per evitare di usare .ready ().

Questo mi ha portato a questo: (sostituisci formSelector con il tuo selettore 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.
    });

});


1

La classe FormData funziona, tuttavia in iOS Safari (almeno su iPhone) non sono stato in grado di utilizzare la soluzione di Raphael Schweikert così com'è.

Mozilla Dev ha una bella pagina sulla manipolazione di oggetti FormData .

Quindi, aggiungi un modulo vuoto da qualche parte nella tua pagina, specificando il tipo:

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

Quindi, creare l'oggetto FormData come:

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

e procedere come nel codice di Raffaello .


Ho avuto un problema con i miei caricamenti ajax jquery che si bloccavano silenziosamente in Safari e ho finito per fare un browser $ ('form-name') condizionato. Submit () per Safari invece del caricamento ajax che funziona in IE9 e FF18. Probabilmente non è una soluzione ideale per i caricamenti multipli, ma lo stavo facendo per un singolo file in un iframe da una finestra di dialogo jquery, quindi ha funzionato bene.
glifo

1

Penso che valga la pena sottolineare un gotcha che ho riscontrato oggi in relazione a questo problema: se l'URL per la chiamata Ajax viene reindirizzato, l'intestazione per il tipo di contenuto: "multipart / form-data" può andare persa.

Ad esempio, stavo pubblicando su http://server.com/context?param=x

Nella scheda di rete di Chrome ho visto l'intestazione multipart corretta per questa richiesta, ma poi un reindirizzamento 302 a http://server.com/context/?param=x (notare la barra dopo il contesto)

Durante il reindirizzamento l'intestazione multipart è andata persa. Assicurati che le richieste non vengano reindirizzate se queste soluzioni non funzionano per te.


1

Tutte le soluzioni sopra sono belle ed eleganti, ma l'oggetto FormData () non si aspetta alcun parametro, ma usa append () dopo averlo istanziato, come quello che hai scritto sopra:

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


1

Se l'input del file nameindica un array e flag multiplee si analizza l'intero formcon FormData, non è necessario iterativamente append()i file di input. FormDatagestirà automaticamente più file.

$('#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>

Si noti che button type="button"viene utilizzato un normale , non type="submit". Ciò dimostra che non esiste alcuna dipendenza dall'utilizzo submitper ottenere questa funzionalità.

La $_FILESvoce risultante è così negli strumenti di sviluppo di 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: ci sono casi in cui alcune immagini verranno caricate correttamente quando caricate come un singolo file, ma falliranno se caricate in un set di più file. Il sintomo è che PHP segnala vuoto $_POSTe $_FILESsenza AJAX che genera errori. Il problema si verifica con Chrome 75.0.3770.100 e PHP 7.0. Sembra accadere solo con 1 su diverse dozzine di immagini nel mio set di test.



0

PHP

<?php 
    var_dump( $_FILES ); 
?>

jQuery e Form 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>


-1
  1. ottieni l'oggetto modulo da jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. ok, i dati sono i tuoi desideri

$ ("# id") [0] restituisce per primo nessuno vuoto <input type = "file" /> del modulo, come si invia l'intero modulo inclusi tutti <input type = "file" />?
Mohammad-Hossein Jamali,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.