Capítulo 15 File

HTML5 ofrece una forma estándar de interactuar con archivos locales a través de la especificación del API de archivos. El API de archivos se puede utilizar, por ejemplo, para crear una vista previa en miniatura de imágenes mientras se envían al servidor o para permitir que una aplicación guarde una referencia de un archivo mientras el usuario se encuentra sin conexión. También se podría utilizar para verificar si el tipo MIME de un archivo seleccionado por el usuario coincide con los formatos de archivo permitidos o para restringir el tamaño de un fichero, antes de enviarlo al servidor.

A continuación se indican las interfaces que ofrece la especificación para acceder a archivos desde un sistema de archivos local:

  • File: representa un archivo local y proporciona información únicamente de lectura (el nombre, el tamaño del archivo, el tipo MIME y una referencia al manejador del archivo).
  • FileList: representa un conjunto de objetos File (tanto para un conjunto de ficheros seleccionados a través de <input type="file" multiple> como para un conjunto de ficheros arrastrados desde el sistema de ficheros al navegador).
  • Blob: permite fragmentar un archivo en intervalos de bytes.

Cuando se utiliza junto con las estructuras de datos anteriores, el API de FileReader se puede utilizar para leer un archivo de forma asíncrona mediante el control de eventos de JavaScript. Por lo tanto, se puede controlar el progreso de una lectura, detectar si se han producido errores y determinar si ha finalizado una carga de un fichero. El modelo de evento de FileReader guarda muchas semejanzas con el API de XMLHttpRequest.

15.1 Detección de la funcionalidad

En primer lugar, se debe comprobar que el navegador sea totalmente compatible con el API de archivos. Si la aplicación solo va a utilizar algunas funcionalidades del API, se debe modificar este fragmento de código para adaptarlo a nuestras necesidades:

// Check for the various File API support.
if (window.File && window.FileReader && window.FileList && window.Blob) {
    // Great success! All the File APIs are supported.
} else {
    alert('The File APIs are not fully supported in this browser.');
}

15.2 Acceso a través del formulario

La forma más sencilla de cargar un archivo es utilizar un elemento <input type="file"> estándar de formulario. JavaScript devuelve la lista de objetos File seleccionados como un objeto FileList.

A continuación, se muestra un ejemplo en el que se utiliza el atributo multiple para permitir la selección simultánea de varios archivos:

<input type="file" id="files" name="files[]" multiple />
<output id="list"></output>
function handleFileSelect(e) {
    var files = e.target.files; // FileList object
    // files is a FileList of File objects. List some properties.
    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
        output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                    f.size, ' bytes, last modified: ',
                    f.lastModifiedDate.toLocaleDateString(), '</li>');
    }
    list.innerHTML = '<ul>' + output.join('') + '</ul>';
}
 
files.addEventListener('change', handleFileSelect, false);

15.3 Cómo leer archivos

Después de obtener una referencia de File, podemos crear una instancia de un objeto FileReader para leer su contenido y almacenarlo en memoria. Cuando finaliza la carga, se lanza el evento onload y se puede utilizar su atributo result para acceder a los datos del archivo.

A continuación se indican las cuatro opciones de lectura asíncrona de archivo que incluye FileReader:

  • FileReader.readAsBinaryString(Blob|File): la propiedad result contendrá los datos del archivo/objeto BLOB en forma de cadena binaria. Cada byte se representa con un número entero comprendido entre 0 y 255, ambos incluidos.
  • FileReader.readAsText(Blob|File, opt_encoding): la propiedad result contendrá los datos del archivo/objeto BLOB en forma de cadena de texto. De forma predeterminada, la cadena se decodifica con el formato UTF-8. Podemos especificar un parámetro de codificación opcional para especificar un formato diferente.
  • FileReader.readAsDataURL(Blob|File): la propiedad result contendrá los datos del archivo/objeto BLOB codificados como una URL de datos.
  • FileReader.readAsArrayBuffer(Blob|File): la propiedad result contendrá los datos del archivo/objeto BLOB como un objeto ArrayBuffer.

Una vez que se ha activado uno de estos métodos de lectura en el objeto FileReader, se pueden escuchar los eventos onloadstart, onprogress, onload, onabort, onerror y onloadend para realizar un seguimiento de su progreso de lectura. En el ejemplo que se muestra a continuación, obtenemos las imágenes de los elementos seleccionados por el usuario, leemos su contenido con reader.readAsDataURL() mostramos una miniatura de la imagen:

<input type="file" id="files" name="files[]" multiple />
<output id="list"></output>
function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object
    // Loop through the FileList and render image files as thumbnails.
    for (var i = 0, f; f = files[i]; i++) {
 
        // Only process image files.
        if (!f.type.match('image.*')) {
            continue;
        }
 
        var reader = new FileReader();
        // Closure to capture the file information.
        reader.onload = (function(theFile) {
            return function(e) {
                // Render thumbnail.
                var span = document.createElement('span');
                span.innerHTML = ['<img class="thumb" src="', e.target.result,
                                  '" title="', escape(theFile.name), '"/>'].join('');
                list.insertBefore(span, null);
            };
        })(f);
 
        // Read in the image file as a data URL.
        reader.readAsDataURL(f);
    }
}
 
files.addEventListener('change', handleFileSelect, false);

15.4 Fragmentación de archivos

En algunos casos, leer el archivo completo en memoria no es la mejor opción. Supongamos, por ejemplo, que se quiere crear una herramienta de subida de archivos de forma asíncrona. Para acelerar la subida, se podría leer y enviar el archivo en diferentes fragmentos de bytes. El servidor se encargaría de reconstruir el contenido del archivo en el orden correcto.

Afortunadamente, la interfaz File nos proporciona un método de fragmentación de ficheros. El método utiliza un byte de inicio como primer argumento, un byte de finalización como segundo argumento. El siguiente ejemplo muestra cómo leer un fichero por partes:

var files = document.getElementById('files').files;
if (!files.length) {
  alert('Please select a file!');
  return;
}
 
var file  = files[0];
var start = 0;
var stop  = file.size - 1;
 
var reader = new FileReader();
 
reader.onloadend = function(e) {
  if (e.target.readyState == FileReader.DONE) { // DONE == 2
    document.getElementById('byte_content').textContent = e.target.result;
    document.getElementById('byte_range').textContent = 
        ['Read bytes: ', start + 1, ' - ', stop + 1,
         ' of ', file.size, ' byte file'].join('');
  }
};
 
if (file.webkitSlice) {
  var blob = file.webkitSlice(start, stop + 1);
} else if (file.mozSlice) {
  var blob = file.mozSlice(start, stop + 1);
}
reader.readAsBinaryString(blob);