Отправка 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.)
Этот код делает две вещи.
- Получает
input
Атрибут name автоматически делает HTML более понятным. Теперь, покаform
имеет класс putImages, обо всем остальном позаботится автоматически. Этоinput
не нужно иметь никакого специального имени. - Формат массива, который отправляет обычный 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
- получить объект формы с помощью jquery-> $ ("# id") [0]
- data = new FormData ($ ("# id") [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>