Надсилання багаточастинних / форматних даних за допомогою jQuery.ajax


563

У мене виникла проблема з надсиланням файлу на сервер PHP-скрипт за допомогою функції aqx 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);
    }
});

На жаль, використання об'єкта FormData не працює на IE <10.
Алехандро Гарсія Іглесіас

@GarciaWebDev нібито ви можете використовувати полі-заливку з Flash для підтримки того ж API. Ознайомтеся з github.com/Modernizr/Modernizr/wiki/… для отримання додаткової інформації.
yuxhuang


3
Ви можете використовувати $(':file')для вибору всіх вхідних файлів. Це просто трохи простіше.
Шахар

@RameshwarVyevhare Ця відповідь була опублікована через п’ять років після відповіді на це питання. Будь ласка, не тролюйте подібні запитання лише для просування власних відповідей.
Ryan P

Відповіді:


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замість цього?
Timmmm

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
                )

        )

)

Якщо ви робите прогресивне вдосконалення, використовуючи JS Raphael для подання файлів ...

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 для подання. Отже, ось деякі незначні зміни до СВ:

// 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.


Мав те саме питання із Safari. Дякую за підказку!
medoingthings

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

Відповідь Девіна Венабле була близькою до того, що я хотів, але я хотів, щоб він працював у кількох формах, і використовувати дію, вже вказану у формі, щоб кожен файл пішов у потрібне місце.

Я також хотів використовувати метод 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.
    });

});

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 .

Отже, додайте порожню форму десь на своїй сторінці, вказавши ентетип:

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

Потім створіть об’єкт FormData як:

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

і продовжуйте, як у коді Рафаеля .


У мене виникла проблема з завантаженням jquery ajax, який мовчки висить у Safari, і в кінцевому підсумку робив браузер умовно $ ('форма-ім’я'). Submit () для Safari замість завантаження ajax, що працює в IE9 та FF18. Можливо, це не ідеальне рішення для декількох завантажень, але я робив це для одного файлу в кадр iframe з діалогу jquery, щоб він працював нормально.
гліф

1

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

Наприклад, я публікував повідомлення на http://server.com/context?param=x

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

Під час переадресації багатопартійний заголовок був втрачений. Переконайтеся, що запити не переспрямовуються, якщо ці рішення не працюють для вас.


1

Всі рішення, наведені вище, виглядають добре і елегантно, але об'єкт FormData () не очікує жодного параметра, але використовує додавання () після його інстанції, як те, що було написано вище:

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 = новий FormData ($ ("# id") [0]);
  3. добре, дані - це ваше бажання

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