Skip to content

Instantly share code, notes, and snippets.

@AutoSponge
Last active Jun 9, 2022
Embed
What would you like to do?
light dom render custom element
<!DOCTYPE html>
<html lang="en">
<head>
<title>Minimal Custom Elements (light &amp; shadow)!</title>
<!-- Import the webpage's stylesheet -->
<link rel="stylesheet" href="/style.css" />
<!-- Import the webpage's javascript file -->
<script src="/script.js" defer></script>
</head>
<body>
<main>
<h1>Render custom elements in light and shadow dom</h1>
<em>Now with slots!</em>
<p>(blank custom element with placeholder info in slots)</p>
<element-details></element-details>
<element-details light>
<span>(complex example with</span>
<span slot="element-name">slot</span>
<span slot="description"
>A placeholder inside a web component that users can fill with their
own markup, with the effect of composing different DOM trees
together.</span
>
<dl slot="attributes">
<dt>name</dt>
<dd>The name of the slot.</dd>
</dl>
default slot)
</element-details>
<element-details light>
<span slot="element-name">template (light)</span>
<span slot="description"
>A mechanism for holding client- side content that is not to be
rendered when a page is loaded but may subsequently be instantiated
during runtime using JavaScript. Note: the style element is only
injected once since it's global.</span
>
<dl slot="attributes">
<dt>light</dt>
<dd>(no value) renders to light dom</dd>
</dl>
</element-details>
<element-details>
(default slot in shadow)
<span slot="element-name">template (shadow)</span>
<span slot="description"
>A mechanism for holding client- side content that is not to be
rendered when a page is loaded but may subsequently be instantiated
during runtime using JavaScript. Note: the style element is cloned
with the template.</span
>
</element-details>
<p id="dynamic">(put a dynamic instance here)</p>
<p>
See <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots" rel="noopener nofollow noreferrer" >MDN</a> for the original version of this demo.
</p>
</main>
</body>
</html>
customElements.define(
"element-details",
class extends HTMLElement {
constructor() {
super();
this.templateId = `${this.localName}-template`;
this.template = null;
this.queryTemplate();
}
queryTemplate() {
this.template = document.getElementById(this.templateId);
return this.template;
}
renderTemplate() {
document.body.insertAdjacentHTML(
"beforeend",
`<template id="element-details-template">
<style>
details {font-family: "Open Sans Light",Helvetica,Arial}
.name {font-weight: bold; color: #217ac0; font-size: 120%}
h4 { margin: 10px 0 -8px 0; }
h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
h4 span { border: 1px solid #cee9f9; border-radius: 4px }
h4 span { color: white }
.attributes { margin-left: 22px; font-size: 90% }
.attributes p { margin-left: 16px; font-style: italic }
</style>
<slot></slot>
<details>
<summary>
<span>
<code class="name">&lt;<slot name="element-name">NEED NAME</slot>&gt;</code>
<i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
</span>
</summary>
<div class="attributes">
<h4><span>Attributes</span></h4>
<slot name="attributes"><p>None</p></slot>
</div>
</details>
<hr>
</template>`
);
this.queryTemplate();
}
renderLight() {
const content = this.template.content.cloneNode(true);
const style = content.querySelector("style");
if (style) style.parentNode.removeChild(style);
if (style && !document.querySelector(`#${this.localName}-style`)) {
style.id = `${this.localName}-style`;
document.head.insertAdjacentElement("beforeend", style);
}
// handle default slot contents and default slot
const defaultSlot = content.querySelector("slot:not([name])");
if (defaultSlot) {
const fragment = document.createDocumentFragment();
const children = Array.from(this.childNodes).filter(
(node) => !node.matches || !node.matches(`[slot]`)
);
fragment.append(...children);
content.replaceChild(fragment, defaultSlot);
}
// handle named slots
const slots = content.querySelectorAll("slot[name]");
slots.forEach((slot) => {
const el = this.querySelector(`[slot="${slot.name}"]`);
const parent = slot.parentNode;
if (el) return parent.replaceChild(el, slot);
// handle missing slots with default content
const fragment = document.createDocumentFragment();
while (slot.firstChild) {
fragment.appendChild(slot.firstChild);
}
parent.replaceChild(fragment, slot);
});
this.appendChild(content);
}
renderShadow() {
const templateId = `${this.localName}-template`;
var template = document.getElementById(templateId).content;
this.attachShadow({ mode: "open" }).appendChild(template.cloneNode(true));
}
connectedCallback() {
if (!this.template) this.renderTemplate();
if (this.hasAttribute("light")) return this.renderLight();
this.renderShadow();
}
}
);
document
.getElementById("dynamic")
.insertAdjacentHTML(
"beforeend",
`<element-details><span slot="description">dynamic</span></element-details>`
);
@AutoSponge
Copy link
Author

AutoSponge commented Jun 9, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment