Mutadores de elementos DOM

Crear un flujo para la configuración de elementos

Alan Badillo Salas
Programacion Holistica
6 min readMay 2, 2019

--

Motivado por los transducers he decidido establecer un flujo para la mutación de elementos DOM a tráves de diversas configuraciones que mejorarán el elemento de forma fluída y transparente.

Comencemos por un ejemplo sencillo, en el siguiente código se muestra cómo a un botón se le hacen una serie de modificaciones para cambiarle su estilo, ajustarle atributos y escuchar sus algunos eventos.

Observa que al botón se le han modificado dos estilos (color y width), se le ha modificado el atributo innerText, se le han agregado dos atributos de tipo dataset (clicks y isHover), se le han agregados callbacks para escuchar sus eventos click, mouseover y mouseout dentro de los cuáles se accede y modifican a los datasets del botón, para finalmente agregarlo a body. El resultado es un botón guiado por un flujo de mutaciones que hacen que el botón sufra todos esos cambios.

Del ejemplo anterior podemos pensar en construir una serie de funciones genéricas que tomen cualquier elemento DOM y realicen las modificaciones sobre los atributos style, dataset, innerHTML, entre otros y otra serie de funciones que se suscriban a los eventos del elemento genérico e invoque los flujos posteriores.

Para la primer parte, podemos diseñar las funciones, como se muestra en el código.

Observa que las funciones de mutación se construyen con una función que recibe su configuración, por ejemplo colorStyle = color => { ... } la cuál define un mutador de element.style.color = color. El resultado del mutador es otra función que recibe el elemento a mutar return element => { ... }, en este caso element es el elemento DOM que será mutado siempre de la misma forma, sin importar si soporta o no la mutación. Entonces podemos decir que el mutador es una función compuesta que en una primera función configura la mutación y en el segundo recibe el elemento a mutar, generando el fragmento de mutación. En términos matemáticos se realizaf(y) -> g dónde f es la función de mutación, y es la configuración del mutador, y g es la función compuesta de mutación (el mutador) que podrá ser aplicado a cualquier elementox, es decir, x' = g(x) dónde x' es el elemento x mutado según f configurado por y.

En términos simples, podemos pensar al mutador como una función de inyección sobre el elemento genérico. La doble función compuesta nos permite aplicar un mismo mutador a diferentes objetos, véase el siguiente ejemplo.

Observa la definición de colorBlueMutator y textHelloMutator los cuáles mutarán a los elementos button y h1 con la misma configuración. Esto significa que colorStyle es f con la configuración cornflowerblue que es y y colorBlueMutator es g, es decir, el mutador resultante. De forma similar sucede para textHelloMutator.

Ahora debemos establecer un flujo continuo de mutación, es decir, alguien que guie las mutaciones que va a sufrir un elemento.

Observa que se ha definido una nueva función llamada mutatorFlow la cual recibe el elemento element y devuelve una función compuesta que recibe a todos los mutadores enviados por parámetros usando el operador spread. El flujo de mutación establece aplicarle al elemento a cada mutador recibido, de esa forma podemos llamar al flujo de mutación de forma sencilla y este le aplicará al elemento cada mutador recibido. De esta forma los mutadores podrían ser reciclados, cuándo tienen funciones muy específicas, por ejemplo, mutadores para quitar bordes en los estilos, definir un conjunto de datasets específicos, establecer clases o hacer búsquedas sobre hijos.

Llegamos al preludio de esta entrega, hemos logrado construir un flujo de mutaciones que nos permiten reciclar lógica de alteración a nuestros elementos DOM de forma sencilla, permitiéndonos abstraer las mutaciones como funciones, creando una sintaxis rica y natural. Pero aún falta definir el modo en el que los eventos deberían comportarse en el flujo de mutación, es cierto, que en este momento somos capaces de definir mutaciones que se suscriban a eventos y realicen lógica orientada por un callback, sin embargo, lo ideal sería que el mutador de eventos logre de alguna manera incorporarse al flujo de manera natural con los otros elementos.

Antes de continuar el diseño de un mutador de eventos, veámos un ejemplo avanzado sobre el uso de mutadores, para despertar el entusiasmo de su uso.

Observa que se ha construido una función llamada domMutator la cual recibe el elemento a mutar y una colección de configuraciones para las diferentes mutaciones. Esta función condensa una lista de mutaciones la cuál esparse sobre el flujo de mutación tomando sus configuraciones desde el objeto de opciones. Gracias a que los mutadores domStyle y domAttribute funcionan con o sin opciones recibidas, se puede o no recibir una configuración para hacer dicha mutación. Esto nos permite tener un meta-mutador que altera con diversos mutadores al elemento.

Ahora si es momento de llegar al punto clave sobre la construicción de mutadores que hemos venido construyendo, y es precisamente el cómo abstraer los eventos de un elemento, para realizar una serie de mutaciones. Los primero será observar el mutador que escucha el evento click del elemento.

El mutador simplemente recibe un callback y lo registra al evento del elemento, sin embargo, esto impide llevar el flujo de mutación, por lo que necesitamos crear una tubería que tenga como entrada los datos del evento, y que tenga asociado el elemento, para que apartir de él se puedan continuar agregando mutaciones. La tubería será diseñado como un objeto recursivo que vaya agregando mutaciones a partir de un origen.

Observa que hemos definido una función mutatorPipe que genera una gramática simple basada en las operaciones input y mutate, las cuales establecen la entrada de la mutación (la configuración) y el mutador respectivamente. La tubería de mutación nos va a permitir continuar construyendo un flujo más flexible que el propuesto por mutatorFlow ya que puede determinar más gramáticas como, por ejemplo, mapear la entrada mediante un callback, suscribirse a un evento, etc.

Mejoraremos la gramática de la tubería para permitirnos suscribirnos a eventos. Debes prestar atención a la gramática cuándo se suscribe a un método y cuándo se encuentra fuera de él.

Observa que se ha modificado la gramática del mutatorPipe para agregar un método llamado when que recibe el nombre de un evento, este método pondrá a la tubería en un estado especial llamado intoWhen el cuál retendrá en un buffer todas las operaciones pipe que se estén solicitando, esto permitirá construir una tubería parcial, dónde en lugar de ejecutarlas directamente, las almacenará en el buffer hasta que el buffer se cierre mediante el otro método llamado next que se agregó. El método next por su parte, tomará todos las sentencias pipe almacenadas en el buffer y las ejecutará cuándo se ejecute el evento desde el elemento. Prácticamente esto nos permitirá registrar eventos y establecer flujos para estos eventos desde la misma tubería :D.

Finalmente, para que la tubería sea aislada y reutilizable, debemos romper la estructura.

Observa que se definió en la tubería un nuevo método llamado exec que ejecuta un callback recibido. Como se puede notar la función buttonPipe toma todas las ventajas del mutatorPipe para armar un flujo estable sobre el elemento recibido con las opciones ingresadas, para definir flujos de mutación invariantes y reconfigurables que pueden ser reciclados fácilmente.

En esta entrega hemos visto la evolución natural de los mutadores de elementos, partiendo de funciones básicas llamadas mutadores, que tienen el objetivo de configurar o reconfigurar un elemento. Para establecer un flujo entre mutadores, se puede definir una función que reciba todos los mutadores y orqueste la mutación del elemento a través de todos los mutadores. Luego podemos definir un mutador formal, el cuál establece una gramática de mutación para permitirnos crear subcontextos de mutación que deberían aplicarse cuándo se generan eventos dentro del elemento.

Con todo esto, podemos contruir ahora nuestra propia gramática de mutación para construir microframeworks potentes, que superen por mucho a lo construido hasta ahora.

Si te gustó este post y deseas apoyarme, no olvides hacerlo en el siguiente enlace.

--

--

Alan Badillo Salas
Programacion Holistica

L. Matemáticas Aplicadas UAM-Cuajimalpa. M. en Inteligencia Artificial IPN-CIDETEC. Desarrollador Full Stack MEAN/MERN. Data Scientist.