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


563

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

Я знаю, что это возможно (хотя до сих пор я не нашел решений 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);
    }
});

К сожалению, использование объекта FormData не работает в IE <10.
Алехандро Гарсия Иглесиас

@GarciaWebDev предположительно вы можете использовать полифилл с Flash для поддержки того же API. Проверьте github.com/Modernizr/Modernizr/wiki/… для получения дополнительной информации.
Юйшуан


3
Вы можете использовать $(':file')для выбора всех входных файлов. Это немного проще.
Шахар

@RameshwarVyevhare Этот ответ был опубликован через пять лет после ответа на этот вопрос. Пожалуйста, не троллите подобные вопросы только для продвижения ваших собственных ответов.
Райан П

Ответы:


882

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


2
Кроме того, есть эмуляция FormData, которая сделает перенос этого решения на старые браузеры довольно простым. Все, что вам нужно сделать, это проверить data.fakeи установить contentTypeсвойство вручную, а также переопределить xhr для использования sendAsBinary().
Рафаэль Швейкерт

13
То есть вы не используете jQueryни FormDataкласс, ни вопрос и задаете мне вопрос в контексте, касающемся jQuery, и ответ, касающийся использования FormData? Извините, но я не думаю, что могу помочь вам там ...
Рафаэль Швейкерт

3
Это использует application/x-www-form-urlencoded. Есть ли способ использовать multipart/form-dataвместо этого?
Тимммм

4
@Timmmm Нет, он использует multipart/form-data. Использование application/x-www-form-urlencodedне будет работать.
Рафаэль Швейкерт

5
Боюсь, @RoyiNamir Это только документировано в коде .
Рафаэль Швейкерт

51

Просто хотел добавить немного к отличному ответу Рафаэля. Вот как заставить 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);
    }
});

... так $_FILESвыглядит массив PHP после использования этого 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.


Была такая же проблема с Safari. Спасибо за подсказку!
размышления

49

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

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

Это сработало идеально, и его очень просто реализовать.
Jh62

45

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

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

2
Я пытался заставить это работать, но он, казалось, не распознал serializefiles () как функцию, несмотря на то, что это определение находится в верхней части страницы.
Fallenreaper

1
это работает для меня просто отлично. получение данных с var data = $("#avatar-form").serializefiles();отправкой через параметр данных ajax и анализ с помощью выраженного огромного: form.parse(req, function(err, fields, files){спасибо за этот фрагмент кода :)
SchurigH

23

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

9

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

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

});

2

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

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

В контексте загрузки файла через fetchсовместимость смотрите: developer.mozilla.org/en-US/docs/Web/API/…
Омар Тарик,

@OmarTariq да у меня есть аналогичная ссылка в моем ответе))
Алексей Никулин

К сожалению. Как я могу пропустить это:-|
Омар Тарик

1

Класс 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"));

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


У меня была проблема с моей jquery-загрузкой ajax, молча зависавшей в Safari, и в итоге я сделал браузер условным $ ('form-name'). Submit () для Safari вместо загрузки ajax, которая работает в IE9 и FF18. Возможно, это не идеальное решение для мультизагрузки, но я делал это для отдельного файла в iframe из диалога jquery, чтобы все работало нормально.
глиф

1

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

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

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

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


1

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

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


1

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


0

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

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


0

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>


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

$ ("# id") [0] возвращает сначала ни одного пустого <input type = "file" /> формы, как вы отправляете всю форму, включая все <input type = "file" /> из нее?
Мохаммад-Хоссейн Джамали
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.