Sending av multipart/formdata med jQuery.ajax

Jeg'har et problem med å sende en fil til et PHP-script på serversiden ved hjelp av jQuery's ajax-funksjon. Det er mulig å hente fil-listen med $('#fileinput').attr('files'), men hvordan er det mulig å sende disse dataene til serveren? Den resulterende matrisen ($_POST) på serversidens php-skript er 0 (NULL) når du bruker file-input.

Jeg vet at det er mulig (selv om jeg ikke fant noen jQuery-løsninger før nå, bare Prototye-kode (http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html)).

Dette ser ut til å være relativt nytt, så vær så snill å ikke nevne at filopplasting er umulig via XHR/Ajax, for det fungerer definitivt.

Jeg trenger funksjonaliteten i Safari 5, FF og Chrome ville vært fint, men er ikke avgjørende.

Min kode for nå er:

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

Fra og med Safari 5/Firefox 4 er det enklest å bruke klassen FormData:

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

Nå har du et FormData-objekt som er klart til å sendes sammen med 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);
    }
});

Det er viktig at du setter alternativet contentType til false, slik at jQuery ikke legger til en Content-Type header for deg, ellers vil grensestrengen mangle. Du må også la processData-flagget være satt til false, ellers vil jQuery prøve å konvertere FormData til en streng, noe som vil mislykkes.

Du kan nå hente filen i PHP ved hjelp av:

$_FILES['file-0']

(Det finnes bare én fil, file-0, med mindre du har angitt attributtet multiple på filinndataene, i så fall vil tallene øke for hver fil).

Bruke FormData-emulering for eldre nettlesere.

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

Gjenopprette FormData fra et eksisterende skjema.

I stedet for å iterere filene manuelt, kan FormData-objektet også opprettes med innholdet i et eksisterende skjemaobjekt:

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

Bruk en PHP-innfødt array i stedet for en teller.

Bare navngi filelementene dine på samme måte og avslutt navnet i parentes:

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

$_FILES['file'] blir da en matrise som inneholder filopplastingsfeltene for hver opplastede fil. Jeg anbefaler faktisk dette fremfor min opprinnelige løsning, da det er enklere å iterere over.

Kommentarer (31)

Ville bare legge til litt til Raphaels gode svar. Slik får du PHP til å produsere de samme $_FILES, uavhengig av om du bruker JavaScript til å sende.

HTML-skjema:


   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />

PHP produserer denne $_FILES når den sendes inn uten 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
                )

        )

)

Hvis du gjør progressiv forbedring ved hjelp av Raphael's JS for å sende inn filene...

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

... slik ser PHP&#39s $_FILES-array ut etter at du har brukt JavaScript til å sende inn:

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
        )

)

Det er en fin matrise, og faktisk det noen forvandler $_FILES til, men jeg synes det er nyttig å jobbe med de samme $_FILES, uavhengig av om JavaScript ble brukt til å sende inn. Så her er noen mindre endringer i 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);
});

(redigering 14. april 2017: Jeg fjernet skjemaelementet fra konstruktøren av FormData() - det fikset koden i Safari).

Denne koden gjør to ting.

  1. Henter input-navnattributtet automatisk, noe som gjør HTML-en mer vedlikeholdbar. Så lenge form har klassen putImages, blir alt annet tatt hånd om automatisk. Det vil si at input ikke trenger å ha noe spesielt navn.
  2. Array-formatet som vanlig HTML sender inn, gjenskapes av JavaScript i data.append-linjen. Legg merke til parentesene.

Med disse endringene produserer innsending med JavaScript nå nøyaktig samme $_FILES-array som innsending med enkel HTML.

Kommentarer (1)

Jeg bygde nettopp denne funksjonen basert på noe informasjon jeg leste.

Bruk den som å bruke .serialize(), i stedet bare sett.serializefiles();`.
Fungerer her i testene mine;

//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);
Kommentarer (2)