Răsfoiți Sursa

refactor DragListener for touch. also, delay option

Adam Shaw 10 ani în urmă
părinte
comite
d2d70fa70c
4 a modificat fișierele cu 406 adăugiri și 267 ștergeri
  1. 1 0
      lumbar.json
  2. 183 0
      src/common/DragListener.autoscroll.js
  3. 197 242
      src/common/DragListener.js
  4. 25 25
      src/common/HitDragListener.js

+ 1 - 0
lumbar.json

@@ -23,6 +23,7 @@
         "src/common/Popover.js",
         "src/common/CoordCache.js",
         "src/common/DragListener.js",
+        "src/common/DragListener.autoscroll.js",
         "src/common/HitDragListener.js",
         "src/common/MouseFollower.js",
         "src/common/Grid.js",

+ 183 - 0
src/common/DragListener.autoscroll.js

@@ -0,0 +1,183 @@
+/*
+this.scrollEl is set in DragListener
+*/
+DragListener.mixin({
+
+	isAutoScroll: false,
+
+	scrollBounds: null, // { top, bottom, left, right }
+	scrollTopVel: null, // pixels per second
+	scrollLeftVel: null, // pixels per second
+	scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
+
+	// defaults
+	scrollSensitivity: 30, // pixels from edge for scrolling to start
+	scrollSpeed: 200, // pixels per second, at maximum speed
+	scrollIntervalMs: 50, // millisecond wait between scroll increment
+
+
+	initAutoScroll: function() {
+		var scrollEl = this.scrollEl;
+
+		this.isAutoScroll =
+			this.options.scroll &&
+			scrollEl &&
+			!scrollEl.is(window) &&
+			!scrollEl.is(document);
+
+		if (this.isAutoScroll) {
+			// debounce makes sure rapid calls don't happen
+			this.listenTo(scrollEl, 'scroll', debounce(this.handleDebouncedScroll, 100));
+		}
+	},
+
+
+	destroyAutoScroll: function() {
+		// remove the scroll handler if there is a scrollEl
+		if (this.isAutoScroll) {
+			this.stopListeningTo(this.scrollEl, 'scroll'); // will probably get removed by unbindHandlers too :(
+		}
+	},
+
+
+	// Computes and stores the bounding rectangle of scrollEl
+	computeScrollBounds: function() {
+		if (this.isAutoScroll) {
+			this.scrollBounds = getOuterRect(this.scrollEl);
+			// TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
+		}
+	},
+
+
+	// Called when the dragging is in progress and scrolling should be updated
+	updateAutoScroll: function(ev) {
+		var sensitivity = this.scrollSensitivity;
+		var bounds = this.scrollBounds;
+		var topCloseness, bottomCloseness;
+		var leftCloseness, rightCloseness;
+		var topVel = 0;
+		var leftVel = 0;
+
+		if (bounds) { // only scroll if scrollEl exists
+
+			// compute closeness to edges. valid range is from 0.0 - 1.0
+			topCloseness = (sensitivity - (getEvY(ev) - bounds.top)) / sensitivity;
+			bottomCloseness = (sensitivity - (bounds.bottom - getEvY(ev))) / sensitivity;
+			leftCloseness = (sensitivity - (getEvX(ev) - bounds.left)) / sensitivity;
+			rightCloseness = (sensitivity - (bounds.right - getEvX(ev))) / sensitivity;
+
+			// translate vertical closeness into velocity.
+			// mouse must be completely in bounds for velocity to happen.
+			if (topCloseness >= 0 && topCloseness <= 1) {
+				topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
+			}
+			else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
+				topVel = bottomCloseness * this.scrollSpeed;
+			}
+
+			// translate horizontal closeness into velocity
+			if (leftCloseness >= 0 && leftCloseness <= 1) {
+				leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
+			}
+			else if (rightCloseness >= 0 && rightCloseness <= 1) {
+				leftVel = rightCloseness * this.scrollSpeed;
+			}
+		}
+
+		this.setScrollVel(topVel, leftVel);
+	},
+
+
+	// Sets the speed-of-scrolling for the scrollEl
+	setScrollVel: function(topVel, leftVel) {
+
+		this.scrollTopVel = topVel;
+		this.scrollLeftVel = leftVel;
+
+		this.constrainScrollVel(); // massages into realistic values
+
+		// if there is non-zero velocity, and an animation loop hasn't already started, then START
+		if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
+			this.scrollIntervalId = setInterval(
+				proxy(this, 'scrollIntervalFunc'), // scope to `this`
+				this.scrollIntervalMs
+			);
+		}
+	},
+
+
+	// Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
+	constrainScrollVel: function() {
+		var el = this.scrollEl;
+
+		if (this.scrollTopVel < 0) { // scrolling up?
+			if (el.scrollTop() <= 0) { // already scrolled all the way up?
+				this.scrollTopVel = 0;
+			}
+		}
+		else if (this.scrollTopVel > 0) { // scrolling down?
+			if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
+				this.scrollTopVel = 0;
+			}
+		}
+
+		if (this.scrollLeftVel < 0) { // scrolling left?
+			if (el.scrollLeft() <= 0) { // already scrolled all the left?
+				this.scrollLeftVel = 0;
+			}
+		}
+		else if (this.scrollLeftVel > 0) { // scrolling right?
+			if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
+				this.scrollLeftVel = 0;
+			}
+		}
+	},
+
+
+	// This function gets called during every iteration of the scrolling animation loop
+	scrollIntervalFunc: function() {
+		var el = this.scrollEl;
+		var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by
+
+		// change the value of scrollEl's scroll
+		if (this.scrollTopVel) {
+			el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
+		}
+		if (this.scrollLeftVel) {
+			el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
+		}
+
+		this.constrainScrollVel(); // since the scroll values changed, recompute the velocities
+
+		// if scrolled all the way, which causes the vels to be zero, stop the animation loop
+		if (!this.scrollTopVel && !this.scrollLeftVel) {
+			this.endAutoScroll();
+		}
+	},
+
+
+	// Kills any existing scrolling animation loop
+	endAutoScroll: function() {
+		if (this.scrollIntervalId) {
+			clearInterval(this.scrollIntervalId);
+			this.scrollIntervalId = null;
+
+			this.handleScrollEnd();
+		}
+	},
+
+
+	// Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
+	handleDebouncedScroll: function() {
+		// recompute all coordinates, but *only* if this is *not* part of our scrolling animation
+		if (!this.scrollIntervalId) {
+			this.handleScrollEnd();
+		}
+	},
+
+
+	// Called when scrolling has stopped, whether through auto scroll, or the user scrolling
+	handleScrollEnd: function() {
+	}
+
+});

+ 197 - 242
src/common/DragListener.js

@@ -7,26 +7,22 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, {
 
 	options: null,
 
-	isListening: false,
-	isDragging: false,
+	subjectEl: null, // the element being dragged
+	subjectHref: null, // for IE8 bug-fighting behavior
 
 	// coordinates of the initial mousedown
 	originX: null,
 	originY: null,
 
-	// for IE8 bug-fighting behavior, for now
-	subjectEl: null, // the element being draged. optional
-	subjectHref: null,
-
 	scrollEl: null,
-	scrollBounds: null, // { top, bottom, left, right }
-	scrollTopVel: null, // pixels per second
-	scrollLeftVel: null, // pixels per second
-	scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
 
-	scrollSensitivity: 30, // pixels from edge for scrolling to start
-	scrollSpeed: 200, // pixels per second, at maximum speed
-	scrollIntervalMs: 50, // millisecond wait between scroll increment
+	isInteracting: false,
+	isTouch: false,
+	isDistanceSurpassed: false,
+	isDelayEnded: false,
+	isDragging: false,
+
+	delayTimeoutId: null,
 
 
 	constructor: function(options) {
@@ -36,344 +32,303 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, {
 	},
 
 
-	// Call this when the user does a mousedown. Will probably lead to startListening
-	mousedown: function(ev) {
-		if (isPrimaryMouseButton(ev)) {
+	// Interaction (high-level)
+	// -----------------------------------------------------------------------------------------------------------------
 
-			ev.preventDefault(); // prevents native selection in most browsers
 
-			this.startListening(ev);
+	startInteraction: function(ev, isTouch) {
+		if (!this.isInteracting) {
+
+			this.isInteracting = true;
+			this.isDelayEnded = false;
+			this.isDistanceSurpassed = false;
+
+			this.isTouch = isTouch || false;
+			this.originX = getEvX(ev);
+			this.originY = getEvY(ev);
+			this.scrollEl = getScrollParent($(ev.target));
+
+			this.bindHandlers();
+			this.initAutoScroll();
+
+			this.handleInteractionStart(ev);
+
+			this.processDelay(this.options.delay || 0, ev);
 
-			// start the drag immediately if there is no minimum distance for a drag start
 			if (!this.options.distance) {
-				this.startDrag(ev);
+				this.handleDistanceSurpassed(ev);
 			}
 		}
 	},
 
 
-	// Call this to start tracking mouse movements
-	startListening: function(ev) {
-		var scrollParent;
+	handleInteractionStart: function(ev) {
+		this.trigger('interactionStart', ev);
+	},
 
-		if (!this.isListening) {
 
-			// grab scroll container and attach handler
-			if (ev && this.options.scroll) {
-				scrollParent = getScrollParent($(ev.target));
-				if (!scrollParent.is(window) && !scrollParent.is(document)) {
-					this.scrollEl = scrollParent;
+	endInteraction: function(ev) {
+		if (this.isInteracting) {
+			this.endDrag(ev);
 
-					this.listenTo(
-						this.scrollEl,
-						'scroll',
-						debounce(this.scrollHandler, 100) // make sure rapid calls don't happen
-					);
-				}
+			if (this.delayTimeoutId) {
+				clearTimeout(this.delayTimeoutId);
+				this.delayTimeoutId = null;
 			}
 
-			this.listenTo($(document), {
-				mousemove: this.mousemove,
-				mouseup: this.mouseup,
-				selectstart: this.preventDefault // prevents native selection in IE<=8
-			});
-
-			if (ev) {
-				this.originX = ev.pageX;
-				this.originY = ev.pageY;
-			}
-			else {
-				// if no starting information was given, origin will be the topleft corner of the screen.
-				// if so, dx/dy in the future will be the absolute coordinates.
-				this.originX = 0;
-				this.originY = 0;
-			}
+			this.destroyAutoScroll();
+			this.unbindHandlers();
 
-			this.isListening = true;
-			this.listenStart(ev);
+			this.isInteracting = false;
+			this.handleInteractionEnd(ev);
 		}
 	},
 
 
-	// Called when drag listening has started (but a real drag has not necessarily began)
-	listenStart: function(ev) {
-		this.trigger('listenStart', ev);
+	handleInteractionEnd: function(ev) {
+		this.trigger('interactionEnd', ev);
 	},
 
 
-	// Called when the user moves the mouse
-	mousemove: function(ev) {
-		var dx = ev.pageX - this.originX;
-		var dy = ev.pageY - this.originY;
-		var minDistance;
-		var distanceSq; // current distance from the origin, squared
+	// Binding To DOM
+	// -----------------------------------------------------------------------------------------------------------------
 
-		if (!this.isDragging) { // if not already dragging...
-			// then start the drag if the minimum distance criteria is met
-			minDistance = this.options.distance || 1;
-			distanceSq = dx * dx + dy * dy;
-			if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
-				this.startDrag(ev);
-			}
+
+	bindHandlers: function() {
+
+		if (this.isTouch) {
+			this.listenTo($(document), {
+				touchmove: this.handleTouchMove,
+				touchend: this.endInteraction,
+				touchcancel: this.endInteraction
+			});
+		}
+		else {
+			this.listenTo($(document), {
+				mousemove: this.handleMouseMove,
+				mouseup: this.endInteraction
+			});
 		}
 
-		if (this.isDragging) {
-			this.drag(dx, dy, ev); // report a drag, even if this mousemove initiated the drag
+		this.listenTo($(document), {
+			selectstart: preventDefault, // don't allow selection while dragging
+			contextmenu: preventDefault // long taps would open menu on Chrome dev tools
+		});
+
+		if (this.scrollEl) {
+			this.listenTo(this.scrollEl, 'scroll', this.handleScroll);
 		}
 	},
 
 
-	// Call this to initiate a legitimate drag.
-	// This function is called internally from this class, but can also be called explicitly from outside
-	startDrag: function(ev) {
+	unbindHandlers: function() {
+		this.stopListeningTo($(document));
 
-		if (!this.isListening) { // startDrag must have manually initiated
-			this.startListening();
+		if (this.scrollEl) {
+			this.stopListeningTo(this.scrollEl);
 		}
+	},
+
+
+	// Drag (high-level)
+	// -----------------------------------------------------------------------------------------------------------------
+
+
+	// isTouch is only required if startInteraction not called
+	startDrag: function(ev, isTouch) {
+		this.startInteraction(ev, isTouch); // ensure interaction began
 
 		if (!this.isDragging) {
 			this.isDragging = true;
-			this.dragStart(ev);
+			this.handleDragStart(ev);
 		}
 	},
 
 
-	// Called when the actual drag has started (went beyond minDistance)
-	dragStart: function(ev) {
-		var subjectEl = this.subjectEl;
-
+	handleDragStart: function(ev) {
 		this.trigger('dragStart', ev);
+		this.initHrefHack();
+	},
 
-		// remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
-		if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
-			subjectEl.removeAttr('href');
+
+	handleMove: function(ev) {
+		var dx = getEvX(ev) - this.originX;
+		var dy = getEvY(ev) - this.originY;
+		var minDistance;
+		var distanceSq; // current distance from the origin, squared
+
+		if (!this.isDistanceSurpassed) {
+			minDistance = this.options.distance || 0;
+			distanceSq = dx * dx + dy * dy;
+			if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
+				this.handleDistanceSurpassed(ev);
+			}
+		}
+
+		if (this.isDragging) {
+			this.handleDrag(dx, dy, ev);
 		}
 	},
 
 
 	// Called while the mouse is being moved and when we know a legitimate drag is taking place
-	drag: function(dx, dy, ev) {
+	handleDrag: function(dx, dy, ev) {
 		this.trigger('drag', dx, dy, ev);
-		this.updateScroll(ev); // will possibly cause scrolling
+		this.updateAutoScroll(ev); // will possibly cause scrolling
 	},
 
 
-	// Called when the user does a mouseup
-	mouseup: function(ev) {
-		this.stopListening(ev);
-	},
-
-
-	// Called when the drag is over. Will not cause listening to stop however.
-	// A concluding 'cellOut' event will NOT be triggered.
-	stopDrag: function(ev) {
+	endDrag: function(ev) {
 		if (this.isDragging) {
-			this.stopScrolling();
-			this.dragStop(ev);
 			this.isDragging = false;
+			this.handleDragEnd(ev);
 		}
 	},
 
 
-	// Called when dragging has been stopped
-	dragStop: function(ev) {
-		var _this = this;
-
-		this.trigger('dragStop', ev);
-
-		// restore a mousedown'd <a>'s href (for IE8 bug)
-		setTimeout(function() { // must be outside of the click's execution
-			if (_this.subjectHref) {
-				_this.subjectEl.attr('href', _this.subjectHref);
-			}
-		}, 0);
+	handleDragEnd: function(ev) {
+		this.trigger('dragEnd', ev);
+		this.destroyHrefHack();
 	},
 
 
-	// Call this to stop listening to the user's mouse events
-	stopListening: function(ev) {
-		this.stopDrag(ev); // if there's a current drag, kill it
+	// Delay
+	// -----------------------------------------------------------------------------------------------------------------
 
-		if (this.isListening) {
-
-			// remove the scroll handler if there is a scrollEl
-			if (this.scrollEl) {
-				this.stopListeningTo(this.scrollEl, 'scroll');
-			}
 
-			// stops listening to mousemove, mouseup, selectstart
-			this.stopListeningTo($(document));
+	processDelay: function(delay, initialEv) {
+		var _this = this;
 
-			this.isListening = false;
-			this.listenStop(ev);
+		// TODO: fix this
+		//// prevents mousemove+mousedown+click for touch "click"
+		//if (FC.isTouchEnabled) {
+		//	delay = Math.max(1, delay);
+		//}
+
+		if (delay) {
+			console.log('delaying', delay);
+			this.delayTimeoutId = setTimeout(function() {
+				console.log('end delay');
+				_this.handleDelayEnd(initialEv);
+			}, delay);
+		}
+		else {
+			this.handleDelayEnd(initialEv);
 		}
 	},
 
 
-	// Called when drag listening has stopped
-	listenStop: function(ev) {
-		this.trigger('listenStop', ev);
-	},
-
+	handleDelayEnd: function(ev) {
+		this.isDelayEnded = true;
 
-	// Triggers a callback. Calls a function in the option hash of the same name.
-	// Arguments beyond the first `name` are forwarded on.
-	trigger: function(name) {
-		if (this.options[name]) {
-			this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
+		if (this.isDistanceSurpassed) {
+			this.startDrag(ev);
 		}
 	},
 
 
-	// Stops a given mouse event from doing it's native browser action. In our case, text selection.
-	preventDefault: function(ev) {
-		ev.preventDefault();
-	},
-
-
-	/* Scrolling
-	------------------------------------------------------------------------------------------------------------------*/
+	// Distance
+	// -----------------------------------------------------------------------------------------------------------------
 
 
-	// Computes and stores the bounding rectangle of scrollEl
-	computeScrollBounds: function() {
-		var el = this.scrollEl;
+	handleDistanceSurpassed: function(ev) {
+		this.isDistanceSurpassed = true;
 
-		this.scrollBounds = el ? getOuterRect(el) : null;
-			// TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
+		if (this.isDelayEnded) {
+			this.startDrag(ev);
+		}
 	},
 
 
-	// Called when the dragging is in progress and scrolling should be updated
-	updateScroll: function(ev) {
-		var sensitivity = this.scrollSensitivity;
-		var bounds = this.scrollBounds;
-		var topCloseness, bottomCloseness;
-		var leftCloseness, rightCloseness;
-		var topVel = 0;
-		var leftVel = 0;
+	// Mouse / Touch
+	// -----------------------------------------------------------------------------------------------------------------
 
-		if (bounds) { // only scroll if scrollEl exists
 
-			// compute closeness to edges. valid range is from 0.0 - 1.0
-			topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity;
-			bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity;
-			leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity;
-			rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity;
+	handleMouseDown: function(ev) {
+		if (isPrimaryMouseButton(ev)) {
+			ev.preventDefault(); // prevents native selection in most browsers
+			this.startInteraction(ev, false); // isTouch=false
+		}
+	},
 
-			// translate vertical closeness into velocity.
-			// mouse must be completely in bounds for velocity to happen.
-			if (topCloseness >= 0 && topCloseness <= 1) {
-				topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
-			}
-			else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
-				topVel = bottomCloseness * this.scrollSpeed;
-			}
 
-			// translate horizontal closeness into velocity
-			if (leftCloseness >= 0 && leftCloseness <= 1) {
-				leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
-			}
-			else if (rightCloseness >= 0 && rightCloseness <= 1) {
-				leftVel = rightCloseness * this.scrollSpeed;
-			}
+	handleTouchStart: function(ev) {
+		if (isSingleTouch(ev)) {
+			this.startInteraction(ev, true); // isTouch=true
 		}
-
-		this.setScrollVel(topVel, leftVel);
 	},
 
 
-	// Sets the speed-of-scrolling for the scrollEl
-	setScrollVel: function(topVel, leftVel) {
-
-		this.scrollTopVel = topVel;
-		this.scrollLeftVel = leftVel;
+	handleMouseMove: function(ev) {
+		this.handleMove(ev);
+	},
 
-		this.constrainScrollVel(); // massages into realistic values
 
-		// if there is non-zero velocity, and an animation loop hasn't already started, then START
-		if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
-			this.scrollIntervalId = setInterval(
-				proxy(this, 'scrollIntervalFunc'), // scope to `this`
-				this.scrollIntervalMs
-			);
+	handleTouchMove: function(ev) {
+		// prevent inertia and touchmove-scrolling while dragging
+		if (this.isDragging) {
+			ev.preventDefault();
 		}
+
+		this.handleMove(ev);
 	},
 
 
-	// Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
-	constrainScrollVel: function() {
-		var el = this.scrollEl;
+	// Scrolling (unrelated to auto-scroll)
+	// -----------------------------------------------------------------------------------------------------------------
 
-		if (this.scrollTopVel < 0) { // scrolling up?
-			if (el.scrollTop() <= 0) { // already scrolled all the way up?
-				this.scrollTopVel = 0;
-			}
-		}
-		else if (this.scrollTopVel > 0) { // scrolling down?
-			if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
-				this.scrollTopVel = 0;
-			}
-		}
 
-		if (this.scrollLeftVel < 0) { // scrolling left?
-			if (el.scrollLeft() <= 0) { // already scrolled all the left?
-				this.scrollLeftVel = 0;
-			}
-		}
-		else if (this.scrollLeftVel > 0) { // scrolling right?
-			if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
-				this.scrollLeftVel = 0;
-			}
+	handleScroll: function(ev) {
+		// if the drag is being initiated by touch, but a scroll happens before
+		// the drag-initiating delay is over, cancel the drag
+		if (this.isTouch && !this.isDragging) {
+			this.endInteraction();
 		}
 	},
 
 
-	// This function gets called during every iteration of the scrolling animation loop
-	scrollIntervalFunc: function() {
-		var el = this.scrollEl;
-		var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by
+	// <A> HREF Hack
+	// -----------------------------------------------------------------------------------------------------------------
 
-		// change the value of scrollEl's scroll
-		if (this.scrollTopVel) {
-			el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
-		}
-		if (this.scrollLeftVel) {
-			el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
-		}
 
-		this.constrainScrollVel(); // since the scroll values changed, recompute the velocities
+	initHrefHack: function() {
+		var subjectEl = this.subjectEl;
 
-		// if scrolled all the way, which causes the vels to be zero, stop the animation loop
-		if (!this.scrollTopVel && !this.scrollLeftVel) {
-			this.stopScrolling();
+		// remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
+		if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
+			subjectEl.removeAttr('href');
 		}
 	},
 
 
-	// Kills any existing scrolling animation loop
-	stopScrolling: function() {
-		if (this.scrollIntervalId) {
-			clearInterval(this.scrollIntervalId);
-			this.scrollIntervalId = null;
+	destroyHrefHack: function() {
+		var subjectEl = this.subjectEl;
+		var subjectHref = this.subjectHref;
 
-			// when all done with scrolling, recompute positions since they probably changed
-			this.scrollStop();
-		}
+		// restore a mousedown'd <a>'s href (for IE8 bug)
+		setTimeout(function() { // must be outside of the click's execution
+			if (subjectHref) {
+				subjectEl.attr('href', subjectHref);
+			}
+		}, 0);
 	},
 
 
-	// Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
-	scrollHandler: function() {
-		// recompute all coordinates, but *only* if this is *not* part of our scrolling animation
-		if (!this.scrollIntervalId) {
-			this.scrollStop();
-		}
-	},
+	// Utils
+	// -----------------------------------------------------------------------------------------------------------------
 
 
-	// Called when scrolling has stopped, whether through auto scroll, or the user scrolling
-	scrollStop: function() {
+	// Triggers a callback. Calls a function in the option hash of the same name.
+	// Arguments beyond the first `name` are forwarded on.
+	trigger: function(name) {
+		if (this.options[name]) {
+			this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
+		}
+		// makes _methods callable by event name. TODO: kill this
+		if (this['_' + name]) {
+			this['_' + name].apply(this, Array.prototype.slice.call(arguments, 1));
+		}
 	}
 
+
 });

+ 25 - 25
src/common/HitDragListener.js

@@ -25,18 +25,18 @@ var HitDragListener = DragListener.extend({
 
 	// Called when drag listening starts (but a real drag has not necessarily began).
 	// ev might be undefined if dragging was started manually.
-	listenStart: function(ev) {
+	handleInteractionStart: function(ev) {
 		var subjectEl = this.subjectEl;
 		var subjectRect;
 		var origPoint;
 		var point;
 
-		DragListener.prototype.listenStart.apply(this, arguments); // call the super-method
+		DragListener.prototype.handleInteractionStart.apply(this, arguments); // call the super-method
 
 		this.computeCoords();
 
 		if (ev) {
-			origPoint = { left: ev.pageX, top: ev.pageY };
+			origPoint = { left: getEvX(ev), top: getEvY(ev) };
 			point = origPoint;
 
 			// constrain the point to bounds of the element being dragged
@@ -72,55 +72,55 @@ var HitDragListener = DragListener.extend({
 	// Recomputes the drag-critical positions of elements
 	computeCoords: function() {
 		this.component.prepareHits();
-		this.computeScrollBounds(); // why is this here???
+		this.computeScrollBounds(); // why is this here??????
 	},
 
 
 	// Called when the actual drag has started
-	dragStart: function(ev) {
+	handleDragStart: function(ev) {
 		var hit;
 
-		DragListener.prototype.dragStart.apply(this, arguments); // call the super-method
+		DragListener.prototype.handleDragStart.apply(this, arguments); // call the super-method
 
 		// might be different from this.origHit if the min-distance is large
-		hit = this.queryHit(ev.pageX, ev.pageY);
+		hit = this.queryHit(getEvX(ev), getEvY(ev));
 
 		// report the initial hit the mouse is over
 		// especially important if no min-distance and drag starts immediately
 		if (hit) {
-			this.hitOver(hit);
+			this.handleHitOver(hit);
 		}
 	},
 
 
 	// Called when the drag moves
-	drag: function(dx, dy, ev) {
+	handleDrag: function(dx, dy, ev) {
 		var hit;
 
-		DragListener.prototype.drag.apply(this, arguments); // call the super-method
+		DragListener.prototype.handleDrag.apply(this, arguments); // call the super-method
 
-		hit = this.queryHit(ev.pageX, ev.pageY);
+		hit = this.queryHit(getEvX(ev), getEvY(ev));
 
 		if (!isHitsEqual(hit, this.hit)) { // a different hit than before?
 			if (this.hit) {
-				this.hitOut();
+				this.handleHitOut();
 			}
 			if (hit) {
-				this.hitOver(hit);
+				this.handleHitOver(hit);
 			}
 		}
 	},
 
 
 	// Called when dragging has been stopped
-	dragStop: function() {
-		this.hitDone();
-		DragListener.prototype.dragStop.apply(this, arguments); // call the super-method
+	handleDragEnd: function() {
+		this.handleHitDone();
+		DragListener.prototype.handleDragEnd.apply(this, arguments); // call the super-method
 	},
 
 
 	// Called when a the mouse has just moved over a new hit
-	hitOver: function(hit) {
+	handleHitOver: function(hit) {
 		var isOrig = isHitsEqual(hit, this.origHit);
 
 		this.hit = hit;
@@ -130,26 +130,26 @@ var HitDragListener = DragListener.extend({
 
 
 	// Called when the mouse has just moved out of a hit
-	hitOut: function() {
+	handleHitOut: function() {
 		if (this.hit) {
 			this.trigger('hitOut', this.hit);
-			this.hitDone();
+			this.handleHitDone();
 			this.hit = null;
 		}
 	},
 
 
 	// Called after a hitOut. Also called before a dragStop
-	hitDone: function() {
+	handleHitDone: function() {
 		if (this.hit) {
 			this.trigger('hitDone', this.hit);
 		}
 	},
 
 
-	// Called when drag listening has stopped
-	listenStop: function() {
-		DragListener.prototype.listenStop.apply(this, arguments); // call the super-method
+	// Called when the interaction ends, whether there was a real drag or not
+	handleInteractionEnd: function() {
+		DragListener.prototype.handleInteractionEnd.apply(this, arguments); // call the super-method
 
 		this.origHit = null;
 		this.hit = null;
@@ -159,8 +159,8 @@ var HitDragListener = DragListener.extend({
 
 
 	// Called when scrolling has stopped, whether through auto scroll, or the user scrolling
-	scrollStop: function() {
-		DragListener.prototype.scrollStop.apply(this, arguments); // call the super-method
+	handleScrollEnd: function() {
+		DragListener.prototype.handleScrollEnd.apply(this, arguments); // call the super-method
 
 		this.computeCoords(); // hits' absolute positions will be in new places. recompute
 	},