Tengo un problema al enviar un archivo a un script PHP del lado del servidor usando la función ajax de jQuery.
Es posible obtener la lista de archivos con $('#fileinput').attr('files')
pero ¿cómo es posible enviar estos datos al servidor? El array resultante ($_POST
) en el php-script del lado del servidor es 0 (NULL
) cuando se utiliza el file-input.
Sé que es posible (aunque no he encontrado ninguna solución jQuery hasta ahora, sólo código Prototye (http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html)).
Esto parece ser relativamente nuevo, así que por favor no mencione que la carga de archivos sería imposible a través de XHR/Ajax, porque definitivamente funciona.
Necesito la funcionalidad en Safari 5, FF y Chrome estarían bien pero no son esenciales.
Mi código por ahora es:
$.ajax({
url: 'php/upload.php',
data: $('#file').attr('files'),
cache: false,
contentType: 'multipart/form-data',
processData: false,
type: 'POST',
success: function(data){
alert(data);
}
});
A partir de Safari 5/Firefox 4, lo más fácil es utilizar la clase FormData
:
var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function(i, file) {
data.append('file-'+i, file);
});
Así que ahora tienes un objeto FormData
, listo para ser enviado junto con el 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);
}
});
Es imperativo que establezcas la opción contentType
a false
, forzando a jQuery a no añadir una cabecera Content-Type
por ti, de lo contrario, la cadena límite no aparecerá en ella.
Además, debe dejar la bandera processData
establecida en false, de lo contrario, jQuery tratará de convertir su FormData
en una cadena, lo cual fallará.
Ahora puede recuperar el archivo en PHP usando:
$_FILES['file-0']
(Sólo hay un archivo, file-0
, a menos que haya especificado el atributo multiple
en su entrada de archivo, en cuyo caso, los números se incrementarán con cada archivo).
Utilizando la emulación de FormData para navegadores antiguos
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);
Crear FormData a partir de un formulario existente
En lugar de iterar manualmente los archivos, el objeto FormData también puede ser creado con el contenido de un objeto formulario existente:
var data = new FormData(jQuery('form')[0]);
Utilizar un array nativo de PHP en lugar de un contador
Simplemente nombra los elementos de tu archivo igual y termina el nombre entre paréntesis:
jQuery.each(jQuery('#file')[0].files, function(i, file) {
data.append('file[]', file);
});
$_FILES['archivo']
será entonces un array que contendrá los campos de subida de archivos para cada archivo subido. En realidad, recomiendo esto sobre mi solución inicial, ya que es más simple para iterar sobre.
Sólo quería añadir un poco a la gran respuesta de Raphael. Aquí'es cómo conseguir que PHP produzca el mismo $_FILES
, independientemente de si se utiliza JavaScript para enviar.
Formulario 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 produce este $_FILES
, cuando se envía sin JavaScript:
lenguaje: lang-none -->
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
)
)
)
Si haces una mejora progresiva, usando Raphael's JS para enviar los archivos...
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);
}
});
... este es el aspecto de la matriz $_FILES
de PHP, después de usar ese JavaScript para enviar:
idioma: lang-none -->
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
)
)
Eso'es una bonita matriz, y en realidad lo que algunas personas transforman $_FILES
en, pero me parece que'es útil para trabajar con el mismo $_FILES
, independientemente de si se utilizó JavaScript para enviar. Por lo tanto, aquí hay algunos cambios menores a la 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);
});
(Edición del 14 de abril de 2017: he eliminado el elemento del formulario del constructor de FormData() -- eso ha arreglado este código en Safari).
Ese código hace dos cosas.
input
name automáticamente, haciendo el HTML más mantenible. Ahora, mientras el formulario
tenga la clase putImages, todo lo demás se hace automáticamente. Es decir, el input
no necesita tener ningún nombre especial.Con estos cambios, el envío con JavaScript produce ahora exactamente el mismo array $_FILES
que el envío con HTML simple.
Acabo de construir esta función basándome en una información que he leído.
Utilízala como si usaras .serialize()
, en su lugar sólo pon .serializefiles();
;
Funcionando aquí en mis pruebas.
//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);