Отправка multipart/formdata с помощью jQuery.ajax

У меня проблема с отправкой файла на серверный 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);
    }
});

12 ответов

Решение

Начиная с 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'] затем будет массив, содержащий поля загрузки файла для каждого загруженного файла. Я на самом деле рекомендую это поверх моего первоначального решения, так как его проще перебирать.

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

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

Просто хотел добавить немного к отличному ответу Рафаэля. Вот как заставить 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 Атрибут name автоматически делает HTML более понятным. Теперь, пока form имеет класс putImages, обо всем остальном позаботится автоматически. Это input не нужно иметь никакого специального имени.
  2. Формат массива, который отправляет обычный HTML, воссоздается JavaScript в строке data.append. Обратите внимание на скобки.

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

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

Используйте это как использование .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);

Если ваша форма определена в вашем 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){
            ...

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

Я также хотел использовать метод 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.
    });

});

Если файл input 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. Кажется, это происходит только с одним из нескольких десятков изображений в моем тестовом наборе.

В наши дни вам даже не понадобится даже таблица поддержки API jQuery:)

let result = fetch('url', {method: 'POST', body: new FormData(documemt.querySelector("#form"))})

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

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

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

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

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

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

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

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

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

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

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

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

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

Более старые версии IE не поддерживают FormData (полный список поддержки браузера для FormData находится здесь: https://developer.mozilla.org/en-US/docs/Web/API/FormData).

Либо вы можете использовать плагин jquery (например, http://malsup.com/jquery/form/), либо вы можете использовать решение на основе IFrame для публикации данных из нескольких частей формы через ajax: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

  1. получить объект формы с помощью jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. хорошо, данные - это ваше желание

PHP

<?php 
    var_dump( $_FILES ); 
?>

jQuery и форма 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>

Другие вопросы по тегам