Capítulo 12 API File – 3

Capítulo 12 API File - 3

12.4    Contenido de archivos

Además de la parte principal de API File y la extensión ya estudiada, existe otra especificación llamada API File: Writer. Esta extensión declara nuevas interfaces para escribir y agregar contenido a archivos. Trabaja junto con el resto de la API combinando métodos y compartiendo objetos para lograr su objetivo.

IMPORTANTE: La integración entre todas las especificaciones involucradas en API despertó debate acerca de si algunas de las interfaces propuestas deberían ser movidas desde una API a otra. Para obtener información actualizada a este respecto, visite nuestro sitio web o el sitio de W3C en www.w3.org.
Escribiendo contenido

Para escribir contenido dentro de un archivo necesitamos crear un objeto FileWriter. Estos objetos son retornados por el método createWriter() de la interface FileEntry. Esta interface fue adicionada a la interface Entry y provee un total de dos métodos para trabajar con archivos:

createWriter(función éxito, función error) Este método retorna un objeto FileWriter asociado con la entrada seleccionada.Devonn

file(función éxito, función error) Este es un método que vamos a usar más adelante para leer el contenido del archivo. Crea un objeto File asociado con la entrada seleccionada (como el retornado por el elemento <input> o una operación arrastrar y soltar).

El objeto FileWriter retornado por el método createWriter() tiene sus propios métodos, propiedades y eventos para facilitar el proceso de agregar contenido a un archivo:

write(datos) Este es el método que escribe contenido dentro del archivo. El contenido a ser insertado es provisto por el atributo datos en forma de blob. seek(desplazamiento) Este método establece la posición del archivo en la cual el contenido será escrito. El valor del atributo desplazamiento debe ser declarado en bytes. truncate(tamaño) Este método cambia el tamaño del archivo de acuerdo al valor del atributo tamaño (en bytes).

position Esta propiedad retorna la posición actual en la cual la siguiente escritura ocurrirá. La posición será 0 para un nuevo archivo o diferente de 0 si algún contenido fue escrito dentro del archivo o el método seek() fue aplicado previamente. length Esta propiedad retorna el largo del archivo.

writestart Este evento es disparado cuando el proceso de escritura comienza. progress Este evento es disparado periódicamente para informar el progreso. write Este evento es disparado cuando los datos han sido completamente escritos en el archivo.

abort Este evento es disparado si el proceso es abortado. error Este evento es disparado si ocurre un error en el proceso. writeend Este evento es disparado cuando el proceso termina.

Necesitamos crear un objeto más para preparar el contenido a ser agregado al archivo. El constructor BlobBuilder() retorna un objeto BlobBuilder con los siguientes métodos:

getBlob(tipo) Este método retorna el contenido del objeto BlobBuilder como un blob.

Es útil para crear el blob que necesitamos usar con el método write (). append(datos) Este método agrega el valor de datos al objeto BlobBuilder. El atributo datos puede ser un blob, un dato del tipo ArrayBuffer o simplemente texto.

El documento HTML del Listado 12-17 incorpora un segundo campo para insertar texto que representará el contenido del archivo. Será la plantilla utilizada en los próximos ejemplos:

<!DOCTYPE html>

<html lang=»es»>

<head>

<title>File API</title>

<link rel=»stylesheet» href=»file.css»>

<script src=»file.j s»></script>

</head>

<body>

<section id=»cajaformulario»>

<form name=»formulario»>

<p>Archivo:<br><input type=»text» name=»entrada»

id=»entrada» required></p> <p>Texto:<br><textarea name=»texto» id=»texto»

required></textarea></p> <p><input type=»button» name=»boton» id=»boton»

value=»Aceptar»></p>

</form>

</section>

<section id=»cajadatos»>

No hay información disponible </section>

</body>

</html>

Listado 12-17. Formulario para ingresar el nombre del archivo y su contenido.

Para la escritura del contenido abrimos el Sistema de Archivos, obtenemos o creamos el archivo con getFile() e insertamos contenido en su interior con los valores ingresados por el usuario. Con este fin, crearemos dos nuevas funciones: escribirarchivo() y escribircontenido().

IMPORTANTE: Hemos intentado mantener los códigos lo más simples posible por propósitos didácticos. Sin embargo, usted siempre puede aprovechar funciones anónimas para mantener todo dentro del mismo entorno (dentro de la misma función) o utilizar Programación Orientada a Objetos para implementaciones más poderosas y escalables.

function iniciar(){

caj adatos=document.getElementById(‘caj adatos’); var boton=document.getElementById(‘boton’); boton.addEventListener(‘click’, escribirarchivo, false);

window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024,

creardd, errores);

}

function creardd(sistema){ dd=sistema.root;

}

function errores(e){

alert(‘Error: ‘+e.code);

}

function escribirarchivo(){

var nombre=document.getElementById(‘entrada’).value;

dd.getFile(nombre, {create: true, exclusive:

false},function(entrada){ entrada.createWriter(escribircontenido, errores);

}, errores);

}

function escribircontenido(fileWriter) {

var texto=document.getElementById(‘texto’).value;

fileWriter.onwriteend=exito;

var blob=new WebKitBlobBuilder();

blob.append(texto);

fileWriter.write(blob.getBlob());

}

function exito(){

document.getElementById(‘entrada’).value=»; document.getElementById(‘texto’).value=»; caj adatos.innerHTML=’Hecho!’;

}

window.addEventListener(‘load’, iniciar, false);

Listado 12-18. Escribiendo contenido.

IMPORTANTE: Del mismo modo que el método requestFileSystem(), Google Chrome ha agregado un prefijo al constructor BlobBuilder() en la implementación actual. Deberemos usar WebKitBlobBuilder() en éste y los siguientes ejemplos para probar nuestros códigos en este navegador. Como siempre, el método original podrá ser utilizado luego de que la etapa experimental sea finalizada.

Cuando el botón «Aceptar» es presionado, la información en los campos del formulario es procesada por las funciones escribirarchivo() y escribircontenido(). La función escribirarchivo () toma el valor del campo entrada y usa getFile () para abrir o crear el archivo si no existe. El objeto Entry retornado es usado por createWriter () para crear un objeto FileWriter. Si la operación es exitosa, este objeto es enviado a la función escribircontenido ().

La función escribircontenido() recibe el objeto FileWriter y, usando el valor del campo texto, escribe contenido dentro del archivo. El texto debe ser convertido en un blob antes de ser usado. Con este propósito, un objeto BlobBuilder es creado con el constructor BlobBuilder (), el texto es agregado a este objeto por el método append () y el contenido es recuperado como un blob usando getBlob (). Ahora la información se encuentra en el formato apropiado para ser escrita dentro del archivo usando write().

Todo el proceso es asíncrono, por supuesto, lo que significa que el estado de la operación será contantemente informado a través de eventos. En la función escribircontenido (), solo escuchamos al evento writeend (usando el manejador de eventos onwriteend) para llamar a la función exito () y escribir el mensaje «Hecho!» en la pantalla cuando la operación es finalizada. Sin embargo, usted puede seguir el progreso o controlar los errores aprovechando el resto de los eventos disparados por el objeto FileWriter.

Hágalo usted mismo: Copie la plantilla en el Listado 12-17 dentro de un nuevo archivo HTML (esta plantilla usa los mismos estilos CSS del Listado 12-2). Cree un archivo Javascript llamado file.js con el código del Listado 12-18. Abra el documento HTML en su navegador e inserte el nombre y el texto del archivo que quiere crear. Si el mensaje «Hecho!» aparece en pantalla, el proceso fue exitoso.

Agregando contenido

Debido a que no especificamos la posición en la cual el contenido debía ser escrito, el código previo simplemente escribirá el blob al comienzo del archivo. Para seleccionar una posición específica o agregar contenido al final de un archivo ya existente, es necesario usar previamente el método seek ().

function escribircontenido(fileWriter) {

var texto=document.getElementById(‘texto’).value;

fileWriter.seek(fileWriter.length);

fileWriter.onwriteend=exito;

var blob=new WebKitBlobBuilder() ;

blob.append(texto);

fileWriter.write(blob.getBlob());

}

Listado 12-19. Agregando contenido al final del archivo.

La función del Listado 12-19 mejora la anterior función escribircontenido() incorporando un método seek () para mover la posición de escritura al final del archivo. De este modo, el contenido escrito por el método write () no sobrescribirá el contenido anterior.

Para calcular la posición del final del archivo en bytes, usamos la propiedad length mencionada anteriormente. El resto del código es exactamente el mismo que en el Listado 12-18.

Hágalo usted mismo: Reemplace la función escribircontenido() del Listado 12-18 por la nueva en el Listado 12-19 y abra el archivo HTML en su navegador. Inserte en el formulario el mismo nombre del archivo creado usando el código previo y el texto que quiere agregar al mismo.

Leyendo contenido

Es momento de leer lo que acabamos de escribir. El proceso de lectura usa técnicas de la parte principal de API File, estudiada al comienzo de este capítulo. Vamos a usar el constructor FileReader () y métodos de lectura como readAsText () para obtener el contenido del archivo.

function iniciar(){

caj adatos=document.getElementByld(‘caj adatos’); var boton=document.getElementById(‘boton’); boton.addEventListener(‘click’, leerarchivo, false);

window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024,

creardd, errores);

}

function creardd(sistema){ dd=sistema.root;

}

function errores(e){

alert(‘Error: ‘+e.code);

}

function leerarchivo(){

var nombre=document.getElementById(‘entrada’).value; dd.getFile(nombre, {create: false}, function(entrada) { entrada.file(leercontenido, errores);

}, errores);

}

function leercontenido(archivo){

caj adatos.innerHTML=’Nombre:      ‘+archivo.name+'<br>’;

caj adatos.innerHTML+=’Tipo: ‘+archivo.type+'<br>’;

cajadatos.innerHTML+=’Tamaño: ‘+archivo.size+’ bytes<br>’;

var lector=new FileReader(); lector.onload=exito; lector.readAsText(archivo);

}

function exito(e){

var resultado=e.target.result; document.getElementById(‘entrada’).value=»; caj adatos.innerHTML+=’Contenido:    ‘+resultado;

}

window.addEventListener(‘load’, iniciar, false);

Listado 12-20. Leyendo el contenido de un archivo en el Sistema de Archivos.

Los métodos provistos por la interface FileReader para leer el contenido de un archivo, como readAsText(), requieren un blob o un objeto File como atributo. El objeto File representa el archivo a ser leído y es generado por el elemento <input> o una operación arrastrar y soltar. Como dijimos anteriormente, la interface FileEntry ofrece la opción de crear esta clase de objetos utilizando un método llamado file ().

Cuando el botón «Aceptar» es presionado, la función leerarchivo() toma el valor del campo entrada del formulario y abre el archivo con ese nombre usando getFile ().

El objeto Entry retornado por este método es representado por la variable entrada y usado para generar el objeto File con el método file ().

Debido a que el objeto File obtenido de este modo es exactamente el mismo generado por el elemento <input> o la operación arrastrar y soltar, todas las mismas propiedades usadas antes están disponibles y podemos mostrar información básica acerca del archivo incluso antes de que el proceso de lectura del contenido comience. En la función leercontenido (), los valores de estas propiedades son mostrados en pantalla y el contenido del archivo es leído.

El proceso de lectura es una copia exacta del código del Listado 12-3: el objeto FileReader es creado con el constructor FileReader(), el manejador de eventos onload es registrado para llamar a la función exito () cuando el proceso es finalizado, y el contenido del archivo es finalmente leído por el método readAsText ().

En la función exito(), en lugar de imprimir un mensaje como hicimos previamente, el contenido del archivo es mostrado en pantalla. Para hacer esto, tomamos el valor de la propiedad result perteneciente al objeto FileReader y lo declaramos como contenido del elemento cajadatos.

Hágalo Usted Mismo: El código en el Listado 12-20 utiliza solo el valor del campo entrada (no necesita escribir un contenido para el archivo, solo ingresar su nombre). Abra el archivo HTML con la última plantilla en su navegador e inserte el nombre del archivo que quiere leer. Debe ser un archivo que usted ya creó usando códigos previos o el sistema retornará un mensaje de error (create: false). Si el nombre de archivo es correcto, la información sobre este archivo y su contenido serán mostrados en pantalla.

12.5   Sistema de archivos de la vida real

Siempre es bueno estudiar un caso de la vida real que nos permita entender el potencial de los conceptos aprendidos. Para finalizar este capítulo, vamos a crear una aplicación que combina varias técnicas de API File con las posibilidades de manipulación de imágenes ofrecida por API Canvas.

Este ejemplo toma múltiples archivos de imagen y dibuja estas imágenes en el lienzo en una posición seleccionada al azar. Cada cambio efectuado en el lienzo es grabado en un archivo para lecturas posteriores, por lo tanto cada vez que acceda a la aplicación el último trabajo realizado sobre el lienzo será mostrado en pantalla.

El documento HTML que vamos a crear es similar a la primera plantilla utilizada en este capítulo. Sin embargo, esta vez incluimos un elemento <canvas> dentro del elemento cajadatos:

<!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>Imágenes:<br><input type=»file» name=»archivos»

id=»archivos» multiple></p>

</form>

</section>

<section id=»cajadatos»>

<canvas id=»lienzo» width=»500″ height=»350″></canvas>

</section>

</body>

</html>

Listado 12-21. Nueva plantilla con el elemento <canvas>.

El código de este ejemplo incluye métodos y técnicas de programación con las que ya está familiarizado, pero la combinación de especificaciones puede resultar confusa al principio. Veamos primero el código y analicemos luego cada parte paso a paso:

function iniciar(){

var elemento=document.getElementById(‘lienzo’); lienzo=elemento.getContext(‘2d’) ;

var archivos=document.getElementById(‘archivos’); archivos.addEventListener(‘change’, procesar, false); window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024,

creardd, errores);

}

function creardd(sistema){ dd=sistema.root; cargarlienzo();

}

function errores(e){

alert(‘Error: ‘+e.code);

}

function procesar(e){

var archivos=e.target.files; for(var f=0;f<archivos.length;f++){ var archivo=archivos[f]; if(archivo.type.match(/image.*/i)){ var lector=new FileReader(); lector.onload=mostrar; lector.readAsDataURL(archivo);

}

}

}

function mostrar(e){

var resultado=e.target.result;

var imagen=new Image(); imagen.src=resultado;

imagen.addEventListener(«load», function(){ var x=Math.floor(Math.random()*451); var y=Math.floor(Math.random()*301); lienzo.drawImage(imagen,x,y,100,100) ; grabarlienzo();

}, false);

}

function cargarlienzo(){

dd.getFile(‘lienzo.dat’, {create: false}, function(entrada) { entrada.file(function(archivo){ var lector=new FileReader(); lector.onload=function(e){ var imagen=new Image() ; imagen.src=e.target.result;

imagen.addEventListener(«load», function(){ lienzo.drawImage(imagen,0,0);

}, false);

};

lector.readAsBinaryString(archivo);

}, errores);

}, errores);

}

function grabarlienzo(){

var elemento=document.getElementById(‘lienzo’); var info=elemento.toDataURL();

dd.getFile(‘lienzo.dat’, {create: true, exclusive: false},

function(entrada) {

entrada.createWriter(function(fileWriter){ var blob=new WebKitBlobBuilder() ; blob.append(info) ; fileWriter.write(blob.getBlob());

}, errores);

}, errores);

}

window.addEventListener(‘load’, iniciar, false);

Listado 12-22. Combinando API File y API Canvas.

En este ejemplo trabajamos con dos APIs: API File (con sus extensiones) y API Canvas. En la función iniciar(), ambas APIs son inicializadas. El contexto para el lienzo es generado primero usando getContext (), y el Sistema de Archivos es solicitado después por el método requestFileSystem().

Como siempre, una vez que el Sistema de Archivos está listo, la función creardd () es llamada y la variable dd es inicializada en esta función con una referencia al directorio raíz del Sistema de Archivos. Esta vez una llamada a una nueva función fue agregada al final de creardd () con el propósito de cargar el archivo conteniendo la imagen generada por la aplicación la última vez que fue ejecutada.

Veamos en primer lugar cómo la imagen grabada en el archivo mencionado es construida. Cuando el usuario selecciona un nuevo archivo de imagen desde el formulario, el evento change es disparado por el elemento <input> y la función procesar() es llamada. Esta función toma los archivos enviados por el formulario, extrae cada archivo del objeto File recibido, controla si se trata de una imagen o no, y en caso positivo lee el contenido de cada entrada con el método readAsDataURL(), retornando un valor en formato data:url.

Como puede ver, cada archivo es leído por la función procesar (), uno a la vez. Cada vez que una de estas operaciones es exitosa, el evento load es disparado y la función mostrar () es ejecutada.

La función mostrar () toma los datos del objeto lector (recibidos a través del evento), crea un objeto imagen con el constructor Image (), y asigna los datos leídos previamente como la fuente de esa imagen con la línea imagen.src=resultado.

Cuando trabajamos con imágenes siempre debemos considerar el tiempo que la imagen tarda en ser cargada en memoria. Por esta razón, luego de declarar la nueva fuente del objeto imagen agregamos una escucha para el evento load que nos permitirá procesar la imagen solo cuando fue completamente cargada. Cuando este evento es disparado, la función anónima declarada para responder al evento en el método addEventListener () es ejecutada. Esta función calcula una posición al azar para la imagen dentro del lienzo y la dibuja usando el método drawImage(). La imagen es reducida por este método a un tamaño fijo de 100×100 pixeles, sin importar el tamaño original (estudie la función mostrar () en el Listado 12-22 para entender cómo funciona todo el proceso).

Luego de que las imágenes seleccionadas son dibujadas, la función grabarlienzo() es llamada. Esta función se encargará de grabar el estado del lienzo cada vez que es modificado, permitiendo a la aplicación recuperar el último trabajo realizado la próxima vez que es ejecutada. El método de API Canvas llamado toDataURL() es usado para retornar el contenido del lienzo como data:url. Para procesar estos datos, varias operaciones son realizadas dentro de grabarlienzo(). Primero, los datos en formato data:url son almacenados dentro de la variable info. Luego, el archivo lienzo.dat es creado (si aún no existe) o abierto con getFile (). Si esta operación es exitosa, la entrada es tomada por una función anónima y el objeto FileWriter es creado por el método createWriter(). Si esta operación es exitosa, este método también llama a una función anónima donde el valor de la variable info (los datos sobre el estado actual del lienzo) son agregados a un objeto BlobBuilder y el blob dentro del mismo es finalmente escrito dentro del archivo lienzo.dat por medio de write ().

IMPORTANTE: En esta oportunidad no escuchamos ningún evento del objeto FileWriter porque no hay nada que necesitemos hacer en caso de éxito o error. Sin embargo, usted siempre puede aprovechar los eventos para reportar el estado de la operación en la pantalla o tener control total sobre cada parte del proceso.

Bien, es momento de volver a la función cargarlienzo (). Como ya mencionamos, esta función es llamada por la función creardd () tan pronto como la aplicación es cargada. Tiene el propósito de leer el archivo con el trabajo anterior y dibujarlo en pantalla. A este punto usted ya sabe de qué archivo estamos hablando y cómo es generado, veamos entonces cómo esta función restaura el último trabajo realizado sobre el lienzo.

La función cargarlienzo () carga el archivo lienzo. dat para obtener los datos en formato data:url generados la última vez que el lienzo fue modificado. Si el archivo no existe, el método getFile() retornará un error, pero cuando es encontrado el método ejecuta una función anónima que tomará la entrada y usará el método file() para generar un objeto File con estos datos. Este método, si es exitoso, también ejecuta una función anónima para leer el archivo y obtener su contenido como datos binarios usando readAsBinaryString (). El contenido obtenido, como ya sabemos, es una cadena de texto en formato data:url que debe ser asignado como fuente de una imagen antes de ser dibujado en el lienzo. Por este motivo, lo que hacemos dentro de la función anónima llamada por el evento load una vez que el archivo es completamente cargado, es crear un objeto imagen, declarar los datos obtenidos como la fuente de esta imagen, y (cuando la imagen es completamente cargada) dibujarla en el lienzo con drawImage ().

El resultado obtenido por esta pequeña pero interesante aplicación es sencillo: las imágenes seleccionadas desde el elemento <input> son dibujadas en el lienzo en una posición al azar y el estado del lienzo es preservado en un archivo. Si el navegador es cerrado, no importa por cuánto tiempo, la próxima vez que la aplicación es usada el archivo es leído, el estado previo del lienzo es restaurado y nuestro último trabajo sigue ahí, como si nunca lo hubiésemos abandonado. No es realmente un ejemplo útil, pero se puede apreciar su potencial.

Hágalo Usted Mismo: Usando la API Drag and Drop puede arrastrar y soltar archivos de imagen dentro del lienzo en lugar de cargar las imágenes desde el elemento <input>. Intente combinar el código del Listado 12-22 con algunos códigos del Capítulo 8 para integrar estas APIs.

Publicaciones Similares