Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/events/eventtarget.js
2868 views
1
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview A disposable implementation of a custom
17
* listenable/event target. See also: documentation for
18
* {@code goog.events.Listenable}.
19
*
20
* @author [email protected] (Erik Arvidsson) [Original implementation]
21
* @see ../demos/eventtarget.html
22
* @see goog.events.Listenable
23
*/
24
25
goog.provide('goog.events.EventTarget');
26
27
goog.require('goog.Disposable');
28
goog.require('goog.asserts');
29
goog.require('goog.events');
30
goog.require('goog.events.Event');
31
goog.require('goog.events.Listenable');
32
goog.require('goog.events.ListenerMap');
33
goog.require('goog.object');
34
35
36
37
/**
38
* An implementation of {@code goog.events.Listenable} with full W3C
39
* EventTarget-like support (capture/bubble mechanism, stopping event
40
* propagation, preventing default actions).
41
*
42
* You may subclass this class to turn your class into a Listenable.
43
*
44
* Unless propagation is stopped, an event dispatched by an
45
* EventTarget will bubble to the parent returned by
46
* {@code getParentEventTarget}. To set the parent, call
47
* {@code setParentEventTarget}. Subclasses that don't support
48
* changing the parent can override the setter to throw an error.
49
*
50
* Example usage:
51
* <pre>
52
* var source = new goog.events.EventTarget();
53
* function handleEvent(e) {
54
* alert('Type: ' + e.type + '; Target: ' + e.target);
55
* }
56
* source.listen('foo', handleEvent);
57
* // Or: goog.events.listen(source, 'foo', handleEvent);
58
* ...
59
* source.dispatchEvent('foo'); // will call handleEvent
60
* ...
61
* source.unlisten('foo', handleEvent);
62
* // Or: goog.events.unlisten(source, 'foo', handleEvent);
63
* </pre>
64
*
65
* @constructor
66
* @extends {goog.Disposable}
67
* @implements {goog.events.Listenable}
68
*/
69
goog.events.EventTarget = function() {
70
goog.Disposable.call(this);
71
72
/**
73
* Maps of event type to an array of listeners.
74
* @private {!goog.events.ListenerMap}
75
*/
76
this.eventTargetListeners_ = new goog.events.ListenerMap(this);
77
78
/**
79
* The object to use for event.target. Useful when mixing in an
80
* EventTarget to another object.
81
* @private {!Object}
82
*/
83
this.actualEventTarget_ = this;
84
85
/**
86
* Parent event target, used during event bubbling.
87
*
88
* TODO(chrishenry): Change this to goog.events.Listenable. This
89
* currently breaks people who expect getParentEventTarget to return
90
* goog.events.EventTarget.
91
*
92
* @private {goog.events.EventTarget}
93
*/
94
this.parentEventTarget_ = null;
95
};
96
goog.inherits(goog.events.EventTarget, goog.Disposable);
97
goog.events.Listenable.addImplementation(goog.events.EventTarget);
98
99
100
/**
101
* An artificial cap on the number of ancestors you can have. This is mainly
102
* for loop detection.
103
* @const {number}
104
* @private
105
*/
106
goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
107
108
109
/**
110
* Returns the parent of this event target to use for bubbling.
111
*
112
* @return {goog.events.EventTarget} The parent EventTarget or null if
113
* there is no parent.
114
* @override
115
*/
116
goog.events.EventTarget.prototype.getParentEventTarget = function() {
117
return this.parentEventTarget_;
118
};
119
120
121
/**
122
* Sets the parent of this event target to use for capture/bubble
123
* mechanism.
124
* @param {goog.events.EventTarget} parent Parent listenable (null if none).
125
*/
126
goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
127
this.parentEventTarget_ = parent;
128
};
129
130
131
/**
132
* Adds an event listener to the event target. The same handler can only be
133
* added once per the type. Even if you add the same handler multiple times
134
* using the same type then it will only be called once when the event is
135
* dispatched.
136
*
137
* @param {string|!goog.events.EventId} type The type of the event to listen for
138
* @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
139
* to handle the event. The handler can also be an object that implements
140
* the handleEvent method which takes the event object as argument.
141
* @param {boolean=} opt_capture In DOM-compliant browsers, this determines
142
* whether the listener is fired during the capture or bubble phase
143
* of the event.
144
* @param {Object=} opt_handlerScope Object in whose scope to call
145
* the listener.
146
* @deprecated Use {@code #listen} instead, when possible. Otherwise, use
147
* {@code goog.events.listen} if you are passing Object
148
* (instead of Function) as handler.
149
*/
150
goog.events.EventTarget.prototype.addEventListener = function(
151
type, handler, opt_capture, opt_handlerScope) {
152
goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
153
};
154
155
156
/**
157
* Removes an event listener from the event target. The handler must be the
158
* same object as the one added. If the handler has not been added then
159
* nothing is done.
160
*
161
* @param {string} type The type of the event to listen for.
162
* @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
163
* to handle the event. The handler can also be an object that implements
164
* the handleEvent method which takes the event object as argument.
165
* @param {boolean=} opt_capture In DOM-compliant browsers, this determines
166
* whether the listener is fired during the capture or bubble phase
167
* of the event.
168
* @param {Object=} opt_handlerScope Object in whose scope to call
169
* the listener.
170
* @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
171
* {@code goog.events.unlisten} if you are passing Object
172
* (instead of Function) as handler.
173
*/
174
goog.events.EventTarget.prototype.removeEventListener = function(
175
type, handler, opt_capture, opt_handlerScope) {
176
goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
177
};
178
179
180
/** @override */
181
goog.events.EventTarget.prototype.dispatchEvent = function(e) {
182
this.assertInitialized_();
183
184
var ancestorsTree, ancestor = this.getParentEventTarget();
185
if (ancestor) {
186
ancestorsTree = [];
187
var ancestorCount = 1;
188
for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
189
ancestorsTree.push(ancestor);
190
goog.asserts.assert(
191
(++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
192
'infinite loop');
193
}
194
}
195
196
return goog.events.EventTarget.dispatchEventInternal_(
197
this.actualEventTarget_, e, ancestorsTree);
198
};
199
200
201
/**
202
* Removes listeners from this object. Classes that extend EventTarget may
203
* need to override this method in order to remove references to DOM Elements
204
* and additional listeners.
205
* @override
206
*/
207
goog.events.EventTarget.prototype.disposeInternal = function() {
208
goog.events.EventTarget.superClass_.disposeInternal.call(this);
209
210
this.removeAllListeners();
211
this.parentEventTarget_ = null;
212
};
213
214
215
/** @override */
216
goog.events.EventTarget.prototype.listen = function(
217
type, listener, opt_useCapture, opt_listenerScope) {
218
this.assertInitialized_();
219
return this.eventTargetListeners_.add(
220
String(type), listener, false /* callOnce */, opt_useCapture,
221
opt_listenerScope);
222
};
223
224
225
/** @override */
226
goog.events.EventTarget.prototype.listenOnce = function(
227
type, listener, opt_useCapture, opt_listenerScope) {
228
return this.eventTargetListeners_.add(
229
String(type), listener, true /* callOnce */, opt_useCapture,
230
opt_listenerScope);
231
};
232
233
234
/** @override */
235
goog.events.EventTarget.prototype.unlisten = function(
236
type, listener, opt_useCapture, opt_listenerScope) {
237
return this.eventTargetListeners_.remove(
238
String(type), listener, opt_useCapture, opt_listenerScope);
239
};
240
241
242
/** @override */
243
goog.events.EventTarget.prototype.unlistenByKey = function(key) {
244
return this.eventTargetListeners_.removeByKey(key);
245
};
246
247
248
/** @override */
249
goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
250
// TODO(chrishenry): Previously, removeAllListeners can be called on
251
// uninitialized EventTarget, so we preserve that behavior. We
252
// should remove this when usages that rely on that fact are purged.
253
if (!this.eventTargetListeners_) {
254
return 0;
255
}
256
return this.eventTargetListeners_.removeAll(opt_type);
257
};
258
259
260
/** @override */
261
goog.events.EventTarget.prototype.fireListeners = function(
262
type, capture, eventObject) {
263
// TODO(chrishenry): Original code avoids array creation when there
264
// is no listener, so we do the same. If this optimization turns
265
// out to be not required, we can replace this with
266
// getListeners(type, capture) instead, which is simpler.
267
var listenerArray = this.eventTargetListeners_.listeners[String(type)];
268
if (!listenerArray) {
269
return true;
270
}
271
listenerArray = listenerArray.concat();
272
273
var rv = true;
274
for (var i = 0; i < listenerArray.length; ++i) {
275
var listener = listenerArray[i];
276
// We might not have a listener if the listener was removed.
277
if (listener && !listener.removed && listener.capture == capture) {
278
var listenerFn = listener.listener;
279
var listenerHandler = listener.handler || listener.src;
280
281
if (listener.callOnce) {
282
this.unlistenByKey(listener);
283
}
284
rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
285
}
286
}
287
288
return rv && eventObject.returnValue_ != false;
289
};
290
291
292
/** @override */
293
goog.events.EventTarget.prototype.getListeners = function(type, capture) {
294
return this.eventTargetListeners_.getListeners(String(type), capture);
295
};
296
297
298
/** @override */
299
goog.events.EventTarget.prototype.getListener = function(
300
type, listener, capture, opt_listenerScope) {
301
return this.eventTargetListeners_.getListener(
302
String(type), listener, capture, opt_listenerScope);
303
};
304
305
306
/** @override */
307
goog.events.EventTarget.prototype.hasListener = function(
308
opt_type, opt_capture) {
309
var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
310
return this.eventTargetListeners_.hasListener(id, opt_capture);
311
};
312
313
314
/**
315
* Sets the target to be used for {@code event.target} when firing
316
* event. Mainly used for testing. For example, see
317
* {@code goog.testing.events.mixinListenable}.
318
* @param {!Object} target The target.
319
*/
320
goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
321
this.actualEventTarget_ = target;
322
};
323
324
325
/**
326
* Asserts that the event target instance is initialized properly.
327
* @private
328
*/
329
goog.events.EventTarget.prototype.assertInitialized_ = function() {
330
goog.asserts.assert(
331
this.eventTargetListeners_,
332
'Event target is not initialized. Did you call the superclass ' +
333
'(goog.events.EventTarget) constructor?');
334
};
335
336
337
/**
338
* Dispatches the given event on the ancestorsTree.
339
*
340
* @param {!Object} target The target to dispatch on.
341
* @param {goog.events.Event|Object|string} e The event object.
342
* @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
343
* tree of the target, in reverse order from the closest ancestor
344
* to the root event target. May be null if the target has no ancestor.
345
* @return {boolean} If anyone called preventDefault on the event object (or
346
* if any of the listeners returns false) this will also return false.
347
* @private
348
*/
349
goog.events.EventTarget.dispatchEventInternal_ = function(
350
target, e, opt_ancestorsTree) {
351
var type = e.type || /** @type {string} */ (e);
352
353
// If accepting a string or object, create a custom event object so that
354
// preventDefault and stopPropagation work with the event.
355
if (goog.isString(e)) {
356
e = new goog.events.Event(e, target);
357
} else if (!(e instanceof goog.events.Event)) {
358
var oldEvent = e;
359
e = new goog.events.Event(type, target);
360
goog.object.extend(e, oldEvent);
361
} else {
362
e.target = e.target || target;
363
}
364
365
var rv = true, currentTarget;
366
367
// Executes all capture listeners on the ancestors, if any.
368
if (opt_ancestorsTree) {
369
for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
370
i--) {
371
currentTarget = e.currentTarget = opt_ancestorsTree[i];
372
rv = currentTarget.fireListeners(type, true, e) && rv;
373
}
374
}
375
376
// Executes capture and bubble listeners on the target.
377
if (!e.propagationStopped_) {
378
currentTarget = /** @type {?} */ (e.currentTarget = target);
379
rv = currentTarget.fireListeners(type, true, e) && rv;
380
if (!e.propagationStopped_) {
381
rv = currentTarget.fireListeners(type, false, e) && rv;
382
}
383
}
384
385
// Executes all bubble listeners on the ancestors, if any.
386
if (opt_ancestorsTree) {
387
for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
388
currentTarget = e.currentTarget = opt_ancestorsTree[i];
389
rv = currentTarget.fireListeners(type, false, e) && rv;
390
}
391
}
392
393
return rv;
394
};
395
396