1
/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3
* full text of the license. */
7
* @requires OpenLayers/Util.js
11
* Namespace: OpenLayers.Event
12
* Utility functions for event handling.
18
* {Object} A hashtable cache of the event observers. Keyed by
19
* element._eventCacheID
24
* Constant: KEY_BACKSPACE
36
* Constant: KEY_RETURN
72
* Constant: KEY_DELETE
80
* Cross browser event element detection.
86
* {DOMElement} The element that caused the event
88
element: function(event) {
89
return event.target || event.srcElement;
94
* Determine whether event was caused by a left click.
102
isLeftClick: function(event) {
103
return (((event.which) && (event.which == 1)) ||
104
((event.button) && (event.button == 1)));
108
* Method: isRightClick
109
* Determine whether event was caused by a right mouse click.
117
isRightClick: function(event) {
118
return (((event.which) && (event.which == 3)) ||
119
((event.button) && (event.button == 2)));
124
* Stops an event from propagating.
128
* allowDefault - {Boolean} If true, we stop the event chain but
129
* still allow the default browser
130
* behaviour (text selection, radio-button
134
stop: function(event, allowDefault) {
137
if (event.preventDefault) {
138
event.preventDefault();
140
event.returnValue = false;
144
if (event.stopPropagation) {
145
event.stopPropagation();
147
event.cancelBubble = true;
152
* Method: findElement
159
* {DOMElement} The first node with the given tagName, starting from the
160
* node the event was triggered on and traversing the DOM upwards
162
findElement: function(event, tagName) {
163
var element = OpenLayers.Event.element(event);
164
while (element.parentNode && (!element.tagName ||
165
(element.tagName.toUpperCase() != tagName.toUpperCase()))){
166
element = element.parentNode;
175
* elementParam - {DOMElement || String}
177
* observer - {function}
178
* useCapture - {Boolean}
180
observe: function(elementParam, name, observer, useCapture) {
181
var element = OpenLayers.Util.getElement(elementParam);
182
useCapture = useCapture || false;
184
if (name == 'keypress' &&
185
(navigator.appVersion.match(/Konqueror|Safari|KHTML/)
186
|| element.attachEvent)) {
190
//if observers cache has not yet been created, create it
191
if (!this.observers) {
195
//if not already assigned, make a new unique cache ID
196
if (!element._eventCacheID) {
197
var idPrefix = "eventCacheID_";
199
idPrefix = element.id + "_" + idPrefix;
201
element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
204
var cacheID = element._eventCacheID;
206
//if there is not yet a hash entry for this element, add one
207
if (!this.observers[cacheID]) {
208
this.observers[cacheID] = [];
211
//add a new observer to this element's list
212
this.observers[cacheID].push({
215
'observer': observer,
216
'useCapture': useCapture
219
//add the actual browser event listener
220
if (element.addEventListener) {
221
element.addEventListener(name, observer, useCapture);
222
} else if (element.attachEvent) {
223
element.attachEvent('on' + name, observer);
228
* Method: stopObservingElement
229
* Given the id of an element to stop observing, cycle through the
230
* element's cached observers, calling stopObserving on each one,
231
* skipping those entries which can no longer be removed.
234
* elementParam - {DOMElement || String}
236
stopObservingElement: function(elementParam) {
237
var element = OpenLayers.Util.getElement(elementParam);
238
var cacheID = element._eventCacheID;
240
this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
244
* Method: _removeElementObservers
247
* elementObservers - {Array(Object)} Array of (element, name,
248
* observer, usecapture) objects,
249
* taken directly from hashtable
251
_removeElementObservers: function(elementObservers) {
252
if (elementObservers) {
253
for(var i = elementObservers.length-1; i >= 0; i--) {
254
var entry = elementObservers[i];
255
var args = new Array(entry.element,
259
var removed = OpenLayers.Event.stopObserving.apply(this, args);
265
* Method: stopObserving
268
* elementParam - {DOMElement || String}
270
* observer - {function}
271
* useCapture - {Boolean}
274
* {Boolean} Whether or not the event observer was removed
276
stopObserving: function(elementParam, name, observer, useCapture) {
277
useCapture = useCapture || false;
279
var element = OpenLayers.Util.getElement(elementParam);
280
var cacheID = element._eventCacheID;
282
if (name == 'keypress') {
283
if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
284
element.detachEvent) {
289
// find element's entry in this.observers cache and remove it
290
var foundEntry = false;
291
var elementObservers = OpenLayers.Event.observers[cacheID];
292
if (elementObservers) {
294
// find the specific event type in the element's list
296
while(!foundEntry && i < elementObservers.length) {
297
var cacheEntry = elementObservers[i];
299
if ((cacheEntry.name == name) &&
300
(cacheEntry.observer == observer) &&
301
(cacheEntry.useCapture == useCapture)) {
303
elementObservers.splice(i, 1);
304
if (elementObservers.length == 0) {
305
delete OpenLayers.Event.observers[cacheID];
314
//actually remove the event listener from browser
316
if (element.removeEventListener) {
317
element.removeEventListener(name, observer, useCapture);
318
} else if (element && element.detachEvent) {
319
element.detachEvent('on' + name, observer);
326
* Method: unloadCache
327
* Cycle through all the element entries in the events cache and call
328
* stopObservingElement on each.
330
unloadCache: function() {
331
// check for OpenLayers.Event before checking for observers, because
332
// OpenLayers.Event may be undefined in IE if no map instance was
334
if (OpenLayers.Event && OpenLayers.Event.observers) {
335
for (var cacheID in OpenLayers.Event.observers) {
336
var elementObservers = OpenLayers.Event.observers[cacheID];
337
OpenLayers.Event._removeElementObservers.apply(this,
340
OpenLayers.Event.observers = false;
344
CLASS_NAME: "OpenLayers.Event"
347
/* prevent memory leaks in IE */
348
OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
350
// FIXME: Remove this in 3.0. In 3.0, Event.stop will no longer be provided
353
OpenLayers.Util.applyDefaults(window.Event, OpenLayers.Event);
355
var Event = OpenLayers.Event;
359
* Class: OpenLayers.Events
361
OpenLayers.Events = OpenLayers.Class({
364
* Constant: BROWSER_EVENTS
365
* {Array(String)} supported events
368
"mouseover", "mouseout",
369
"mousedown", "mouseup", "mousemove",
370
"click", "dblclick", "rightclick", "dblrightclick",
371
"resize", "focus", "blur"
375
* Property: listeners
376
* {Object} Hashtable of Array(Function): events listener functions
382
* {Object} the code object issuing application events
388
* {DOMElement} the DOM element receiving browser events
393
* Property: eventTypes
394
* {Array(String)} list of support application events
399
* Property: eventHandler
400
* {Function} bound event handler attached to elements
405
* APIProperty: fallThrough
411
* APIProperty: includeXY
412
* {Boolean} Should the .xy property automatically be created for browser
413
* mouse events? In general, this should be false. If it is true, then
414
* mouse events will automatically generate a '.xy' property on the
415
* event object that is passed. (Prior to OpenLayers 2.7, this was true
416
* by default.) Otherwise, you can call the getMousePosition on the
417
* relevant events handler on the object available via the 'evt.object'
418
* property of the evt object. So, for most events, you can call:
419
* function named(evt) {
420
* this.xy = this.object.events.getMousePosition(evt)
423
* This option typically defaults to false for performance reasons:
424
* when creating an events object whose primary purpose is to manage
425
* relatively positioned mouse events within a div, it may make
426
* sense to set it to true.
428
* This option is also used to control whether the events object caches
429
* offsets. If this is false, it will not: the reason for this is that
430
* it is only expected to be called many times if the includeXY property
431
* is set to true. If you set this to true, you are expected to clear
432
* the offset cache manually (using this.clearMouseCache()) if:
433
* the border of the element changes
434
* the location of the element in the page changes
439
* Constructor: OpenLayers.Events
440
* Construct an OpenLayers.Events object.
443
* object - {Object} The js object to which this Events object is being
444
* added element - {DOMElement} A dom element to respond to browser events
445
* eventTypes - {Array(String)} Array of custom application events
446
* fallThrough - {Boolean} Allow events to fall through after these have
448
* options - {Object} Options for the events object.
450
initialize: function (object, element, eventTypes, fallThrough, options) {
451
OpenLayers.Util.extend(this, options);
452
this.object = object;
453
this.element = element;
454
this.fallThrough = fallThrough;
457
// keep a bound copy of handleBrowserEvent() so that we can
458
// pass the same function to both Event.observe() and .stopObserving()
459
this.eventHandler = OpenLayers.Function.bindAsEventListener(
460
this.handleBrowserEvent, this
463
// if eventTypes is specified, create a listeners list for each
464
// custom application event.
465
this.eventTypes = [];
466
if (eventTypes != null) {
467
for (var i=0, len=eventTypes.length; i<len; i++) {
468
this.addEventType(eventTypes[i]);
472
// if a dom element is specified, add a listeners list
473
// for browser events on the element and register them
474
if (this.element != null) {
475
this.attachToElement(element);
482
destroy: function () {
484
OpenLayers.Event.stopObservingElement(this.element);
488
this.listeners = null;
490
this.eventTypes = null;
491
this.fallThrough = null;
492
this.eventHandler = null;
496
* APIMethod: addEventType
497
* Add a new event type to this events object.
498
* If the event type has already been added, do nothing.
501
* eventName - {String}
503
addEventType: function(eventName) {
504
if (!this.listeners[eventName]) {
505
this.eventTypes.push(eventName);
506
this.listeners[eventName] = [];
511
* Method: attachToElement
514
* element - {HTMLDOMElement} a DOM element to attach browser events to
516
attachToElement: function (element) {
517
for (var i=0, len=this.BROWSER_EVENTS.length; i<len; i++) {
518
var eventType = this.BROWSER_EVENTS[i];
520
// every browser event has a corresponding application event
521
// (whether it's listened for or not).
522
this.addEventType(eventType);
524
// use Prototype to register the event cross-browser
525
OpenLayers.Event.observe(element, eventType, this.eventHandler);
527
// disable dragstart in IE so that mousedown/move/up works normally
528
OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
533
* Convenience method for registering listeners with a common scope.
538
* "loadstart": loadStartListener,
539
* "loadend": loadEndListener,
544
on: function(object) {
545
for(var type in object) {
546
if(type != "scope") {
547
this.register(type, object.scope, object[type]);
553
* APIMethod: register
554
* Register an event on the events object.
556
* When the event is triggered, the 'func' function will be called, in the
557
* context of 'obj'. Imagine we were to register an event, specifying an
558
* OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
559
* context in the callback function will be our Bounds object. This means
560
* that within our callback function, we can access the properties and
561
* methods of the Bounds object through the "this" variable. So our
562
* callback could execute something like:
563
* : leftStr = "Left: " + this.left;
567
* : centerStr = "Center: " + this.getCenterLonLat();
570
* type - {String} Name of the event to register
571
* obj - {Object} The object to bind the context to for the callback#.
572
* If no object is specified, default is the Events's
574
* func - {Function} The callback function. If no callback is
575
* specified, this function does nothing.
579
register: function (type, obj, func) {
581
if ( (func != null) &&
582
(OpenLayers.Util.indexOf(this.eventTypes, type) != -1) ) {
587
var listeners = this.listeners[type];
588
listeners.push( {obj: obj, func: func} );
593
* APIMethod: registerPriority
594
* Same as register() but adds the new listener to the *front* of the
595
* events queue instead of to the end.
597
* TODO: get rid of this in 3.0 - Decide whether listeners should be
598
* called in the order they were registered or in reverse order.
602
* type - {String} Name of the event to register
603
* obj - {Object} The object to bind the context to for the callback#.
604
* If no object is specified, default is the Events's
606
* func - {Function} The callback function. If no callback is
607
* specified, this function does nothing.
609
registerPriority: function (type, obj, func) {
615
var listeners = this.listeners[type];
616
if (listeners != null) {
617
listeners.unshift( {obj: obj, func: func} );
624
* Convenience method for unregistering listeners with a common scope.
629
* "loadstart": loadStartListener,
630
* "loadend": loadEndListener,
635
un: function(object) {
636
for(var type in object) {
637
if(type != "scope") {
638
this.unregister(type, object.scope, object[type]);
644
* APIMethod: unregister
648
* obj - {Object} If none specified, defaults to this.object
651
unregister: function (type, obj, func) {
655
var listeners = this.listeners[type];
656
if (listeners != null) {
657
for (var i=0, len=listeners.length; i<len; i++) {
658
if (listeners[i].obj == obj && listeners[i].func == func) {
659
listeners.splice(i, 1);
668
* Remove all listeners for a given event type. If type is not registered,
674
remove: function(type) {
675
if (this.listeners[type] != null) {
676
this.listeners[type] = [];
681
* APIMethod: triggerEvent
682
* Trigger a specified registered event.
689
* {Boolean} The last listener return. If a listener returns false, the
690
* chain of listeners will stop getting called.
692
triggerEvent: function (type, evt) {
694
// prep evt object with object & div references
698
evt.object = this.object;
699
evt.element = this.element;
704
// execute all callbacks registered for specified type
705
// get a clone of the listeners array to
706
// allow for splicing during callbacks
707
var listeners = (this.listeners[type]) ?
708
this.listeners[type].slice() : null;
709
if ((listeners != null) && (listeners.length > 0)) {
711
for (var i=0, len=listeners.length; i<len; i++) {
712
var callback = listeners[i];
713
// bind the context to callback.obj
714
continueChain = callback.func.apply(callback.obj, [evt]);
716
if ((continueChain != undefined) && (continueChain == false)) {
717
// if callback returns false, execute no more callbacks.
721
// don't fall through to other DOM elements
722
if (!this.fallThrough) {
723
OpenLayers.Event.stop(evt, true);
726
return continueChain;
730
* Method: handleBrowserEvent
731
* Basically just a wrapper to the triggerEvent() function, but takes
732
* care to set a property 'xy' on the event with the current mouse
738
handleBrowserEvent: function (evt) {
739
if (this.includeXY) {
740
evt.xy = this.getMousePosition(evt);
742
this.triggerEvent(evt.type, evt);
746
* APIMethod: clearMouseCache
747
* Clear cached data about the mouse position. This should be called any
748
* time the element that events are registered on changes position
751
clearMouseCache: function() {
752
this.element.scrolls = null;
753
this.element.lefttop = null;
754
this.element.offsets = null;
758
* Method: getMousePosition
764
* {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
767
getMousePosition: function (evt) {
768
if (!this.includeXY) {
769
this.clearMouseCache();
770
} else if (!this.element.hasScrollEvent) {
771
OpenLayers.Event.observe(window, 'scroll',
772
OpenLayers.Function.bind(this.clearMouseCache, this));
773
this.element.hasScrollEvent = true;
776
if (!this.element.scrolls) {
777
this.element.scrolls = [];
778
this.element.scrolls[0] = (document.documentElement.scrollLeft
779
|| document.body.scrollLeft);
780
this.element.scrolls[1] = (document.documentElement.scrollTop
781
|| document.body.scrollTop);
784
if (!this.element.lefttop) {
785
this.element.lefttop = [];
786
this.element.lefttop[0] = (document.documentElement.clientLeft || 0);
787
this.element.lefttop[1] = (document.documentElement.clientTop || 0);
790
if (!this.element.offsets) {
791
this.element.offsets = OpenLayers.Util.pagePosition(this.element);
792
this.element.offsets[0] += this.element.scrolls[0];
793
this.element.offsets[1] += this.element.scrolls[1];
795
return new OpenLayers.Pixel(
796
(evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
797
- this.element.lefttop[0],
798
(evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
799
- this.element.lefttop[1]
803
CLASS_NAME: "OpenLayers.Events"