Created
May 1, 2020 01:12
-
-
Save cferdinandi/30de88b0fe2d24bd7b7c3bac3126e99c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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