-
-
Notifications
You must be signed in to change notification settings - Fork 115
/
Copy pathevents.js
212 lines (188 loc) · 6.65 KB
/
events.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
import has from 'lodash.has';
const EventSupport = Blaze._EventSupport = {};
const DOMBackend = Blaze._DOMBackend;
// List of events to always delegate, never capture.
// Since jQuery fakes bubbling for certain events in
// certain browsers (like `submit`), we don't want to
// get in its way.
//
// We could list all known bubbling
// events here to avoid creating speculative capturers
// for them, but it would only be an optimization.
const eventsToDelegate = EventSupport.eventsToDelegate = {
blur: 1,
change: 1,
click: 1,
focus: 1,
focusin: 1,
focusout: 1,
reset: 1,
submit: 1
};
const EVENT_MODE = EventSupport.EVENT_MODE = {
TBD: 0,
BUBBLING: 1,
CAPTURING: 2
};
let NEXT_HANDLERREC_ID = 1;
const HandlerRec = function (elem, type, selector, handler, recipient) {
this.elem = elem;
this.type = type;
this.selector = selector;
this.handler = handler;
this.recipient = recipient;
this.id = (NEXT_HANDLERREC_ID++);
this.mode = EVENT_MODE.TBD;
// It's important that delegatedHandler be a different
// instance for each handlerRecord, because its identity
// is used to remove it.
//
// It's also important that the closure have access to
// `this` when it is not called with it set.
this.delegatedHandler = (function (h) {
return function (evt) {
if ((! h.selector) && evt.currentTarget !== evt.target)
// no selector means only fire on target
return;
return h.handler.apply(h.recipient, arguments);
};
})(this);
// WHY CAPTURE AND DELEGATE: jQuery can't delegate
// non-bubbling events, because
// event capture doesn't work in IE 8. However, there
// are all sorts of new-fangled non-bubbling events
// like "play" and "touchenter". We delegate these
// events using capture in all browsers except IE 8.
// IE 8 doesn't support these events anyway.
const tryCapturing = elem.addEventListener &&
(!has(eventsToDelegate,
DOMBackend.Events.parseEventType(type)));
if (tryCapturing) {
this.capturingHandler = (function (h) {
return function (evt) {
if (h.mode === EVENT_MODE.TBD) {
// must be first time we're called.
if (evt.bubbles) {
// this type of event bubbles, so don't
// get called again.
h.mode = EVENT_MODE.BUBBLING;
DOMBackend.Events.unbindEventCapturer(
h.elem, h.type, h.capturingHandler);
return;
} else {
// this type of event doesn't bubble,
// so unbind the delegation, preventing
// it from ever firing.
h.mode = EVENT_MODE.CAPTURING;
DOMBackend.Events.undelegateEvents(
h.elem, h.type, h.delegatedHandler);
}
}
h.delegatedHandler(evt);
};
})(this);
} else {
this.mode = EVENT_MODE.BUBBLING;
}
};
EventSupport.HandlerRec = HandlerRec;
HandlerRec.prototype.bind = function () {
// `this.mode` may be EVENT_MODE_TBD, in which case we bind both. in
// this case, 'capturingHandler' is in charge of detecting the
// correct mode and turning off one or the other handlers.
if (this.mode !== EVENT_MODE.BUBBLING) {
DOMBackend.Events.bindEventCapturer(
this.elem, this.type, this.selector || '*',
this.capturingHandler);
}
if (this.mode !== EVENT_MODE.CAPTURING)
DOMBackend.Events.delegateEvents(
this.elem, this.type,
this.selector || '*', this.delegatedHandler);
};
HandlerRec.prototype.unbind = function () {
if (this.mode !== EVENT_MODE.BUBBLING)
DOMBackend.Events.unbindEventCapturer(this.elem, this.type,
this.capturingHandler);
if (this.mode !== EVENT_MODE.CAPTURING)
DOMBackend.Events.undelegateEvents(this.elem, this.type,
this.delegatedHandler);
};
EventSupport.listen = function (element, events, selector, handler, recipient, getParentRecipient) {
// Prevent this method from being JITed by Safari. Due to a
// presumed JIT bug in Safari -- observed in Version 7.0.6
// (9537.78.2) -- this method may crash the Safari render process if
// it is JITed.
// Repro: https://github.com/dgreensp/public/tree/master/safari-crash
try { element = element; } finally {}
const eventTypes = [];
events.replace(/[^ /]+/g, function (e) {
eventTypes.push(e);
});
const newHandlerRecs = [];
for (let i = 0, N = eventTypes.length; i < N; i++) {
const type = eventTypes[i];
let eventDict = element.$blaze_events;
if (! eventDict)
eventDict = (element.$blaze_events = {});
let info = eventDict[type];
if (! info) {
info = eventDict[type] = {};
info.handlers = [];
}
const handlerList = info.handlers;
const handlerRec = new HandlerRec(
element, type, selector, handler, recipient);
newHandlerRecs.push(handlerRec);
handlerRec.bind();
handlerList.push(handlerRec);
// Move handlers of enclosing ranges to end, by unbinding and rebinding
// them. In jQuery (or other DOMBackend) this causes them to fire
// later when the backend dispatches event handlers.
if (getParentRecipient) {
for (let r = getParentRecipient(recipient); r;
r = getParentRecipient(r)) {
// r is an enclosing range (recipient)
for (let j = 0, Nj = handlerList.length;
j < Nj; j++) {
const h = handlerList[j];
if (h.recipient === r) {
h.unbind();
h.bind();
handlerList.splice(j, 1); // remove handlerList[j]
handlerList.push(h);
j--; // account for removed handler
Nj--; // don't visit appended handlers
}
}
}
}
}
return {
// closes over just `element` and `newHandlerRecs`
stop: function () {
const eventDict = element.$blaze_events;
if (! eventDict)
return;
// newHandlerRecs has only one item unless you specify multiple
// event types. If this code is slow, it's because we have to
// iterate over handlerList here. Clearing a whole handlerList
// via stop() methods is O(N^2) in the number of handlers on
// an element.
for (let i = 0; i < newHandlerRecs.length; i++) {
const handlerToRemove = newHandlerRecs[i];
const info = eventDict[handlerToRemove.type];
if (! info)
continue;
const handlerList = info.handlers;
for (let j = handlerList.length - 1; j >= 0; j--) {
if (handlerList[j] === handlerToRemove) {
handlerToRemove.unbind();
handlerList.splice(j, 1); // remove handlerList[j]
}
}
}
newHandlerRecs.length = 0;
}
};
};