forked from hotwired/stimulus
/
dispatcher.ts
130 lines (111 loc) · 4.06 KB
/
dispatcher.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
import { Application } from "./application"
import { Binding } from "./binding"
import { BindingObserverDelegate } from "./binding_observer"
import { EventListener } from "./event_listener"
export class Dispatcher implements BindingObserverDelegate {
readonly application: Application
private eventListenerMaps: Map<EventTarget, Map<string, EventListener>>
private started: boolean
constructor(application: Application) {
this.application = application
this.eventListenerMaps = new Map()
this.started = false
}
start() {
if (!this.started) {
this.started = true
this.eventListeners.forEach((eventListener) => eventListener.connect())
}
}
stop() {
if (this.started) {
this.started = false
this.eventListeners.forEach((eventListener) => eventListener.disconnect())
}
}
get eventListeners(): EventListener[] {
return Array.from(this.eventListenerMaps.values()).reduce(
(listeners, map) => listeners.concat(Array.from(map.values())),
[] as EventListener[]
)
}
// Binding observer delegate
bindingConnected(binding: Binding) {
for (const eventListener of this.fetchEventListenersForBinding(binding)) {
eventListener.bindingConnected(binding)
}
}
bindingDisconnected(binding: Binding, clearEventListeners = false) {
for (const eventListener of this.fetchEventListenersForBinding(binding)) {
eventListener.bindingDisconnected(binding)
}
if (clearEventListeners) this.clearEventListenersForBinding(binding)
}
// Error handling
handleError(error: Error, message: string, detail: object = {}) {
this.application.handleError(error, `Error ${message}`, detail)
}
private clearEventListenersForBinding(binding: Binding) {
for (const eventListener of this.fetchEventListenersForBinding(binding)) {
if (!eventListener.hasBindings()) {
eventListener.disconnect()
this.removeMappedEventListenerFor(binding)
}
}
}
private removeMappedEventListenerFor(binding: Binding) {
const { eventTargets, eventName, eventOptions } = binding
for (const eventTarget of eventTargets) {
const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget)
const cacheKey = this.cacheKey(eventName, eventOptions)
eventListenerMap.delete(cacheKey)
if (eventListenerMap.size == 0) this.eventListenerMaps.delete(eventTarget)
}
}
private fetchEventListenersForBinding(binding: Binding): EventListener[] {
const { eventTargets, eventName, eventOptions } = binding
return eventTargets.map((eventTarget) => this.fetchEventListener(eventTarget, eventName, eventOptions))
}
private fetchEventListener(
eventTarget: EventTarget,
eventName: string,
eventOptions: AddEventListenerOptions
): EventListener {
const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget)
const cacheKey = this.cacheKey(eventName, eventOptions)
let eventListener = eventListenerMap.get(cacheKey)
if (!eventListener) {
eventListener = this.createEventListener(eventTarget, eventName, eventOptions)
eventListenerMap.set(cacheKey, eventListener)
}
return eventListener
}
private createEventListener(
eventTarget: EventTarget,
eventName: string,
eventOptions: AddEventListenerOptions
): EventListener {
const eventListener = new EventListener(eventTarget, eventName, eventOptions)
if (this.started) {
eventListener.connect()
}
return eventListener
}
private fetchEventListenerMapForEventTarget(eventTarget: EventTarget): Map<string, EventListener> {
let eventListenerMap = this.eventListenerMaps.get(eventTarget)
if (!eventListenerMap) {
eventListenerMap = new Map()
this.eventListenerMaps.set(eventTarget, eventListenerMap)
}
return eventListenerMap
}
private cacheKey(eventName: string, eventOptions: any): string {
const parts = [eventName]
Object.keys(eventOptions)
.sort()
.forEach((key) => {
parts.push(`${eventOptions[key] ? "" : "!"}${key}`)
})
return parts.join(":")
}
}