diff --git a/js/jquery.mobile.vmouse.js b/js/jquery.mobile.vmouse.js index 6f11d5bf..4c3f63cf 100644 --- a/js/jquery.mobile.vmouse.js +++ b/js/jquery.mobile.vmouse.js @@ -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 );