Просмотр исходного кода

kill mouseignore hacks all over. roll MouseIgnoreMixin into GlobalEmitter. fixes touch bugs

Adam Shaw 9 лет назад
Родитель
Сommit
0731990c08
6 измененных файлов с 140 добавлено и 80 удалено
  1. 0 1
      src.json
  2. 2 27
      src/common/DragListener.js
  3. 136 12
      src/common/GlobalEmitter.js
  4. 0 11
      src/common/Grid.events.js
  5. 0 27
      src/common/MouseIgnorerMixin.js
  6. 2 2
      src/common/View.js

+ 0 - 1
src.json

@@ -10,7 +10,6 @@
     "common/TaskQueue.js",
     "common/EmitterMixin.js",
     "common/ListenerMixin.js",
-    "common/MouseIgnorerMixin.js",
     "common/Popover.js",
     "common/CoordCache.js",
     "common/DragListener.js",

+ 2 - 27
src/common/DragListener.js

@@ -3,7 +3,7 @@
 ----------------------------------------------------------------------------------------------------------------------*/
 // TODO: use Emitter
 
-var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMixin, {
+var DragListener = FC.DragListener = Class.extend(ListenerMixin, {
 
 	options: null,
 	subjectEl: null,
@@ -30,8 +30,6 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
 
 
 	constructor: function(options) {
-		this.initMouseIgnoring(500); // init mixin
-
 		this.options = options || {};
 	},
 
@@ -44,7 +42,7 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
 		var isTouch = getEvIsTouch(ev);
 
 		if (ev.type === 'mousedown') {
-			if (this.isIgnoringMouse) {
+			if (GlobalEmitter.get().shouldIgnoreMouse()) {
 				return;
 			}
 			else if (!isPrimaryMouseButton(ev)) {
@@ -103,13 +101,6 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
 
 			this.isInteracting = false;
 			this.handleInteractionEnd(ev, isCancelled);
-
-			// a touchstart+touchend on the same element will result in the following addition simulated events:
-			// mouseover + mouseout + click
-			// let's ignore these bogus events
-			if (this.isTouch) {
-				this.tempIgnoreMouse();
-			}
 		}
 	},
 
@@ -125,7 +116,6 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
 
 	bindHandlers: function() {
 		var _this = this;
-		var touchStartIgnores = 1;
 
 		// some browsers (Safari in iOS 10) don't allow preventDefault on touch events that are bound after touchstart,
 		// so listen to the GlobalEmitter singleton, which is always bound, instead of the document directly.
@@ -135,22 +125,7 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
 			this.listenTo(globalEmitter, {
 				touchmove: this.handleTouchMove,
 				touchend: this.endInteraction,
-				touchcancel: this.endInteraction,
-
 				scroll: this.handleTouchScroll,
-
-				// Sometimes touchend doesn't fire
-				// (can't figure out why. touchcancel doesn't fire either. has to do with scrolling?)
-				// If another touchstart happens, we know it's bogus, so cancel the drag.
-				// touchend will continue to be broken until user does a shorttap/scroll, but this is best we can do.
-				touchstart: function(ev) {
-					if (touchStartIgnores) { // bindHandlers is called from within a touchstart,
-						touchStartIgnores--; // and we don't want this to fire immediately, so ignore.
-					}
-					else {
-						_this.endInteraction(ev, true); // isCancelled=true
-					}
-				}
 			});
 		}
 		else {

+ 136 - 12
src/common/GlobalEmitter.js

@@ -3,24 +3,33 @@
 Listens to document and window-level user-interaction events, like touch events and mouse events,
 and fires these events as-is to whoever is observing a GlobalEmitter.
 Best when used as a singleton via GlobalEmitter.get()
+
+Normalizes mouse/touch events. For examples:
+- ignores the the simulated mouse events that happen after a quick tap: mousemove+mousedown+mouseup+click
+- compensates for various buggy scenarios where a touchend does not fire
 */
+
+FC.touchMouseIgnoreWait = 500;
+
 var GlobalEmitter = Class.extend(ListenerMixin, EmitterMixin, {
 
+	isTouching: false,
+	mouseIgnoreDepth: 0,
 	handleScrollProxy: null,
 
 
 	bind: function() {
-		var _this = this;
-		var doc = $(document);
-
-		$.each([
-			'mousedown', 'mousemove', 'mouseup',
-			'touchstart', 'touchmove', 'touchcancel', 'touchend',
-			'selectstart', 'contextmenu'
-		], function(i, eventName) {
-			_this.listenTo(doc, eventName, function(ev) {
-				_this.trigger(eventName, ev);
-			});
+		this.listenTo($(document), {
+			touchstart: this.handleTouchStart,
+			touchmove: this.handleTouchMove,
+			touchcancel: this.handleTouchCancel,
+			touchend: this.handleTouchEnd,
+			mousedown: this.handleMouseDown,
+			mousemove: this.handleMouseMove,
+			mouseup: this.handleMouseUp,
+			click: this.handleClick,
+			selectstart: this.handleSelectStart,
+			contextmenu: this.handleContextMenu
 		});
 
 		// attach a handler to get called when ANY scroll action happens on the page.
@@ -33,7 +42,6 @@ var GlobalEmitter = Class.extend(ListenerMixin, EmitterMixin, {
 		);
 	},
 
-
 	unbind: function() {
 		this.stopListeningTo($(document));
 
@@ -45,8 +53,124 @@ var GlobalEmitter = Class.extend(ListenerMixin, EmitterMixin, {
 	},
 
 
+	// Touch Handlers
+	// -----------------------------------------------------------------------------------------------------------------
+
+	handleTouchStart: function(ev) {
+
+		// if a previous touch interaction never ended with a touchend, then implicitly end it,
+		// but since a new touch interaction is about to begin, don't start the mouse ignore period.
+		this.stopTouch(ev, true); // skipMouseIgnore=true
+
+		this.isTouching = true;
+		this.trigger('touchstart', ev);
+	},
+
+	handleTouchMove: function(ev) {
+		if (this.isTouching) {
+			this.trigger('touchmove', ev);
+		}
+	},
+
+	handleTouchCancel: function(ev) {
+		if (this.isTouching) {
+			this.trigger('touchcancel', ev);
+
+			// Have touchcancel fire an artificial touchend. That way, handlers won't need to listen to both.
+			// If touchend fires later, it won't have any effect b/c isTouching will be false.
+			this.stopTouch(ev);
+		}
+	},
+
+	handleTouchEnd: function(ev) {
+		this.stopTouch(ev);
+	},
+
+
+	// Mouse Handlers
+	// -----------------------------------------------------------------------------------------------------------------
+
+	handleMouseDown: function(ev) {
+		if (!this.shouldIgnoreMouse()) {
+			this.trigger('mousedown', ev);
+		}
+		else {
+			this.stopTouch(ev); // kill touch interaction if touchend neglected to fire
+		}
+	},
+
+	handleMouseMove: function(ev) {
+		if (!this.shouldIgnoreMouse()) {
+			this.trigger('mousemove', ev);
+		}
+		else {
+			this.stopTouch(ev); // kill touch interaction if touchend neglected to fire
+		}
+	},
+
+	handleMouseUp: function(ev) {
+		if (!this.shouldIgnoreMouse()) {
+			this.trigger('mouseup', ev);
+		}
+		else {
+			this.stopTouch(ev); // kill touch interaction if touchend neglected to fire
+		}
+	},
+
+	handleClick: function(ev) {
+		if (!this.shouldIgnoreMouse()) {
+			this.trigger('click', ev);
+		}
+		else {
+			this.stopTouch(ev); // kill touch interaction if touchend neglected to fire
+		}
+	},
+
+
+	// Misc Handlers
+	// -----------------------------------------------------------------------------------------------------------------
+
+	handleSelectStart: function(ev) {
+		this.trigger('selectstart', ev);
+	},
+
+	handleContextMenu: function(ev) {
+		this.trigger('contextmenu', ev);
+	},
+
 	handleScroll: function(ev) {
 		this.trigger('scroll', ev);
+	},
+
+
+	// Utils
+	// -----------------------------------------------------------------------------------------------------------------
+
+	stopTouch: function(ev, skipMouseIgnore) {
+		if (this.isTouching) {
+			this.isTouching = false;
+			this.trigger('touchend', ev);
+
+			if (!skipMouseIgnore) {
+				this.startTouchMouseIgnore();
+			}
+		}
+	},
+
+	startTouchMouseIgnore: function() {
+		var _this = this;
+		var wait = FC.touchMouseIgnoreWait;
+
+		if (wait) {
+			this.mouseIgnoreDepth++;
+			setTimeout(function() {
+				_this.mouseIgnoreDepth--;
+			}, wait);
+		}
+	},
+
+	shouldIgnoreMouse: function() {
+		return this.isTouching || Boolean(this.mouseIgnoreDepth);
 	}
 
 });

+ 0 - 11
src/common/Grid.events.js

@@ -232,7 +232,6 @@ Grid.mixin({
 	// Attaches event-element-related handlers to an arbitrary container element. leverages bubbling.
 	bindSegHandlersToEl: function(el) {
 		this.bindSegHandlerToEl(el, 'touchstart', this.handleSegTouchStart);
-		this.bindSegHandlerToEl(el, 'touchend', this.handleSegTouchEnd);
 		this.bindSegHandlerToEl(el, 'mouseenter', this.handleSegMouseover);
 		this.bindSegHandlerToEl(el, 'mouseleave', this.handleSegMouseout);
 		this.bindSegHandlerToEl(el, 'mousedown', this.handleSegMousedown);
@@ -337,16 +336,6 @@ Grid.mixin({
 				delay: isSelected ? 0 : eventLongPressDelay // do delay if not already selected
 			});
 		}
-
-		// a long tap simulates a mouseover. ignore this bogus mouseover.
-		this.tempIgnoreMouse();
-	},
-
-
-	handleSegTouchEnd: function(seg, ev) {
-		// touchstart+touchend = click, which simulates a mouseover.
-		// ignore this bogus mouseover.
-		this.tempIgnoreMouse();
 	},
 
 

+ 0 - 27
src/common/MouseIgnorerMixin.js

@@ -1,27 +0,0 @@
-
-// simple class for toggle a `isIgnoringMouse` flag on delay
-// initMouseIgnoring must first be called, with a millisecond delay setting.
-var MouseIgnorerMixin = {
-
-	isIgnoringMouse: false, // bool
-	delayUnignoreMouse: null, // method
-
-
-	initMouseIgnoring: function(delay) {
-		this.delayUnignoreMouse = debounce(proxy(this, 'unignoreMouse'), delay || 1000);
-	},
-
-
-	// temporarily ignore mouse actions on segments
-	tempIgnoreMouse: function() {
-		this.isIgnoringMouse = true;
-		this.delayUnignoreMouse();
-	},
-
-
-	// delayUnignoreMouse eventually calls this
-	unignoreMouse: function() {
-		this.isIgnoringMouse = false;
-	}
-
-};

+ 2 - 2
src/common/View.js

@@ -547,7 +547,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 
 	// Binds DOM handlers to elements that reside outside the view container, such as the document
 	bindGlobalHandlers: function() {
-		this.listenTo($(document), {
+		this.listenTo(GlobalEmitter.get(), {
 			touchstart: this.processUnselect,
 			mousedown: this.handleDocumentMousedown
 		});
@@ -556,7 +556,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
 
 	// Unbinds DOM handlers from elements that reside outside the view container
 	unbindGlobalHandlers: function() {
-		this.stopListeningTo($(document));
+		this.stopListeningTo(GlobalEmitter.get());
 	},