Subir un archivo con HTML5

Upload A File

HTML5 dentro de sus especificaciones ha actualizado al objeto XMLHttpRequest proporcionando nuevas características, de manera  que la última versión de este objeto se lo conoce como especificación XMLHttpRequest Level 2, podemos resumir sus ventajas en los siguientes puntos:

  • Subir y bajar archivos como flujo de bytes (stream bytes), archivos binarios de gran tamaño (BLOBs) o formularios de datos.
  • Nuevas capacidades para monitorear el progreso de operaciones gracias nuevos eventos.
  • Peticiones inter dominio (cross domain)

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>

Una imágen de este código en funcionamiento:

Update

Mucha gente ha estado pidiendo el CSS y el archivo de backend, la verdad es que no los había subido por dos razones:

  1. Hacer el CSS es lo más fácil de todo, si leíste, comprendiste e hiciste tu propia implementación de este código seguro que te es indiferente tener o no el CSS. Si por el contrario eres el tipo de persona que solo quiere pegar y copiar…jejeje…bueno probablemente no estés listo para programar aún 🙂
  2. El archivo de backend solo te sirve si utilizas el mismo stack que yo, de lo contrario no te sirve de mucho, de nuevo, si solo quieres pegar copiar…jejeje…aun te falta por aprender algunas cosas 🙂

Aquí están los tan solicitados archivos:

Esto es todo por esta entrada, espero le pueda resultar útil.

Algunos buenos libros que pueden ser de su interés:

IT professional with several years of experience in management and systems development with different goals within public and private sectors.

38 Comments

  1. Alex

    Hola Amigo, Necesito ver el código de la pagina url que recibe la petición de subir el archivo… Gracias

  2. 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 !

  3. jjuan

    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

    • jjuan

      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

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

  4. anndy

    Hola necesito saber cual es la direccion local del archivo que estor seleccionando me ayudas

  5. clau

    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

  6. Juanma

    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.

  7. Daniel Briones Reyes

    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.

  8. Re

    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.

  9. Rubén Muñoz Flores

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

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

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

  12. Carlos

    Podrias mostrar el archivo /ReadMoveWebServices/WSUploadFile.asmx/UploadFile

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

  14. rod

    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!

Leave Comment

Your email address will not be published. Required fields are marked *

%d bloggers like this: