forked from yysun/apprun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcomponent.ts
150 lines (132 loc) · 4.58 KB
/
component.ts
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
import app, { App } from './app';
import { Reflect } from './decorator'
export class Component extends App {
element;
private _history = [];
private _history_idx = -1;
private enable_history;
private global_event;
protected rendered;
private renderState(state) {
if (!this.view) return;
const html = this.view(state);
const el = (typeof this.element === 'string') ?
document.getElementById(this.element) : this.element;
if (el && app.render) app.render(el, html);
if (el) el['_component'] = this;
}
public setState(state, options: {render:boolean, history:boolean, callback?}) {
if (state instanceof Promise) {
// Promise will not be saved nor rendered saved
// state will be saved and rendered when promise is resolved
state.then(s => {
this.setState(s, options)
}).catch(err => {
console.error(err);
throw err;
})
} else {
if (state == null) return;
this.state = state;
if (options.render !== false) this.renderState(state);
if (options.history !== false && this.enable_history) {
this._history = [...this._history, state];
this._history_idx = this._history.length - 1;
}
if (typeof options.callback === 'function') options.callback(this.state);
if (this.rendered) (this.rendered(this.state));
}
}
constructor(
protected state?,
protected view?,
protected update?,
protected options?) {
super();
}
public mount(element = null, options?: { render?: boolean, history?, global_event?: boolean}) {
console.assert(!this.element, 'Component already mounted.')
this.options = options = Object.assign(this.options || {}, options);
this.element = element;
this.global_event = options.global_event;
this.enable_history = !!options.history;
if (this.enable_history) {
const prev = () => {
this._history_idx --;
if (this._history_idx >=0) {
this.setState(this._history[this._history_idx], { render: true, history: false });
}
else {
this._history_idx = 0;
}
};
const next = () => {
this._history_idx ++;
if (this._history_idx < this._history.length) {
this.setState(this._history[this._history_idx], { render: true, history: false });
}
else {
this._history_idx = this._history.length - 1;
}
};
this.on(options.history.prev || 'history-prev', prev)
this.on(options.history.next || 'history-next', next)
}
this.add_actions();
if (this.state === undefined) this.state = this['model'] || {};
if (options.render) {
this.setState(this.state, { render: true, history: true });
} else {
this.setState(this.state, { render: false, history: true });
}
return this;
}
is_global_event(name: string): boolean {
return name && (name.startsWith('#') || name.startsWith('/'));
}
add_action(name, action, options: any = {}) {
if (!action || typeof action !== 'function') return;
this.on(name, (...p) => {
const newState = action(this.state, ...p);
this.setState(newState, options)
}, options);
}
add_actions() {
const actions = this.update || {};
Reflect.getMetadataKeys(this).forEach(key => {
if (key.startsWith('apprun-update:')) {
const meta = Reflect.getMetadata(key, this)
actions[meta.name] = meta.action || this[meta.key]
}
})
const all = {};
Object.keys(actions).forEach(name => {
const action = actions[name];
if (typeof action === 'function' || Array.isArray(action)) {
name.split(',').forEach(n => all[n.trim()] = action)
}
})
Object.keys(all).forEach(name => {
const action = all[name];
if (typeof action === 'function') {
this.add_action(name, action);
} else if (Array.isArray(action)) {
this.add_action(name, action[0], action[1]);
}
});
}
start = (element = null): Component => {
return this.mount(element, { render: true });
}
render = () => this.view(this.state);
public run(name: string, ...args) {
return this.global_event || this.is_global_event(name) ?
app.run(name, ...args) :
super.run(name, ...args);
}
public on(name: string, fn: (...args) => void, options?: any) {
return this.global_event || this.is_global_event(name) ?
app.on(name, fn, options) :
super.on(name, fn, options);
}
}