joicomponents
Version:
Design patterns to build native web components
110 lines (88 loc) • 4.75 kB
Markdown
# HowTo: CreateShadowDOM
> This cookbook advocate setting up the shadowDOM in the `constructor()`.
> This builds on a few premises:
> 1. If you need to delay custom element construction, use [Pattern: TemplateSwitcheroo](6_Pattern_TemplateSwitcheroo).
> 2. To select which DOM is shown based on attributes, is likely best done using
> `:host([attribute="something"])`.
> 3. The default values of attributes is "not set". Avoid using an HTML value as a web components
> default setting
> 4. Sure, if you *really* need to know the host element attributes or delay until `connectedCallback()`,
> you can construct the shadowDOM in `connectedCallback()` as an *alternative* strategy.
The shadowDOM is initiated in the `constructor()`.
There are three main strategies to create a shadowDom:
1. **Basic strategy**
2. **Template strategy**
3. **Engine strategy**
## Basic strategy: `.innerHTML`
The Basic strategy is to create and populate the shadowRoot using `.innerHTML` in the `constructor()`.
Use this strategy when:
* there are no (hackable) variables in your template (`.innerHTML` is unsafe with variable content),
* the template is fairly static, and
* few instances are made of the element (as the `.innerHTML` needs to be run for every element).
```javascript
class BasicComponent extends HTMLElement {
constructor(){
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = "<span>the</span> <span>basics</span>";
}
}
```
The Basic strategy is often a simple and good way to get started during development.
Then, just refactor to one of the other two strategies when your component nears completion.
## Template strategy: `HTMLTemplateElement`
The Template strategy consists of the following steps:
1. Set up a template outside of the element.
2. Set up the shadowRoot in the constructor.
3. Populate the shadowRoot by deep cloning the template.
```javascript
const template = document.createElement("template"); //[1]
template.innerHTML = "<span>the</span> <span>template</span>"; //[2]
class TemplateComponent extends HTMLElement {
constructor(){
super();
this.attachShadow({mode: "open"});
this.shadowRoot.appendChild(template.content.cloneNode(true)); //[3]
}
}
```
1. An `HTMLTemplateElement` object is setup outside of the custom element.
2. A set of elements is added to the template using `.innerHTML`.
3. A *clone* of the `template.content` is added to the shadowRoot of each element.
The benefit of the template strategy over the basic strategy, is that
`this.shadowRoot.appendChild(template.content.cloneNode(true))` is more efficient than
`this.shadowRoot.innerHTML = "<span>it takes</span>a little time<span>to parse html.</span>"`
when you create *several* instances of the same custom element.
## Engine strategy: using LighterDOM or lit-html
LighterDOM and lit-html are two template literals engines.
Both LighterDOM and lit-html create efficient and safe ways to include variables in your template.
Under the hood, both use tagged functions for template literals.
But, LighterDOM and lit-html can also reuse templates across elements of different types,
and they also do clever analysis to:
* avoid updates of the DOM when there are no changes,
* *only* update the part of the template that change, and
* add references to listener functions using the .addEventListener API where appropriate.
Choose the Engine strategy over the other strategies when:
* the content of the template can change (particularly in a loopy type of way) and
* the template might contain variables provided from the outside.
```javascript
import hyperHTML from 'https://unpkg.com/hyperhtml?module';
class HyperComponent extends HTMLElement {
constructor(){
super();
this.attachShadow({mode: "open"});
let a = "the";
let b = "engine";
this.html = hyperHTML.bind(this.shadowRoot); //[1]
this.html`<span>${a}</span> <span>${b}</span>`; //[2]
}
}
```
1. Adds the custom template engine for this element as a
tag-function for string literals called `this.html`.
2. Then use `this.html` to update the element shadowDom.
#### References
* [MDN: Using custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
* [MDN: Using templates and slots](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)
* [hyperHTML](https://viperhtml.js.org/hyper.html)
* [lit-HTML](https://github.com/Polymer/lit-html)