Utilizar ficheros desde aplicaciones web
========================================

El uso de la API File añadida al DOM en HTML5, ahora hace posible que la página web solicite al usuario que seleccione archivos locales y, a continuación, leer el contenido de esos archivos. Esta selección se puede hacer, ya sea usando un elemento `<input>` de HTML o mediante arrastrar y soltar.

Selección de ficheros utilizando HTML
-------------------------------------

Seleccionar solo un fichero usando la API File es sencillo:
```

<input type="file" id="input" onchange="handleFiles(this.files)">
```


Cuando el usuario elige el fichero, la función `handleFiles()` es llamada con un objeto `FileList`el cual a su vez contiene un objeto `File` que representa el fichero elegido por el usuario.

Si se prefiere permitir al usuario elegir varios ficheros, entonces se pone el atributo `multiple` en el elemento `input:`
```

<input type="file" id="input" multiple="true" onchange="handleFiles(this.files)">
```


En este caso, la lista de ficheros pasada a la función `handleFiles()` contiene un objeto `File` por cada fichero seleccionado por el usuario.

Comenzando en Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1), se puede ocultar el realmente feo `<input>` y representar tu propio interfaz para abrir el picker de ficheros y pintar que fichero o ficheros ha seleccionado el usuario. Esto se puede hacer añadiendo al elemento input la característica "display:none" en su css, o estilo y llamando al método `click()` del elemento `<input>`.

Como aparece en este HTML:
```

<form>
  <input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
</form>

<a href="javascript:doClick()">Select some files</a>
```


Tu método `doClick()` puede parecerse al siguiente:
```

function doClick() {
  var el = document.getElementById("fileElem");
  if (el) {
    el.click();
  }
}
```


Obviamente puedes cambiar como quieras, el estilo del botón para abrir el picker de ficheros.

### Añadir dinámicamente un listener tipo change

Si tu campo input ha sido creado usando una libreria JavaScript como jQuery, se necesitará usar `element.addEventListener()` para añadir el manejador de eventos tipo change, como en el ejemplo siguiente:
```

var inputElement = document.getElementById("inputField");
inputElement.addEventListener("change", handleFiles, false);

function handleFiles() {
  var fileList = this.files;

  /* now you can work with the file list */
}
```


Nota: en este caso, la función `handleFiles()` mira la lista de ficheros con la finalidad de aceptar un parametro, mientras los eventos listeners sean añadidos de esta manera no pueden aceptar un parametro del input.

Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1) introduce soporte para los métodos de DOM `window.createBlobURL()` y `window.revokeBlobURL()`. El cual te permite crear un simple string con la URL que referenciar cualquier dato que pueda referenciar usando un objeto `File` DOM, esto incluye los ficheros locales del usuario del dispositivo.

Cuando tienes un objeto`File` te gusta poder referenciarlo con una URL desde el HTML, la manera de crear tu propio blob URL para poder hacer esto es la siguiente:
```

var blobURL = window.createBlobURL(fileObj);
```


El blob URL es un string que identifica el objeto`File`. Cada vez que se llama a `window.createBlobURL()`, un único blob URL se crea. Cada vez que esto pasa debe de ser liberado. Cuando el documento se cierra, se libera automáticamente, pero si tu página lo usa dinámicamente deberás liberarlos explicitamente llamando a `window.revokeBlobURL()`:
```

window.revokeBlobURL(blobURL);
```

Selección de ficheros usando drag and drop
------------------------------------------

También se puede dejar al usuario arrastrar los ficheros en tu aplicación web.

El primer paso para establecer la zona de arrastre, el lugar en donde dejar los ficheros arrastrados. La parte exacta en donde se coloque esta zona de arrastre dentro de tu aplicación, dependerá del diseño de la aplicación, pero crear el elemento es sencillo:
```

var dropbox;

dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
```


En este ejemplo se transforma el elemento de ID "dropbox" en la zona de arrastre. Esto se hace añadiendo listeners para lo eventos de `dragenter`, `dragover`, y `drop`.

Realmente en este caso no se necesita hacer nada con los eventos `dragenter` y `dragover`, por lo que ambas funciones seran muy sencillas. Sólo pararán la propagacion de eventos y prevendran las acciones por defecto:
```

function dragenter(e) {
  e.stopPropagation();
  e.preventDefault();
}

function dragover(e) {
  e.stopPropagation();
  e.preventDefault();
}
```


La verdadera magia ocurre en la función `drop()`:
```

function drop(e) {
  e.stopPropagation();
  e.preventDefault();

  var dt = e.dataTransfer;
  var files = dt.files;

  handleFiles(files);
}
```


Aquí se recupera el campo `dataTransfer` del evento, a partir de ahi se extraen la lista de ficheros, y se pasan a `handleFiles()`. A partir de este punto se manejan los ficheros de igual manera tanto si se han obtenido usado el elemento `input` o drag and drop.

Recuperando la información de los ficheros selecionados
-------------------------------------------------------

El objeto `FileList` es dado por el DOM y lista todos los ficheros selecionados por el usuario, cada uno es un objeto `File`. Para determinar el número de ficheros que han sido seleccionados se comprueba la longitud de la lista con el atributo `length`:
```

var numFiles = files.length;
```


Al objeto `File` se accede de manera sencilla, accediendo a la lista como si fuera un array:
```

for (var i = 0; i < files.length; i++) {
  var file = files[i];
  ..
}
```


El bucle itera por todos los ficheros de la lista.

Cada objeto `File` tiene tres atributos que contienen información util sobre el fichero.

`name`

Es de solo lectura y es el nombre del fichero sin ninguna información sobre el path.

`size`

Es de solo lectura y es el tamaño del fichero en bytes como un entero de 64-bit.

`type`

Es de solo lectura y contiene MIME type del fichero, o "" si el tipo no puede ser determinado.

Ejemplo: Mostrando miniaturas de una selección de imágenes del usuario
----------------------------------------------------------------------

Digamos que usted esta desarrollando el próximo gran sitio web para compartir fotos, y quiere usar HTML5 para mostrar previsualizaciones de miniaturas de imágenes antes de que el usuario actual las suba. Basta con establecer su elemento input o zona de arrastre como se indicó anteriormente, y hacer una llamada a una función `handleFiles()` como la siguiente.
```

function handleFiles(files) {
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    var imageType = /image.*/;

    if (!file.type.match(imageType)) {
      continue;
    }

    var img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img);

    var reader = new FileReader();
    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
    reader.readAsDataURL(file);
  }
}
```


Aquí nuestro bucle recorre los archivos seleccionados por el usuario y mira el atributo `type` de cada archivo para ver si es una imagen (haciendo que una expresión regular coincida con la cadena de texto "image.\*"). Para cada archivo que sea una imagen, creamos un nuevo elemento `img`. Se puede usar CSS para establecer bordes bonitos, sombras, y especificar el tamaño de la imagen, el de manera que ni siquiera necesita hacerse aquí.

Cada imagen tiene la clase CSS "obj" añadida, para hacerla mas fácil buscarla en el árbol del DOM. Además añadimos un atributo `file` a cada imagen especificando el `Fichero` para la imagen; esto nos permitira traer el hecho de subir las imágenes más tarde. Finalmente, usamos `Node.appendChild()` para añadir la nueva miniatura en el área de la previsualización de nuestro documento.

A continuación establecemos el `FileReader` para controlar la carga de la imagen de forma asíncrona y enlazarla con el elemento `img`. Después de crear el nuevo objeto `FileReader`, configuramos su función `onload`, luego llamamos a `readAsDataURL()` para comenzar la operación de lectura en segundo plano. Cuando el contenido completo de la imagen ha sido cargado, se convierte a `data:` URL, el cuál es pasado al callback `onload`. Nuestra implementación de esta rutina simplemente establece el atributo `src` del elemento `img` cargado, cuyo resultado es la imagen apareciendo en la miniatura en la pantalla del usuario.

Este ejemplo el objeto blob URLs para mostrar las miniaturas de las imágenes. Además, se muestra otra información del archivo incluyendo sus nombres y tamaños. Tú puedes ver el ejemplo (tenga en cuenta que se requiere una versión Nghtly de Firefox del 23 de september 23 o mas reciente, o Firefox 4.0 beta 7).

El HTML que representa la interfaz es::
```

<form>
  <input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
</form>
<a href="javascript:doClick()">Seleccione algunos archivos</a>
<div id="fileList">
  <p>¡No se han seleccionado archivos!</p>
</div>
```


Esto establece nuestro elemento `<input>` de nuestro, así como un enlace que invoca el selector de archivos, ya que mantenmos el input file oculto para prevenir mostrar un UI menos atractivo. Esto se explica anteriormente en al la sección Using hidden file input elements using the click() method, así como el método `doClick()` que invoca el el selector de archivos.

El método `handleFiles()` es el siguiente:
```

function handleFiles(files) {
  var d = document.getElementById("fileList");
  if (!files.length) {
    d.innerHTML = "<p>¡No se han seleccionado archivos!</p>";
  } else {
    var list = document.createElement("ul");
    d.appendChild(list);
    for (var i=0; i < files.length; i++) {
      var li = document.createElement("li");
      list.appendChild(li);

      var img = document.createElement("img");
      img.src = window.createBlobURL(files[i]);;
      img.height = 60;
      img.onload = function() {
        window.revokeBlobURL(this.src);
      }
      li.appendChild(img);

      var info = document.createElement("span");
      info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
      li.appendChild(info);
    }
  }
}
```


Esto comienza buscando la URL del `<div>` con la ID "fileList". Este es el bloque donde vamos a insertar la lista de archivos incluyendo las miniaturas.

Si el objeto `FileList` (en-US) pasado a `handleFiles()` es `null`, simplemente establecemos el bloque inner HTML para mostrar "¡No se han seleccionado archivos!". En caso contrario, comenzamos a construir nuestra lista de archivos como sigue:

1. Un nuevo elemento de lista desordenada (`<ul>` es creado.
2. El nuevo elemento de la lista es insertado en el bloque `<div>` llamando a si método `element.appendChild()`.
3. Para cada `File` en el `FileList` (en-US) representado `files`:
    1. Se crea un nuevo elemento de lista (`<li>`) y lo inserta en la lista.
    2. Se crea un nuevo elemento imagen (`<img>`).
    3. Establece en la fuente de la imagen un nuevo blob URL representandoel archivo, usando `window.createBlobURL()` para crear el blob URL.
    4. Se establece el alto de la imagen en 60 pixels.
    5. Se configura el controlador para el evento load para liberar el blob URL, puesto que ya no es necesario una vez que la imagen ha sido cargada. Esto se hace llamando al método `window.revokeBlobURL()`, pasando la cadena en el blob URL como se ha especificado para `img.src`.
    6. Se añade el nuevo elemento de la lista a la lista.
Ejemplo: Subiendo un archivo seleccionado por el usuario
--------------------------------------------------------

Otra cosa que podrías querer hacer es dejar que el usuario suba el archivo o los archivos seleccionados (como las imagenes seleccionadas usando el ejemplo anterior) al servidor. Esto se puede hacer de forma asíncrona muy facilmente.

### Creando las tareas de subida

Continuando con el código que contruye las miniaturas en el ejemplo anterior, hay que recordar que cada imagen en miniatura se encuentra en la clase CSS "obj", con el correspondiente `File` adjunto en un attributo `file`. Vamos a seleccionar todas las imágenes que el usuario ha seleccionado facilmente para subirlas usando `Document.querySelectorAll()`, como sigue:
```

function sendFiles() {
  var imgs = document.querySelectorAll(".obj");

  for (var i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}
```


La segunda línea crea un array, llamado `imgs`, de todos los elementos en el documento con la clase CSS "obj". En nuestro caso, estos serán todas las miniaturas de las imágenes. Una vez que tenemos esa lista, of all the elements in the document with the CSS class "obj". In our case, these will be all the image thumbnails. Once we have that list, es trivial pasar por la lista, creando una nueva instancia de `FileUpload` por cada una. Subiendo el correspondiente archivo por cada uno de ellos.

### Manipulando el proceso de carga de un archivo

La función `FileUpload` acepta dos entradas: un elemento de imagen y un archivo desde el que leer los datos de la imagen.
```

function FileUpload(img, file) {
  this.ctrl = createThrobber(img);
  var xhr = new XMLHttpRequest();
  this.xhr = xhr;

  var self = this;
  this.xhr.upload.addEventListener("progress", function(e) {
        if (e.lengthComputable) {
          var percentage = Math.round((e.loaded * 100) / e.total);
          self.ctrl.update(percentage);
        }
      }, false);

  xhr.upload.addEventListener("load", function(e){
          self.ctrl.update(100);
          var canvas = self.ctrl.ctx.canvas;
          canvas.parentNode.removeChild(canvas);
      }, false);

  xhr.open("POST", "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php");
  xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
  xhr.sendAsBinary(file.getAsBinary());
}
```


La función `FileUpload()` mostrada arriba crea un throbber, el cual es usado para mostrar la información del progreso, a continuación se crea un `XMLHttpRequest` para manejar la subida de los datos.

Antes de subir los datos de hecho, se toman varias medidas preparatorias:

1. El listener del progreso de la carga del `XMLHttpRequest`'s se configura para actualizar el throbber con una nueva información de porcentaje, por lo que a medida que progresa la carga, el throbber será actualizado en base a la última información.
2. El evento "load" de la carga del `XMLHttpRequest`'s se configura para actualizar el throbber con el 100% como la información de progreso (para asegurar que el indicador de progreso realmente alcanza el 100%, en caso de las peculiaridades de granularidad durante el proceso). Entoces se elimina el throbber, puesto que ya no es necesario. Esto causa que el throbber desaparezca una vez la carga está completa.
3. La petición para cargar la imagen se inicia llamando al método `XMLHttpRequest`'s `open()` para comenzar a generar una petición de tipo POST.
4. El MIME type para la carga se establece llamando a la función `deXMLHttpRequest` `overrideMimeType()`. En este caso, estamos usando un MIME type genérico; usted puede o no puede necesitar establecer MIME type en absoluto, dependiendo de su necesidad.
5. Finalmente, se llama a la función `sendAsBinary()` del `XMLHttpRequest` es llamada para subir el contenido del archivo. *TEsto es necesario ser revisado, actualmete se está usando la rutina sincronizada en desuso getAsBinary() para extraer los datos del archivo..*
### Manipulación del proceso de carga para un archivo, de forma asíncrona
```

function fileUpload(file) {
  // Please report improvements to: marco.buratto at tiscali.it

  var fileName = file.name;
  var fileSize = file.size;
  var fileData = file.getAsBinary(); // works on TEXT data ONLY.

  var boundary = "xxxxxxxxx";
  var uri = "serverLogic.php";

  var xhr = new XMLHttpRequest();

  xhr.open("POST", uri, true);
  xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary); // simulate a file MIME POST request.
  xhr.setRequestHeader("Content-Length", fileSize);

  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if ((xhr.status >= 200 && xhr.status <= 200) || xhr.status == 304) {

        if (xhr.responseText != "") {
          alert(xhr.responseText); // display response.
        }
      }
    }
  }

  var body = "--" + boundary + "\r\n";
  body += "Content-Disposition: form-data; name='fileId'; filename='" + fileName + "'\r\n";
  body += "Content-Type: application/octet-stream\r\n\r\n";
  body += fileData + "\r\n";
  body += "--" + boundary + "--";

  xhr.send(body);
  return true;
}
```


*Esto necesita ser modificado para funcionar con datos binarios también.*

Ver también
-----------

* `File`
* `FileList`
* `FileReader`
* Usando XMLHttpRequest
* `XMLHttpRequest`