Traducción en curso

Este artículo explica como puedes usar los elementos <template> y <slot> para crear una plantilla flexible que luego puede ser usada para llenar el DOM de un componente web.

La verdad acerca de las plantillas

Cuando tienes que reutilizar las mismas estructuras de lenguaje marcado repetidas veces en una página web, hace sentido utilizar algun tipo de plantilla más allá de repetir la misma estructura una y otra vez. Así se hacia antes, pero es mucho mas facil hacerlo con el elemento HTML <template> (el cual es compatible con los navegadores modernos). Este elemento y su contenido no son renderizados en el DOM, pero si pueden ser referenciados via JavaScript.

Echemos un vistazo a un ejemplo sencillo.

<template id="my-paragraph">
  <p>My paragraph</p>
</template>

Este no aparecerá en tu página hasta que tu hagas una referencia a el con JavaScript y luego lo agregues al DOM, haciendo algo parecido a lo siguiente.

let template = document.getElementById('my-paragraph');
let templateContent = template.content;
document.body.appendChild(templateContent);

Aunque simple, ya puedes comenzar a ver su utilidad.

Usando plantillas con componentes web

Las plantillas son útiles por si mismas, pero estas trabajan aun mejor con componentes web. Definamos un componente web que usa nuestra plantilla con el contenido de su DOM sombra. Lo nombraremos <my-paragraph>:

customElements.define('my-paragraph',
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById('my-paragraph');
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(templateContent.cloneNode(true));
  }
})

El punto clave a tomar en cuenta aqui es que agregamos un clon del contenido de la plantilla a la raíz sombra, creado usando el método Node.cloneNode() method.

Y debido a que estamos agregando su contenido a un DOM de sombra, podemos incluir cierta información de estilo dentro de la plantilla en un elemento <style>, que luego se encapsula dentro del elemento personalizado. Esto no funcionaría si nosotros solo lo agregasemos al DOM estandar.

Por ejemplo:

<template id="my-paragraph">
  <style>
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
</template>

Ahora podemos usarlo simplemente agregandolo a nuestro documento HTML:

<my-paragraph></my-paragraph>

Nota: Las plantillas son bien soportadas in navegadores: la API del DOM sombra es soportada por default en Firefox (version 63 onwards), Chrome, Opera, and Safari, Edge está trabajando en una implementación.

 

Añadiendo flexibilidad con slots

Hasta ahora todo bien, pero el elemento no es muy flexible. Solo podemos mostrar una parte de texto dentro de él, quiere decir que hasta el momento, es menos útil que un parrafo regular. Podemos mostrar texto diferente en cada instancia de elemento de una manera declarativa agradable usando el elemento <slot> Este tiene un soporte mas limitado que el elemento <template>, disponible desde Chrome 53, Opera 40, Safari 10, Firefox 59, aun no está disponible en Edge.

Los slots son identificados por su atributo name, y te permiten definir marcadores de posición en tu plantilla que pueden rellenarse con cualquier fragmento de marcado que quieras cuando el elemento es usado en el marcado.

Entonces, si queremos agregar un slot dentro de nuestro ejemplo sencillo, podemos actualizar el elemento de párrafo de la plantilla de la siguiente manera:

<p><slot name="my-text">My default text</slot></p>

Si el contenido del slot no está definido cuando el elemento se agrega al marcado, o si el navegador no soporta el elemento slot, <my-paragraph> solo contiene el texto alternativo "My default text".

Para definir el contenido de un slot, incluimos una estructura HTML dentro de el elemento <my-paragraph> con un slot atributo cuyo valor es igual al nombre del slot que  queremos que rellene. Esto puede ser cualquier cosa que tu quieras, por ejemplo.

<my-paragraph>
  <span slot="my-text">Let's have some different text!</span>
</my-paragraph>

o también

<my-paragraph>
  <ul slot="my-text">
    <li>Let's have some different text!</li>
    <li>In a list!</li>
  </ul>
</my-paragraph>

Nota: Los elementos que pueden ser insertados en los slots son conocidos como Slotable; cuando un elemento ha sido insertado en un slot, se dice que fue eslotado por su término en inlgés slotted.

Y ese es nuestro ejemplo trivial. Si quieres jugar con él un poco más, puedes encontrarlo en GitHub (también puedes verlo en vivo).

 

Un ejemplo más completo

Para finalizar el artículo, veamos algo menos trivial.

El siguiente conjunto de fragmentos de código muestran como usar <slot> junto con  <template> y algo de JavaScript para:

  • crear un elemento <element-details>  con slots nombrados en su nodo raíz
  • diseñe el elemento <element-details> de forma que, cuando se use en documentos, este sea renderizado desde la composición del contenido del elemento junto con el contenido de su nodo raíz, es decir, se utilizan partes del contenido del elemento para colocar en el nodo raíz de los slots con atributo name

Observa que es técnicamente posible usar el elemento <slot> sin un elemento <template>, por ejemplo, dentro de un elemento <div> regular, y aun asi tomar ventaja de los indicadores de posición de el elemento <slot> para el contenido del Shadow del DOM y al hacerlo puedes evitar el problema de tener que acceder primero a la propiedad content del elemento de la plantilla y clonarlo. Sin embargo, es por lo general, más práctico agregar slots dentro de un elemento <template>, ya que es poco probable que necesites definir un patrón basado en un elemento ya renderizado.

Además, aun si no está renderizado, el propósito del contenedor como plantilla debería ser semánticamente más claro cuando se usa el elemento <template>. Además, el elemento <template> puede tener elementos agregados directamente a el, como <td>, que desaparecerían al agregarse a <div>.

Nota: Tu puedes encontrar el ejemplo completo en element-details (también lo puedes ver en vivo)

Creando una plantilla con algunos elementos slot

En primer lugar, usamos el elemento <slot> dentro de un elemento <template> para craer un nuevo elemento de tipo document fragment llamado "element-details-template" que contiene algunos named slots:

<template id="element-details-template">
  <style>
  details {font-family: "Open Sans Light",Helvetica,Arial}
  .name {font-weight: bold; color: #217ac0; font-size: 120%}
  h4 { margin: 10px 0 -8px 0; }
  h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
  h4 span { border: 1px solid #cee9f9; border-radius: 4px }
  h4 span { color: white }
  .attributes { margin-left: 22px; font-size: 90% }
  .attributes p { margin-left: 16px; font-style: italic }
  </style>
  <details>
    <summary>
      <span>
        <code class="name">&lt;<slot name="element-name">NEED NAME</slot>&gt;</code>
        <i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
      </span>
    </summary>
    <div class="attributes">
      <h4><span>Attributes</span></h4>
      <slot name="attributes"><p>None</p></slot>
    </div>
  </details>
  <hr>
</template>

Ese elemento <template> tiene varias características.

Crear un nuevo <element-details> desde el elemento <template>

A continuación, crearemos un nuevo elemento personalizado llamado <element-details> y usaremos Element.attachShadow para anclarlo, como su shadow root ese fragmento de documento que nosotros creamos anteriormente con nuestro elemento <template>. Usa exáctamente el mismo patrón que vimos atrás en el ejemplo trivial.

customElements.define('element-details',
  class extends HTMLElement {
    constructor() {
      super();
      var template = document
        .getElementById('element-details-template')
        .content;
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(template.cloneNode(true));
  }
})

Usando el elemento <element-details> con slots nombrados (named slots)

Ahora tomaremos el elemento <element-details> para usarlo en nuestro documento.

<element-details>
  <span slot="element-name">slot</span>
  <span slot="description">A placeholder inside a web
    component that users can fill with their own markup,
    with the effect of composing different DOM trees
    together.</span>
  <dl slot="attributes">
    <dt>name</dt>
    <dd>The name of the slot.</dd>
  </dl>
</element-details>

<element-details>
  <span slot="element-name">template</span>
  <span slot="description">A mechanism for holding client-
    side content that is not to be rendered when a page is
    loaded but may subsequently be instantiated during
    runtime using JavaScript.</span>
</element-details> 

Observaciones del snippet anterior:

  • El snippet tiene dos instancias de elementos <element-details> las cuales usan el atributo slot para referencias los slots nombrados "element-name" y "description" que colocamos en el nodo raíz <element-details>
  • Solo el primero de esos dos elementos <element-details> hace referencia a los "attributes" de slot nombrados. El segundo elemento <element-details> carece de cualquier referencia a "attributes" de slot nombrados.
  • El primer elemento <element-details> está referenciando los "attributes"  de slot nombrados usando un elemento <dl> con <dt> and <dd> como hijos.

Agreguemos algunos estilos

Como toque final,agregaremos algunos estilos CSS para <dl>, <dt>, y <dd>

  dl { margin-left: 6px; }
  dt { font-weight: bold; color: #217ac0; font-size: 110% }
  dt { font-family: Consolas, "Liberation Mono", Courier }
  dd { margin-left: 16px }

Resultado

Finalmente, juntemos todos los fragmentos y veamos cómo se ve el resultado renderizado.

ScreenshotLive sample

Observa los siguientes puntos del resultado renderizado

  • Aún cuando las instancias del elemento <element-details> en el documento no usan directamente el elemento <details>, ellos se renderizan usando <details> porque el shadow root hace que ellos se llene con eso.
  • Dentro de la salida de <details>, el contenido de los elementos <elemento-detalles> llena los named slots desde el shadow root. En otras palabras, el árbol DOM de los elementos <elemento-detalles> se compone junto con el contenido del shadow root.
  • Para ambos elementos <element-details>, un encabezado de Attributos se agrega automáticamente desde el shadow root antes de la posición del named slot "attributes".
  • Ya que el primer elemento <element-details> tiene un elemento <dl> el cual hace referencia explicita al named slot "attributes" desde su shadow root, el contenido de ese <dl> reemplaza los named slot "attributes" desde el shadow root
  • Ya que el segundo elemento <element-details> no hace referencia explicita al named slot "attributes" desde su shadow root, su contenido para el que named slot se llena con el contenido predeterminado desde shadow root.

Etiquetas y colaboradores del documento

Colaboradores en esta página: BrunoUY, mdnwebdocs-bot, ulisestrujillo
Última actualización por: BrunoUY,