From d81fe5ac952a080074bdf7ea17e9ffe5fed2e257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Thu, 31 Oct 2013 13:35:01 +0100 Subject: [PATCH] Map browser event handler refactoring This commit refactors the code of the map browser event handler. The changes are the following: Previously the `handleTouch*_` listener functions handled both `touch*` and `mspointer*` events. With this commit we use per-event type listeners. This duplicates some code (could be avoidable), but makes it easier to introduce mouse/touch/pointer specific treatments in the future. We now listen to pointermove, pointerup, touchmove and touchend only when needed. In this way we're assured that a `down` event was received (and that `this.down_` is set) when the `up` or `move` listeners are called. This fixes the bug @oterral reported on the mailing list: https://groups.google.com/d/msg/ol3-dev/jR844F7Nlg8/eN9dt4uATK0J. To avoid problems in browsers that trigger different types of events (e.g. both mouse and pointer events) the `down` listeners unregister the other `down` listeners. For example, the `pointerdown` listener unregisters the `mousedown` and `touchstart` listeners, for ever. --- src/ol/mapbrowserevent.js | 162 +++++++++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 38 deletions(-) diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js index 237ac700c9..3c09b84dda 100644 --- a/src/ol/mapbrowserevent.js +++ b/src/ol/mapbrowserevent.js @@ -141,10 +141,16 @@ ol.MapBrowserEventHandler = function(map) { this.mousedownListenerKey_ = null; /** - * @type {Array.} + * @type {goog.events.Key} * @private */ - this.touchListenerKeys_ = null; + this.pointerdownListenerKey_ = null; + + /** + * @type {goog.events.Key} + * @private + */ + this.touchstartListenerKey_ = null; /** * @type {goog.events.BrowserEvent} @@ -158,21 +164,13 @@ ol.MapBrowserEventHandler = function(map) { goog.events.EventType.MOUSEDOWN, this.handleMouseDown_, false, this); - // touch events - this.touchListenerKeys_ = [ - goog.events.listen(element, [ + this.pointerdownListenerKey_ = goog.events.listen(element, + goog.events.EventType.MSPOINTERDOWN, + this.handlePointerDown_, false, this); + + this.touchstartListenerKey_ = goog.events.listen(element, goog.events.EventType.TOUCHSTART, - goog.events.EventType.MSPOINTERDOWN - ], this.handleTouchStart_, false, this), - goog.events.listen(goog.global.document, [ - goog.events.EventType.TOUCHMOVE, - goog.events.EventType.MSPOINTERMOVE - ], this.handleTouchMove_, false, this), - goog.events.listen(goog.global.document, [ - goog.events.EventType.TOUCHEND, - goog.events.EventType.MSPOINTERUP - ], this.handleTouchEnd_, false, this) - ]; + this.handleTouchStart_, false, this); }; goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget); @@ -227,6 +225,17 @@ ol.MapBrowserEventHandler.prototype.handleMouseUp_ = function(browserEvent) { * @private */ ol.MapBrowserEventHandler.prototype.handleMouseDown_ = function(browserEvent) { + if (!goog.isNull(this.pointerdownListenerKey_)) { + // mouse device detected - unregister the pointerdown and touchstart + // listeners + goog.events.unlistenByKey(this.pointerdownListenerKey_); + this.pointerdownListenerKey_ = null; + + goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_)); + goog.events.unlistenByKey(this.touchstartListenerKey_); + this.touchstartListenerKey_ = null; + } + var newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.DOWN, this.map_, browserEvent); this.dispatchEvent(newEvent); @@ -265,21 +274,49 @@ ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) { * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private */ -ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) { +ol.MapBrowserEventHandler.prototype.handlePointerDown_ = + function(browserEvent) { if (!goog.isNull(this.mousedownListenerKey_)) { + // pointer device detected - unregister the mousedown and touchstart + // listeners goog.events.unlistenByKey(this.mousedownListenerKey_); this.mousedownListenerKey_ = null; + + goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_)); + goog.events.unlistenByKey(this.touchstartListenerKey_); + this.touchstartListenerKey_ = null; } - // prevent context menu - // When the IE pointer events are used, this prevents a - // 'mousedown' from being fired after this event for the primary - // contact (first finger on the screen or mouse) - browserEvent.preventDefault(); - this.down_ = browserEvent; - this.dragged_ = false; + var newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent); this.dispatchEvent(newEvent); + + this.down_ = browserEvent; + this.dragged_ = false; + this.dragListenerKeys_ = [ + goog.events.listen(goog.global.document, + goog.events.EventType.MSPOINTERMOVE, + this.handlePointerMove_, false, this), + goog.events.listen(goog.global.document, goog.events.EventType.MSPOINTERUP, + this.handlePointerUp_, false, this) + ]; + + // FIXME check if/when this is necessary + // prevent context menu + browserEvent.preventDefault(); +}; + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerMove_ = + function(browserEvent) { + this.dragged_ = true; + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent); + this.dispatchEvent(newEvent); }; @@ -287,20 +324,64 @@ ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) { * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private */ -ol.MapBrowserEventHandler.prototype.handleTouchMove_ = function(browserEvent) { - if (this.down_) { - // 'touchmove' events are dispatched only when this.down_ is set - // (set after a touch start) to prevent unwanted events when the - // mouse hover the page. This only happens with the IE pointer - // event system. - this.dragged_ = true; - var newEvent = new ol.MapBrowserEvent( - ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent); - this.dispatchEvent(newEvent); +ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(browserEvent) { + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent); + this.dispatchEvent(newEvent); + goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey); + if (!this.dragged_) { + goog.asserts.assert(!goog.isNull(this.down_)); + this.emulateClick_(this.down_); } }; +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) { + if (!goog.isNull(this.mousedownListenerKey_)) { + // touch device detected - unregister the mousedown and pointerdown + // listeners + goog.events.unlistenByKey(this.mousedownListenerKey_); + this.mousedownListenerKey_ = null; + + goog.asserts.assert(!goog.isNull(this.pointerdownListenerKey_)); + goog.events.unlistenByKey(this.pointerdownListenerKey_); + this.pointerdownListenerKey_ = null; + } + + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent); + this.dispatchEvent(newEvent); + + this.down_ = browserEvent; + this.dragged_ = false; + this.dragListenerKeys_ = [ + goog.events.listen(goog.global.document, goog.events.EventType.TOUCHMOVE, + this.handleTouchMove_, false, this), + goog.events.listen(goog.global.document, goog.events.EventType.TOUCHEND, + this.handleTouchEnd_, false, this) + ]; + + // FIXME check if/when this is necessary + browserEvent.preventDefault(); +}; + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handleTouchMove_ = function(browserEvent) { + this.dragged_ = true; + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent); + this.dispatchEvent(newEvent); +}; + + /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private @@ -309,6 +390,7 @@ ol.MapBrowserEventHandler.prototype.handleTouchEnd_ = function(browserEvent) { var newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent); this.dispatchEvent(newEvent); + goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey); if (!this.dragged_) { goog.asserts.assert(!goog.isNull(this.down_)); this.emulateClick_(this.down_); @@ -324,14 +406,18 @@ ol.MapBrowserEventHandler.prototype.disposeInternal = function() { goog.events.unlistenByKey(this.mousedownListenerKey_); this.mousedownListenerKey_ = null; } + if (!goog.isNull(this.pointerdownListenerKey_)) { + goog.events.unlistenByKey(this.pointerdownListenerKey_); + this.pointerdownListenerKey_ = null; + } + if (!goog.isNull(this.touchstartListenerKey_)) { + goog.events.unlistenByKey(this.touchstartListenerKey_); + this.touchstartListenerKey_ = null; + } if (!goog.isNull(this.dragListenerKeys_)) { goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey); this.dragListenerKeys_ = null; } - if (!goog.isNull(this.touchListenerKeys_)) { - goog.array.forEach(this.touchListenerKeys_, goog.events.unlistenByKey); - this.touchListenerKeys_ = null; - } goog.base(this, 'disposeInternal'); };