text: 
  // This plugin is an experiment for abstracting away the touch and mouse
  // events so that developers don't have to worry about which method of input
  // the device their document is loaded on supports.
  //
  // The idea here is to allow the developer to register listeners for the
  // basic mouse events, such as mousedown, mousemove, mouseup, and click,
  // and the plugin will take care of registering the correct listeners
  // behind the scenes to invoke the listener at the fastest possible time
  // for that device, while still retaining the order of event firing in
  // the traditional mouse environment, should multiple handlers be registered
  // on the same element for different events.
  //
  // The current version exposes the following virtual events to jQuery bind methods:
  // "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
  
  (function( $, window, document, undefined ) {
  
  var dataPropertyName = "virtualMouseBindings",
  	touchTargetPropertyName = "virtualTouchID",
  	virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
  	touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
  	mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
  	mouseEventProps = $.event.props.concat( mouseHookProps ),
  	activeDocHandlers = {},
  	resetTimerID = 0,
  	startX = 0,
  	startY = 0,
  	didScroll = false,
  	clickBlockList = [],
  	blockMouseTriggers = false,
  	blockTouchTriggers = false,
  	eventCaptureSupported = "addEventListener" in document,
  	$document = $( document ),
  	nextTouchID = 1,
  	lastTouchID = 0;
  
  $.vmouse = {
  	moveDistanceThreshold: 10,
  	clickDistanceThreshold: 10,
  	resetTimerDuration: 1500
  };
  
  function getNativeEvent( event ) {
  
  	while ( event && typeof event.originalEvent !== "undefined" ) {
  		event = event.originalEvent;
  	}
  	return event;
  }
  
  function createVirtualEvent( event, eventType ) {
  
  	var t = event.type,
  		oe, props, ne, prop, ct, touch, i, j;
  
  	event = $.Event(event);
  	event.type = eventType;
  
  	oe = event.originalEvent;
  	props = $.event.props;
  
  	// addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
  	// https://github.com/jquery/jquery-mobile/issues/3280
  	if ( t.search(/mouse/) >-1 ) {
  		props = mouseEventProps;
  	}
  
  	// copy original event properties over to the new event
  	// this would happen if we could call $.event.fix instead of $.Event
  	// but we don't have a way to force an event to be fixed multiple times
  	if ( oe ) {
  		for ( i = props.length, prop; i; ) {
  			prop = props[ --i ];
  			event[ prop ] = oe[ prop ];
  		}
  	}
  
  	// make sure that if the mouse and click virtual events are generated
  	// without a .which one is defined
  	if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){
  		event.which = 1;
  	}
  
  	if ( t.search(/^touch/) !== -1 ) {
  		ne = getNativeEvent( oe );
  		t = ne.touches;
  		ct = ne.changedTouches;
  		touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined );
  
  		if ( touch ) {
  			for ( j = 0, len = touchEventProps.length; j < len; j++){
  				prop = touchEventProps[ j ];
  				event[ prop ] = touch[ prop ];
  			}
  		}
  	}
  
  	return event;
  }
  
  function getVirtualBindingFlags( element ) {
  
  	var flags = {},
  		b, k;
  
  	while ( element ) {
  
  		b = $.data( element, dataPropertyName );
  
  		for (  k in b ) {
  			if ( b[ k ] ) {
  				flags[ k ] = flags.hasVirtualBinding = true;
  			}
  		}
  		element = element.parentNode;
  	}
  	return flags;
  }
  
  function getClosestElementWithVirtualBinding( element, eventType ) {
  	var b;
  	while ( element ) {
  
  		b = $.data( element, dataPropertyName );
  
  		if ( b && ( !eventType || b[ eventType ] ) ) {
  			return element;
  		}
  		element = element.parentNode;
  	}
  	return null;
  }
  
  function enableTouchBindings() {
  	blockTouchTriggers = false;
  }
  
  function disableTouchBindings() {
  	blockTouchTriggers = true;
  }
  
  function enableMouseBindings() {
  	lastTouchID = 0;
  	clickBlockList.length = 0;
  	blockMouseTriggers = false;
  
  	// When mouse bindings are enabled, our
  	// touch bindings are disabled.
  	disableTouchBindings();
  }
  
  function disableMouseBindings() {
  	// When mouse bindings are disabled, our
  	// touch bindings are enabled.
  	enableTouchBindings();
  }
  
  function startResetTimer() {
  	clearResetTimer();
  	resetTimerID = setTimeout(function(){
  		resetTimerID = 0;
  		enableMouseBindings();
  	}, $.vmouse.resetTimerDuration );
  }
  
  function clearResetTimer() {
  	if ( resetTimerID ){
  		clearTimeout( resetTimerID );
  		resetTimerID = 0;
  	}
  }
  
  function triggerVirtualEvent( eventType, event, flags ) {
  	var ve;
  
  	if ( ( flags && flags[ eventType ] ) ||
  				( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
  
  		ve = createVirtualEvent( event, eventType );
  
  		$( event.target).trigger( ve );
  	}
  
  	return ve;
  }
  
  function mouseEventCallback( event ) {
  	var touchID = $.data(event.target, touchTargetPropertyName);
  
  	if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){
  		var ve = triggerVirtualEvent( "v" + event.type, event );
  		if ( ve ) {
  			if ( ve.isDefaultPrevented() ) {
  				event.preventDefault();
  			}
  			if ( ve.isPropagationStopped() ) {
  				event.stopPropagation();
  			}
  			if ( ve.isImmediatePropagationStopped() ) {
  				event.stopImmediatePropagation();
  			}
  		}
  	}
  }
  
  function handleTouchStart( event ) {
  
  	var touches = getNativeEvent( event ).touches,
  		target, flags;
  
  	if ( touches && touches.length === 1 ) {
  
  		target = event.target;
  		flags = getVirtualBindingFlags( target );
  
  		if ( flags.hasVirtualBinding ) {
  
  			lastTouchID = nextTouchID++;
  			$.data( target, touchTargetPropertyName, lastTouchID );
  
  			clearResetTimer();
  
  			disableMouseBindings();
  			didScroll = false;
  
  			var t = getNativeEvent( event ).touches[ 0 ];
  			startX = t.pageX;
  			startY = t.pageY;
  
  			triggerVirtualEvent( "vmouseover", event, flags );
  			triggerVirtualEvent( "vmousedown", event, flags );
  		}
  	}
  }
  
  function handleScroll( event ) {
  	if ( blockTouchTriggers ) {
  		return;
  	}
  
  	if ( !didScroll ) {
  		triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
  	}
  
  	didScroll = true;
  	startResetTimer();
  }
  
  function handleTouchMove( event ) {
  	if ( blockTouchTriggers ) {
  		return;
  	}
  
  	var t = getNativeEvent( event ).touches[ 0 ],
  		didCancel = didScroll,
  		moveThreshold = $.vmouse.moveDistanceThreshold;
  		didScroll = didScroll ||
  			( Math.abs(t.pageX - startX) > moveThreshold ||
  				Math.abs(t.pageY - startY) > moveThreshold ),
  		flags = getVirtualBindingFlags( event.target );
  
  	if ( didScroll && !didCancel ) {
  		triggerVirtualEvent( "vmousecancel", event, flags );
  	}
  
  	triggerVirtualEvent( "vmousemove", event, flags );
  	startResetTimer();
  }
  
  function handleTouchEnd( event ) {
  	if ( blockTouchTriggers ) {
  		return;
  	}
  
  	disableTouchBindings();
  
  	var flags = getVirtualBindingFlags( event.target ),
  		t;
  	triggerVirtualEvent( "vmouseup", event, flags );
  
  	if ( !didScroll ) {
  		var ve = triggerVirtualEvent( "vclick", event, flags );
  		if ( ve && ve.isDefaultPrevented() ) {
  			// The target of the mouse events that follow the touchend
  			// event don't necessarily match the target used during the
  			// touch. This means we need to rely on coordinates for blocking
  			// any click that is generated.
  			t = getNativeEvent( event ).changedTouches[ 0 ];
  			clickBlockList.push({
  				touchID: lastTouchID,
  				x: t.clientX,
  				y: t.clientY
  			});
  
  			// Prevent any mouse events that follow from triggering
  			// virtual event notifications.
  			blockMouseTriggers = true;
  		}
  	}
  	triggerVirtualEvent( "vmouseout", event, flags);
  	didScroll = false;
  
  	startResetTimer();
  }
  
  function hasVirtualBindings( ele ) {
  	var bindings = $.data( ele, dataPropertyName ),
  		k;
  
  	if ( bindings ) {
  		for ( k in bindings ) {
  			if ( bindings[ k ] ) {
  				return true;
  			}
  		}
  	}
  	return false;
  }
  
  function dummyMouseHandler(){}
  
  function getSpecialEventObject( eventType ) {
  	var realType = eventType.substr( 1 );
  
  	return {
  		setup: function( data, namespace ) {
  			// If this is the first virtual mouse binding for this element,
  			// add a bindings object to its data.
  
  			if ( !hasVirtualBindings( this ) ) {
  				$.data( this, dataPropertyName, {});
  			}
  
  			// If setup is called, we know it is the first binding for this
  			// eventType, so initialize the count for the eventType to zero.
  			var bindings = $.data( this, dataPropertyName );
  			bindings[ eventType ] = true;
  
  			// If this is the first virtual mouse event for this type,
  			// register a global handler on the document.
  
  			activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;
  
  			if ( activeDocHandlers[ eventType ] === 1 ) {
  				$document.bind( realType, mouseEventCallback );
  			}
  
  			// Some browsers, like Opera Mini, won't dispatch mouse/click events
  			// for elements unless they actually have handlers registered on them.
  			// To get around this, we register dummy handlers on the elements.
  
  			$( this ).bind( realType, dummyMouseHandler );
  
  			// For now, if event capture is not supported, we rely on mouse handlers.
  			if ( eventCaptureSupported ) {
  				// If this is the first virtual mouse binding for the document,
  				// register our touchstart handler on the document.
  
  				activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
  
  				if (activeDocHandlers[ "touchstart" ] === 1) {
  					$document.bind( "touchstart", handleTouchStart )
  						.bind( "touchend", handleTouchEnd )
  
  						// On touch platforms, touching the screen and then dragging your finger
  						// causes the window content to scroll after some distance threshold is
  						// exceeded. On these platforms, a scroll prevents a click event from being
  						// dispatched, and on some platforms, even the touchend is suppressed. To
  						// mimic the suppression of the click event, we need to watch for a scroll
  						// event. Unfortunately, some platforms like iOS don't dispatch scroll
  						// events until *AFTER* the user lifts their finger (touchend). This means
  						// we need to watch both scroll and touchmove events to figure out whether
  						// or not a scroll happenens before the touchend event is fired.
  
  						.bind( "touchmove", handleTouchMove )
  						.bind( "scroll", handleScroll );
  				}
  			}
  		},
  
  		teardown: function( data, namespace ) {
  			// If this is the last virtual binding for this eventType,
  			// remove its global handler from the document.
  
  			--activeDocHandlers[ eventType ];
  
  			if ( !activeDocHandlers[ eventType ] ) {
  				$document.unbind( realType, mouseEventCallback );
  			}
  
  			if ( eventCaptureSupported ) {
  				// If this is the last virtual mouse binding in existence,
  				// remove our document touchstart listener.
  
  				--activeDocHandlers[ "touchstart" ];
  
  				if ( !activeDocHandlers[ "touchstart" ] ) {
  					$document.unbind( "touchstart", handleTouchStart )
  						.unbind( "touchmove", handleTouchMove )
  						.unbind( "touchend", handleTouchEnd )
  						.unbind( "scroll", handleScroll );
  				}
  			}
  
  			var $this = $( this ),
  				bindings = $.data( this, dataPropertyName );
  
  			// teardown may be called when an element was
  			// removed from the DOM. If this is the case,
  			// jQuery core may have already stripped the element
  			// of any data bindings so we need to check it before
  			// using it.
  			if ( bindings ) {
  				bindings[ eventType ] = false;
  			}
  
  			// Unregister the dummy event handler.
  
  			$this.unbind( realType, dummyMouseHandler );
  
  			// If this is the last virtual mouse binding on the
  			// element, remove the binding data from the element.
  
  			if ( !hasVirtualBindings( this ) ) {
  				$this.removeData( dataPropertyName );
  			}
  		}
  	};
  }
  
  // Expose our custom events to the jQuery bind/unbind mechanism.
  
  for ( var i = 0; i < virtualEventNames.length; i++ ){
  	$.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
  }
  
  // Add a capture click handler to block clicks.
  // Note that we require event capture support for this so if the device
  // doesn't support it, we punt for now and rely solely on mouse events.
  if ( eventCaptureSupported ) {
  	document.addEventListener( "click", function( e ){
  		var cnt = clickBlockList.length,
  			target = e.target,
  			x, y, ele, i, o, touchID;
  
  		if ( cnt ) {
  			x = e.clientX;
  			y = e.clientY;
  			threshold = $.vmouse.clickDistanceThreshold;
  
  			// The idea here is to run through the clickBlockList to see if
  			// the current click event is in the proximity of one of our
  			// vclick events that had preventDefault() called on it. If we find
  			// one, then we block the click.
  			//
  			// Why do we have to rely on proximity?
  			//
  			// Because the target of the touch event that triggered the vclick
  			// can be different from the target of the click event synthesized
  			// by the browser. The target of a mouse/click event that is syntehsized
  			// from a touch event seems to be implementation specific. For example,
  			// some browsers will fire mouse/click events for a link that is near
  			// a touch event, even though the target of the touchstart/touchend event
  			// says the user touched outside the link. Also, it seems that with most
  			// browsers, the target of the mouse/click event is not calculated until the
  			// time it is dispatched, so if you replace an element that you touched
  			// with another element, the target of the mouse/click will be the new
  			// element underneath that point.
  			//
  			// Aside from proximity, we also check to see if the target and any
  			// of its ancestors were the ones that blocked a click. This is necessary
  			// because of the strange mouse/click target calculation done in the
  			// Android 2.1 browser, where if you click on an element, and there is a
  			// mouse/click handler on one of its ancestors, the target will be the
  			// innermost child of the touched element, even if that child is no where
  			// near the point of touch.
  
  			ele = target;
  
  			while ( ele ) {
  				for ( i = 0; i < cnt; i++ ) {
  					o = clickBlockList[ i ];
  					touchID = 0;
  
  					if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
  								$.data( ele, touchTargetPropertyName ) === o.touchID ) {
  						// XXX: We may want to consider removing matches from the block list
  						//      instead of waiting for the reset timer to fire.
  						e.preventDefault();
  						e.stopPropagation();
  						return;
  					}
  				}
  				ele = ele.parentNode;
  			}
  		}
  	}, true);
  }
  })( jQuery, window, document );
  