jquery.ui.touch.js 6.66 KB
/**
* jQuery.UI.iPad plugin
* Copyright (c) 2010 Stephen von Takach
* licensed under MIT.
* Date: 27/8/2010
*
* Project Home:
* http://code.google.com/p/jquery-ui-for-ipad-and-iphone/
*
* Modified: 19/01/2012
* Organized as a proper plugin and added addTouch()
*/
(function ($) {
	var lastTap = null,				// Holds last tapped element (so we can compare for double tap)
		tapValid = false,			// Are we still in the .6 second window where a double tap can occur
		tapTimeout = null,			// The timeout reference
		rightClickPending = false,	// Is a right click still feasible
		rightClickEvent = null,		// the original event
		holdTimeout = null,			// timeout reference
		cancelMouseUp = false,		// prevents a click from occurring as we want the context menu
		currentDOMElement = null;	// the last DOM element that was seen during a drag.

	function cancelTap() {
		tapValid = false;
	}
	
	function cancelHold() {
		if (rightClickPending) {
			window.clearTimeout(holdTimeout);
			rightClickPending = false;
			rightClickEvent = null;
		}
	}
	
	function startHold(event) {
		if (rightClickPending) {
			return;
		}
		rightClickPending = true; // We could be performing a right click
		rightClickEvent = (event.changedTouches)[0];
		holdTimeout = window.setTimeout(doRightClick, 800);
	}

	function doRightClick() {
		rightClickPending = false;
		// We need to mouse up (as we were down)
		var first = rightClickEvent,
			simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent("mouseup", true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, null);
		first.target.dispatchEvent(simulatedEvent);

		// Emulate a right click
		simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent("mousedown", true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 2, null);
		first.target.dispatchEvent(simulatedEvent);

		// Show a context menu
		simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent("contextmenu", true, true, window, 1, first.screenX + 50, first.screenY + 5, first.clientX + 50, first.clientY + 5, false, false, false, false, 2, null);
		first.target.dispatchEvent(simulatedEvent);

		simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent("mouseup", true, true, window, 1, first.screenX + 50, first.screenY + 5, first.clientX + 50, first.clientY + 5, false, false, false, false, 2, null);
		first.target.dispatchEvent(simulatedEvent);

		cancelMouseUp = true;
		rightClickEvent = null; // Release memory
	}


	// mouse over event then mouse down
	function iPadTouchStart(event) {
		var touches = event.changedTouches,
			first = touches[0],
			type = "mouseover",
			simulatedEvent = document.createEvent("MouseEvent");

		// Mouse over first - I have live events attached on mouse over
		simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, null);
		first.target.dispatchEvent(simulatedEvent);

		type = "mousedown";
		simulatedEvent = document.createEvent("MouseEvent");

		simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, null);
		first.target.dispatchEvent(simulatedEvent);

		if (!tapValid) {
			lastTap = first.target;
			tapValid = true;
			tapTimeout = window.setTimeout(cancelTap, 600);
			startHold(event);
		} else {
			window.clearTimeout(tapTimeout);
			// If a double tap is still a possibility and the elements are the same then perform a double click
			if (first.target == lastTap) {
				lastTap = null;
				tapValid = false;

				type = "click";
				simulatedEvent = document.createEvent("MouseEvent");

				simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, null);
				first.target.dispatchEvent(simulatedEvent);

				type = "dblclick";
				simulatedEvent = document.createEvent("MouseEvent");

				simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, null);
				first.target.dispatchEvent(simulatedEvent);
			} else {
				lastTap = first.target;
				tapValid = true;
				tapTimeout = window.setTimeout(cancelTap, 600);
				startHold(event);
			}
		}
	}

	function getDOMElementFromEvent(event) {
		if (event.targetTouches && event.targetTouches[0]) {
			return document.elementFromPoint(event.targetTouches[0].pageX - window.pageXOffset, event.targetTouches[0].pageY - window.pageYOffset);
		}
		return null;
	}

	function iPadTouchHandler(event) {
		var type = "",
			button = 0; /*left*/
		if (event.touches.length > 1) {
			return;
		}
		switch (event.type) {
		case "touchstart":
			if ($(event.changedTouches[0].target).is("select")) {
				return;
			}
			iPadTouchStart(event); /*We need to trigger two events here to support one touch drag and drop*/
			event.preventDefault();
			currentDOMElement = getDOMElementFromEvent(event);
			return false;

		case "touchmove":
			cancelHold();
			type = "mousemove";
			event.preventDefault();

			currentDOMElement = getDOMElementFromEvent(event);
			break;

		case "touchend":
			if (cancelMouseUp) {
				cancelMouseUp = false;
				event.preventDefault();
				return false;
			}
			cancelHold();
			type = "mouseup";
			break;

		default:
			return;
		}
		var touches = event.changedTouches,
			first = touches[0],
			simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY,false, false, false, false, button, null);
		currentDOMElement.dispatchEvent(simulatedEvent);
		if (type == "mouseup" && tapValid && first.target == lastTap) {	// This actually emulates the ipad's default behavior (which we prevented)
			simulatedEvent = document.createEvent("MouseEvent");		// This check avoids click being emulated on a double tap
			simulatedEvent.initMouseEvent("click", true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY,false, false, false, false, button, null);
			currentDOMElement.dispatchEvent(simulatedEvent);
		}
	}
	
	$.extend($.support, {
		touch: "ontouchend" in document
	});

	$.fn.addTouch = function () {
		if ($.support.touch) {
			this.each(function (i, el) {
				el.addEventListener("touchstart", iPadTouchHandler, false);
				el.addEventListener("touchmove", iPadTouchHandler, false);
				el.addEventListener("touchend", iPadTouchHandler, false);
				el.addEventListener("touchcancel", iPadTouchHandler, false);
			});
		}
		return this;
	};

})(jQuery);