Skip to content

Instantly share code, notes, and snippets.

@mantyr
Last active January 20, 2025 03:56
Show Gist options
  • Select an option

  • Save mantyr/a5ab8812748029134519a519b2d4e588 to your computer and use it in GitHub Desktop.

Select an option

Save mantyr/a5ab8812748029134519a519b2d4e588 to your computer and use it in GitHub Desktop.
Shadow DOM and base component for web components
export class ShadowDomBaseComponent extends HTMLElement {
static shadowMode = 'open';
static templateURL = '';
static templateCache = null;
constructor() {
super();
this.isCustomTag = true;
const shadowMode = this.constructor.shadowMode || 'open';
this.attachShadow({ mode: shadowMode });
}
connectedCallback() {
this.shadowRoot.innerHTML = '';
this.shadowRoot.appendChild(this.constructor.templateCache.cloneNode(true));
}
static async init() {
try {
const defaultTemplateURL = new URL('component.tmpl', import.meta.url);
const address = this.constructor.templateURL || defaultTemplateURL.href;
const response = await fetch(address);
if (!response.ok) throw new Error(`Failed to fetch template from ${address}`);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
this.templateCache = doc.body.firstChild;
} catch (error) {
console.error(`Error preloading template: ${error}`);
throw error;
}
}
static async define(tagName) {
customElements.define(tagName, this);
const customElements = document.querySelectorAll(tagName);
customElements.forEach(element => {
if (!element.isCustomTag) {
const newComponent = new this();
element.replaceWith(newComponent);
}
});
}
}
import {ShadowDomBaseComponent} from "base/shadow-dom-base-component.js"
export class AComponent extends ShadowDomBaseComponent {
static shadowMode = 'open';
constructor() {
super();
}
}
await AComponent.init();
await AComponent.define("a-component");
import {ShadowDomBaseComponent} from "base/shadow-dom-base-component.js"
export class BComponent extends ShadowDomBaseComponent {
static shadowMode = 'close';
static templateURL = "https://example.com/component-b/template.html";
constructor() {
super();
}
connectedCallback() {
super.connectedCallback();
this._attachEvents();
this._renderData();
}
_renderData() {
const dataElement = this.shadowRoot.querySelector('.data-container');
if (dataElement) {
dataElement.textContent = this.getAttribute('data');
}
}
_attachEvents() {
const button = this.shadowRoot.querySelector('button');
if (button) {
button.addEventListener('click', this._onButtonClick.bind(this));
}
}
_onButtonClick() {
console.log('Button clicked!');
alert('Button clicked in BComponent!');
}
}
await BComponent.init();
await BComponent.define("b-component");
<!doctype html>
<html>
<head>
<script type="module" src="component-b/component.js"></script>
</head>
<body>
<b-component data="example text"></b-component>
</body>
</html>
<!-- https://example.com/component-b/template.html -->
<template>
<div>
<h1>BComponent</h1>
<div class="data-container"></div>
<button>Click me</button>
</div>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment