joicomponents
Version:
Design patterns to build native web components
110 lines (100 loc) • 3.53 kB
HTML
<script type="module">
function attributesAbsoluteUrlAsTxt(node) {
let res = "";
for (let i = 0; i < node.attributes.length; i++) {
const name = node.attributes[i].name;
let value = node.attributes[i].value;
if (name === "href" || name === "src")
value = new URL(value, document.baseURI).href;
res += ` ${name}="${value}"`;
}
return res;
}
function transposeTag(node) {
if (!node)
return null; //todo throw a warning here?
const tagName = (node.tagName.startsWith("IFRAME-") ? node.tagName.substr(7) : node.tagName).toLowerCase();
const attributes = attributesAbsoluteUrlAsTxt(node);
const content = node.innerHTML;
return `<${tagName} ${attributes}>${content}</${tagName}>`;
}
class StyledScriptedIframe extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.appendChild(document.createElement("iframe"));
this._iframe = this.shadowRoot.children[0];
this._iframe.setAttribute("sandbox", "allow-scripts");
}
static get observedAttributes() {
return ["srcdoc"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "srcdoc") {
let res = "";
if (this.hasAttribute("included-resources")){
const outerIncluded = this.getAttribute("included-resources")
.split(" ")
.map(selector => document.querySelector(selector));
const outer = outerIncluded.map(child => transposeTag(child));
res += outer.join("");
}
const innerIncluded = Array.from(this.children)
.filter(n => n.tagName === "IFRAME-SCRIPT" || n.tagName === "IFRAME-STYLE" || n.tagName === "IFRAME-LINK");
const inner = innerIncluded.map(child => transposeTag(child));
res += inner.join("");
const src = res + (newValue || "");
let blob = new Blob([src], {type: "text/html"});
this._iframe.setAttribute("src", URL.createObjectURL(blob));
}
}
}
customElements.define("iframe-styled-scripted", StyledScriptedIframe);
</script>
<header-one>Hello<br>sunshine!</header-one>
<hr>
<style id="alpha">
header-one {
color: red;
}
</style>
<style id="beta">
header-one {
background-color: pink;
}
</style>
<link id="uno" rel="stylesheet" href="styledIframeUno.css">
<script id="one">
class HeaderOne extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = "<style>slot {font-weight: bold; font-size: 2em;}</style><slot></slot>"
}
}
customElements.define("header-one", HeaderOne);
console.log("script1"); //should print twice
</script>
<script id="two">
console.log("script2");
</script>
<iframe-styled-scripted
included-resources="script#one script#three style#alpha link#uno"
srcdoc="<header-one>Hello<br>sunshine!</header-one>">
<iframe-style>
header-one {
border-bottom: 4px dotted blue;
}
</iframe-style>
<iframe-link id="dos" rel="stylesheet" href="styledIframeDos.css"></iframe-link>
<iframe-script src="scriptedIframe.js">
exclude me
</iframe-script>
<iframe-script>
console.log("iframe-script says hi!");
</iframe-script>
</iframe-styled-scripted>
<!--
This code is untested. I have only done superficial tests from within devtools in Chrome.
The code should only work in Chrome, Safari, Firefox as template does not work in IE and Edge.
-->