Skip to content

Instantly share code, notes, and snippets.

@IanSSenne
Created January 22, 2020 10:09
Show Gist options
  • Save IanSSenne/34c27d2259e570f260dfbbe1109f2dbf to your computer and use it in GitHub Desktop.
Save IanSSenne/34c27d2259e570f260dfbbe1109f2dbf to your computer and use it in GitHub Desktop.
(() => {
const addStyleSnippet = (() => {
const styles = document.createElement("style");
document.head.appendChild(styles);
return function(snippet) {
styles.innerHTML += snippet;
};
})();
const UTILSTORE = { templates: {}, toRender: [] };
class NodeValuePointer$attribute {
constructor(attribute) {
this.attribute = attribute;
this.default = this.attribute.nodeValue;
}
update(store) {
let nextContent = this.default;
for (let key in store) {
nextContent = nextContent.replace(
new RegExp('\\[\\["' + key + '"\\]\\]', "g"),
store[key] === undefined
? "MISSING VALUE FOR KEY " + store[key]
: store[key]
);
}
this.attribute.nodeValue = nextContent;
}
}
class NodeValuePointer$text {
constructor(textNode) {
this.textNode = textNode;
this.default = this.textNode.textContent;
}
update(store) {
let nextContent = this.default;
for (let key in store) {
nextContent = nextContent.replace(
new RegExp('\\[\\["' + key + '"\\]\\]', "g"),
store[key] === undefined
? "MISSING VALUE FOR KEY " + store[key]
: store[key]
);
}
this.textNode.textContent = nextContent;
}
}
class NodeTemplate {
constructor(name, node) {
UTILSTORE.templates[name] = this;
this.name = name;
this.node = node;
this.children = [];
this.knownData = [];
for (let child of this.node.childNodes) {
if (
(this.node instanceof Text && this.node.textContent.trim() != "") ||
this.node instanceof Element
)
this.children.push(new NodeTemplate(null, child));
}
if (this.node instanceof Text) {
if (this.node.textContent.match(/\[\[".+"\]\]/))
this.knownData.push(new NodeValuePointer$text(this.node));
} else {
for (let attribute of this.node.attributes) {
if (attribute.nodeValue.match(/\[\[".+"\]\]/))
this.knownData.push(new NodeValuePointer$attribute(attribute));
}
}
}
generate(data) {
for (let pointer of this.knownData) {
pointer.update(data);
}
for (let child of this.children) {
child.generate(data);
}
if (this.node.nodeName === "SLOT") {
this.node.innerHTML = "";
for (let i = 0; i < data.children.length; i++)
this.node.appendChild(data.children[i].cloneNode(true));
}
if (this.name) {
let res = this.node.cloneNode(true);
res.attributes.setNamedItem(document.createAttribute("data-visible"));
res.attributes["data-type"].nodeValue = "rendered-template";
res.attributes.removeNamedItem("data-template");
return res;
}
}
}
class NodeTree {
constructor(node) {
this.node = node;
this.children = [];
for (let child of this.node.childNodes) {
this.children.push(new NodeTree(child));
}
}
generateTemplate(name) {
return new NodeTemplate(name, this.node);
}
}
function getNodeTree(node) {
return new NodeTree(node);
}
function isUtilElementCheck(Element) {
if (Element.nodeName != "UTIL") {
console.warn(Element);
throw new Error("unable to call function on non util element");
}
}
function validateUtilFunction(Element, type) {
if (Element.getAttribute("data-type") != type) {
console.warn(Element);
throw new Error("unable to call function with incorrect data-type");
}
}
HTMLUnknownElement.prototype.registerTemplate = function() {
isUtilElementCheck(this);
validateUtilFunction(this, "template");
const name = this.getAttribute("data-template");
if (UTILSTORE.templates[name]) {
console.warn(
'failed to register template "' +
name +
'", do you have multiple declarations for it.'
);
return UTILSTORE.templates[name];
} else {
// document.head.appendChild(this);
this.remove();
console.log('registered template "' + name + '" from element', this);
return getNodeTree(this).generateTemplate(name);
}
};
HTMLUnknownElement.prototype.render = function(data) {
isUtilElementCheck(this);
validateUtilFunction(this, "insert");
const name = this.getAttribute("data-target");
if (data === undefined) {
data = JSON.parse(this.getAttribute("data-input"));
}
data.children = this.children;
const rendered = UTILSTORE.templates[name].generate(data);
this.replaceWith(rendered);
return rendered;
};
addStyleSnippet("util:not([data-visible]){display:none;}");
function renderTemplates() {
if (undefined === UTILSTORE.toRender[0]) return;
while (UTILSTORE.toRender[0]) {
const el = UTILSTORE.toRender.shift();
el.render();
}
PUtil.renderAllTemplates();
}
Object.defineProperty(globalThis, "PUtil", {
value: {
["registerTemplates"]: function() {
Array.from(
document.querySelectorAll('util[data-type="template"]')
).forEach(element => element.registerTemplate());
},
["renderTemplate"]: function(name, data) {
const template = UTILSTORE.templates[name];
return template.generate(data);
},
["renderAllTemplates"]: function() {
UTILSTORE.toRender.push.call(
UTILSTORE.toRender,
Array.from(
document.querySelectorAll('util[data-type="insert"]')
).filter(el => !UTILSTORE.toRender.includes(el))
);
UTILSTORE.toRender = UTILSTORE.toRender.flat();
renderTemplates();
},
["createElement"]: function(str) {
return new DOMParser().parseFromString(str, "text/html").body
.children[0];
}
},
writable: false
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment