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

429

У меня проблема с отправкой файла на серверный PHP- script с помощью jQuery ajax-функции. Можно получить список файлов с $('#fileinput').attr('files'), но как можно отправить эти данные на сервер? Результирующий массив ($_POST) на сервере php- script равен 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);
    }
});
  • 0
    К сожалению, использование объекта FormData не работает в IE <10.
  • 0
    @GarciaWebDev предположительно вы можете использовать полифилл с Flash для поддержки того же API. Проверьте github.com/Modernizr/Modernizr/wiki/… для получения дополнительной информации.
Показать ещё 4 комментария
Теги:
file-upload
multipartform-data
form-data

12 ответов

785
Лучший ответ

Начиная с 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() .
  • 0
    Привет @ Рафаэль Швекерт, мы попробовали это, но, похоже, это не сработало ( stackoverflow.com/questions/10215425/… ). Где вы устанавливаете границу для multipart / form-data?
Показать ещё 26 комментариев
43

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

Используйте его, используя .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 () как функцию, несмотря на то, что это определение находится в верхней части страницы.
  • 1
    это работает для меня просто отлично. получение данных с помощью var data = $("#avatar-form").serializefiles(); отправив это через параметр данных ajax и проанализировав с помощью выраженного formidable: form.parse(req, function(err, fields, files){ спасибо за этот фрагмент кода :)
Показать ещё 1 комментарий
39

Просто хотел добавить немного к Рафаэлю отличный ответ. Здесь, как заставить 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 г.) edit: я удалил элемент формы из конструктора FormData(), который исправил этот код в Safari.)

Этот код выполняет две вещи.

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

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

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

Посмотрите на мой код, он выполняет эту работу для меня

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

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

Devin Venable answer был близок к тому, что я хотел, но я хотел, чтобы он работал с несколькими формами и использовал действие, уже указанное в форме, чтобы каждый файл попадет в нужное место.

Я также хотел использовать метод jQuery on(), чтобы избежать использования .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

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

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

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

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

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

0

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

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

0

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

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

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

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

-1
  • Получить объект формы с помощью jquery- > $( "# id" ) [0]
  • data = new FormData ($ ( "# id" ) [0]);
  • ok, данные - ваши пожелания.
  • 0
    $ ("# id") [0] возвращает первый ни одного пустого <input type = "file" /> формы, как вы отправляете всю форму, включая все <input type = "file" /> из нее?
-10

В качестве альтернативы AJAX вы можете добавить скрытый iframe в свой документ, скопировать туда свою форму и опубликовать ее (чтобы на вашей видимой странице не было перенаправления). Я думаю, вы могли бы удалить iframe впоследствии.

(HTML и JS - это технологии для хакеров, а не для программистов. Вам придется вырваться из этого... Это было... Бог знает, сколько лет исправления и "развития" JS и HTML, и вы все еще не могу сделать что-то настолько простое, не используя внешние библиотеки. Мне это надоело (я знаю о HTML5, просто этого недостаточно и не широко поддерживается))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню