-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
393 lines (360 loc) · 11.8 KB
/
index.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
import { ToDOM } from "./internals/ToDOM.js";
export function Nano() {
var pathAndComponents = [];
var ChildComponents = [];
var dom;
var docToRender;
var routes = [];
var listeners = [];
var routerInUse = false;
var updatedElements = [];
var toRender;
//emitter
//event emitter that triggers during state change and forces "virtual dom" creation and comparison
function emitter(eventType, componentRender, componentName) {
if (!eventType) {
console.error("NO EVENT TYPE PROVIDED");
return;
}
//if state changed time to re-render a virtual dom and compare to it's previous self
if (eventType === "change") {
console.log("change detected");
// console.log(`name is ${componentName}`);
dom.updateDOM(componentRender, componentName);
}
if (eventType === "route") {
Router();
}
}
//dom creator and updater
function virtualDOM() {
var currentDOM;
function createDOM(element) {
var el = document.createElement("div");
el.appendChild(element);
//runs during first render
currentDOM = currentDOM === undefined && el;
}
function saveNode(componentFragment, componentName) {
//saves all rendered component's children that are themselves components
let childName;
let childrenArr = Array.from(componentFragment.children);
let componentChildren = childrenArr.filter((child) => {
if (child.hasAttribute("component")) {
childName = child.getAttribute("component");
}
return child.hasAttribute("component");
});
ChildComponents.push({
name: componentName,
children: { name: childName, component: componentChildren },
});
}
function updateDOM(componentRender, componentName) {
console.log("updating");
// //new render
console.log(componentRender.childNodes, componentName);
console.warn(componentRender); //and its name
let updatedComponent = componentRender.childNodes[0];
//find children in the array with same name as componentName
let foundChild = ChildComponents.find((children) => {
return children.name === componentName;
});
//runs and modifies the updated content IF it had children on first render
if (foundChild) {
//find the empty child
let emptyChild = Array.from(updatedComponent.children).find((child) => {
return child.innerHTML === "";
});
if (emptyChild) {
//re-insert children to the updated dom
foundChild.children.component.forEach((child) => {
updatedComponent.insertBefore(child, emptyChild);
});
updatedComponent.removeChild(emptyChild);
}
}
// document.querySelector(`[component=${componentName}]`).innerHTML =
//ALSO RUNS ON ELEMENTS THAT ARENT IN THE INNERHTML ANYMORE;
if (!routerInUse) {
document.querySelector(`[component=${componentName}]`).innerHTML =
updatedComponent.innerHTML;
return;
}
//save this value inside the array for memoing
// console.log(ToDOM(updatedComponent.innerHTML));
//push the update
let foundUpdated = updatedElements.find((element) => {
return element.name === componentName;
});
let updated = {
updatedElement: updatedComponent,
name: componentName,
};
if (!foundUpdated) {
updatedElements.push(updated);
} else {
//replace
updatedElements[updatedElements.indexOf(foundUpdated)] = updated;
}
console.log(updatedElements);
//append the nodes this returns to "toRender.savedComponents"
function toNode(node) {
if (!node) return;
console.warn(node);
var fragment = document.createDocumentFragment();
fragment.appendChild(node);
return fragment;
}
Array.from(toRender.savedComponents.children).forEach((node) => {
let attr = node.getAttribute("component");
if (attr) {
let found = updatedElements.find((upelem) => {
return attr === upelem.name;
});
if (found) {
toRender.savedComponents.replaceChild(found.updatedElement, node);
}
}
let componentTorender = toRender.savedComponents;
document.getElementById(docToRender).innerHTML = "";
document
.getElementById(docToRender)
.appendChild(componentTorender.cloneNode(true));
BindLinksAndClicks();
// console.warn(toRender.savedComponents);
});
}
return {
createDOM,
updateDOM,
saveNode,
};
}
//state
function createState(val = undefined, renderFunc, componentName) {
//this is the state
var stateStorage = {
state: val,
};
Object.defineProperty(stateStorage, "getState", {
get() {
return stateStorage.state;
},
enumerable: false,
configurable: false,
});
Object.defineProperty(stateStorage, "setState", {
set(newVal) {
this.state = newVal;
},
enumerable: false,
configurable: false,
});
function changer(newVal) {
let prevValue = getter();
Promise.resolve()
.then(() => {
stateStorage.setState = newVal;
})
.then(() => {
if (newVal !== prevValue) {
let updated = ToDOM(renderFunc(newVal));
emitter("change", updated, componentName);
//making sure stored child is updated and stored again;
let matched = ChildComponents.find((child) => {
return child.children.name === componentName;
});
if (matched) {
matched.children.component[0] = updated.childNodes[0];
}
}
});
}
function getter() {
return stateStorage.getState;
}
//must return the closured state and the updater
return [getter, changer];
}
function LinkTo(path) {
Promise.resolve().then(() => {
history.pushState({}, "", `${path}`);
var event = new Event("popstate");
window.dispatchEvent(event);
});
return;
}
function Route(path, ...componenets) {
if (!routerInUse) routerInUse = true;
if (!dom) dom = virtualDOM();
// componenets.forEach((component) => {
// component.parent = false;
// //parent prop changes after first render async
// setTimeout(() => {
// component.parent = true;
// }, 0);
// });
var comps = componenets[0];
// create DOMified components and add it to the routes[] corresponding path components array;
routes.push({ path, components: comps });
// console.warn(routes[0].components);
Assemble(docToRender, comps, path);
return comps;
}
function Router() {
//runs on every route change
//render conditionally
var loc = location.pathname;
if (routerInUse) {
//find path fragment;
toRender = pathAndComponents.find((pnc) => {
return pnc.path === loc;
});
//get the always updated component children to render
let componentTorender = toRender.savedComponents;
document.getElementById(docToRender).innerHTML = "";
document
.getElementById(docToRender)
.appendChild(componentTorender.cloneNode(true));
BindLinksAndClicks();
}
//otherwise just render 404;
}
function Assemble(rootID, args, path) {
var fragment = new DocumentFragment();
//takes in the array of elements from top to bottom
//compiles everything to a DOM tree by calling the helper for each of the fragments
args.forEach((component, idx) => {
fragment.appendChild(ToDOM(component));
//save its children nodes
dom.saveNode(fragment.childNodes[idx], component.name);
});
// dom.createDOM(fragment.cloneNode(true));
//save the path-component fragment if routers being used;
if (routerInUse) {
pathAndComponents.push({ path, savedComponents: fragment });
} else {
document.getElementById(rootID).innerHTML = "";
document.getElementById(rootID).appendChild(fragment);
BindLinksAndClicks();
}
}
function Build(rootID, ...args) {
//flat components if theyre passed from inside Route function
args = [].concat.apply([], args);
docToRender = rootID;
if (!dom) dom = virtualDOM();
//for routing
window.addEventListener("popstate", function (event) {
emitter("route");
});
args.forEach((component) => {
component.parent = false;
//parent prop changes after first render async
setTimeout(() => {
component.parent = true;
}, 0);
});
//runs on first run
//2 different types of first render if router is being used or not
if (routerInUse) {
Router();
return;
}
//if router isnt being used;
Assemble(rootID, args);
}
function BindLinksAndClicks() {
//find and add linkto listeners to elements
var linkElems = document.querySelectorAll("[linkto]");
if (linkElems) {
//find their linkto listeners;
linkElems.forEach((linkItem) => {
let where = linkItem.getAttribute("linkto");
if (where)
linkItem.addEventListener("click", (e) => {
// e.preventDefault();
LinkTo(where);
});
});
}
//bind the clicks
//get the elements that will have a click listeer
var clickElements = document.querySelectorAll("[onTouch]");
//create and add a function as a listener/remove as attribute
if (clickElements) {
clickElements.forEach((clickItem) => {
//index in the listeners array;
var fnIdx = clickItem.getAttribute("onTouch");
clickItem.removeAttribute("onTouch");
clickItem.addEventListener("click", listeners[fnIdx].fn);
});
}
}
//helper function for stringifying listener functions
function BindListener(func) {
// get the indexes of var/let/const/function declarations
//properly space function declarations in the string;
// function SpaceFnString(str, ...searchStrs) {
// var indices = [];
// var copyStr = str;
// var len = str.length;
// if (len == 0) return [];
// //find each given declaration index
// searchStrs.forEach((searchTerm) => {
// let startIndex = 0,
// index;
// let len = searchTerm.length;
// while ((index = str.indexOf(searchTerm, startIndex)) > -1) {
// indices.push({ index, length: len });
// startIndex = index + len;
// }
// });
// //sort indices ascending;
// indices.sort(function (a, b) {
// return a.index - b.index;
// });
// if (indices.length) {
// let incr = 0;
// //add spaces
// indices.forEach((char) => {
// copyStr =
// copyStr.slice(0, char.index + char.length + incr) +
// " " +
// copyStr.slice(char.index + char.length + incr);
// incr = incr + 1;
// });
// }
// return copyStr;
// }
// var funcstring = func.toString();
// //remove all sorts of whitespaces in the stringified function
// var fn = funcstring
// .slice(funcstring.indexOf("{") + 1, funcstring.lastIndexOf("}"))
// .replace(/\s+/g, "")
// .replace(/^\s/, "")
// .replace(/\s$/, "");
// //add space after function declarations;
// fn = SpaceFnString(fn, "var", "let", "const", "function");
//woops instead of creating new function from a string
//its better to just return internal listeners array position for the specific element
var number = listeners.length;
listeners.push({
id: number,
fn: func,
});
return number;
}
return {
Build,
createState,
BindListener,
Route,
};
}
//usage
// var { Build, createState } = Nano();
// Build("root", Header, MainContent);
//gotta export build and statecreator
export default Nano;