Merge pull request #1980 from rwldrn/jquery.mobile.vmouse.js

jQuery core style guide conformance: jquery.mobile.vmouse.js
This commit is contained in:
Ghislain Seguin 2011-06-29 08:52:52 -07:00
commit 92fa98a88a

View file

@ -20,12 +20,12 @@
// The current version exposes the following virtual events to jQuery bind methods:
// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
(function($, window, document, undefined) {
(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(" "),
virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
activeDocHandlers = {},
resetTimerID = 0,
startX = 0,
@ -35,7 +35,7 @@ var dataPropertyName = "virtualMouseBindings",
blockMouseTriggers = false,
blockTouchTriggers = false,
eventCaptureSupported = $.support.eventCapture,
$document = $(document),
$document = $( document ),
nextTouchID = 1,
lastTouchID = 0;
@ -45,42 +45,45 @@ $.vmouse = {
resetTimerDuration: 1500
};
function getNativeEvent(event)
{
while (event && typeof event.originalEvent !== "undefined") {
function getNativeEvent( event ) {
while ( event && typeof event.originalEvent !== "undefined" ) {
event = event.originalEvent;
}
return event;
}
function createVirtualEvent(event, eventType)
{
var t = event.type;
function createVirtualEvent( event, eventType ) {
var t = event.type,
oe, props, ne, prop, ct, touch, i, j;
event = $.Event(event);
event.type = eventType;
var oe = event.originalEvent;
var props = $.event.props;
oe = event.originalEvent;
props = $.event.props;
// 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 ( var i = props.length, prop; i; ) {
if ( oe ) {
for ( i = props.length, prop; i; ) {
prop = props[ --i ];
event[prop] = oe[prop];
event[ prop ] = oe[ prop ];
}
}
if (t.search(/^touch/) !== -1){
var ne = getNativeEvent(oe),
t = ne.touches,
ct = ne.changedTouches,
touch = (t && t.length) ? t[0] : ((ct && ct.length) ? ct[0] : undefined);
if (touch){
for (var i = 0, len = touchEventProps.length; i < len; i++){
var prop = touchEventProps[i];
event[prop] = touch[prop];
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 ];
}
}
}
@ -88,14 +91,18 @@ function createVirtualEvent(event, eventType)
return event;
}
function getVirtualBindingFlags(element)
{
var flags = {};
while (element){
var b = $.data(element, dataPropertyName);
for (var k in b) {
if (b[k]){
flags[k] = flags.hasVirtualBinding = true;
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;
@ -103,11 +110,13 @@ function getVirtualBindingFlags(element)
return flags;
}
function getClosestElementWithVirtualBinding(element, eventType)
{
while (element){
var b = $.data(element, dataPropertyName);
if (b && (!eventType || b[eventType])) {
function getClosestElementWithVirtualBinding( element, eventType ) {
var b;
while ( element ) {
b = $.data( element, dataPropertyName );
if ( b && (!eventType || b[ eventType ]) ) {
return element;
}
element = element.parentNode;
@ -115,18 +124,15 @@ function getClosestElementWithVirtualBinding(element, eventType)
return null;
}
function enableTouchBindings()
{
function enableTouchBindings() {
blockTouchTriggers = false;
}
function disableTouchBindings()
{
function disableTouchBindings() {
blockTouchTriggers = true;
}
function enableMouseBindings()
{
function enableMouseBindings() {
lastTouchID = 0;
clickBlockList.length = 0;
blockMouseTriggers = false;
@ -136,15 +142,13 @@ function enableMouseBindings()
disableTouchBindings();
}
function disableMouseBindings()
{
function disableMouseBindings() {
// When mouse bindings are disabled, our
// touch bindings are enabled.
enableTouchBindings();
}
function startResetTimer()
{
function startResetTimer() {
clearResetTimer();
resetTimerID = setTimeout(function(){
resetTimerID = 0;
@ -152,132 +156,144 @@ function startResetTimer()
}, $.vmouse.resetTimerDuration);
}
function clearResetTimer()
{
function clearResetTimer() {
if (resetTimerID){
clearTimeout(resetTimerID);
resetTimerID = 0;
}
}
function triggerVirtualEvent(eventType, event, flags)
{
var defaultPrevented = false;
function triggerVirtualEvent( eventType, event, flags ) {
var defaultPrevented = false,
ve;
if ( ( flags && flags[ eventType ] ) ||
( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
ve = createVirtualEvent( event, eventType );
$( event.target).trigger( ve );
if ((flags && flags[eventType]) || (!flags && getClosestElementWithVirtualBinding(event.target, eventType))) {
var ve = createVirtualEvent(event, eventType);
$(event.target).trigger(ve);
defaultPrevented = ve.isDefaultPrevented();
}
return defaultPrevented;
}
function mouseEventCallback(event)
{
function mouseEventCallback( event ) {
var touchID = $.data(event.target, touchTargetPropertyName);
if (!blockMouseTriggers && (!lastTouchID || lastTouchID !== touchID)){
triggerVirtualEvent("v" + event.type, event);
if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){
triggerVirtualEvent( "v" + event.type, event );
}
}
function handleTouchStart(event)
{
var touches = getNativeEvent(event).touches;
if (touches && touches.length === 1){
var target = event.target,
flags = getVirtualBindingFlags(target);
if (flags.hasVirtualBinding){
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);
$.data( target, touchTargetPropertyName, lastTouchID );
clearResetTimer();
disableMouseBindings();
didScroll = false;
var t = getNativeEvent(event).touches[0];
var t = getNativeEvent( event ).touches[ 0 ];
startX = t.pageX;
startY = t.pageY;
triggerVirtualEvent("vmouseover", event, flags);
triggerVirtualEvent("vmousedown", event, flags);
triggerVirtualEvent( "vmouseover", event, flags );
triggerVirtualEvent( "vmousedown", event, flags );
}
}
}
function handleScroll(event)
{
if (blockTouchTriggers){
function handleScroll( event ) {
if ( blockTouchTriggers ) {
return;
}
if (!didScroll){
triggerVirtualEvent("vmousecancel", event, getVirtualBindingFlags(event.target));
if ( !didScroll ) {
triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
}
didScroll = true;
startResetTimer();
}
function handleTouchMove(event)
{
if (blockTouchTriggers){
function handleTouchMove( event ) {
if ( blockTouchTriggers ) {
return;
}
var t = getNativeEvent(event).touches[0];
var didCancel = didScroll,
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);
didScroll = didScroll ||
( Math.abs(t.pageX - startX) > moveThreshold ||
Math.abs(t.pageY - startY) > moveThreshold ),
flags = getVirtualBindingFlags( event.target );
var flags = getVirtualBindingFlags(event.target);
if (didScroll && !didCancel){
triggerVirtualEvent("vmousecancel", event, flags);
if ( didScroll && !didCancel ) {
triggerVirtualEvent( "vmousecancel", event, flags );
}
triggerVirtualEvent("vmousemove", event, flags);
triggerVirtualEvent( "vmousemove", event, flags );
startResetTimer();
}
function handleTouchEnd(event)
{
if (blockTouchTriggers){
function handleTouchEnd(event) {
if ( blockTouchTriggers ) {
return;
}
disableTouchBindings();
var flags = getVirtualBindingFlags(event.target);
triggerVirtualEvent("vmouseup", event, flags);
if (!didScroll){
if (triggerVirtualEvent("vclick", event, flags)){
var flags = getVirtualBindingFlags( event.target ),
t;
triggerVirtualEvent( "vmouseup", event, flags );
if ( !didScroll ) {
if ( triggerVirtualEvent( "vclick", event, flags ) ) {
// 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.
var t = getNativeEvent(event).changedTouches[0];
clickBlockList.push({ touchID: lastTouchID, x: t.clientX, y: t.clientY });
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);
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]){
function hasVirtualBindings( ele ) {
var bindings = $.data(ele, dataPropertyName),
k;
if ( bindings ) {
for ( k in bindings ) {
if ( bindings[ k ] ) {
return true;
}
}
@ -287,49 +303,49 @@ function hasVirtualBindings(ele)
function dummyMouseHandler(){}
function getSpecialEventObject(eventType)
{
var realType = eventType.substr(1);
function getSpecialEventObject( eventType ) {
var realType = eventType.substr( 1 );
return {
setup: function(data, namespace) {
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 ( !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;
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);
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);
$( this ).bind( realType, dummyMouseHandler );
// For now, if event capture is not supported, we rely on mouse handlers.
if (eventCaptureSupported){
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)
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
@ -339,56 +355,58 @@ function getSpecialEventObject(eventType)
// 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);
.bind( "touchmove", handleTouchMove )
.bind( "scroll", handleScroll );
}
}
},
teardown: function(data, namespace) {
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);
--activeDocHandlers[ eventType ];
if ( !activeDocHandlers[ eventType ] ) {
$document.unbind( realType, mouseEventCallback );
}
if (eventCaptureSupported){
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);
--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);
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;
if ( bindings ) {
bindings[ eventType ] = false;
}
// Unregister the dummy event handler.
$this.unbind(realType, dummyMouseHandler);
$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);
if ( !hasVirtualBindings(this) ) {
$this.removeData( dataPropertyName );
}
}
};
@ -396,21 +414,23 @@ function getSpecialEventObject(eventType)
// 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]);
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;
var target = e.target;
if (cnt) {
var x = e.clientX,
y = e.clientY,
threshold = $.vmouse.clickDistanceThreshold;
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
@ -438,13 +458,16 @@ if (eventCaptureSupported){
// 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.
var ele = target;
while (ele) {
for (var i = 0; i < cnt; i++) {
var 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){
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();
@ -457,4 +480,4 @@ if (eventCaptureSupported){
}
}, true);
}
})(jQuery, window, document);
})( jQuery, window, document );