forked from MathiasWP/TeroyJS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTeroy.js
More file actions
110 lines (94 loc) · 3.83 KB
/
Teroy.js
File metadata and controls
110 lines (94 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* Teroy: The smallest JavaScript state-based component UI renderer - "Keepin' it Vanilla."
* Length: 100 lines.
* Global support: 87.96% (https://caniuse.com/#feat=mdn-javascript_operators_spread_spread_in_object_literals)
* 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 || typeof component.render !== "function") throw "TEROY: No render() function found in component.";
if (typeof component.render() !== "string") throw "TEROY: Please make sure that the return from the render() function is wrapped in template literals (or any other string primitive).";
this.html = component.render;
this.rendered = false;
this.data = new Proxy(component.data || {}, this.handler());
}
handler() {
const component = this;
return {
get: (obj, prop) => {
if (['[object Object]', '[object Array]'].indexOf(Object.prototype.toString.call(obj[prop])) > -1) return new Proxy(obj[prop], component.handler())
if (component.proxyPaused) return obj[prop];
if (component.rendered) window.requestAnimationFrame(() => component.update());
return obj[prop];
},
set: (obj, prop, value) => {
component.update();
obj[prop] = value;
return true
}
};
};
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;
}
diffAttributes(newAttrs, oldAttrs, node) {
if (newAttrs === oldAttrs) return;
const allAttrs = new Set([...newAttrs, ...oldAttrs].map(i => i.name));
for (const attr of allAttrs) {
const o = oldAttrs.getNamedItem(attr);
const n = newAttrs.getNamedItem(attr);
if (!n) node.removeAttribute(attr)
else if (!o) node.setAttribute(attr, n.value);
else if (n.value !== o.value) node.setAttribute(attr, n.value);
}
}
diff(newNode, oldNode, root) {
if (!oldNode) return root.appendChild(n);
if (!newNode) return root.removeChild(o);
if (newNode.isEqualNode(oldNode)) return;
const OLD_CHILDREN = oldNode.childNodes;
const NEW_CHILDREN = newNode.childNodes;
const MAX = Math.max(OLD_CHILDREN.length, NEW_CHILDREN.length);
let cur_idx = -1;
while (++cur_idx < MAX) {
const o = OLD_CHILDREN[cur_idx];
const n = NEW_CHILDREN[cur_idx];
if (o === n || o && n && n.isEqualNode(o)) continue;
if (!o) root.appendChild(n);
else if (!n) root.removeChild(o);
else if (n.nodeType !== o.nodeType || n.nodeName !== o.nodeName || n.nodeValue !== o.nodeValue) root.replaceChild(n, o);
else {
this.diffAttributes(n.attributes, o.attributes, o);
this.diff(n, o, o)
}
}
}
update() {
this.proxyPaused = true;
this.DOM = this.parse(this.html.apply(this));
this.diff(this.DOM.body, this.element, this.element);
delete this.proxyPaused;
}
}
if (typeof define === "function" && define.amd) define(() => Teroy);
else if (typeof module !== "undefined" && module.exports) module.exports = Teroy;
else this.Teroy = Teroy;
}.call(this));