/
application.ts
118 lines (98 loc) · 3.7 KB
/
application.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
import { Controller, ControllerConstructor } from "./controller"
import { Definition } from "./definition"
import { Dispatcher } from "./dispatcher"
import { ErrorHandler } from "./error_handler"
import { Logger } from "./logger"
import { Router } from "./router"
import { Schema, defaultSchema } from "./schema"
import { ActionDescriptorFilter, ActionDescriptorFilters, defaultActionDescriptorFilters } from "./action_descriptor"
export class Application implements ErrorHandler {
readonly element: Element
readonly schema: Schema
readonly dispatcher: Dispatcher
readonly router: Router
readonly actionDescriptorFilters: ActionDescriptorFilters
logger: Logger = console
debug: boolean = false
static start(element?: Element, schema?: Schema): Application {
const application = new Application(element, schema)
application.start()
return application
}
constructor(element: Element = document.documentElement, schema: Schema = defaultSchema) {
this.element = element
this.schema = schema
this.dispatcher = new Dispatcher(this)
this.router = new Router(this)
this.actionDescriptorFilters = { ...defaultActionDescriptorFilters }
}
async start() {
await domReady()
this.logDebugActivity("application", "starting")
this.dispatcher.start()
this.router.start()
this.logDebugActivity("application", "start")
}
stop() {
this.logDebugActivity("application", "stopping")
this.dispatcher.stop()
this.router.stop()
this.logDebugActivity("application", "stop")
}
register(identifier: string, controllerConstructor: ControllerConstructor) {
this.load({ identifier, controllerConstructor })
}
registerActionOption(name: string, filter: ActionDescriptorFilter) {
this.actionDescriptorFilters[name] = filter
}
load(...definitions: Definition[]): void
load(definitions: Definition[]): void
load(head: Definition | Definition[], ...rest: Definition[]) {
const definitions = Array.isArray(head) ? head : [head, ...rest]
definitions.forEach(definition => {
if ((definition.controllerConstructor as any).shouldLoad) {
this.router.loadDefinition(definition)
}
})
}
unload(...identifiers: string[]): void
unload(identifiers: string[]): void
unload(head: string | string[], ...rest: string[]) {
const identifiers = Array.isArray(head) ? head : [head, ...rest]
identifiers.forEach(identifier => this.router.unloadIdentifier(identifier))
}
// Controllers
get controllers(): Controller[] {
return this.router.contexts.map(context => context.controller)
}
getControllerForElementAndIdentifier(element: Element, identifier: string): Controller | null {
const context = this.router.getContextForElementAndIdentifier(element, identifier)
return context ? context.controller : null
}
// Error handling
handleError(error: Error, message: string, detail: object) {
this.logger.error(`%s\n\n%o\n\n%o`, message, error, detail)
window.onerror?.(message, "", 0, 0, error)
}
// Debug logging
logDebugActivity = (identifier: string, functionName: string, detail: object = {}): void => {
if (this.debug) {
this.logFormattedMessage(identifier, functionName, detail)
}
}
private logFormattedMessage(identifier: string, functionName: string, detail: object = {}) {
detail = Object.assign({ application: this }, detail)
this.logger.groupCollapsed(`${identifier} #${functionName}`)
this.logger.log("details:", { ...detail })
this.logger.groupEnd()
}
}
function domReady() {
return new Promise<void>(resolve => {
if (document.readyState == "loading") {
document.addEventListener("DOMContentLoaded", () => resolve())
} else {
resolve()
}
})
}