Wysyłanie danych wieloczęściowych / formularzy za pomocą jQuery.ajax

563

Mam problem z wysłaniem pliku na serwerowy skrypt PHP przy użyciu funkcji ajax w jQuery. Możliwe jest pobranie listy plików, $('#fileinput').attr('files')ale jak można wysłać te dane na serwer? Wynikowa tablica ( $_POST) po stronie skryptu php to 0 (NULL ) podczas korzystania z wejścia pliku.

Wiem, że jest to możliwe (chociaż do tej pory nie znalazłem żadnych rozwiązań jQuery, tylko kod Prototye ( http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html ) ).

Wydaje się to być stosunkowo nowe, więc proszę nie wspominać, że przesyłanie plików byłoby niemożliwe przez XHR / Ajax, ponieważ zdecydowanie działa.

Potrzebuję funkcjonalności w Safari 5, FF i Chrome byłyby fajne, ale nie są niezbędne.

Mój kod na razie to:

$.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
źródło
Niestety użycie obiektu FormData nie działa w IE <10.
Alejandro García Iglesias
@GarciaWebDev podobno można użyć wypełnienia z Flashem do obsługi tego samego API. Sprawdź github.com/Modernizr/Modernizr/wiki/…, aby uzyskać więcej informacji.
yuxhuang
Możliwy duplikat .
Raphael Schweikert
3
Możesz użyć, $(':file')aby wybrać wszystkie pliki wejściowe. To tylko trochę prostsze.
Shahar,
@RameshwarVyevhare Ta odpowiedź została opublikowana pięć lat po udzieleniu odpowiedzi na to pytanie. Nie trolluj podobnych pytań, aby promować własne odpowiedzi.
Ryan P,

Odpowiedzi:

882

Począwszy od Safari 5 / Firefox 4 najłatwiej jest użyć FormDataklasy:

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

Teraz masz FormDataobiekt gotowy do wysłania wraz z 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);
    }
});

Konieczne jest ustawienie tej contentTypeopcji false, zmuszając jQuery, aby nie dodawał Content-Typenagłówka, w przeciwnym razie nie będzie w nim ciągu granicznego. Musisz także pozostawić processDataflagę ustawioną na false, w przeciwnym razie jQuery spróbuje przekonwertować twójFormData ciąg znaków, co się nie powiedzie.

Możesz teraz pobrać plik w PHP, używając:

$_FILES['file-0']

(Jest tylko jeden plik, file-0chyba że określiłeś multipleatrybut na wejściu pliku, w takim przypadku liczby będą rosły z każdym plikiem).

Korzystanie z emulacji FormData dla starszych przeglądarek

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

Utwórz FormData z istniejącego formularza

Zamiast ręcznie iterować pliki, obiekt FormData można również utworzyć z zawartością istniejącego obiektu formularza:

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

Użyj natywnej tablicy PHP zamiast licznika

Wystarczy nazwać elementy pliku tak samo i zakończyć nazwę w nawiasach:

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

$_FILES['file']będzie wówczas tablicą zawierającą pola przesyłania plików dla każdego przesłanego pliku. Tak naprawdę polecam to w stosunku do mojego początkowego rozwiązania, ponieważ łatwiej jest iterować.

Raphael Schweikert
źródło
2
Ponadto dostępna jest emulacja FormData, która znacznie ułatwi przenoszenie tego rozwiązania do starszych przeglądarek. Wszystko, co musisz zrobić, to sprawdzić data.fakei ustawić contentTypewłaściwość ręcznie, a także zastąpić xhr, aby użyć sendAsBinary().
Raphael Schweikert
13
Więc nie korzystasz jQueryani z FormDataklasy, a pytasz mnie w kontekście pytania dotyczącego jQuery i odpowiedzi dotyczącej używania FormData? Przykro mi, ale nie sądzę, żebym mógł ci tam pomóc ...
Raphael Schweikert,
3
To używa application/x-www-form-urlencoded. Czy istnieje sposób, aby użyć multipart/form-datazamiast tego?
Timmmm
4
@Timmmm Nie, używa multipart/form-data. Korzystanie application/x-www-form-urlencodednie działałoby.
Raphael Schweikert
5
@RoyiNamir Obawiam się, że jest to udokumentowane tylko kodem .
Raphael Schweikert
51

Chciałem tylko dodać trochę do świetnej odpowiedzi Raphaela. Oto jak zmusić PHP do wyprodukowania tego samego$_FILES , niezależnie od tego, czy używasz JavaScript do przesyłania.

Formularz 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 produkuje to $_FILES, gdy przesłane bez 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
                )

        )

)

Jeśli robisz stopniowe ulepszanie, używając JS Raphaela do przesyłania plików ...

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

... tak $_FILESwygląda tablica PHP po użyciu tego JavaScript do przesłania:

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
        )

)

To fajna tablica i faktycznie to, w co niektórzy ludzie przekształcają $_FILES, ale uważam, że warto pracować z tym samym $_FILES, bez względu na to, czy JavaScript był używany do przesyłania. Oto kilka drobnych zmian w 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);
});

(Edycja z 14 kwietnia 2017 r .: usunąłem element formularza z konstruktora FormData () - to naprawiło ten kod w Safari).

Ten kod ma dwie rzeczy.

  1. Pobiera input atrybut name automatycznie, dzięki czemu HTML bardziej linkujących. Teraz, o ile formklasa putImages, wszystko inne jest załatwiane automatycznie. Oznacza to, że inputnie trzeba mieć żadnej specjalnej nazwy.
  2. Format tablicy przesyłany przez normalny HTML jest odtwarzany przez JavaScript w wierszu data.append. Zwróć uwagę na nawiasy.

Dzięki tym zmianom przesyłanie za pomocą JavaScript tworzy teraz dokładnie tę samą $_FILEStablicę, co przesyłanie za pomocą prostego HTML.

ajmicek
źródło
Miałem ten sam problem z Safari. Dzięki za podpowiedź!
medoingthings
49

Spójrz na mój kod, robi to za mnie

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );
Asad Malik
źródło
Działa to doskonale i jest bardzo łatwe do wdrożenia.
Jh62
45

Właśnie zbudowałem tę funkcję na podstawie przeczytanych informacji.

.serialize()Zamiast tego po prostu użyj .serializefiles();.
Pracuję tutaj w moich testach.

//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
źródło
2
Próbowałem uruchomić tę funkcję, ale wydawało się, że nie rozpoznaje ona serializefiles () jako funkcji, mimo że definicja ta znajduje się na górze strony.
Fallenreaper,
1
to działa dla mnie dobrze. pobieranie danych z var data = $("#avatar-form").serializefiles();wysyłaniem tego za pomocą parametru danych ajax i analizowanie ekspresowym formidable: form.parse(req, function(err, fields, files){dziękuję za ten fragment kodu :)
SchurigH
23

Jeśli formularz jest zdefiniowany w kodzie HTML, łatwiej jest przekazać formularz do konstruktora niż iterować i dodawać obrazy.

$('#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
źródło
9

Odpowiedź Devina Venable'a była zbliżona do tego, czego chciałem, ale chciałem takiego, który będzie działał na wielu formularzach i używał akcji już określonej w formularzu, aby każdy plik trafił we właściwe miejsce.

Chciałem również użyć metody on () jQuery, aby uniknąć używania .ready ().

Doprowadziło mnie to do tego: (zastąp formSelector swoim selektorem 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
źródło
2

W dzisiejszych czasach nie potrzebujesz nawet jQuery :) pobierania tabeli wsparcia API

let result = fetch('url', {method: 'POST', body: new FormData(document.querySelector("#form"))})
Alex Nikulin
źródło
W kontekście przesyłania pliku za pośrednictwem fetchzobacz zgodność: developer.mozilla.org/en-US/docs/Web/API/…
Omar Tariq
@OmarTariq tak mam podobny link w mojej odpowiedzi))
Alex Nikulin
Ups Jak mogę tego przegapić:-|
Omar Tariq,
1

Klasa FormData działa, jednak w iOS Safari (przynajmniej na iPhonie) nie byłem w stanie użyć rozwiązania Raphaela Schweikerta.

Mozilla Dev ma fajną stronę na temat manipulowania obiektami FormData .

Dodaj pusty formularz gdzieś na swojej stronie, określając typ:

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

Następnie utwórz obiekt FormData jako:

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

i postępuj jak w kodzie Rafaela .

topkara
źródło
Miałem problem z moimi ładowaniami Jquery ajax cicho zawieszającymi się w Safari i skończyłem robiąc przeglądarkę warunkową $ ('nazwa-formy'). Replace () dla Safari zamiast uploadu ajax, który działa w IE9 i FF18. Prawdopodobnie nie jest to idealne rozwiązanie do wielokrotnego przesyłania, ale robiłem to dla pojedynczego pliku w ramce iframe z okna dialogowego jquery, więc działało dobrze.
glif
1

Jeden problem, na który natknąłem się dzisiaj, myślę, że warto zwrócić uwagę na ten problem: jeśli adres URL wywołania ajax zostanie przekierowany, wówczas nagłówek typu treści: „multipart / form-data” może zostać utracony.

Na przykład pisałem na stronie http://server.com/context?param=x

Na karcie sieciowej Chrome zobaczyłem poprawny wieloczęściowy nagłówek tego żądania, ale potem przekierowanie 302 na http://server.com/context/?param=x (zwróć uwagę na ukośnik po kontekście)

Podczas przekierowania utracono nagłówek wieloczęściowy. Upewnij się, że żądania nie są przekierowywane, jeśli te rozwiązania nie działają dla Ciebie.

James
źródło
1

Wszystkie powyższe rozwiązania wyglądają dobrze i elegancko, ale obiekt FormData () nie oczekuje żadnego parametru, ale po utworzeniu instancji używa append (), tak jak napisano powyżej:

formData.append (nazwa.wartości; wartość.wartości);

szatti1489
źródło
1

Jeśli plik wejściowy namewskazuje tablicę i flagi multiple, a ty analizowania całej karty formz FormData, to nie jest konieczne, aby iteracyjnie append()pliki wejściowe. FormDataautomatycznie obsłuży wiele plików.

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

Zauważ, że button type="button"używa się zwykłego , a nie type="submit". To pokazuje, że nie ma zależności od korzystania submitz tej funkcji.

Wynikowy $_FILESwpis jest taki w narzędziach programistycznych 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"]

Uwaga: zdarzają się przypadki, w których niektóre obrazy będą ładowane w porządku, gdy zostaną przesłane jako pojedynczy plik, ale nie powiedzie się, gdy zostaną przesłane w zestawie wielu plików. Objawem jest to, że raporty PHP są puste $_POSTi $_FILESAJAX nie zgłasza żadnych błędów. Problem występuje w Chrome 75.0.3770.100 i PHP 7.0. Wydaje się, że dzieje się tak tylko z 1 z kilkudziesięciu zdjęć w moim zestawie testowym.

TLEN
źródło
0

Starsze wersje IE nie obsługują FormData (pełna lista przeglądarek FormData znajduje się tutaj: https://developer.mozilla.org/en-US/docs/Web/API/FormData ).

Możesz użyć wtyczki jquery (na przykład http://malsup.com/jquery/form/#code-samples ) lub możesz użyć rozwiązania opartego na ramce IFrame do wysyłania danych wieloczęściowych za pośrednictwem ajax: https: // developer. mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

sudip
źródło
0

PHP

<?php 
    var_dump( $_FILES ); 
?>

jQuery i formularz 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>

antylopa
źródło
-1
  1. pobierz obiekt formularza przez jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. ok, dane są twoje
użytkownik1909226
źródło
$ ("# id") [0] zwraca pierwszy brak pusty <input type = "file" /> formularza, jak przesłać cały formularz, w tym wszystkie <input type = "file" />?
Mohammad-Hossein Jamali