Introducción
Enviar datos con AJAX es algo relativamente fácil; pues al final de todo son simples datos. Ya sean cadenas o números, todo es texto. Pero algo distinto pasa cuando queremos cargar un archivo o fichero con JavaScript hacia PHP.
Esto es más complicado cuando queremos hacerlo con AJAX, pues no hay una forma estandarizada de enviar un archivo; y nos topamos con que el usuario podría seleccionar archivos pesados.
Lo que nos quedaría sería poner un formulario y agregar un <input type="file">
pero nuestra página perdería dinamismo.
Afortunadamente desde hace algunos años existe la API de FormData, la cual ofrece una manera de enviar todo tipo de datos como se enviarían en un formulario; con la ventaja de poder hacerlo sin interrumpir al usuario o recargar la página.
Subir archivo a PHP con JavaScript + FormData
Esto será muy, muy simple y fácil de hacer. Lo que haremos será subir un archivo de cualquier tipo con JavaScript y guardarlo con PHP. No nos fijaremos en la seguridad o esas cosas.
Todo eso será usando FormData
y fetch, el remplazo nativo de XMLHttpRequest
. Por cierto, para esto debes tener configurado e instalado un servidor con PHP.
Ya habíamos hecho algo similar pero usando jQuery y CodeIgniter; ahora usaremos JavaScript y PHP sin frameworks.
El lado del cliente
Sólo necesitamos poner el input
de tipo file
para tomar el archivo y para darle al usuario una interfaz para que pueda seleccionarlo. Quedaría así:
<input id="inputFile" type="file">
<br><br>
<button id="btnEnviar">Enviar</button>
También agregamos un botón que será el encargado de enviar el archivo. Estos dos elementos los tomamos en el script:
const btnEnviar = document.querySelector("#btnEnviar");
const inputFile = document.querySelector("#inputFile");
Necesitamos a ambos. btnEnviar
para escuchar su click y realizar la lógica necesaria; inputFile
para obtener los archivos que el usuario ha elegido.
Entonces, en el click del botón agregamos esto:
btnEnviar.addEventListener("click", () => {
if (inputFile.files.length > 0) {
let formData = new FormData();
formData.append("archivo", inputFile.files[0]); // En la posición 0; es decir, el primer elemento
fetch("guardar.php", {
method: 'POST',
body: formData,
})
.then(respuesta => respuesta.text())
.then(decodificado => {
console.log(decodificado);
});
} else {
// El usuario no ha seleccionado archivos
alert("Selecciona un archivo");
}
});
Lee sobre las funciones flecha aquí si no entiendes eso de () =>
.
Primeramente comprobamos si hay archivos cargados. Los mismos son representados como un arreglo, así que usamos la propiedad length
. Si no hay archivos, le avisamos al usuario con un alert
.
Lo que estamos haciendo es crear un objeto de tipo FormData
.
Le agregamos un elemento (podrían ser miles más, y no todos tienen que ser archivos, podrían ser cadenas o números) con la clave “archivo” justo en donde hacemos formData.append("archivo", inputFile.files[0])
.
Es importante entender que esta clave servirá más tarde para leer el archivo del lado del servidor. Le estamos agregando lo que haya en el arreglo de archivos en la posición 0; es decir, el primer elemento.
Más tarde hacemos una petición con fetch
, la cual es de tipo POST.
Como cuerpo enviamos el objeto formData
. Cuando la promesa se resuelva decodificamos la respuesta como texto y cuando esta segunda promesa se resuelva imprimimos el contenido; en resumen simplemente estamos imprimiendo lo que el servidor dijo.
Aquí termina el lado del cliente.
Lado del servidor con PHP
No sé si es coincidencia, pero siempre el código del lado del servidor es más corto. Supongo que ha de ser porque no validamos ni hacemos tantas cosas como en el lado del cliente.
Pues bien, nuestro archivo estará en el arreglo superglobal de PHP $_FILES
; en la posición clave, indicada por el nombre que le pusimos en el append
de JavaScript.
Como le pusimos “archivo”, aquí también accederemos a “archivo” así:
<?php
$archivo = $_FILES["archivo"];
Pero sólo estamos obteniendo su información; porque cuando lo subimos se mueve a una carpeta temporal.
Para guardarlo en el disco duro, o mejor dicho, en donde está el script, usamos la función move_uploaded_file que quiere decir algo como mover_archivo_subido.
<?php
$resultado = move_uploaded_file($archivo["tmp_name"], $archivo["name"]);
Este toma dos argumentos. El primero indica en dónde está el archivo original, y esa ruta no la sabemos pero PHP sí, y la puso en $archivo["tmp_name"]
.
El segundo argumento indica en dónde pondremos el archivo. Aquí, por simplicidad, lo estoy guardando con su nombre original aunque no recomiendo hacerlo si no confías en el usuario final.
Si queremos, podemos cambiar el segundo argumento por un nombre generado por nosotros. Se me ocurre usar uniqid y concatenarlo con la extensión del archivo, la cual podemos obtener con pathinfo.
La función que mueve el archivo devuelve un booleano. Dependiendo de lo que devuelva imprimimos un mensaje, y eso será lo que reciba el cliente como respuesta.
<?php
$archivo = $_FILES["archivo"];
$resultado = move_uploaded_file($archivo["tmp_name"], $archivo["name"]);
if ($resultado) {
echo "Subido con éxito";
} else {
echo "Error al subir archivo";
}
Probando código
Aquí dejo un GIF:
En él se puede observar que arrastramos un archivo (también podríamos simplemente pulsar el botón de Seleccionar archivo).
La carga es demasiado rápida, y el archivo aparece copiado a la carpeta en donde está el script.
Código final
Lo dejo en GitHub.
como puedo usar formadata pero además de la imagen estoy enviando otros datos de inputos de texto, que tendría que hacer?
Si tiene dudas específicas puede hacerlas en https://parzibyte.me/#contacto
¿Y si tengo dos input distintos, uno para subir una imagen y otro para subir pdf? No doy con la forma de capturar ambos archivos…lo he intentado añadiendo ambos archivos a un mismo formdata pero no consigo nada útil. La cosa es que quiero presionar un boton y todos esos archivos se suban a su lugar correspondiente.
Hola. Para consultas personalizadas puede escribirme en https://parzibyte.me#contacto
Saludos!
Puedes hacer uso de un bucle para subir ambos archivos junto a la propiedad “multiple” del input file.
Pingback: Grabar audio de micrófono con JavaScript y PHP - Parzibyte's blog