Отправка multipart / formdata с помощью jQuery.ajax - PullRequest
527 голосов
/ 22 марта 2011

У меня проблема с отправкой файла на серверный PHP-скрипт с использованием ajax-функции jQuery. Можно получить список файлов с $('#fileinput').attr('files'), но как отправить эти данные на сервер? Результирующий массив ($_POST) на серверном php-скрипте равен 0 (NULL) при использовании файла-ввода.

Я знаю, что это возможно (хотя я до сих пор не нашел решений jQuery, только код Prototye (http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html)).

Это кажется относительно новым, поэтому, пожалуйста, не упоминайте, что загрузка файла будет невозможна через XHR / Ajax, потому что он определенно работает.

Мне нужна функциональность в Safari 5, FF и Chrome были бы хороши, но не обязательны.

Мой код сейчас:

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

Ответы [ 13 ]

832 голосов
/ 12 мая 2011

Начиная с Safari 5 / Firefox 4, проще всего использовать класс FormData:

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

Так что теперь у вас есть объект FormData, готовый для отправки вместе с 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);
    }
});

Крайне важно, чтобы вы установили опцию contentType на false, заставив jQuery не добавлять для вас заголовок Content-Type, в противном случае строка границы будет отсутствовать в нем.Кроме того, вы должны оставить для флага processData значение false, иначе jQuery попытается преобразовать ваш FormData в строку, что не удастся.

Теперь вы можете получить файл в PHP, используя:

$_FILES['file-0']

(Существует только один файл, file-0, если только вы не указали атрибут multiple на входе вашего файла, в этом случае числа будут увеличиваться с каждым файлом.)

Использование эмуляции FormData для старых браузеров

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

Создание FormData из существующей формы

Вместо ручногоитерируя файлы, объект FormData также может быть создан с содержимым существующего объекта формы:

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

Использовать собственный массив PHP вместо счетчика

Просто назовите элементы вашего файла одинаково и введите имя в скобках:

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

$_FILES['file'] будет массивом, содержащим поля загрузки файла для каждого загруженного файла.На самом деле я рекомендую это по сравнению с моим первоначальным решением, так как итерацию проще.

47 голосов
/ 10 января 2012

Просто хотел добавить немного к великому ответу Рафаэля. Вот как заставить PHP производить тот же $_FILES, независимо от того, используете ли вы JavaScript для отправки.

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 выдает это $_FILES при отправке без 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
                )

        )

)

Если вы делаете прогрессивное улучшение, используя Raphael JS для отправки файлов ...

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

... это то, как выглядит массив PHP $_FILES после использования этого JavaScript для отправки:

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
        )

)

Это хороший массив, и на самом деле то, во что некоторые люди преобразуют $_FILES, но я считаю полезным работать с тем же $_FILES, независимо от того, использовался ли JavaScript для отправки. Итак, вот некоторые незначительные изменения в 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);
});

(14 апреля 2017 г., редактирование: я удалил элемент формы из конструктора FormData () - это исправило этот код в Safari.)

Этот код делает две вещи.

  1. Извлекает атрибут имени input автоматически, делая HTML более понятным. Теперь, пока form имеет класс putImages, все остальное решается автоматически. То есть input не должно иметь никакого специального имени.
  2. Формат массива, который отправляет обычный HTML, воссоздается JavaScript в строке data.append. Обратите внимание на скобки.

С этими изменениями отправка с использованием JavaScript теперь создает точно такой же массив $_FILES, что и отправка с простым HTML.

45 голосов
/ 30 августа 2014

Посмотрите на мой код, он делает всю работу за меня

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );
44 голосов
/ 14 сентября 2012

Я только что построил эту функцию на основе прочитанной информации.

Используйте его как .serialize(), вместо этого просто введите .serializefiles();.
Работаю здесь в моих тестах.

//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);
23 голосов
/ 02 июня 2014

Если ваша форма определена в вашем HTML, легче передать форму в конструктор, чем итерировать и добавлять изображения.

$('#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){
            ...
7 голосов
/ 15 июня 2016

Ответ Девина Венейла был близок к тому, что я хотел, но я хотел, чтобы тот работал с несколькими формами и использовал действие, уже указанное в форме, чтобы каждый файл перемещался в нужное место.

Я также хотел использовать метод on () jQuery, чтобы избежать использования .ready ().

Это привело меня к этому: (замените formSelector вашим селектором 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 голос
/ 08 марта 2018

Все решения, представленные выше, выглядят хорошо и элегантно, но объект FormData () не ожидает какого-либо параметра, но использует append () после его создания, как написано выше:

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

1 голос
/ 18 марта 2016

Одна ошибка, с которой я столкнулся сегодня, думаю, стоит отметить, связанную с этой проблемой: если URL-адрес для вызова ajax перенаправлен, тогда заголовок для типа содержимого: 'multipart / form-data' может быть потерян.

Например, я писал на http://server.com/context?param=x

На сетевой вкладке Chrome я увидел правильный многокомпонентный заголовок для этого запроса, но затем перенаправил 302 на http://server.com/context/?param=x (обратите внимание на косую черту после контекста)

Во время перенаправления многокомпонентный заголовок был потерян. Убедитесь, что запросы не перенаправляются, если эти решения не работают для вас.

1 голос
/ 22 февраля 2013

Класс FormData работает, однако в iOS Safari (по крайней мере, на iPhone) я не смог использовать решение Рафаэля Швейкера как есть.

У Mozilla Dev есть хорошая страница по манипулированиюОбъекты FormData .

Итак, добавьте пустую форму где-нибудь на вашей странице, указав тип:

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

Затем создайте объект FormData как:

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

и действуйте, как в коде Рафаэля .

0 голосов
/ 01 июля 2019

Если входной файл name указывает массив и флаги multiple, и вы анализируете весь form с FormData, нет необходимости итеративно append() входных файлов.FormData автоматически обработает несколько файлов.

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

Обратите внимание, что используется обычный button type="button", а не type="submit".Это показывает, что нет никакой зависимости от использования submit для получения этой функциональности.

Результирующая запись $_FILES выглядит так в инструментах разработчика 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"]

Примечание: Есть случаигде некоторые изображения будут загружаться очень хорошо при загрузке в виде одного файла, но они не будут работать при загрузке в виде набора из нескольких файлов.Симптом состоит в том, что PHP сообщает пустые $_POST и $_FILES без AJAX, выдавая какие-либо ошибки.Проблема возникает с Chrome 75.0.3770.100 и PHP 7.0.Похоже, это происходит только с 1 из нескольких десятков изображений в моем тестовом наборе.

...