1// This is a combination of two modified files from jQuery Mobile, 2// jquery.mobile.vmouse.js and jquery.mobile.event.js 3// They were modified to only provide the touch event shortcuts, and 4// avoid the rest of the jQuery Mobile framework. 5// The normal jQuery Mobile license applies. http://jquery.org/license 6// 7// This plugin is an experiment for abstracting away the touch and mouse 8// events so that developers don't have to worry about which method of input 9// the device their document is loaded on supports. 10// 11// The idea here is to allow the developer to register listeners for the 12// basic mouse events, such as mousedown, mousemove, mouseup, and click, 13// and the plugin will take care of registering the correct listeners 14// behind the scenes to invoke the listener at the fastest possible time 15// for that device, while still retaining the order of event firing in 16// the traditional mouse environment, should multiple handlers be registered 17// on the same element for different events. 18// 19// The current version exposes the following virtual events to jQuery bind methods: 20// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" 21 22(function($, window, document, undefined ) { 23$.attrFn = $.attrFn || {}; 24var dataPropertyName = "virtualMouseBindings", 25 touchTargetPropertyName = "virtualTouchID", 26 virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), 27 touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), 28 mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [], 29 mouseEventProps = $.event.props.concat( mouseHookProps ), 30 activeDocHandlers = {}, 31 resetTimerID = 0, 32 startX = 0, 33 startY = 0, 34 didScroll = false, 35 clickBlockList = [], 36 blockMouseTriggers = false, 37 blockTouchTriggers = false, 38 eventCaptureSupported = "addEventListener" in document, 39 $document = $( document ), 40 nextTouchID = 1, 41 lastTouchID = 0; 42 43$.vmouse = { 44 moveDistanceThreshold: 10, 45 clickDistanceThreshold: 10, 46 resetTimerDuration: 1500 47}; 48 49function getNativeEvent( event ) { 50 51 while ( event && typeof event.originalEvent !== "undefined" ) { 52 event = event.originalEvent; 53 } 54 return event; 55} 56 57function createVirtualEvent( event, eventType ) { 58 59 var t = event.type, 60 oe, props, ne, prop, ct, touch, i, j; 61 62 event = $.Event(event); 63 event.type = eventType; 64 65 oe = event.originalEvent; 66 props = $.event.props; 67 68 // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280 69 // https://github.com/jquery/jquery-mobile/issues/3280 70 if ( t.search( /^(mouse|click)/ ) > -1 ) { 71 props = mouseEventProps; 72 } 73 74 // copy original event properties over to the new event 75 // this would happen if we could call $.event.fix instead of $.Event 76 // but we don't have a way to force an event to be fixed multiple times 77 if ( oe ) { 78 for ( i = props.length, prop; i; ) { 79 prop = props[ --i ]; 80 event[ prop ] = oe[ prop ]; 81 } 82 } 83 84 // make sure that if the mouse and click virtual events are generated 85 // without a .which one is defined 86 if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){ 87 event.which = 1; 88 } 89 90 if ( t.search(/^touch/) !== -1 ) { 91 ne = getNativeEvent( oe ); 92 t = ne.touches; 93 ct = ne.changedTouches; 94 touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined ); 95 96 if ( touch ) { 97 for ( j = 0, len = touchEventProps.length; j < len; j++){ 98 prop = touchEventProps[ j ]; 99 event[ prop ] = touch[ prop ]; 100 } 101 } 102 } 103 104 return event; 105} 106 107function getVirtualBindingFlags( element ) { 108 109 var flags = {}, 110 b, k; 111 112 while ( element ) { 113 114 b = $.data( element, dataPropertyName ); 115 116 for ( k in b ) { 117 if ( b[ k ] ) { 118 flags[ k ] = flags.hasVirtualBinding = true; 119 } 120 } 121 element = element.parentNode; 122 } 123 return flags; 124} 125 126function getClosestElementWithVirtualBinding( element, eventType ) { 127 var b; 128 while ( element ) { 129 130 b = $.data( element, dataPropertyName ); 131 132 if ( b && ( !eventType || b[ eventType ] ) ) { 133 return element; 134 } 135 element = element.parentNode; 136 } 137 return null; 138} 139 140function enableTouchBindings() { 141 blockTouchTriggers = false; 142} 143 144function disableTouchBindings() { 145 blockTouchTriggers = true; 146} 147 148function enableMouseBindings() { 149 lastTouchID = 0; 150 clickBlockList.length = 0; 151 blockMouseTriggers = false; 152 153 // When mouse bindings are enabled, our 154 // touch bindings are disabled. 155 disableTouchBindings(); 156} 157 158function disableMouseBindings() { 159 // When mouse bindings are disabled, our 160 // touch bindings are enabled. 161 enableTouchBindings(); 162} 163 164function startResetTimer() { 165 clearResetTimer(); 166 resetTimerID = setTimeout(function(){ 167 resetTimerID = 0; 168 enableMouseBindings(); 169 }, $.vmouse.resetTimerDuration ); 170} 171 172function clearResetTimer() { 173 if ( resetTimerID ){ 174 clearTimeout( resetTimerID ); 175 resetTimerID = 0; 176 } 177} 178 179function triggerVirtualEvent( eventType, event, flags ) { 180 var ve; 181 182 if ( ( flags && flags[ eventType ] ) || 183 ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { 184
185 ve = createVirtualEvent( event, eventType ); 186 187 $( event.target).trigger( ve ); 188 } 189 190 return ve; 191} 192 193function mouseEventCallback( event ) { 194 var touchID = $.data(event.target, touchTargetPropertyName); 195 196 if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){ 197 var ve = triggerVirtualEvent( "v" + event.type, event ); 198 if ( ve ) { 199 if ( ve.isDefaultPrevented() ) { 200 event.preventDefault(); 201 } 202 if ( ve.isPropagationStopped() ) { 203 event.stopPropagation(); 204 } 205 if ( ve.isImmediatePropagationStopped() ) { 206 event.stopImmediatePropagation(); 207 } 208 } 209 } 210} 211 212function handleTouchStart( event ) { 213 214 var touches = getNativeEvent( event ).touches, 215 target, flags; 216 217 if ( touches && touches.length === 1 ) { 218 219 target = event.target; 220 flags = getVirtualBindingFlags( target ); 221 222 if ( flags.hasVirtualBinding ) { 223 224 lastTouchID = nextTouchID++; 225 $.data( target, touchTargetPropertyName, lastTouchID ); 226 227 clearResetTimer(); 228 229 disableMouseBindings(); 230 didScroll = false; 231 232 var t = getNativeEvent( event ).touches[ 0 ]; 233 startX = t.pageX; 234 startY = t.pageY; 235 236 triggerVirtualEvent( "vmouseover", event, flags ); 237 triggerVirtualEvent( "vmousedown", event, flags ); 238 } 239 } 240} 241 242function handleScroll( event ) { 243 if ( blockTouchTriggers ) { 244 return; 245 } 246 247 if ( !didScroll ) { 248 triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); 249 } 250 251 didScroll = true; 252 startResetTimer(); 253} 254 255function handleTouchMove( event ) { 256 if ( blockTouchTriggers ) { 257 return; 258 } 259 260 var t = getNativeEvent( event ).touches[ 0 ], 261 didCancel = didScroll, 262 moveThreshold = $.vmouse.moveDistanceThreshold; 263 didScroll = didScroll || 264 ( Math.abs(t.pageX - startX) > moveThreshold || 265 Math.abs(t.pageY - startY) > moveThreshold ), 266 flags = getVirtualBindingFlags( event.target ); 267 268 if ( didScroll && !didCancel ) { 269 triggerVirtualEvent( "vmousecancel", event, flags ); 270 } 271 272 triggerVirtualEvent( "vmousemove", event, flags ); 273 startResetTimer(); 274} 275 276function handleTouchEnd( event ) { 277 if ( blockTouchTriggers ) { 278 return; 279 } 280 281 disableTouchBindings(); 282 283 var flags = getVirtualBindingFlags( event.target ), t; 284 triggerVirtualEvent( "vmouseup", event, flags ); 285 286 if ( !didScroll ) { 287 var ve = triggerVirtualEvent( "vclick", event, flags ); 288 if ( ve && ve.isDefaultPrevented() ) { 289 // The target of the mouse events that follow the touchend 290 // event don't necessarily match the target used during the 291 // touch. This means we need to rely on coordinates for blocking 292 // any click that is generated. 293 t = getNativeEvent( event ).changedTouches[ 0 ]; 294 clickBlockList.push({ 295 touchID: lastTouchID, 296 x: t.clientX, 297 y: t.clientY 298 }); 299 300 // Prevent any mouse events that follow from triggering 301 // virtual event notifications. 302 blockMouseTriggers = true; 303 } 304 } 305 triggerVirtualEvent( "vmouseout", event, flags); 306 didScroll = false; 307 308 startResetTimer(); 309} 310 311function hasVirtualBindings( ele ) { 312 var bindings = $.data( ele, dataPropertyName ), 313 k; 314 315 if ( bindings ) { 316 for ( k in bindings ) { 317 if ( bindings[ k ] ) { 318 return true; 319 } 320 } 321 } 322 return false; 323} 324 325function dummyMouseHandler(){}; 326 327function getSpecialEventObject( eventType ) { 328 var realType = eventType.substr( 1 ); 329 330 return { 331 setup: function( data, namespace ) { 332 // If this is the first virtual mouse binding for this element, 333 // add a bindings object to its data. 334 335 if ( !hasVirtualBindings( this ) ) { 336 $.data( this, dataPropertyName, {}); 337 } 338 339 // If setup is called, we know it is the first binding for this 340 // eventType, so initialize the count for the eventType to zero. 341 var bindings = $.data( this, dataPropertyName ); 342 bindings[ eventType ] = true; 343 344 // If this is the first virtual mouse event for this type, 345 // register a global handler on the document. 346 347 activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; 348 349 if ( activeDocHandlers[ eventType ] === 1 ) { 350 $document.bind( realType, mouseEventCallback ); 351 } 352 353 // Some browsers, like Opera Mini, won't dispatch mouse/click events 354 // for elements unless they actually have handlers registered on them. 355 // To get around this, we register dummy handlers on the elements. 356 357 $(this).bind(realType, dummyMouseHandler); 358 359 // For now, if event capture is not supported, we rely on mouse handlers. 360 if ( eventCaptureSupported ) { 361 // If this is the first virtual mouse binding for the document, 362 // register our touchstart handler on the document. 363 364 activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; 365 366 if (activeDocHandlers[ "touchstart" ] === 1) { 367 $document.bind( "touchstart", handleTouchStart ) 368 .bind( "touchend", handleTouchEnd ) 369 370 // On touch platforms, touching the screen and then dragging your finger 371 // causes the window content to scroll after some distance threshold is 372 // exceeded. On these platforms, a scroll prevents a click event from being 373 // dispatched, and on some platforms, even the touchend is suppressed. To 374 // mimic the suppression of the click event, we need to watch for a scroll 375 // event. Unfortunately, some platforms like iOS don't dispatch scroll
376 // events until *AFTER* the user lifts their finger (touchend). This means 377 // we need to watch both scroll and touchmove events to figure out whether 378 // or not a scroll happenens before the touchend event is fired. 379 380 .bind( "touchmove", handleTouchMove ) 381 .bind( "scroll", handleScroll ); 382 } 383 } 384 }, 385 386 teardown: function( data, namespace ) { 387 // If this is the last virtual binding for this eventType, 388 // remove its global handler from the document. 389 390 --activeDocHandlers[ eventType ]; 391 392 if ( !activeDocHandlers[ eventType ] ) { 393 $document.unbind( realType, mouseEventCallback ); 394 } 395 396 if ( eventCaptureSupported ) { 397 // If this is the last virtual mouse binding in existence, 398 // remove our document touchstart listener. 399 400 --activeDocHandlers[ "touchstart" ]; 401 402 if ( !activeDocHandlers[ "touchstart" ] ) { 403 $document.unbind( "touchstart", handleTouchStart ) 404 .unbind( "touchmove", handleTouchMove ) 405 .unbind( "touchend", handleTouchEnd ) 406 .unbind( "scroll", handleScroll ); 407 } 408 } 409 410 var $this = $( this ), 411 bindings = $.data( this, dataPropertyName ); 412 413 // teardown may be called when an element was 414 // removed from the DOM. If this is the case, 415 // jQuery core may have already stripped the element 416 // of any data bindings so we need to check it before 417 // using it. 418 if ( bindings ) { 419 bindings[ eventType ] = false; 420 } 421 422 // Unregister the dummy event handler. 423 424 $this.unbind( realType, dummyMouseHandler ); 425 426 // If this is the last virtual mouse binding on the 427 // element, remove the binding data from the element. 428 429 if ( !hasVirtualBindings( this ) ) { 430 $this.removeData( dataPropertyName ); 431 } 432 } 433 }; 434} 435 436// Expose our custom events to the jQuery bind/unbind mechanism. 437 438for ( var i = 0; i < virtualEventNames.length; i++ ){ 439 $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); 440} 441 442// Add a capture click handler to block clicks. 443// Note that we require event capture support for this so if the device 444// doesn't support it, we punt for now and rely solely on mouse events. 445if (eventCaptureSupported) { 446 document.addEventListener( "click", function( e ){ 447 var cnt = clickBlockList.length, 448 target = e.target, 449 x, y, ele, i, o, touchID; 450 451 if ( cnt ) { 452 x = e.clientX; 453 y = e.clientY; 454 threshold = $.vmouse.clickDistanceThreshold; 455 456 // The idea here is to run through the clickBlockList to see if 457 // the current click event is in the proximity of one of our 458 // vclick events that had preventDefault() called on it. If we find 459 // one, then we block the click. 460 // 461 // Why do we have to rely on proximity? 462 // 463 // Because the target of the touch event that triggered the vclick 464 // can be different from the target of the click event synthesized 465 // by the browser. The target of a mouse/click event that is syntehsized 466 // from a touch event seems to be implementation specific. For example, 467 // some browsers will fire mouse/click events for a link that is near 468 // a touch event, even though the target of the touchstart/touchend event 469 // says the user touched outside the link. Also, it seems that with most 470 // browsers, the target of the mouse/click event is not calculated until the 471 // time it is dispatched, so if you replace an element that you touched 472 // with another element, the target of the mouse/click will be the new 473 // element underneath that point. 474 // 475 // Aside from proximity, we also check to see if the target and any 476 // of its ancestors were the ones that blocked a click. This is necessary 477 // because of the strange mouse/click target calculation done in the 478 // Android 2.1 browser, where if you click on an element, and there is a 479 // mouse/click handler on one of its ancestors, the target will be the 480 // innermost child of the touched element, even if that child is no where 481 // near the point of touch. 482 483 ele = target; 484 485 while (ele) { 486 for ( i = 0; i < cnt; i++ ) { 487 o = clickBlockList[ i ]; 488 touchID = 0; 489 490 if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) || 491 $.data( ele, touchTargetPropertyName ) === o.touchID ) { 492 // XXX: We may want to consider removing matches from the block list 493 // instead of waiting for the reset timer to fire. 494 e.preventDefault(); 495 e.stopPropagation(); 496 return; 497 } 498 } 499 ele = ele.parentNode; 500 } 501 } 502 }, true); 503} 504})( jQuery, window, document ); 505 506 507(function( $, window, undefined ) {// add new event shortcuts 508$.each(("touchstart touchmove touchend tap taphold swipe swipeleft swiperight" ).split(""), function(i, name) { 509 510 $.fn[name] = function(fn) { 511 return fn ? this.bind(name, fn) : this.trigger(name); 512 }; 513 514 $.attrFn[name] = true; 515 } 516); 517 518function triggerCustomEvent(obj, eventType, event) { 519 var originalType = event.type; 520 event.type = eventType; 521 $.event.dispatch.call( obj, event ); 522 event.type = originalType; 523} 524 525// also handles taphold 526$.event.special.tap = { 527 528 setup: function() { 529 var thisObject = this, 530 $this = $(thisObject); 531 532 $this.bind( "touchstart", function( event ) { 533 534 if ( event.which && event.which !== 1 ) { 535 return false; 536 } 537 538 var origTarget = event.target, 539 origEvent = event.originalEvent, 540 timer; 541 542 function clearTapTimer() { 543 clearTimeout( timer ); 544 } 545 546 function clearTapHandlers() { 547 clearTapTimer(); 548 549 $this.unbind( "vclick", clickHandler ) 550 .unbind( "vmouseup", clearTapTimer ); 551 $( document ).unbind( "vmousecancel", clearTapHandlers ); 552 } 553 554 function clickHandler(event) { 555 clearTapHandlers(); 556 557 // ONLY trigger a 'tap' event if the start target is 558 // the same as the stop target. 559 if ( origTarget == event.target ) { 560 triggerCustomEvent( thisObject, "tap", event ); 561 } 562 } 563 564 $this.bind( "vmouseup", clearTapTimer ) 565 .bind( "vclick", clickHandler ); 566 $( document ).bind( "vmousecancel", clearTapHandlers ); 567 568 timer = setTimeout(function() { 569 triggerCustomEvent( 570 thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) 571 ); 572 }, 750 ); 573 }); 574 } 575}; 576 577// also handles swipeleft, swiperight 578$.event.special.swipe = { 579 // More than this horizontal displacement, and we will suppress scrolling. 580 scrollSupressionThreshold: 10, 581 582 // More time than this, and it isn't a swipe. 583 durationThreshold: 1000, 584 585 // Swipe horizontal displacement must be more than this. 586 horizontalDistanceThreshold: 30, 587 588 // Swipe vertical displacement must be less than this. 589 verticalDistanceThreshold: 75, 590 591 setup: function() { 592 var thisObject = this, 593 $this = $( thisObject ); 594 595 $this.bind( "touchstart", function( event ) { 596 var data = event.originalEvent.touches ? 597 event.originalEvent.touches[ 0 ] : event, 598 start = { 599 time: ( new Date() ).getTime(), 600 coords: [ data.pageX, data.pageY ], 601 origin: $( event.target ) 602 }, 603 stop; 604 605 function moveHandler( event ) { 606 607 if ( !start ) { 608 return; 609 } 610 611 var data = event.originalEvent.touches ? 612 event.originalEvent.touches[ 0 ] : event; 613 614 stop = { 615 time: ( new Date() ).getTime(), 616 coords: [ data.pageX, data.pageY ] 617 }; 618 619 // prevent scrolling
620 if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { 621 event.preventDefault(); 622 } 623 } 624 625 $this.bind( "touchmove", moveHandler ) 626 .one( "touchend", function( event ) { 627 $this.unbind( "touchmove", moveHandler ); 628 629 if ( start && stop ) { 630 if ( stop.time - start.time < $.event.special.swipe.durationThreshold && 631 Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && 632 Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { 633 634 start.origin.trigger( "swipe" ) 635 .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" ); 636 } 637 } 638 start = stop = undefined; 639 }); 640 }); 641 } 642}; 643 644 645$.each({ 646 taphold: "tap", 647 swipeleft: "swipe", 648 swiperight: "swipe" 649}, function( event, sourceEvent ) { 650 $.event.special[ event ] = { 651 setup: function() { 652 $( this ).bind( sourceEvent, $.noop ); 653 } 654 }; 655}); 656 657})( jQuery, this );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.