/
mod.js
145 lines (126 loc) · 4.4 KB
/
mod.js
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// import udomdiff from "https://unpkg.com/udomdiff/esm/index.js";
/**
* # OnMount store and function
* 1. When the component is executed we store the mount function inside the variable mountFunction;
* 2. When the component start its webcomponent we get the function inside the variable and attach to the class component;
* 3. Reset the mountFunction variable back to null;
* 4. Repeat for each time a component is mounted.
*/
let mountFunction = null;
export function onMount(fn) {
mountFunction = fn;
}
/**
* # Template literal html function
* 1. On each value, verify if it is a function and execute trying to get its value;
* 2. Return a string fulfilled with all the data and all the data necessary to run it again.
*/
export function html(strings, ...values) {
const parsedValues = values.map((value) => {
if (value instanceof Function) return value();
return value;
});
const template = String.raw({ raw: strings }, ...parsedValues);
return { template, templateLiteral: { strings, values } };
}
export default function Component(
componentName,
componentFunction,
) {
// Create a webcomponent
customElements.define(
componentName,
class extends HTMLElement {
/**
* # Store for the component
* 1. Set: store the the state key and value, render the component again;
* 2. Get: find the key already stored, if not found, returns undefined.
*/
state = {
set: (key, value) => {
this.state[key] = value;
this.updateStateIdAttribute();
},
get(key) {
return () => this[key];
},
};
// mountFunction stored locally
onMount = null;
// String from template literal
template = "";
// Values to generate the string from html
templateLiteral = {};
// Function to update the HTML
refresh = () => {
const { template } = html(
this.templateLiteral.strings,
...this.templateLiteral.values,
);
// const parsedTemplate = stringToHtml(template);
// const node = (node, info) => {
// console.log({ node, info });
// console.log({
// templateChildNodes: parsedTemplate.childNodes,
// shadowRootChildNodes: this.shadowRoot.childNodes,
// });
// return node;
// }
// const futureNodes = udomdiff(
// this.shadowRoot, // parent node where changes will happen
// this.shadowRoot.childNodes, // array of current nodes
// parsedTemplate.childNodes, // array of future nodes
// node, // callback to retrieve the node
// );
this.shadowRoot.innerHTML = template;
this.onMount?.(this);
};
constructor() {
super();
/**
* Component function
*/
const { template, templateLiteral } = componentFunction(this);
this.template = template;
this.templateLiteral = templateLiteral;
this.onMount = mountFunction;
// Reset mountFunction to null
mountFunction = null;
}
// Update state attribute to update the component
updateStateIdAttribute() {
const id = Number(this.attributes.state?.value || 0);
this.setAttribute("state", id + 1);
}
// Update component on changing attribute state on webcomponent
attributeChangedCallback() {
this.refresh();
}
// Only call the function attributeChangeCallback when state attribute is changed
static get observedAttributes() {
return ["state"];
}
// When the component start
connectedCallback() {
// If it is not server side rendered (if does not already have a shadowRoot)
if (!this.shadowRoot) {
const parsedTemplate = stringToHtml(this.template);
this.attachShadow({ mode: "open" }).replaceChildren(
...parsedTemplate.childNodes,
);
}
/**
* If there is an onMount function, execute
* Usually this is the place where the document.addEventListeners are storaged
* For the future: try to avoid this pattern
*/
this.onMount?.(this);
}
},
);
}
const domParser = new DOMParser();
function stringToHtml(string) {
const document = domParser.parseFromString(string, "text/html");
return document.body;
}