-
Notifications
You must be signed in to change notification settings - Fork 5
/
nanny.js
108 lines (97 loc) · 4.7 KB
/
nanny.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
import {html, svg, render} from 'uhtml';
export default function Nanny(State, Path = window.location.pathname,Routes = State.Routes || [], Effects = State.Effects || [],Calcs = State.Calculations || []){
// Retrieve state from local storage.
if (State.LocalStorageKey && localStorage.getItem(State.LocalStorageKey)) {
State = {...State,...JSON.parse(localStorage.getItem(State.LocalStorageKey))}
}
// Built-In State Methods
State.Evaluate = prop => prop === undefined ? {...State} : {...State}[prop];
State.JSON = () => JSON.stringify(State);
State.HTML = html;
State.SVG = svg;
State.Link = path => event => {
event.preventDefault();
Path = path || event.target.attributes.href.value;
window.history.pushState({ Path }, Path, `${Path}`);
Render();
};
window.addEventListener("popstate", event => {
Path = window.location.pathname;
Render();
});
State.Route = route => Routes.append(route);
State.Every = (moment,...transformers) => setInterval(_ => State.Update(...transformers),moment);
State.Delay = (moment,...transformers) => setTimeout(_ => State.Update(...transformers),moment);
State.Increment = (prop,n=1) => State.Update({[prop]: State[prop] + n});
State.Decrement = (prop,n=1) => State.Update({[prop]: State[prop] - n});
State.Toggle = prop => State.Update({[prop]: !State[prop]});
State.Append = (list,value) => State.Update({[list]: [ ...State[list], value ]});
State.Insert = (list,index,value) => State.Update({[list]: [...State[list].slice(0,index), value,...State[list].slice(index, State[list].length)]});
State.Replace = (list,index,value) => State.Update({[list]: [...State[list].slice(0,index),value,...State[list].slice(index+1, State[list].length)]});
State.Remove = (list,index) => State.Update({[list]: [...State[list].slice(0,index),...State[list].slice(index+1, State[list].length)]});
State.Effect = (effect,list) => {
if(!Effects.some(e => e[0].toString() === effect.toString() && e[1] === list)) Effects.push([effect,list]);
};
State.Calculate = calc => {
const list = calc.toString().match(/state\.[a-z_$]+/g).map(x => x.slice(6));
if(!Calcs.some(c => c[0].toString() === calc.toString() && c[1].toString() === list.toString())) Calcs.push([calc,list]);
};
State.Update = (...transformers) => {
setState(...transformers);
if (State.LocalStorageKey){
const localState = {...State};
if(State.LocalStorageBlackList){
State.LocalStorageBlackList.split(",").forEach(key => delete localState[key]);
}
localStorage.setItem(State.LocalStorageKey,JSON.stringify(localState));
}
if (State.Debug) console.log(State.JSON());
Render();
};
// Run any setup code once and then render the initial state
if (State.Initiate) setState(State.Initiate)
State.View(State);
setState(State);
if (State.Debug) console.log(State.JSON());
Render();
// Helper functions
function update(oldObj,newObj){
Object.entries(newObj).forEach(([prop,value]) => oldObj[prop] = value)
}
function setState(...transformers){
State = transformers.reduce((state,transformer) => {
const {Update,HTML,SVG,Evaluate,JSON,Link,Every,Delay,Content,Effect,Calculate,Increment,Decrement,Toggle,Append,Insert,Replace,Remove,...newState} = typeof(transformer) === "function" ? transformer(state) : transformer;
update(state,newState);
Effects.filter(effect => !effect[1] || effect[1].split(",").some(prop => newState.hasOwnProperty(prop))).forEach(effect => effect[0](state));
Calcs.filter(calc => !calc[1] || calc[1].some(prop => newState.hasOwnProperty(prop))).forEach(calc => {
update(state,calc[0](state));
Effects.filter(effect => effect[1] && effect[1].split(",").some(prop => calc[0](state).hasOwnProperty(prop))).forEach(effect => effect[0](state));
});
return state
},State);
}
const findRoute = path => (path === '/' ? ['/'] : path.split('/').filter(char => char !== ''))
.reduce(
(obj, path) => {
const param = obj.routes.find(r => r.path[0] === ':');
return obj.routes.find(r => r.path === path)
? {...obj.routes.find(r => r.path === path),params: obj.params}
: { ...param, params: {...obj.params,[param.path.slice(1)]: path} }
},{ routes: Routes }
);
function Render() {
if (Routes.length){
const route = findRoute(Path);
document.title = route.title || document.title
if (route.update) {
setState(route.params ? route.update(route.params) : route.update);
}
if (route.view) {
State.Content = route.view(State);
}
}
render(State.Element || document.body, State.View(State));
}
return State.Update
}
export { Nanny, html, svg }