12- API File
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.
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.
<!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>
Plantilla para trabajar con los archivos del usuario.
El archivo CSS incluye estilos para esta plantilla y otros que vamos a usar más adelante:
#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;
}
Estilos para el formulario y la cajadatos.
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.
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);
Leyendo un archivo de texto.
Desde el campo archivos del documento HTML del Listado 12-1 el usuario puede
seleccionar un archivo para ser procesado. Para detectar esta acción, en la función
iniciar() del Listado 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, Listado 6-17. También puede encontrar un ejemplo de cómo trabajar
con múltiples archivos en el código del Listado 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 Listado 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 de los Listados 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.
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);
Cargando imágenes.
El ejemplo del Listado 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 Listado 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.
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);
Trabajando con blobs.
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 del Listado 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 del Listado 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.
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);
Usando eventos para controlar el proceso de lectura.
Con el código del Listado 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 Listado 12-1 y el código Javascript del
Listado 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().
Creando archivos
La parte principal de API File es útil para cargar y procesar archivos ubicados en el
ordenador del usuario, pero toma archivos que ya existen en el disco duro. No contempla
la posibilidad de crear nuevos archivos o directorios. Una extensión de esta API llamada
API File: Directories & System se hace cargo de esta situación. La API reserva un espacio
específico en el disco duro, un espacio de almacenamiento especial en el cual la aplicación
web podrá crear y procesar archivos y directorios exactamente como una aplicación de
escritorio lo haría. El espacio es único y solo accesible por la aplicación que lo creó.
IMPORTANTE: Al momento de escribir estas líneas Google Chrome es el único
navegador que ha implementado esta extensión de API File, pero no reserva
espacio de almacenamiento por defecto. Si intentamos ejecutar los siguientes
códigos un error QUOTA_EXCEEDED (cupo excedido) será mostrado. Para poder
usar API File: Directories & System, Chrome debe ser abierto con la siguiente
bandera: --unlimited-quota-for-files. Para incorporar esta bandera en
Windows, vaya a su escritorio, haga clic con el botón derecho del ratón sobre el
ícono de Google Chrome y seleccione la opción Propiedades. Dentro de la
ventana abierta verá un campo llamado Destino con la ruta y el nombre del
archivo del programa. Al final de esta línea agregue la bandera --unlimitedquota-
for-files. La ruta obtenida será similar a la siguiente:
C:\Usuarios\...\Chrome\Application\chrome.exe --unlimited-quota-for-files
Plantilla
Para probar esta parte de la API vamos a necesitar un nuevo formulario con un campo de
texto y un botón para crear y procesar archivos y directorios:
<!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>Nombre:<br><input type="text" name="entrada"
id="entrada" required></p>
<p><input type="button" name="boton" id="boton"
value="Aceptar"></p>
</form>
</section>
<section id="cajadatos">
No hay entradas disponibles
</section>
</body>
</html>
Nueva plantilla para File API: Directories & System.
Hágalo Usted Mismo: El documento HTML genera un nuevo formulario pero
preserva la misma estructura y estilos CSS. Solo necesita reemplazar el código
HTML anterior por el del Listado 12-7 y copiar los códigos Javascript dentro del
archivo file.js para probar los siguientes ejemplos.
IMPORTANTE: El atributo request fue incluido en el elemento <input>, pero
no será considerado en los códigos de este capítulo. Para volver efectivo el
proceso de validación, deberemos aplicar API Forms. Lea el código del Listado 10-
5, Capítulo 10, para encontrar un ejemplo sobre cómo hacerlo.
El disco duro
El espacio reservado para la aplicación es como un espacio aislado, una pequeña unidad
de disco duro con su propio directorio raíz y configuración. Para comenzar a trabajar con
esta unidad virtual, primero tenemos que solicitar que un Sistema de Archivos sea
inicializado para nuestra aplicación.
requestFileSystem(tipo, tamaño, función éxito, función error) Este método crea el Sistema
de Archivos del tamaño y tipo especificados por sus atributos. El valor del atributo tipo
puede ser TEMPORARY (temporario) o PERSISTENT (persistente) de acuerdo al tiempo
que deseamos que los archivos sean preservados. El atributo tamaño determina el
espacio total que será reservado en el disco duro para este Sistema de Archivos en bytes.
En caso de error o éxito, el método llama a las correspondientes funciones.
El método requestFileSystem() retorna un objeto FileSystem con dos propiedades:
root El valor de esta propiedad es una referencia al directorio raíz del Sistema de Archivos.
Este es también un objeto DirectoryEntry (Entrada de Directorio) y tiene los
métodos asignados a esta clase de objetos, como veremos más adelante. Usando esta
propiedad podemos referenciar el espacio de almacenamiento y por medio de esta
referencia trabajar con archivos y directorios.
name Esta propiedad retorna información acerca del Sistema de Archivos, como el nombre
asignado por el navegador y su condición.
function iniciar(){
cajadatos=document.getElementById('cajadatos');
var boton=document.getElementById('boton');
boton.addEventListener('click', crear, false);
window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024,
creardd, errores);
}
function creardd(sistema) {
dd=sistema.root;
}
function crear(){
var nombre=document.getElementById('entrada').value;
if(nombre!=''){
dd.getFile(nombre, {create: true, exclusive: false}, mostrar,
errores);
}
}
function mostrar(entrada){
document.getElementById('entrada').value='';
cajadatos.innerHTML='Entrada Creada!<br>';
cajadatos.innerHTML+='Nombre: '+entrada.name+'<br>';
cajadatos.innerHTML+='Ruta: '+entrada.fullPath+'<br>';
cajadatos.innerHTML+='Sistema: '+entrada.filesystem.name;
}
function errores(e){
alert('Error: '+e.code);
}
window.addEventListener('load', iniciar, false);
Creando nuestro propio Sistema de Archivos.
IMPORTANTE: Google Chrome es el único navegador en este momento con una
implementación funcional de esta parte de File API. Debido a que la implementación
es experimental, tuvimos que reemplazar el método requestFileSystem() por el
específico de Chrome webkitRequestFileSystem(). Usando este método, podrá
probar en su navegador los códigos para éste y los siguientes ejemplos. Una vez que
la etapa de experimentación esté finalizada podrá volver a usar el método original.
Usando el documento HTML del Listado 12-7 y el código del Listado 12-8, obtenemos
nuestra primera aplicación para trabajar con nuevos archivos en el ordenador del usuario.
El código llama al método requestFileSystem() para crear el Sistema de Archivos (u
obtener una referencia si el sistema ya existe), y si esta es la primera visita, el Sistema de
Archivos es creado como permanente, con un tamaño de 5 megabytes (5*1024*1024). En
caso de que esta última operación sea un éxito, la función creardd() es ejecutada,
continuando con el proceso de inicialización. Para controlar errores, usamos la función
errores(), del mismo modo que lo hicimos para otras APIs.
Cuando el Sistema de Archivos es creado o abierto, la función creardd() recibe un
objeto FileSystem y graba el valor de la propiedad root en la variable dd para
referenciar el directorio raíz más adelante.
Creando archivos
El proceso de iniciación del Sistema de Archivos ha sido finalizado. El resto de las funciones en
el código del Listado 12-8 crean un nuevo archivo y muestran los datos de la entrada (un
archivo o directorio) en la pantalla. Cuando el botón “Aceptar” es presionado en el formulario,
la función crear() es llamada. Esta función asigna el texto insertado en el elemento <input>
a la variable nombre y crea un archivo con ese nombre usando el método getFile().
Este último método es parte de la interface DirectoryEntry incluida en la API. La
interface provee un total de cuatro métodos para crear y manejar archivos y directorios:
getFile(ruta, opciones, función éxito, función error) Este método crea o abre un archivo.
El atributo ruta debe incluir el nombre del archivo y la ruta donde el archivo está
localizado (desde la raíz de nuestro Sistema de Archivos). Hay dos banderas que
podemos usar para configurar el comportamiento de este método: create y
exclusive. Ambas reciben valores booleanos. La bandera create (crear) indica si el
archivo será creado o no (en caso de que no exista, por supuesto), y la bandera
exclusive (exclusivo), cuando es declarada como true (verdadero), fuerza al
método getFile() a retornar un error si intentamos crear un archivo que ya existe.
Este método también recibe dos funciones para responder en case de éxito o error.
getDirectory(ruta, opciones, función éxito, función error) Este método tiene exactamente
las mismas características que el anterior pero es exclusivo para directorios (carpetas).
createReader() Este método retorna un objeto DirectoryReader para leer entradas
desde un directorio específico.
removeRecursively() Este es un método específico para eliminar directorios y todo su
contenido.
En el código del Listado 12-8, el método getFile() usa el valor de la variable nombre
para crear u obtener el archivo. El archivo será creado si no existe (create: true) o será
leído en caso contrario (exclusive: false). La función crear() también controla que
el valor de la variable nombre no sea una cadena vacía antes de ejecutar getFile().
El método getFile() usa dos funciones, mostrar() y errores(), para responder
al éxito o fracaso de la operación. La función mostrar() recibe un objeto Entry
(entrada) y muestra el valor de sus propiedades en la pantalla. Este tipo de objetos tiene
varios métodos y propiedades asociadas que estudiaremos más adelante. Por ahora
hemos aprovechado solo las propiedades name, fullPath y filesystem.
Creando directorios
El método getFile() (específico para archivos) y el método getDirectory()
(específico para directorios) son exactamente iguales. Para crear un directorio (carpeta)
en nuestro Sistema de Archivos del ejemplo anterior, solo tenemos que reemplazar el
nombre getFile() por getDirectory(), como es mostrado en el siguiente código:
function crear(){
var nombre=document.getElementById('entrada').value;
if(nombre!=''){
dd.getDirectory(nombre, {create: true, exclusive: false},
mostrar, errores);
}
}
Usando getDirectory() para crear un directorio.
Ambos métodos son parte del objeto DirectoryEntry llamado root, que estamos
representando con la variable dd, por lo que siempre deberemos usar esta variable para
llamar a los métodos y crear archivos y directorios en el Sistema de Archivos de nuestra
aplicación.
Hágalo usted mismo: Use la función en el Listado 12-9 para reemplazar la función
crear() del Listado 12-8 y así crear directorios en lugar de archivos. Suba los
archivos a su servidor, abra el documento HTML del Listado 12-7 en su navegador
y cree un directorio usando el formulario en la pantalla.
Listando archivos
Como mencionamos antes, el método createReader() nos permite acceder a una lista
de entradas (archivos y directorios) en una ruta específica. Este método retorna un objeto
DirectoryReader que contiene el método readEntries() para leer las entradas
obtenidas:
readEntries(función éxito, función error) Este método lee el siguiente bloque de entradas
desde el directorio seleccionado. Cada vez que el método es llamado, la función
utilizada para procesar operaciones exitosas retorna un objeto con la lista de entradas
o el valor null si no se encontró ninguna.
El método readEntries() lee la lista de entradas por bloque. Como consecuencia,
no existe garantía alguna de que todas las entradas serán retornadas en una sola llamada.
Tendremos que llamar al método tantas veces como sea necesario hasta que el objeto
retornado sea un objeto vacío.
Además, deberemos hacer otra consideración antes de escribir nuestro próximo
código. El método createReader() retorna un objeto DirectoryReader para un
directorio específico. Para obtener los archivos que queremos, primero tenemos que
obtener el correspondiente objeto Entry del directorio que queremos leer usando el ya
conocido método getDirectory():
function iniciar(){
cajadatos=document.getElementById('cajadatos');
var boton=document.getElementById('boton');
boton.addEventListener('click', crear, false);
window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024,
creardd, errores);
}
function creardd(sistema) {
dd=sistema.root;
ruta='';
mostrar();
}
function errores(e){
alert('Error: '+e.code);
}
function crear(){
var nombre=document.getElementById('entrada').value;
if(nombre!=''){
nombre=ruta+nombre;
dd.getFile(nombre, {create: true, exclusive: false}, mostrar,
errores);
}
}
function mostrar(){
document.getElementById('entrada').value='';
cajadatos.innerHTML='';
dd.getDirectory(ruta,null,leerdir,errores);
}
function leerdir(dir){
var lector=dir.createReader();
var leer=function(){
lector.readEntries(function(archivos){
if(archivos.length){
listar(archivos);
leer();
}
}, errores);
}
leer();
}
function listar(archivos){
for(var i=0; i<archivos.length; i++) {
if(archivos[i].isFile) {
cajadatos.innerHTML+=archivos[i].name+'<br>';
}else if(archivos[i].isDirectory){
cajadatos.innerHTML+='<span onclick=
"cambiardir(\''+archivos[i].name+'\')"
class="directorio">+'+archivos[i].name+'</span><br>';
}
}
}
function cambiardir(nuevaruta){
ruta=ruta+nuevaruta+'/';
mostrar();
}
window.addEventListener('load', iniciar, false);
Sistema de Archivos completo.
Este código no reemplazará al Explorador de Archivos de Windows, pero al menos
provee toda la información que necesitamos para entender cómo construir un Sistema de
Archivos útil y funcional para la web. Vamos a analizarlo parte por parte.
La función iniciar() hace lo mismo que en códigos previos: inicia o crea el Sistema
de Archivos y llama a la función creardd() si tiene éxito. Además de declarar la variable
dd para referenciar el directorio raíz de nuestro disco duro virtual, la función creardd()
también inicializa la variable ruta con una cadena de texto vacía (representando el
directorio raíz) y llama a la función mostrar() para mostrar la lista de entradas en
pantalla tan pronto como la aplicación es cargada.
La variable ruta será usada en el resto de la aplicación para conservar el valor de la
ruta actual dentro de Sistema de Archivos en la que el usuario está trabajando. Para
entender su importancia, puede ver cómo la función crear() fue modificada en el código
del Listado 12-10 para usar este valor y así crear nuevos archivos en la ruta
correspondiente (dentro del directorio seleccionado por el usuario). Ahora, cada vez que
un nuevo nombre de archivo es enviado desde el formulario, la ruta es agregada al
nombre y el archivo es creado en el directorio actual.
Como ya explicamos, para mostrar la lista de entradas, debemos primero abrir el
directorio a ser leído. Usando el método getDirectory() en la función mostrar(), el
directorio actual (de acuerdo a la variable ruta) es abierto y una referencia a este
directorio es enviada a la función leerdir() si la operación es exitosa. Esta función
guarda la referencia en la variable dir, crea un nuevo objeto DirectoryReader para el
directorio actual y obtiene la lista de entradas con el método readEntries().
En leerdir(), funciones anónimas son usadas para mantener el código organizado y no
superpoblar el entorno global. En primer lugar, createReader() crea un objeto
DirectoryReader para el directorio representado por dir. Luego, una nueva función
llamada leer() es creada dinámicamente para leer las entradas usando el método
readEntries(). Este método lee las entradas por bloque, lo que significa que debemos
llamarlo varias veces para asegurarnos de que todas las entradas disponibles en el directorio
son leídas. La función leer() nos ayuda a lograr este propósito. El proceso es el siguiente: al
final de la función leerdir(), la función leer() es llamada por primera vez. Dentro de la
función leer() llamamos al método readEntries(). Este método usa otra función anónima
en caso de éxito para recibir el objeto files y procesar su contenido (archivos). Si este
objeto no está vacío, la función listar() es llamada para mostrar en pantalla las entradas
leídas, y la función leer() es ejecutada nuevamente para obtener el siguiente bloque de
entradas (la función se llama a sí misma una y otra vez hasta que ninguna entrada es
retornada).
La función listar() está a cargo de imprimir la lista de entradas (archivos y
directorios) en pantalla. Toma el objeto files y comprueba las características de cada
entrada usando dos propiedades importantes de la interface Entry: isFile e
isDirectory. Como sus nombres en inglés indican, estas propiedades contienen valores
booleanos para informar si la entrada es un archivo o un directorio. Luego de que esta
condición es controlada, la propiedad name es usada para mostrar información en la
pantalla.
Existe una diferencia en cómo nuestra aplicación mostrará un archivo o un directorio
en la pantalla. Cuando una entrada es detectada como directorio, es mostrada a través de
un elemento <span> con un manejador de eventos onclick que llamará a la función
cambiardir() si el usuario hace clic sobre el mismo. El propósito de esta función es
declarar la nueva ruta actual para apuntar al directorio seleccionado. Recibe el nombre del
directorio, agrega el directorio al valor de la variable ruta y llama a la función mostrar()
para actualizar la información en pantalla (ahora deberían verse las entradas dentro del
nuevo directorio seleccionado). Esta característica nos permite abrir directorios y ver su
contenido con solo un clic del ratón, exactamente como una explorador de archivos
común y corriente haría.
Este ejemplo no contempla la posibilidad de retroceder en la estructura para ver el
contenido de directorios padres. Para hacerlo, debemos aprovechar otro método provisto
por la interface Entry:
getParent(función éxito, función error) Este método retorna un objeto Entry del directorio
que contiene la entrada seleccionada. Una vez que obtenemos el objeto Entry podemos
leer sus propiedades para obtener toda la información acerca del directorio padre de esa
entrada en particular.
Cómo trabajar con el método getParent() es simple: supongamos que una
estructura de directorios como fotos/misvacaciones fue creada y el usuario está
listando el contenido de misvacaciones en este momento. Para regresar al directorio
fotos, podríamos incluir un enlace en el documento HTML con un manejador de eventos
onclick que llame a la función encargada de modificar la ruta actual para apuntar a esta
nueva dirección (el directorio fotos). La función llamada al hacer clic sobre el enlace
podría ser similar a la siguiente:
function volver(){
dd.getDirectory(ruta,null,function(dir){
dir.getParent(function(padre){
ruta=padre.fullPath;
mostrar();
}, errores);
},errores);
}
Regresando al directorio padre.
La función volver() en el Listado 12-11 cambia el valor de la variable ruta para
apuntar al directorio padre del directorio actual. Lo primero que hacemos es obtener una
referencia del directorio actual usando el método getDirectory(). Si la operación es
exitosa, una función anónima es ejecutada. En esta función, el método getParent() es
usado para encontrar el directorio padre del directorio referenciado por dir (el directorio
actual). Si esta operación es exitosa, otra función anónima es ejecutada para recibir el
objeto padre y declarar el valor de la ruta actual igual al valor de la propiedad fullPath
(esta propiedad contiene la ruta completa hacia el directorio padre). La función
mostrar() es llamada al final del proceso para actualizar la información en pantalla
(mostrar las entradas ubicadas en la nueva ruta).
Por supuesto, esta aplicación puede ser extremadamente enriquecida y mejorada,
pero eso es algo que dejamos en sus manos.
Hágalo Usted Mismo: Agregue la función del Listado 12-11 al código del Listado
12-10 y cree un enlace en el documento HTML para llamar a esta función (por
ejemplo, <span onclick="volver()">volver</span>).
Manejando archivos
Ya mencionamos que la interface Entry incluye un grupo de propiedades y métodos para
obtener información y operar archivos. Muchas de las propiedades disponibles ya fueron
usadas en previos ejemplos. Ya aprovechamos las propiedades isFile e isDirectory
para comprobar la clase de entrada, y también usamos los valores de name, fullPath y
filesystem para mostrar información sobre la entrada en pantalla. El método
getParent(), estudiado en el último código, es también parte de esta interface. Sin
embargo, existen todavía algunos métodos más que son útiles para realizar operaciones
comunes sobre archivos y directorios. Usando estos métodos podremos mover, copiar y
eliminar entradas exactamente como en cualquier aplicación de escritorio:
moveTo(directorio, nombre, función éxito, función error) Este método mueve una entrada
a una ubicación diferente en el Sistema de Archivos. Si el atributo nombre es provisto, el
nombre de la entrada será cambiado a este valor.
copyTo(directorio, nombre, función éxito, función error) Este método genera una copia
de una entrada en otra ubicación dentro del Sistema de Archivos. Si el atributo nombre
es provisto, el nombre de la nueva entrada será cambiado a este valor.
remove() Este método elimina un archivo o un directorio vacío (para eliminar un directorio con
contenido, debemos usar el método removeRecursively() presentado anteriormente).
Necesitaremos una nueva plantilla para probar estos métodos. Para simplificar los
códigos, vamos a crear un formulario con solo dos campos, uno para el origen y otro para
el destino de cada operación:
<!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>Origen:<br><input type="text" name="origen" id="origen"
required></p>
<p>Destino:<br><input type="text" name="destino"
id="destino" required></p>
<p><input type="button" name="boton" id="boton"
value="Aceptar"></p>
</form>
</section>
<section id="cajadatos"></section>
</body>
</html>
nueva plantilla para operar con archivos
Moviendo
El método moveTo() requiere un objeto Entry para el archivo y otro para el directorio en
donde el archivo será movido. Por lo tanto, primero tenemos que crear una referencia al
archivo que vamos a mover usando getFile(), luego obtenemos la referencia del directorio
destino con getDirectory(), y finalmente aplicamos moveTo() con esta información:
function iniciar(){
cajadatos=document.getElementById('cajadatos');
var boton=document.getElementById('boton');
boton.addEventListener('click', modificar, false);
window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024,
creardd, errores);
}
function creardd(sistema){
dd=sistema.root;
ruta='';
mostrar();
}
function errores(e){
alert('Error: '+e.code);
}
function modificar(){
var origen=document.getElementById('origen').value;
var destino=document.getElementById('destino').value;
dd.getFile(origen,null,function(archivo){
dd.getDirectory(destino,null,function(dir){
archivo.moveTo(dir,null,exito,errores);
},errores);
},errores);
}
function exito(){
document.getElementById('origen').value='';
document.getElementById('destino').value='';
mostrar();
}
function mostrar(){
cajadatos.innerHTML='';
dd.getDirectory(ruta,null,leerdir,errores);
}
function leerdir(dir){
var lector=dir.createReader();
var leer=function(){
lector.readEntries(function(archivos){
if(archivos.length){
listar(archivos);
leer();
}
}, errores);
}
leer();
}
function listar(archivos){
for(var i=0; i<archivos.length; i++) {
if(archivos[i].isFile) {
cajadatos.innerHTML+=archivos[i].name+'<br>';
}else if(archivos[i].isDirectory){
cajadatos.innerHTML+='<span onclick=
"cambiardir(\''+archivos[i].name+'\')"
class="directorio">+'+archivos[i].name+'</span><br>';
}
}
}
function cambiardir(nuevaruta){
ruta=ruta+nuevaruta+'/';
mostrar();
}
window.addEventListener('load', iniciar, false);
Moviendo archivos.
En este último ejemplo, usamos funciones de códigos previos para crear o abrir nuestro
Sistema de Archivos y mostrar el listado de entradas en pantalla. La única función nueva en el
Listado 12-13 es modificar(). Esta función toma los valores de los campos del formulario
origen y destino y los utiliza para abrir el archivo original y luego, si la operación es exitosa,
abrir el directorio de destino. Si ambas operaciones son exitosas el método moveTo() es
aplicado sobre el objeto file y el archivo es movido al directorio representado por dir. Si
esta última operación es exitosa, la función exito() es llamada para vaciar los campos en el
formulario y actualizar las entradas en pantalla ejecutando la función mostrar().
Hágalo usted mismo: Para probar este ejemplo necesita un archivo HTML con la
plantilla del Listado 12-12, el archivo CSS usado desde el comienzo de este capítulo,
y un archivo llamado file.js con el código del Listado 12-13 (recuerde subir los
archivos a su servidor). Cree archivos y directorios usando códigos previos para
tener entradas con las que trabajar. Utilice el formulario del último documento
HTML para insertar los valores del archivo a ser movido (con la ruta completa desde
la raíz) y el directorio en el cual el archivo será movido (si el directorio se encuentra
en la raíz del Sistema de Archivos no necesita usar barras, solo su nombre).
Copiando
Por supuesto, la única diferencia entre el método moveTo() y el método copyTo() es
que el último preserva el archivo original. Para usar el método copyTo(), solo debemos
cambiar el nombre del método en el código del Listado 12-13. La función modificar()
quedará como en el siguiente ejemplo:
function modificar(){
var origen=document.getElementById('origen').value;
var destino=document.getElementById('destino').value;
dd.getFile(origen,null,function(archivo){
dd.getDirectory(destino,null,function(dir){
archivo.copyTo(dir,null,exito,errores);
},errores);
},errores);
}
Hágalo usted mismo: Reemplace la función modificar() del Listado 12-13 con
esta última y abra la plantilla del Listado 12-12 para probar el código. Para copiar
un archivo, debe repetir los pasos usados previamente para moverlo. Inserte la
ruta del archivo a copiar en el campo origen y la ruta del directorio donde desea
generar la copia en el campo destino.
Eliminando
Eliminar archivos y directorio es incluso más sencillo que mover y copiar. Todo lo que
tenemos que hacer es obtener un objeto Entry del archivo o directorio que deseamos
eliminar y aplicar el método remove() a esta referencia:
function modificar(){
var origen=document.getElementById('origen').value;
var origen=ruta+origen;
dd.getFile(origen,null,function(entrada){
entrada.remove(exito,errores);
},errores);
}
Eliminando archivos y directorios.
El código del Listado 12-15 solo utiliza el valor del campo origen del formulario. Este
valor, junto con el valor de la variable ruta, representará la ruta completa de la entrada
que queremos eliminar. Usando este valor y el método getFile() creamos un objeto
Entry para la entrada y luego aplicamos el método remove().
Hágalo usted mismo: Reemplace la función modificar() en el código del Listado
12-13 con la nueva del Listado 12-15. Esta vez solo necesita ingresar el valor del
campo origen para especificar el archivo a ser eliminado.
Para eliminar un directorio en lugar de un archivo, el objeto Entry debe ser creado
para ese directorio usando getDirectory(), pero el método remove() trabaja
exactamente igual sobre un tipo de entrada u otro. Sin embargo, debemos considerar una
situación con respecto a la eliminación de directorios: si el directorio no está vacío, el
método remove() retornará un error. Para eliminar un directorio y su contenido, todo al
mismo tiempo, debemos usar otro método mencionado anteriormente llamado
removeRecursively():
function modificar(){
var destino=document.getElementById('destino').value;
dd.getDirectory(destino,null,function(entrada){
entrada.removeRecursively(exito,errores);
},errores);
}
Eliminando directorios no vacíos.
En la función del Listado 12-16 usamos el valor del campo destino para indicar el
directorio a ser eliminado. El método removeRecursively() eliminará el directorio y su
contenido en una sola ejecución y llamará a la función exito() si la operación es
realizada con éxito.
Hágalo usted mismo: Las funciones modificar() presentadas en los Listados 12-
14, 12-15 y 12-16 fueron construidas para reemplazar la misma función en el
Listado 12-13. Para ejecutar estos ejemplos, utilice el código del Listado 12-13,
reemplace la función modificar() por la que quiere probar y abra la plantilla del
Listado 12-12 en su navegador. De acuerdo al método elegido, deberá ingresar uno
o dos valores en el formulario.
IMPORTANTE: Si tiene problemas para ejecutar estos ejemplos, le recomendamos
usar la última versión disponible del navegador Chromium (www.chromium.org). Los
códigos para esta parte de File API fueron también probados con éxito en Google
Chrome.
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.
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.js"></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>
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(){
cajadatos=document.getElementById('cajadatos');
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='';
cajadatos.innerHTML='Hecho!';
}
window.addEventListener('load', iniciar, false);
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());
}
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(){
cajadatos=document.getElementById('cajadatos');
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){
cajadatos.innerHTML='Nombre: '+archivo.name+'<br>';
cajadatos.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='';
cajadatos.innerHTML+='Contenido: '+resultado;
}
window.addEventListener('load', iniciar, false);
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.
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>
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);
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 100x100 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.
Referencia rápida
Del mismo modo que la API IndexedDB, las características de API File y sus extensiones
fueron organizadas en interfaces. Cada interface provee métodos, propiedades y eventos
que trabajan combinados con el resto para ofrecer diferentes alternativas con las que
crear, leer y procesar archivos. En esta referencia rápida vamos a presentar todas las
características estudiadas en este capítulo en un orden acorde a esta organización oficial.
IMPORTANTE: Las descripciones presentadas en esta referencia rápida solo
muestran los aspectos más relevantes de cada interface. Para estudiar la
especificación completa, visite nuestro sitio web y siga los enlaces correspondientes
a este capítulo.
Interface Blob (API File)
Esta interface provee propiedades y métodos para operar con blobs. Es heredada por la
interface File.
size Esta propiedad retorna el tamaño del blob o el archivo en bytes.
type Esta propiedad retorna el tipo de medio dentro de un blob o archivo.
slice(comienzo, largo, tipo) Este método retorna la parte del blob o archivo indicada por
los valores en bytes de los atributos comienzo y largo.
Interface File (API File)
Esta interface es una extensión de la interface Blob para procesar archivos.
name Esta propiedad retorna el nombre del archivo.
Interface FileReader (API File)
Esta interface provee métodos, propiedades y eventos para cargar blobs y archivos en
memoria.
readAsArrayBuffer(archivo) Este método retorna el contenido de blobs o archivos en el
formato ArrayBuffer.
readAsBinaryString(archivo) Este método retorna el contenido de blobs o archivos como
una cadena binaria.
readAsText(archivo) Este método interpreta el contenido de blobs o archivos y lo retorna
en formato texto.
readAsDataURL(archivo) Este método retorna el contenido de blobs o archivos en el
formato data:url.
abort() Este método aborta el proceso de lectura.
result Esta propiedad representa los datos retornados por los métodos de lectura.
loadstart Este evento es disparado cuando la lectura comienza.
progress Este evento es disparado periódicamente para reportar el estado del proceso de
lectura.
load Este evento es disparado cuando el proceso de lectura es finalizado.
abort Este evento es disparado cuando el proceso de lectura es abortado.
error Este evento es disparado cuando un error ocurre en el proceso.
loadend Este evento es disparado cuando la carga del archivo es finalizada, haya sido el
proceso exitoso o no.
Interface LocalFileSystem (API File: Directories and System)
Esta interface es provista para iniciar un Sistema de Archivos para la aplicación.
requestFileSystem(tipo, tamaño, función éxito, función error) Este método solicita la
inicialización de un Sistema de Archivos configurado de acuerdo a los valores de sus
atributos. El atributo tipo puede recibir dos valores diferentes: TEMPORARY (temporario) o
PERSISTENT (persistente). El tamaño debe ser especificado en bytes.
Interface FileSystem (API File: Directories and System)
Esta interface provee información acerca del Sistema de Archivos.
name Esta propiedad retorna el nombre del Sistema de Archivos.
root Esta propiedad retorna una referencia el directorio raíz del Sistema de Archivos.
Interface Entry (API File: Directories and System)
Esta interface provee métodos y propiedades para procesar entradas (archivos y directorios)
en el Sistema de Archivos.
isFile Esta propiedad es un valor booleano que indica si la entrada es un archivo o no.
isDirectory Esta propiedad es un valor booleano que indica si la entrada es un directorio o no.
name Esta propiedad retorna el nombre de la entrada.
fullPath Esta propiedad retorna la ruta completa de la entrada desde el directorio raíz del
Sistema de Archivos.
filesystem Esta propiedad contiene una referencia al Sistema de Archivos.
moveTo(directorio, nombre, función éxito, función error) Este método mueve una entrada a
una ubicación diferente dentro del Sistema de Archivos. El atributo directorio
representa el directorio dentro del cual la entrada será movida. El atributo nombre, si es
especificado, cambia el nombre de la entrada en la nueva ubicación.
copyTo(directorio, nombre, función éxito, función error) Este método genera una copia
de la entrada dentro del Sistema de Archivos. El atributo directorio representa el
directorio dentro del cual la copia de la entrada será creada. El atributo nombre, si es
especificado, cambia el nombre de la copia.
remove(función éxito, función error) Este método elimina un archivo o un directorio vacío.
getParent(función éxito, función error) Este método retorna el objeto DirectoryEntry
padre de la entrada seleccionada.
Interface DirectoryEntry (API File: Directories and System)
Esta interface provee métodos para crear y leer archivos y directorios.
createReader() Este método crear un objeto DirectoryReader para leer entradas.
getFile(ruta, opciones, función éxito, función error) Este método crea o lee el archivo
indicado por el atributo ruta. El atributo opciones es declarado por dos banderas:
create (crear) y exclusive (exclusivo). La primera indica si el archivo será creado o
no, y la segunda, cuando es declarada como true (verdadero), fuerza al método a
retornar un error si el archivo ya existe.
getDirectory(ruta, opciones, función éxito, función error) Este método crea o lee el
directorio indicado por el atributo ruta. El atributo opciones es declarado por dos
banderas: create (crear) y exclusive (exclusivo). La primera indica si el directorio
será creado o no, y la segunda, cuando es declarada como true (verdadero), fuerza al
método a retornar un error si el directorio ya existe.
removeRecursively(función éxito, función error) Este método elimina un directorio y todo
su contenido.
Interface DirectoryReader (API File: Directories and System)
Esta interface ofrece la posibilidad de obtener una lista de entradas en un directorio
específico.
readEntries(función éxito, función error) Este método lee un bloque de entradas desde el
directorio seleccionado. Retorna el valor null si no se encuentran más entradas.
Interface FileEntry (API File: Directories and System)
Esta interface provee métodos para obtener un objeto File para un archivo específico y
un objeto FileWriter para poder agregar contenido al mismo.
createWriter(función éxito, función error) Este método crea un objeto FileWriter para
escribir contenido dentro de un archivo.
file(función éxito, función error) Este método retorna un objeto File que representa el
archivo seleccionado.
Interface BlobBuilder (API File: Writer)
Esta interface provee métodos para trabajar con objetos blob.
getBlob(tipo) Este método retorna el contenido de un objeto blob como un blob.
append(datos) Este método agrega datos a un objeto blob. La interface provee tres métodos
append() diferentes para agregar datos en forma de texto, blob, o como un ArrayBuffer.
Interface FileWriter (API File: Writer)
La interface FileWriter es una expansión de la interface FileSaver. La última no es descripta
aquí, pero los eventos listados debajo son parte de ella.
position Este propiedad retorna la posición actual en la cual se realizará la siguiente
escritura.
length Esta propiedad retorna el largo del archivo en bytes.
write(blob) Este método escribe contenido en un archivo.
seek(desplazamiento) Este método especifica una nueva posición en la cual se realizará la
siguiente escritura.
truncate(tamaño) Este método cambia el largo del archivo al valor del atributo tamaño
(en bytes).
writestart Este evento es disparado cuando la escritura comienza.
progress Este evento es disparado periódicamente para informar sobre el estado del
proceso de escritura.
write Este evento es disparado cuando el proceso de escritura es finalizado.
abort Este evento es disparado cuando el proceso de escritura es abortado.
error Este evento es disparado si ocurre un error en el proceso de escritura.
writeend Este evento es disparado cuando la solicitud es finalizada, haya sido exitosa o no.
Interface FileError (API File y extensiones)
Varios métodos en esta API retornan un valor a través de una función para indicar errores
en el proceso. Este valor puede ser comparado con la siguiente lista para encontrar el
error correspondiente:
NOT_FOUND_ERR - valor 1.
SECURITY_ERR - valor 2.
ABORT_ERR - valor 3.
NOT_READABLE_ERR - valor 4.
ENCODING_ERR - valor 5
NO_MODIFICATION_ALLOWED_ERR - valor 6.
INVALID_STATE_ERR - valor 7.
SYNTAX_ERR - valor 8.
INVALID_MODIFICATION_ERR - valor 9.
QUOTA_EXCEEDED_ERR - valor 10.
TYPE_MISMATCH_ERR - valor 11.
PATH_EXISTS_ERR - valor 12.