Cuadros de diálogo de tamaño dinámico en Apps Script

Antes de comenzar, tengo que decir que la sencilla prueba de concepto que motiva este artículo estaba originalmente destinada a vivir y morir en 280 caracteres 🐦.

Pero como últimamente tengo la sensación de que algunos de mis tuits parece que llevan una camiseta varias tallas por debajo de la que realmente les quedaría bien, he decidido escribir estas líneas para no tener que ser tan telegráfico y poder aportar algo de contexto, especialmente a aquellos usuarios que se están iniciando en el uso de esta estupenda herramienta de automatización que es Google Apps Script.

Y ejem, lo que ha salido ya no cabe en 280 caracteres ni de c*ña, aunque creo que si te estás iniciando en GAS pero palabros como HTML, CSS o JavaScript te suenan poco o nada tal vez pueda resultarte una lectura interesante.

En este artículo repasaremos los distintos modos en los que un script GAS puede relacionarse con el usuario que lo utiliza y mostraremos un modo sencillo de modificar de manera dinámica el tamaño de los cuadros de diálogo construidos mediante el Servicio HTML de Apps Script. Para lograrlo, introduciremos algunos conceptos básicos relativos al modelo de objetos de los documentos HTML y a su manipulación mediante JavaScript.

TABLA DE CONTENIDOS

De escapadas rurales, alcornoques y Apps Script

Hace unos pocos fines de semana mi familia decidió arrancarme del teclado y llevarme de pateo por los recónditos parajes de la Serra d'Espadà, en la que fue una escapadita rural bastante encantadora que se prolongó durante todo el sábado y buena parte del domingo.

Selfie en la Serra d'Espadà
Estoy fuera de mi casa. No es un montaje. Y hasta sonrío (ligeramente, no nos vengamos arriba).

Por ponerte en contexto, querido lector o lectora, tengo que decirte que aún no hemos llegado al punto de que sea necesario disolver subrepticiamente tranquilizantes en mi cortado mañanero para arrancarme de mi habitat natural, mi zona de trabajo doméstica.  Aunque la cosa anda cerca.

Afortunadamente, casi nada (tampoco las drogas que anulan la voluntad) puede hacerme pernoctar fuera de casa sin mi leal portátil LG Gram que, con sus 15 pulgadas alojadas en un chasis de magnesio de durabilidad militar de 1.095 gramos, está siempre dispuesto a acompañarme en cualquier emergencia ruralita (que no exija curro de GPU intenso, claro está).

Pero vamos al grano.

Paseando, camino arriba, camino abajo, entre los alcornoques, pinos, tejos y castaños de los alrededores de Sueras, recordé que uno de los participantes en el último GASmeets de Apps Script Ñ le sugirió a nuestro invitado, Milton Slonim, que el cuadro de diálogo que abre su excelente complemento Formula Studio fuera más grande. Todo sea por poder parir fórmulas más complejas, por supuesto.

Captura de pantalla del editor de Formula Studio
Editor de Formula Studio (generado con Bootstrap dentro de un custom dialog de Apps Script).

Por esa razón, tras la estupenda y probablemente excesiva cena del sábado por la noche (por cierto, de Carnaval) en el único hotelito de Sueras (no, no me llevo comisión), me dio por jugar un rato con Apps Script y echarle un tiento a eso de toquetear al vuelo el tamaño de los cuadros de diálogo en los scripts vinculados a los editores de documentos de Google.

¿Cómo interacciona un Apps Script con el usuario?

Antes de entrar en materia, hablemos un poco de los elementos que Apps Script nos ofrece, en el contexto de los editores de documentos, para posibilitar la interacción con el usuario.

👉 Menús personalizados

Menú personalizado.

Seguro que los conoces, se trata de los los típicos comandos que aparecen integrados en el extremo derecho del menú principal del editor y que permiten ejecutar determinadas funciones pertenecientes al código del script subyacente.

Lo de arriba se construye con unas pocas líneas de código como estas:


// Genera el menú del script en el editor de documentos
function onOpen() {

  DocumentApp.getUi().createMenu('☑️ DocList-2-Tasks')
    .addItem('✊ Selección a tareas (mías y sin asignar)', 'generarTareasMiasMas')
    .addItem('☝️ Selección a tareas (solo mías)', 'generarTareasMias')
    .addItem('❓ Selección a tareas (solo sin asignar)', 'generarTareasSinAsignar')
    .addItem('🖐 Selección a tareas (todas)', 'generarTareasTodas')
    .addItem('🪄 Autodetectar', 'smartSelect')
    .addToUi();

}
    

⚠️ Alertas

Se utilizan para mostrar en pantalla un mensaje informativo acompañado de un conjunto de botones a escoger entre los característicos ACEPTAR (predeterminado) / CANCELAR / SÍ / NO y un texto de título (opcional) de mayor tamaño.

Se trata de un elemento de tipo exclusivamente modal, es decir, hasta que no cierras la alerta el script que la ha generado queda congelado.

El código necesario es aún más sencillo. En esta ocasión no se han incluido botones alternativos ni título. El método alert() devuelve un valor que permite determinar el botón pulsado.


boton = DocumentApp.getUi().alert('¡No hay nada seleccionado!');
    

❓ Preguntas

Ejemplo de pregunta (prompt).

Son similares a las alertas anteriores, también modales, pero en este caso incluyen además una caja de texto de una sola línea (lo bueno, si breve...) para que el usuario introduzca información.

El código es casi idéntico, aunque ahora el objeto devuelto por el método prompt() contiene tanto el botón que ha presionado (al igual que en las alertas), como el texto introducido por el usuario.


const ui = DocumentApp.getUi();
const respuesta = ui.prompt(`[${tareas.length}] tareas reconocidas`, 'Nombre de la lista de tareas o en blanco para usar la primera disponible.', ui.ButtonSet.OK_CANCEL);
const texto = respuesta.getResponseText();
const boton = respuesta.getSelectedButton();
    

🪟 Cuadros de diálogo personalizados

Ejemnplo de cuadro de diálogo HTML.

Llegamos a la crème de la crème de los elementos de interacción.

Estos artilugios muestran contenido en un panel flotante, de tamaño personalizable, que se construye utilizando código HTML, CSS y JavaScript (JS para los amigos).

Para que te hagas una idea, estos cuadros de diálogo son algo así como páginas web en miniatura que aparecen por encima del documento y despliegan una interfaz de usuario rica e interactiva, construida con toda la libertad que otorgan las tecnologías citadas.

Pueden ser modales (lo más habitual) o no modales, pero en ambos casos el código JS en su interior puede realizar llamadas a otras funciones que forman parte del script incrustado en el documento, hoja de cálculo o presentación, así como intercambiar datos con ellos utilizando la mágica API de cliente google.script.run.

☝ Si no estás familiarizado con esto de la comunicación cliente / servidor quizás quieras echarle un vistazo a alguno de estos artículos donde hablo de ella:
📌 Evaluando con matrices de comprobación (parte II)
📌 Una aplicación web de consulta de notas con Google Apps Script
📌 RecogeCV, una webapp de recogida de currículums

🔲 Paneles laterales personalizados

Ejemplo panel lateral personalizado.

Se trata de elementos que tienen la misma naturaleza que los cuadros de diálogo de los que acabamos de hablar, aunque con ciertas salvedades:

  • Aparecen en un panel situado en el lateral derecho de la ventana del editor.
  • Tienen una anchura fija predeterminada de 300 puntos.
  • Son de tipo no modal, por lo que es posible seguir trabajando con el documento que tengamos abierto.

Estos cinco artefactos, cuyas particularidades yo no he hecho sino resumir en plan muy rápido, están muy bien descritos en la documentación para desarrolladores de Google, en la que además encontrarás numerosos fragmentos de código de ejemplo:

El código necesario para construir cuadros de diálogos y  paneles laterales personalizados puede ser relativamente sencillo o decididamente complicado. En lo que sigue nos centraremos exclusivamente en los primeros, los cuadros de diálogo personalizados (por cierto, qué nefasta traducción del inglés).

Creando un cuadro de diálogo (mondo y lirondo)

Lo primero que necesitamos es un archivo que contenga código HTML. Lo añadiremos de este modo al editor Apps Script.

Menú para crear un archivo HTML en el IDE Apps Script.
Los archivos HTML se crean así de fácil en el editor Apps Script.

Pero ese archivo, de entrada, no nos sirve para nada. Apps Script no diferencia per se estos archivos de los que contienen código nativo Apps Script, por lo que tendremos que explicarle que es así utilizando el método:

HtmlService.createHtmlOutputFromFile(nombre_de_archivo)

Este método devuelve un objeto de tipo HtmlOutput, que expone a su vez una serie de métodos set* para realizar diversos ajustes sobre él. De manera específica, usaremos los métodos...

  • setWidth(anchura_px)
  • setHeight(altura_px)

...para establecer el tamaño inicial, en píxeles, de la ventana en la que se visualizará el contenido HTML del cuadro de diálogo.

Por último, hay que activar el cuadro de diálogo para hacer que se muestre en pantalla. Dependiendo de su modo de funcionamiento (modal o no modal) usaremos el método apropiado de la clase Ui.

  • showModalDialog(HtmlOutput, título) → Ventana modal.
  • showModelessDialog(HtmlOutput, título) → Ventana no modal.
Captura de pantalla con código que abre un diálogo modal personalizado.
Cuatro líneas bastan para abrir un cuadro de diálogo personalizado. La "salsa" la encontraremos  dentro del archivo Dialog.html.

Supongamos, por ejemplo, que el archivo Dialog.html tiene el siguiente contenido:



El resultado al ejecutar la función dynamicDialog():


function dynamicDialog() {
  
  const dialog = HtmlService.createHtmlOutputFromFile('Dialog')
    .setWidth(500)
    .setHeight(300);
  SpreadsheetApp.getUi().showModalDialog(dialog, 'Hi there!');
  
}

Será este:

Cuadro de diálogo personalizado con contenido HTML de ejemplo.
Poca cosa, ¿verdad?

Fíjate en el panel flotante, no muestra ningún tipo de indicación visual que nos haga suponer que podemos hacer clic con la rata en sus bordes y arrastrarlos para ajustar su tamaño. Y eso es porque esa posibilidad simplemente no existe, así que habrá que pensar en otra(s) cosa(s).

🤷‍♂️ Sí, ya sé que este artículo está escrito en castellano pero tanto los comentarios del código como algunas de las capturas muestran los textos en inglés. Ya dije hace un rato que esto iba a ser solo un tuit de tres líneas acompañado de un GIF animado y un enlace a un gist con el código y, por aquello del alcance y esas cosas, me pareció buena idea prepararlo en inglés. Pero luego ha salido lo que ha salido. En fin, coherente que es uno.

Creando un cuadro de diálogo de tamaño dinámico

¿Por qué razón querríamos cambiar el tamaño de un cuadro de diálogo una vez abierto? Pues, por ejemplo, para introducir textos extensos con mayor comodidad. Al fin y al cabo, esas barras de desplazamiento interiores, especialmente las horizontales, pueden resultar a menudo molestas

Una caja de texto que no puede expandirse lo suficente.
La caja de texto no puede crecer más allá de las dimensiones del cuadro de diálogo.

Para conseguirlo vamos a utilizar dos estrategias:

  • Añadiremos al cuadro de diálogo un par de botones que, al ser pulsados, incrementarán o reducirán sus dimensiones en una cantidad determinada de píxeles.
  • Haremos que el cuadro de diálogo crezca al ajustar las dimensiones de una hipotética caja de texto como la que puedes ver en la micro animación anterior.

Ya verás, es cosa de un periquete. Y de paso vamos a aprender a jugar un poco con el modelo de objetos de un documento HTML, esto es, el DOM.

DOM, DOM... ¿Hay alguien en casa?

Tal vez te preguntes por qué demonios necesitas saber nada del DOM ese de marras si lo único que te interesa es crear scripts GAS para automatizar tus historias.

Es una buena pregunta.

Entender cómo pueden construirse documentos web dinámicos usando HTMLCSS y JavaScript nos conduce inexorablemente al terreno del desarrollo web, así con mayúsculas.

Un hombre adentrándose en un lugar misterioso.
Dev Patel justo antes de que le explicaran quién es el DOM verde, claro (ni caso, me hacía falta una imagen de relleno 😝).

Estas tecnologías constituyen estándares globales que trascienden los entornos propietarios, como Google Apps Script, de cualquier fabricante. Tal vez en algún momento te apetezca ir más allá de GAS y jugar con otras (muchas) cosas que hay ahí afuera. Eso que tendrás adelantado.

Pero bueno, volviendo al aquí y ahora y sin necesidad de ponernos trascendentales, lo cierto es que, como ya sabes, los cuadros de diálogo y paneles laterales de los editores de documentos de Google, y por supuesto también las webapps Apps Script, se construyen con estas tecnologías web. Conocerlas te permitirá construir aplicaciones más sofisticadas, con interfaces de usuario más ricas y vistosas.

Las personas que usen tus scripts seguro que te lo agradecen. Y es que como dicen por ahí, el saber no ocupa lugar.  

☝ Ojito, Google está apostando desde hace algún tiempo por un nuevo tipo de complementos universales en los que la interfaz de usuario está basada en unas tarjetas muy resultonas, totalmente ajenas a HTML, que se construyen utilizando algo llamado Card Service.  De ellos y ellas hablaremos en un próximo artículo.

Un complemento Google Workspace de nueva generación que (aún) no hace absolutamente nada.

Utilizar el DOM no es muy distinto a usar las clases (objetos), propiedades y métodos de los distintos servicios Apps Script que nos permiten interactuar con  las aplicaciones de Google Workspace.

Cada elemento dentro de un documento HTML (o de un cuadro de diálogo, como es nuestro caso) está representado por un objeto en el DOM.

Estos objetos están dispuestos de manera jerárquica, del mismo modo que los elementos visuales que los representan, y contiene una plétora de propiedades que definen su aspecto y comportamiento.

Además, el DOM cuenta con algunos métodos que nos serán de utilidad para manipular esos objetos, objetos a los que, por si fuera poco, les van a "pasar cosas" cuando se desencadenen ciertos eventos.

¡Y el DOM no está solo! También tenemos el BOM, es decir, el Browser Object Model o modelo de objetos del navegador, que expone otros tantos objetos, propiedades, métodos y eventos propios.

De hecho el BOM es quien corta la pana, hasta el importantísimo objeto Document del DOM, del que hablaremos en un momento, es una propiedad del objeto Window del BOM. Gracias al BOM podremos interactuar con los componentes del navegador: ventana, historial, cookies, alertas, temporizadores... todo un mundo por explorar más allá de los confines estrictos de Apps Script.

¿DOM? ¿BOM? ¿Manipular propiedades? ¿Responder a eventos? ¿Nos hemos vuelto locos? ¿Cómo voy a hacer yo todo eso 😨?.

Fácil, usando JavaScript.

Pero no hay nada que temer. Si estamos familiarizados con Apps Script entonces también lo vamos a estar con JavaScript, dado que son casi, casi lo mismito. ¿Y sabes una cosa? Creo que te va a encantar...

🎀 Mete esto en tu HTMochiLa: Tanto el DOM como el BOM nos proporcionan objetos, propiedades, métodos y eventos, ponles un lacito y ya tienes una estupenda API para jugar con ellos usando JavaScript.

Comencemos con un poco de HTML

Vamos a montar en un periquete un cuadro de diálogo como este y, por el camino, aprenderemos a usar el DOM así en plan básico.

Cuadro de diálogo de tamaño variable.
Nuestra hermoso cuadro de diálogo personalizado dinámico.

Seguro que ardes en deseos de ver el código. No te haré esperar.



Abro paréntesis para soltar así a bocajarro tres o cuatro obviedades sobre los documentos HTML:

1️⃣ HTML es un lenguaje de marcas compuesto por etiquetas, entidades que se escriben entre signos de menor (<) y mayor (>).

2️⃣ Las etiquetas sirven para delimitar secciones y definir elementos dentro del documento HTML. Suelen venir en parejas, con una de apertura y otra de cierre (<etiqueta> ... </etiqueta>), aunque en algunos casos esta última es opcional

3️⃣ Dentro de las etiquetas pueden aparecer uno o más atributos, cuyo valor se establece mediante expresiones como  atributo="valor". Los atributos caracterizan el aspecto o comportamiento de los elementos representados por las etiquetas. Fíjate además en los bloques delimitados por las etiquetas  <html>, <head> y <body> (y sus respectivos cierres), constituyen el armazón de un documento HTML de pro.

4️⃣ Para modificar la presentación de los elementos HTML usaremos  CSS. Sí, eso que aparece junto a las etiquetas <style> en el fragmento de código de aquí arriba.

...Y con esto ya estaría el minicurso de HTML incluido en este artículo 😅. Si te has quedado con ganas de más, te recomiendo estos otros dos, estupendos para arrancar.

Tiremos ahora de un simpático diagrama para representar el DOM de nuestro súper básico cuadro de diálogo HTML.

DOM de ejemplo.
Los atributos id de las pastillas con fondo naranja nos permitirán modificar las propiedades de ciertos objetos usando JavaScript.

JavaScript mon amour, où es-tu?

En cualquier manual, tutorial, cursito o curso de desarrollo web te van a contar que hay que mantener en archivos separados tus movidas HTML, CSS y JS.

Y no te engañan. Indudablemente esta es una buena práctica para evitar líos cuando los proyectos crecen. Pero en este caso, y por simplicidad, añadiremos nuestras pizcas de JavaScript dentro de una sección delimitada mediante las etiquetas <script> ... </script> justo antes de la etiqueta de cierre final </body>.

☝ Por cierto, y por aquello de darte abundantes cosillas que leer cuando termines de digerir este artículo: Dos excelentes referencias sobre JavaScript, que además harán sin duda de ti un mejor programador de Apps Script. Empieza por el que quieras, ambos son im-pres-cin-di-bles:
📌 Eloquent JavaScript
📌 The Modern JavaScript Tutorial

Lo primero que vamos a necesitar son algunas inicializaciones.

 
  <script>

    const padding = 2;
    const textMinX = 400;
    const textMinY = 180;
    const dialogMinX = 500;
    const dialogMinY = 300;
    const deltaX = dialogMinX - textMinX - 2 * padding;
    const deltaY = dialogMinY - textMinY - 2 * padding;
    const step = 50;
    const $ = id => document.getElementById(id);

Con estas constantes controlaremos:

  • El relleno, en píxeles de la caja de texto.
  • El tamaño mínimo de la caja de texto.
  • El tamaño mínimo del cuadro de diálogo.
  • La diferencia de anchura y altura que deben mantener el cuadro de diálogo y la caja de texto en todo momento.
  • El paso (nº de píxels) que usaremos para modificar la altura y anchura de cada elemento cuando se haga clic en alguno de los botones.

 Si estás comenzando con esto Apps Script es probable que la definición de la última constante te parezca una marcianada.  Lo que hacemos aquí es declarar de manera simplificada una función, llamada $, que invoca al método getElementById() del objeto document del DOM HTML, que es el padre (o madre 😜) de todo lo que viene detrás (JavaScript es así, o lo odias o lo amas). ¿Y esto por qué? Por pura vagancia, osea, para escribir menos.

Dicho esto, ya solo queda conseguir que los botones reaccionen al ser pulsados y que al arrastrar el tirador que permite ajustar el tamaño de la caja de texto el propio cuadro de diálogo también crezca o se reduzca de manera acompasada.

Pero para conseguirlo tenemos que introducir un nuevo jugador: tachán, tachán (fanfarria)...

¡Qué pasen los manejadores de eventos!

Interludio: JavaScript sí, pero mejor reactivo

Los documentos HTML tienen una gran vida interior. En ellos pueden pasar un montón de cosas extremadamente interesantes.

Algunas acciones afectan de manera específica a sus elementos constituyentes, como los clics del ratón sobre determinados objetos, las operaciones de arrastrar y soltar, la introducción de texto en los cajetines de un formulario o incluso las pulsaciones atómicas de teclas.

Otras atañen al documento en su totalidad, como el cambio de tamaño de la ventana del navegador o la finalización del proceso de carga de todo su contenido.

Pero lo realmente interesante es que podemos ejecutar el código JS que nos dé la gana enganchándolo a los manejadores de evento que nos facilita la API del DOM/BOM para cada una de esos sucesos.

¿Y eso cómo se hace? Bueno, pues se me ocurren 3️⃣ maneras (abro paréntesis):

1️⃣ El modo más simple consiste en asignarle al atributo del evento que pretendemos capturar el nombre de la función a ejecutar como cadena literal de texto (osea, entre comillas) en la propia etiqueta HTML del elemento afectado.

La lista completa de esos atributos especiales de evento tan útiles la encontrarás aquí.

Por ejemplo, aquí se ejecutará la función cerrar() (olvídate de lo que hay dentro, ahora nos da igual) cada vez que se haga clic en el botón definido en la línea 3 gracias a ese inequívoco onclick="cerrar()".

Código de establecimiento de un manejador de evento usando un atributo HTML.
Hay varias razones de índole técnica, que no vienen mucho al caso en este artículo introductorio, para evitar hacer las cosas de este modo. Una de ellas, la más obvia, es que estamos mezclando marcado (HTML) y código JS, presentación y comportamiento. No es una buena idea.

Y no me refiero solo a que declaramos la función en un bloque <script> justo a continuación de <body>, que ya sabemos que no es lo más de lo más, sino que estamos usando el nombre de una función JS dentro de la mismísima etiqueta del botón. Podríamos incluso haber escrito un puñado de instrucciones JavaScript como valor del atributo, en lugar del nombre de una función. Rápido y sucio, pero poco aseado, maldita sea.

☝ En este fragmento de código (y en los dos que le siguen) he colocado la función JavaScript fuera del elemento <body> del documento simplemente para diferenciar de manera drástica código de marcado. Cualquier navegador se lo tragará sin más, pero cuidadín, los bloques <script> deben ir dentro del <head> o del <body>, de lo contrario el documento HTML no es técnicamente válido.

2️⃣ Un método un poco más "fino" consiste en establecer el manejador del evento dentro del bloque de código JS <script> . Así al menos mantenemos todo el código que usa la página más recogidito.

Código básico de asignación de manejador de evento usando JavaScript.
Fíjate, en la línea 8 usamos el método getElementByID() del objeto document del DOM (¡ya te he hablado de ambos!) para identificar el elemento HTML que nos interesa a través de su atributo id.

A continuación, establecemos el valor de su propiedad onclick para determinar cuál debe ser la función a ejecutar cuando el botón registre un clic del ratón sobre él.

Sí, cuesta más decirlo que hacerlo.

Esta línea nº 8 se ejecutará en cuanto cargue la página, en tanto que la función cerrar() solo lo hará como resultado de un clic en el botón de marras. Y ojito, aquí la función no va entre comillas ni usamos paréntesis puesto que se trata del nombre de una función JavaScript, algo con entidad propia en este lenguaje.

Ahora nos bastará con leer cuidadosamente la sección en la que se encuentra todo el código JavaScript de nuestro documento HTML para entender como se comportará.

3️⃣ Por último, el procedimiento más riguroso pasa por pedirle educadamente a nuestra servicial API que cree un manejador de evento. Presta atención a la línea nº 8 de nuevo.

En lugar de establecer a piñón en el elemento en cuestión el valor de la propiedad que representa el evento que nos interesa cazar, ahora usamos el método addEventListener(), al que se le pasan como parámetros:

  • El nombre del evento (atento, ahora se llama click, el "on" se nos ha caído, aquí otra conveniente lista).
  • Nuevamente, el nombre de la función a ejecutar al hacer clic en el botón.

¿Pero esto no es lo mismo?

Solo aparentemente. De hecho, este es el método que se suele recomendar, entre otras cosas porque permite asociar múltiples manejadores (funciones JS) al mismo evento de un elemento dado, sin que se incordien entre ellos.

Detalles, detalles... ¡Que sería de la vida sin ellos!

☝ Si ya haces tus pinitos con Apps Script seguramente encontrarás cierto paralelismo entre sus triggers (activadores) y toda esta movida que te estoy contando. Efectivamente, al fin y al cabo se trata de ejecutar código de manera reactiva ante ciertas circunstancias. No obstante, los eventos del DOM son mucho más variados, ricos y complejos.

Y cerramos paréntesis.

Ahora sí, la implementación JS

Querido o querida GAS-padawan, ya estás en condiciones de enfrentarte al resto de la implementación que, para mantenerte en tu zona de desarrollo próximo, usará algunas tretas que van una pizca más allá de lo que hemos aprendido hasta el momento.

Nos habíamos quedado en la inicialización de constantes.

A continuación tenemos que instalar sendos manejadores de eventos que modifiquen el tamaño de la ventana cuando se utilicen los botones o se estire la caja de texto.  Para ello toca meterle mano a los elementos de la página usando JS, pero...

⚠️ ¿Tenemos la seguridad de que estos elementos ya existen en el DOM del documento en el momento en que tratamos de acceder a ellos?

Hay varias maneras de esperar a que la página haya cargado para comenzar a ejecutar el código de inicialización, todas ellas con sus matices.

Lo cierto es que en un ejemplo tan requetesencillo como este, en el que no hay dependencia externa alguna ni subterfugios de ningún tipo, nos bastaría con colocar el bloque <script> al final, como hemos hecho. Pero como no cuesta nada ir asumiendo buenas costumbres, en el código que te facilito verás que he optado por interceptar el evento DOMContentLoaded del objeto window del BOM.


    // Init
    window.addEventListener('DOMContentLoaded', () => {
      
      // Event handler 1 >> Resize buttons
      document.querySelectorAll('button').forEach(b => b.addEventListener('click', e => {
        
        let updated = false;
        const delta = e.target.id == 'btn_bigger' ? step : -step;
        
        if (window.innerWidth + delta >= dialogMinX) {
          google.script.host.setWidth(window.innerWidth + delta);
          $('textArea').style.width = `${$('textArea').clientWidth + delta - 2 * padding}px`;
          updated = true;
        }

        if (window.innerHeight + delta >= dialogMinY) { 
          google.script.host.setHeight(window.innerHeight + delta);
          $('textArea').style.height = `${$('textArea').clientHeight + delta - 2 * padding}px`;
          updated = true;
        }
        
        if (updated) updateInfo();

      }));

Bisturí 🔪, por favor, y vamos por partes:

1️⃣ En la línea 38 se crea el manejador del evento que nos indica que el contenido HTML del documento ya existe.

La función que se ejecutará se declara inmediatamente mediante una función flecha anónima, una alternativa compacta al la sintaxis más tradicional.

☝ Las funciones flecha molan una barbaridad, para qué engañarnos, vete acostumbrando a ellas porque se usan muchísimo y permiten escribir código más legible y elegante... aunque ⚠️ cuidado ⚠️,  no son siempre intercambiables con las que se declaran del modo más tradicional.

Así, en lugar de pasarle al manejador el nombre de la función a la que debe llamar y escribirla más abajo, como en los ejemplos cortos del apartado anterior, le espetamos el código ahí mismo.

2️⃣ Seguimos dentro del manejador del evento DOMContentLoaded. En las líneas 41 - 60 se añaden (y declaran al mismo tiempo) los manejadores de eventos para responder a la pulsación de los sendos botones presentes en nuestro cuadro de diálogo:

Con  document.querySelectorAll('button') le pedimos a la API del DOM que nos facilite todos los elementos con la etiqueta <button> presentes en el documento HTML (solo hay dos, que son los que nos interesan), los recorremos con forEach() y designamos el código que se ejecutará cada vez que se haga clic sobre ellos.

El parámetro e nos lo pasará el DOM cada vez que llame al código del manejador y nos permitirá identificar sobre qué botón se ha hecho clic.

El código del manejador asociado a los botoncitos es bastante explícito:

  • En la línea 44 se identifica el  botón que ha registrado el clic a través de su atributo id y la propiedad e.target.id del objeto evento que, recuerda, nos facilita la API del DOM. Eso permite determinar si debe aplicarse un incremento de tamaño positivo o negativo.
  • A continuación, en las líneas 46 - 56 se realizan las comprobaciones relativas a las dimensiones mínimas del cuadro de diálogo  y, en su caso, se tira de la API de cliente google.script.host para cambiar al vuelo el tamaño tanto del cuadro como de la caja de texto. El tamaño real de la ventana en cada momento nos lo proporciona el BOM (window.innerWidth y window.innerHeight). No se realiza ninguna comprobación sobre el tamaño máximo, pero la cosa sería análoga, como puedes intuir.
  • Finalmente, si el tamaño realmente ha variado, en la línea 58 se llama a la función updateInfo(), que veremos en un momento, y que únicamente sirve para actualizar los indicadores numéricos que muestran las dimensiones actuales de la caja de texto.

Vamos con el repecho final. Seguro que tras la disección de código anterior y el consiguiente subidón tras haberlo interiorizado, esta parrafada te parecerá ya tan familiar como ese entreñable conocido o conocida con el que te vas de cañas en los congresos educativos.


      // Event handler 2 >> Text area resize (well, sort of)
      $('textArea').addEventListener('mouseup', () => {
        
        if ($('textArea').clientWidth >= textMinX) {
          google.script.host.setWidth($('textArea').clientWidth + deltaX);
        } else {
          $('textArea').style.width = `${textMinX}px`;
          google.script.host.setWidth(dialogMinX);
        }
        
        if ($('textArea').clientHeight >= textMinY) {
          google.script.host.setHeight($('textArea').clientHeight + deltaY);
        } else {
          $('textArea').style.height = `${textMinY}px`;
          google.script.host.setHeight(dialogMinY);
        }
        
        updateInfo();

      });

      updateInfo();

    });

    // Helper
    function updateInfo() {
      $('txt_width').value = $('textArea').clientWidth;
      $('txt_height').value = $('textArea').clientHeight;
    }
    
  </script>

Este bloque crea el manejador del evento que nos permitirá detectar que la caja de texto ha cambiado de tamaño, utilizando nuevamente el método addEventListener().

Pero, ¿qué manejador es ese?

Una manera (poco brillante, lo reconozco) de detectar el cambio es vigilar el evento mouseup, que se produce cuando el usuario suelta el botón de la rata tras haberlo presionado previamente sobre el área del documento HTML ocupada por la caja de texto.

Cuando esto ocurre, se leen las propiedades clientWidth (anchura) y clientHeight (altura) de la caja y, si estas superen las dimensiones mínimas, se ajusta nuevamente el tamaño del cuadro de diálogo contenedor para que su anchura y altura se mantengan en todo momento deltaX y deltaY píxeles por encima, respectivamente.

Puede que el usuario simplemente haya hecho clic en algún punto de la superficie del cuadro de texto, en lugar de arrastrar el tirador. En ese caso los valores numéricos que se utilizarán para establecer la altura y anchura del cuadro de diálogo serán los actuales, por lo que visualmente no notaremos nada. Lo suyo sería que el código que gestiona esto comparase las dimensiones de la caja de texto con las últimas registradas y no hiciera nada si no han cambiado, por supuesto. Venga, te lo dejo como ejercicio.

Imagen que muestra el tirador para cambiar el tamaño de la caja de texto.
En sus pantallas, el tirador de cambio de tamaño de la caja de texto.

Esta estrategia no evita sin embargo un pequeño glitch visual cuando llevamos el tirador más allá de las dimensiones de la ventana, que se solventa inmediatamente haciendo clic una vez más sobre la caja de texto para que todo se recomponga. Pero el tinglado funciona, es relativamente sencillo y, en cualquier caso, es el nuestro y por eso le queremos.

Lo suyo hubiera sido afinar un poco más y cazar de algún modo el evento específico que se produce al cambiar el tamaño de la caja, pero la cosa se complica un poco y he preferido contener aquí el alcance del artículo que, recuerda, pretendía ser introductorio. ¡Creo que ya hemos hecho bastante!

En fin, que ya solo nos queda cerrar cuidadosamente todas las etiquetas que hemos ido abriendo...



...y tomarnos una 🍺 bien fría. O un te / café / mate / carajillo / agua de grifo (o garrafa) o lo que quiera que tomes para celebrar las pequeñas victorias como esta ✌. Nadie dijo que no se pudiera disfrutar del camino, aunque este sea largo y de destino incierto (ya lo sabes, me va el drama).

Animación que demuestra el funcionamiento del cuadro de diálogo escalable.
Demo del tinglado que hemos construido.

Aquí tienes la hoja de cálculo utilizada para grabar la micro animación anterior. En su interior encontrarás el código que hemos desarrollado paso a paso.

¿A que eso de la programación reactiva no está ni tan mal 😉?

Comentarios finales y siguientes pasos

Una vez más, hasta aquí hemos llegado. Gracias por acompañarme.

Personalmente, combinar HTML / CSS / JS con Apps Script me parece fascinante y, aunque solo lo he mencionado de pasada, nos da pie a crear esos enigmáticos a la par que utilísimos artefactos que son las webapps, esencialmente una forma de hospedar pequeñas aplicaciones web a coste cero.

Aunque en este artículo he mencionado tres publicaciones previas de mi otro blog en las que hablo de estos temas, lo cierto es que nunca he explicado de manera pormenorizada algunos de sus aspectos más básicos (y otros que no lo son tanto). Y aunque es algo sobre lo que hay abundante información ahí afuera, creo que me apetece hacerlo a mi manera.

Si el tema te interesa, tienes cualquier sugerencia, comentario o duda sobre el artículo o simplemente quieres decirme que te ha gustado (o no), puedes utilizar la caja de comentarios de aquí abajo o responder en las publicaciones en redes sociales en las que le he dado difusión.

Ah, y no te olvides de que los appsscripters de habla hispana tenemos un servidor Discord enterito para hablar de nuestras cosas 👇.

Logo comunidad Apps Script Ñ.

¡Ven a visitarnos! ⚡


Comentarios