diff --git a/src/ol/control/control.js b/src/ol/control/control.js index 9fe4232b91..13465598c5 100644 --- a/src/ol/control/control.js +++ b/src/ol/control/control.js @@ -107,10 +107,10 @@ ol.control.Control.prototype.setMap = function(map) { if (this.map_) { goog.dom.removeNode(this.element); } - if (this.listenerKeys.length > 0) { - ol.events.unlistenByKey(this.listenerKeys); - this.listenerKeys.length = 0; + for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) { + ol.events.unlistenByKey(this.listenerKeys[i]); } + this.listenerKeys.length = 0; this.map_ = map; if (this.map_) { var target = this.target_ ? diff --git a/src/ol/control/zoomslidercontrol.js b/src/ol/control/zoomslidercontrol.js index 67329d0781..a68dc1a140 100644 --- a/src/ol/control/zoomslidercontrol.js +++ b/src/ol/control/zoomslidercontrol.js @@ -250,17 +250,15 @@ ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) { this.dragging_ = true; if (!this.dragListenerKeys_) { + var drag = this.handleDraggerDrag_; + var end = this.handleDraggerEnd_; this.dragListenerKeys_ = [ - ol.events.listen(document, [ - ol.events.EventType.MOUSEMOVE, - ol.events.EventType.TOUCHMOVE, - ol.pointer.EventType.POINTERMOVE - ], this.handleDraggerDrag_, this), - ol.events.listen(document, [ - ol.events.EventType.MOUSEUP, - ol.events.EventType.TOUCHEND, - ol.pointer.EventType.POINTERUP - ], this.handleDraggerEnd_, this) + ol.events.listen(document, ol.events.EventType.MOUSEMOVE, drag, this), + ol.events.listen(document, ol.events.EventType.TOUCHMOVE, drag, this), + ol.events.listen(document, ol.pointer.EventType.POINTERMOVE, drag, this), + ol.events.listen(document, ol.events.EventType.MOUSEUP, end, this), + ol.events.listen(document, ol.events.EventType.TOUCHEND, end, this), + ol.events.listen(document, ol.pointer.EventType.POINTERUP, end, this) ]; } } diff --git a/src/ol/deviceorientation.js b/src/ol/deviceorientation.js index 6726ac59d8..faa1e92d1a 100644 --- a/src/ol/deviceorientation.js +++ b/src/ol/deviceorientation.js @@ -83,7 +83,7 @@ ol.DeviceOrientation = function(opt_options) { /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.listenerKey_ = null; diff --git a/src/ol/events.js b/src/ol/events.js index 8940bea04e..75fa70dcc9 100644 --- a/src/ol/events.js +++ b/src/ol/events.js @@ -2,7 +2,6 @@ goog.provide('ol.events'); goog.provide('ol.events.EventType'); goog.provide('ol.events.KeyCode'); -goog.require('goog.asserts'); goog.require('goog.object'); @@ -74,7 +73,7 @@ ol.events.EventTargetLike; /** * Key to use with {@link ol.Observable#unByKey}. * - * @typedef {ol.events.ListenerObjType|Array.} + * @typedef {ol.events.ListenerObjType} * @api */ ol.events.Key; @@ -157,6 +156,50 @@ ol.events.getListeners = function(target, type) { }; +/** + * Get the lookup of listeners. If one does not exist on the target, it is + * created. + * @param {ol.events.EventTargetLike} target Target. + * @return {!Object.>} Map of + * listeners by event type. + * @private + */ +ol.events.getListenerMap_ = function(target) { + var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; + if (!listenerMap) { + listenerMap = target[ol.events.LISTENER_MAP_PROP_] = {}; + } + return listenerMap; +}; + + +/** + * Clean up all listener objects of the given type. All properties on the + * listener objects will be removed, and if no listeners remain in the listener + * map, it will be removed from the target. + * @param {ol.events.EventTargetLike} target Target. + * @param {ol.events.EventType|string} type Type. + * @private + */ +ol.events.removeListeners_ = function(target, type) { + var listeners = ol.events.getListeners(target, type); + if (listeners) { + for (var i = 0, ii = listeners.length; i < ii; ++i) { + target.removeEventListener(type, listeners[i].boundListener); + goog.object.clear(listeners[i]) + } + listeners.length = 0; + var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; + if (listenerMap) { + delete listenerMap[type]; + if (Object.keys(listenerMap).length === 0) { + delete target[ol.events.LISTENER_MAP_PROP_]; + } + } + } +}; + + /** * Registers an event listener on an event target. Inspired by * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} @@ -164,10 +207,8 @@ ol.events.getListeners = function(target, type) { * This function efficiently binds a `listener` to a `this` object, and returns * a key for use with {@link ol.events.unlistenByKey}. * - * @param {EventTarget|ol.events.EventTarget| - * {removeEventListener: function(string, Function, boolean=)}} target - * Event target. - * @param {ol.events.EventType|string|Array.<(ol.events.EventType|string)>} type + * @param {ol.events.EventTargetLike} target Event target. + * @param {ol.events.EventType|string} type * Event type. * @param {ol.events.ListenerFunctionType} listener Listener. * @param {Object=} opt_this Object referenced by the `this` keyword in the @@ -176,18 +217,7 @@ ol.events.getListeners = function(target, type) { * @return {ol.events.Key} Unique key for the listener. */ ol.events.listen = function(target, type, listener, opt_this, opt_once) { - if (Array.isArray(type)) { - var keys = []; - type.forEach(function(t) { - keys.push(ol.events.listen(target, t, listener, opt_this, opt_once)); - }); - return keys; - } - - var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; - if (!listenerMap) { - target[ol.events.LISTENER_MAP_PROP_] = listenerMap = {}; - } + var listenerMap = ol.events.getListenerMap_(target); var listenerArray = listenerMap[type]; if (!listenerArray) { listenerArray = listenerMap[type] = []; @@ -229,8 +259,7 @@ ol.events.listen = function(target, type, listener, opt_this, opt_once) { * listener. * * @param {ol.events.EventTargetLike} target Event target. - * @param {ol.events.EventType|string|Array.<(ol.events.EventType|string)>} type - * Event type. + * @param {ol.events.EventType|string} type Event type. * @param {ol.events.ListenerFunctionType} listener Listener. * @param {Object=} opt_this Object referenced by the `this` keyword in the * listener. Default is the `target`. @@ -250,20 +279,12 @@ ol.events.listenOnce = function( * arguments that were used for a previous {@link ol.events.listen} call. * * @param {ol.events.EventTargetLike} target Event target. - * @param {ol.events.EventType|string|Array.<(ol.events.EventType|string)>} type - * Event type. + * @param {ol.events.EventType|string} type Event type. * @param {ol.events.ListenerFunctionType} listener Listener. * @param {Object=} opt_this Object referenced by the `this` keyword in the * listener. Default is the `target`. */ ol.events.unlisten = function(target, type, listener, opt_this) { - if (Array.isArray(type)) { - type.forEach(function(t) { - ol.events.unlisten(target, t, listener, opt_this); - }); - return; - } - goog.asserts.assertString(type, 'type is a string'); var listenerArray = ol.events.getListeners(target, type); if (listenerArray) { var listenerObj = ol.events.findListener_(listenerArray, listener, @@ -282,25 +303,16 @@ ol.events.unlisten = function(target, type, listener, opt_this) { * The argument passed to this function is the key returned from * {@link ol.events.listen} or {@link ol.events.listenOnce}. * - * @param {ol.events.Key} key Key or keys. + * @param {ol.events.Key} key The key. */ ol.events.unlistenByKey = function(key) { - if (Array.isArray(key)) { - key.forEach(ol.events.unlistenByKey); - return; - } - if (key && key.target) { key.target.removeEventListener(key.type, key.boundListener); var listenerArray = ol.events.getListeners(key.target, key.type); if (listenerArray) { ol.events.findListener_(listenerArray, key.listener, key.bindTo, true); if (listenerArray.length === 0) { - var listenerMap = key.target[ol.events.LISTENER_MAP_PROP_]; - delete listenerMap[key.type]; - if (Object.keys(listenerMap).length === 0) { - delete key.target[ol.events.LISTENER_MAP_PROP_]; - } + ol.events.removeListeners_(key.target, key.type); } } goog.object.clear(key); @@ -315,10 +327,8 @@ ol.events.unlistenByKey = function(key) { * @param {ol.events.EventTargetLike} target Target. */ ol.events.unlistenAll = function(target) { - var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; - if (listenerMap) { - for (var type in listenerMap) { - ol.events.unlistenByKey(listenerMap[type]); - } + var listenerMap = ol.events.getListenerMap_(target); + for (var type in listenerMap) { + ol.events.removeListeners_(target, type); } }; diff --git a/src/ol/feature.js b/src/ol/feature.js index 3cd97ebd2c..a99ff3747d 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -87,7 +87,7 @@ ol.Feature = function(opt_geometryOrProperties) { /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.geometryChangeKey_ = null; diff --git a/src/ol/interaction/draganddropinteraction.js b/src/ol/interaction/draganddropinteraction.js index 16819f9285..bab371d51e 100644 --- a/src/ol/interaction/draganddropinteraction.js +++ b/src/ol/interaction/draganddropinteraction.js @@ -153,8 +153,11 @@ ol.interaction.DragAndDrop.prototype.setMap = function(map) { this.dropListenKeys_ = [ ol.events.listen(dropArea, ol.events.EventType.DROP, ol.interaction.DragAndDrop.handleDrop_, this), - ol.events.listen(dropArea, [ol.events.EventType.DRAGENTER, - ol.events.EventType.DRAGOVER, ol.events.EventType.DROP], + ol.events.listen(dropArea, ol.events.EventType.DRAGENTER, + ol.interaction.DragAndDrop.handleStop_, this), + ol.events.listen(dropArea, ol.events.EventType.DRAGOVER, + ol.interaction.DragAndDrop.handleStop_, this), + ol.events.listen(dropArea, ol.events.EventType.DROP, ol.interaction.DragAndDrop.handleStop_, this) ] } diff --git a/src/ol/interaction/snapinteraction.js b/src/ol/interaction/snapinteraction.js index 005195dde5..eb8598ade6 100644 --- a/src/ol/interaction/snapinteraction.js +++ b/src/ol/interaction/snapinteraction.js @@ -168,11 +168,13 @@ ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) { segmentWriter.call(this, feature, geometry); if (listen) { - this.geometryModifyListenerKeys_[feature_uid] = geometry.on( + this.geometryModifyListenerKeys_[feature_uid] = ol.events.listen( + geometry, ol.events.EventType.CHANGE, this.handleGeometryModify_.bind(this, feature), this); - this.geometryChangeListenerKeys_[feature_uid] = feature.on( + this.geometryChangeListenerKeys_[feature_uid] = ol.events.listen( + feature, ol.Object.getChangeEventType(feature.getGeometryName()), this.handleGeometryChange_, this); } diff --git a/src/ol/layer/heatmaplayer.js b/src/ol/layer/heatmaplayer.js index 7bdd5cafcd..a5daba97ea 100644 --- a/src/ol/layer/heatmaplayer.js +++ b/src/ol/layer/heatmaplayer.js @@ -83,10 +83,12 @@ ol.layer.Heatmap = function(opt_options) { this.setRadius(options.radius !== undefined ? options.radius : 8); - ol.events.listen(this, [ - ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.BLUR), - ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.RADIUS) - ], this.handleStyleChanged_, this); + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.BLUR), + this.handleStyleChanged_, this); + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.RADIUS), + this.handleStyleChanged_, this); this.handleStyleChanged_(); diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index 61e53335d9..61ae7a32c0 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -41,19 +41,19 @@ ol.layer.Layer = function(options) { /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.mapPrecomposeKey_ = null; /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.mapRenderKey_ = null; /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.sourceChangeKey_ = null; @@ -164,13 +164,17 @@ ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { * @api */ ol.layer.Layer.prototype.setMap = function(map) { - ol.events.unlistenByKey(this.mapPrecomposeKey_); - this.mapPrecomposeKey_ = null; + if (this.mapPrecomposeKey_) { + ol.events.unlistenByKey(this.mapPrecomposeKey_); + this.mapPrecomposeKey_ = null; + } if (!map) { this.changed(); } - ol.events.unlistenByKey(this.mapRenderKey_); - this.mapRenderKey_ = null; + if (this.mapRenderKey_) { + ol.events.unlistenByKey(this.mapRenderKey_); + this.mapRenderKey_ = null; + } if (map) { this.mapPrecomposeKey_ = ol.events.listen( map, ol.render.EventType.PRECOMPOSE, function(evt) { diff --git a/src/ol/map.js b/src/ol/map.js index 6b8bbb470f..9397724a4f 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -243,7 +243,7 @@ ol.Map = function(options) { /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.viewPropertyListenerKey_ = null; @@ -284,7 +284,7 @@ ol.Map = function(options) { */ this.overlayContainerStopEvent_ = document.createElement('DIV'); this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent'; - ol.events.listen(this.overlayContainerStopEvent_, [ + var overlayEvents = [ ol.events.EventType.CLICK, ol.events.EventType.DBLCLICK, ol.events.EventType.MOUSEDOWN, @@ -292,13 +292,18 @@ ol.Map = function(options) { ol.events.EventType.MSPOINTERDOWN, ol.MapBrowserEvent.EventType.POINTERDOWN, goog.userAgent.GECKO ? 'DOMMouseScroll' : ol.events.EventType.MOUSEWHEEL - ], ol.events.Event.stopPropagation); + ]; + for (var i = 0, ii = overlayEvents.length; i < ii; ++i) { + ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i], + ol.events.Event.stopPropagation); + } this.viewport_.appendChild(this.overlayContainerStopEvent_); var mapBrowserEventHandler = new ol.MapBrowserEventHandler(this); - ol.events.listen(mapBrowserEventHandler, - goog.object.getValues(ol.MapBrowserEvent.EventType), - this.handleMapBrowserEvent, this); + for (var key in ol.MapBrowserEvent.EventType) { + ol.events.listen(mapBrowserEventHandler, ol.MapBrowserEvent.EventType[key], + this.handleMapBrowserEvent, this); + } this.registerDisposable(mapBrowserEventHandler); /** @@ -309,9 +314,9 @@ ol.Map = function(options) { /** * @private - * @type {ol.events.Key|undefined} + * @type {Array.} */ - this.keyHandlerKey_; + this.keyHandlerKeys_ = null; ol.events.listen(this.viewport_, ol.events.EventType.WHEEL, this.handleBrowserEvent, this); @@ -1060,8 +1065,11 @@ ol.Map.prototype.handleTargetChanged_ = function() { var targetElement = this.getTargetElement(); - if (this.keyHandlerKey_) { - ol.events.unlistenByKey(this.keyHandlerKey_); + if (this.keyHandlerKeys_) { + for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) { + ol.events.unlistenByKey(this.keyHandlerKeys_[i]); + } + this.keyHandlerKeys_ = null; } if (!targetElement) { @@ -1076,9 +1084,12 @@ ol.Map.prototype.handleTargetChanged_ = function() { var keyboardEventTarget = !this.keyboardEventTarget_ ? targetElement : this.keyboardEventTarget_; - this.keyHandlerKey_ = ol.events.listen(keyboardEventTarget, - [ol.events.EventType.KEYDOWN, ol.events.EventType.KEYPRESS], - this.handleBrowserEvent, this); + this.keyHandlerKeys_ = [ + ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN, + this.handleBrowserEvent, this), + ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS, + this.handleBrowserEvent, this) + ]; if (!this.handleResize_) { this.handleResize_ = this.updateSize.bind(this); diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js index 95bbadaed0..f6eef67ffd 100644 --- a/src/ol/mapbrowserevent.js +++ b/src/ol/mapbrowserevent.js @@ -154,7 +154,7 @@ ol.MapBrowserEventHandler = function(map) { this.dragListenerKeys_ = null; /** - * @type {ol.events.Key} + * @type {?ol.events.Key} * @private */ this.pointerdownListenerKey_ = null; diff --git a/src/ol/observable.js b/src/ol/observable.js index 6281ce2452..0f3d53e273 100644 --- a/src/ol/observable.js +++ b/src/ol/observable.js @@ -35,11 +35,18 @@ goog.inherits(ol.Observable, ol.events.EventTarget); /** * Removes an event listener using the key returned by `on()` or `once()`. - * @param {ol.events.Key} key The key returned by `on()` or `once()`. + * @param {ol.events.Key|Array.} key The key returned by `on()` + * or `once()` (or an array of keys). * @api stable */ ol.Observable.unByKey = function(key) { - ol.events.unlistenByKey(key); + if (Array.isArray(key)) { + for (var i = 0, ii = key.length; i < ii; ++i) { + ol.events.unlistenByKey(key[i]); + } + } else { + ol.events.unlistenByKey(/** @type {ol.events.Key} */ (key)); + } }; @@ -90,11 +97,23 @@ ol.Observable.prototype.getRevision = function() { * @param {string|Array.} type The event type or array of event types. * @param {function(?): ?} listener The listener function. * @param {Object=} opt_this The object to use as `this` in `listener`. - * @return {ol.events.Key} Unique key for the listener. + * @return {ol.events.Key|Array.} Unique key for the listener. If + * called with an array of event types as the first argument, the return + * will be an array of keys. * @api stable */ ol.Observable.prototype.on = function(type, listener, opt_this) { - return ol.events.listen(this, type, listener, opt_this); + if (Array.isArray(type)) { + var len = type.length; + var keys = new Array(len); + for (var i = 0; i < len; ++i) { + keys[i] = ol.events.listen(this, type[i], listener, opt_this); + } + return keys; + } else { + return ol.events.listen( + this, /** @type {string} */ (type), listener, opt_this); + } }; @@ -103,11 +122,23 @@ ol.Observable.prototype.on = function(type, listener, opt_this) { * @param {string|Array.} type The event type or array of event types. * @param {function(?): ?} listener The listener function. * @param {Object=} opt_this The object to use as `this` in `listener`. - * @return {ol.events.Key} Unique key for the listener. + * @return {ol.events.Key|Array.} Unique key for the listener. If + * called with an array of event types as the first argument, the return + * will be an array of keys. * @api stable */ ol.Observable.prototype.once = function(type, listener, opt_this) { - return ol.events.listenOnce(this, type, listener, opt_this); + if (Array.isArray(type)) { + var len = type.length; + var keys = new Array(len); + for (var i = 0; i < len; ++i) { + keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this); + } + return keys; + } else { + return ol.events.listenOnce( + this, /** @type {string} */ (type), listener, opt_this); + } }; @@ -120,7 +151,14 @@ ol.Observable.prototype.once = function(type, listener, opt_this) { * @api stable */ ol.Observable.prototype.un = function(type, listener, opt_this) { - ol.events.unlisten(this, type, listener, opt_this); + if (Array.isArray(type)) { + for (var i = 0, ii = type.length; i < ii; ++i) { + ol.events.unlisten(this, type[i], listener, opt_this); + } + return; + } else { + ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this); + } }; @@ -128,7 +166,8 @@ ol.Observable.prototype.un = function(type, listener, opt_this) { * Removes an event listener using the key returned by `on()` or `once()`. * Note that using the {@link ol.Observable.unByKey} static function is to * be preferred. - * @param {ol.events.Key} key The key returned by `on()` or `once()`. + * @param {ol.events.Key|Array.} key The key returned by `on()` + * or `once()` (or an array of keys). * @function * @api stable */ diff --git a/src/ol/overlay.js b/src/ol/overlay.js index 9bd2b78d21..1fdd1aa9d1 100644 --- a/src/ol/overlay.js +++ b/src/ol/overlay.js @@ -137,7 +137,7 @@ ol.Overlay = function(options) { /** * @private - * @type {ol.events.Key} + * @type {?ol.events.Key} */ this.mapPostrenderListenerKey_ = null; diff --git a/src/ol/reproj/image.js b/src/ol/reproj/image.js index 7b8c32f52e..4d8b81581a 100644 --- a/src/ol/reproj/image.js +++ b/src/ol/reproj/image.js @@ -102,7 +102,7 @@ ol.reproj.Image = function(sourceProj, targetProj, /** * @private - * @type {ol.events.Key|null} + * @type {?ol.events.Key} */ this.sourceListenerKey_ = null; diff --git a/test/spec/ol/events.test.js b/test/spec/ol/events.test.js index d4781811f8..2ee3f7a8fe 100644 --- a/test/spec/ol/events.test.js +++ b/test/spec/ol/events.test.js @@ -84,16 +84,9 @@ describe('ol.events', function() { ol.events.listen(target, 'foo', function() {}); expect(add.callCount).to.be(1); }); - it('adds listeners for multiple types with a single call', function() { - ol.events.listen(target, ['foo', 'bar'], function() {}); - expect(add.getCall(0).args[0]).to.be('foo'); - expect(add.getCall(1).args[0]).to.be('bar'); - }); it('returns a key', function() { var key = ol.events.listen(target, 'foo', function() {}); expect(key).to.be.a(Object); - key = ol.events.listen(target, ['foo', 'bar'], function() {}); - expect(key).to.be.a(Array); }); it('does not add the same listener twice', function() { var listener = function() {}; @@ -173,12 +166,15 @@ describe('ol.events', function() { describe('unlistenAll()', function() { it('unregisters all listeners registered for a target', function() { - var key = ol.events.listen(target, ['foo', 'bar'], function() {}); + var keys = [ + ol.events.listen(target, 'foo', function() {}), + ol.events.listen(target, 'bar', function() {}) + ]; ol.events.unlistenAll(target); expect(ol.events.getListeners(target, 'foo')).to.be(undefined); expect(ol.events.getListeners(target, 'bar')).to.be(undefined); expect(ol.events.LISTENER_MAP_PROP_ in target).to.be(false); - expect(key).to.eql([{}, {}]); + expect(keys).to.eql([{}, {}]); }); });