El objeto XMLHttpRequest
El objeto XMLHttpRequest ha sido actualizado varias veces desde que fue definido como parte del esfuerzo WHATWG’s HTML utilizando la tecnología de Microsoft; entonces tuvimos la especificación original XMLHttpRequest Level 1 como parte de la W3C, también tuvimos la especificación actualizada XMLHttpRequest Level 2, y ahora tenemos la últma version de este documento conocido como XMLHttpRequest Living Specification. Podemo resumir sus ventajas en los siguientes puntos:
- Permite subir y bajar archivos como flujo de bytes (stream bytes), archivos binarios de gran tamaño (BLOBs) o formularios de datos.
- Tiene manejadores de eventos de progreso, errores, aborto, comienzo, y fin de operaciones.
- Peticiones inter dominio (cross-domain or CORS)
- Nuevos tipos de respuestas para JSON
- Es parte fundamental de la HTML5 File API specification
Es importante recalcar que antes de HTML5 y la nueva versión del objeto XMLHttpRequest se requería recurrir a tecnología de lado servidor para poder realizar una operación que permitiera subir un archivo, es decir no era posible subir un archivo nativamente del lado cliente. Tecnologías como AJAX y Flash hacían lo propio para tratar de hacer esto posible pero con serias limitaciones, por lo que XMLHttpRequest viene a cubrir este antiguo problema de gran manera. Existen otras características adicionales que acomapañan XMLHttpRequest Level 2 , si desea conocer más puede recurrir a la especificación oficial.
La mayoría de las veces utilizo código y comentarios en inglés, espero que esto no le resulte demasiado inconveniente, si tiene alguna duda la contestaré a la brevedad.
Comenzando
Lo primero que haremos es definir la interfaz de usuario para esta pequeña implementación comenzando por las etiquetas HTML, el código es muy sencillo y solo contempla algunos elementos de formulario, y algunos div que solo sirven para dar una mejor presentación auxiliandose de CSS3. No analizaré en este post lo que respecta a las hojas de estilo utilizadas ya que no es algo necesario para el funcionamiento del ejemplo.
<!DOCTYPE html> <html> <head> <title>Upload File</title> <meta charset="iso-8859-1" /> </head> <body> <div id="wrap"> <div class="field"> <ul class="options"> <li> <input type="file" id="myfile" name="myfile" class="rm-input" onchange="selectedFile();"/></li> <li> <div id="fileSize"></div></li> <li> <div id="fileType"></div></li> <li> <input type="button" value="Subir Archivo" onClick="uploadFile()" class="rm-button" /></li> </ul> </div> <progress id="progressBar" value="0" max="100" class="rm-progress"></progress> <div id="percentageCalc"></div> </div> </body> </html>
El código anterior se explica casi por si solo, pero resumamos lo que tiene:
- Un input de tipo file que servirá para seleccionar el archivo que se desa subir.
- Un div que servirá para imprimir el tamaño del archivo seleccionado.
- Un div que servirá para imprimir el tipo MIME del archivo seleccionado.
- Un botón que disparará el proceso para subir el archivo elegido.
- Una barra que indicará el progreso en el proceso de subida del archivo (nuevo elemento HTML5).
- Finalmente un div donde se mostrará el progreso en formato de porcentaje.
La función selectedFile()
Cada vez que selecciona un archivo con el elemento file, también dispara al evento onchange el cual llama a la función selectedFile(). En esta función suceden cosas interesantes, para empezar se hace referencia a una colección de archivos instanciada por un objeto nuevo en HTML5 llamado FileList, los objetos que obtenemos como miembros de las lista de FilesList son objetos File. En este caso obtendremos las propiedades size y type desde el objeto File recuperado.
Aprovenchando la información que proporciona la propiedad size, dentro de la función se calcula y se muestra en megabytes o kilobytes el tamaño del archivo que se ha seleccionado. Con la propiedad type se obtiene el tipo MIME del archivo seleccionado, el cual se muestra en el div correspondiente.
function selectedFile() { var archivoSeleccionado = document.getElementById("myfile"); var file = archivoSeleccionado.files[0]; if (file) { var fileSize = 0; if (file.size > 1048576) fileSize = (Math.round(file.size * 100 / 1048576) / 100).toString() + ' MB'; else fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + ' Kb'; var divfileSize = document.getElementById('fileSize'); var divfileType = document.getElementById('fileType'); divfileSize.innerHTML = 'Tamaño: ' + fileSize; divfileType.innerHTML = 'Tipo: ' + file.type; } }
La función uploadFile()
Esta es la función que hace un mayor uso de las nuevas posibilidades de XMLHttpRequest , y es la que se encargará de disparar el proceso principal del lado cliente.
function uploadFile(){ //var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile"; var url = "/ReadMoveWebSite/UploadMinimal.aspx"; var archivoSeleccionado = document.getElementById("myfile"); var file = archivoSeleccionado.files[0]; var fd = new FormData(); fd.append("archivo", file); var xmlHTTP = new XMLHttpRequest(); //xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false); xmlHTTP.upload.addEventListener("progress", progressFunction, false); xmlHTTP.addEventListener("load", transferCompleteFunction, false); xmlHTTP.addEventListener("error", uploadFailed, false); xmlHTTP.addEventListener("abort", uploadCanceled, false); xmlHTTP.open("POST", url, true); //xmlHTTP.setRequestHeader('book_id','10'); xmlHTTP.send(fd); }
Inicialmente tenemos una variable url que usaremos para indicar donde está la página o servicio web que recibirá la petición de esta página para hacer el proceso indicado en el servidor; enseguida tal y como se hizo en la funcion selectedFile() se hace referencia al objeto File miembro FileList obtenido.
En la cuarta línea hay algo novedoso y muy útil, me refiero al objeto FormData, este objeto permite instanciar vía JavaScript un formulario web, es decir, es como si usted colocara con etiquetas HTML un formulario, o bien puede hacer referencia a uno ya existente asignándolo a un objeto FormData. Sin duada esto es de gran ayuda ya que significa que ahora usted puede crear un formulario y alterar los valores que envía de manera dinámica. Para adjuntar valores a un formualrio instanciado o referenciado con FormData se utiliza el método append(archivo, objeto), de esta manera en la quinta línea se agrega nuestro objeto File con el nombre de archivo.
Este es el fragmento de la función que abarca lo planteado:
//var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile"; var url = "/ReadMoveWebSite/UploadMinimal.aspx"; var archivoSeleccionado = document.getElementById("myfile"); var file = archivoSeleccionado.files[0]; var fd = new FormData(); fd.append("archivo", file);
Manejadores de eventos
Continuando con el resto de la función, podemos observar que finalmente instancía el objeto XMLHttpRequest y se asigna a la variable xmlHTTP, enseguida procedemos a la siguiente novedad, me refiero a la posibilidad de crear los nuevos eventos que forman parte de XMLHttpRequest Level 2 gracias al objeto upload. Los eventes que se agregan en este caso son:
- loadstart. Evento que se dispara cuando inicia el proceso para subir el archivo.
- progress. Evento que se dispara cada vez que hay un avance en el proceso que sube el archivo.
- load. Evento que se dispara cuando la transferecia se completa.
- error. Se dispara si el proceso falla con error explícito.
- abort. Se dispara si el usuario interrumpe o la conexión de interrumpe.
No son los único eventos disponibles, consulte la especificación oficial para mayor información.
Los manejadores de eventos se declaran en el siguiente código:
var xmlHTTP= new XMLHttpRequest(); //xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false); xmlHTTP.upload.addEventListener("progress", progressFunction, false); xmlHTTP.addEventListener("load", transferCompleteFunction, false); xmlHTTP.addEventListener("error", uploadFailed, false); xmlHTTP.addEventListener("abort", uploadCanceled, false);
Las funciones que se llaman en cada evento son las siguientes:
function progressFunction(evt){ var progressBar = document.getElementById("progressBar"); var percentageDiv = document.getElementById("percentageCalc"); if (evt.lengthComputable) { progressBar.max = evt.total; progressBar.value = evt.loaded; percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%"; } } function loadStartFunction(evt){ alert('Comenzando a subir el archivo'); } function transferCompleteFunction(evt){ alert('Transferencia completa'); var progressBar = document.getElementById("progressBar"); var percentageDiv = document.getElementById("percentageCalc"); progressBar.value = 100; percentageDiv.innerHTML = "100%"; } function uploadFailed(evt) { alert("Hubo un error al subir el archivo."); } function uploadCanceled(evt) { alert("La operación se canceló o la conexión fue interrunpida."); }
La función progressFunction es la que actualiza tanto la barra de estado como el porcentaje que indican de manera gráfica y numérica el avance del proceso, el resto de las funciones únicamente despliegan un mensaje apropiado para el caso.
Código comentado
Si ha observado el código presentado habrá notado algunas líneas comentadas, eso es debido a que este es el código base para hacer algo un poco más complejo, pero decidí dejar esas líneas porque pueden ser una referencia útil para alguien:
//var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile";
La línea anterior es una llamada a un servicio HTTP .Net en lugar de a una página.
//xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false);
Esta línea llama a una función que muestra un mensaje cuando inicia el proceso, la cual comenté porque después de ejecuar varias veces el código me pareció molesto.
El código completo
Así luce la implementación completa del código, no describo el código CSS3 ya que es irrelevante en lo que respecta a la funcionalidad, pero comparto una imágen que muestra como se ve ejecutándose:
<!DOCTYPE html> <html> <head> <title>Upload File</title> <meta charset="iso-8859-1" /> <link rel="stylesheet" type="text/css" href="estilosUploadFile.css" /> <script type="text/javascript"> function selectedFile() { var archivoSeleccionado = document.getElementById("myfile"); var file = archivoSeleccionado.files[0]; if (file) { var fileSize = 0; if (file.size > 1048576) fileSize = (Math.round(file.size * 100 / 1048576) / 100).toString() + ' MB'; else fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + ' Kb'; var divfileSize = document.getElementById('fileSize'); var divfileType = document.getElementById('fileType'); divfileSize.innerHTML = 'Tamaño: ' + fileSize; divfileType.innerHTML = 'Tipo: ' + file.type; } } function uploadFile(){ //var url = "http://localhost/ReadMoveWebServices/WSUploadFile.asmx?op=UploadFile"; var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile"; var archivoSeleccionado = document.getElementById("myfile"); var file = archivoSeleccionado.files[0]; var fd = new FormData(); fd.append("archivo", file); var xmlHTTP= new XMLHttpRequest(); //xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false); xmlHTTP.upload.addEventListener("progress", progressFunction, false); xmlHTTP.addEventListener("load", transferCompleteFunction, false); xmlHTTP.addEventListener("error", uploadFailed, false); xmlHTTP.addEventListener("abort", uploadCanceled, false); xmlHTTP.open("POST", url, true); //xmlHTTP.setRequestHeader('book_id','10'); xmlHTTP.send(fd); } function progressFunction(evt){ var progressBar = document.getElementById("progressBar"); var percentageDiv = document.getElementById("percentageCalc"); if (evt.lengthComputable) { progressBar.max = evt.total; progressBar.value = evt.loaded; percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%"; } } function loadStartFunction(evt){ alert('Comenzando a subir el archivo'); } function transferCompleteFunction(evt){ alert('Transferencia completa'); var progressBar = document.getElementById("progressBar"); var percentageDiv = document.getElementById("percentageCalc"); progressBar.value = 100; percentageDiv.innerHTML = "100%"; } function uploadFailed(evt) { alert("Hubo un error al subir el archivo."); } function uploadCanceled(evt) { alert("La operación se canceló o la conexión fue interrunpida."); } </script> </head> <body> <div id="wrap"> <div class="field"> <ul class="options"> <li> <input type="file" id="myfile" name="myfile" class="rm-input" onchange="selectedFile();"/> </li> <li> <div id="fileSize"></div></li> <li> <div id="fileType"></div></li> <li> <input type="button" value="Subir Archivo" onClick="uploadFile()" class="rm-button" /></li> </ul> </div> <progress id="progressBar" value="0" max="100" class="rm-progress"></progress> <div id="percentageCalc"></div> </div> </body> </html>
No descibo el CSS3 porque es irrelevante en términos de funcionalidad, pero comparto una imagen que muestra como luce la implementación en el navegador y un enlace al CSS3 estilosUploadFile.zip.
También comparto el HTTP service que utilicé para este ejemplo (el código de lado servidor, archivo de backend o cualquier otro nombre que ud. prefiera 😃) pero esto no será de mucha ayuda amenos que utilice exactemente el mismo stack que yo utilicé en su momento. En otras palabras, si usted es el tipo de personas que solo quiera copiar y pegar….jejeje bueno…quizá no está listo para hace esto todavía. Aquí está el tan solicitado archivo WSUploadFile.zip
Es todo por ahora amigos, espero que esto les sea de utilidad.
Algunos buenos libros que pueden ayudarle en su viaje por HTML5:
Emmanuel Herrera
IT professional with several years of experience in management and systems development with different goals within public and private sectors.
Emmanuel worked through development and management layers, transitioning from developer and team development leader to Project Manager, Project Coordinator, and eventually to Scrum Master, Product Owner, and Agile Coach.
Some certifications include: PSM, PSPO, SSM.
Hola Amigo, Necesito ver el código de la pagina url que recibe la petición de subir el archivo… Gracias
Este es el archivo original que utilicé para este ejemplo, pero el código no está simplificado, aunque en realidad es simple:
https://dl.dropboxusercontent.com/u/247486/WSUploadFile.asmx.cs
En este caso es un archivo desarrollado con .net, pero desde luego se puede hacer el equivalente con otras tecnologías.
Puedes Dejarme el Css tambien? solo para ver lo que hiciste 🙂
Amigo podrias poner el codigo de la clase respectiva (ej. subirArchivo.aspx.cs) de tu archivo .aspx?? por favor, para ver como mandas llamar el archivo WSUploadFile.asmx.cs. De antemano gracias.
No existe un archivo .aspx
Me pasas tu facebook asi te paso unos codigo ? ?No quiero postiarlo en publico… tendrias que tener facebook…. :/ me extraña… jaja.Espero un correo o el facebook lo antes posible. gracias !
Jejeje, sin ofender amigo, pero porqué querría un código que no quieres compartir, cumplir con requisitos y además pronto?
Si lo mismo pense cuando vi el otro comentario, parace que esta dando ordenes, hay modo de pedir las cosas pero lo mas tonto fue cuando dijo “lo antes posible” como para decirle, si patron, alguna otra cosa?
Jejeje habemos gente de todo tipo 🙂
buenas noches, no tengo ni idea de si lo que voy a decir es correcto o no pero aun asi me atrevo, soy un inculto en esto de la programacion pero necesito poder poner un codigo que haga la subida de datos pero que me lo envie a la vez de rellenar un formulario al mismo correo electronico que recibira el citado formulario
es posible????
gracias y espero me puedan ayudar
ruego me respondan y si pueden me incluyan el codigo para poder pegarlo.
seria lo maximo, no me queda mas que eso para poder tener lo que necesito y se que para una persona que hace esto a diario sera una chorrada de facil pero para mi es imposible
agradezco el tiempo que me dediquen y que me ayuden
un saludo
Lo siento amigo, pero desafortunadamente lo que pides no se resuelve con un simple “copy-paste” y mucho menos sin saber programar.
Sí, desde luego que es posible, sin embargo ahí requieres de contar con servidor de correo y (lo más fácil) con con una tecnología de sevidor que sirva como intermediario entre tu formulario y el servidor de correo, como por ejemplo PHP, ASP.NET
Hola tengo una duda con la url para subir el archivo me podrías explicar si esa url debe ser la de mi host? o mejor darme ejemplos por que es lo único que no he podido entender a donde debe ir direccionada esa url?
La respuesta corta es, sí, sin embargo para ser más preciso la URL es el código del lado servidor que recibirá el envío del archivo y lo guardará donde tu quieres. En el ejemplo utilicé asp.net 3.5 (que era lo que tenía disponible al momento), pero puedes usar la tecnología de servidor de tu preferencia y de esto dependerá la lógica con la cual se procese el archivo recibido para guardarlo donde desees.
sigo sin entender :(,
Requieres aprender asp, php, jsp, node.js o algo similar primero, de lo contrario seguirás batallando
Hola necesito saber cual es la direccion local del archivo que estor seleccionando me ayudas
hola muchas gracias por este aporte y tus explicaciones, quisiera preguntarte que debo cambiar para subir archivos a una carpeta en mi sitio? desde ya muchas gracias
Del lado cliente nada, donde deberías especificar el path donde se guarda la información es en tu código del lado servidor.
Hola. Me parece muy interesante cómo se hace el proceso, pero no sé cómo leer lo que se envía (función uploadFile) desde un Servlet de java para desde allí poder guardar la imagen en base de datos. ¿Alguien podría decirme cómo leerlo o ponerme un ejemplo? Muchas gracias.
muy buen tutorial, lo probe y funciona excelente, me surgió la inquietud de llevarlo al siguiente nivel para combinarlo con otra funcionalidad HTML5 que es la de file multiple, me imagino habra que leer el archivo y crear un array para subir cada archivo o crear una especie de replicas individuales para subir uno por uno y conservar el código del tutorial el primer camino es mas elegante pero implica mas dominio de javascript y el segundo respeta todo el código mas crear una función extra que cree estructuras input, divs y barra de estado por archivo, tienes pensado ampliar en un futuro este tutorial? saludos.
Gracias por tu comentario. La respuesta pronta es: sí lo he pensado, sí lo quiero hacer, solo tengo que encontrar un tiempo para hacerlo. :p
hola, qué tal, la verdad no sé nada de programación, pero quiero hacer un curso online interactivo, de hecho ya lo hice y está en PowerPoint entonces, después de descargar millones de programas encontré uno que lo pasa a html5 y parece que es mucho mejor que flash, porque se ve en todas partes (dispositivos) el programa (html5 point) me hizo una versión para computadora y me permite ver cómo quedaría en Android y en Apple, igual que en la computadora, pero… no sé cómo se puede subir a internet si lo puedo poner en un blog o algo…. Agradecería mucho tu ayuda.
Que tal? para ser honesto lo que preguntas no tiene nada que ver con el tema que trata este post; no lo tomes a mal, pero para hacer algo tan básico como lo que mencionas solo tienes que hacer una o dos búsquedas en Google, te sugiero que busques información sobre como publicar contenido en un hosting gratuito o de pago o incluso sobre algún LMS, suerte.
Hola Emmanuel. Me interesa mucho el código que muestras aquí. Lo he tratado de implementar en una web ASP.net con VB y funciona muy bien en el lado del cliente (con la barra y todo) pero no me sube los archivos al servidor. Por lo que voy entendiendo debo colocar código también en el code behind, cierto?, el tema es que no sé que se coloca en ese archivo, me podrias guiar un poco. Y lo otro, la url de la funcion uploadfile debe ser la dirección a la pagina del code behind??. Te agradecería mucho me pudieras orientar un poco. Gracias de antemano..
Hola Emmanuel. He implementado tu código en una web asp.net. Funciona muy bien la parte visual de cara al usuario, se da el mensaje de que se subió el archivo pero en realidad no sube el archivo. Me imagino que será por que debo agregar código al code behind cierto???, si es así me podrías dar un ejemplo de lo que se debe especificar. Estoy acostumbrado a programar con controles asp pero esto del javascript es nuevo, por eso mi pregunta. Muchas gracias de antemano!
Qué tal? Primero tengo que aclararte algo, este ejemplo no es para hacer un copy-paste de los archivos o del código y que todo funcione automáticamente, esto solo sucedería si usaras exactamente el mismo stack que yo utilicé, el ejemplo pone la lógica base, pero ciertamente lo que comentas que te imaginas es básicamente correcto, lo que llamas “code behind” es lo que cada quien debe hacer del LADO SERVIDOR correspondiente al framework que esté utilizando en su web server. Esto quiere decir que si alguien está utilizando PHP (por ejemplo), debe hacer su propia versión equivalente del archivo “WSUploadFile.asmx”. Desde luego aunque esto es fácil, exige saber programar un poco (no todo es copy-paste en la vida). Suerte, saludos.
Emmanuel muchas gracias por tu respuesta. Me imaginaba que por ahí estaba la solución. He estado buscando info acerca del codebehind para este caso pero no he encontrado mucho, por que el caso es bastante particular. Hay mucha información de como implementar botones de subida con JQery y Ajax, pero lo que tu muestras es distinto. He visto que, como existen funciones de javascript , el codebehin tiene sentencias distintas a lo habitual en VB (que es mi caso). Si no fuera mucha la molestia podrías mostrarnos el codebehind de tu ejemplo, o de otro ejemplo ficticio… A mi me resulta mucho mas fácil entender la programación con ejemplos prácticos. De verdad te lo agradecería mucho. Un saludo
Hola Emmanuel. Me ha interesado mucho el código que has mostrado. He tratado de implementarlo en una web ASP.net pero no me sube los archivos (aunque funciona la barra de progreso y me avisa que se ha completado la carga). Nunca he trabajado con javascript y evidentemente debo adaptar el código a mi web. Me imagino que debo incluir algo en el codebehind (aspx.vb) cierto??, me podrías guiar un poco, si es que es así. Me refiero a las sentencias que se deben especificar,. Y mi otra gran duda es la URL que está en la función uploadfile() ¿Que debo colocar ahi??, Agradecería muchísimo tu ayuda. Saludos a todos
Que tal amigo, puedes publicar el css por favor. Saludos y gracias
Podrias mostrar el archivo /ReadMoveWebServices/WSUploadFile.asmx/UploadFile
Por alguna razón cada cierto tiempo me tiran el archivo, pero el enlace está en la parte final del post (SubirArchivo_CSS&WebService.zip)
excelente ejemplo y muy bien explicado, sería bueno que lo complementaras, con asp o java, la herramienta que subiste tiene tiene mucho potencial. saludos
Gracias, solo una aclaración que te podría servir, al final del post está un enlace que incluye el archivo .asmx, eso es un Web Service de .Net en el lado servidor. Técnicamente un .asmx es casi .aspx, pero usar .asp o un .aspx explícitamente, resulta innecesario y obsoleto a estas alturas. Aunque efectivamente del lado servidor cada quien puede usar lo que mejor le convenga ya que las opciones son muchas. Saludos.
Este código está muy bueno.
Me estoy actualizando a Html 5 y la verdad es que sorprende todo los añadidos que le han hecho.
Dime una cosa si es posible. Por favor
Como se puede hacer para subir archivos no de uno en uno sino por lotes.
Te lo agrezco.
Gracias
Gracias por tu comentario.
Esta es una muy buena pregunta, que me da el pretexto para hacer un poco de historia :). De lado de HTML5 es muy fácil solo tienes que utilizar el atributo multiple, la manera más correcta de hacerlo es (sin minimizaciones):
< input type="file" name="files" multiple="multiple" >
Menciono lo de hacer historia porque el atributo existe desde hace mucho tiempo en HTML5, pero hasta hace relativamente poco tiempo los navegadores lo comenzaron a soportar, porque en el pasado había riesgos de hackeo y spamming de archivos (imagina que alguien te manda miles de archivos al mismo tiempo de un solo click y te tumba el servidor), pero parece que esto ya se ha resuelto. Sin embargo también hay que hacer ajustes de lado servidor. En mi caso, como estoy utilizando un .asmx (un web service de .Net) tendría que serializar la info enviada desde el lado cliente de alguna manera, algo muy similar debe hacerse con la tecnología servidor que tú utilices. Creo que trabajaré en ese ejemplo próximamente, saludos!
NO ME FUNCIONA ,EL PROBLEMA:
No me funciona, mira lo que hice:
1- En la varieble (url) puse la dirección del servidor apuntando a una carpeta donde suponía que guardaría el archivo .
Puse esta url: http://192.168.43.1:8000/NEW/.
2-Luego dejé el código siguiente igual:
fd.append(“archivo”,file)
Osea, solo combie la url, todo lo demás está idéntico, y no me pasa el archivo a esa carpeta (la carpeta NEW).
Que ocurre ???
Jejeje lee el último párrafo del post compañero.