Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Created May 1, 2020 01:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cferdinandi/30de88b0fe2d24bd7b7c3bac3126e99c to your computer and use it in GitHub Desktop.
Save cferdinandi/30de88b0fe2d24bd7b7c3bac3126e99c to your computer and use it in GitHub Desktop.
/**
* Teroy: The smallest JavaScript state-based component UI renderer - "Keepin' it Vanilla."
* Length: 100 lines.
* Global support: 93.89% (https://caniuse.com/#feat=proxy)
* Github: https://github.com/MathiasWP/TeroyJS
* NPM: https://www.npmjs.com/package/teroy
* Creator: Mathias Picker.
* License: MIT
*/
(function() {
class Teroy {
constructor(element, component) {
this.element = document.querySelector(element);
if (!this.element) {
throw `TEROY: ${element} not found.`;
}
if (!component.render) {
// @note You might want to also check that render is a function
// if (!component.render || typeof component.render !== 'function')
throw "TEROY: No render() function found in component.";
}
if (typeof component.render() !== "string") {
// @note Since the resulting output can vary based on data, I'm not sure how valuable this line is
// Especially since you're using an empty string fallback below
throw "TEROY: Please return a string from the render() function.";
}
this.html = component.render || "";
this.rendered = false;
this.data = new Proxy(component.data || {}, {
component: this,
set(target, prop, val) {
target[prop] = val;
return this.component.update();
},
get(target, value) {
if (this.component.proxyPaused) {
return target[value];
} else {
if (this.component.rendered) {
// @note nice use of raF here. Great for performance.
// You might consider adding debouncing in case someone updates a few different properties at once
// It will minimize lag from back-to-back renders
window.requestAnimationFrame(() => {
this.component.update();
});
}
return target[value];
}
}
});
}
select(selector) {
return this.element.querySelector(selector);
}
selectAll(selector) {
return this.element.querySelectorAll(selector);
}
parse(string) {
return new DOMParser().parseFromString(string, "text/html");
}
show() {
if (this.rendered) {
return console.warn("TEROY: Component is already showing on page, no need to show it again.");
}
this.DOM = this.parse(this.html());
Array.from(this.DOM.body.childNodes).forEach(child => {
this.element.appendChild(child);
});
this.rendered = true;
}
update() {
this.proxyPaused = true;
this.DOM = this.parse(this.html.apply(this));
const OLDDOMCHILDREN = Array.from(this.element.childNodes);
const NEWDOMCHILDREN = Array.from(this.DOM.querySelector("body").childNodes);
const maxLength = Math.max(OLDDOMCHILDREN.length, NEWDOMCHILDREN.length);
for (let i = 0; i < maxLength; i++) {
if (!OLDDOMCHILDREN[i]) {
this.element.appendChild(NEWDOMCHILDREN[i]);
} else if (!NEWDOMCHILDREN[i]) {
this.element.removeChild(OLDDOMCHILDREN[i]);
} else if (
NEWDOMCHILDREN[i].outerHTML !== OLDDOMCHILDREN[i].outerHTML ||
NEWDOMCHILDREN[i].wholeText !== OLDDOMCHILDREN[i].wholeText
) {
this.element.replaceChild(NEWDOMCHILDREN[i], OLDDOMCHILDREN[i]);
}
}
delete this.proxyPaused;
}
}
if (typeof define === "function" && define.amd) {
define(function() {
return Teroy;
});
} else if (typeof module !== "undefined" && module.exports) {
module.exports = Teroy;
} else {
this.Teroy = Teroy;
}
}.call(this));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment