web components (4): shadow DOM

Cuarta y última entrada de la serie dedicada a los web components

Franz Marc

archivado en: JavaScript / 31 Diciembre, 2015 / taller:

DOM

En la entrada anterior de esta serie dedicada a los web components se produjo un problema cuando importamos un componente en el que había definido unos estilos contra la etiqueta <p>, lo cuales afectaban a todos los párrafos de la página anfitriona y no solo a los del componente. Esto no se habría producido si todo hubiera estado encapsulado, que es el tema que vamos a ver en esta entrada; pero antes recordemos un par de conceptos previos.

Como es sabido, el DOM (Document Object Model) es el conjunto de elementos que forman una página web y estos elementos se estructuran de forma arbórea formando una jerarquía de padres e hijos. Por ejemplo, en este documento:

<body>

<header>

<h3 class="awesome">Bazinga!</h3>

</header>

<article>

<p>Foo</p>

<p>Bar</p>

</article>

</body>

Los nodos <article> y <header> son hijos de directos de <body> y a su vez son padres, respectivamente, de dos <p>, que son hermanos, y un <h3>.

wc-dom

Existen distintos tipos de nodo, los cuales derivan del objeto node. Los más importantes son:

  • DOCUMENT_NODE: como window.document, que incluye toda la página web.
  • ELEMENT_NODE: <body>, <p>, <h3>...
  • ATRIBUTE_NODE: class="tal", id="tal".
  • TEXT_NODE: el texto, incluidos los saltos de carro y los espacios en blanco.

Para terminar la foto solo nos falta por recordar que a los nodos del tipo elemento le podemos dar estilos, como el color o el tamaño de una fuente. El conjunto de esos estilos es lo que se conoce como CSSOM (CSS Object Model).

Pues bien, otra de las características de los web components es que están encapsulados en su propio DOM+CSSOM, tal y como ocurre con el contenido que hay dentro de los <iframe> de una página. Para entendernos, es como si pudiéramos desplegar un DOM propio al <article> que vimos antes, el cual será hermético al DOM de la página que lo contiene y viceversa. Es el llamado Shadow DOM, el DOM en la sombra.

Habilitar la consola

Antes de empezar a trabajar con el shadow DOM conviene habilitar el chrome para que nos lo muestre. Para eso, pulsamos f12 y el botón de opciones (settings).

wc-sh1

Luego ya solo tenemos que marcar la opción «Show user agent shadow DOM» y, ale op, ya está visible.

wc-sh2

 

Hecho esto, vamos al lío ^^.

Algo confuso

En general, el shadow DOM, de aquí en adelante «sd», es la faceta de los web components menos estandarizada y más sujeta a cambios, por lo que es fácil perderse en la documentación online si no has participado en su gestación.

Entonces, al parecer, en un principio para crear un elemento en la sombra se usaba el método createShadowRoot(). Así, por ejemplo, para enviar al reino de las sombras a este nodo:

<article id="nodo-sombra">

<p>Zasprist!</p>

</article>

Basta con hacer algo así:

<script>

var shadow = document.getElementById("nodo-sombra").createShadowRoot();

</script>

Los elementos que cuentan con su propio sd muestran en la consola su shadow-root

wc-sh3

Y ahora empieza el lío. En el momento de escribir estas líneas, a finales de diciembre de 2015, en la documentación oficial de mozilla indican que «This method has been deprecated in favor of attachShadow». Es decir, que en lugar de este método debe usarse attachShadow. De hecho, parece que los desarrolladores de web components tuvieron que hacer algo al respecto y en la especificación se habla de este método en vez de createShadowRoot(), además, en el blog de webkit también hablan de attachShadow.

Sin embargo, ni en el chrome 49.0.2599.0 canary ni en la versión normal 47.0.2526.106 a mí me funciona atachShadow, pero sí que va bien createShadowRoot. Así las cosas, en los ejemplos de esta entrada, será este último método el que usaré a la espera de aclarar este lío.

Se denomina «shadow tree» al DOM interno que nace de un nodo en la sombra, el cual recibe el nombre de «shadow root».

Estilos en la sombra

Ya sea de una manera u otra, una vez que hemos enganchado un shadow dom a un nodo, podemos hacer cosas muy chulas, como aplicarle sus propios estilos. En el caso anterior, por ejemplo, si añadimos un estilo a los párrafos del nodo en la sombra, solo afectaría a los que forman parte de su dom.

<article>

<p>Foo</p>

<p>Bar</p>

</article>

<article id="nodo-sombra"> </article>

<script>

var shadow = document.getElementById("nodo-sombra").createShadowRoot();

/* Insertamos un párrafo en el nodo en la sombra */

shadow.innerHTML = "<p>Zasprist!</p>";

/* Creamos un estilo que solo afectará a los p del nodo shadow */

var miEstilo = document.createElement('style');

miEstilo.innerHTML = "p {border: 1px solid #900; color: #090;}";

shadow.appendChild(miEstilo);

</script>

wc-demo-estilos

Ojo, esta «encapsulación» de las css funciona también si definimos los estilos normalmente, pero para eso necesitamos trabajar con templates. Así, por ejemplo, en este caso:

<body>

<p>Otro párrafo</p>

<article id="shadow-root">

</article>

<template id="foo">

<style>

p {

color: #D00000;

font-weight: bold;

}
</style>

<p>Contenido de la plantilla</p>

</template>

<script>

/* Seleccionamos el contenido de la plantilla */

var plantilla = document.getElementById("foo").content;

/* Lo convertimos en nodos trabajables */

var nodosListos = document.importNode(plantilla, true);

/* Seleccionamos el nodo desde el que crearemos un nuevo árbol */

var shadowRoot = document.getElementById("shadow-root").createShadowRoot();

/* Insertamos dentro la plantilla */

shadowRoot.appendChild(nodosListos);

</script>

</body>

Aunque el selector del estilo es un párrafo <p>, como está definido dentro de su propio shadow tree, no afecta al párrafo del DOM principal.

wc-estilos

El selector :host se utiliza dentro del shadow dom para referirse a los elementos en la sombra; para modificar desde fuera los estilos de dentro se utilizaba el pseudo-selector ::shadow, aunque parece que esto también lo están cambiando.

ShadowScript

Esta encapsulación de las css ocurre también con el javaScript o, mejor dicho, con los selectores de javaScript. Por ejemplo, siguiendo con el caso anterior, si hay dos párrafos con el mismo id, pero uno está dentro de su propio shadow tree, al hacer un getElementById, solo seleccionará este último.

<body>

<p id="bar">Otro párrafo</p>

<article id="shadow-root"></article>

<template id="foo">

<p id="bar">Contenido de la plantilla</p>

</template>

<script>

var plantilla = document.getElementById("foo").content;

var nodosListos = document.importNode(plantilla, true);

var shadowRoot = document.getElementById("shadow-root").createShadowRoot();

shadowRoot.appendChild(nodosListos);

/* Esto solo afecta al id "bar" de la plantilla */

shadowRoot.getElementById("bar").style.color = "blue";

</script>

</body>

wc-idencapsulado

Y esto mismo sucede con todos los selectores de javaScript. Es decir: getElementById, getElementsByClassName, getElementsByTagName, etcétera.

Puntos de inserción

Lo que sigue igual resulta un poco confuso, pero como viene sucediendo con los artículos de esta serie, basta con que comprendamos la esencia del proceso y ya más adelante veremos cómo resolverlo de forma más sencilla con polymer.

Los nodos de una página web suelen interactuar con otros nodos para formar moléculas de html más complejas. Por ejemplo, la etiqueta <table>, necesita <tr> y <td> para mostrar una tabla en condiciones, al igual que las listas <ul> no funcionan sin sus <li>.

Dentro de un custom element en el shadow root podemos definir la relación entre los nodos internos de igual manera que en el dom normal, denominado en este contexto «light dom». Pero también puede ocurrir que necesitemos que se relacionen con los nodos de fuera y para eso contamos con un nuevo elemento llamado <content>, que en el futuro parece que se llamará <spot>.

Como ocurre con createShadowRoot, en el momento de escribir estas líneas, <content> está deprecated y en su lugar parece que se utilizará <spot>. Sin embargo, los navegadores aún manejan <content>, que es el que usaré en esta entrada.

El elemento <content> sirve, por lo tanto, para interrelacionar el light dom con el shadow dom y eso lo hace gracias a un atributo llamado select, en el que podemos definir qué elemento de fuera es el que debe ir dentro. Esto se entiende bien con un ejemplo en el que recurriré a una criatura de la mitología griega llamado Proteo, el cual tenía la capacidad de cambiar de forma.

La idea es esta: en nuestra plantilla pondremos un párrafo que no cambia y un contenido que variará en función de lo indicado en el light dom. Algo así:

<article id="shadow-root">

<b>león</b>

</article>

<template id="plantillaProteo">

<p id="parrafoShadow">Soy proteo y ahora soy un</p>

<content select="b"></content>

</template>

<script>

var plantilla = document.getElementById("plantillaProteo").content;

var nodosListos = document.importNode(plantilla, true);

var shadowRoot = document.getElementById("shadow-root").createShadowRoot();

shadowRoot.appendChild(nodosListos);

</script>

Es decir, en el <content> que hay en el template se insertan los nodos que hay en el light dom definidos entre el par de etiquetas <b></b>, que es el selector indicado en el atributo select.

wc-proteo

Pero como Proteo no sería Proteo si solo pudiera transformarse en león, podemos añadir un input en el light dom y cambiar el selector <b> por un <span> con un id más manejable.

<article id="shadow-root">

<span id="formaProteo">león</span>

</article>

<input type="text" id="js-cambiaProteo">

Así, ahora podemos cambiar la forma de Proteo con un script tan sencillo como este:

<script>

document.getElementById("js-cambiaProteo").addEventListener("keyup", function() {

document.getElementById("formaProteo").innerHTML = this.value

});

</script>

Ale op, si ahora escribimos algo en el input, ese cambio modificará el <content> de la plantilla.

***

Bueno, pues más o menos, espero que con estas cuatro entradas nos hagamos una idea de los mecanismos que hay detrás de los web components. Espero retomar pronto el tema, ya con polymer.

Feliz año nuevo ; ) 

|| Tags: ,

valoración de los lectores sobre web components (4): shadow DOM

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (7 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

Aportar un comentario

*