Using templates can help to modularize and reuse HTML code across a website. By defining templates that encapsulate frequently-used HTML content, we can reduce code duplication, and make it easier to update. Template usually be used together with custom elements. When you create a custom element with shadow DOM, the shadow DOM is created dynamically and exists as an encapsulated DOM tree within the custom element.
Shadow DOM
Shadow DOM is another long story, I am not going to go into detail about it, you can refer to MDN Web Docs
“ Shadow DOM allows hidden DOM trees to be attached to elements in the regular DOM tree — this shadow DOM tree starts with a shadow root, underneath which you can attach any element, in the same way as the normal DOM.“
“You can attach a shadow root to any element using the Element.attachShadow() method. This takes as its parameter an options object that contains one option — mode — with a value of open or closed”
Using Template
First, let us use template to display in a specific format.
For example, we might have a list of pictures fetched from an API that we want to display in a specific format, we can define a template and use JavaScript to populate the template with data and add it to the DOM.
We fetch 10 random images from the API using a fetch request. For each item in the response, we clone the item-template and set the src, textContent, and append the cloned template instance to the container.
<template id="item-template">
<div class="item">
<img src="" alt="Item image">
<h2></h2>
<p></p>
</div>
</template>
const catTemplate = document.getElementById('item-template');
const catContainer = document.getElementById('container');
const apiKey = 'live_g2TlRXkZBAczbt1TkDyGIjgWk2Yd1wHzR7NahHoqdHGLa0bVho0uKmSl8u9otL1v';
fetch(`https://api.thecatapi.com/v1/images/search?limit=10`, {
headers: {
'x-api-key': apiKey
}
})
.then(response => response.json())
.then(data => {
data.forEach(cat => {
const catInstance = catTemplate.content.cloneNode(true);
catInstance.querySelector('img').src = cat.url;
catInstance.querySelector('h2').textContent = cat.id;
catInstance.querySelector('p').textContent = `Width: ${cat.width} Height: ${cat.height}`;
catContainer.appendChild(catInstance);
});
})
.catch(error => {
console.error('Error fetching cat images:', error);
});
Nested template
Now we have populated the container with 10 cat pictures, then we want to add some interactive features to each picture. We want to add two buttons on each picture. We can create another template and nest it into the item-template.
<template id="item-template">
<div class="item">
<img src="" alt="Item image">
<h2></h2>
<p></p>
<button-group></button-group>
</div>
</template>
<template id="button-template">
<button class="share-btn">Share</button>
<button class="favorite-btn">Favorite</button>
</template>
Using template with Custom elements
Template and custom elements usually appear together.
Without defining a custom element(which here comes from template by using document.getElementById(‘id-name’).content), the template will not be rendered.
In this example, we define a custom element called button-group that encapsulates the button-template. We then add the button-group element to the item-template as mentioned above and use it in the loop that populates the container with items from the API.
class ButtonGroup extends HTMLElement {
constructor() {
super();
const template = document.getElementById('button-template');
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.content.cloneNode(true));
shadowRoot.querySelector('.share-btn').addEventListener('click', () => {
console.log('Share button clicked');
});
shadowRoot.querySelector('.favorite-btn').addEventListener('click', () => {
console.log('Favorite button clicked');
});
}
}
customElements.define('button-group', ButtonGroup);
Using template with Custom element and Slot
As we mentioned above, a template is used to define the structure and styling of a custom element, but you might want to customize the content of the custom element without modifying the template itself. That’s where the slot element comes in handy. It serves as a placeholder in the template, allowing you to pass content from the main document into the custom element, effectively adjusting the data in the cloned template.
<template id="button-template">
<button class="share-btn">Share</button>
<button class="favorite-btn">Favorite</button>
<slot name="cat">
<legend>meow!</legend>
</slot>
</template>
In this example, we add a slot in button-template, then the picture will show “meow!”
However, we could edit “meow!” without changing the button-template, instead, we could change the content that has the slot with the same name attribute, here it is in item-template and we change it to “meow meow”, then the picture will show “meow meow”.
<template id="item-template">
<div class="item">
<img src="" alt="Item image">
<h2></h2>
<p></p>
<button-group>
<p slot="cat">meow meow</p>
</button-group>
</div>
</template>
Reference:
Mozilla Developer Network. (n.d.). Using shadow DOM. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
web.dev. (n.d.). Template, slot, and shadow. web.dev. https://web.dev/learn/html/template/
Repo of the work:
https://github.com/Blurmilk/579-presentation