From d133c86b510df29c404dc3df27e3bccccaa5a7a4 Mon Sep 17 00:00:00 2001 From: danzel Date: Wed, 29 Aug 2012 16:03:54 +1200 Subject: [PATCH 1/9] Add LongPress support to mobile. Fires a contextmenu event after a single press without movement of 1 second --- build/deps.js | 1 + debug/leaflet-include.js | 1 + src/dom/DomEvent.LongPress.js | 60 +++++++++++++++++++++++++++++++++++ src/dom/DomEvent.js | 3 ++ 4 files changed, 65 insertions(+) create mode 100644 src/dom/DomEvent.LongPress.js diff --git a/build/deps.js b/build/deps.js index ff8fa15b..5f36cdab 100644 --- a/build/deps.js +++ b/build/deps.js @@ -170,6 +170,7 @@ var deps = { src: ['dom/DomEvent.js', 'dom/DomEvent.DoubleTap.js', 'dom/DomEvent.MsTouch.js', + 'dom/DomEvent.LongPress.js', 'core/Handler.js', 'map/handler/Map.TouchZoom.js'], deps: ['MapAnimationZoom'], diff --git a/debug/leaflet-include.js b/debug/leaflet-include.js index 6833ff10..d2d73390 100644 --- a/debug/leaflet-include.js +++ b/debug/leaflet-include.js @@ -17,6 +17,7 @@ 'dom/DomEvent.js', 'dom/DomEvent.DoubleTap.js', 'dom/DomEvent.MsTouch.js', + 'dom/DomEvent.LongPress.js', 'dom/DomUtil.js', 'dom/Draggable.js', diff --git a/src/dom/DomEvent.LongPress.js b/src/dom/DomEvent.LongPress.js new file mode 100644 index 00000000..4384c1b0 --- /dev/null +++ b/src/dom/DomEvent.LongPress.js @@ -0,0 +1,60 @@ +L.Util.extend(L.DomEvent, { + // inspired by Zepto touch code by Thomas Fuchs + addLongPressListener: function (obj, handler, id) { + var touch, + start, + timeoutId = null, + delay = 1000, + maxMovement = 10, + diffX, diffY, + pre = '_leaflet_', + touchstart = 'touchstart', + touchmove = 'touchmove', + touchend = 'touchend'; + + function onTouchStart(e) { + clearTimeout(timeoutId); + + if (e.touches.length !== 1) { + return; + } + + touch = e.touches[0]; + start = Date.now(); + + timeoutId = setTimeout(function () { + touch.type = 'contextmenu'; + handler(touch); + }, delay); + } + + function onTouchMove(e) { + diffX = e.touches[0].pageX - touch.pageX; + diffY = e.touches[0].pageY - touch.pageY; + + if (diffX * diffX + diffY * diffY > maxMovement * maxMovement) { + clearTimeout(timeoutId); + } + } + + function onTouchEnd() { + clearTimeout(timeoutId); + } + obj[pre + touchstart + id] = onTouchStart; + obj[pre + touchmove + id] = onTouchMove; + obj[pre + touchend + id] = onTouchEnd; + + obj.addEventListener(touchstart, onTouchStart, false); + obj.addEventListener(touchmove, onTouchMove, false); + obj.addEventListener(touchend, onTouchEnd, false); + return this; + }, + + removeLongPressListener: function (obj, id) { + var pre = '_leaflet_'; + obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false); + obj.removeEventListener(obj, obj[pre + 'touchmove' + id], false); + obj.removeEventListener(obj, obj[pre + 'touchend' + id], false); + return this; + } +}); diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 85141509..2d619a91 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -21,6 +21,9 @@ L.DomEvent = { } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { return this.addDoubleTapListener(obj, handler, id); + } else if (L.Browser.touch && (type === 'contextmenu') && this.addLongPressListener) { + return this.addLongPressListener(obj, handler, id); + } else if ('addEventListener' in obj) { if (type === 'mousewheel') { From d3d85198e2920def6ba2e380b74b541bd418ceba Mon Sep 17 00:00:00 2001 From: danzel Date: Mon, 3 Sep 2012 11:31:01 +1200 Subject: [PATCH 2/9] Need to clone the x/y of the touch as the actual object gets updated. This fixes contextMenu occurring while touch panning the map. --- src/dom/DomEvent.LongPress.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/dom/DomEvent.LongPress.js b/src/dom/DomEvent.LongPress.js index 4384c1b0..12416b6d 100644 --- a/src/dom/DomEvent.LongPress.js +++ b/src/dom/DomEvent.LongPress.js @@ -1,7 +1,7 @@ L.Util.extend(L.DomEvent, { // inspired by Zepto touch code by Thomas Fuchs addLongPressListener: function (obj, handler, id) { - var touch, + var touch, touchStartX, touchStartY, start, timeoutId = null, delay = 1000, @@ -20,17 +20,21 @@ L.Util.extend(L.DomEvent, { } touch = e.touches[0]; + touchStartX = touch.pageX; + touchStartY = touch.pageY; start = Date.now(); timeoutId = setTimeout(function () { + touch.pageX = touchStartX; + touch.pageY = touchStartY; touch.type = 'contextmenu'; handler(touch); }, delay); } function onTouchMove(e) { - diffX = e.touches[0].pageX - touch.pageX; - diffY = e.touches[0].pageY - touch.pageY; + diffX = e.touches[0].pageX - touchStartX; + diffY = e.touches[0].pageY - touchStartY; if (diffX * diffX + diffY * diffY > maxMovement * maxMovement) { clearTimeout(timeoutId); From 1aff46cb27a54bf845eafb6a30257a36e109921a Mon Sep 17 00:00:00 2001 From: danzel Date: Mon, 3 Sep 2012 14:18:29 +1200 Subject: [PATCH 3/9] Rewrite longpress->contextmenu to live inside Draggable instead as it needs deep interaction with it. When an emulated contextmenu is fired on touch we stop dragging and stop a click event from occurring. New map option to disable it if required: touchContextMenuEmulation --- build/deps.js | 1 - debug/leaflet-include.js | 1 - src/dom/DomEvent.LongPress.js | 64 ----------------------------------- src/dom/DomEvent.js | 3 -- src/dom/Draggable.js | 18 +++++++++- src/map/handler/Map.Drag.js | 8 +++-- 6 files changed, 22 insertions(+), 73 deletions(-) delete mode 100644 src/dom/DomEvent.LongPress.js diff --git a/build/deps.js b/build/deps.js index 5f36cdab..ff8fa15b 100644 --- a/build/deps.js +++ b/build/deps.js @@ -170,7 +170,6 @@ var deps = { src: ['dom/DomEvent.js', 'dom/DomEvent.DoubleTap.js', 'dom/DomEvent.MsTouch.js', - 'dom/DomEvent.LongPress.js', 'core/Handler.js', 'map/handler/Map.TouchZoom.js'], deps: ['MapAnimationZoom'], diff --git a/debug/leaflet-include.js b/debug/leaflet-include.js index d2d73390..6833ff10 100644 --- a/debug/leaflet-include.js +++ b/debug/leaflet-include.js @@ -17,7 +17,6 @@ 'dom/DomEvent.js', 'dom/DomEvent.DoubleTap.js', 'dom/DomEvent.MsTouch.js', - 'dom/DomEvent.LongPress.js', 'dom/DomUtil.js', 'dom/Draggable.js', diff --git a/src/dom/DomEvent.LongPress.js b/src/dom/DomEvent.LongPress.js deleted file mode 100644 index 12416b6d..00000000 --- a/src/dom/DomEvent.LongPress.js +++ /dev/null @@ -1,64 +0,0 @@ -L.Util.extend(L.DomEvent, { - // inspired by Zepto touch code by Thomas Fuchs - addLongPressListener: function (obj, handler, id) { - var touch, touchStartX, touchStartY, - start, - timeoutId = null, - delay = 1000, - maxMovement = 10, - diffX, diffY, - pre = '_leaflet_', - touchstart = 'touchstart', - touchmove = 'touchmove', - touchend = 'touchend'; - - function onTouchStart(e) { - clearTimeout(timeoutId); - - if (e.touches.length !== 1) { - return; - } - - touch = e.touches[0]; - touchStartX = touch.pageX; - touchStartY = touch.pageY; - start = Date.now(); - - timeoutId = setTimeout(function () { - touch.pageX = touchStartX; - touch.pageY = touchStartY; - touch.type = 'contextmenu'; - handler(touch); - }, delay); - } - - function onTouchMove(e) { - diffX = e.touches[0].pageX - touchStartX; - diffY = e.touches[0].pageY - touchStartY; - - if (diffX * diffX + diffY * diffY > maxMovement * maxMovement) { - clearTimeout(timeoutId); - } - } - - function onTouchEnd() { - clearTimeout(timeoutId); - } - obj[pre + touchstart + id] = onTouchStart; - obj[pre + touchmove + id] = onTouchMove; - obj[pre + touchend + id] = onTouchEnd; - - obj.addEventListener(touchstart, onTouchStart, false); - obj.addEventListener(touchmove, onTouchMove, false); - obj.addEventListener(touchend, onTouchEnd, false); - return this; - }, - - removeLongPressListener: function (obj, id) { - var pre = '_leaflet_'; - obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false); - obj.removeEventListener(obj, obj[pre + 'touchmove' + id], false); - obj.removeEventListener(obj, obj[pre + 'touchend' + id], false); - return this; - } -}); diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 2d619a91..85141509 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -21,9 +21,6 @@ L.DomEvent = { } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { return this.addDoubleTapListener(obj, handler, id); - } else if (L.Browser.touch && (type === 'contextmenu') && this.addLongPressListener) { - return this.addLongPressListener(obj, handler, id); - } else if ('addEventListener' in obj) { if (type === 'mousewheel') { diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index ee8fd25c..4f99fdb8 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -12,9 +12,10 @@ L.Draggable = L.Class.extend({ TAP_TOLERANCE: 15 }, - initialize: function (element, dragStartTarget) { + initialize: function (element, dragStartTarget, contextMenuEmulation) { this._element = element; this._dragStartTarget = dragStartTarget || element; + this._contextMenuEmulation = contextMenuEmulation; }, enable: function () { @@ -64,6 +65,20 @@ L.Draggable = L.Class.extend({ this._startPoint = new L.Point(first.clientX, first.clientY); this._startPos = this._newPos = L.DomUtil.getPosition(this._element); + //Touch contextmenu event emulation + if (e.touches && e.touches.length === 1 && L.Browser.touch && this._contextMenuEmulation) { + var self = this; + this._contextMenuTimeout = setTimeout(function () { + var dist = (self._newPos && self._newPos.distanceTo(self._startPos)) || 0; + + if (dist < L.Draggable.TAP_TOLERANCE) { + self._simulateClick = false; + self._onUp(); + self._simulateEvent('contextmenu', first); + } + }, 1000); + } + L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this); L.DomEvent.on(document, L.Draggable.END, this._onUp, this); }, @@ -106,6 +121,7 @@ L.Draggable = L.Class.extend({ _onUp: function (e) { var simulateClickTouch; + clearTimeout(this._contextMenuTimeout); if (this._simulateClick && e.changedTouches) { var first = e.changedTouches[0], el = first.target, diff --git a/src/map/handler/Map.Drag.js b/src/map/handler/Map.Drag.js index 1344853a..15cd4cac 100644 --- a/src/map/handler/Map.Drag.js +++ b/src/map/handler/Map.Drag.js @@ -10,6 +10,8 @@ L.Map.mergeOptions({ inertiaMaxSpeed: 6000, // px/s inertiaThreshold: L.Browser.touch ? 32 : 18, // ms + touchContextMenuEmulation: true, + // TODO refactor, move to CRS worldCopyJump: true }); @@ -17,7 +19,9 @@ L.Map.mergeOptions({ L.Map.Drag = L.Handler.extend({ addHooks: function () { if (!this._draggable) { - this._draggable = new L.Draggable(this._map._mapPane, this._map._container); + var options = this._map.options; + + this._draggable = new L.Draggable(this._map._mapPane, this._map._container, options.touchContextMenuEmulation); this._draggable.on({ 'dragstart': this._onDragStart, @@ -25,8 +29,6 @@ L.Map.Drag = L.Handler.extend({ 'dragend': this._onDragEnd }, this); - var options = this._map.options; - if (options.worldCopyJump) { this._draggable.on('predrag', this._onPreDrag, this); this._map.on('viewreset', this._onViewReset, this); From 514908f81228f2cf07dd8b69b41ac4bd12fb4a50 Mon Sep 17 00:00:00 2001 From: danzel Date: Mon, 3 Sep 2012 15:41:06 +1200 Subject: [PATCH 4/9] Stop touchStart propagation in Draggable. Without this dragging marker will fire a contextmenu event on touch as the map gets a touch start/end event but none of the moves. --- src/dom/Draggable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 4f99fdb8..904e3a85 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -40,6 +40,7 @@ L.Draggable = L.Class.extend({ ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } L.DomEvent.preventDefault(e); + L.DomEvent.stopPropagation(e); if (L.Draggable._disabled) { return; } From 9783d9ba22ffd4dbfc8bb0b3f77287d41886c520 Mon Sep 17 00:00:00 2001 From: danzel Date: Mon, 8 Oct 2012 13:33:29 +1300 Subject: [PATCH 5/9] Cancel contextMenu emulation timeout if >1 touches happen (Put them down for a touch zoom and didn't move them for example) --- src/dom/Draggable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 904e3a85..9dd6d0a2 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -48,6 +48,7 @@ L.Draggable = L.Class.extend({ if (e.touches && e.touches.length > 1) { this._simulateClick = false; + clearTimeout(this._contextMenuTimeout); return; } From 3aeff55f24480e0bab251533c2d9706234ef16d8 Mon Sep 17 00:00:00 2001 From: danzel Date: Wed, 31 Oct 2012 13:05:38 +1300 Subject: [PATCH 6/9] Rename contextMenuEmulation -> longPress --- src/dom/Draggable.js | 6 +++--- src/map/handler/Map.Drag.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 9dd6d0a2..5e5fc671 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -12,10 +12,10 @@ L.Draggable = L.Class.extend({ TAP_TOLERANCE: 15 }, - initialize: function (element, dragStartTarget, contextMenuEmulation) { + initialize: function (element, dragStartTarget, longPress) { this._element = element; this._dragStartTarget = dragStartTarget || element; - this._contextMenuEmulation = contextMenuEmulation; + this._longPress = longPress; }, enable: function () { @@ -68,7 +68,7 @@ L.Draggable = L.Class.extend({ this._startPos = this._newPos = L.DomUtil.getPosition(this._element); //Touch contextmenu event emulation - if (e.touches && e.touches.length === 1 && L.Browser.touch && this._contextMenuEmulation) { + if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) { var self = this; this._contextMenuTimeout = setTimeout(function () { var dist = (self._newPos && self._newPos.distanceTo(self._startPos)) || 0; diff --git a/src/map/handler/Map.Drag.js b/src/map/handler/Map.Drag.js index 15cd4cac..a97c20a8 100644 --- a/src/map/handler/Map.Drag.js +++ b/src/map/handler/Map.Drag.js @@ -10,7 +10,7 @@ L.Map.mergeOptions({ inertiaMaxSpeed: 6000, // px/s inertiaThreshold: L.Browser.touch ? 32 : 18, // ms - touchContextMenuEmulation: true, + longPress: true, // TODO refactor, move to CRS worldCopyJump: true @@ -21,7 +21,7 @@ L.Map.Drag = L.Handler.extend({ if (!this._draggable) { var options = this._map.options; - this._draggable = new L.Draggable(this._map._mapPane, this._map._container, options.touchContextMenuEmulation); + this._draggable = new L.Draggable(this._map._mapPane, this._map._container, options.longPress); this._draggable.on({ 'dragstart': this._onDragStart, From 5f960721d9f3267e8cb4b77571238b59e449aa1b Mon Sep 17 00:00:00 2001 From: danzel Date: Wed, 31 Oct 2012 13:11:16 +1300 Subject: [PATCH 7/9] Use L.Util.bind instead of var self. --- src/dom/Draggable.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 5e5fc671..426cc2cb 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -69,16 +69,15 @@ L.Draggable = L.Class.extend({ //Touch contextmenu event emulation if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) { - var self = this; - this._contextMenuTimeout = setTimeout(function () { - var dist = (self._newPos && self._newPos.distanceTo(self._startPos)) || 0; + this._contextMenuTimeout = setTimeout(L.Util.bind(function () { + var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0; if (dist < L.Draggable.TAP_TOLERANCE) { - self._simulateClick = false; - self._onUp(); - self._simulateEvent('contextmenu', first); + this._simulateClick = false; + this._onUp(); + this._simulateEvent('contextmenu', first); } - }, 1000); + }, this), 1000); } L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this); From 081edc283e33fc0602302093a142d8443cddb132 Mon Sep 17 00:00:00 2001 From: danzel Date: Wed, 31 Oct 2012 13:25:29 +1300 Subject: [PATCH 8/9] Rename timeout to match other vars --- src/dom/Draggable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 426cc2cb..09f38608 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -48,7 +48,7 @@ L.Draggable = L.Class.extend({ if (e.touches && e.touches.length > 1) { this._simulateClick = false; - clearTimeout(this._contextMenuTimeout); + clearTimeout(this._longPressTimeout); return; } @@ -69,7 +69,7 @@ L.Draggable = L.Class.extend({ //Touch contextmenu event emulation if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) { - this._contextMenuTimeout = setTimeout(L.Util.bind(function () { + this._longPressTimeout = setTimeout(L.Util.bind(function () { var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0; if (dist < L.Draggable.TAP_TOLERANCE) { @@ -122,7 +122,7 @@ L.Draggable = L.Class.extend({ _onUp: function (e) { var simulateClickTouch; - clearTimeout(this._contextMenuTimeout); + clearTimeout(this._longPressTimeout); if (this._simulateClick && e.changedTouches) { var first = e.changedTouches[0], el = first.target, From edaa1bbcecb4a6967fe453c75c25dea195fb4305 Mon Sep 17 00:00:00 2001 From: danzel Date: Wed, 31 Oct 2012 13:26:56 +1300 Subject: [PATCH 9/9] Disable longPress contextmenu emulation on msTouch, it natively fires its own contextmenu event after a longpress is released. --- src/dom/Draggable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 09f38608..89a46f5a 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -15,7 +15,7 @@ L.Draggable = L.Class.extend({ initialize: function (element, dragStartTarget, longPress) { this._element = element; this._dragStartTarget = dragStartTarget || element; - this._longPress = longPress; + this._longPress = longPress && !L.Browser.msTouch; }, enable: function () {