Native Web Components - part II
Content
Templates
let's have a look at the code we wrote in the part I and try to find out what we can do better :-). So this is our last version of code:
const style = ` <style> #favDialog { position: relative; padding-top: 2em; } .close { position: absolute; font-weight: 700; right: 0; top: 0; padding: .5em; cursor: pointer; } .close:hover { background-color: #e3e3e3; } .menu { margin-top: 2em; display: flex; justify-content: space-between; align-items: center; } </style>`; const template = ` <dialog id="favDialog" open> <span class="close">╳</span> <form method="dialog"> <section> <p><label for="favAnimal">Favorite animal:</label> <select id="favAnimal" name="favAnimal"> <option></option> <option>Brine shrimp</option> <option>Red panda</option> <option>Spider monkey</option> </select></p> </section> <div class="menu"> <button id="cancel" type="reset">Cancel</button> <button type="submit">Confirm</button> </div> </form> </dialog> `; customElements.define('my-popup', class extends HTMLElement { constructor() { super(); } #shadow = this.attachShadow({ mode : 'open'}); #render() { this.#shadow.innerHTML = `${style} ${template}`; } connectedCallback() { this.#render(); } });
First thing to point out here is the line
this.#shadow.innerHTML = `${style} ${template}`;
this means, that every time the component has to render the content it rebuilds the whole HTML structure. Therefore, "innerHTML" is less efficient than creating a new element and appending to an existing HTML-Tag. In other words, creating a new element and appending it to the DOM tree provides better performance than the innerHTML.
And what if this element, what we want to create is a template-Element? (If you don't know what HTML Templates are, you can check this in part I.) This means we create a template, which will only be parsed and not rendered. Then clone its content and append it to our ShadowDOM. In this way, we'll make our component more performant.
Let's change our code a bit:
#render() { const myTemplate = document.createElement('template'); myTemplate.innerHTML = `<style>${css}</style>${html}` this.#shadow.appendChild(myTemplate.content.cloneNode(true)); }
Well, after improving our render method. Let's try to make it a bit dynamic, because for now our component can only render a form with a select element and two buttons. What if we want the content of our popup/dialog to be generic. For this purpose slots are very suitable. So let's have a look.
Slots
A slot is a placeholder inside a web component that users can fill with their own markup.
const html = ` <dialog id="favDialog" open> <span class="close">╳</span> <slot></slot> </dialog> `;
<my-popup> <anything-you-want - this will replace the "slot" defined in the template above /> </my-popup>
we can also name our slot. This is useful when we have to manage multiple slots in a template. For instance:
<dialog id="favDialog" open> <span class="close">╳</span> <slot name="my-content">Default content</slot> <slot name="my-menu">Default Menu</slot> </dialog>
and then use the web component like this (h1 and div can be any other HTML-Tags):
<my-popup> <h1 slot="my-content">Headline</h1> <div slot="my-menu"> <button type="submit">save</button> </div> </my-popup>
The slot property of the Element interface returns the name of the shadow DOM slot the element is inserted in. If no slot names are defined, the user will see the default content of the slots.
We will keep our example simple and just use an unnamed slot as a placeholder for our form.
this is the last Version of our Popup:
As you can see in the HTML, I put the styles of the menu in the my-popup Tag since these styles refer to the "dynamic" content of our popup. In this way we keep things separated and we can therefore better debug our code.
Next
In the next part (III), we'll have a look at Styles and the specific CSS properties for the shadow-dom and also handle Events.