Manipular archivos en JavaScript
File System Access API

File System Access API

Manipular archivos en JavaScript
File System Access API

Hasta hace relativamente poco, la única forma de manipular archivos desde el navegador implicaba manipularlos en el servidor. Es decir, si queríamos hacer cosas como generar un archivo o leer un archivo, estas eran cosas que se debían de hacer en el servidor Web. Ya que, por motivos de seguridad, no se consideraba recomendable que el navegador pudiera acceder directamente a los archivos del usuario para manipularlos.

Sin embargo, las necesidades de trasladar la carga de trabajo del servidor al navegador del usuario han llevado a la creación de un nuevo API JavaScript llamado File System Access API que nos va a permitir manipular directamente desde el navegador los archivos del usuario. Obviamente, para ello será necesario que el usuario proporcione permisos de acceso a nuestro sitio Web, de forma similar a cuando permitimos a una aplicación de nuestro teléfono móvil acceder a nuestros archivos.

Es importante tener en cuenta que a día de hoy, en Agosto de 2022, el soporte de este API en los navegadores Web es bastante limitado. De hecho, en algunos navegadores, como por ejemplo Firefox o la mayoría de navegadores para dispositivos móviles, el soporte es directamente nulo.

Finalmente, es recomendable que para comprender completamente el código que vamos a ver tengas experiencia en el uso de promesas en JavaScript, y sobretodo en el uso de async / await. El código ha sido probado con la última versión de Chrome sin problemas.

Como leer un archivo de texto con JavaScript

Para el siguiente ejemplo vamos a usar un botón para cargar el archivo y un textarea donde mostraremos el contenido de dicho archivo de texto.

HTML

<input id="cargar1" type="button" value="Cargar un único archivo" />
<textarea id="contenido" cols="30" rows="10"></textarea>

JavaScript

// ejemplo de como cargar un archivo de texto en un textarea
document.querySelector("#cargar1").addEventListener("click", async ()=>{ 
  try {
    // NOTA showOpenFilePicker nos devuelve un array!
    const referencias = await window.showOpenFilePicker({}); 

    // a partir de la referencia, obtenemos el archivo (casilla 0 del array)
    const archivo = await referencias[0].getFile();

    // a partir del archivo obtenemos su contenido
    const contenido = await archivo.text();				

    // mostramos contenido en el textarea 
    document.querySelector("#contenido").value = contenido;
  } catch(err) {
    console.log("Se ha producido un error o se ha cancelado la carga. " + err);
  }  			
});

Como podemos ver, es todo bastante sencillo. Pero, ¿qué sucede si queremos cargar otro tipo de archivos o queremos cargar múltiples archivos?

Cargar imágenes desde archivo en JavaScript

HTML

<input type="button" id="cargar2" value="Cargar múltiples archivos">

JavaScript

// ejemplo de como cargar múltiples archivos de imagen
document.querySelector("#cargar2").addEventListener("click", async ()=>{ 
  try {
    // showOpenFilePicker nos devuelve un array			
    const referencias = await window.showOpenFilePicker({
      types: [
        { description: 'Imágenes', accept: { 'image/*': ['.png', '.gif', '.jpeg', '.jpg'] } },
      ],
      excludeAcceptAllOption: true, // no permitir la opcion de "cualquier tipo de archivo" ?
      multiple: true // permitir elegir varios archivos ?
    });

    // por cada archivo...
    referencias.forEach(async (refer)=>{					
      // a partir de la referencia, obtenemos el archivo
      var archivo = await refer.getFile();

      // creamos un elemento imagen usando dicho archivo
      var img = document.createElement("img");
      img.src = URL.createObjectURL(archivo);
      img.alt = archivo.name;

      // añadimos dicho elemento imagen a la página
      document.body.append(img);				
    });				
  } catch(err) {
    console.log("Se ha producido un error o se ha cancelado la carga. " + err);
  }  			
});

Tal y como se puede observar, el método showOpenFilePicker() permite indicar un objeto con varias opciones. Entre ellas, podemos indicar los tipos de archivos aceptados, o si se permite o no la selección múltiple de archivos. Después, simplemente tenemos que recorrernos el array que devuelve este método para ir procesando cada archivo. En el caso de imágenes, podemos usar el método URL.createObjectURL() para obtener un URL que podemos usar en un elemento HTML o mediante CSS. Existen formas más avanzadas de leer archivos binarios, pero exceden las pretensiones de este artículo.

Como leer el contenido de una carpeta con JavaScript

Un método alternativo a showOpenFilePicker() es el método showDirectoryPicker() que en este caso nos va a permitir acceder al contenido de una carpeta del ordenador del usuario.

HTML

<input type="button" id="cargar3" value="Cargar carpeta">
<div id="directorio"></div>

JavaScript

// ejemplo de como cargar el contenido de un directorio
document.querySelector("#cargar3").addEventListener("click", async ()=>{
  try {
    // seleccionar un directorio
    const referencia = await window.showDirectoryPicker();

    // obtenemos el listado de archivos
    let html = "<ul>";
    for await (const archivo of referencia.values()) {
      html += "<li>";
      if (archivo.kind == "file")
        // es un archivo
        html += archivo.name;
      else
        // es una subcarpeta
        html += "<b>" + archivo.name.toUpperCase() + "</b>";
      html += "</li>";
    }
    html += "</ul>";
    document.querySelector("#directorio").innerHTML = html;

  } catch(err) {
    console.log("Se ha producido un error o se ha cancelado la carga. " + err);
  }
});

Tal y como se puede observar, una vez tenemos acceso a la carpeta, podemos obtener acceso a los archivos y subcarpetas que contiene. Si queremos cargar alguno de los archivos, podemos usar el método getFile() tal y como hemos visto en los anteriores ejemplos.

Bien, ya hemos visto como leer archivos y carpetas. Pero, ¿podemos crear archivos?

Como crear un archivo de texto en JavaScript

La forma de crear un archivo es casi idéntica a la que usamos para leerlo. La principal diferencia es que en este caso mediante el método createWritable() crearemos un objeto FileSystemWritableFileStream que nos permitirá guardar el contenido deseado en dicho archivo.

En el siguiente ejemplo, guardaremos a un archivo de texto lo que el usuario escriba en el textarea.

HTML

<textarea id="editor" cols="30" rows="10"></textarea>
<input type="button" id="guardar1" value="Guardar en un archivo">

JavaScript

// ejemplo de como guardar en un archivo de texto
document.querySelector("#guardar1").addEventListener("click", async ()=>{
  let referencia;
  try {
    referencia = await window.showSaveFilePicker(
      { types: [ { description: "Texto", accept: { "text/plain": [".txt"], } }, 
    ]});
  } catch(err) {
    console.log("Se ha producido un error o se ha cancelado el proceso. " + err);
    return;
  }  	

  try {
    // obtenemos el contenido
    const contenido = document.querySelector("#editor").value;
    // guardamos el archivo
    const archivo = await referencia.createWritable();				 
    await archivo.write(contenido);
    await archivo.close();				
  } catch(err) {
    alert("Error al guardar el archivo. " + err);
  }  								
});

¿Y si en lugar de guardar un archivo de texto, queremos crear una imagen y guardarla en un archivo?

Guardar una imagen a archivo en JavaScript

Para poder guardar una imagen, primero debemos de crearla usando el elemento <canvas> de HTML5.

HTML

<canvas id="dibujo"></canvas>
<input type="button" id="guardar2" value="Guardar imagen">

JavaScript

// ejemplo de como guardar una imagen
document.querySelector("#guardar2").addEventListener("click", async ()=>{
  let referencia;
  try {
    referencia = await window.showSaveFilePicker(
      { types: [ { description: "Imágenes", accept: { "image/PNG": [".png"], } }, 
    ]});
  } catch(err) {
    console.log("Se ha producido un error o se ha cancelado el proceso. " + err);
    return;
  } 

  try {
    // obtenemos el canvas y cambiamos su tamaño
    const canvas = document.querySelector("#dibujo");
    canvas.width = 300;
    canvas.height = 180;
    // dibujamos algo en el canvas
    const context = canvas.getContext("2d");
    context.fillStyle = "#AD1519";
    context.fillRect(0, 0, 300, 180);
    context.fillStyle = "#FABD00";
    context.fillRect(0, 45, 300, 90);
    // convertimos el canvas en un blob 
    // (en este método podemos indicar otro formato de imagen que no sea PNG)
    canvas.toBlob(async (blob)=>{
      // guardamos el blob a archivo
      const archivo = await referencia.createWritable();         
      await archivo.write(blob);
      await archivo.close();        
    });
      
  } catch(err) {
    alert("Error al guardar el archivo. " + err);
  } 
});

Ya hemos visto que podemos leer y guardar archivos, ¿podemos borrarlos? Si, se puede. Pero por lo general ni es necesario, ni es recomendable. Lo mejor es que, por seguridad, y si no existe un motivo de peso para lo contrario, sea el propio usuario el que realice de forma manual cualquier operación de borrado de archivos o carpetas.

Conclusiones

Como hemos podido ver, este nuevo API es bastante potente y nos permite crear todo tipo de aplicaciones que requieran manipular archivos desde el propio navegador, sin necesidad de ir enviando y recibiendo información hacia/desde el servidor Web, con toda la carga de trabajo que ello conlleva.

Esperemos que el soporte por parte de los navegadores vaya mejorando poco a poco y pronto sea un API que se pueda usar sin problemas en cualquier plataforma. Queda por ver si el potencial riesgo de seguridad que supone este API es gestionado correctamente por los navegadores y los usuarios.

Escribe un comentario