domingo, 17 de julio de 2016

Almacenamiento de archivos y procesamiento de archivos de usuario - HTML, CSS y JavaScript



1. Almacenamiento de archivos

Los archivos son unidades de información que usuarios pueden fácilmente compartir con otras personas. Los usuarios no pueden compartir el valor de una variable o un par clave/valor como los usados por la API Web Storage, pero ciertamente pueden hacer copias de sus archivos y enviarlos por medio de un DVD, memorias portátiles, discos duros, transmitirlos a través de Internet, etc… Los archivos pueden almacenar grandes cantidades de datos y ser movidos, duplicados o transmitidos independientemente de la naturaleza de su contenido.
Los archivos fueron siempre una parte esencial de cada aplicación, pero hasta ahora no había forma posible de trabajar con ellos en la web. Las opciones estaban limitadas a subir o descargar archivos ya existentes en servidores u ordenadores de usuarios. No existía la posibilidad de crear archivos, copiarlos o procesarlos en la web… hasta hoy. 
La especificación de HTML5 fue desarrollada considerando cada aspecto necesario para la construcción y funcionalidad de aplicaciones web. Desde el diseño hasta la estructura elemental de los datos, todo fue cubierto. Y los archivos no podían ser ignorados, por supuesto. Por esta razón, la especificación incorpora la API File. 
LA API File comparte algunas características con las API de almacenamiento estudiadas en capítulos previos. Esta API posee una infraestructura de bajo nivel, aunque no tan compleja como IndexedDB, y al igual que otras puede trabajar de forma síncrona o asíncrona. La parte síncrona fue desarrollada para ser usada con la API Web Workers (del mismo modo que IndexedDB y otras APIs), y la parte asíncrona está destinada a aplicaciones web convencionales. Estas características nos obligan nuevamente a cuidar cada aspecto del proceso, controlar si la operación fue exitosa o devolvió errores, y probablemente adoptar (o desarrollar nosotros mismos) en el futuro APIs más simples construidas sobre la misma.
API File es una vieja API que ha sido mejorada y expandida. Al día de hoy está compuesta por tres especificaciones: API File, API File: Directories & System, y API File: Writer, pero esta situación puede cambiar durante los siguientes meses con la incorporación de nuevas especificaciones o incluso la unificación de algunas de las ya existentes. 
Básicamente, la API File nos permite interactuar con archivos locales y procesar su contenido en nuestra aplicación, la extensión la API File: Directories & System provee las herramientas para trabajar con un pequeño sistema de archivos creado específicamente para cada aplicación, y la extensión API File: Writer es para escribir contenido dentro de archivos que fueron creados o descargados por la aplicación.


2. Procesando archivos de usuario

Trabajar con archivos locales desde aplicaciones web puede ser peligroso. Los navegadores deben considerar medidas de seguridad antes de siquiera contemplar la posibilidad de dejar que las aplicaciones tengan acceso a los archivos del usuario. A este respecto, File API provee solo dos métodos para cargar archivos desde una aplicación: la etiqueta <input> y la operación arrastrar y soltar.
En el Capítulo 8 aprendimos cómo usar la API Drag and Drop para arrastrar archivos desde una aplicación de escritorio y soltarlos dentro de un espacio en la página web. La etiqueta <input> (también estudiada en capítulos anteriores), cuando es usada con el tipo file, trabaja de forma similar a API Drag and Drop. Ambas técnicas transmiten archivos a través de la propiedad files. Del mismo modo que lo hicimos en ejemplos previos, lo único que debemos hacer es explorar el valor de esta propiedad para obtener cada archivo que fue seleccionado o arrastrado.
IMPORTANTE: Esta API y sus extensiones no trabajan en este momento desde un servidor local, y solo Google Chrome y Firefox tienen implementaciones disponibles. Algunas de estas implementaciones son tan nuevas que solo trabajan en navegadores experimentales como Chromium (www.chromium.org) o Firefox Beta. Para ejecutar los códigos de este capítulo, deberá usar las últimas versiones de navegadores disponibles y subir todos los archivos a su servidor.

Plantilla

En esta primera parte del capítulo vamos a usar la etiqueta <input> para seleccionar archivos, pero usted puede, si lo desea, aprovechar la información en el Capítulo 8 para integrar estos códigos con API Drag and Drop.

Código 12-1. Plantilla para trabajar con los archivos del usuario.

<!DOCTYPE html>
<html lang="es">
<head>
<title>File API</title>
<link rel="stylesheet" href="file.css">
<script src="file.js"></script>
</head>
<body>
<section id="cajaformulario">
<form name="formulario">

<p>Archivos:<br><input type="file" name="archivos"
id="archivos"></p>
</form>
</section>
<section id="cajadatos">
No se seleccionaron archivos
</section>
</body>
</html>

El archivo CSS incluye estilos para esta plantilla y otros que vamos a usar más adelante:

Código 12-2. Estilos para el formulario y la cajadatos.

#cajaformulario{
float: left;
padding: 20px;
border: 1px solid #999999;
}
#cajadatos{
float: left;
width: 500px;
margin-left: 20px;
padding: 20px;
border: 1px solid #999999;
}
.directorio{
color: #0000FF;
font-weight: bold;
cursor: pointer;
}


Leyendo archivos

Para leer archivos en el ordenador de los usuarios tenemos que usar la interface FileReader. Esta interface retorna un objeto con varios métodos para obtener el contenido de cada archivo:
  • readAsText(archivo, codificación) Para procesar el contenido como texto podemos usar este método. Un evento load es disparado desde el objeto FileReader cuando el archivo es cargado. El contenido es retornado codificado como texto UTF-8 a menos que el atributo codificación sea especificado con un valor diferente. Este método intentará interpretar cada byte o una secuencia de múltiples bytes como caracteres de texto.
  • readAsBinaryString(archivo) La información es leída por este método como una sucesión de enteros en el rango de 0 a 255. Este método nos asegura que cada byte es leído como es, sin ningún intento de interpretación. Es útil para procesar contenido binario como imágenes o videos.
  • readAsDataURL(archivo) Este método genera una cadena del tipo data:url codificada en base64 que representa los datos del archivo.
  • readAsArrayBuffer(archivo) Este método retorna los datos del archivo como datos del tipo ArrayBuffer.


Código 12-3. Leyendo un archivo de texto.

function iniciar(){
cajadatos=document.getElementById('cajadatos');
var archivos=document.getElementById('archivos');
archivos.addEventListener('change', procesar, false);
}
function procesar(e){
var archivos=e.target.files;
var archivo=archivos[0];
var lector=new FileReader();
lector.onload=mostrar;
lector.readAsText(archivo);
}
function mostrar(e){
var resultado=e.target.result;
cajadatos.innerHTML=resultado;
}
window.addEventListener('load', iniciar, false);

Desde el campo archivos del documento HTML del Código 12-1 el usuario puede seleccionar un archivo para ser procesado. Para detectar esta acción, en la función iniciar() del Código 12-3 agregamos una escucha para el evento change. De este modo, la función procesar() será ejecutada cada vez que algo cambie en el elemento <input> (un nuevo archivo es seleccionado).
La propiedad files enviada por el elemento <input> (y también por la API Drag and Drop) es un array conteniendo todos los archivos seleccionados. Cuando el atributo multiple no está presente en la etiqueta <input> no es posible seleccionar múltiples archivos, por lo que el primer elemento del array será el único disponible. Al comienzo de la función procesar() tomamos el contenido de la propiedad files, lo asignamos a la variable archivos y luego seleccionamos el primer archivo con la línea var archivo=archivos[0].
IMPORTANTE: Para aprender más acerca del atributo multiple lea nuevamente el Capítulo 6, Código 6-17. También puede encontrar un ejemplo de cómo trabajar con múltiples archivos en el Código  8-10, Capítulo 8. 
Lo primero que debemos hacer para comenzar a procesar el archivo es obtener un objeto FileReader usando el constructor FileReader(). En la función procesar() del Código 12-3 llamamos a este objeto lector. En el siguiente paso, registramos un manejador de eventos onload para el objeto lector con el objetivo de detectar cuando el archivo fue cargado y ya podemos comenzar a procesarlo. Finalmente, el método readAsText() lee el archivo y retorna su contenido en formato texto.
Cuando el método readAsText() finaliza la lectura del archivo, el evento load es disparado y la función mostrar() es llamada. Esta función toma el contenido del archivo desde la propiedad result del objeto lector y lo muestra en pantalla.
Este código, por supuesto, espera recibir archivos de texto, pero el método readAsText() toma lo que le enviamos y lo interpreta como texto, incluyendo archivos con contenido binario (por ejemplo, imágenes). Si carga archivos con diferente contenido, verá caracteres extraños aparecer en pantalla.
  • Hágalo usted mismo: Cree archivos con los Códigos  12-1, 12-2 y 12-3. Los nombres para los archivos CSS y Javascript fueron declarados en el documento HTML como file.css y file.js respectivamente. Abra la plantilla en el navegador y use el formulario para seleccionar un archivo en su ordenador. Intente con archivos de texto así como imágenes para ver cómo los métodos utilizados presentan el contenido en pantalla.
  • IMPORTANTE: En este momento, API File y cada una de sus especificaciones están siendo implementadas por los fabricantes de navegadores. Los códigos en esta capítulo fueron testeados en Google Chrome y Firefox 4+, pero las últimas versiones de Chrome no habían implementado aún el método addEventListener() para FileReader y otros objetos. Por esta razón, usamos manejadores de eventos en nuestro ejemplo, como onload, cada vez que era necesario para que el código trabajara correctamente. Por ejemplo, lector.onload=mostrar fue usado en lugar de lector.addEventListener('load', mostrar, false). Como siempre, le recomendamos probar los códigos en cada navegador disponible para encontrar qué implementación trabaja correctamente con esta API.


Propiedades de archivos

En una aplicación real, información como el nombre del archivo, su tamaño o tipo será necesaria para informar al usuario sobre los archivos que están siendo procesados o incluso controlar cuáles son o no son admitidos. El objeto enviado por el elemento <input> incluye varias propiedades para acceder a esta información:
name Esta propiedad retorna el nombre completo del archivo (nombre y extensión).
size Esta propiedad retorna el tamaño del archivo en bytes.
type Esta propiedad retorna el tipo de archivo, especificado en tipos MIME.

Código 12-4. Cargando imágenes.

function iniciar(){
cajadatos=document.getElementById('cajadatos');
var archivos=document.getElementById('archivos');
archivos.addEventListener('change', procesar, false);
}
function procesar(e){
var archivos=e.target.files;
cajadatos.innerHTML='';
var archivo=archivos[0];
if(!archivo.type.match(/image.*/i)){
alert('seleccione una imagen');
}else{
cajadatos.innerHTML+='Nombre: '+archivo.name+'<br>';
cajadatos.innerHTML+='Tamaño: '+archivo.size+' bytes<br>';
var lector=new FileReader();
lector.onload=mostrar;
lector.readAsDataURL(archivo);
}
}
function mostrar(e){
var resultado=e.target.result;
cajadatos.innerHTML+='<img src="'+resultado+'">';
}
window.addEventListener('load', iniciar, false);

El ejemplo del Código 12-4 es similar al anterior excepto que esta vez usamos el método readAsDataURL() para leer el archivo. Este método retorna el contenido del archivo en el formato data:url que puede ser usado luego como fuente de un elemento <img> para mostrar la imagen seleccionada en la pantalla.
Cuando necesitamos procesar una clase particular de archivo, lo primero que debemos hacer es leer la propiedad type del archivo. En la función procesar() del Código 12-4 controlamos este valor aprovechando el viejo método match(). Si el archivo no es una imagen, mostramos un mensaje de error con alert(). Si, por otro lado, el archivo es efectivamente una imagen, el nombre y tamaño del archivo son mostrados en pantalla y el archivo es abierto.
A pesar del uso de readAsDataURL(), el proceso de apertura es exactamente el mismo. El objeto FileReader es creado, el manejador onload es registrado y el archivo es cargado. Una vez que el proceso termina, la función mostrar() usa el contenido de la propiedad result como fuente del elemento <img> y la imagen seleccionada es mostrada en la pantalla.
Conceptos básicos: Para construir el filtro aprovechamos Expresiones Regulares y el conocido método Javascript match(). Este método busca por cadenas de texto que concuerden con la expresión regular, retornando un array con todas las coincidencias o el valor null en caso de no encontrar ninguna. El tipo MIME para imágenes es algo como image/jpeg para imágenes en formato JPG, o image/gif para imágenes en formato GIF, por lo que la expresión /image.*/i aceptará cualquier formato de imagen, pero solo permitirá que imágenes y no otro tipo de archivos sean leídos. Para más información sobre Expresiones Regulares o tipos MIME, visite nuestro sitio web y siga los enlaces correspondientes a este capítulo.

Blobs

Además de archivos, la API trabaja con otra clase de fuente de datos llamada blobs. Un blob es un objeto representando datos en crudo. Fueron creados con el propósito de superar limitaciones de Javascript para trabajar con datos binarios. Un blob es normalmente generado a partir de un archivo, pero no necesariamente. Es una buena alternativa para trabajar con datos sin cargar archivos enteros en memoria, y provee la posibilidad de procesar información binaria en pequeñas piezas. 
Un blob tiene propósitos múltiples, pero está enfocado en ofrecer una mejor manera de procesar grandes piezas de datos crudos o archivos. Para generar blobs desde otros blobs o archivos, la API ofrece el método slice():
  • slice(comienzo, largo, tipo) Este método retorna un nuevo blob generado desde otro blob o un archivo. El primer atributo indica el punto de comienzo, el segundo el largo del nuevo blob, y el último es un atributo opcional para especificar el tipo de datos usados.


Código 12-5. Trabajando con blobs.

function iniciar(){
cajadatos=document.getElementById('cajadatos');
var archivos=document.getElementById('archivos');
archivos.addEventListener('change', procesar, false);
}
function procesar(e){
var archivos=e.target.files;
cajadatos.innerHTML='';
var archivo=archivos[0];
var lector=new FileReader();
lector.onload=function(e){ mostrar(e, archivo); };
var blob=archivo.slice(0,1000);
lector.readAsBinaryString(blob);
}
function mostrar(e, archivo){
var resultado=e.target.result;
cajadatos.innerHTML='Nombre: '+archivo.name+'<br>';
cajadatos.innerHTML+='Tipo: '+archivo.type+'<br>';
cajadatos.innerHTML+='Tamaño: '+archivo.size+' bytes<br>';
cajadatos.innerHTML+='Tamaño Blob: '+resultado.length+'
bytes<br>';
cajadatos.innerHTML+='Blob: '+resultado;
}
window.addEventListener('load', iniciar, false);

IMPORTANTE: Debido a inconsistencias con métodos previos, un reemplazo para el método slice está siendo implementado en este momento. Hasta que este método esté disponible, para probar el Código 12-5 en las últimas versiones de Firefox y Google Chrome tendrá que reemplazar slice por mozSlice y webkitSlice respectivamente. Para más información, visite nuestro sitio web y siga los enlaces correspondientes a este capítulo.
En el Código 12-5, hicimos exactamente lo que veníamos haciendo hasta el momento, pero esta vez en lugar de leer el archivo completo creamos un blob con el método slice(). El blob tiene 1000 bytes de largo y comienza desde el byte 0 del archivo. Si el archivo cargado es más pequeño que 1000 bytes, el blob será del mismo largo del archivo (desde el comienzo hasta el EOF, o End Of File). 
Para mostrar la información obtenida por este proceso, registramos el manejador de eventos onload y llamamos a la función mostrar() desde una función anónima con la que pasamos la referencia del objeto archivo. Este objeto es recibido por mostrar() y sus propiedades son mostradas en la pantalla. 
Las ventajas ofrecidas por blobs son incontables. Podemos crear un bucle para dividir un archivo en varios blobs, por ejemplo, y luego procesar esta información parte por parte, creando programas para subir archivos al servidor o aplicaciones para procesar imágenes, entre otras. Los blobs ofrecen nuevas posibilidades a los códigos Javascript. 


Eventos

El tiempo que toma a un archivo para ser cargado en memoria depende de su tamaño.
Para archivos pequeños, el proceso se asemeja a una operación instantánea, pero grandes archivos pueden tomar varios segundos en cargar. Además del evento load ya estudiado, la API provee eventos especiales para informar sobre cada instancia del proceso:
  • loadstart Este evento es disparado desde el objeto FileReader cuando la carga comienza.
  • progress Este evento es disparado periódicamente mientras el archivo o blob está siendo leído.
  • abort Este evento es disparado si el proceso es abortado.
  • error Este evento es disparado cuando la carga ha fallado.
  • loadend Este evento es similar a load, pero es disparado en caso de éxito o fracaso.


Código 12-6. Usando eventos para controlar el proceso de lectura.

function iniciar(){
cajadatos=document.getElementById('cajadatos');
var archivos=document.getElementById('archivos');
archivos.addEventListener('change', procesar, false);
}
function procesar(e){
var archivos=e.target.files;
cajadatos.innerHTML='';
var archivo=archivos[0];
var lector=new FileReader();
lector.onloadstart=comenzar;
lector.onprogress=estado;
lector.onloadend=function(){ mostrar(archivo); };
lector.readAsBinaryString(archivo);
}
function comenzar(e){
cajadatos.innerHTML='<progress value="0" max="100">0%</progress>';
}
function estado(e){
var por=parseInt(e.loaded/e.total*100);
cajadatos.innerHTML='<progress value="'+por+'" max="100">'
+por+'%</progress>';
}
function mostrar(archivo){
cajadatos.innerHTML='Nombre: '+archivo.name+'<br>';
cajadatos.innerHTML+='Tipo: '+archivo.type+'<br>';
cajadatos.innerHTML+='Tamaño: '+archivo.size+' bytes<br>';
}
window.addEventListener('load', iniciar, false);

Con el Código 12-6 creamos una aplicación que carga un archivo y muestra el progreso de la operación a través de una barra de progreso. Tres manejadores de eventos fueron registrados en el objeto FileReader para controlar el proceso de lectura y dos funciones fueron creadas para responder a estos eventos: comenzar() y estado(). La función comenzar() iniciará la barra de progreso con el valor 0% y la mostrará en pantalla. 
Esta barra de progreso podría usar cualquier valor o rango, pero decidimos usar porcentajes para que sea más comprensible para el usuario. En la función estado(), este porcentaje es calculado a partir de las propiedades loaded y total retornadas por el evento progress. La barra de progreso es recreada en la pantalla cada vez que el evento progress es disparado. 
Hágalo usted mismo: Usando la plantilla del Código 12-1 y el código Javascript del Código 12-6, pruebe cargar un archivo extenso (puede intentar con un video, por ejemplo) para ver la barra de progreso en funcionamiento. Si el navegador no reconoce el elemento <progress>, el contenido de este elemento es mostrado en su lugar.
IMPORTANTE: En nuestro ejemplo utilizamos innerHTML para agregar un nuevo elemento <progress> al documento. Esta no es una práctica recomendada pero es útil y conveniente por razones didácticas. Normalmente los elementos son agregados al documento usando el método Javascript createElement() junto con appendChild().


Espero haber ayudado en algo. Hasta la próxima oportunidad!











  

No hay comentarios:

Publicar un comentario en la entrada