UNPKG

joicomponents

Version:

Design patterns to build native web components

169 lines (152 loc) 5.11 kB
const outerTemplate = document.createElement("template"); outerTemplate.innerHTML = ` <style> :host{ overflow: visible; display: inline-block; } iframe { border: none; margin: 0; padding: 0; overflow: visible; /*initial values, they are overridden immediately*/ height: 0; width: 0; } </style> <iframe sandbox="allow-scripts" scrolling="no" frameborder="0"></iframe>`; function makeBaseString(baseURI) { if (!baseURI) return ""; const base = document.createElement("base"); base.setAttribute("href", baseURI); return base.outerHTML; } const innerStyle = `<style> html { border: none; margin: 0; padding: 0; overflow: visible; } </style> `; const allowOverflow = ` <script>(function(){ let pW = undefined; let pH = undefined; const r = document.children[0]; const s = document.styleSheets[0].cssRules[0]; window.addEventListener("message", function(e){ for (let [key, value] of Object.entries(JSON.parse(e.data))) s.style[key] = value; alertSize(); }); function alertSize() { const w = r.scrollWidth, h = r.scrollHeight; if (pW === w && pH === h) return setTimeout(alertSize, 150); pW = w; pH = h; parent.postMessage(JSON.stringify({w, h}), "*"); requestAnimationFrame(alertSize); } alertSize(); })();</script>`; const allowBrowse = ` <script src="https://unpkg.com/joievents@1.0.16/src/link-click-es6.js"></script> <script src="https://unpkg.com/joievents@1.0.16/src/browse.js"></script> <script> window.addEventListener("browse", function(e){ e.preventDefault(); let event = {type: e.type, timeStamp: e.timeStamp, href: e.getHref()}; parent.postMessage(JSON.stringify({event}), "*"); }, true); </script>`; 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}>`; } import {LayoutAttributesMixin} from "https://unpkg.com/joicomponents@1.2.31/src/layout/LayoutAttributesMixin.js"; class InlineIframe extends LayoutAttributesMixin(HTMLElement) { constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.appendChild(outerTemplate.content.cloneNode(true)); this._iframe = this.shadowRoot.children[1]; window.addEventListener("message", this.onMessage.bind(this)) } static get observedAttributes() { return ["srcdoc", "_layout-width"]; } attributeChangedCallback(name, oldValue, newValue) { if (name === "_layout-width") { console.log(newValue); this.sendMessage(JSON.stringify({width: newValue})); } if (name === "srcdoc") { const src = newValue ? this._makeIframeContent(newValue) : ""; let blob = new Blob([src], {type: "text/html"}); this._iframe.setAttribute("src", URL.createObjectURL(blob)); } } _makeIframeContent(txt) { let res = innerStyle; if (this.hasAttribute("allow-overflow")) res += allowOverflow; if (this.hasAttribute("base")) res += makeBaseString(this.getAttribute("base")); res += this.addScriptsAndStyles(); res += txt; if (this.hasAttribute("allow-browse")) res += allowBrowse; return res; } addScriptsAndStyles() { const outerIncluded = this.getAttribute("included-resources") .split(" ") .map(selector => document.querySelector(selector)); const innerIncluded = Array.from(this.children) .filter(n => n.tagName === "IFRAME-SCRIPT" || n.tagName === "IFRAME-STYLE" || n.tagName === "IFRAME-LINK"); const outer = outerIncluded.map(child => transposeTag(child)); const inner = innerIncluded.map(child => transposeTag(child)); return outer.join("") + inner.join(""); } sendMessage(value) { if (!this._ready) //delay sending this message until return requestAnimationFrame(this.sendMessage.bind(this, value)); //inner iframe is ready to receive messages this._iframe.contentWindow.postMessage(value, "*"); } onMessage(e) { if (e.source !== this._iframe.contentWindow) return; this._ready = true; const res = JSON.parse(e.data); if (res.w) this._iframe.style.width = res.w + "px"; if (res.h) this._iframe.style.height = res.h + "px"; if (res.event) this.dispatchEvent(new CustomEvent(res.event.type, { bubbles: true, composed: true, detail: {href: res.event.href} })); } } customElements.define("inline-iframe", InlineIframe);