Skip to content

Instantly share code, notes, and snippets.

@mattkenefick
Last active August 4, 2021 15:03
Show Gist options
  • Save mattkenefick/6c5b8e540f77c3f112d0b7fc565b48f0 to your computer and use it in GitHub Desktop.
Save mattkenefick/6c5b8e540f77c3f112d0b7fc565b48f0 to your computer and use it in GitHub Desktop.
Shadow DOM
<main>
<my-paragraph>
Lorem ispum sit amet dolor
</my-paragraph>
<hr />
<labeled-input>
This is the form label
</labeled-input>
</main>
<!-- Template for the MyParagraphElement class -->
<template id="my-paragraph">
<style>
section {
background-color: #fde7fc;
padding: 5px;
}
</style>
<section>
<h3>Example Header</h3>
<div>
<slot>Ambulance is on its way</slot>
</div>
<button>
Click Me
</button>
</section>
</template>
<!-- Template for the LabeledInputElement class -->
<template id="labeled-input">
<label>
<div><slot></slot></div>
<input type="text" />
</label>
</template>
/**
* Base class for our shadow elements
*/
class CustomHtmlElement extends HTMLElement
{
/**
* Optional template element. If one is not supplied, we
* will try to infer one based on the classname.
*
* @param HTMLElement template
* @return void
*/
static attach(template) {
if (!template) {
// Convert MyParagraphElement to my-paragraph
const tagName = this.name
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.toLowerCase()
.replace(/-?element/, '');
template = document.querySelector(`#${tagName}`);
}
// Save template reference
this.template = template;
// Create shadow object
customElements.define(this.template.id, this);
}
/**
* @return void
*/
constructor() {
super();
// Clone element from our template
this.templateNode = this.constructor.template.content.cloneNode(true);
// Make shadow
this.attachShadow({ mode: 'open' }).appendChild(this.templateNode);
// Attach events call
this.attachEvents();
}
/**
* @return void
*/
attachEvents() {
// Not implemented
}
/**
* @return void
*/
detachEvents() {
// Not implemented
}
}
/**
* Custom element class extends our root class
*
* @extends CustomHtmlElement
*/
class MyParagraphElement extends CustomHtmlElement {
/**
* Attach events to the DOM
*
* @return void
*/
attachEvents() {
this.shadowRoot
.querySelector('button')
.addEventListener('click', this.Handle_OnClickButton);
}
/**
* Respond to click events
*
* @param MouseEvent e
* @return void
*/
Handle_OnClickButton(e) {
alert('This button has been clicked');
}
}
/**
* Basic labeled input
*
* @extends CustomHtmlElement
*/
class LabeledInputElement extends CustomHtmlElement {
// Not implemented
}
// -------------------------------------------------------------------------
// ⬇︎ We could explicitly pass in an element
// const element = document.querySelector('#my-paragraph');
// MyParagraphElement.attach(element);
// ⬇︎ Or we could derive it from the class name automatically
// MyParagraphElement.attach();
// ⬇︎ Or we can try to infer it inversely based on our templates
Array.from(document.getElementsByTagName('template')).forEach(element => {
// Convert "my-paragraph" to "MyParagraphElement"
const className = element.id
.replace(/^([a-z])/, m => m.toUpperCase())
.replace(/-([a-z])/g, m => m.toUpperCase())
.replace('-', '')
+ 'Element';
const reference = eval(className);
reference.attach();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment