1/* 2* jQuery Mobile Framework Git Build: SHA1: 59099abd314c790a083a211574c05bc83129e16a <> Date: Mon Nov 14 16:39:50 2011 -0500 3* http://jquerymobile.com 4* 5* Copyright 2011 (c) jQuery Project 6* Dual licensed under the MIT or GPL Version 2 licenses. 7* http://jquery.org/license 8* 9*/ 10/*! 11 * jQuery UI Widget @VERSION 12 * 13 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 14 * Dual licensed under the MIT or GPL Version 2 licenses. 15 * http://jquery.org/license 16 * 17 * http://docs.jquery.com/UI/Widget 18 */ 19 20(function( $, undefined ) { 21 22// jQuery 1.4+ 23if ( $.cleanData ) { 24 var _cleanData = $.cleanData; 25 $.cleanData = function( elems ) { 26 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 27 $( elem ).triggerHandler( "remove" ); 28 } 29 _cleanData( elems ); 30 }; 31} else { 32 var _remove = $.fn.remove; 33 $.fn.remove = function( selector, keepData ) { 34 return this.each(function() { 35 if ( !keepData ) { 36 if ( !selector || $.filter( selector, [ this ] ).length ) { 37 $( "*", this ).add( [ this ] ).each(function() { 38 $( this ).triggerHandler( "remove" ); 39 }); 40 } 41 } 42 return _remove.call( $(this), selector, keepData ); 43 }); 44 }; 45} 46 47$.widget = function( name, base, prototype ) { 48 var namespace = name.split( "." )[ 0 ], 49 fullName; 50 name = name.split( "." )[ 1 ]; 51 fullName = namespace + "-" + name; 52 53 if ( !prototype ) { 54 prototype = base; 55 base = $.Widget; 56 } 57 58 // create selector for plugin 59 $.expr[ ":" ][ fullName ] = function( elem ) { 60 return !!$.data( elem, name ); 61 }; 62 63 $[ namespace ] = $[ namespace ] || {}; 64 $[ namespace ][ name ] = function( options, element ) { 65 // allow instantiation without initializing for simple inheritance 66 if ( arguments.length ) { 67 this._createWidget( options, element ); 68 } 69 }; 70 71 var basePrototype = new base(); 72 // we need to make the options hash a property directly on the new instance 73 // otherwise we'll modify the options hash on the prototype that we're 74 // inheriting from 75// $.each( basePrototype, function( key, val ) { 76// if ( $.isPlainObject(val) ) { 77// basePrototype[ key ] = $.extend( {}, val ); 78// } 79// }); 80 basePrototype.options = $.extend( true, {}, basePrototype.options ); 81 $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { 82 namespace: namespace, 83 widgetName: name, 84 widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, 85 widgetBaseClass: fullName 86 }, prototype ); 87 88 $.widget.bridge( name, $[ namespace ][ name ] ); 89}; 90 91$.widget.bridge = function( name, object ) { 92 $.fn[ name ] = function( options ) { 93 var isMethodCall = typeof options === "string", 94 args = Array.prototype.slice.call( arguments, 1 ), 95 returnValue = this; 96 97 // allow multiple hashes to be passed on init 98 options = !isMethodCall && args.length ? 99 $.extend.apply( null, [ true, options ].concat(args) ) : 100 options; 101 102 // prevent calls to internal methods 103 if ( isMethodCall && options.charAt( 0 ) === "_" ) { 104 return returnValue; 105 } 106 107 if ( isMethodCall ) { 108 this.each(function() { 109 var instance = $.data( this, name ); 110 if ( !instance ) { 111 throw "cannot call methods on " + name + " prior to initialization; " + 112 "attempted to call method '" + options + "'"; 113 } 114 if ( !$.isFunction( instance[options] ) ) { 115 throw "no such method '" + options + "' for " + name + " widget instance"; 116 } 117 var methodValue = instance[ options ].apply( instance, args ); 118 if ( methodValue !== instance && methodValue !== undefined ) { 119 returnValue = methodValue; 120 return false; 121 } 122 }); 123 } else { 124 this.each(function() { 125 var instance = $.data( this, name ); 126 if ( instance ) { 127 instance.option( options || {} )._init(); 128 } else { 129 $.data( this, name, new object( options, this ) ); 130 } 131 }); 132 } 133 134 return returnValue; 135 }; 136}; 137 138$.Widget = function( options, element ) { 139 // allow instantiation without initializing for simple inheritance 140 if ( arguments.length ) { 141 this._createWidget( options, element ); 142 } 143}; 144 145$.Widget.prototype = { 146 widgetName: "widget", 147 widgetEventPrefix: "", 148 options: { 149 disabled: false 150 }, 151 _createWidget: function( options, element ) { 152 // $.widget.bridge stores the plugin instance, but we do it anyway 153 // so that it's stored even before the _create function runs 154 $.data( element, this.widgetName, this ); 155 this.element = $( element ); 156 this.options = $.extend( true, {}, 157 this.options, 158 this._getCreateOptions(), 159 options ); 160 161 var self = this; 162 this.element.bind( "remove." + this.widgetName, function() { 163 self.destroy(); 164 }); 165 166 this._create(); 167 this._trigger( "create" ); 168 this._init(); 169 }, 170 _getCreateOptions: function() { 171 var options = {}; 172 if ( $.metadata ) { 173 options = $.metadata.get( element )[ this.widgetName ]; 174 } 175 return options; 176 }, 177 _create: function() {}, 178 _init: function() {}, 179 180 destroy: function() { 181 this.element 182 .unbind( "." + this.widgetName ) 183 .removeData( this.widgetName ); 184 this.widget() 185 .unbind( "." + this.widgetName ) 186 .removeAttr( "aria-disabled" ) 187 .removeClass( 188 this.widgetBaseClass + "-disabled " + 189 "ui-state-disabled" ); 190 }, 191 192 widget: function() { 193 return this.element; 194 }, 195 196 option: function( key, value ) { 197 var options = key; 198 199 if ( arguments.length === 0 ) { 200 // don't return a reference to the internal hash 201 return $.extend( {}, this.options ); 202 } 203 204 if (typeof key === "string" ) { 205 if ( value === undefined ) { 206 return this.options[ key ]; 207 } 208 options = {}; 209 options[ key ] = value; 210 } 211 212 this._setOptions( options ); 213 214 return this; 215 }, 216 _setOptions: function( options ) { 217 var self = this; 218 $.each( options, function( key, value ) { 219 self._setOption( key, value ); 220 }); 221 222 return this; 223 }, 224 _setOption: function( key, value ) { 225 this.options[ key ] = value; 226 227 if ( key === "disabled" ) { 228 this.widget() 229 [ value ? "addClass" : "removeClass"]( 230 this.widgetBaseClass + "-disabled" + " " + 231 "ui-state-disabled" ) 232 .attr( "aria-disabled", value ); 233 } 234 235 return this; 236 }, 237 238 enable: function() { 239 return this._setOption( "disabled", false ); 240 }, 241 disable: function() { 242 return this._setOption( "disabled", true ); 243 }, 244 245 _trigger: function( type, event, data ) { 246 var callback = this.options[ type ]; 247 248 event = $.Event( event ); 249 event.type = ( type === this.widgetEventPrefix ? 250 type : 251 this.widgetEventPrefix + type ).toLowerCase(); 252 data = data || {}; 253 254 // copy original event properties over to the new event 255 // this would happen if we could call $.event.fix instead of $.Event 256 // but we don't have a way to force an event to be fixed multiple times 257 if ( event.originalEvent ) { 258 for ( var i = $.event.props.length, prop; i; ) { 259 prop = $.event.props[ --i ]; 260 event[ prop ] = event.originalEvent[ prop ]; 261 } 262 } 263 264 this.element.trigger( event, data ); 265 266 return !( $.isFunction(callback) && 267 callback.call( this.element[0], event, data ) === false || 268 event.isDefaultPrevented() ); 269 } 270}; 271 272}
272)( jQuery ); 273/* 274* widget factory extentions for mobile 275*/ 276 277(function( $, undefined ) { 278 279$.widget( "mobile.widget", { 280 // decorate the parent _createWidget to trigger `widgetinit` for users 281 // who wish to do post post `widgetcreate` alterations/additions 282 // 283 // TODO create a pull request for jquery ui to trigger this event 284 // in the original _createWidget 285 _createWidget: function() { 286 $.Widget.prototype._createWidget.apply( this, arguments ); 287 this._trigger( 'init' ); 288 }, 289 290 _getCreateOptions: function() { 291 292 var elem = this.element, 293 options = {}; 294 295 $.each( this.options, function( option ) { 296 297 var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) { 298 return "-" + c.toLowerCase(); 299 }) 300 ); 301 302 if ( value !== undefined ) { 303 options[ option ] = value; 304 } 305 }); 306 307 return options; 308 }, 309 310 enhanceWithin: function( target ) { 311 // TODO remove dependency on the page widget for the keepNative. 312 // Currently the keepNative value is defined on the page prototype so 313 // the method is as well 314 var page = $(target).closest(":jqmData(role='page')").data( "page" ), 315 keepNative = (page && page.keepNativeSelector()) || ""; 316 317 $( this.options.initSelector, target ).not( keepNative )[ this.widgetName ](); 318 } 319}); 320 321})( jQuery ); 322/* 323* a workaround for window.matchMedia 324*/ 325 326(function( $, undefined ) { 327 328var $window = $( window ), 329 $html = $( "html" ); 330 331/* $.mobile.media method: pass a CSS media type or query and get a bool return 332 note: this feature relies on actual media query support for media queries, though types will work most anywhere 333 examples: 334 $.mobile.media('screen') //>> tests for screen media type 335 $.mobile.media('screen and (min-width: 480px)') //>> tests for screen media type with window width > 480px 336 $.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') //>> tests for webkit 2x pixel ratio (iPhone 4) 337*/ 338$.mobile.media = (function() { 339 // TODO: use window.matchMedia once at least one UA implements it 340 var cache = {}, 341 testDiv = $( "<div id='jquery-mediatest'>" ), 342 fakeBody = $( "<body>" ).append( testDiv ); 343 344 return function( query ) { 345 if ( !( query in cache ) ) { 346 var styleBlock = document.createElement( "style" ), 347 cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }"; 348 349 //must set type for IE! 350 styleBlock.type = "text/css"; 351 352 if ( styleBlock.styleSheet ){ 353 styleBlock.styleSheet.cssText = cssrule; 354 } else { 355 styleBlock.appendChild( document.createTextNode(cssrule) ); 356 } 357 358 $html.prepend( fakeBody ).prepend( styleBlock ); 359 cache[ query ] = testDiv.css( "position" ) === "absolute"; 360 fakeBody.add( styleBlock ).remove(); 361 } 362 return cache[ query ]; 363 }; 364})(); 365 366})(jQuery); 367/* 368* support tests 369*/ 370 371(function( $, undefined ) { 372 373var fakeBody = $( "<body>" ).prependTo( "html" ), 374 fbCSS = fakeBody[ 0 ].style, 375 vendors = [ "Webkit", "Moz", "O" ], 376 webos = "palmGetResource" in window, //only used to rule out scrollTop 377 bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB 378 379// thx Modernizr 380function propExists( prop ) { 381 var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), 382 props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ); 383 384 for ( var v in props ){ 385 if ( fbCSS[ props[ v ] ] !== undefined ) { 386 return true; 387 } 388 } 389} 390 391// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting ) 392function baseTagTest() { 393 var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/", 394 base = $( "head base" ), 395 fauxEle = null, 396 href = "", 397 link, rebase; 398 399 if ( !base.length ) { 400 base = fauxEle = $( "<base>", { "href": fauxBase }).appendTo( "head" ); 401 } else { 402 href = base.attr( "href" ); 403 } 404 405 link = $( "<a href='testurl' />" ).prependTo( fakeBody ); 406 rebase = link[ 0 ].href; 407 base[ 0 ].href = href || location.pathname; 408 409 if ( fauxEle ) { 410 fauxEle.remove(); 411 } 412 return rebase.indexOf( fauxBase ) === 0; 413} 414 415 416// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683 417// allows for inclusion of IE 6+, including Windows Mobile 7 418$.mobile.browser = {}; 419$.mobile.browser.ie = (function() { 420 var v = 3, 421 div = document.createElement( "div" ), 422 a = div.all || []; 423 424 while ( div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->", a[ 0 ] ); 425 426 return v > 4 ? v : !v; 427})(); 428 429 430$.extend( $.support, { 431 orientation: "orientation" in window && "onorientationchange" in window, 432 touch: "ontouchend" in document, 433 cssTransitions: "WebKitTransitionEvent" in window, 434 pushState: "pushState" in history && "replaceState" in history, 435 mediaquery: $.mobile.media( "only all" ), 436 cssPseudoElement: !!propExists( "content" ), 437 touchOverflow: !!propExists( "overflowScrolling" ), 438 boxShadow: !!propExists( "boxShadow" ) && !bb, 439 scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos, 440 dynamicBaseTag: baseTagTest() 441}); 442 443fakeBody.remove(); 444 445 446// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian) 447// or that generally work better browsing in regular http for full page refreshes (Opera Mini) 448// Note: This detection below is used as a last resort. 449// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible 450var nokiaLTE7_3 = (function(){ 451 452 var ua = window.navigator.userAgent; 453 454 //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older 455 return ua.indexOf( "Nokia" ) > -1 && 456 ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) && 457 ua.indexOf( "AppleWebKit" ) > -1 && 458 ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ ); 459})(); 460 461$.mobile.ajaxBlacklist = 462 // BlackBerry browsers, pre-webkit 463 window.blackberry && !window.WebKitPoint || 464 // Opera Mini 465 window.operamini && Object.prototype.toString.call( window.operamini ) === "[object OperaMini]" || 466 // Symbian webkits pre 7.3 467 nokiaLTE7_3; 468
469// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices 470// to render the stylesheets when they're referenced before this script, as we'd recommend doing. 471// This simply reappends the CSS in place, which for some reason makes it apply 472if ( nokiaLTE7_3 ) { 473 $(function() { 474 $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" ); 475 }); 476} 477 478// For ruling out shadows via css 479if ( !$.support.boxShadow ) { 480 $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" ); 481} 482 483})( jQuery ); 484/* 485* "mouse" plugin 486*/ 487 488// This plugin is an experiment for abstracting away the touch and mouse 489// events so that developers don't have to worry about which method of input 490// the device their document is loaded on supports. 491// 492// The idea here is to allow the developer to register listeners for the 493// basic mouse events, such as mousedown, mousemove, mouseup, and click, 494// and the plugin will take care of registering the correct listeners 495// behind the scenes to invoke the listener at the fastest possible time 496// for that device, while still retaining the order of event firing in 497// the traditional mouse environment, should multiple handlers be registered 498// on the same element for different events. 499// 500// The current version exposes the following virtual events to jQuery bind methods: 501// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" 502 503(function( $, window, document, undefined ) { 504 505var dataPropertyName = "virtualMouseBindings", 506 touchTargetPropertyName = "virtualTouchID", 507 virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), 508 touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), 509 activeDocHandlers = {}, 510 resetTimerID = 0, 511 startX = 0, 512 startY = 0, 513 didScroll = false, 514 clickBlockList = [], 515 blockMouseTriggers = false, 516 blockTouchTriggers = false, 517 eventCaptureSupported = "addEventListener" in document, 518 $document = $( document ), 519 nextTouchID = 1, 520 lastTouchID = 0; 521 522$.vmouse = { 523 moveDistanceThreshold: 10, 524 clickDistanceThreshold: 10, 525 resetTimerDuration: 1500 526}; 527 528function getNativeEvent( event ) { 529 530 while ( event && typeof event.originalEvent !== "undefined" ) { 531 event = event.originalEvent; 532 } 533 return event; 534} 535 536function createVirtualEvent( event, eventType ) { 537 538 var t = event.type, 539 oe, props, ne, prop, ct, touch, i, j; 540 541 event = $.Event(event); 542 event.type = eventType; 543 544 oe = event.originalEvent; 545 props = $.event.props; 546 547 // copy original event properties over to the new event 548 // this would happen if we could call $.event.fix instead of $.Event 549 // but we don't have a way to force an event to be fixed multiple times 550 if ( oe ) { 551 for ( i = props.length, prop; i; ) { 552 prop = props[ --i ]; 553 event[ prop ] = oe[ prop ]; 554 } 555 } 556 557 // make sure that if the mouse and click virtual events are generated 558 // without a .which one is defined 559 if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){ 560 event.which = 1; 561 } 562 563 if ( t.search(/^touch/) !== -1 ) { 564 ne = getNativeEvent( oe ); 565 t = ne.touches; 566 ct = ne.changedTouches; 567 touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined ); 568 569 if ( touch ) { 570 for ( j = 0, len = touchEventProps.length; j < len; j++){ 571 prop = touchEventProps[ j ]; 572 event[ prop ] = touch[ prop ]; 573 } 574 } 575 } 576 577 return event; 578} 579 580function getVirtualBindingFlags( element ) { 581 582 var flags = {}, 583 b, k; 584 585 while ( element ) { 586 587 b = $.data( element, dataPropertyName ); 588 589 for ( k in b ) { 590 if ( b[ k ] ) { 591 flags[ k ] = flags.hasVirtualBinding = true; 592 } 593 } 594 element = element.parentNode; 595 } 596 return flags; 597} 598 599function getClosestElementWithVirtualBinding( element, eventType ) { 600 var b; 601 while ( element ) { 602 603 b = $.data( element, dataPropertyName ); 604 605 if ( b && ( !eventType || b[ eventType ] ) ) { 606 return element; 607 } 608 element = element.parentNode; 609 } 610 return null; 611} 612 613function enableTouchBindings() { 614 blockTouchTriggers = false; 615} 616 617function disableTouchBindings() { 618 blockTouchTriggers = true; 619} 620 621function enableMouseBindings() { 622 lastTouchID = 0; 623 clickBlockList.length = 0; 624 blockMouseTriggers = false;
625 626 // When mouse bindings are enabled, our 627 // touch bindings are disabled. 628 disableTouchBindings(); 629} 630 631function disableMouseBindings() { 632 // When mouse bindings are disabled, our 633 // touch bindings are enabled. 634 enableTouchBindings(); 635} 636 637function startResetTimer() { 638 clearResetTimer(); 639 resetTimerID = setTimeout(function(){ 640 resetTimerID = 0; 641 enableMouseBindings(); 642 }, $.vmouse.resetTimerDuration ); 643} 644 645function clearResetTimer() { 646 if ( resetTimerID ){ 647 clearTimeout( resetTimerID ); 648 resetTimerID = 0; 649 } 650} 651 652function triggerVirtualEvent( eventType, event, flags ) { 653 var ve; 654 655 if ( ( flags && flags[ eventType ] ) || 656 ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { 657 658 ve = createVirtualEvent( event, eventType ); 659 660 $( event.target).trigger( ve ); 661 } 662 663 return ve; 664} 665 666function mouseEventCallback( event ) { 667 var touchID = $.data(event.target, touchTargetPropertyName); 668 669 if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){ 670 var ve = triggerVirtualEvent( "v" + event.type, event ); 671 if ( ve ) { 672 if ( ve.isDefaultPrevented() ) { 673 event.preventDefault(); 674 } 675 if ( ve.isPropagationStopped() ) { 676 event.stopPropagation(); 677 } 678 if ( ve.isImmediatePropagationStopped() ) { 679 event.stopImmediatePropagation(); 680 } 681 } 682 } 683} 684 685function handleTouchStart( event ) { 686 687 var touches = getNativeEvent( event ).touches, 688 target, flags; 689 690 if ( touches && touches.length === 1 ) { 691 692 target = event.target; 693 flags = getVirtualBindingFlags( target ); 694 695 if ( flags.hasVirtualBinding ) { 696 697 lastTouchID = nextTouchID++; 698 $.data( target, touchTargetPropertyName, lastTouchID ); 699 700 clearResetTimer(); 701 702 disableMouseBindings(); 703 didScroll = false; 704 705 var t = getNativeEvent( event ).touches[ 0 ]; 706 startX = t.pageX; 707 startY = t.pageY; 708 709 triggerVirtualEvent( "vmouseover", event, flags ); 710 triggerVirtualEvent( "vmousedown", event, flags ); 711 } 712 } 713} 714 715function handleScroll( event ) { 716 if ( blockTouchTriggers ) { 717 return; 718 } 719 720 if ( !didScroll ) { 721 triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); 722 } 723 724 didScroll = true; 725 startResetTimer(); 726} 727 728function handleTouchMove( event ) { 729 if ( blockTouchTriggers ) { 730 return; 731 } 732 733 var t = getNativeEvent( event ).touches[ 0 ], 734 didCancel = didScroll, 735 moveThreshold = $.vmouse.moveDistanceThreshold; 736 didScroll = didScroll || 737 ( Math.abs(t.pageX - startX) > moveThreshold || 738 Math.abs(t.pageY - startY) > moveThreshold ), 739 flags = getVirtualBindingFlags( event.target ); 740 741 if ( didScroll && !didCancel ) { 742 triggerVirtualEvent( "vmousecancel", event, flags ); 743 } 744 745 triggerVirtualEvent( "vmousemove", event, flags ); 746 startResetTimer(); 747} 748 749function handleTouchEnd( event ) { 750 if ( blockTouchTriggers ) { 751 return; 752 } 753 754 disableTouchBindings(); 755 756 var flags = getVirtualBindingFlags( event.target ), 757 t; 758 triggerVirtualEvent( "vmouseup", event, flags ); 759 760 if ( !didScroll ) { 761 var ve = triggerVirtualEvent( "vclick", event, flags ); 762 if ( ve && ve.isDefaultPrevented() ) { 763 // The target of the mouse events that follow the touchend 764 // event don't necessarily match the target used during the 765 // touch. This means we need to rely on coordinates for blocking 766 // any click that is generated. 767 t = getNativeEvent( event ).changedTouches[ 0 ]; 768 clickBlockList.push({ 769 touchID: lastTouchID, 770 x: t.clientX, 771 y: t.clientY 772 }); 773 774 // Prevent any mouse events that follow from triggering 775 // virtual event notifications. 776 blockMouseTriggers = true; 777 } 778 } 779 triggerVirtualEvent( "vmouseout", event, flags); 780 didScroll = false; 781 782 startResetTimer(); 783} 784 785function hasVirtualBindings( ele ) { 786 var bindings = $.data( ele, dataPropertyName ), 787 k; 788 789 if ( bindings ) { 790 for ( k in bindings ) { 791 if ( bindings[ k ] ) { 792 return true; 793 } 794 } 795 } 796 return false; 797} 798 799function dummyMouseHandler(){} 800 801function getSpecialEventObject( eventType ) { 802 var realType = eventType.substr( 1 ); 803 804 return { 805 setup: function( data, namespace ) { 806 // If this is the first virtual mouse binding for this element, 807 // add a bindings object to its data. 808 809 if ( !hasVirtualBindings( this ) ) { 810 $.data( this, dataPropertyName, {}); 811 } 812 813 // If setup is called, we know it is the first binding for this 814 // eventType, so initialize the count for the eventType to zero. 815 var bindings = $.data( this, dataPropertyName ); 816 bindings[ eventType ] = true; 817 818 // If this is the first virtual mouse event for this type, 819 // register a global handler on the document. 820 821 activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; 822 823 if ( activeDocHandlers[ eventType ] === 1 ) { 824 $document.bind( realType, mouseEventCallback ); 825 } 826 827 // Some browsers, like Opera Mini, won't dispatch mouse/click events 828 // for elements unless they actually have handlers registered on them. 829 // To get around this, we register dummy handlers on the elements. 830 831 $( this ).bind( realType, dummyMouseHandler ); 832 833 // For now, if event capture is not supported, we rely on mouse handlers. 834 if ( eventCaptureSupported ) { 835 // If this is the first virtual mouse binding for the document, 836 // register our touchstart handler on the document. 837 838 activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; 839 840 if (activeDocHandlers[ "touchstart" ] === 1) { 841 $document.bind( "touchstart", handleTouchStart ) 842 .bind( "touchend", handleTouchEnd ) 843 844 // On touch platforms, touching the screen and then dragging your finger 845 // causes the window content to scroll after some distance threshold is 846 // exceeded. On these platforms, a scroll prevents a click event from being 847 // dispatched, and on some platforms, even the touchend is suppressed. To 848 // mimic the suppression of the click event, we need to watch for a scroll 849 // event. Unfortunately, some platforms like iOS don't dispatch scroll
850 // events until *AFTER* the user lifts their finger (touchend). This means 851 // we need to watch both scroll and touchmove events to figure out whether 852 // or not a scroll happenens before the touchend event is fired. 853 854 .bind( "touchmove", handleTouchMove ) 855 .bind( "scroll", handleScroll ); 856 } 857 } 858 }, 859 860 teardown: function( data, namespace ) { 861 // If this is the last virtual binding for this eventType, 862 // remove its global handler from the document. 863 864 --activeDocHandlers[ eventType ]; 865 866 if ( !activeDocHandlers[ eventType ] ) { 867 $document.unbind( realType, mouseEventCallback ); 868 } 869 870 if ( eventCaptureSupported ) { 871 // If this is the last virtual mouse binding in existence, 872 // remove our document touchstart listener. 873 874 --activeDocHandlers[ "touchstart" ]; 875 876 if ( !activeDocHandlers[ "touchstart" ] ) { 877 $document.unbind( "touchstart", handleTouchStart ) 878 .unbind( "touchmove", handleTouchMove ) 879 .unbind( "touchend", handleTouchEnd ) 880 .unbind( "scroll", handleScroll ); 881 } 882 } 883 884 var $this = $( this ), 885 bindings = $.data( this, dataPropertyName ); 886 887 // teardown may be called when an element was 888 // removed from the DOM. If this is the case, 889 // jQuery core may have already stripped the element 890 // of any data bindings so we need to check it before 891 // using it. 892 if ( bindings ) { 893 bindings[ eventType ] = false; 894 } 895 896 // Unregister the dummy event handler. 897 898 $this.unbind( realType, dummyMouseHandler ); 899 900 // If this is the last virtual mouse binding on the 901 // element, remove the binding data from the element. 902 903 if ( !hasVirtualBindings( this ) ) { 904 $this.removeData( dataPropertyName ); 905 } 906 } 907 }; 908} 909 910// Expose our custom events to the jQuery bind/unbind mechanism. 911 912for ( var i = 0; i < virtualEventNames.length; i++ ){ 913 $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); 914} 915 916// Add a capture click handler to block clicks. 917// Note that we require event capture support for this so if the device 918// doesn't support it, we punt for now and rely solely on mouse events. 919if ( eventCaptureSupported ) { 920 document.addEventListener( "click", function( e ){ 921 var cnt = clickBlockList.length, 922 target = e.target, 923 x, y, ele, i, o, touchID; 924 925 if ( cnt ) { 926 x = e.clientX; 927 y = e.clientY; 928 threshold = $.vmouse.clickDistanceThreshold; 929 930 // The idea here is to run through the clickBlockList to see if 931 // the current click event is in the proximity of one of our 932 // vclick events that had preventDefault() called on it. If we find 933 // one, then we block the click. 934 // 935 // Why do we have to rely on proximity? 936 // 937 // Because the target of the touch event that triggered the vclick 938 // can be different from the target of the click event synthesized 939 // by the browser. The target of a mouse/click event that is syntehsized 940 // from a touch event seems to be implementation specific. For example, 941 // some browsers will fire mouse/click events for a link that is near 942 // a touch event, even though the target of the touchstart/touchend event 943 // says the user touched outside the link. Also, it seems that with most 944 // browsers, the target of the mouse/click event is not calculated until the 945 // time it is dispatched, so if you replace an element that you touched 946 // with another element, the target of the mouse/click will be the new 947 // element underneath that point. 948 // 949 // Aside from proximity, we also check to see if the target and any 950 // of its ancestors were the ones that blocked a click. This is necessary 951 // because of the strange mouse/click target calculation done in the 952 // Android 2.1 browser, where if you click on an element, and there is a 953 // mouse/click handler on one of its ancestors, the target will be the 954 // innermost child of the touched element, even if that child is no where 955 // near the point of touch. 956 957 ele = target; 958 959 while ( ele ) { 960 for ( i = 0; i < cnt; i++ ) { 961 o = clickBlockList[ i ]; 962 touchID = 0; 963 964 if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) || 965 $.data( ele, touchTargetPropertyName ) === o.touchID ) { 966 // XXX: We may want to consider removing matches from the block list 967 // instead of waiting for the reset timer to fire. 968 e.preventDefault(); 969 e.stopPropagation(); 970 return; 971 } 972 } 973 ele = ele.parentNode; 974 } 975 } 976 }, true); 977} 978}
978)( jQuery, window, document ); 979/* 980* "events" plugin - Handles events 981*/ 982 983(function( $, window, undefined ) { 984 985// add new event shortcuts 986$.each( ( "touchstart touchmove touchend orientationchange throttledresize " + 987 "tap taphold swipe swipeleft swiperight scrollstart scrollstop" ).split( " " ), function( i, name ) { 988 989 $.fn[ name ] = function( fn ) { 990 return fn ? this.bind( name, fn ) : this.trigger( name ); 991 }; 992 993 $.attrFn[ name ] = true; 994}); 995 996var supportTouch = $.support.touch, 997 scrollEvent = "touchmove scroll", 998 touchStartEvent = supportTouch ? "touchstart" : "mousedown", 999 touchStopEvent = supportTouch ? "touchend" : "mouseup", 1000 touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; 1001 1002function triggerCustomEvent( obj, eventType, event ) { 1003 var originalType = event.type; 1004 event.type = eventType; 1005 $.event.handle.call( obj, event ); 1006 event.type = originalType; 1007} 1008 1009// also handles scrollstop 1010$.event.special.scrollstart = { 1011 1012 enabled: true, 1013 1014 setup: function() { 1015 1016 var thisObject = this, 1017 $this = $( thisObject ), 1018 scrolling, 1019 timer; 1020 1021 function trigger( event, state ) { 1022 scrolling = state; 1023 triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event ); 1024 } 1025 1026 // iPhone triggers scroll after a small delay; use touchmove instead 1027 $this.bind( scrollEvent, function( event ) { 1028 1029 if ( !$.event.special.scrollstart.enabled ) { 1030 return; 1031 } 1032 1033 if ( !scrolling ) { 1034 trigger( event, true ); 1035 } 1036 1037 clearTimeout( timer ); 1038 timer = setTimeout(function() { 1039 trigger( event, false ); 1040 }, 50 ); 1041 }); 1042 } 1043}; 1044 1045// also handles taphold 1046$.event.special.tap = { 1047 setup: function() { 1048 var thisObject = this, 1049 $this = $( thisObject ); 1050 1051 $this.bind( "vmousedown", function( event ) { 1052 1053 if ( event.which && event.which !== 1 ) { 1054 return false; 1055 } 1056 1057 var origTarget = event.target, 1058 origEvent = event.originalEvent, 1059 timer; 1060 1061 function clearTapTimer() { 1062 clearTimeout( timer ); 1063 } 1064 1065 function clearTapHandlers() { 1066 clearTapTimer(); 1067 1068 $this.unbind( "vclick", clickHandler ) 1069 .unbind( "vmouseup", clearTapTimer ) 1070 .unbind( "vmousecancel", clearTapHandlers ); 1071 } 1072 1073 function clickHandler(event) { 1074 clearTapHandlers(); 1075 1076 // ONLY trigger a 'tap' event if the start target is 1077 // the same as the stop target. 1078 if ( origTarget == event.target ) { 1079 triggerCustomEvent( thisObject, "tap", event ); 1080 } 1081 } 1082 1083 $this.bind( "vmousecancel", clearTapHandlers ) 1084 .bind( "vmouseup", clearTapTimer ) 1085 .bind( "vclick", clickHandler ); 1086 1087 timer = setTimeout(function() { 1088 triggerCustomEvent( thisObject, "taphold", $.Event( "taphold" ) ); 1089 }, 750 ); 1090 }); 1091 } 1092}; 1093 1094// also handles swipeleft, swiperight 1095$.event.special.swipe = { 1096 scrollSupressionThreshold: 10, // More than this horizontal displacement, and we will suppress scrolling. 1097 1098 durationThreshold: 1000, // More time than this, and it isn't a swipe. 1099 1100 horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this. 1101 1102 verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this. 1103 1104 setup: function() { 1105 var thisObject = this, 1106 $this = $( thisObject ); 1107 1108 $this.bind( touchStartEvent, function( event ) { 1109 var data = event.originalEvent.touches ? 1110 event.originalEvent.touches[ 0 ] : event, 1111 start = { 1112 time: ( new Date() ).getTime(), 1113 coords: [ data.pageX, data.pageY ], 1114 origin: $( event.target ) 1115 }, 1116 stop; 1117 1118 function moveHandler( event ) { 1119 1120 if ( !start ) { 1121 return; 1122 } 1123 1124 var data = event.originalEvent.touches ? 1125 event.originalEvent.touches[ 0 ] : event; 1126 1127 stop = { 1128 time: ( new Date() ).getTime(), 1129 coords: [ data.pageX, data.pageY ] 1130 }; 1131 1132 // prevent scrolling 1133 if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { 1134 event.preventDefault(); 1135 } 1136 } 1137 1138 $this.bind( touchMoveEvent, moveHandler ) 1139 .one( touchStopEvent, function( event ) { 1140 $this.unbind( touchMoveEvent, moveHandler ); 1141 1142 if ( start && stop ) { 1143 if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
1144 Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && 1145 Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { 1146 1147 start.origin.trigger( "swipe" ) 1148 .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" ); 1149 } 1150 } 1151 start = stop = undefined; 1152 }); 1153 }); 1154 } 1155}; 1156 1157(function( $, window ) { 1158 // "Cowboy" Ben Alman 1159 1160 var win = $( window ), 1161 special_event, 1162 get_orientation, 1163 last_orientation; 1164 1165 $.event.special.orientationchange = special_event = { 1166 setup: function() { 1167 // If the event is supported natively, return false so that jQuery 1168 // will bind to the event using DOM methods. 1169 if ( $.support.orientation && $.mobile.orientationChangeEnabled ) { 1170 return false; 1171 } 1172 1173 // Get the current orientation to avoid initial double-triggering. 1174 last_orientation = get_orientation(); 1175 1176 // Because the orientationchange event doesn't exist, simulate the 1177 // event by testing window dimensions on resize. 1178 win.bind( "throttledresize", handler ); 1179 }, 1180 teardown: function(){ 1181 // If the event is not supported natively, return false so that 1182 // jQuery will unbind the event using DOM methods. 1183 if ( $.support.orientation && $.mobile.orientationChangeEnabled ) { 1184 return false; 1185 } 1186 1187 // Because the orientationchange event doesn't exist, unbind the 1188 // resize event handler. 1189 win.unbind( "throttledresize", handler ); 1190 }, 1191 add: function( handleObj ) { 1192 // Save a reference to the bound event handler. 1193 var old_handler = handleObj.handler; 1194 1195 1196 handleObj.handler = function( event ) { 1197 // Modify event object, adding the .orientation property. 1198 event.orientation = get_orientation(); 1199 1200 // Call the originally-bound event handler and return its result. 1201 return old_handler.apply( this, arguments ); 1202 }; 1203 } 1204 }; 1205 1206 // If the event is not supported natively, this handler will be bound to 1207 // the window resize event to simulate the orientationchange event. 1208 function handler() { 1209 // Get the current orientation. 1210 var orientation = get_orientation(); 1211 1212 if ( orientation !== last_orientation ) { 1213 // The orientation has changed, so trigger the orientationchange event. 1214 last_orientation = orientation; 1215 win.trigger( "orientationchange" ); 1216 } 1217 } 1218 1219 // Get the current page orientation. This method is exposed publicly, should it 1220 // be needed, as jQuery.event.special.orientationchange.orientation() 1221 $.event.special.orientationchange.orientation = get_orientation = function() { 1222 var isPortrait = true, elem = document.documentElement; 1223 1224 // prefer window orientation to the calculation based on screensize as 1225 // the actual screen resize takes place before or after the orientation change event 1226 // has been fired depending on implementation (eg android 2.3 is before, iphone after). 1227 // More testing is required to determine if a more reliable method of determining the new screensize 1228 // is possible when orientationchange is fired. (eg, use media queries + element + opacity) 1229 if ( $.support.orientation ) { 1230 // if the window orientation registers as 0 or 180 degrees report 1231 // portrait, otherwise landscape 1232 isPortrait = window.orientation % 180 == 0; 1233 } else { 1234 isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1; 1235 } 1236 1237 return isPortrait ? "portrait" : "landscape"; 1238 }; 1239 1240})( jQuery, window ); 1241 1242 1243// throttled resize event 1244(function() { 1245 1246 $.event.special.throttledresize = { 1247 setup: function() { 1248 $( this ).bind( "resize", handler ); 1249 }, 1250 teardown: function(){ 1251 $( this ).unbind( "resize", handler ); 1252 } 1253 }; 1254 1255 var throttle = 250, 1256 handler = function() { 1257 curr = ( new Date() ).getTime(); 1258 diff = curr - lastCall; 1259 1260 if ( diff >= throttle ) { 1261 1262 lastCall = curr; 1263 $( this ).trigger( "throttledresize" ); 1264 1265 } else { 1266 1267 if ( heldCall ) { 1268 clearTimeout( heldCall ); 1269 } 1270 1271 // Promise a held call will still execute 1272 heldCall = setTimeout( handler, throttle - diff ); 1273 } 1274 }, 1275 lastCall = 0, 1276 heldCall, 1277 curr, 1278 diff; 1279})(); 1280 1281 1282$.each({ 1283 scrollstop: "scrollstart", 1284 taphold: "tap", 1285 swipeleft: "swipe", 1286 swiperight: "swipe" 1287}, function( event, sourceEvent ) { 1288 1289 $.event.special[ event ] = { 1290 setup: function() { 1291 $( this ).bind( sourceEvent, $.noop ); 1292 } 1293 }; 1294}); 1295 1296})( jQuery, this ); 1297// Script: jQuery hashchange event 1298// 1299// *Version: 1.3, Last updated: 7/21/2010* 1300// 1301// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ 1302// GitHub - http://github.com/cowboy/jquery-hashchange/ 1303// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js 1304// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8
1304kb gzipped) 1305// 1306// About: License 1307// 1308// Copyright (c) 2010 "Cowboy" Ben Alman, 1309// Dual licensed under the MIT and GPL licenses. 1310// http://benalman.com/about/license/ 1311// 1312// About: Examples 1313// 1314// These working examples, complete with fully commented code, illustrate a few 1315// ways in which this plugin can be used. 1316// 1317// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ 1318// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ 1319// 1320// About: Support and Testing 1321// 1322// Information about what version or versions of jQuery this plugin has been 1323// tested with, what browsers it has been tested in, and where the unit tests 1324// reside (so you can test it yourself). 1325// 1326// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 1327// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, 1328// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. 1329// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ 1330// 1331// About: Known issues 1332// 1333// While this jQuery hashchange event implementation is quite stable and 1334// robust, there are a few unfortunate browser bugs surrounding expected 1335// hashchange event-based behaviors, independent of any JavaScript 1336// window.onhashchange abstraction. See the following examples for more 1337// information: 1338// 1339// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ 1340// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ 1341// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ 1342// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ 1343// 1344// Also note that should a browser natively support the window.onhashchange 1345// event, but not report that it does, the fallback polling loop will be used. 1346// 1347// About: Release History 1348// 1349// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more 1350// "removable" for mobile-only development. Added IE6/7 document.title 1351// support. Attempted to make Iframe as hidden as possible by using 1352// techniques from http://www.paciellogroup.com/blog/?p=604. Added 1353// support for the "shortcut" format $(window).hashchange( fn ) and 1354// $(window).hashchange() like jQuery provides for built-in events. 1355// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and 1356// lowered its default value to 50. Added <jQuery.fn.hashchange.domain> 1357// and <jQuery.fn.hashchange.src> properties plus document-domain.html 1358// file to address access denied issues when setting document.domain in 1359// IE6/7. 1360// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin 1361// from a page on another domain would cause an error in Safari 4. Also, 1362// IE6/7 Iframe is now inserted after the body (this actually works), 1363// which prevents the page from scrolling when the event is first bound. 1364// Event can also now be bound before DOM ready, but it won't be usable 1365// before then in IE6/7. 1366// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug 1367// where browser version is incorrectly reported as 8.0, despite 1368// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. 1369// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special 1370// window.onhashchange functionality into a separate plugin for users 1371// who want just the basic event & back button support, without all the 1372// extra awesomeness that BBQ provides. This plugin will be included as 1373// part of jQuery BBQ, but also be available separately. 1374 1375(function($,window,undefined){ 1376 '$:nomunge'; // Used by YUI compressor. 1377 1378 // Reused string. 1379 var str_hashchange = 'hashchange', 1380 1381 // Method / object references. 1382 doc = document, 1383 fake_onhashchange, 1384 special = $.event.special, 1385 1386 // Does the browser support window.onhashchange? Note that IE8 running in 1387 // IE7 compatibility mode reports true for 'onhashchange' in window, even 1388 // though the event isn't supported, so also test document.documentMode. 1389 doc_mode = doc.documentMode, 1390 supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); 1391 1392 // Get location.hash (or what you'd expect location.hash to be) sans any 1393 // leading #. Thanks for making this necessary, Firefox! 1394 function get_fragment( url ) { 1395 url = url || location.href; 1396 return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); 1397 }; 1398 1399 // Method: jQuery.fn.hashchange 1400 // 1401 // Bind a handler to the window.onhashchange event or trigger all bound 1402 // window.onhashchange event handlers. This behavior is consistent with 1403 // jQuery's built-in event handlers. 1404 // 1405 // Usage: 1406 // 1407 // > jQuery(window).hashchange( [ handler ] ); 1408 // 1409 // Arguments: 1410 // 1411 // handler - (Function) Optional handler to be bound to the hashchange 1412 // event. This is a "shortcut" for the more verbose form:
1413 // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, 1414 // all bound window.onhashchange event handlers will be triggered. This 1415 // is a shortcut for the more verbose 1416 // jQuery(window).trigger( 'hashchange' ). These forms are described in 1417 // the <hashchange event> section. 1418 // 1419 // Returns: 1420 // 1421 // (jQuery) The initial jQuery collection of elements. 1422 1423 // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and 1424 // $(elem).hashchange() for triggering, like jQuery does for built-in events. 1425 $.fn[ str_hashchange ] = function( fn ) { 1426 return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); 1427 }; 1428 1429 // Property: jQuery.fn.hashchange.delay 1430 // 1431 // The numeric interval (in milliseconds) at which the <hashchange event> 1432 // polling loop executes. Defaults to 50. 1433 1434 // Property: jQuery.fn.hashchange.domain 1435 // 1436 // If you're setting document.domain in your JavaScript, and you want hash 1437 // history to work in IE6/7, not only must this property be set, but you must 1438 // also set document.domain BEFORE jQuery is loaded into the page. This 1439 // property is only applicable if you are supporting IE6/7 (or IE8 operating 1440 // in "IE7 compatibility" mode). 1441 // 1442 // In addition, the <jQuery.fn.hashchange.src> property must be set to the 1443 // path of the included "document-domain.html" file, which can be renamed or 1444 // modified if necessary (note that the document.domain specified must be the 1445 // same in both your main JavaScript as well as in this file). 1446 // 1447 // Usage: 1448 // 1449 // jQuery.fn.hashchange.domain = document.domain; 1450 1451 // Property: jQuery.fn.hashchange.src 1452 // 1453 // If, for some reason, you need to specify an Iframe src file (for example, 1454 // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can 1455 // do so using this property. Note that when using this property, history 1456 // won't be recorded in IE6/7 until the Iframe src file loads. This property 1457 // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 1458 // compatibility" mode). 1459 // 1460 // Usage: 1461 // 1462 // jQuery.fn.hashchange.src = 'path/to/file.html'; 1463 1464 $.fn[ str_hashchange ].delay = 50; 1465 /* 1466 $.fn[ str_hashchange ].domain = null; 1467 $.fn[ str_hashchange ].src = null; 1468 */ 1469 1470 // Event: hashchange event 1471 // 1472 // Fired when location.hash changes. In browsers that support it, the native 1473 // HTML5 window.onhashchange event is used, otherwise a polling loop is 1474 // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to 1475 // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 1476 // compatibility" mode), a hidden Iframe is created to allow the back button 1477 // and hash-based history to work. 1478 // 1479 // Usage as described in <jQuery.fn.hashchange>: 1480 // 1481 // > // Bind an event handler. 1482 // > jQuery(window).hashchange( function(e) { 1483 // > var hash = location.hash; 1484 // > ... 1485 // > }); 1486 // > 1487 // > // Manually trigger the event handler. 1488 // > jQuery(window).hashchange(); 1489 // 1490 // A more verbose usage that allows for event namespacing: 1491 // 1492 // > // Bind an event handler. 1493 // > jQuery(window).bind( 'hashchange', function(e) { 1494 // > var hash = location.hash; 1495 // > ... 1496 // > }); 1497 // > 1498 // > // Manually trigger the event handler. 1499 // > jQuery(window).trigger( 'hashchange' ); 1500 // 1501 // Additional Notes: 1502 // 1503 // * The polling loop and Iframe are not created until at least one handler 1504 // is actually bound to the 'hashchange' event. 1505 // * If you need the bound handler(s) to execute immediately, in cases where 1506 // a location.hash exists on page load, via bookmark or page refresh for 1507 // example, use jQuery(window).hashchange() or the more verbose 1508 // jQuery(window).trigger( 'hashchange' ). 1509 // * The event can be bound before DOM ready, but since it won't be usable 1510 // before then in IE6/7 (due to the necessary Iframe), recommended usage is 1511 // to bind it inside a DOM ready handler. 1512 1513 // Override existing $.event.special.hashchange methods (allowing this plugin 1514 // to be defined after jQuery BBQ in BBQ's source code). 1515 special[ str_hashchange ] = $.extend( special[ str_hashchange ], { 1516 1517 // Called only when the first 'hashchange' event is bound to window. 1518 setup: function() { 1519 // If window.onhashchange is supported natively, there's nothing to do.. 1520 if ( supports_onhashchange ) { return false; } 1521 1522 // Otherwise, we need to create our own. And we don't want to call this 1523 // until the user binds to the event, just in case they never do, since it 1524 // will create a polling loop and possibly even a hidden Iframe. 1525 $( fake_onhashchange.start ); 1526 }, 1527 1528 // Called only when the last 'hashchange' event is unbound from window. 1529 teardown: function() { 1530 // If window.onhashchange is supported natively, there's nothing to do.. 1531 if ( supports_onhashchange ) { return false; } 1532 1533 // Otherwise, we need to stop ours (if possible). 1534 $( fake_onhashchange.stop ); 1535 } 1536 1537 }); 1538 1539 // fake_onhashchange does all the work of triggering the window.onhashchange 1540 // event for browsers that don't natively support it, including creating a
1541 // polling loop to watch for hash changes and in IE 6/7 creating a hidden 1542 // Iframe to enable back and forward. 1543 fake_onhashchange = (function(){ 1544 var self = {}, 1545 timeout_id, 1546 1547 // Remember the initial hash so it doesn't get triggered immediately. 1548 last_hash = get_fragment(), 1549 1550 fn_retval = function(val){ return val; }, 1551 history_set = fn_retval, 1552 history_get = fn_retval; 1553 1554 // Start the polling loop. 1555 self.start = function() { 1556 timeout_id || poll(); 1557 }; 1558 1559 // Stop the polling loop. 1560 self.stop = function() { 1561 timeout_id && clearTimeout( timeout_id ); 1562 timeout_id = undefined; 1563 }; 1564 1565 // This polling loop checks every $.fn.hashchange.delay milliseconds to see 1566 // if location.hash has changed, and triggers the 'hashchange' event on 1567 // window when necessary. 1568 function poll() { 1569 var hash = get_fragment(), 1570 history_hash = history_get( last_hash ); 1571 1572 if ( hash !== last_hash ) { 1573 history_set( last_hash = hash, history_hash ); 1574 1575 $(window).trigger( str_hashchange ); 1576 1577 } else if ( history_hash !== last_hash ) { 1578 location.href = location.href.replace( /#.*/, '' ) + history_hash; 1579 } 1580 1581 timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); 1582 }; 1583 1584 // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 1585 // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv 1586 // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 1587 $.browser.msie && !supports_onhashchange && (function(){ 1588 // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 1589 // when running in "IE7 compatibility" mode. 1590 1591 var iframe, 1592 iframe_src; 1593 1594 // When the event is bound and polling starts in IE 6/7, create a hidden 1595 // Iframe for history handling. 1596 self.start = function(){ 1597 if ( !iframe ) { 1598 iframe_src = $.fn[ str_hashchange ].src; 1599 iframe_src = iframe_src && iframe_src + get_fragment(); 1600 1601 // Create hidden Iframe. Attempt to make Iframe as hidden as possible 1602 // by using techniques from http://www.paciellogroup.com/blog/?p=604. 1603 iframe = $('<iframe tabindex="-1" title="empty"/>').hide() 1604 1605 // When Iframe has completely loaded, initialize the history and 1606 // start polling. 1607 .one( 'load', function(){ 1608 iframe_src || history_set( get_fragment() ); 1609 poll(); 1610 }) 1611 1612 // Load Iframe src if specified, otherwise nothing. 1613 .attr( 'src', iframe_src || 'javascript:0' ) 1614 1615 // Append Iframe after the end of the body to prevent unnecessary 1616 // initial page scrolling (yes, this works). 1617 .insertAfter( 'body' )[0].contentWindow; 1618 1619 // Whenever `document.title` changes, update the Iframe's title to 1620 // prettify the back/next history menu entries. Since IE sometimes 1621 // errors with "Unspecified error" the very first time this is set 1622 // (yes, very useful) wrap this with a try/catch block. 1623 doc.onpropertychange = function(){ 1624 try { 1625 if ( event.propertyName === 'title' ) { 1626 iframe.document.title = doc.title; 1627 } 1628 } catch(e) {} 1629 }; 1630 1631 } 1632 }; 1633 1634 // Override the "stop" method since an IE6/7 Iframe was created. Even 1635 // if there are no longer any bound event handlers, the polling loop 1636 // is still necessary for back/next to work at all! 1637 self.stop = fn_retval; 1638 1639 // Get history by looking at the hidden Iframe's location.hash. 1640 history_get = function() { 1641 return get_fragment( iframe.location.href ); 1642 }; 1643 1644 // Set a new history item by opening and then closing the Iframe 1645 // document, *then* setting its location.hash. If document.domain has 1646 // been set, update that as well. 1647 history_set = function( hash, history_hash ) { 1648 var iframe_doc = iframe.document, 1649 domain = $.fn[ str_hashchange ].domain; 1650 1651 if ( hash !== history_hash ) { 1652 // Update Iframe with any initial `document.title` that might be set. 1653 iframe_doc.title = doc.title; 1654 1655 // Opening the Iframe's document after it has been closed is what 1656 // actually adds a history entry. 1657 iframe_doc.open(); 1658 1659 // Set document.domain for the Iframe document as well, if necessary. 1660 domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' ); 1661 1662 iframe_doc.close(); 1663 1664 // Update the Iframe's hash, for great justice. 1665 iframe.location.hash = hash; 1666 } 1667 }; 1668 1669 })(); 1670 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1671 // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^ 1672 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1673 1674 return self; 1675 })(); 1676 1677})(jQuery,this); 1678/* 1679* "page" plugin 1680*/ 1681 1682(function( $, undefined ) { 1683 1684$.widget( "mobile.page", $.mobile.widget, { 1685 options: { 1686 theme: "c", 1687 domCache: false, 1688 keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')" 1689 }, 1690 1691 _create: function() { 1692 1693 this._trigger( "beforecreate" ); 1694 1695 this.element 1696 .attr( "tabindex", "0" ) 1697 .addClass( "ui-page ui-body-" + this.options.theme ); 1698 }, 1699 1700 keepNativeSelector: function() { 1701 var options = this.options, 1702 keepNativeDefined = options.keepNative && $.trim(options.keepNative); 1703 1704 if( keepNativeDefined && options.keepNative !== options.keepNativeDefault ){ 1705 return [options.keepNative, options.keepNativeDefault].join(", "); 1706 } 1707 1708 return options.keepNativeDefault; 1709 } 1710}); 1711})( jQuery ); 1712/* 1713* "core" - The base file for jQm 1714*/ 1715 1716(function( $, window, undefined ) { 1717 1718 var nsNormalizeDict = {}; 1719 1720 // jQuery.mobile configurable options 1721 $.extend( $.mobile, { 1722 1723 // Namespace used framework-wide for data-attrs. Default is no namespace 1724 ns: "", 1725 1726 // Define the url parameter used for referencing widget-generated sub-pages.
1727 // Translates to to example.html&ui-page=subpageIdentifier 1728 // hash segment before &ui-page= is used to make Ajax request 1729 subPageUrlKey: "ui-page", 1730 1731 // Class assigned to page currently in view, and during transitions 1732 activePageClass: "ui-page-active", 1733 1734 // Class used for "active" button state, from CSS framework 1735 activeBtnClass: "ui-btn-active", 1736 1737 // Automatically handle clicks and form submissions through Ajax, when same-domain 1738 ajaxEnabled: true, 1739 1740 // Automatically load and show pages based on location.hash 1741 hashListeningEnabled: true, 1742 1743 // disable to prevent jquery from bothering with links 1744 linkBindingEnabled: true, 1745 1746 // Set default page transition - 'none' for no transitions 1747 defaultPageTransition: "slide", 1748 1749 // Minimum scroll distance that will be remembered when returning to a page 1750 minScrollBack: 250, 1751 1752 // Set default dialog transition - 'none' for no transitions 1753 defaultDialogTransition: "pop", 1754 1755 // Show loading message during Ajax requests 1756 // if false, message will not appear, but loading classes will still be toggled on html el 1757 loadingMessage: "loading", 1758 1759 // Error response message - appears when an Ajax page request fails 1760 pageLoadErrorMessage: "Error Loading Page", 1761 1762 //automatically initialize the DOM when it's ready 1763 autoInitializePage: true, 1764 1765 pushStateEnabled: true, 1766 1767 // turn of binding to the native orientationchange due to android orientation behavior 1768 orientationChangeEnabled: true, 1769 1770 // Support conditions that must be met in order to proceed 1771 // default enhanced qualifications are media query support OR IE 7+ 1772 gradeA: function(){ 1773 return $.support.mediaquery || $.mobile.browser.ie && $.mobile.browser.ie >= 7; 1774 }, 1775 1776 // TODO might be useful upstream in jquery itself ? 1777 keyCode: { 1778 ALT: 18, 1779 BACKSPACE: 8, 1780 CAPS_LOCK: 20, 1781 COMMA: 188, 1782 COMMAND: 91, 1783 COMMAND_LEFT: 91, // COMMAND 1784 COMMAND_RIGHT: 93, 1785 CONTROL: 17, 1786 DELETE: 46, 1787 DOWN: 40, 1788 END: 35, 1789 ENTER: 13, 1790 ESCAPE: 27, 1791 HOME: 36, 1792 INSERT: 45, 1793 LEFT: 37, 1794 MENU: 93, // COMMAND_RIGHT 1795 NUMPAD_ADD: 107, 1796 NUMPAD_DECIMAL: 110, 1797 NUMPAD_DIVIDE: 111, 1798 NUMPAD_ENTER: 108, 1799 NUMPAD_MULTIPLY: 106, 1800 NUMPAD_SUBTRACT: 109, 1801 PAGE_DOWN: 34, 1802 PAGE_UP: 33, 1803 PERIOD: 190, 1804 RIGHT: 39, 1805 SHIFT: 16, 1806 SPACE: 32, 1807 TAB: 9, 1808 UP: 38, 1809 WINDOWS: 91 // COMMAND 1810 }, 1811 1812 // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value 1813 silentScroll: function( ypos ) { 1814 if ( $.type( ypos ) !== "number" ) { 1815 ypos = $.mobile.defaultHomeScroll; 1816 } 1817 1818 // prevent scrollstart and scrollstop events 1819 $.event.special.scrollstart.enabled = false; 1820 1821 setTimeout(function() { 1822 window.scrollTo( 0, ypos ); 1823 $( document ).trigger( "silentscroll", { x: 0, y: ypos }); 1824 }, 20 ); 1825 1826 setTimeout(function() { 1827 $.event.special.scrollstart.enabled = true; 1828 }, 150 ); 1829 }, 1830 1831 // Expose our cache for testing purposes. 1832 nsNormalizeDict: nsNormalizeDict, 1833 1834 // Take a data attribute property, prepend the namespace 1835 // and then camel case the attribute string. Add the result 1836 // to our nsNormalizeDict so we don't have to do this again. 1837 nsNormalize: function( prop ) { 1838 if ( !prop ) { 1839 return; 1840 } 1841 1842 return nsNormalizeDict[ prop ] || ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) ); 1843 }, 1844 1845 getInheritedTheme: function( el, defaultTheme ) { 1846 1847 // Find the closest parent with a theme class on it. Note that 1848 // we are not using $.fn.closest() on purpose here because this 1849 // method gets called quite a bit and we need it to be as fast 1850 // as possible. 1851 1852 var e = el[ 0 ], 1853 ltr = "", 1854 re = /ui-(bar|body)-([a-z])\b/, 1855 c, m; 1856 1857 while ( e ) { 1858 var c = e.className || ""; 1859 if ( ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) { 1860 // We found a parent with a theme class 1861 // on it so bail from this loop. 1862 break; 1863 } 1864 e = e.parentNode; 1865 } 1866 1867 // Return the theme letter we found, if none, return the 1868 // specified default. 1869 1870 return ltr || defaultTheme || "a"; 1871 } 1872 }); 1873 1874 // Mobile version of data and removeData and hasData methods 1875 // ensures all data is set and retrieved using jQuery Mobile's data namespace 1876 $.fn.jqmData = function( prop, value ) { 1877 var result; 1878 if ( typeof prop != "undefined" ) { 1879 result = this.data( prop ? $.mobile.nsNormalize( prop ) : prop, value ); 1880 } 1881 return result; 1882 }; 1883 1884 $.jqmData = function( elem, prop, value ) { 1885 var result; 1886 if ( typeof prop != "undefined" ) { 1887 result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value ); 1888 } 1889 return result; 1890 }; 1891 1892 $.fn.jqmRemoveData = function( prop ) { 1893 return this.removeData( $.mobile.nsNormalize( prop ) ); 1894 }; 1895 1896 $.jqmRemoveData = function( elem, prop ) { 1897 return $.removeData( elem, $.mobile.nsNormalize( prop ) ); 1898 }; 1899 1900 $.fn.removeWithDependents = function() { 1901 $.removeWithDependents( this ); 1902 }; 1903 1904 $.removeWithDependents = function( elem ) { 1905 var $elem = $( elem ); 1906 1907 ( $elem.jqmData('dependents') || $() ).remove(); 1908 $elem.remove(); 1909 }; 1910 1911 $.fn.addDependents = function( newDependents ) { 1912 $.addDependents( $(this), newDependents ); 1913 }; 1914 1915 $.addDependents = function( elem, newDependents ) { 1916 var dependents = $(elem).jqmData( 'dependents' ) || $(); 1917 1918 $(elem).jqmData( 'dependents', $.merge(dependents, newDependents) ); 1919 }; 1920 1921 // note that this helper doesn't attempt to handle the callback 1922 // or setting of an html elements text, its only purpose is 1923 // to return the html encoded version of the text in all cases. (thus the name) 1924 $.fn.getEncodedText = function() { 1925 return $( "<div/>" ).text( $(this).text() ).html(); 1926 }; 1927 1928 // Monkey-patching Sizzle to filter the :jqmData selector 1929 var oldFind = $.find, 1930 jqmDataRE = /:jqmData\(([^)]*)\)/g; 1931
1932 $.find = function( selector, context, ret, extra ) { 1933 selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" ); 1934 1935 return oldFind.call( this, selector, context, ret, extra ); 1936 }; 1937 1938 $.extend( $.find, oldFind ); 1939 1940 $.find.matches = function( expr, set ) { 1941 return $.find( expr, null, null, set ); 1942 }; 1943 1944 $.find.matchesSelector = function( node, expr ) { 1945 return $.find( expr, null, null, [ node ] ).length > 0; 1946 }; 1947})( jQuery, this ); 1948 1949/* 1950* core utilities for auto ajax navigation, base tag mgmt, 1951*/ 1952 1953( function( $, undefined ) { 1954 1955 //define vars for interal use 1956 var $window = $( window ), 1957 $html = $( 'html' ), 1958 $head = $( 'head' ), 1959 1960 //url path helpers for use in relative url management 1961 path = { 1962 1963 // This scary looking regular expression parses an absolute URL or its relative 1964 // variants (protocol, site, document, query, and hash), into the various 1965 // components (protocol, host, path, query, fragment, etc that make up the 1966 // URL as well as some other commonly used sub-parts. When used with RegExp.exec() 1967 // or String.match, it parses the URL into a results array that looks like this: 1968 // 1969 // [0]: http://jblas:[email protected]:8080/mail/inbox?msg=1234&type=unread#msg-content 1970 // [1]: http://jblas:[email protected]:8080/mail/inbox?msg=1234&type=unread 1971 // [2]: http://jblas:[email protected]:8080/mail/inbox 1972 // [3]: http://jblas:[email protected]:8080 1973 // [4]: http: 1974 // [5]: // 1975 // [6]: jblas:[email protected]:8080 1976 // [7]: jblas:password 1977 // [8]: jblas 1978 // [9]: password 1979 // [10]: mycompany.com:8080 1980 // [11]: mycompany.com 1981 // [12]: 8080 1982 // [13]: /mail/inbox 1983 // [14]: /mail/ 1984 // [15]: inbox 1985 // [16]: ?msg=1234&type=unread 1986 // [17]: #msg-content 1987 // 1988 urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, 1989 1990 //Parse a URL into a structure that allows easy access to 1991 //all of the URL components by name. 1992 parseUrl: function( url ) { 1993 // If we're passed an object, we'll assume that it is 1994 // a parsed url object and just return it back to the caller. 1995 if ( $.type( url ) === "object" ) { 1996 return url; 1997 } 1998 1999 var matches = path.urlParseRE.exec( url || "" ) || []; 2000 2001 // Create an object that allows the caller to access the sub-matches 2002 // by name. Note that IE returns an empty string instead of undefined, 2003 // like all other browsers do, so we normalize everything so its consistent 2004 // no matter what browser we're running on. 2005 return { 2006 href: matches[ 0 ] || "", 2007 hrefNoHash: matches[ 1 ] || "", 2008 hrefNoSearch: matches[ 2 ] || "", 2009 domain: matches[ 3 ] || "", 2010 protocol: matches[ 4 ] || "", 2011 doubleSlash: matches[ 5 ] || "", 2012 authority: matches[ 6 ] || "", 2013 username: matches[ 8 ] || "", 2014 password: matches[ 9 ] || "", 2015 host: matches[ 10 ] || "", 2016 hostname: matches[ 11 ] || "", 2017 port: matches[ 12 ] || "", 2018 pathname: matches[ 13 ] || "", 2019 directory: matches[ 14 ] || "", 2020 filename: matches[ 15 ] || "", 2021 search: matches[ 16 ] || "", 2022 hash: matches[ 17 ] || "" 2023 }; 2024 }, 2025 2026 //Turn relPath into an asbolute path. absPath is 2027 //an optional absolute path which describes what 2028 //relPath is relative to. 2029 makePathAbsolute: function( relPath, absPath ) { 2030 if ( relPath && relPath.charAt( 0 ) === "/" ) { 2031 return relPath; 2032 } 2033 2034 relPath = relPath || ""; 2035 absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : ""; 2036 2037 var absStack = absPath ? absPath.split( "/" ) : [], 2038 relStack = relPath.split( "/" ); 2039 for ( var i = 0; i < relStack.length; i++ ) { 2040 var d = relStack[ i ]; 2041 switch ( d ) { 2042 case ".": 2043 break; 2044 case "..": 2045 if ( absStack.length ) { 2046 absStack.pop(); 2047 } 2048 break; 2049 default: 2050 absStack.push( d ); 2051 break; 2052 } 2053 } 2054 return "/" + absStack.join( "/" ); 2055 }, 2056 2057 //Returns true if both urls have the same domain. 2058 isSameDomain: function( absUrl1, absUrl2 ) { 2059 return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain; 2060 }, 2061 2062 //Returns true for any relative variant. 2063 isRelativeUrl: function( url ) { 2064 // All relative Url variants have one thing in common, no protocol. 2065 return path.parseUrl( url ).protocol === ""; 2066 }, 2067 2068 //Returns true for an absolute url. 2069 isAbsoluteUrl: function( url ) { 2070 return path.parseUrl( url ).protocol !== ""; 2071 }, 2072 2073 //Turn the specified realtive URL into an absolute one. This function 2074 //can handle all relative variants (protocol, site, document, query, fragment). 2075 makeUrlAbsolute: function( relUrl, absUrl ) { 2076 if ( !path.isRelativeUrl( relUrl ) ) { 2077 return relUrl; 2078 } 2079 2080 var relObj = path.parseUrl( relUrl ), 2081 absObj = path.parseUrl( absUrl ), 2082 protocol = relObj.protocol || absObj.protocol, 2083 doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ), 2084 authority = relObj.authority || absObj.authority, 2085 hasPath = relObj.pathname !== "", 2086 pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), 2087 search = relObj.search || ( !hasPath && absObj.search ) || "", 2088 hash = relObj.hash; 2089 2090 return protocol + doubleSlash + authority + pathname + search + hash; 2091 }, 2092 2093 //Add search (aka query) params to the specified url. 2094 addSearchParams: function( url, params ) { 2095 var u = path.parseUrl( url ), 2096 p = ( typeof params === "object" ) ? $.param( params ) : params, 2097 s = u.search || "?"; 2098 return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" ); 2099 }, 2100 2101 convertUrlToDataUrl: function( absUrl ) { 2102 var u = path.parseUrl( absUrl ); 2103 if ( path.isEmbeddedPage( u ) ) { 2104 // For embedded pages, remove the dialog hash key as in getFilePath(), 2105 // otherwise the Data Url won't match the id of the embedded Page. 2106 return u.hash.split( dialogHashKey )[0].replace( /^#/, "" ); 2107 } else if ( path.isSameDomain( u, documentBase ) ) { 2108 return u.hrefNoHash.replace( documentBase.domain, "" ); 2109 } 2110 return absUrl; 2111 }, 2112 2113 //get path from current hash, or from a file path 2114 get: function( newPath ) { 2115 if( newPath === undefined ) { 2116 newPath = location.hash; 2117 } 2118 return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' ); 2119 }, 2120 2121 //return the substring of a filepath before the sub-page key, for making a server request 2122 getFilePath: function( path ) { 2123 var splitkey = '&' + $.mobile.subPageUrlKey; 2124 return path && path.split( splitkey )[0].split( dialogHashKey )[0]; 2125 }, 2126 2127 //set location hash to path 2128 set: function( path ) { 2129 location.hash = path; 2130 }, 2131 2132 //test if a given url (string) is a path 2133 //NOTE might be exceptionally naive 2134 isPath: function( url ) { 2135 return ( /\// ).test( url ); 2136 }, 2137 2138 //return a url path with the window's location protocol/hostname/pathname removed 2139 clean: function( url ) { 2140 return url.replace( documentBase.domain, "" ); 2141 }, 2142
2143 //just return the url without an initial # 2144 stripHash: function( url ) { 2145 return url.replace( /^#/, "" ); 2146 }, 2147 2148 //remove the preceding hash, any query params, and dialog notations 2149 cleanHash: function( hash ) { 2150 return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) ); 2151 }, 2152 2153 //check whether a url is referencing the same domain, or an external domain or different protocol 2154 //could be mailto, etc 2155 isExternal: function( url ) { 2156 var u = path.parseUrl( url ); 2157 return u.protocol && u.domain !== documentUrl.domain ? true : false; 2158 }, 2159 2160 hasProtocol: function( url ) { 2161 return ( /^(:?\w+:)/ ).test( url ); 2162 }, 2163 2164 //check if the specified url refers to the first page in the main application document. 2165 isFirstPageUrl: function( url ) { 2166 // We only deal with absolute paths. 2167 var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ), 2168 2169 // Does the url have the same path as the document? 2170 samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ), 2171 2172 // Get the first page element. 2173 fp = $.mobile.firstPage, 2174 2175 // Get the id of the first page element if it has one. 2176 fpId = fp && fp[0] ? fp[0].id : undefined; 2177 2178 // The url refers to the first page if the path matches the document and 2179 // it either has no hash value, or the hash is exactly equal to the id of the 2180 // first page element. 2181 return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) ); 2182 }, 2183 2184 isEmbeddedPage: function( url ) { 2185 var u = path.parseUrl( url ); 2186 2187 //if the path is absolute, then we need to compare the url against 2188 //both the documentUrl and the documentBase. The main reason for this 2189 //is that links embedded within external documents will refer to the 2190 //application document, whereas links embedded within the application 2191 //document will be resolved against the document base. 2192 if ( u.protocol !== "" ) { 2193 return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) ); 2194 } 2195 return (/^#/).test( u.href ); 2196 } 2197 }, 2198 2199 //will be defined when a link is clicked and given an active class 2200 $activeClickedLink = null, 2201 2202 //urlHistory is purely here to make guesses at whether the back or forward button was clicked 2203 //and provide an appropriate transition 2204 urlHistory = { 2205 // Array of pages that are visited during a single page load. 2206 // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs) 2207 stack: [], 2208 2209 //maintain an index number for the active page in the stack 2210 activeIndex: 0, 2211 2212 //get active 2213 getActive: function() { 2214 return urlHistory.stack[ urlHistory.activeIndex ]; 2215 }, 2216 2217 getPrev: function() { 2218 return urlHistory.stack[ urlHistory.activeIndex - 1 ]; 2219 }, 2220 2221 getNext: function() { 2222 return urlHistory.stack[ urlHistory.activeIndex + 1 ]; 2223 }, 2224 2225 // addNew is used whenever a new page is added 2226 addNew: function( url, transition, title, pageUrl, role ) { 2227 //if there's forward history, wipe it 2228 if( urlHistory.getNext() ) { 2229 urlHistory.clearForward(); 2230 } 2231 2232 urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl, role: role } ); 2233 2234 urlHistory.activeIndex = urlHistory.stack.length - 1; 2235 }, 2236 2237 //wipe urls ahead of active index 2238 clearForward: function() { 2239 urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 ); 2240 }, 2241 2242 directHashChange: function( opts ) { 2243 var back , forward, newActiveIndex, prev = this.getActive(); 2244 2245 // check if url isp in history and if it's ahead or behind current page 2246 $.each( urlHistory.stack, function( i, historyEntry ) { 2247 2248 //if the url is in the stack, it's a forward or a back 2249 if( opts.currentUrl === historyEntry.url ) { 2250 //define back and forward by whether url is older or newer than current page 2251 back = i < urlHistory.activeIndex; 2252 forward = !back; 2253 newActiveIndex = i; 2254 } 2255 }); 2256 2257 // save new page index, null check to prevent falsey 0 result 2258 this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex; 2259 2260 if( back ) { 2261 ( opts.either || opts.isBack )( true ); 2262 } else if( forward ) { 2263 ( opts.either || opts.isForward )( false ); 2264 } 2265 }, 2266 2267 //disable hashchange event listener internally to ignore one change 2268 //toggled internally when location.hash is updated to match the url of a successful page load 2269 ignoreNextHashChange: false 2270 }, 2271 2272 //define first selector to receive focus when a page is shown 2273 focusable = "[tabindex],a,button:visible,select:visible,input", 2274 2275 //queue to hold simultanious page transitions 2276 pageTransitionQueue = [], 2277 2278 //indicates whether or not page is in process of transitioning 2279 isPageTransitioning = false, 2280 2281 //nonsense hash change key for dialogs, so they create a history entry 2282 dialogHashKey = "&ui-state=dialog", 2283 2284 //existing base tag? 2285 $base = $head.children( "base" ), 2286 2287 //tuck away the original document URL minus any fragment. 2288 documentUrl = path.parseUrl( location.href ), 2289 2290 //if the document has an embedded base tag, documentBase is set to its 2291 //initial value. If a base tag does not exist, then we default to the documentUrl. 2292 documentBase = $base.length ? path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), documentUrl.href ) ) : documentUrl, 2293 2294 //cache the comparison once. 2295 documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash ); 2296 2297 //base element management, defined depending on dynamic base tag support 2298 var base = $.support.dynamicBaseTag ? { 2299
2300 //define base element, for use in routing asset urls that are referenced in Ajax-requested markup 2301 element: ( $base.length ? $base : $( "<base>", { href: documentBase.hrefNoHash } ).prependTo( $head ) ), 2302 2303 //set the generated BASE element's href attribute to a new page's base path 2304 set: function( href ) { 2305 base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) ); 2306 }, 2307 2308 //set the generated BASE element's href attribute to a new page's base path 2309 reset: function() { 2310 base.element.attr( "href", documentBase.hrefNoHash ); 2311 } 2312 2313 } : undefined; 2314 2315/* 2316 internal utility functions 2317--------------------------------------*/ 2318 2319 2320 //direct focus to the page title, or otherwise first focusable element 2321 function reFocus( page ) { 2322 var pageTitle = page.find( ".ui-title:eq(0)" ); 2323 2324 if( pageTitle.length ) { 2325 pageTitle.focus(); 2326 } 2327 else{ 2328 page.focus(); 2329 } 2330 } 2331 2332 //remove active classes after page transition or error 2333 function removeActiveLinkClass( forceRemoval ) { 2334 if( !!$activeClickedLink && ( !$activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval ) ) { 2335 $activeClickedLink.removeClass( $.mobile.activeBtnClass ); 2336 } 2337 $activeClickedLink = null; 2338 } 2339 2340 function releasePageTransitionLock() { 2341 isPageTransitioning = false; 2342 if( pageTransitionQueue.length > 0 ) { 2343 $.mobile.changePage.apply( null, pageTransitionQueue.pop() ); 2344 } 2345 } 2346 2347 // Save the last scroll distance per page, before it is hidden 2348 var setLastScrollEnabled = true, 2349 firstScrollElem, getScrollElem, setLastScroll, delayedSetLastScroll; 2350 2351 getScrollElem = function() { 2352 var scrollElem = $window, activePage, 2353 touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled; 2354 2355 if( touchOverflow ){ 2356 activePage = $( ".ui-page-active" ); 2357 scrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage; 2358 } 2359 2360 return scrollElem; 2361 }; 2362 2363 setLastScroll = function( scrollElem ) { 2364 // this barrier prevents setting the scroll value based on the browser 2365 // scrolling the window based on a hashchange 2366 if( !setLastScrollEnabled ) { 2367 return; 2368 } 2369 2370 var active = $.mobile.urlHistory.getActive(); 2371 2372 if( active ) { 2373 var lastScroll = scrollElem && scrollElem.scrollTop(); 2374 2375 // Set active page's lastScroll prop. 2376 // If the location we're scrolling to is less than minScrollBack, let it go. 2377 active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll; 2378 } 2379 }; 2380 2381 // bind to scrollstop to gather scroll position. The delay allows for the hashchange 2382 // event to fire and disable scroll recording in the case where the browser scrolls 2383 // to the hash targets location (sometimes the top of the page). once pagechange fires 2384 // getLastScroll is again permitted to operate 2385 delayedSetLastScroll = function() { 2386 setTimeout( setLastScroll, 100, $(this) ); 2387 }; 2388 2389 // disable an scroll setting when a hashchange has been fired, this only works 2390 // because the recording of the scroll position is delayed for 100ms after 2391 // the browser might have changed the position because of the hashchange 2392 $window.bind( $.support.pushState ? "popstate" : "hashchange", function() { 2393 setLastScrollEnabled = false; 2394 }); 2395 2396 // handle initial hashchange from chrome :( 2397 $window.one( $.support.pushState ? "popstate" : "hashchange", function() { 2398 setLastScrollEnabled = true; 2399 }); 2400 2401 // wait until the mobile page container has been determined to bind to pagechange 2402 $window.one( "pagecontainercreate", function(){ 2403 // once the page has changed, re-enable the scroll recording 2404 $.mobile.pageContainer.bind( "pagechange", function() { 2405 var scrollElem = getScrollElem(); 2406 2407 setLastScrollEnabled = true; 2408 2409 // remove any binding that previously existed on the get scroll 2410 // which may or may not be different than the scroll element determined for 2411 // this page previously 2412 scrollElem.unbind( "scrollstop", delayedSetLastScroll ); 2413 2414 // determine and bind to the current scoll element which may be the window 2415 // or in the case of touch overflow the element with touch overflow 2416 scrollElem.bind( "scrollstop", delayedSetLastScroll ); 2417 }); 2418 }); 2419 2420 // bind to scrollstop for the first page as "pagechange" won't be fired in that case 2421 getScrollElem().bind( "scrollstop", delayedSetLastScroll ); 2422 2423 // Make the iOS clock quick-scroll work again if we're using native overflow scrolling 2424 /* 2425 if( $.support.touchOverflow ){ 2426 if( $.mobile.touchOverflowEnabled ){ 2427 $( window ).bind( "scrollstop", function(){ 2428 if( $( this ).scrollTop() === 0 ){ 2429 $.mobile.activePage.scrollTop( 0 ); 2430 } 2431 }); 2432 } 2433 } 2434 */ 2435 2436 //function for transitioning between two existing pages 2437 function transitionPages( toPage, fromPage, transition, reverse ) { 2438 2439 //get current scroll distance 2440 var active = $.mobile.urlHistory.getActive(), 2441 touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled, 2442 toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ), 2443 screenHeight = getScreenHeight(); 2444 2445 // Scroll to top, hide addr bar 2446 window.scrollTo( 0, $.mobile.defaultHomeScroll ); 2447 2448 if( fromPage ) { 2449 //trigger before show/hide events 2450 fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } ); 2451 } 2452 2453 if( !touchOverflow){ 2454 toPage.height( screenHeight + toScroll ); 2455 } 2456 2457 toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } ); 2458 2459 //clear page loader 2460 $.mobile.hidePageLoadingMsg(); 2461 2462 if( touchOverflow && toScroll ){ 2463 2464 toPage.addClass( "ui-mobile-pre-transition" ); 2465 // Send focus to page as it is now display: block 2466 reFocus( toPage ); 2467 2468 //set page's scrollTop to remembered distance 2469 if( toPage.is( ".ui-native-fixed" ) ){ 2470 toPage.find( ".ui-content" ).scrollTop( toScroll ); 2471 } 2472 else{ 2473 toPage.scrollTop( toScroll ); 2474 } 2475 } 2476 2477 //find the transition handler for the specified transition. If there 2478 //isn't one in our transitionHandlers dictionary, use the default one. 2479 //call the handler immediately to kick-off the transition. 2480 var th = $.mobile.transitionHandlers[transition || "none"] || $.mobile.defaultTransitionHandler, 2481 promise = th( transition, reverse, toPage, fromPage ); 2482 2483 promise.done(function() { 2484 //reset toPage height back 2485 if( !touchOverflow ){ 2486 toPage.height( "" ); 2487 // Send focus to the newly shown page 2488 reFocus( toPage ); 2489 } 2490 2491 // Jump to top or prev scroll, sometimes on iOS the page has not rendered yet. 2492 if( !touchOverflow ){ 2493 $.mobile.silentScroll( toScroll ); 2494 } 2495 2496 //trigger show/hide events 2497 if( fromPage ) { 2498 if( !touchOverflow ){ 2499 fromPage.height( "" ); 2500 } 2501 2502 fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } ); 2503 } 2504 2505 //trigger pageshow, define prevPage as either fromPage or empty jQuery obj 2506 toPage.data( "page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } ); 2507 }); 2508 2509 return promise; 2510 } 2511 2512 //simply set the active page's minimum height to screen height, depending on orientation 2513 function getScreenHeight(){ 2514 var orientation = $.event.special.orientationchange.orientation(), 2515 port = orientation === "portrait", 2516 winMin = port ? 480 : 320,
2517 screenHeight = port ? screen.availHeight : screen.availWidth, 2518 winHeight = Math.max( winMin, $( window ).height() ), 2519 pageMin = Math.min( screenHeight, winHeight ); 2520 2521 return pageMin; 2522 } 2523 2524 $.mobile.getScreenHeight = getScreenHeight; 2525 2526 //simply set the active page's minimum height to screen height, depending on orientation 2527 function resetActivePageHeight(){ 2528 // Don't apply this height in touch overflow enabled mode 2529 if( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){ 2530 return; 2531 } 2532 $( "." + $.mobile.activePageClass ).css( "min-height", getScreenHeight() ); 2533 } 2534 2535 //shared page enhancements 2536 function enhancePage( $page, role ) { 2537 // If a role was specified, make sure the data-role attribute 2538 // on the page element is in sync. 2539 if( role ) { 2540 $page.attr( "data-" + $.mobile.ns + "role", role ); 2541 } 2542 2543 //run page plugin 2544 $page.page(); 2545 } 2546 2547/* exposed $.mobile methods */ 2548 2549 //animation complete callback 2550 $.fn.animationComplete = function( callback ) { 2551 if( $.support.cssTransitions ) { 2552 return $( this ).one( 'webkitAnimationEnd', callback ); 2553 } 2554 else{ 2555 // defer execution for consistency between webkit/non webkit 2556 setTimeout( callback, 0 ); 2557 return $( this ); 2558 } 2559 }; 2560 2561 //expose path object on $.mobile 2562 $.mobile.path = path; 2563 2564 //expose base object on $.mobile 2565 $.mobile.base = base; 2566 2567 //history stack 2568 $.mobile.urlHistory = urlHistory; 2569 2570 $.mobile.dialogHashKey = dialogHashKey; 2571 2572 //default non-animation transition handler 2573 $.mobile.noneTransitionHandler = function( name, reverse, $toPage, $fromPage ) { 2574 if ( $fromPage ) { 2575 $fromPage.removeClass( $.mobile.activePageClass ); 2576 } 2577 $toPage.addClass( $.mobile.activePageClass ); 2578 2579 return $.Deferred().resolve( name, reverse, $toPage, $fromPage ).promise(); 2580 }; 2581 2582 //default handler for unknown transitions 2583 $.mobile.defaultTransitionHandler = $.mobile.noneTransitionHandler; 2584 2585 //transition handler dictionary for 3rd party transitions 2586 $.mobile.transitionHandlers = { 2587 none: $.mobile.defaultTransitionHandler 2588 }; 2589 2590 //enable cross-domain page support 2591 $.mobile.allowCrossDomainPages = false; 2592 2593 //return the original document url 2594 $.mobile.getDocumentUrl = function(asParsedObject) { 2595 return asParsedObject ? $.extend( {}, documentUrl ) : documentUrl.href; 2596 }; 2597 2598 //return the original document base url 2599 $.mobile.getDocumentBase = function(asParsedObject) { 2600 return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href; 2601 }; 2602 2603 $.mobile._bindPageRemove = function() { 2604 var page = $(this); 2605 2606 // when dom caching is not enabled or the page is embedded bind to remove the page on hide 2607 if( !page.data("page").options.domCache 2608 && page.is(":jqmData(external-page='true')") ) { 2609 2610 page.bind( 'pagehide.remove', function() { 2611 var $this = $( this ), 2612 prEvent = new $.Event( "pageremove" ); 2613 2614 $this.trigger( prEvent ); 2615 2616 if( !prEvent.isDefaultPrevented() ){ 2617 $this.removeWithDependents(); 2618 } 2619 }); 2620 } 2621 }; 2622 2623 // Load a page into the DOM. 2624 $.mobile.loadPage = function( url, options ) { 2625 // This function uses deferred notifications to let callers 2626 // know when the page is done loading, or if an error has occurred. 2627 var deferred = $.Deferred(), 2628 2629 // The default loadPage options with overrides specified by 2630 // the caller. 2631 settings = $.extend( {}, $.mobile.loadPage.defaults, options ), 2632 2633 // The DOM element for the page after it has been loaded. 2634 page = null, 2635 2636 // If the reloadPage option is true, and the page is already 2637 // in the DOM, dupCachedPage will be set to the page element 2638 // so that it can be removed after the new version of the 2639 // page is loaded off the network. 2640 dupCachedPage = null, 2641 2642 // determine the current base url 2643 findBaseWithDefault = function(){ 2644 var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) ); 2645 return closestBase || documentBase.hrefNoHash; 2646 }, 2647 2648 // The absolute version of the URL passed into the function. This 2649 // version of the URL may contain dialog/subpage params in it. 2650 absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() ); 2651 2652 2653 // If the caller provided data, and we're using "get" request, 2654 // append the data to the URL. 2655 if ( settings.data && settings.type === "get" ) { 2656 absUrl = path.addSearchParams( absUrl, settings.data ); 2657 settings.data = undefined; 2658 } 2659 2660 // If the caller is using a "post" request, reloadPage must be true 2661 if( settings.data && settings.type === "post" ){ 2662 settings.reloadPage = true; 2663 } 2664 2665 // The absolute version of the URL minus any dialog/subpage params. 2666 // In otherwords the real URL of the page to be loaded. 2667 var fileUrl = path.getFilePath( absUrl ), 2668 2669 // The version of the Url actually stored in the data-url attribute of 2670 // the page. For embedded pages, it is just the id of the page. For pages 2671 // within the same domain as the document base, it is the site relative 2672 // path. For cross-domain pages (Phone Gap only) the entire absolute Url 2673 // used to load the page. 2674 dataUrl = path.convertUrlToDataUrl( absUrl ); 2675 2676 // Make sure we have a pageContainer to work with. 2677 settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; 2678 2679 // Check to see if the page already exists in the DOM. 2680 page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" ); 2681 2682 // If we failed to find the page, check to see if the url is a 2683 // reference to an embedded page. If so, it may have been dynamically 2684 // injected by a developer, in which case it would be lacking a data-url 2685 // attribute and in need of enhancement. 2686 if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) { 2687 page = settings.pageContainer.children( "#" + dataUrl ) 2688 .attr( "data-" + $.mobile.ns + "url", dataUrl ); 2689 } 2690 2691 // If we failed to find a page in the DOM, check the URL to see if it 2692 // refers to the first page in the application. If it isn't a reference 2693 // to the first page and refers to non-existent embedded page, error out. 2694 if ( page.length === 0 ) { 2695 if ( $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) { 2696 // Check to make sure our cached-first-page is actually 2697 // in the DOM. Some user deployed apps are pruning the first 2698 // page from the DOM for various reasons, we check for this 2699 // case here because we don't want a first-page with an id 2700 // falling through to the non-existent embedded page error 2701 // case. If the first-page is not in the DOM, then we let
2702 // things fall through to the ajax loading code below so 2703 // that it gets reloaded. 2704 if ( $.mobile.firstPage.parent().length ) { 2705 page = $( $.mobile.firstPage ); 2706 } 2707 } else if ( path.isEmbeddedPage( fileUrl ) ) { 2708 deferred.reject( absUrl, options ); 2709 return deferred.promise(); 2710 } 2711 } 2712 2713 // Reset base to the default document base. 2714 if ( base ) { 2715 base.reset(); 2716 } 2717 2718 // If the page we are interested in is already in the DOM, 2719 // and the caller did not indicate that we should force a 2720 // reload of the file, we are done. Otherwise, track the 2721 // existing page as a duplicated. 2722 if ( page.length ) { 2723 if ( !settings.reloadPage ) { 2724 enhancePage( page, settings.role ); 2725 deferred.resolve( absUrl, options, page ); 2726 return deferred.promise(); 2727 } 2728 dupCachedPage = page; 2729 } 2730 2731 var mpc = settings.pageContainer, 2732 pblEvent = new $.Event( "pagebeforeload" ), 2733 triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings }; 2734 2735 // Let listeners know we're about to load a page. 2736 mpc.trigger( pblEvent, triggerData ); 2737 2738 // If the default behavior is prevented, stop here! 2739 if( pblEvent.isDefaultPrevented() ){ 2740 return deferred.promise(); 2741 } 2742 2743 if ( settings.showLoadMsg ) { 2744 2745 // This configurable timeout allows cached pages a brief delay to load without showing a message 2746 var loadMsgDelay = setTimeout(function(){ 2747 $.mobile.showPageLoadingMsg(); 2748 }, settings.loadMsgDelay ), 2749 2750 // Shared logic for clearing timeout and removing message. 2751 hideMsg = function(){ 2752 2753 // Stop message show timer 2754 clearTimeout( loadMsgDelay ); 2755 2756 // Hide loading message 2757 $.mobile.hidePageLoadingMsg(); 2758 }; 2759 } 2760 2761 if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) { 2762 deferred.reject( absUrl, options ); 2763 } else { 2764 // Load the new page. 2765 $.ajax({ 2766 url: fileUrl, 2767 type: settings.type, 2768 data: settings.data, 2769 dataType: "html", 2770 success: function( html, textStatus, xhr ) { 2771 //pre-parse html to check for a data-url, 2772 //use it as the new fileUrl, base path, etc 2773 var all = $( "<div></div>" ), 2774 2775 //page title regexp 2776 newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1, 2777 2778 // TODO handle dialogs again 2779 pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ), 2780 dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" ); 2781 2782 2783 // data-url must be provided for the base tag so resource requests can be directed to the 2784 // correct url. loading into a temprorary element makes these requests immediately 2785 if( pageElemRegex.test( html ) 2786 && RegExp.$1 2787 && dataUrlRegex.test( RegExp.$1 ) 2788 && RegExp.$1 ) { 2789 url = fileUrl = path.getFilePath( RegExp.$1 ); 2790 } 2791 2792 if ( base ) { 2793 base.set( fileUrl ); 2794 } 2795 2796 //workaround to allow scripts to execute when included in page divs 2797 all.get( 0 ).innerHTML = html; 2798 page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first(); 2799 2800 //if page elem couldn't be found, create one and insert the body element's contents 2801 if( !page.length ){ 2802 page = $( "<div data-" + $.mobile.ns + "role='page'>" + html.split( /<\/?body[^>]*>/gmi )[1] + "</div>" ); 2803 } 2804 2805 if ( newPageTitle && !page.jqmData( "title" ) ) { 2806 if ( ~newPageTitle.indexOf( "&" ) ) { 2807 newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text(); 2808 } 2809 page.jqmData( "title", newPageTitle ); 2810 } 2811 2812 //rewrite src and href attrs to use a base url 2813 if( !$.support.dynamicBaseTag ) { 2814 var newPath = path.get( fileUrl ); 2815 page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() { 2816 var thisAttr = $( this ).is( '[href]' ) ? 'href' : 2817 $(this).is('[src]') ? 'src' : 'action', 2818 thisUrl = $( this ).attr( thisAttr ); 2819 2820 // XXX_jblas: We need to fix this so that it removes the document 2821 // base URL, and then prepends with the new page URL. 2822 //if full path exists and is same, chop it - helps IE out 2823 thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); 2824 2825 if( !/^(\w+:|#|\/)/.test( thisUrl ) ) { 2826 $( this ).attr( thisAttr, newPath + thisUrl ); 2827 } 2828 }); 2829 } 2830 2831 //append to page and enhance 2832 // TODO taging a page with external to make sure that embedded pages aren't removed 2833 // by the various page handling code is bad. Having page handling code in many 2834 // places is bad. Solutions post 1.0 2835 page 2836 .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) ) 2837 .attr( "data-" + $.mobile.ns + "external-page", true ) 2838 .appendTo( settings.pageContainer ); 2839 2840 // wait for page creation to leverage options defined on widget 2841 page.one( 'pagecreate', $.mobile._bindPageRemove ); 2842 2843 enhancePage( page, settings.role ); 2844 2845 // Enhancing the page may result in new dialogs/sub pages being inserted
2846 // into the DOM. If the original absUrl refers to a sub-page, that is the 2847 // real page we are interested in. 2848 if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) { 2849 page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" ); 2850 } 2851 2852 //bind pageHide to removePage after it's hidden, if the page options specify to do so 2853 2854 // Remove loading message. 2855 if ( settings.showLoadMsg ) { 2856 hideMsg(); 2857 } 2858 2859 // Add the page reference and xhr to our triggerData. 2860 triggerData.xhr = xhr; 2861 triggerData.textStatus = textStatus; 2862 triggerData.page = page; 2863 2864 // Let listeners know the page loaded successfully. 2865 settings.pageContainer.trigger( "pageload", triggerData ); 2866 2867 deferred.resolve( absUrl, options, page, dupCachedPage ); 2868 }, 2869 error: function( xhr, textStatus, errorThrown ) { 2870 //set base back to current path 2871 if( base ) { 2872 base.set( path.get() ); 2873 } 2874 2875 // Add error info to our triggerData. 2876 triggerData.xhr = xhr; 2877 triggerData.textStatus = textStatus; 2878 triggerData.errorThrown = errorThrown; 2879 2880 var plfEvent = new $.Event( "pageloadfailed" ); 2881 2882 // Let listeners know the page load failed. 2883 settings.pageContainer.trigger( plfEvent, triggerData ); 2884 2885 // If the default behavior is prevented, stop here! 2886 // Note that it is the responsibility of the listener/handler 2887 // that called preventDefault(), to resolve/reject the 2888 // deferred object within the triggerData. 2889 if( plfEvent.isDefaultPrevented() ){ 2890 return; 2891 } 2892 2893 // Remove loading message. 2894 if ( settings.showLoadMsg ) { 2895 2896 // Remove loading message. 2897 hideMsg(); 2898 2899 //show error message 2900 $( "<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>"+ $.mobile.pageLoadErrorMessage +"</h1></div>" ) 2901 .css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 }) 2902 .appendTo( settings.pageContainer ) 2903 .delay( 800 ) 2904 .fadeOut( 400, function() { 2905 $( this ).remove(); 2906 }); 2907 } 2908 2909 deferred.reject( absUrl, options ); 2910 } 2911 }); 2912 } 2913 2914 return deferred.promise(); 2915 }; 2916 2917 $.mobile.loadPage.defaults = { 2918 type: "get", 2919 data: undefined, 2920 reloadPage: false, 2921 role: undefined, // By default we rely on the role defined by the @data-role attribute. 2922 showLoadMsg: false, 2923 pageContainer: undefined, 2924 loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message. 2925 }; 2926 2927 // Show a specific page in the page container. 2928 $.mobile.changePage = function( toPage, options ) { 2929 // If we are in the midst of a transition, queue the current request. 2930 // We'll call changePage() once we're done with the current transition to 2931 // service the request. 2932 if( isPageTransitioning ) { 2933 pageTransitionQueue.unshift( arguments ); 2934 return; 2935 } 2936 2937 var settings = $.extend( {}, $.mobile.changePage.defaults, options ); 2938 2939 // Make sure we have a pageContainer to work with. 2940 settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; 2941 2942 // Make sure we have a fromPage. 2943 settings.fromPage = settings.fromPage || $.mobile.activePage; 2944 2945 var mpc = settings.pageContainer, 2946 pbcEvent = new $.Event( "pagebeforechange" ), 2947 triggerData = { toPage: toPage, options: settings }; 2948 2949 // Let listeners know we're about to change the current page. 2950 mpc.trigger( pbcEvent, triggerData ); 2951 2952 // If the default behavior is prevented, stop here! 2953 if( pbcEvent.isDefaultPrevented() ){ 2954 return; 2955 } 2956 2957 // We allow "pagebeforechange" observers to modify the toPage in the trigger 2958 // data to allow for redirects. Make sure our toPage is updated. 2959 2960 toPage = triggerData.toPage; 2961 2962 // Set the isPageTransitioning flag to prevent any requests from 2963 // entering this method while we are in the midst of loading a page 2964 // or transitioning. 2965 2966 isPageTransitioning = true; 2967 2968 // If the caller passed us a url, call loadPage() 2969 // to make sure it is loaded into the DOM. We'll listen 2970 // to the promise object it returns so we know when 2971 // it is done loading or if an error ocurred. 2972 if ( typeof toPage == "string" ) { 2973 $.mobile.loadPage( toPage, settings ) 2974 .done(function( url, options, newPage, dupCachedPage ) { 2975 isPageTransitioning = false;
2976 options.duplicateCachedPage = dupCachedPage; 2977 $.mobile.changePage( newPage, options ); 2978 }) 2979 .fail(function( url, options ) { 2980 isPageTransitioning = false; 2981 2982 //clear out the active button state 2983 removeActiveLinkClass( true ); 2984 2985 //release transition lock so navigation is free again 2986 releasePageTransitionLock(); 2987 settings.pageContainer.trigger( "pagechangefailed", triggerData ); 2988 }); 2989 return; 2990 } 2991 2992 // If we are going to the first-page of the application, we need to make 2993 // sure settings.dataUrl is set to the application document url. This allows 2994 // us to avoid generating a document url with an id hash in the case where the 2995 // first-page of the document has an id attribute specified. 2996 if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) { 2997 settings.dataUrl = documentUrl.hrefNoHash; 2998 } 2999 3000 // The caller passed us a real page DOM element. Update our 3001 // internal state and then trigger a transition to the page. 3002 var fromPage = settings.fromPage, 3003 url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ), 3004 // The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path 3005 pageUrl = url, 3006 fileUrl = path.getFilePath( url ), 3007 active = urlHistory.getActive(), 3008 activeIsInitialPage = urlHistory.activeIndex === 0, 3009 historyDir = 0, 3010 pageTitle = document.title, 3011 isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog"; 3012 3013 // By default, we prevent changePage requests when the fromPage and toPage 3014 // are the same element, but folks that generate content manually/dynamically 3015 // and reuse pages want to be able to transition to the same page. To allow 3016 // this, they will need to change the default value of allowSamePageTransition 3017 // to true, *OR*, pass it in as an option when they manually call changePage(). 3018 // It should be noted that our default transition animations assume that the 3019 // formPage and toPage are different elements, so they may behave unexpectedly. 3020 // It is up to the developer that turns on the allowSamePageTransitiona option 3021 // to either turn off transition animations, or make sure that an appropriate 3022 // animation transition is used. 3023 if( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) { 3024 isPageTransitioning = false; 3025 mpc.trigger( "pagechange", triggerData ); 3026 return; 3027 } 3028 3029 // We need to make sure the page we are given has already been enhanced. 3030 enhancePage( toPage, settings.role ); 3031 3032 // If the changePage request was sent from a hashChange event, check to see if the 3033 // page is already within the urlHistory stack. If so, we'll assume the user hit 3034 // the forward/back button and will try to match the transition accordingly. 3035 if( settings.fromHashChange ) { 3036 urlHistory.directHashChange({ 3037 currentUrl: url, 3038 isBack: function() { historyDir = -1; }, 3039 isForward: function() { historyDir = 1; } 3040 }); 3041 } 3042 3043 // Kill the keyboard. 3044 // XXX_jblas: We need to stop crawling the entire document to kill focus. Instead, 3045 // we should be tracking focus with a live() handler so we already have 3046 // the element in hand at this point. 3047 // Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement 3048 // is undefined when we are in an IFrame. 3049 try { 3050 if(document.activeElement && document.activeElement.nodeName.toLowerCase() != 'body') { 3051 $(document.activeElement).blur(); 3052 } else { 3053 $( "input:focus, textarea:focus, select:focus" ).blur(); 3054 } 3055 } catch(e) {} 3056 3057 // If we're displaying the page as a dialog, we don't want the url 3058 // for the dialog content to be used in the hash. Instead, we want 3059 // to append the dialogHashKey to the url of the current page. 3060 if ( isDialog && active ) { 3061 // on the initial page load active.url is undefined and in that case should 3062 // be an empty string. Moving the undefined -> empty string back into 3063 // urlHistory.addNew seemed imprudent given undefined better represents 3064 // the url state 3065 url = ( active.url || "" ) + dialogHashKey; 3066 } 3067 3068 // Set the location hash. 3069 if( settings.changeHash !== false && url ) { 3070 //disable hash listening temporarily 3071 urlHistory.ignoreNextHashChange = true; 3072 //update hash and history 3073 path.set( url ); 3074 } 3075 3076 // if title element wasn't found, try the page div data attr too 3077 // If this is a deep-link or a reload ( active === undefined ) then just use pageTitle 3078 var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children(":jqmData(role='header')").find(".ui-title" ).getEncodedText(); 3079 if( !!newPageTitle && pageTitle == document.title ) { 3080 pageTitle = newPageTitle; 3081 } 3082 if ( !toPage.jqmData( "title" ) ) { 3083 toPage.jqmData( "title", pageTitle ); 3084 } 3085 3086 // Make sure we have a transition defined. 3087 settings.transition = settings.transition 3088 || ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) 3089 || ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition ); 3090
3091 //add page to history stack if it's not back or forward 3092 if( !historyDir ) { 3093 urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role ); 3094 } 3095 3096 //set page title 3097 document.title = urlHistory.getActive().title; 3098 3099 //set "toPage" as activePage 3100 $.mobile.activePage = toPage; 3101 3102 // If we're navigating back in the URL history, set reverse accordingly. 3103 settings.reverse = settings.reverse || historyDir < 0; 3104 3105 transitionPages( toPage, fromPage, settings.transition, settings.reverse ) 3106 .done(function() { 3107 removeActiveLinkClass(); 3108 3109 //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden 3110 if ( settings.duplicateCachedPage ) { 3111 settings.duplicateCachedPage.remove(); 3112 } 3113 3114 //remove initial build class (only present on first pageshow) 3115 $html.removeClass( "ui-mobile-rendering" ); 3116 3117 releasePageTransitionLock(); 3118 3119 // Let listeners know we're all done changing the current page. 3120 mpc.trigger( "pagechange", triggerData ); 3121 }); 3122 }; 3123 3124 $.mobile.changePage.defaults = { 3125 transition: undefined, 3126 reverse: false, 3127 changeHash: true, 3128 fromHashChange: false, 3129 role: undefined, // By default we rely on the role defined by the @data-role attribute. 3130 duplicateCachedPage: undefined, 3131 pageContainer: undefined, 3132 showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage 3133 dataUrl: undefined, 3134 fromPage: undefined, 3135 allowSamePageTransition: false 3136 }; 3137 3138/* Event Bindings - hashchange, submit, and click */ 3139 function findClosestLink( ele ) 3140 { 3141 while ( ele ) { 3142 if ( ele.nodeName.toLowerCase() == "a" ) { 3143 break; 3144 } 3145 ele = ele.parentNode; 3146 } 3147 return ele; 3148 } 3149 3150 // The base URL for any given element depends on the page it resides in. 3151 function getClosestBaseUrl( ele ) 3152 { 3153 // Find the closest page and extract out its url. 3154 var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ), 3155 base = documentBase.hrefNoHash; 3156 3157 if ( !url || !path.isPath( url ) ) { 3158 url = base; 3159 } 3160 3161 return path.makeUrlAbsolute( url, base); 3162 } 3163 3164 3165 //The following event bindings should be bound after mobileinit has been triggered 3166 //the following function is called in the init file 3167 $.mobile._registerInternalEvents = function(){ 3168 3169 //bind to form submit events, handle with Ajax 3170 $( "form" ).live('submit', function( event ) { 3171 var $this = $( this ); 3172 if( !$.mobile.ajaxEnabled || 3173 $this.is( ":jqmData(ajax='false')" ) ) { 3174 return; 3175 } 3176 3177 var type = $this.attr( "method" ), 3178 target = $this.attr( "target" ), 3179 url = $this.attr( "action" ); 3180 3181 // If no action is specified, browsers default to using the 3182 // URL of the document containing the form. Since we dynamically 3183 // pull in pages from external documents, the form should submit 3184 // to the URL for the source document of the page containing 3185 // the form. 3186 if ( !url ) { 3187 // Get the @data-url for the page containing the form. 3188 url = getClosestBaseUrl( $this ); 3189 if ( url === documentBase.hrefNoHash ) { 3190 // The url we got back matches the document base, 3191 // which means the page must be an internal/embedded page, 3192 // so default to using the actual document url as a browser 3193 // would. 3194 url = documentUrl.hrefNoSearch; 3195 } 3196 } 3197 3198 url = path.makeUrlAbsolute( url, getClosestBaseUrl($this) ); 3199 3200 //external submits use regular HTTP 3201 if( path.isExternal( url ) || target ) { 3202 return; 3203 } 3204 3205 $.mobile.changePage( 3206 url, 3207 { 3208 type: type && type.length && type.toLowerCase() || "get", 3209 data: $this.serialize(), 3210 transition: $this.jqmData( "transition" ), 3211 direction: $this.jqmData( "direction" ), 3212 reloadPage: true 3213 } 3214 ); 3215 event.preventDefault(); 3216 }); 3217 3218 //add active state on vclick 3219 $( document ).bind( "vclick", function( event ) { 3220 // if this isn't a left click we don't care. Its important to note 3221 // that when the virtual event is generated it will create 3222 if ( event.which > 1 || !$.mobile.linkBindingEnabled ){ 3223 return; 3224 } 3225 3226 var link = findClosestLink( event.target ); 3227 if ( link ) { 3228 if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) { 3229 removeActiveLinkClass( true ); 3230 $activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" ); 3231 $activeClickedLink.addClass( $.mobile.activeBtnClass ); 3232 $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur(); 3233 } 3234 } 3235 }); 3236 3237 // click routing - direct to HTTP or Ajax, accordingly 3238 $( document ).bind( "click", function( event ) { 3239 if( !$.mobile.linkBindingEnabled ){ 3240 return; 3241 } 3242 3243 var link = findClosestLink( event.target ); 3244 3245 // If there is no link associated with the click or its not a left 3246 // click we want to ignore the click 3247 if ( !link || event.which > 1) { 3248 return; 3249 } 3250 3251 var $link = $( link ), 3252 //remove active link class if external (then it won't be there if you come back) 3253 httpCleanup = function(){ 3254 window.setTimeout( function() { removeActiveLinkClass( true ); }, 200 ); 3255 }; 3256 3257 //if there's a data-rel=back attr, go back in history 3258 if( $link.is( ":jqmData(rel='back')" ) ) { 3259 window.history.back(); 3260 return false;
3261 } 3262 3263 var baseUrl = getClosestBaseUrl( $link ), 3264 3265 //get href, if defined, otherwise default to empty hash 3266 href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl ); 3267 3268 //if ajax is disabled, exit early 3269 if( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ){ 3270 httpCleanup(); 3271 //use default click handling 3272 return; 3273 } 3274 3275 // XXX_jblas: Ideally links to application pages should be specified as 3276 // an url to the application document with a hash that is either 3277 // the site relative path or id to the page. But some of the 3278 // internal code that dynamically generates sub-pages for nested 3279 // lists and select dialogs, just write a hash in the link they 3280 // create. This means the actual URL path is based on whatever 3281 // the current value of the base tag is at the time this code 3282 // is called. For now we are just assuming that any url with a 3283 // hash in it is an application page reference. 3284 if ( href.search( "#" ) != -1 ) { 3285 href = href.replace( /[^#]*#/, "" ); 3286 if ( !href ) { 3287 //link was an empty hash meant purely 3288 //for interaction, so we ignore it. 3289 event.preventDefault(); 3290 return; 3291 } else if ( path.isPath( href ) ) { 3292 //we have apath so make it the href we want to load. 3293 href = path.makeUrlAbsolute( href, baseUrl ); 3294 } else { 3295 //we have a simple id so use the documentUrl as its base. 3296 href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash ); 3297 } 3298 } 3299 3300 // Should we handle this link, or let the browser deal with it? 3301 var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ), 3302 3303 // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR 3304 // requests if the document doing the request was loaded via the file:// protocol. 3305 // This is usually to allow the application to "phone home" and fetch app specific 3306 // data. We normally let the browser handle external/cross-domain urls, but if the 3307 // allowCrossDomainPages option is true, we will allow cross-domain http/https 3308 // requests to go through our page loading logic. 3309 isCrossDomainPageLoad = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search( /^https?:/ ) != -1 ), 3310 3311 //check for protocol or rel and its not an embedded page 3312 //TODO overlap in logic from isExternal, rel=external check should be 3313 // moved into more comprehensive isExternalLink 3314 isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad ); 3315 3316 if( isExternal ) { 3317 httpCleanup(); 3318 //use default click handling 3319 return; 3320 } 3321 3322 //use ajax 3323 var transition = $link.jqmData( "transition" ), 3324 direction = $link.jqmData( "direction" ), 3325 reverse = ( direction && direction === "reverse" ) || 3326 // deprecated - remove by 1.0 3327 $link.jqmData( "back" ), 3328 3329 //this may need to be more specific as we use data-rel more 3330 role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined; 3331 3332 $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } ); 3333 event.preventDefault(); 3334 }); 3335
3336 //prefetch pages when anchors with data-prefetch are encountered 3337 $( ".ui-page" ).live( "pageshow.prefetch", function() { 3338 var urls = []; 3339 $( this ).find( "a:jqmData(prefetch)" ).each(function(){ 3340 var $link = $(this), 3341 url = $link.attr( "href" ); 3342 3343 if ( url && $.inArray( url, urls ) === -1 ) { 3344 urls.push( url ); 3345 3346 $.mobile.loadPage( url, {role: $link.attr("data-" + $.mobile.ns + "rel")} ); 3347 } 3348 }); 3349 }); 3350 3351 $.mobile._handleHashChange = function( hash ) { 3352 //find first page via hash 3353 var to = path.stripHash( hash ), 3354 //transition is false if it's the first page, undefined otherwise (and may be overridden by default) 3355 transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined, 3356 3357 // default options for the changPage calls made after examining the current state 3358 // of the page and the hash 3359 changePageOptions = { 3360 transition: transition, 3361 changeHash: false, 3362 fromHashChange: true 3363 }; 3364 3365 //if listening is disabled (either globally or temporarily), or it's a dialog hash 3366 if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) { 3367 urlHistory.ignoreNextHashChange = false; 3368 return; 3369 } 3370 3371 // special case for dialogs 3372 if( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 ) { 3373 3374 // If current active page is not a dialog skip the dialog and continue 3375 // in the same direction 3376 if(!$.mobile.activePage.is( ".ui-dialog" )) { 3377 //determine if we're heading forward or backward and continue accordingly past 3378 //the current dialog 3379 urlHistory.directHashChange({ 3380 currentUrl: to, 3381 isBack: function() { window.history.back(); }, 3382 isForward: function() { window.history.forward(); } 3383 }); 3384 3385 // prevent changePage() 3386 return; 3387 } else { 3388 // if the current active page is a dialog and we're navigating 3389 // to a dialog use the dialog objected saved in the stack 3390 urlHistory.directHashChange({ 3391 currentUrl: to, 3392 3393 // regardless of the direction of the history change 3394 // do the following 3395 either: function( isBack ) { 3396 var active = $.mobile.urlHistory.getActive(); 3397 3398 to = active.pageUrl; 3399 3400 // make sure to set the role, transition and reversal 3401 // as most of this is lost by the domCache cleaning 3402 $.extend( changePageOptions, { 3403 role: active.role, 3404 transition: active.transition, 3405 reverse: isBack 3406 }); 3407 } 3408 }); 3409 } 3410 } 3411 3412 //if to is defined, load it 3413 if ( to ) { 3414 // At this point, 'to' can be one of 3 things, a cached page element from 3415 // a history stack entry, an id, or site-relative/absolute URL. If 'to' is 3416 // an id, we need to resolve it against the documentBase, not the location.href, 3417 // since the hashchange could've been the result of a forward/backward navigation 3418 // that crosses from an external page/dialog to an internal page/dialog. 3419 to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to; 3420 $.mobile.changePage( to, changePageOptions ); 3421 } else { 3422 //there's no hash, go to the first page in the dom 3423 $.mobile.changePage( $.mobile.firstPage, changePageOptions ); 3424 } 3425 }; 3426 3427 //hashchange event handler 3428 $window.bind( "hashchange", function( e, triggered ) { 3429 $.mobile._handleHashChange( location.hash ); 3430 }); 3431 3432 //set page min-heights to be device specific 3433 $( document ).bind( "pageshow", resetActivePageHeight ); 3434 $( window ).bind( "throttledresize", resetActivePageHeight ); 3435 3436 };//_registerInternalEvents callback 3437 3438})( jQuery ); 3439/* 3440* history.pushState support, layered on top of hashchange 3441*/ 3442 3443( function( $, window ) { 3444 // For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents 3445 // Scope self to pushStateHandler so we can reference it sanely within the 3446 // methods handed off as event handlers 3447 var pushStateHandler = {}, 3448 self = pushStateHandler, 3449 $win = $( window ), 3450 url = $.mobile.path.parseUrl( location.href ); 3451 3452 $.extend( pushStateHandler, { 3453 // TODO move to a path helper, this is rather common functionality 3454 initialFilePath: (function() { 3455 return url.pathname + url.search; 3456 })(), 3457 3458 initialHref: url.hrefNoHash, 3459 3460 // Flag for tracking if a Hashchange naturally occurs after each popstate + replace 3461 hashchangeFired: false, 3462 3463 state: function() { 3464 return { 3465 hash: location.hash || "#" + self.initialFilePath, 3466 title: document.title, 3467 3468 // persist across refresh 3469 initialHref: self.initialHref 3470 }; 3471 }, 3472 3473 resetUIKeys: function( url ) { 3474 var dialog = $.mobile.dialogHashKey, 3475 subkey = "&" + $.mobile.subPageUrlKey, 3476 dialogIndex = url.indexOf( dialog ); 3477 3478 if( dialogIndex > -1 ) { 3479 url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex ); 3480 } else if( url.indexOf( subkey ) > -1 ) { 3481 url = url.split( subkey ).join( "#" + subkey ); 3482 } 3483 3484 return url; 3485 }, 3486
3487 // TODO sort out a single barrier to hashchange functionality 3488 nextHashChangePrevented: function( value ) { 3489 $.mobile.urlHistory.ignoreNextHashChange = value; 3490 self.onHashChangeDisabled = value; 3491 }, 3492 3493 // on hash change we want to clean up the url 3494 // NOTE this takes place *after* the vanilla navigation hash change 3495 // handling has taken place and set the state of the DOM 3496 onHashChange: function( e ) { 3497 // disable this hash change 3498 if( self.onHashChangeDisabled ){ 3499 return; 3500 } 3501 3502 var href, state, 3503 hash = location.hash, 3504 isPath = $.mobile.path.isPath( hash ), 3505 resolutionUrl = isPath ? location.href : $.mobile.getDocumentUrl(); 3506 hash = isPath ? hash.replace( "#", "" ) : hash; 3507 3508 // propulate the hash when its not available 3509 state = self.state(); 3510 3511 // make the hash abolute with the current href 3512 href = $.mobile.path.makeUrlAbsolute( hash, resolutionUrl ); 3513 3514 if ( isPath ) { 3515 href = self.resetUIKeys( href ); 3516 } 3517 3518 // replace the current url with the new href and store the state 3519 // Note that in some cases we might be replacing an url with the 3520 // same url. We do this anyways because we need to make sure that 3521 // all of our history entries have a state object associated with 3522 // them. This allows us to work around the case where window.history.back() 3523 // is called to transition from an external page to an embedded page.
3524 // In that particular case, a hashchange event is *NOT* generated by the browser. 3525 // Ensuring each history entry has a state object means that onPopState() 3526 // will always trigger our hashchange callback even when a hashchange event 3527 // is not fired. 3528 history.replaceState( state, document.title, href ); 3529 }, 3530 3531 // on popstate (ie back or forward) we need to replace the hash that was there previously 3532 // cleaned up by the additional hash handling 3533 onPopState: function( e ) { 3534 var poppedState = e.originalEvent.state, holdnexthashchange = false; 3535 3536 // if there's no state its not a popstate we care about, ie chrome's initial popstate 3537 // or forward popstate 3538 if( poppedState ) { 3539 // disable any hashchange triggered by the browser 3540 self.nextHashChangePrevented( true ); 3541 3542 // defer our manual hashchange until after the browser fired 3543 // version has come and gone 3544 setTimeout(function() { 3545 // make sure that the manual hash handling takes place 3546 self.nextHashChangePrevented( false ); 3547 3548 // change the page based on the hash 3549 $.mobile._handleHashChange( poppedState.hash ); 3550 }, 100); 3551 } 3552 }, 3553 3554 init: function() { 3555 $win.bind( "hashchange", self.onHashChange ); 3556 3557 // Handle popstate events the occur through history changes 3558 $win.bind( "popstate", self.onPopState ); 3559 3560 // if there's no hash, we need to replacestate for returning to home 3561 if ( location.hash === "" ) { 3562 history.replaceState( self.state(), document.title, location.href ); 3563 } 3564 } 3565 }); 3566 3567 $( function() { 3568 if( $.mobile.pushStateEnabled && $.support.pushState ){ 3569 pushStateHandler.init(); 3570 } 3571 }); 3572})( jQuery, this ); 3573/* 3574* "transitions" plugin - Page change tranistions 3575*/ 3576 3577(function( $, window, undefined ) { 3578 3579function css3TransitionHandler( name, reverse, $to, $from ) { 3580 3581 var deferred = new $.Deferred(), 3582 reverseClass = reverse ? " reverse" : "", 3583 viewportClass = "ui-mobile-viewport-transitioning viewport-" + name, 3584 doneFunc = function() { 3585 3586 $to.add( $from ).removeClass( "out in reverse " + name ); 3587 3588 if ( $from && $from[ 0 ] !== $to[ 0 ] ) { 3589 $from.removeClass( $.mobile.activePageClass ); 3590 } 3591 3592 $to.parent().removeClass( viewportClass ); 3593 3594 deferred.resolve( name, reverse, $to, $from ); 3595 }; 3596 3597 $to.animationComplete( doneFunc ); 3598 3599 $to.parent().addClass( viewportClass ); 3600 3601 if ( $from ) { 3602 $from.addClass( name + " out" + reverseClass ); 3603 } 3604 $to.addClass( $.mobile.activePageClass + " " + name + " in" + reverseClass ); 3605 3606 return deferred.promise(); 3607} 3608 3609// Make our transition handler public. 3610$.mobile.css3TransitionHandler = css3TransitionHandler; 3611 3612// If the default transition handler is the 'none' handler, replace it with our handler. 3613if ( $.mobile.defaultTransitionHandler === $.mobile.noneTransitionHandler ) { 3614 $.mobile.defaultTransitionHandler = css3TransitionHandler; 3615} 3616 3617})( jQuery, this ); 3618/* 3619* "degradeInputs" plugin - degrades inputs to another type after custom enhancements are made. 3620*/ 3621 3622(function( $, undefined ) { 3623 3624$.mobile.page.prototype.options.degradeInputs = { 3625 color: false, 3626 date: false, 3627 datetime: false, 3628 "datetime-local": false, 3629 email: false, 3630 month: false, 3631 number: false, 3632 range: "number", 3633 search: "text", 3634 tel: false, 3635 time: false, 3636 url: false, 3637 week: false 3638}; 3639 3640 3641//auto self-init widgets 3642$( document ).bind( "pagecreate create", function( e ){ 3643 3644 var page = $(e.target).closest(':jqmData(role="page")').data("page"), options; 3645 3646 if( !page ) { 3647 return; 3648 } 3649 3650 options = page.options; 3651 3652 // degrade inputs to avoid poorly implemented native functionality 3653 $( e.target ).find( "input" ).not( page.keepNativeSelector() ).each(function() { 3654 var $this = $( this ), 3655 type = this.getAttribute( "type" ), 3656 optType = options.degradeInputs[ type ] || "text"; 3657 3658 if ( options.degradeInputs[ type ] ) { 3659 var html = $( "<div>" ).html( $this.clone() ).html(), 3660 // In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead 3661 hasType = html.indexOf( " type=" ) > -1, 3662 findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/, 3663 repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" ); 3664 3665 $this.replaceWith( html.replace( findstr, repstr ) ); 3666 } 3667 }); 3668 3669}); 3670 3671}
3671)( jQuery );/* 3672* "dialog" plugin. 3673*/ 3674 3675(function( $, window, undefined ) { 3676 3677$.widget( "mobile.dialog", $.mobile.widget, { 3678 options: { 3679 closeBtnText : "Close", 3680 overlayTheme : "a", 3681 initSelector : ":jqmData(role='dialog')" 3682 }, 3683 _create: function() { 3684 var self = this, 3685 $el = this.element, 3686 headerCloseButton = $( "<a href='#' data-" + $.mobile.ns + "icon='delete' data-" + $.mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText + "</a>" ); 3687 3688 $el.addClass( "ui-overlay-" + this.options.overlayTheme ); 3689 3690 // Class the markup for dialog styling 3691 // Set aria role 3692 $el.attr( "role", "dialog" ) 3693 .addClass( "ui-dialog" ) 3694 .find( ":jqmData(role='header')" ) 3695 .addClass( "ui-corner-top ui-overlay-shadow" ) 3696 .prepend( headerCloseButton ) 3697 .end() 3698 .find( ":jqmData(role='content'),:jqmData(role='footer')" ) 3699 .addClass( "ui-overlay-shadow" ) 3700 .last() 3701 .addClass( "ui-corner-bottom" ); 3702 3703 // this must be an anonymous function so that select menu dialogs can replace 3704 // the close method. This is a change from previously just defining data-rel=back 3705 // on the button and letting nav handle it 3706 headerCloseButton.bind( "vclick", function() { 3707 self.close(); 3708 }); 3709 3710 /* bind events 3711 - clicks and submits should use the closing transition that the dialog opened with 3712 unless a data-transition is specified on the link/form 3713 - if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally 3714 */ 3715 $el.bind( "vclick submit", function( event ) { 3716 var $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" ), 3717 active; 3718 3719 if ( $target.length && !$target.jqmData( "transition" ) ) { 3720 3721 active = $.mobile.urlHistory.getActive() || {}; 3722 3723 $target.attr( "data-" + $.mobile.ns + "transition", ( active.transition || $.mobile.defaultDialogTransition ) ) 3724 .attr( "data-" + $.mobile.ns + "direction", "reverse" ); 3725 } 3726 }) 3727 .bind( "pagehide", function() { 3728 $( this ).find( "." + $.mobile.activeBtnClass ).removeClass( $.mobile.activeBtnClass ); 3729 }); 3730 }, 3731 3732 // Close method goes back in history 3733 close: function() { 3734 window.history.back(); 3735 } 3736}); 3737 3738//auto self-init widgets 3739$( $.mobile.dialog.prototype.options.initSelector ).live( "pagecreate", function(){ 3740 $( this ).dialog(); 3741}); 3742 3743})( jQuery, this ); 3744/* 3745* This plugin handles theming and layout of headers, footers, and content areas 3746*/ 3747 3748(function( $, undefined ) { 3749 3750$.mobile.page.prototype.options.backBtnText = "Back"; 3751$.mobile.page.prototype.options.addBackBtn = false; 3752$.mobile.page.prototype.options.backBtnTheme = null; 3753$.mobile.page.prototype.options.headerTheme = "a"; 3754$.mobile.page.prototype.options.footerTheme = "a"; 3755$.mobile.page.prototype.options.contentTheme = null; 3756 3757$( ":jqmData(role='page'), :jqmData(role='dialog')" ).live( "pagecreate", function( e ) { 3758 3759 var $page = $( this ), 3760 o = $page.data( "page" ).options, 3761 pageRole = $page.jqmData( "role" ), 3762 pageTheme = o.theme; 3763 3764 $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", this ).each(function() { 3765 var $this = $( this ), 3766 role = $this.jqmData( "role" ), 3767 theme = $this.jqmData( "theme" ), 3768 contentTheme = theme || o.contentTheme || pageTheme, 3769 $headeranchors, 3770 leftbtn, 3771 rightbtn, 3772 backBtn; 3773 3774 $this.addClass( "ui-" + role ); 3775 3776 //apply theming and markup modifications to page,header,content,footer 3777 if ( role === "header" || role === "footer" ) { 3778 3779 var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme; 3780 3781 $this 3782 //add theme class 3783 .addClass( "ui-bar-" + thisTheme ) 3784 // Add ARIA role 3785 .attr( "role", role === "header" ? "banner" : "contentinfo" ); 3786 3787 // Right,left buttons 3788 $headeranchors = $this.children( "a" ); 3789 leftbtn = $headeranchors.hasClass( "ui-btn-left" ); 3790 rightbtn = $headeranchors.hasClass( "ui-btn-right" ); 3791 3792 leftbtn = leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length; 3793 3794 rightbtn = rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length; 3795 3796 // Auto-add back btn on pages beyond first view 3797 if ( o.addBackBtn && 3798 role === "header" && 3799 $( ".ui-page" ).length > 1 && 3800 $this.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) && 3801 !leftbtn ) { 3802 3803 backBtn = $( "<a href='#' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" ) 3804 // If theme is provided, override default inheritance 3805 .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme ) 3806 .prependTo( $this ); 3807 } 3808 3809 // Page title 3810 $this.children( "h1, h2, h3, h4, h5, h6" ) 3811 .addClass( "ui-title" ) 3812 // Regardless of h element number in src, it becomes h1 for the enhanced page 3813 .attr({ 3814 "tabindex": "0", 3815 "role": "heading", 3816 "aria-level": "1" 3817 }); 3818 3819 } else if ( role === "content" ) { 3820 if ( contentTheme && pageRole === "dialog" ) { 3821 $this.addClass( "ui-body-" + ( contentTheme ) ); 3822 } 3823 3824 // Add ARIA role 3825 $this.attr( "role", "main" ); 3826 } 3827 }); 3828}); 3829 3830})( jQuery );/* 3831* "collapsible" plugin 3832*/ 3833 3834(function( $, undefined ) { 3835 3836$.widget( "mobile.collapsible", $.mobile.widget, { 3837 options: { 3838 expandCueText: " click to expand contents", 3839 collapseCueText: " click to collapse contents", 3840 collapsed: true, 3841 heading: "h1,h2,h3,h4,h5,h6,legend", 3842 theme: null, 3843 contentTheme: null, 3844 iconTheme: "d", 3845 initSelector: ":jqmData(role='collapsible')" 3846 }, 3847 _create: function() { 3848 3849 var $el = this.element, 3850 o = this.options,
3851 collapsible = $el.addClass( "ui-collapsible" ), 3852 collapsibleHeading = $el.children( o.heading ).first(), 3853 collapsibleContent = collapsible.wrapInner( "<div class='ui-collapsible-content'></div>" ).find( ".ui-collapsible-content" ), 3854 collapsibleSet = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ), 3855 collapsiblesInSet = collapsibleSet.children( ":jqmData(role='collapsible')" ); 3856 3857 // Replace collapsibleHeading if it's a legend 3858 if ( collapsibleHeading.is( "legend" ) ) { 3859 collapsibleHeading = $( "<div role='heading'>"+ collapsibleHeading.html() +"</div>" ).insertBefore( collapsibleHeading ); 3860 collapsibleHeading.next().remove(); 3861 } 3862 3863 // If we are in a collapsible set 3864 if ( collapsibleSet.length ) { 3865 // Inherit the theme from collapsible-set 3866 if ( !o.theme ) { 3867 o.theme = collapsibleSet.jqmData( "theme" ); 3868 } 3869 // Inherit the content-theme from collapsible-set 3870 if ( !o.contentTheme ) { 3871 o.contentTheme = collapsibleSet.jqmData( "content-theme" ); 3872 } 3873 } 3874 3875 collapsibleContent.addClass( ( o.contentTheme ) ? ( "ui-body-" + o.contentTheme ) : ""); 3876 3877 collapsibleHeading 3878 //drop heading in before content 3879 .insertBefore( collapsibleContent ) 3880 //modify markup & attributes 3881 .addClass( "ui-collapsible-heading" ) 3882 .append( "<span class='ui-collapsible-heading-status'></span>" ) 3883 .wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" ) 3884 .find( "a" ) 3885 .first() 3886 .buttonMarkup({ 3887 shadow: false, 3888 corners: false, 3889 iconPos: "left", 3890 icon: "plus", 3891 theme: o.theme 3892 }); 3893 3894 if ( !collapsibleSet.length ) { 3895 collapsibleHeading 3896 .find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) ) 3897 .addClass( "ui-corner-top ui-corner-bottom" ); 3898 } else { 3899 // If we are in a collapsible set 3900 3901 // Initialize the collapsible set if it's not already initialized 3902 if ( !collapsibleSet.jqmData( "collapsiblebound" ) ) { 3903 3904 collapsibleSet 3905 .jqmData( "collapsiblebound", true ) 3906 .bind( "expand", function( event ) { 3907 3908 $( event.target ) 3909 .closest( ".ui-collapsible" ) 3910 .siblings( ".ui-collapsible" ) 3911 .trigger( "collapse" ); 3912 3913 }); 3914 } 3915 3916 collapsiblesInSet.first() 3917 .find( "a" ) 3918 .first() 3919 .addClass( "ui-corner-top" ) 3920 .find( ".ui-btn-inner" ) 3921 .addClass( "ui-corner-top" ); 3922 3923 collapsiblesInSet.last() 3924 .jqmData( "collapsible-last", true ) 3925 .find( "a" ) 3926 .first() 3927 .addClass( "ui-corner-bottom" ) 3928 .find( ".ui-btn-inner" ) 3929 .addClass( "ui-corner-bottom" ); 3930 3931 3932 if ( collapsible.jqmData( "collapsible-last" ) ) { 3933 collapsibleHeading 3934 .find( "a" ).first().add ( collapsibleHeading.find( ".ui-btn-inner" ) ) 3935 .addClass( "ui-corner-bottom" ); 3936 } 3937 } 3938 3939 //events 3940 collapsible 3941 .bind( "expand collapse", function( event ) { 3942 if ( !event.isDefaultPrevented() ) { 3943 3944 event.preventDefault(); 3945 3946 var $this = $( this ), 3947 isCollapse = ( event.type === "collapse" ), 3948 contentTheme = o.contentTheme; 3949 3950 collapsibleHeading 3951 .toggleClass( "ui-collapsible-heading-collapsed", isCollapse) 3952 .find( ".ui-collapsible-heading-status" ) 3953 .text( isCollapse ? o.expandCueText : o.collapseCueText ) 3954 .end() 3955 .find( ".ui-icon" ) 3956 .toggleClass( "ui-icon-minus", !isCollapse ) 3957 .toggleClass( "ui-icon-plus", isCollapse ); 3958 3959 $this.toggleClass( "ui-collapsible-collapsed", isCollapse ); 3960 collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse ); 3961 3962 if ( contentTheme && ( !collapsibleSet.length || collapsible.jqmData( "collapsible-last" ) ) ) { 3963 collapsibleHeading 3964 .find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) ) 3965 .toggleClass( "ui-corner-bottom", isCollapse ); 3966 collapsibleContent.toggleClass( "ui-corner-bottom", !isCollapse ); 3967 } 3968 collapsibleContent.trigger( "updatelayout" ); 3969 } 3970 }) 3971 .trigger( o.collapsed ? "collapse" : "expand" ); 3972 3973 collapsibleHeading 3974 .bind( "click", function( event ) { 3975 3976 var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ? 3977 "expand" : "collapse"; 3978 3979 collapsible.trigger( type ); 3980 3981 event.preventDefault(); 3982 }); 3983 } 3984}); 3985 3986//auto self-init widgets 3987$( document ).bind( "pagecreate create", function( e ){ 3988 $( $.mobile.collapsible.prototype.options.initSelector, e.target ).collapsible(); 3989}); 3990 3991})( jQuery ); 3992/* 3993* "fieldcontain" plugin - simple class additions to make form row
3993separators 3994*/ 3995 3996(function( $, undefined ) { 3997 3998$.fn.fieldcontain = function( options ) { 3999 return this.addClass( "ui-field-contain ui-body ui-br" ); 4000}; 4001 4002//auto self-init widgets 4003$( document ).bind( "pagecreate create", function( e ){ 4004 $( ":jqmData(role='fieldcontain')", e.target ).fieldcontain(); 4005}); 4006 4007})( jQuery );/* 4008* plugin for creating CSS grids 4009*/ 4010 4011(function( $, undefined ) { 4012 4013$.fn.grid = function( options ) { 4014 return this.each(function() { 4015 4016 var $this = $( this ), 4017 o = $.extend({ 4018 grid: null 4019 },options), 4020 $kids = $this.children(), 4021 gridCols = {solo:1, a:2, b:3, c:4, d:5}, 4022 grid = o.grid, 4023 iterator; 4024 4025 if ( !grid ) { 4026 if ( $kids.length <= 5 ) { 4027 for ( var letter in gridCols ) { 4028 if ( gridCols[ letter ] === $kids.length ) { 4029 grid = letter; 4030 } 4031 } 4032 } else { 4033 grid = "a"; 4034 } 4035 } 4036 iterator = gridCols[grid]; 4037 4038 $this.addClass( "ui-grid-" + grid ); 4039 4040 $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" ); 4041 4042 if ( iterator > 1 ) { 4043 $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" ); 4044 } 4045 if ( iterator > 2 ) { 4046 $kids.filter( ":nth-child(3n+3)" ).addClass( "ui-block-c" ); 4047 } 4048 if ( iterator > 3 ) { 4049 $kids.filter( ":nth-child(4n+4)" ).addClass( "ui-block-d" ); 4050 } 4051 if ( iterator > 4 ) { 4052 $kids.filter( ":nth-child(5n+5)" ).addClass( "ui-block-e" ); 4053 } 4054 }); 4055}; 4056})( jQuery );/* 4057* "navbar" plugin 4058*/ 4059 4060(function( $, undefined ) { 4061 4062$.widget( "mobile.navbar", $.mobile.widget, { 4063 options: { 4064 iconpos: "top", 4065 grid: null, 4066 initSelector: ":jqmData(role='navbar')" 4067 }, 4068 4069 _create: function(){ 4070 4071 var $navbar = this.element, 4072 $navbtns = $navbar.find( "a" ), 4073 iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? 4074 this.options.iconpos : undefined; 4075 4076 $navbar.addClass( "ui-navbar" ) 4077 .attr( "role","navigation" ) 4078 .find( "ul" ) 4079 .grid({ grid: this.options.grid }); 4080 4081 if ( !iconpos ) { 4082 $navbar.addClass( "ui-navbar-noicons" ); 4083 } 4084 4085 $navbtns.buttonMarkup({ 4086 corners: false, 4087 shadow: false, 4088 iconpos: iconpos 4089 }); 4090 4091 $navbar.delegate( "a", "vclick", function( event ) { 4092 $navbtns.not( ".ui-state-persist" ).removeClass( $.mobile.activeBtnClass ); 4093 $( this ).addClass( $.mobile.activeBtnClass ); 4094 }); 4095 } 4096}); 4097 4098//auto self-init widgets 4099$( document ).bind( "pagecreate create", function( e ){ 4100 $( $.mobile.navbar.prototype.options.initSelector, e.target ).navbar(); 4101}); 4102 4103})( jQuery ); 4104/* 4105* "listview" plugin 4106*/ 4107 4108(function( $, undefined ) { 4109 4110//Keeps track of the number of lists per page UID 4111//This allows support for multiple nested list in the same page 4112//https://github.com/jquery/jquery-mobile/issues/1617 4113var listCountPerPage = {}; 4114 4115$.widget( "mobile.listview", $.mobile.widget, { 4116 options: { 4117 theme: null, 4118 countTheme: "c", 4119 headerTheme: "b", 4120 dividerTheme: "b", 4121 splitIcon: "arrow-r", 4122 splitTheme: "b", 4123 inset: false, 4124 initSelector: ":jqmData(role='listview')" 4125 }, 4126 4127 _create: function() { 4128 var t = this; 4129 4130 // create listview markup 4131 t.element.addClass(function( i, orig ) { 4132 return orig + " ui-listview " + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" ); 4133 }); 4134 4135 t.refresh( true ); 4136 }, 4137 4138 _removeCorners: function( li, which ) { 4139 var top = "ui-corner-top ui-corner-tr ui-corner-tl", 4140 bot = "ui-corner-bottom ui-corner-br ui-corner-bl"; 4141 4142 li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) ); 4143 4144 if ( which === "top" ) { 4145 li.removeClass( top ); 4146 } else if ( which === "bottom" ) { 4147 li.removeClass( bot ); 4148 } else { 4149 li.removeClass( top + " " + bot ); 4150 } 4151 }, 4152 4153 _refreshCorners: function( create ) { 4154 var $li, 4155 $visibleli, 4156 $topli, 4157 $bottomli; 4158 4159 if ( this.options.inset ) { 4160 $li = this.element.children( "li" ); 4161 // at create time the li are not visible yet so we need to rely on .ui-screen-hidden 4162 $visibleli = create?$li.not( ".ui-screen-hidden" ):$li.filter( ":visible" ); 4163 4164 this._removeCorners( $li ); 4165 4166 // Select the first visible li element 4167 $topli = $visibleli.first() 4168 .addClass( "ui-corner-top" ); 4169 4170 $topli.add( $topli.find( ".ui-btn-inner" ) 4171 .not( ".ui-li-link-alt span:first-child" ) ) 4172 .addClass( "ui-corner-top" ) 4173 .end() 4174 .find( ".ui-li-link-alt, .ui-li-link-alt span:first-child" ) 4175 .addClass( "ui-corner-tr" ) 4176 .end() 4177 .find( ".ui-li-thumb" ) 4178 .not(".ui-li-icon") 4179 .addClass( "ui-corner-tl" ); 4180 4181 // Select the last visible li element
4182 $bottomli = $visibleli.last() 4183 .addClass( "ui-corner-bottom" ); 4184 4185 $bottomli.add( $bottomli.find( ".ui-btn-inner" ) ) 4186 .find( ".ui-li-link-alt" ) 4187 .addClass( "ui-corner-br" ) 4188 .end() 4189 .find( ".ui-li-thumb" ) 4190 .not(".ui-li-icon") 4191 .addClass( "ui-corner-bl" ); 4192 } 4193 if ( !create ) { 4194 this.element.trigger( "updatelayout" ); 4195 } 4196 }, 4197 4198 // This is a generic utility method for finding the first 4199 // node with a given nodeName. It uses basic DOM traversal 4200 // to be fast and is meant to be a substitute for simple 4201 // $.fn.closest() and $.fn.children() calls on a single 4202 // element. Note that callers must pass both the lowerCase 4203 // and upperCase version of the nodeName they are looking for. 4204 // The main reason for this is that this function will be 4205 // called many times and we want to avoid having to lowercase 4206 // the nodeName from the element every time to ensure we have 4207 // a match. Note that this function lives here for now, but may 4208 // be moved into $.mobile if other components need a similar method. 4209 _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) 4210 { 4211 var dict = {}; 4212 dict[ lcName ] = dict[ ucName ] = true; 4213 while ( ele ) { 4214 if ( dict[ ele.nodeName ] ) { 4215 return ele; 4216 } 4217 ele = ele[ nextProp ]; 4218 } 4219 return null; 4220 }, 4221 _getChildrenByTagName: function( ele, lcName, ucName ) 4222 { 4223 var results = [], 4224 dict = {}; 4225 dict[ lcName ] = dict[ ucName ] = true; 4226 ele = ele.firstChild; 4227 while ( ele ) { 4228 if ( dict[ ele.nodeName ] ) { 4229 results.push( ele ); 4230 } 4231 ele = ele.nextSibling; 4232 } 4233 return $( results ); 4234 }, 4235 4236 _addThumbClasses: function( containers ) 4237 { 4238 var i, img, len = containers.length; 4239 for ( i = 0; i < len; i++ ) { 4240 img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) ); 4241 if ( img.length ) { 4242 img.addClass( "ui-li-thumb" ); 4243 $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); 4244 } 4245 } 4246 }, 4247 4248 refresh: function( create ) { 4249 this.parentPage = this.element.closest( ".ui-page" ); 4250 this._createSubPages(); 4251 4252 var o = this.options, 4253 $list = this.element, 4254 self = this, 4255 dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme, 4256 listsplittheme = $list.jqmData( "splittheme" ), 4257 listspliticon = $list.jqmData( "spliticon" ), 4258 li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ), 4259 counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1, 4260 itemClassDict = {}, 4261 item, itemClass, itemTheme, 4262 a, last, splittheme, countParent, icon, imgParents, img; 4263 4264 if ( counter ) { 4265 $list.find( ".ui-li-dec" ).remove(); 4266 } 4267 4268 if ( !o.theme ) { 4269 o.theme = $.mobile.getInheritedTheme( this.element, "c" ); 4270 } 4271 4272 for ( var pos = 0, numli = li.length; pos < numli; pos++ ) { 4273 item = li.eq( pos ); 4274 itemClass = "ui-li"; 4275 4276 // If we're creating the element, we update it regardless 4277 if ( create || !item.hasClass( "ui-li" ) ) { 4278 itemTheme = item.jqmData("theme") || o.theme; 4279 a = this._getChildrenByTagName( item[ 0 ], "a", "A" ); 4280 4281 if ( a.length ) { 4282 icon = item.jqmData("icon"); 4283 4284 item.buttonMarkup({ 4285 wrapperEls: "div", 4286 shadow: false, 4287 corners: false, 4288 iconpos: "right", 4289 icon: a.length > 1 || icon === false ? false : icon || "arrow-r", 4290 theme: itemTheme 4291 }); 4292 4293 if ( ( icon != false ) && ( a.length == 1 ) ) { 4294 item.addClass( "ui-li-has-arrow" ); 4295 } 4296 4297 a.first().addClass( "ui-link-inherit" ); 4298 4299 if ( a.length > 1 ) { 4300 itemClass += " ui-li-has-alt"; 4301 4302 last = a.last(); 4303 splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme; 4304 4305 last.appendTo(item) 4306 .attr( "title", last.getEncodedText() ) 4307 .addClass( "ui-li-link-alt" ) 4308 .empty() 4309 .buttonMarkup({ 4310 shadow: false, 4311 corners: false, 4312 theme: itemTheme, 4313 icon: false, 4314 iconpos: false 4315 }) 4316 .find( ".ui-btn-inner" ) 4317 .append( 4318 $( document.createElement( "span" ) ).buttonMarkup({ 4319 shadow: true, 4320 corners: true, 4321 theme: splittheme, 4322 iconpos: "notext", 4323 icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon 4324 }) 4325 ); 4326 } 4327 } else if ( item.jqmData( "role" ) === "list-divider" ) { 4328 4329 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme; 4330 item.attr( "role", "heading" ); 4331 4332 //reset counter when a divider heading is encountered 4333 if ( counter ) { 4334 counter = 1; 4335 } 4336 4337 } else { 4338 itemClass += " ui-li-static ui-body-" + itemTheme; 4339 } 4340 } 4341 4342 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) { 4343 countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" ); 4344 4345 countParent.addClass( "ui-li-jsnumbering" ) 4346 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" ); 4347 } 4348 4349 // Instead of setting item class directly on the list item and its
4350 // btn-inner at this point in time, push the item into a dictionary 4351 // that tells us what class to set on it so we can do this after this 4352 // processing loop is finished. 4353 4354 if ( !itemClassDict[ itemClass ] ) { 4355 itemClassDict[ itemClass ] = []; 4356 } 4357 4358 itemClassDict[ itemClass ].push( item[ 0 ] ); 4359 } 4360 4361 // Set the appropriate listview item classes on each list item 4362 // and their btn-inner elements. The main reason we didn't do this 4363 // in the for-loop above is because we can eliminate per-item function overhead 4364 // by calling addClass() and children() once or twice afterwards. This 4365 // can give us a significant boost on platforms like WP7.5. 4366 4367 for ( itemClass in itemClassDict ) { 4368 $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass ); 4369 } 4370 4371 $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ) 4372 .end() 4373 4374 .find( "p, dl" ).addClass( "ui-li-desc" ) 4375 .end() 4376 4377 .find( ".ui-li-aside" ).each(function() { 4378 var $this = $(this); 4379 $this.prependTo( $this.parent() ); //shift aside to front for css float 4380 }) 4381 .end() 4382 4383 .find( ".ui-li-count" ).each( function() { 4384 $( this ).closest( "li" ).addClass( "ui-li-has-count" ); 4385 }).addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" ); 4386 4387 // The idea here is to look at the first image in the list item 4388 // itself, and any .ui-link-inherit element it may contain, so we 4389 // can place the appropriate classes on the image and list item. 4390 // Note that we used to use something like: 4391 // 4392 // li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... ); 4393 // 4394 // But executing a find() like that on Windows Phone 7.5 took a 4395 // really long time. Walking things manually with the code below 4396 // allows the 400 listview item page to load in about 3 seconds as 4397 // opposed to 30 seconds. 4398 4399 this._addThumbClasses( li ); 4400 this._addThumbClasses( $list.find( ".ui-link-inherit" ) ); 4401 4402 this._refreshCorners( create ); 4403 }, 4404 4405 //create a string for ID/subpage url creation 4406 _idStringEscape: function( str ) { 4407 return str.replace(/[^a-zA-Z0-9]/g, '-'); 4408 }, 4409 4410 _createSubPages: function() { 4411 var parentList = this.element, 4412 parentPage = parentList.closest( ".ui-page" ), 4413 parentUrl = parentPage.jqmData( "url" ), 4414 parentId = parentUrl || parentPage[ 0 ][ $.expando ], 4415 parentListId = parentList.attr( "id" ), 4416 o = this.options, 4417 dns = "data-" + $.mobile.ns, 4418 self = this, 4419 persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ), 4420 hasSubPages; 4421 4422 if ( typeof listCountPerPage[ parentId ] === "undefined" ) { 4423 listCountPerPage[ parentId ] = -1; 4424 } 4425 4426 parentListId = parentListId || ++listCountPerPage[ parentId ]; 4427 4428 $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) { 4429 var self = this, 4430 list = $( this ), 4431 listId = list.attr( "id" ) || parentListId + "-" + i, 4432 parent = list.parent(), 4433 nodeEls = $( list.prevAll().toArray().reverse() ), 4434 nodeEls = nodeEls.length ? nodeEls : $( "<span>" + $.trim(parent.contents()[ 0 ].nodeValue) + "</span>" ), 4435 title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text 4436 id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId, 4437 theme = list.jqmData( "theme" ) || o.theme, 4438 countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme, 4439 newPage, anchor; 4440 4441 //define hasSubPages for use in later removal 4442 hasSubPages = true; 4443 4444 newPage = list.detach() 4445 .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" ) 4446 .parent() 4447 .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" ) 4448 .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='"+ persistentFooterID +"'>") : "" ) 4449 .parent() 4450 .appendTo( $.mobile.pageContainer ); 4451 4452 newPage.page(); 4453 4454 anchor = parent.find('a:first'); 4455 4456 if ( !anchor.length ) { 4457 anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() ); 4458 } 4459 4460 anchor.attr( "href", "#" + id ); 4461 4462 }).listview(); 4463 4464 // on pagehide, remove any nested pages along with the parent page, as long as they aren't active 4465 // and aren't embedded 4466 if( hasSubPages && 4467 parentPage.is( ":jqmData(external-page='true')" ) && 4468 parentPage.data("page").options.domCache === false ) { 4469 4470 var newRemove = function( e, ui ){ 4471 var nextPage = ui.nextPage, npURL; 4472 4473 if( ui.nextPage ){ 4474 npURL = nextPage.jqmData( "url" ); 4475 if( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ){ 4476 self.childPages().remove(); 4477 parentPage.remove(); 4478 } 4479 } 4480 }; 4481 4482 // unbind the original page remove and replace with our specialized version 4483 parentPage 4484 .unbind( "pagehide.remove" ) 4485 .bind( "pagehide.remove", newRemove); 4486 } 4487 }, 4488 4489 // TODO sort out a better way to track sub pages of the listview this is brittle 4490 childPages: function(){ 4491 var parentUrl = this.parentPage.jqmData( "url" ); 4492 4493 return $( ":jqmData(url^='"+ parentUrl + "&" + $.mobile.subPageUrlKey +"')"); 4494 } 4495}); 4496 4497//auto self-init widgets 4498$( document ).bind( "pagecreate create", function( e ){ 4499 $( $.mobile.listview.prototype.options.initSelector, e.target ).listview(); 4500}); 4501 4502})( jQuery ); 4503/* 4504* "listview" filter extension 4505*/ 4506 4507(function( $, undefined ) { 4508 4509$.mobile.listview.prototype.options.filter = false;
4510$.mobile.listview.prototype.options.filterPlaceholder = "Filter items..."; 4511$.mobile.listview.prototype.options.filterTheme = "c"; 4512$.mobile.listview.prototype.options.filterCallback = function( text, searchValue ){ 4513 return text.toLowerCase().indexOf( searchValue ) === -1; 4514}; 4515 4516$( ":jqmData(role='listview')" ).live( "listviewcreate", function() { 4517 4518 var list = $( this ), 4519 listview = list.data( "listview" ); 4520 4521 if ( !listview.options.filter ) { 4522 return; 4523 } 4524 4525 var wrapper = $( "<form>", { 4526 "class": "ui-listview-filter ui-bar-" + listview.options.filterTheme, 4527 "role": "search" 4528 }), 4529 search = $( "<input>", { 4530 placeholder: listview.options.filterPlaceholder 4531 }) 4532 .attr( "data-" + $.mobile.ns + "type", "search" ) 4533 .jqmData( "lastval", "" ) 4534 .bind( "keyup change", function() { 4535 4536 var $this = $(this), 4537 val = this.value.toLowerCase(), 4538 listItems = null, 4539 lastval = $this.jqmData( "lastval" ) + "", 4540 childItems = false, 4541 itemtext = "", 4542 item, change; 4543 4544 // Change val as lastval for next execution 4545 $this.jqmData( "lastval" , val ); 4546 change = val.substr( 0 , lastval.length - 1 ).replace( lastval , "" ); 4547 4548 if ( val.length < lastval.length || change.length != ( val.length - lastval.length ) ) { 4549 4550 // Removed chars or pasted something totally different, check all items 4551 listItems = list.children(); 4552 } else { 4553 4554 // Only chars added, not removed, only use visible subset 4555 listItems = list.children( ":not(.ui-screen-hidden)" ); 4556 } 4557 4558 if ( val ) { 4559 4560 // This handles hiding regular rows without the text we search for 4561 // and any list dividers without regular rows shown under it 4562 4563 for ( var i = listItems.length - 1; i >= 0; i-- ) { 4564 item = $( listItems[ i ] ); 4565 itemtext = item.jqmData( "filtertext" ) || item.text(); 4566 4567 if ( item.is( "li:jqmData(role=list-divider)" ) ) { 4568 4569 item.toggleClass( "ui-filter-hidequeue" , !childItems ); 4570 4571 // New bucket! 4572 childItems = false; 4573 4574 } else if ( listview.options.filterCallback( itemtext, val ) ) { 4575 4576 //mark to be hidden 4577 item.toggleClass( "ui-filter-hidequeue" , true ); 4578 } else { 4579 4580 // There's a shown item in the bucket 4581 childItems = true; 4582 } 4583 } 4584 4585 // Show items, not marked to be hidden 4586 listItems 4587 .filter( ":not(.ui-filter-hidequeue)" ) 4588 .toggleClass( "ui-screen-hidden", false ); 4589 4590 // Hide items, marked to be hidden 4591 listItems 4592 .filter( ".ui-filter-hidequeue" ) 4593 .toggleClass( "ui-screen-hidden", true ) 4594 .toggleClass( "ui-filter-hidequeue", false ); 4595 4596 } else { 4597 4598 //filtervalue is empty => show all 4599 listItems.toggleClass( "ui-screen-hidden", false ); 4600 } 4601 listview._refreshCorners(); 4602 }) 4603 .appendTo( wrapper ) 4604 .textinput(); 4605 4606 if ( $( this ).jqmData( "inset" ) ) { 4607 wrapper.addClass( "ui-listview-filter-inset" ); 4608 } 4609 4610 wrapper.bind( "submit", function() { 4611 return false; 4612 }) 4613 .insertBefore( list ); 4614}); 4615 4616})( jQuery );/* 4617* "nojs" plugin - class to make elements hidden to A grade browsers 4618*/ 4619 4620(function( $, undefined ) { 4621 4622$( document ).bind( "pagecreate create", function( e ){ 4623 $( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" ); 4624 4625}); 4626 4627})( jQuery );/* 4628* "checkboxradio" plugin 4629*/ 4630 4631(function( $, undefined ) { 4632 4633$.widget( "mobile.checkboxradio", $.mobile.widget, { 4634 options: { 4635 theme: null, 4636 initSelector: "input[type='checkbox'],input[type='radio']" 4637 }, 4638 _create: function() { 4639 var self = this, 4640 input = this.element, 4641 // NOTE: Windows Phone could not find the label through a selector 4642 // filter works though. 4643 label = input.closest( "form,fieldset,:jqmData(role='page')" ).find( "label[for='" + input[ 0 ].id + "']"), 4644 inputtype = input.attr( "type" ), 4645 checkedState = inputtype + "-on", 4646 uncheckedState = inputtype + "-off", 4647 icon = input.parents( ":jqmData(type='horizontal')" ).length ? undefined : uncheckedState, 4648 activeBtn = icon ? "" : " " + $.mobile.activeBtnClass, 4649 checkedClass = "ui-" + checkedState + activeBtn, 4650 uncheckedClass = "ui-" + uncheckedState, 4651 checkedicon = "ui-icon-" + checkedState, 4652 uncheckedicon = "ui-icon-" + uncheckedState; 4653 4654 if ( inputtype !== "checkbox" && inputtype !== "radio" ) { 4655 return; 4656 } 4657 4658 // Expose for other methods 4659 $.extend( this, { 4660 label: label, 4661 inputtype: inputtype, 4662 checkedClass: checkedClass, 4663 uncheckedClass: uncheckedClass, 4664 checkedicon: checkedicon, 4665 uncheckedicon: uncheckedicon 4666 }); 4667 4668 // If there's no selected theme... 4669 if( !this.options.theme ) { 4670 this.options.theme = this.element.jqmData( "theme" ); 4671 } 4672 4673 label.buttonMarkup({ 4674 theme: this.options.theme, 4675 icon: icon, 4676 shadow: false 4677 }); 4678 4679 // Wrap the input + label in a div 4680 input.add( label ) 4681 .wrapAll( "<div class='ui-" + inputtype + "'></div>" ); 4682 4683 label.bind({ 4684 vmouseover: function( event ) { 4685 if ( $( this ).parent().is( ".ui-disabled" ) ) { 4686 event.stopPropagation(); 4687 } 4688 }, 4689 4690 vclick: function( event ) { 4691 if ( input.is( ":disabled" ) ) { 4692 event.preventDefault(); 4693 return; 4694 } 4695 4696 self._cacheVals(); 4697 4698 input.prop( "checked", inputtype === "radio" && true || !input.prop( "checked" ) ); 4699 4700 // trigger click handler's bound directly to the input as a substitute for 4701 // how label clicks behave normally in the browsers 4702 // TODO: it would be nice to let the browser's handle the clicks and pass them 4703 // through to the associate input. we can swallow that click at the parent 4704 // wrapper element level 4705 input.triggerHandler( 'click' ); 4706
4707 // Input set for common radio buttons will contain all the radio 4708 // buttons, but will not for checkboxes. clearing the checked status 4709 // of other radios ensures the active button state is applied properly 4710 self._getInputSet().not( input ).prop( "checked", false ); 4711 4712 self._updateAll(); 4713 return false; 4714 } 4715 4716 }); 4717 4718 input 4719 .bind({ 4720 vmousedown: function() { 4721 self._cacheVals(); 4722 }, 4723 4724 vclick: function() { 4725 var $this = $(this); 4726 4727 // Adds checked attribute to checked input when keyboard is used 4728 if ( $this.is( ":checked" ) ) { 4729 4730 $this.prop( "checked", true); 4731 self._getInputSet().not($this).prop( "checked", false ); 4732 } else { 4733 4734 $this.prop( "checked", false ); 4735 } 4736 4737 self._updateAll(); 4738 }, 4739 4740 focus: function() { 4741 label.addClass( "ui-focus" ); 4742 }, 4743 4744 blur: function() { 4745 label.removeClass( "ui-focus" ); 4746 } 4747 }); 4748 4749 this.refresh(); 4750 }, 4751 4752 _cacheVals: function() { 4753 this._getInputSet().each(function() { 4754 var $this = $(this); 4755 4756 $this.jqmData( "cacheVal", $this.is( ":checked" ) ); 4757 }); 4758 }, 4759 4760 //returns either a set of radios with the same name attribute, or a single checkbox 4761 _getInputSet: function(){ 4762 if(this.inputtype == "checkbox") { 4763 return this.element; 4764 } 4765 4766 return this.element.closest( "form,fieldset,:jqmData(role='page')" ) 4767 .find( "input[name='"+ this.element.attr( "name" ) +"'][type='"+ this.inputtype +"']" ); 4768 }, 4769 4770 _updateAll: function() { 4771 var self = this; 4772 4773 this._getInputSet().each(function() { 4774 var $this = $(this); 4775 4776 if ( $this.is( ":checked" ) || self.inputtype === "checkbox" ) { 4777 $this.trigger( "change" ); 4778 } 4779 }) 4780 .checkboxradio( "refresh" ); 4781 }, 4782 4783 refresh: function() { 4784 var input = this.element, 4785 label = this.label, 4786 icon = label.find( ".ui-icon" ); 4787 4788 // input[0].checked expando doesn't always report the proper value 4789 // for checked='checked' 4790 if ( $( input[ 0 ] ).prop( "checked" ) ) { 4791 4792 label.addClass( this.checkedClass ).removeClass( this.uncheckedClass ); 4793 icon.addClass( this.checkedicon ).removeClass( this.uncheckedicon ); 4794 4795 } else { 4796 4797 label.removeClass( this.checkedClass ).addClass( this.uncheckedClass ); 4798 icon.removeClass( this.checkedicon ).addClass( this.uncheckedicon ); 4799 } 4800 4801 if ( input.is( ":disabled" ) ) { 4802 this.disable(); 4803 } else { 4804 this.enable(); 4805 } 4806 }, 4807 4808 disable: function() { 4809 this.element.prop( "disabled", true ).parent().addClass( "ui-disabled" ); 4810 }, 4811 4812 enable: function() { 4813 this.element.prop( "disabled", false ).parent().removeClass( "ui-disabled" ); 4814 } 4815}); 4816 4817//auto self-init widgets 4818$( document ).bind( "pagecreate create", function( e ){ 4819 $.mobile.checkboxradio.prototype.enhanceWithin( e.target ); 4820}); 4821 4822})( jQuery ); 4823/* 4824* "button" plugin - links that proxy to native input/buttons 4825*/ 4826 4827(function( $, undefined ) { 4828 4829$.widget( "mobile.button", $.mobile.widget, { 4830 options: { 4831 theme: null, 4832 icon: null, 4833 iconpos: null, 4834 inline: null, 4835 corners: true, 4836 shadow: true, 4837 iconshadow: true, 4838 initSelector: "button, [type='button'], [type='submit'], [type='reset'], [type='image']" 4839 }, 4840 _create: function() { 4841 var $el = this.element, 4842 o = this.options, 4843 type, 4844 name, 4845 $buttonPlaceholder; 4846 4847 // Add ARIA role 4848 this.button = $( "<div></div>" ) 4849 .text( $el.text() || $el.val() ) 4850 .insertBefore( $el ) 4851 .buttonMarkup({ 4852 theme: o.theme, 4853 icon: o.icon, 4854 iconpos: o.iconpos, 4855 inline: o.inline, 4856 corners: o.corners, 4857 shadow: o.shadow, 4858 iconshadow: o.iconshadow 4859 }) 4860 .append( $el.addClass( "ui-btn-hidden" ) ); 4861 4862 type = $el.attr( "type" ); 4863 name = $el.attr( "name" ); 4864 4865 // Add hidden input during submit if input type="submit" has a name. 4866 if ( type !== "button" && type !== "reset" && name ) { 4867 $el.bind( "vclick", function() { 4868 // Add hidden input if it doesn’t already exist. 4869 if( $buttonPlaceholder === undefined ) { 4870 $buttonPlaceholder = $( "<input>", { 4871 type: "hidden", 4872 name: $el.attr( "name" ), 4873 value: $el.attr( "value" ) 4874 }).insertBefore( $el ); 4875 4876 // Bind to doc to remove after submit handling 4877 $( document ).one("submit", function(){ 4878 $buttonPlaceholder.remove(); 4879 4880 // reset the local var so that the hidden input 4881 // will be re-added on subsequent clicks 4882 $buttonPlaceholder = undefined; 4883 }); 4884 } 4885 }); 4886 } 4887 4888 this.refresh(); 4889 }, 4890 4891 enable: function() { 4892 this.element.attr( "disabled", false ); 4893 this.button.removeClass( "ui-disabled" ).attr( "aria-disabled", false ); 4894 return this._setOption( "disabled", false ); 4895 }, 4896 4897 disable: function() { 4898 this.element.attr( "disabled", true ); 4899 this.button.addClass( "ui-disabled" ).attr( "aria-disabled", true ); 4900 return this._setOption( "disabled", true ); 4901 }, 4902 4903 refresh: function() { 4904 var $el = this.element; 4905 4906 if ( $el.prop("disabled") ) { 4907 this.disable(); 4908 } else { 4909 this.enable(); 4910 } 4911 4912 // the textWrapper is stored as a data element on the button object 4913 // to prevent referencing by it's implementation details (eg 'class') 4914 this.button.data( 'textWrapper' ).text( $el.text() || $el.val() ); 4915 } 4916}); 4917 4918//auto self-init widgets 4919$( document ).bind( "pagecreate create", function( e ){ 4920 $.mobile.button.prototype.enhanceWithin( e.target ); 4921}); 4922 4923})( jQuery );/* 4924* "slider" plugin 4925*/ 4926 4927( function( $, undefined ) { 4928 4929$.widget( "mobile.slider", $.mobile.widget, { 4930 options: { 4931 theme: null, 4932 trackTheme: null, 4933 disabled: false, 4934 initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')" 4935 }, 4936 4937 _create: function() { 4938 4939 // TODO: Each of these should have comments explain what they're for 4940 var self = this, 4941 4942 control = this.element, 4943 4944 parentTheme = $.mobile.getInheritedTheme( control, "c" ), 4945 4946 theme = this.options.theme || parentTheme, 4947 4948 trackTheme = this.options.trackTheme || parentTheme, 4949 4950 cType = control[ 0 ].nodeName.toLowerCase(), 4951 4952 selectClass = ( cType == "select" ) ? "ui-slider-switch" : "", 4953 4954 controlID = control.attr( "id" ), 4955 4956 labelID = controlID + "-label", 4957 4958 label = $( "[for='"+ controlID +"']" ).attr( "id", labelID ), 4959 4960 val = function() {
4961 return cType == "input" ? parseFloat( control.val() ) : control[0].selectedIndex; 4962 }, 4963 4964 min = cType == "input" ? parseFloat( control.attr( "min" ) ) : 0, 4965 4966 max = cType == "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1, 4967 4968 step = window.parseFloat( control.attr( "step" ) || 1 ), 4969 4970 slider = $( "<div class='ui-slider " + selectClass + " ui-btn-down-" + trackTheme + 4971 " ui-btn-corner-all' role='application'></div>" ), 4972 4973 handle = $( "<a href='#' class='ui-slider-handle'></a>" ) 4974 .appendTo( slider ) 4975 .buttonMarkup({ corners: true, theme: theme, shadow: true }) 4976 .attr({ 4977 "role": "slider", 4978 "aria-valuemin": min, 4979 "aria-valuemax": max, 4980 "aria-valuenow": val(), 4981 "aria-valuetext": val(), 4982 "title": val(), 4983 "aria-labelledby": labelID 4984 }), 4985 options; 4986 4987 $.extend( this, { 4988 slider: slider, 4989 handle: handle, 4990 dragging: false, 4991 beforeStart: null, 4992 userModified: false, 4993 mouseMoved: false 4994 }); 4995 4996 if ( cType == "select" ) { 4997 4998 slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" ); 4999 5000 // make the handle move with a smooth transition 5001 handle.addClass( "ui-slider-handle-snapping" ); 5002 5003 options = control.find( "option" ); 5004 5005 control.find( "option" ).each(function( i ) { 5006 5007 var side = !i ? "b":"a", 5008 corners = !i ? "right" :"left", 5009 theme = !i ? " ui-btn-down-" + trackTheme :( " " + $.mobile.activeBtnClass ); 5010 5011 $( "<div class='ui-slider-labelbg ui-slider-labelbg-" + side + theme + " ui-btn-corner-" + corners + "'></div>" ) 5012 .prependTo( slider ); 5013 5014 $( "<span class='ui-slider-label ui-slider-label-" + side + theme + " ui-btn-corner-" + corners + "' role='img'>" + $( this ).getEncodedText() + "</span>" ) 5015 .prependTo( handle ); 5016 }); 5017 5018 } 5019 5020 label.addClass( "ui-slider" ); 5021 5022 // monitor the input for updated values 5023 control.addClass( cType === "input" ? "ui-slider-input" : "ui-slider-switch" ) 5024 .change( function() { 5025 // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again 5026 if (!self.mouseMoved) { 5027 self.refresh( val(), true ); 5028 } 5029 }) 5030 .keyup( function() { // necessary? 5031 self.refresh( val(), true, true ); 5032 }) 5033 .blur( function() { 5034 self.refresh( val(), true ); 5035 }); 5036 5037 // prevent screen drag when slider activated 5038 $( document ).bind( "vmousemove", function( event ) { 5039 if ( self.dragging ) { 5040 // self.mouseMoved must be updated before refresh() because it will be used in the control "change" event 5041 self.mouseMoved = true; 5042 5043 if ( cType === "select" ) { 5044 // make the handle move in sync with the mouse 5045 handle.removeClass( "ui-slider-handle-snapping" ); 5046 } 5047 5048 self.refresh( event ); 5049 5050 // only after refresh() you can calculate self.userModified 5051 self.userModified = self.beforeStart !== control[0].selectedIndex; 5052 return false; 5053 } 5054 }); 5055 5056 slider.bind( "vmousedown", function( event ) { 5057 self.dragging = true; 5058 self.userModified = false; 5059 self.mouseMoved = false; 5060 5061 if ( cType === "select" ) { 5062 self.beforeStart = control[0].selectedIndex; 5063 } 5064 5065 self.refresh( event ); 5066 return false; 5067 }); 5068 5069 slider.add( document ) 5070 .bind( "vmouseup", function() { 5071 if ( self.dragging ) { 5072 5073 self.dragging = false; 5074 5075 if ( cType === "select") { 5076 5077 // make the handle move with a smooth transition 5078 handle.addClass( "ui-slider-handle-snapping" ); 5079 5080 if ( self.mouseMoved ) { 5081 5082 // this is a drag, change the value only if user dragged enough 5083 if ( self.userModified ) { 5084 self.refresh( self.beforeStart == 0 ? 1 : 0 ); 5085 } 5086 else { 5087 self.refresh( self.beforeStart ); 5088 } 5089 5090 } 5091 else { 5092 // this is just a click, change the value 5093 self.refresh( self.beforeStart == 0 ? 1 : 0 ); 5094 } 5095 5096 } 5097 5098 self.mouseMoved = false; 5099 5100 return false; 5101 } 5102 }); 5103 5104 slider.insertAfter( control ); 5105 5106 // NOTE force focus on handle 5107 this.handle 5108 .bind( "vmousedown", function() { 5109 $( this ).focus(); 5110 }) 5111 .bind( "vclick", false ); 5112 5113 this.handle 5114 .bind( "keydown", function( event ) { 5115 var index = val(); 5116 5117 if ( self.options.disabled ) { 5118 return; 5119 } 5120 5121 // In all cases prevent the default and mark the handle as active 5122 switch ( event.keyCode ) { 5123 case $.mobile.keyCode.HOME: 5124 case $.mobile.keyCode.END: 5125 case $.mobile.keyCode.PAGE_UP: 5126 case $.mobile.keyCode.PAGE_DOWN: 5127 case $.mobile.keyCode.UP: 5128 case $.mobile.keyCode.RIGHT: 5129 case $.mobile.keyCode.DOWN: 5130 case $.mobile.keyCode.LEFT: 5131 event.preventDefault(); 5132 5133 if ( !self._keySliding ) { 5134 self._keySliding = true; 5135 $( this ).addClass( "ui-state-active" ); 5136 } 5137 break; 5138 } 5139 5140 // move the slider according to the keypress 5141 switch ( event.keyCode ) { 5142 case $.mobile.keyCode.HOME: 5143 self.refresh( min ); 5144 break; 5145 case $.mobile.keyCode.END: 5146 self.refresh( max ); 5147 break; 5148 case $.mobile.keyCode.PAGE_UP: 5149 case $.mobile.keyCode.UP: 5150 case $.mobile.keyCode.RIGHT: 5151 self.refresh( index + step ); 5152 break; 5153 case $.mobile.keyCode.PAGE_DOWN: 5154 case $.mobile.keyCode.DOWN: 5155 case $.mobile.keyCode.LEFT: 5156 self.refresh( index - step ); 5157 break; 5158 } 5159 }) // remove active mark 5160 .keyup( function( event ) { 5161 if ( self._keySliding ) { 5162 self._keySliding = false;
5163 $( this ).removeClass( "ui-state-active" ); 5164 } 5165 }); 5166 5167 this.refresh(undefined, undefined, true); 5168 }, 5169 5170 refresh: function( val, isfromControl, preventInputUpdate ) { 5171 5172 if ( this.options.disabled || this.element.attr('disabled')) { 5173 this.disable(); 5174 } 5175 5176 var control = this.element, percent, 5177 cType = control[0].nodeName.toLowerCase(), 5178 min = cType === "input" ? parseFloat( control.attr( "min" ) ) : 0, 5179 max = cType === "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length - 1; 5180 5181 if ( typeof val === "object" ) { 5182 var data = val, 5183 // a slight tolerance helped get to the ends of the slider 5184 tol = 8; 5185 if ( !this.dragging || 5186 data.pageX < this.slider.offset().left - tol || 5187 data.pageX > this.slider.offset().left + this.slider.width() + tol ) { 5188 return; 5189 } 5190 percent = Math.round( ( ( data.pageX - this.slider.offset().left ) / this.slider.width() ) * 100 ); 5191 } else { 5192 if ( val == null ) { 5193 val = cType === "input" ? parseFloat( control.val() ) : control[0].selectedIndex; 5194 } 5195 percent = ( parseFloat( val ) - min ) / ( max - min ) * 100; 5196 } 5197 5198 if ( isNaN( percent ) ) { 5199 return; 5200 } 5201 5202 if ( percent < 0 ) { 5203 percent = 0; 5204 } 5205 5206 if ( percent > 100 ) { 5207 percent = 100; 5208 } 5209 5210 var newval = Math.round( ( percent / 100 ) * ( max - min ) ) + min; 5211 5212 if ( newval < min ) { 5213 newval = min; 5214 } 5215 5216 if ( newval > max ) { 5217 newval = max; 5218 } 5219 5220 // Flip the stack of the bg colors 5221 if ( percent > 60 && cType === "select" ) { 5222 // TODO: Dead path? 5223 } 5224 this.handle.css( "left", percent + "%" ); 5225 this.handle.attr( { 5226 "aria-valuenow": cType === "input" ? newval : control.find( "option" ).eq( newval ).attr( "value" ), 5227 "aria-valuetext": cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText(), 5228 title: newval 5229 }); 5230 5231 // add/remove classes for flip toggle switch 5232 if ( cType === "select" ) { 5233 if ( newval === 0 ) { 5234 this.slider.addClass( "ui-slider-switch-a" ) 5235 .removeClass( "ui-slider-switch-b" ); 5236 } else { 5237 this.slider.addClass( "ui-slider-switch-b" ) 5238 .removeClass( "ui-slider-switch-a" ); 5239 } 5240 } 5241 5242 if ( !preventInputUpdate ) { 5243 var valueChanged = false; 5244 5245 // update control"s value 5246 if ( cType === "input" ) { 5247 valueChanged = control.val() !== newval; 5248 control.val( newval ); 5249 } else { 5250 valueChanged = control[ 0 ].selectedIndex !== newval; 5251 control[ 0 ].selectedIndex = newval; 5252 } 5253 if ( !isfromControl && valueChanged ) { 5254 control.trigger( "change" ); 5255 } 5256 } 5257 }, 5258 5259 enable: function() { 5260 this.element.attr( "disabled", false ); 5261 this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false ); 5262 return this._setOption( "disabled", false ); 5263 }, 5264 5265 disable: function() { 5266 this.element.attr( "disabled", true ); 5267 this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true ); 5268 return this._setOption( "disabled", true ); 5269 } 5270 5271}); 5272 5273//auto self-init widgets 5274$( document ).bind( "pagecreate create", function( e ){ 5275 $.mobile.slider.prototype.enhanceWithin( e.target ); 5276}); 5277 5278})( jQuery ); 5279/* 5280* "textinput" plugin for text inputs, textareas 5281*/ 5282 5283(function( $, undefined ) { 5284 5285$.widget( "mobile.textinput", $.mobile.widget, { 5286 options: { 5287 theme: null, 5288 initSelector: "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])" 5289 }, 5290 5291 _create: function() { 5292 5293 var input = this.element, 5294 o = this.options, 5295 theme = o.theme || $.mobile.getInheritedTheme( this.element, "c" ), 5296 themeclass = " ui-body-" + theme, 5297 focusedEl, clearbtn; 5298 5299 $( "label[for='" + input.attr( "id" ) + "']" ).addClass( "ui-input-text" ); 5300 5301 focusedEl = input.addClass("ui-input-text ui-body-"+ theme ); 5302 5303 // XXX: Temporary workaround for issue 785 (Apple bug 8910589). 5304 // Turn off autocorrect and autocomplete on non-iOS 5 devices 5305 // since the popup they use can't be dismissed by the user. Note 5306 // that we test for the presence of the feature by looking for 5307 // the autocorrect property on the input element. We currently 5308 // have no test for iOS 5 or newer so we're temporarily using
5309 // the touchOverflow support flag for jQM 1.0. Yes, I feel dirty. - jblas 5310 if ( typeof input[0].autocorrect !== "undefined" && !$.support.touchOverflow ) { 5311 // Set the attribute instead of the property just in case there 5312 // is code that attempts to make modifications via HTML. 5313 input[0].setAttribute( "autocorrect", "off" ); 5314 input[0].setAttribute( "autocomplete", "off" ); 5315 } 5316 5317 5318 //"search" input widget 5319 if ( input.is( "[type='search'],:jqmData(type='search')" ) ) { 5320 5321 focusedEl = input.wrap( "<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield" + themeclass + "'></div>" ).parent(); 5322 clearbtn = $( "<a href='#' class='ui-input-clear' title='clear text'>clear text</a>" ) 5323 .tap(function( event ) { 5324 input.val( "" ).focus(); 5325 input.trigger( "change" ); 5326 clearbtn.addClass( "ui-input-clear-hidden" ); 5327 event.preventDefault(); 5328 }) 5329 .appendTo( focusedEl ) 5330 .buttonMarkup({ 5331 icon: "delete", 5332 iconpos: "notext", 5333 corners: true, 5334 shadow: true 5335 }); 5336 5337 function toggleClear() { 5338 setTimeout(function() { 5339 clearbtn.toggleClass( "ui-input-clear-hidden", !input.val() ); 5340 }, 0); 5341 } 5342 5343 toggleClear(); 5344 5345 input.bind('paste cut keyup focus change blur', toggleClear); 5346 5347 } else { 5348 input.addClass( "ui-corner-all ui-shadow-inset" + themeclass ); 5349 } 5350 5351 input.focus(function() { 5352 focusedEl.addClass( "ui-focus" ); 5353 }) 5354 .blur(function(){ 5355 focusedEl.removeClass( "ui-focus" ); 5356 }); 5357 5358 // Autogrow 5359 if ( input.is( "textarea" ) ) { 5360 var extraLineHeight = 15, 5361 keyupTimeoutBuffer = 100, 5362 keyup = function() { 5363 var scrollHeight = input[ 0 ].scrollHeight, 5364 clientHeight = input[ 0 ].clientHeight; 5365 5366 if ( clientHeight < scrollHeight ) { 5367 input.height(scrollHeight + extraLineHeight); 5368 } 5369 }, 5370 keyupTimeout; 5371 5372 input.keyup(function() { 5373 clearTimeout( keyupTimeout ); 5374 keyupTimeout = setTimeout( keyup, keyupTimeoutBuffer ); 5375 }); 5376 5377 // Issue 509: the browser is not providing scrollHeight properly until the styles load 5378 if ( $.trim( input.val() ) ) { 5379 // bind to the window load to make sure the height is calculated based on BOTH 5380 // the DOM and CSS 5381 $( window ).load( keyup ); 5382 5383 // binding to pagechange here ensures that for pages loaded via 5384 // ajax the height is recalculated without user input 5385 $( document ).one( "pagechange", keyup ); 5386 } 5387 } 5388 }, 5389 5390 disable: function(){ 5391 ( this.element.attr( "disabled", true ).is( "[type='search'],:jqmData(type='search')" ) ? 5392 this.element.parent() : this.element ).addClass( "ui-disabled" ); 5393 }, 5394 5395 enable: function(){ 5396 ( this.element.attr( "disabled", false).is( "[type='search'],:jqmData(type='search')" ) ? 5397 this.element.parent() : this.element ).removeClass( "ui-disabled" ); 5398 } 5399}); 5400 5401//auto self-init widgets 5402$( document ).bind( "pagecreate create", function( e ){ 5403 $.mobile.textinput.prototype.enhanceWithin( e.target ); 5404}); 5405 5406})( jQuery ); 5407/* 5408* custom "selectmenu" plugin 5409*/ 5410 5411(function( $, undefined ) { 5412 var extendSelect = function( widget ){ 5413 5414 var select = widget.select, 5415 selectID = widget.selectID, 5416 label = widget.label, 5417 thisPage = widget.select.closest( ".ui-page" ), 5418 screen = $( "<div>", {"class": "ui-selectmenu-screen ui-screen-hidden"} ).appendTo( thisPage ), 5419 selectOptions = widget._selectOptions(), 5420 isMultiple = widget.isMultiple = widget.select[ 0 ].multiple, 5421 buttonId = selectID + "-button", 5422 menuId = selectID + "-menu", 5423 menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' data-" +$.mobile.ns + "theme='"+ widget.options.theme +"' data-" +$.mobile.ns + "overlay-theme='"+ widget.options.overlayTheme +"'>" + 5424 "<div data-" + $.mobile.ns + "role='header'>" + 5425 "<div class='ui-title'>" + label.getEncodedText() + "</div>"+ 5426 "</div>"+ 5427 "<div data-" + $.mobile.ns + "role='content'></div>"+ 5428 "</div>" ).appendTo( $.mobile.pageContainer ).page(), 5429 5430 listbox = $("<div>", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-" + widget.options.overlayTheme + " " + $.mobile.defaultDialogTransition } ).insertAfter(screen), 5431 5432 list = $( "<ul>", { 5433 "class": "ui-selectmenu-list", 5434 "id": menuId, 5435 "role": "listbox", 5436 "aria-labelledby": buttonId 5437 }).attr( "data-" + $.mobile.ns + "theme", widget.options.theme ).appendTo( listbox ), 5438 5439 header = $( "<div>", { 5440 "class": "ui-header ui-bar-" + widget.options.theme 5441 }).prependTo( listbox ), 5442 5443 headerTitle = $( "<h1>", { 5444 "class": "ui-title" 5445 }).appendTo( header ), 5446 5447 headerClose = $( "<a>", { 5448 "text": widget.options.closeText, 5449 "href": "#", 5450 "class": "ui-btn-left" 5451 }).attr( "data-" + $.mobile.ns + "iconpos", "notext" ).attr( "data-" + $.mobile.ns + "icon", "delete" ).appendTo( header ).buttonMarkup(), 5452 5453 menuPageContent = menuPage.find( ".ui-content" ), 5454 5455 menuPageClose = menuPage.find( ".ui-header a" ); 5456 5457 5458 $.extend( widget, { 5459 select: widget.select, 5460 selectID: selectID, 5461 buttonId: buttonId, 5462 menuId: menuId, 5463 thisPage: thisPage, 5464 menuPage: menuPage, 5465 label: label, 5466 screen: screen, 5467 selectOptions: selectOptions, 5468 isMultiple: isMultiple, 5469 theme: widget.options.theme, 5470 listbox: listbox, 5471 list: list, 5472 header: header, 5473 headerTitle: headerTitle, 5474 headerClose: headerClose, 5475 menuPageContent: menuPageContent, 5476 menuPageClose: menuPageClose, 5477 placeholder: "", 5478 5479 build: function() { 5480 var self = this; 5481 5482 // Create list from select, update state 5483 self.refresh(); 5484 5485 self.select.attr( "tabindex", "-1" ).focus(function() { 5486 $( this ).blur(); 5487 self.button.focus(); 5488 }); 5489 5490 // Button events 5491 self.button.bind( "vclick keydown" , function( event ) { 5492 if ( event.type == "vclick" || 5493 event.keyCode && ( event.keyCode === $.mobile.keyCode.ENTER || 5494 event.keyCode === $.mobile.keyCode.SPACE ) ) { 5495 5496 self.open(); 5497 event.preventDefault(); 5498 } 5499 }); 5500 5501 // Events for list items 5502 self.list.attr( "role", "listbox" ) 5503 .delegate( ".ui-li>a", "focusin", function() { 5504 $( this ).attr( "tabindex", "0" ); 5505 }) 5506 .delegate( ".ui-li>a", "focusout", function() { 5507 $( this ).attr( "tabindex", "-1" ); 5508 }) 5509 .delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) { 5510 5511 // index of option tag to be selected 5512 var oldIndex = self.select[ 0 ].selectedIndex, 5513 newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ), 5514 option = self._selectOptions().eq( newIndex )[ 0 ]; 5515 5516 // toggle selected status on the tag for multi selects 5517 option.selected = self.isMultiple ? !option.selected : true; 5518 5519 // toggle checkbox class for multiple selects 5520 if ( self.isMultiple ) { 5521 $( this ).find( ".ui-icon" ) 5522 .toggleClass( "ui-icon-checkbox-on", option.selected ) 5523 .toggleClass( "ui-icon-checkbox-off", !option.selected ); 5524 } 5525 5526 // trigger change if value changed 5527 if ( self.isMultiple || oldIndex !== newIndex ) { 5528 self.select.trigger( "change" ); 5529 } 5530 5531 //hide custom select for single selects only 5532 if ( !self.isMultiple ) { 5533 self.close(); 5534 } 5535 5536 event.preventDefault(); 5537 }) 5538 .keydown(function( event ) { //keyboard events for menu items 5539 var target = $( event.target ), 5540 li = target.closest( "li" ), 5541 prev, next; 5542 5543 // switch logic based on which key was pressed 5544 switch ( event.keyCode ) { 5545 // up or left arrow keys 5546 case 38: 5547 prev = li.prev(); 5548 5549 // if there's a previous option, focus it 5550 if ( prev.length ) { 5551 target 5552 .blur() 5553 .attr( "tabindex", "-1" ); 5554 5555 prev.find( "a" ).first().focus(); 5556 } 5557 5558 return false;
5559 break; 5560 5561 // down or right arrow keys 5562 case 40: 5563 next = li.next(); 5564 5565 // if there's a next option, focus it 5566 if ( next.length ) { 5567 target 5568 .blur() 5569 .attr( "tabindex", "-1" ); 5570 5571 next.find( "a" ).first().focus(); 5572 } 5573 5574 return false; 5575 break; 5576 5577 // If enter or space is pressed, trigger click 5578 case 13: 5579 case 32: 5580 target.trigger( "click" ); 5581 5582 return false; 5583 break; 5584 } 5585 }); 5586 5587 // button refocus ensures proper height calculation 5588 // by removing the inline style and ensuring page inclusion 5589 self.menuPage.bind( "pagehide", function() { 5590 self.list.appendTo( self.listbox ); 5591 self._focusButton(); 5592 5593 // TODO centralize page removal binding / handling in the page plugin. 5594 // Suggestion from @jblas to do refcounting 5595 // 5596 // TODO extremely confusing dependency on the open method where the pagehide.remove 5597 // bindings are stripped to prevent the parent page from disappearing. The way 5598 // we're keeping pages in the DOM right now sucks 5599 // 5600 // rebind the page remove that was unbound in the open function 5601 // to allow for the parent page removal from actions other than the use 5602 // of a dialog sized custom select 5603 // 5604 // doing this here provides for the back button on the custom select dialog 5605 $.mobile._bindPageRemove.call( self.thisPage ); 5606 }); 5607 5608 // Events on "screen" overlay 5609 self.screen.bind( "vclick", function( event ) { 5610 self.close(); 5611 }); 5612 5613 // Close button on small overlays 5614 self.headerClose.click( function() { 5615 if ( self.menuType == "overlay" ) { 5616 self.close(); 5617 return false; 5618 } 5619 }); 5620 5621 // track this dependency so that when the parent page 5622 // is removed on pagehide it will also remove the menupage 5623 self.thisPage.addDependents( this.menuPage ); 5624 }, 5625 5626 _isRebuildRequired: function() { 5627 var list = this.list.find( "li" ), 5628 options = this._selectOptions(); 5629 5630 // TODO exceedingly naive method to determine difference 5631 // ignores value changes etc in favor of a forcedRebuild 5632 // from the user in the refresh method 5633 return options.text() !== list.text(); 5634 }, 5635 5636 refresh: function( forceRebuild , foo ){ 5637 var self = this, 5638 select = this.element, 5639 isMultiple = this.isMultiple, 5640 options = this._selectOptions(), 5641 selected = this.selected(), 5642 // return an array of all selected index's 5643 indicies = this.selectedIndices(); 5644 5645 if ( forceRebuild || this._isRebuildRequired() ) { 5646 self._buildList(); 5647 } 5648 5649 self.setButtonText(); 5650 self.setButtonCount(); 5651 5652 self.list.find( "li:not(.ui-li-divider)" ) 5653 .removeClass( $.mobile.activeBtnClass ) 5654 .attr( "aria-selected", false ) 5655 .each(function( i ) { 5656 5657 if ( $.inArray( i, indicies ) > -1 ) { 5658 var item = $( this ); 5659 5660 // Aria selected attr 5661 item.attr( "aria-selected", true ); 5662 5663 // Multiple selects: add the "on" checkbox state to the icon 5664 if ( self.isMultiple ) { 5665 item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" ); 5666 } else { 5667 item.addClass( $.mobile.activeBtnClass ); 5668 } 5669 } 5670 }); 5671 }, 5672 5673 close: function() { 5674 if ( this.options.disabled || !this.isOpen ) { 5675 return; 5676 } 5677 5678 var self = this; 5679 5680 if ( self.menuType == "page" ) { 5681 // doesn't solve the possible issue with calling change page 5682 // where the objects don't define data urls which prevents dialog key 5683 // stripping - changePage has incoming refactor 5684 window.history.back(); 5685 } else { 5686 self.screen.addClass( "ui-screen-hidden" ); 5687 self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass( "in" ); 5688 self.list.appendTo( self.listbox ); 5689 self._focusButton(); 5690 } 5691 5692 // allow the dialog to be closed again 5693 self.isOpen = false; 5694 }, 5695 5696 open: function() { 5697 if ( this.options.disabled ) { 5698 return; 5699 } 5700 5701 var self = this, 5702 menuHeight = self.list.parent().outerHeight(), 5703 menuWidth = self.list.parent().outerWidth(), 5704 activePage = $( ".ui-page-active" ), 5705 tOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled, 5706 tScrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage; 5707 scrollTop = tOverflow ? tScrollElem.scrollTop() : $( window ).scrollTop(), 5708 btnOffset = self.button.offset().top, 5709 screenHeight = window.innerHeight, 5710 screenWidth = window.innerWidth; 5711 5712 //add active class to button 5713 self.button.addClass( $.mobile.activeBtnClass ); 5714 5715 //remove after delay 5716 setTimeout( function() { 5717 self.button.removeClass( $.mobile.activeBtnClass ); 5718 }, 300); 5719 5720 function focusMenuItem() { 5721 self.list.find( $.mobile.activeBtnClass ).focus(); 5722 } 5723 5724 if ( menuHeight >
5724 screenHeight - 80 || !$.support.scrollTop ) { 5725 // prevent the parent page from being removed from the DOM, 5726 // otherwise the results of selecting a list item in the dialog 5727 // fall into a black hole 5728 self.thisPage.unbind( "pagehide.remove" ); 5729 5730 //for webos (set lastscroll using button offset) 5731 if ( scrollTop == 0 && btnOffset > screenHeight ) { 5732 self.thisPage.one( "pagehide", function() { 5733 $( this ).jqmData( "lastScroll", btnOffset ); 5734 }); 5735 } 5736 5737 self.menuPage.one( "pageshow", function() { 5738 // silentScroll() is called whenever a page is shown to restore 5739 // any previous scroll position the page may have had. We need to 5740 // wait for the "silentscroll" event before setting focus to avoid 5741 // the browser"s "feature" which offsets rendering to make sure 5742 // whatever has focus is in view. 5743 $( window ).one( "silentscroll", function() { 5744 focusMenuItem(); 5745 }); 5746 5747 self.isOpen = true; 5748 }); 5749 5750 self.menuType = "page"; 5751 self.menuPageContent.append( self.list ); 5752 self.menuPage.find("div .ui-title").text(self.label.text()); 5753 $.mobile.changePage( self.menuPage, { 5754 transition: $.mobile.defaultDialogTransition 5755 }); 5756 } else { 5757 self.menuType = "overlay"; 5758 5759 self.screen.height( $(document).height() ) 5760 .removeClass( "ui-screen-hidden" ); 5761 5762 // Try and center the overlay over the button 5763 var roomtop = btnOffset - scrollTop, 5764 roombot = scrollTop + screenHeight - btnOffset, 5765 halfheight = menuHeight / 2, 5766 maxwidth = parseFloat( self.list.parent().css( "max-width" ) ), 5767 newtop, newleft; 5768 5769 if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) { 5770 newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight; 5771 } else { 5772 // 30px tolerance off the edges 5773 newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30; 5774 } 5775 5776 // If the menuwidth is smaller than the screen center is 5777 if ( menuWidth < maxwidth ) { 5778 newleft = ( screenWidth - menuWidth ) / 2; 5779 } else { 5780 5781 //otherwise insure a >= 30px offset from the left 5782 newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2; 5783 5784 // 30px tolerance off the edges 5785 if ( newleft < 30 ) { 5786 newleft = 30; 5787 } else if ( (newleft + menuWidth) > screenWidth ) { 5788 newleft = screenWidth - menuWidth - 30; 5789 } 5790 } 5791 5792 self.listbox.append( self.list ) 5793 .removeClass( "ui-selectmenu-hidden" ) 5794 .css({ 5795 top: newtop, 5796 left: newleft 5797 }) 5798 .addClass( "in" ); 5799 5800 focusMenuItem(); 5801 5802 // duplicate with value set in page show for dialog sized selects 5803 self.isOpen = true; 5804 } 5805 }, 5806 5807 _buildList: function() { 5808 var self = this, 5809 o = this.options, 5810 placeholder = this.placeholder, 5811 optgroups = [], 5812 lis = [], 5813 dataIcon = self.isMultiple ? "checkbox-off" : "false"; 5814 5815 self.list.empty().filter( ".ui-listview" ).listview( "destroy" ); 5816 5817 // Populate menu with options from select element 5818 self.select.find( "option" ).each( function( i ) { 5819 var $this = $( this ), 5820 $parent = $this.parent(), 5821 text = $this.getEncodedText(), 5822 anchor = "<a href='#'>"+ text +"</a>", 5823 classes = [], 5824 extraAttrs = []; 5825 5826 // Are we inside an optgroup? 5827 if ( $parent.is( "optgroup" ) ) { 5828 var optLabel = $parent.attr( "label" ); 5829 5830 // has this optgroup already been built yet? 5831 if ( $.inArray( optLabel, optgroups ) === -1 ) { 5832 lis.push( "<li data-" + $.mobile.ns + "role='list-divider'>"+ optLabel +"</li>" ); 5833 optgroups.push( optLabel ); 5834 } 5835 } 5836 5837 // Find placeholder text 5838 // TODO: Are you sure you want to use getAttribute? ^RW 5839 if ( !this.getAttribute( "value" ) || text.length == 0 || $this.jqmData( "placeholder" ) ) { 5840 if ( o.hidePlaceholderMenuItems ) { 5841 classes.push( "ui-selectmenu-placeholder" ); 5842 } 5843 placeholder = self.placeholder = text; 5844 } 5845 5846 // support disabled option tags 5847 if ( this.disabled ) { 5848 classes.push( "ui-disabled" ); 5849 extraAttrs.push( "aria-disabled='true'" ); 5850 } 5851
5852 lis.push( "<li data-" + $.mobile.ns + "option-index='" + i + "' data-" + $.mobile.ns + "icon='"+ dataIcon +"' class='"+ classes.join(" ") + "' " + extraAttrs.join(" ") +">"+ anchor +"</li>" ); 5853 }); 5854 5855 self.list.html( lis.join(" ") ); 5856 5857 self.list.find( "li" ) 5858 .attr({ "role": "option", "tabindex": "-1" }) 5859 .first().attr( "tabindex", "0" ); 5860 5861 // Hide header close link for single selects 5862 if ( !this.isMultiple ) { 5863 this.headerClose.hide(); 5864 } 5865 5866 // Hide header if it's not a multiselect and there's no placeholder 5867 if ( !this.isMultiple && !placeholder.length ) { 5868 this.header.hide(); 5869 } else { 5870 this.headerTitle.text( this.placeholder ); 5871 } 5872 5873 // Now populated, create listview 5874 self.list.listview(); 5875 }, 5876 5877 _button: function(){ 5878 return $( "<a>", { 5879 "href": "#", 5880 "role": "button", 5881 // TODO value is undefined at creation 5882 "id": this.buttonId, 5883 "aria-haspopup": "true", 5884 5885 // TODO value is undefined at creation 5886 "aria-owns": this.menuId 5887 }); 5888 } 5889 }); 5890 }; 5891 5892 $( "select" ).live( "selectmenubeforecreate", function(){ 5893 var selectmenuWidget = $( this ).data( "selectmenu" ); 5894 5895 if( !selectmenuWidget.options.nativeMenu ){ 5896 extendSelect( selectmenuWidget ); 5897 } 5898 }); 5899})( jQuery ); 5900/* 5901* "selectmenu" plugin 5902*/ 5903 5904(function( $, undefined ) { 5905 5906$.widget( "mobile.selectmenu", $.mobile.widget, { 5907 options: { 5908 theme: null, 5909 disabled: false, 5910 icon: "arrow-d", 5911 iconpos: "right", 5912 inline: null, 5913 corners: true, 5914 shadow: true, 5915 iconshadow: true, 5916 menuPageTheme: "b", 5917 overlayTheme: "a", 5918 hidePlaceholderMenuItems: true, 5919 closeText: "Close", 5920 nativeMenu: true, 5921 initSelector: "select:not(:jqmData(role='slider'))" 5922 }, 5923 5924 _button: function(){ 5925 return $( "<div/>" ); 5926 }, 5927 5928 _setDisabled: function( value ) { 5929 this.element.attr( "disabled", value ); 5930 this.button.attr( "aria-disabled", value ); 5931 return this._setOption( "disabled", value ); 5932 }, 5933 5934 _focusButton : function() { 5935 var self = this; 5936 5937 setTimeout( function() { 5938 self.button.focus(); 5939 }, 40); 5940 }, 5941 5942 _selectOptions: function() { 5943 return this.select.find( "option" ); 5944 }, 5945 5946 // setup items that are generally necessary for select menu extension 5947 _preExtension: function(){ 5948 this.select = this.element.wrap( "<div class='ui-select'>" ); 5949 this.selectID = this.select.attr( "id" ); 5950 this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" ); 5951 this.isMultiple = this.select[ 0 ].multiple; 5952 if ( !this.options.theme ) { 5953 this.options.theme = $.mobile.getInheritedTheme( this.select, "c" ); 5954 } 5955 }, 5956 5957 _create: function() { 5958 this._preExtension(); 5959 5960 // Allows for extension of the native select for custom selects and other plugins 5961 // see select.custom for example extension 5962 // TODO explore plugin registration 5963 this._trigger( "beforeCreate" ); 5964 5965 this.button = this._button(); 5966 5967 var self = this, 5968 5969 options = this.options, 5970 5971 // IE throws an exception at options.item() function when 5972 // there is no selected item 5973 // select first in this case 5974 selectedIndex = this.select[ 0 ].selectedIndex == -1 ? 0 : this.select[ 0 ].selectedIndex, 5975 5976 // TODO values buttonId and menuId are undefined here 5977 button = this.button 5978 .text( $( this.select[ 0 ].options.item( selectedIndex ) ).text() ) 5979 .insertBefore( this.select ) 5980 .buttonMarkup( { 5981 theme: options.theme, 5982 icon: options.icon, 5983 iconpos: options.iconpos, 5984 inline: options.inline, 5985 corners: options.corners, 5986 shadow: options.shadow, 5987 iconshadow: options.iconshadow 5988 }); 5989 5990 // Opera does not properly support opacity on select elements 5991 // In Mini, it hides the element, but not its text 5992 // On the desktop,it seems to do the opposite 5993 // for these reasons, using the nativeMenu option results in a full native select in Opera 5994 if ( options.nativeMenu && window.opera && window.opera.version ) { 5995 this.select.addClass( "ui-select-nativeonly" ); 5996 } 5997 5998 // Add counter for multi selects 5999 if ( this.isMultiple ) { 6000 this.buttonCount = $( "<span>" ) 6001 .addClass( "ui-li-count ui-btn-up-c ui-btn-corner-all" ) 6002 .hide() 6003 .appendTo( button.addClass('ui-li-has-count') ); 6004 } 6005 6006 // Disable if specified 6007 if ( options.disabled || this.element.attr('disabled')) { 6008 this.disable(); 6009 } 6010 6011 // Events on native select 6012 this.select.change( function() { 6013 self.refresh(); 6014 }); 6015 6016 this.build(); 6017 }, 6018 6019 build: function() { 6020 var self = this; 6021 6022 this.select 6023 .appendTo( self.button ) 6024 .bind( "vmousedown", function() { 6025 // Add active class to button 6026 self.button.addClass( $.mobile.activeBtnClass ); 6027 }) 6028 .bind( "focus vmouseover", function() { 6029 self.button.trigger( "vmouseover" ); 6030 }) 6031 .bind( "vmousemove", function() { 6032 // Remove active class on scroll/touchmove 6033 self.button.removeClass( $.mobile.activeBtnClass ); 6034 }) 6035 .bind( "change blur vmouseout", function() { 6036 self.button.trigger( "vmouseout" ) 6037 .removeClass( $.mobile.activeBtnClass ); 6038 }) 6039 .bind( "change blur", function() { 6040 self.button.removeClass( "ui-btn-down-" + self.options.theme ); 6041 }); 6042 }, 6043 6044 selected: function() { 6045 return this._selectOptions().filter( ":selected" ); 6046 }, 6047 6048 selectedIndices: function() { 6049 var self = this; 6050 6051 return this.selected().map( function() { 6052 return self._selectOptions().index( this ); 6053 }).get(); 6054 }, 6055 6056 setButtonText: function() { 6057 var self = this, selected = this.selected(); 6058 6059 this.button.find( ".ui-btn-text" ).text( function() { 6060 if ( !self.isMultiple ) { 6061 return selected.text(); 6062 } 6063 6064 return selected.length ? selected.map( function() { 6065 return $( this ).text(); 6066 }).get().join( ", " ) : self.placeholder; 6067 }); 6068 }, 6069 6070 setButtonCount: function() { 6071 var selected = this.selected(); 6072 6073 // multiple count inside button 6074 if ( this.isMultiple ) { 6075 this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length ); 6076 } 6077 }, 6078 6079 refresh: function() { 6080 this.setButtonText(); 6081 this.setButtonCount(); 6082 }, 6083 6084 // open and close preserved in native selects 6085 // to simplify users code when looping over selects 6086 open: $.noop, 6087 close: $.noop, 6088 6089 disable: function() { 6090 this._setDisabled( true ); 6091 this.button.addClass( "ui-disabled" ); 6092 }, 6093 6094 enable: function() { 6095 this._setDisabled( false ); 6096 this.button.removeClass( "ui-disabled" ); 6097 } 6098}); 6099 6100//auto self-init widgets 6101$( document ).bind( "pagecreate create", function( e ){ 6102 $.mobile.selectmenu.prototype.enhanceWithin( e.target ); 6103}); 6104})( jQuery ); 6105/* 6106* "buttons" plugin - for making button-like links 6107*/ 6108 6109( function( $, undefined ) { 6110 6111$.fn.buttonMarkup = function( options ) { 6112 options = options || {}; 6113 6114 for ( var i = 0; i < this.length; i++ ) { 6115 var el = this.eq( i ), 6116 e = el[ 0 ], 6117 o = $.extend( {}, $.fn.buttonMarkup.defaults, { 6118 icon: options.icon !== undefined ? options.icon : el.jqmData( "icon" ), 6119 iconpos: options.iconpos !== undefined ? options.iconpos : el.jqmData( "iconpos" ), 6120 theme: options.theme !== undefined ? options.theme : el.jqmData( "theme" ), 6121 inline: options.inline !== undefined ? options.inline : el.jqmData( "inline" ), 6122 shadow: options.shadow !== undefined ? options.shadow : el.jqmData( "shadow" ), 6123 corners: options.corners !== undefined ? options.corners : el.jqmData( "corners" ), 6124 iconshadow: options.iconshadow !== undefined ? options.iconshadow : el.jqmData( "iconshadow" ) 6125 }, options ), 6126 6127 // Classes Defined 6128 innerClass = "ui-btn-inner", 6129 textClass = "ui-btn-text", 6130 buttonClass, iconClass, 6131 6132 // Button inner markup 6133 buttonInner = document.createElement( o.wrapperEls ), 6134 buttonText = document.createElement( o.wrapperEls ), 6135 buttonIcon = o.icon ? document.createElement( "span" ) : null; 6136 6137 if ( attachEvents ) { 6138 attachEvents(); 6139 } 6140 6141 // if not, try to find closest theme container 6142 if ( !o.theme ) { 6143 o.theme = $.mobile.getInheritedTheme( el, "c" ); 6144 } 6145 6146 buttonClass = "ui-btn ui-btn-up-" + o.theme; 6147 6148 if ( o.inline ) { 6149 buttonClass += " ui-btn-inline"; 6150 } 6151 6152 if ( o.icon ) { 6153 o.icon = "ui-icon-" + o.icon; 6154 o.iconpos = o.iconpos || "left"; 6155 6156 iconClass = "ui-icon " + o.icon; 6157 6158 if ( o.iconshadow ) { 6159 iconClass += " ui-icon-shadow"; 6160 } 6161 } 6162 6163 if ( o.iconpos ) { 6164 buttonClass += " ui-btn-icon-" + o.iconpos; 6165 6166 if ( o.iconpos == "notext" && !el.attr( "title" ) ) { 6167 el.attr( "title", el.getEncodedText() ); 6168 } 6169 } 6170 6171 if ( o.corners ) { 6172 buttonClass += " ui-btn-corner-all"; 6173 innerClass += " ui-btn-corner-all"; 6174 } 6175 6176 if ( o.shadow ) { 6177 buttonClass += " ui-shadow"; 6178 } 6179 6180 e.setAttribute( "data-" + $.mobile.ns + "theme", o.theme ); 6181 el.addClass( buttonClass ); 6182 6183 buttonInner.className = innerClass; 6184 buttonInner.setAttribute("aria-hidden", "true"); 6185 6186 buttonText.className = textClass; 6187 buttonInner.appendChild( buttonText ); 6188 6189 if ( buttonIcon ) { 6190 buttonIcon.className = iconClass; 6191 buttonInner.appendChild( buttonIcon ); 6192 } 6193 6194 while ( e.firstChild ) { 6195 buttonText.appendChild( e.firstChild ); 6196 } 6197 6198 e.appendChild( buttonInner ); 6199 6200 // TODO obviously it would be nice to pull this element out instead of 6201 // retrieving it from the DOM again, but this change is much less obtrusive 6202 // and 1.0 draws nigh 6203 $.data( e, 'textWrapper', $( buttonText ) ); 6204 } 6205 6206 return this; 6207}; 6208 6209$.fn.buttonMarkup.defaults = { 6210 corners: true, 6211 shadow: true, 6212 iconshadow: true, 6213 inline: false, 6214 wrapperEls: "span" 6215}; 6216 6217function closestEnabledButton( element ) { 6218 var cname; 6219 6220 while ( element ) { 6221 cname = element.className && element.className.split(' '); 6222 if ( cname && $.inArray( "ui-btn", cname ) > -1 && $.inArray( "ui-disabled", cname ) < 0 ) { 6223 break; 6224 } 6225 element = element.parentNode; 6226 } 6227 6228 return element; 6229} 6230 6231var attachEvents = function() { 6232 $( document ).bind( {
6233 "vmousedown": function( event ) { 6234 var btn = closestEnabledButton( event.target ), 6235 $btn, theme; 6236 6237 if ( btn ) { 6238 $btn = $( btn ); 6239 theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); 6240 $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme ); 6241 } 6242 }, 6243 "vmousecancel vmouseup": function( event ) { 6244 var btn = closestEnabledButton( event.target ), 6245 $btn, theme; 6246 6247 if ( btn ) { 6248 $btn = $( btn ); 6249 theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); 6250 $btn.removeClass( "ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme ); 6251 } 6252 }, 6253 "vmouseover focus": function( event ) { 6254 var btn = closestEnabledButton( event.target ), 6255 $btn, theme; 6256 6257 if ( btn ) { 6258 $btn = $( btn ); 6259 theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); 6260 $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme ); 6261 } 6262 }, 6263 "vmouseout blur": function( event ) { 6264 var btn = closestEnabledButton( event.target ), 6265 $btn, theme; 6266 6267 if ( btn ) { 6268 $btn = $( btn ); 6269 theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); 6270 $btn.removeClass( "ui-btn-hover-" + theme + " ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme ); 6271 } 6272 } 6273 }); 6274 6275 attachEvents = null; 6276}; 6277 6278//links in bars, or those with data-role become buttons 6279//auto self-init widgets 6280$( document ).bind( "pagecreate create", function( e ){ 6281 6282 $( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a", e.target ) 6283 .not( ".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" ) 6284 .buttonMarkup(); 6285}); 6286 6287})( jQuery ); 6288/* 6289* "controlgroup" plugin - corner-rounding for groups of buttons, checks, radios, etc 6290*/ 6291 6292(function( $, undefined ) { 6293 6294$.fn.controlgroup = function( options ) { 6295 6296 return this.each(function() { 6297 6298 var $el = $( this ), 6299 o = $.extend({ 6300 direction: $el.jqmData( "type" ) || "vertical", 6301 shadow: false, 6302 excludeInvisible: true 6303 }, options ), 6304 groupheading = $el.children( "legend" ), 6305 flCorners = o.direction == "horizontal" ? [ "ui-corner-left", "ui-corner-right" ] : [ "ui-corner-top", "ui-corner-bottom" ], 6306 type = $el.find( "input" ).first().attr( "type" ); 6307 6308 // Replace legend with more stylable replacement div 6309 if ( groupheading.length ) { 6310 $el.wrapInner( "<div class='ui-controlgroup-controls'></div>" ); 6311 $( "<div role='heading' class='ui-controlgroup-label'>" + groupheading.html() + "</div>" ).insertBefore( $el.children(0) ); 6312 groupheading.remove(); 6313 } 6314 6315 $el.addClass( "ui-corner-all ui-controlgroup ui-controlgroup-" + o.direction ); 6316 6317 // TODO: This should be moved out to the closure 6318 // otherwise it is redefined each time controlgroup() is called 6319 function flipClasses( els ) { 6320 els.removeClass( "ui-btn-corner-all ui-shadow" ) 6321 .eq( 0 ).addClass( flCorners[ 0 ] ) 6322 .end() 6323 .last().addClass( flCorners[ 1 ] ).addClass( "ui-controlgroup-last" ); 6324 } 6325 6326 flipClasses( $el.find( ".ui-btn" + ( o.excludeInvisible ? ":visible" : "" ) ) ); 6327 flipClasses( $el.find( ".ui-btn-inner" ) ); 6328 6329 if ( o.shadow ) { 6330 $el.addClass( "ui-shadow" ); 6331 } 6332 }); 6333}; 6334 6335//auto self-init widgets 6336$( document ).bind( "pagecreate create", function( e ){ 6337 $( ":jqmData(role='controlgroup')", e.target ).controlgroup({ excludeInvisible: false }); 6338}); 6339 6340})(jQuery);/* 6341* "links" plugin - simple class additions for links 6342*/ 6343 6344(function( $, undefined ) { 6345 6346$( document ).bind( "pagecreate create", function( e ){ 6347 6348 //links within content areas 6349 $( e.target ) 6350 .find( "a" ) 6351 .not( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" ) 6352 .addClass( "ui-link" ); 6353 6354}); 6355 6356}
6356)( jQuery );/* 6357* "fixHeaderFooter" plugin - on-demand positioning for headers,footers 6358*/ 6359 6360(function( $, undefined ) { 6361 6362var slideDownClass = "ui-header-fixed ui-fixed-inline fade", 6363 slideUpClass = "ui-footer-fixed ui-fixed-inline fade", 6364 6365 slideDownSelector = ".ui-header:jqmData(position='fixed')", 6366 slideUpSelector = ".ui-footer:jqmData(position='fixed')"; 6367 6368$.fn.fixHeaderFooter = function( options ) { 6369 6370 if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) { 6371 return this; 6372 } 6373 6374 return this.each(function() { 6375 var $this = $( this ); 6376 6377 if ( $this.jqmData( "fullscreen" ) ) { 6378 $this.addClass( "ui-page-fullscreen" ); 6379 } 6380 6381 // Should be slidedown 6382 $this.find( slideDownSelector ).addClass( slideDownClass ); 6383 6384 // Should be slideup 6385 $this.find( slideUpSelector ).addClass( slideUpClass ); 6386 }); 6387}; 6388 6389// single controller for all showing,hiding,toggling 6390$.mobile.fixedToolbars = (function() { 6391 6392 if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) { 6393 return; 6394 } 6395 6396 var stickyFooter, delayTimer, 6397 currentstate = "inline", 6398 autoHideMode = false, 6399 showDelay = 100, 6400 ignoreTargets = "a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed", 6401 toolbarSelector = ".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last", 6402 // for storing quick references to duplicate footers 6403 supportTouch = $.support.touch, 6404 touchStartEvent = supportTouch ? "touchstart" : "mousedown", 6405 touchStopEvent = supportTouch ? "touchend" : "mouseup", 6406 stateBefore = null, 6407 scrollTriggered = false, 6408 touchToggleEnabled = true; 6409 6410 function showEventCallback( event ) { 6411 // An event that affects the dimensions of the visual viewport has 6412 // been triggered. If the header and/or footer for the current page are in overlay 6413 // mode, we want to hide them, and then fire off a timer to show them at a later 6414 // point. Events like a resize can be triggered continuously during a scroll, on 6415 // some platforms, so the timer is used to delay the actual positioning until the 6416 // flood of events have subsided. 6417 // 6418 // If we are in autoHideMode, we don't do anything because we know the scroll 6419 // callbacks for the plugin will fire off a show when the scrolling has stopped. 6420 if ( !autoHideMode && currentstate === "overlay" ) { 6421 if ( !delayTimer ) { 6422 $.mobile.fixedToolbars.hide( true ); 6423 } 6424 6425 $.mobile.fixedToolbars.startShowTimer(); 6426 } 6427 } 6428 6429 $(function() { 6430 var $document = $( document ), 6431 $window = $( window ); 6432 6433 $document 6434 .bind( "vmousedown", function( event ) { 6435 if ( touchToggleEnabled ) { 6436 stateBefore = currentstate; 6437 } 6438 }) 6439 .bind( "vclick", function( event ) { 6440 if ( touchToggleEnabled ) { 6441 6442 if ( $(event.target).closest( ignoreTargets ).length ) { 6443 return; 6444 } 6445 6446 if ( !scrollTriggered ) { 6447 $.mobile.fixedToolbars.toggle( stateBefore ); 6448 stateBefore = null; 6449 } 6450 } 6451 }) 6452 .bind( "silentscroll", showEventCallback ); 6453 6454 6455 // The below checks first for a $(document).scrollTop() value, and if zero, binds scroll events to $(window) instead. 6456 // If the scrollTop value is actually zero, both will return zero anyway. 6457 // 6458 // Works with $(document), not $(window) : Opera Mobile (WinMO phone; kinda broken anyway) 6459 // Works with $(window), not $(document) : IE 7/8 6460 // Works with either $(window) or $(document) : Chrome, FF 3.6/4, Android 1.6/2.1, iOS 6461 // Needs work either way : BB5, Opera Mobile (iOS) 6462 6463 ( ( $document.scrollTop() === 0 ) ? $window : $document ) 6464 .bind( "scrollstart", function( event ) { 6465 6466 scrollTriggered = true; 6467 6468 if ( stateBefore === null ) { 6469 stateBefore = currentstate; 6470 } 6471 6472 // We only enter autoHideMode if the headers/footers are in 6473 // an overlay state or the show timer was started. If the 6474 // show timer is set, clear it so the headers/footers don't 6475 // show up until after we're done scrolling. 6476 var isOverlayState = stateBefore == "overlay"; 6477 6478 autoHideMode = isOverlayState || !!delayTimer; 6479 6480 if ( autoHideMode ) { 6481 $.mobile.fixedToolbars.clearShowTimer(); 6482 6483 if ( isOverlayState ) { 6484 $.mobile.fixedToolbars.hide( true ); 6485 } 6486 } 6487 }) 6488 .bind( "scrollstop", function( event ) { 6489 6490 if ( $( event.target ).closest( ignoreTargets ).length ) { 6491 return; 6492 } 6493 6494 scrollTriggered = false;
6495 6496 if ( autoHideMode ) { 6497 $.mobile.fixedToolbars.startShowTimer(); 6498 autoHideMode = false; 6499 } 6500 stateBefore = null; 6501 }); 6502 6503 $window.bind( "resize updatelayout", showEventCallback ); 6504 }); 6505 6506 // 1. Before page is shown, check for duplicate footer 6507 // 2. After page is shown, append footer to new page 6508 $( ".ui-page" ) 6509 .live( "pagebeforeshow", function( event, ui ) { 6510 6511 var page = $( event.target ), 6512 footer = page.find( ":jqmData(role='footer')" ), 6513 id = footer.data( "id" ), 6514 prevPage = ui.prevPage, 6515 prevFooter = prevPage && prevPage.find( ":jqmData(role='footer')" ), 6516 prevFooterMatches = prevFooter.length && prevFooter.jqmData( "id" ) === id; 6517 6518 if ( id && prevFooterMatches ) { 6519 stickyFooter = footer; 6520 setTop( stickyFooter.removeClass( "fade in out" ).appendTo( $.mobile.pageContainer ) ); 6521 } 6522 }) 6523 .live( "pageshow", function( event, ui ) { 6524 6525 var $this = $( this ); 6526 6527 if ( stickyFooter && stickyFooter.length ) { 6528 6529 setTimeout(function() { 6530 setTop( stickyFooter.appendTo( $this ).addClass( "fade" ) ); 6531 stickyFooter = null; 6532 }, 500); 6533 } 6534 6535 $.mobile.fixedToolbars.show( true, this ); 6536 }); 6537 6538 // When a collapsiable is hidden or shown we need to trigger the fixed toolbar to reposition itself (#1635) 6539 $( ".ui-collapsible-contain" ).live( "collapse expand", showEventCallback ); 6540 6541 // element.getBoundingClientRect() is broken in iOS 3.2.1 on the iPad. The 6542 // coordinates inside of the rect it returns don't have the page scroll position 6543 // factored out of it like the other platforms do. To get around this, 6544 // we'll just calculate the top offset the old fashioned way until core has 6545 // a chance to figure out how to handle this situation. 6546 // 6547 // TODO: We'll need to get rid of getOffsetTop() once a fix gets folded into core. 6548 6549 function getOffsetTop( ele ) { 6550 var top = 0, 6551 op, body; 6552 6553 if ( ele ) { 6554 body = document.body; 6555 op = ele.offsetParent; 6556 top = ele.offsetTop; 6557 6558 while ( ele && ele != body ) { 6559 top += ele.scrollTop || 0; 6560 6561 if ( ele == op ) { 6562 top += op.offsetTop; 6563 op = ele.offsetParent; 6564 } 6565 6566 ele = ele.parentNode; 6567 } 6568 } 6569 return top; 6570 } 6571 6572 function setTop( el ) { 6573 var fromTop = $(window).scrollTop(), 6574 thisTop = getOffsetTop( el[ 0 ] ), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead. 6575 thisCSStop = el.css( "top" ) == "auto" ? 0 : parseFloat(el.css( "top" )), 6576 screenHeight = window.innerHeight, 6577 thisHeight = el.outerHeight(), 6578 useRelative = el.parents( ".ui-page:not(.ui-page-fullscreen)" ).length, 6579 relval; 6580 6581 if ( el.is( ".ui-header-fixed" ) ) { 6582 6583 relval = fromTop - thisTop + thisCSStop; 6584 6585 if ( relval < thisTop ) { 6586 relval = 0; 6587 } 6588 6589 return el.css( "top", useRelative ? relval : fromTop ); 6590 } else { 6591 // relval = -1 * (thisTop - (fromTop + screenHeight) + thisCSStop + thisHeight); 6592 // if ( relval > thisTop ) { relval = 0; } 6593 relval = fromTop + screenHeight - thisHeight - (thisTop - thisCSStop ); 6594 6595 return el.css( "top", useRelative ? relval : fromTop + screenHeight - thisHeight ); 6596 } 6597 } 6598 6599 // Exposed methods 6600 return { 6601 6602 show: function( immediately, page ) { 6603 6604 $.mobile.fixedToolbars.clearShowTimer(); 6605 6606 currentstate = "overlay"; 6607 6608 var $ap = page ? $( page ) : 6609 ( $.mobile.activePage ? $.mobile.activePage : 6610 $( ".ui-page-active" ) ); 6611 6612 return $ap.children( toolbarSelector ).each(function() { 6613 6614 var el = $( this ), 6615 fromTop = $( window ).scrollTop(), 6616 // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead. 6617 thisTop = getOffsetTop( el[ 0 ] ), 6618 screenHeight = window.innerHeight, 6619 thisHeight = el.outerHeight(), 6620 alreadyVisible = ( el.is( ".ui-header-fixed" ) && fromTop <= thisTop + thisHeight ) || 6621 ( el.is( ".ui-footer-fixed" ) && thisTop <= fromTop + screenHeight ); 6622 6623 // Add state class 6624 el.addClass( "ui-fixed-overlay" ).removeClass( "ui-fixed-inline" ); 6625 6626 if ( !alreadyVisible && !immediately ) { 6627 el.animationComplete(function() { 6628 el.removeClass( "in" ); 6629 }).addClass( "in" ); 6630 } 6631 setTop(el); 6632 }); 6633 }, 6634 6635 hide: function( immediately ) { 6636 6637 currentstate = "inline"; 6638 6639 var $ap = $.mobile.activePage ? $.mobile.activePage : 6640 $( ".ui-page-active" ); 6641 6642 return $ap.children( toolbarSelector ).each(function() { 6643 6644 var el = $(this), 6645 thisCSStop = el.css( "top" ), 6646 classes; 6647 6648 thisCSStop = thisCSStop == "auto" ? 0 : 6649 parseFloat(thisCSStop); 6650 6651 // Add state class 6652 el.addClass( "ui-fixed-inline" ).removeClass( "ui-fixed-overlay" ); 6653 6654 if ( thisCSStop < 0 || ( el.is( ".ui-header-fixed" ) && thisCSStop !== 0 ) ) { 6655 6656 if ( immediately ) { 6657 el.css( "top", 0); 6658 } else { 6659 6660 if ( el.css( "top" ) !== "auto" && parseFloat( el.css( "top" ) ) !== 0 ) { 6661 6662 classes = "out reverse"; 6663 6664 el.animationComplete(function() { 6665 el.removeClass( classes ).css( "top", 0 ); 6666 }).addClass( classes ); 6667 } 6668 } 6669 } 6670 }); 6671 }, 6672 6673 startShowTimer: function() { 6674 6675 $.mobile.fixedToolbars.clearShowTimer(); 6676 6677 var args = [].slice.call( arguments ); 6678 6679 delayTimer = setTimeout(function() { 6680 delayTimer = undefined; 6681 $.mobile.fixedToolbars.show.apply( null, args ); 6682 }, showDelay); 6683 }, 6684 6685 clearShowTimer: function() { 6686 if ( delayTimer ) { 6687 clearTimeout( delayTimer ); 6688 } 6689 delayTimer = undefined; 6690 }, 6691 6692 toggle: function( from ) { 6693 if ( from ) { 6694 currentstate = from; 6695 } 6696 return ( currentstate === "overlay" ) ? $.mobile.fixedToolbars.hide() : 6697 $.mobile.fixedToolbars.show(); 6698 }, 6699 6700 setTouchToggleEnabled: function( enabled ) { 6701 touchToggleEnabled = enabled; 6702 } 6703 }; 6704})(); 6705 6706//auto self-init widgets 6707$( document ).bind( "pagecreate create", function( event ) { 6708 6709 if ( $( ":jqmData(position='fixed')", event.target ).length ) { 6710 6711 $( event.target ).each(function() { 6712 6713 if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) { 6714 return this; 6715 } 6716 6717 var $this = $( this ); 6718 6719 if ( $this.jqmData( "fullscreen" ) ) { 6720 $this.addClass( "ui-page-fullscreen" ); 6721 } 6722 6723 // Should be slidedown 6724 $this.find( slideDownSelector ).addClass( slideDownClass ); 6725 6726 // Should be slideup 6727 $this.find( slideUpSelector ).addClass( slideUpClass ); 6728 6729 }) 6730 6731 } 6732}); 6733 6734})( jQuery ); 6735/* 6736* "fixHeaderFooter" native plugin - Behavior for "fixed" headers,footers, and scrolling inner content 6737*/ 6738 6739(function( $, undefined ) { 6740 6741// Enable touch overflow scrolling when it's natively supported 6742$.mobile.touchOverflowEnabled = false;
6743 6744// Enabled zoom when touch overflow is enabled. Can cause usability issues, unfortunately 6745$.mobile.touchOverflowZoomEnabled = false; 6746 6747$( document ).bind( "pagecreate", function( event ) { 6748 if( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){ 6749 6750 var $target = $( event.target ), 6751 scrollStartY = 0; 6752 6753 if( $target.is( ":jqmData(role='page')" ) ){ 6754 6755 $target.each(function() { 6756 var $page = $( this ), 6757 $fixies = $page.find( ":jqmData(role='header'), :jqmData(role='footer')" ).filter( ":jqmData(position='fixed')" ), 6758 fullScreen = $page.jqmData( "fullscreen" ), 6759 $scrollElem = $fixies.length ? $page.find( ".ui-content" ) : $page; 6760 6761 $page.addClass( "ui-mobile-touch-overflow" ); 6762 6763 $scrollElem.bind( "scrollstop", function(){ 6764 if( $scrollElem.scrollTop() > 0 ){ 6765 window.scrollTo( 0, $.mobile.defaultHomeScroll ); 6766 } 6767 }); 6768 6769 if( $fixies.length ){ 6770 6771 $page.addClass( "ui-native-fixed" ); 6772 6773 if( fullScreen ){ 6774 6775 $page.addClass( "ui-native-fullscreen" ); 6776 6777 $fixies.addClass( "fade in" ); 6778 6779 $( document ).bind( "vclick", function(){ 6780 $fixies 6781 .removeClass( "ui-native-bars-hidden" ) 6782 .toggleClass( "in out" ) 6783 .animationComplete(function(){ 6784 $(this).not( ".in" ).addClass( "ui-native-bars-hidden" ); 6785 }); 6786 }); 6787 } 6788 } 6789 }); 6790 } 6791 } 6792}); 6793 6794})( jQuery ); 6795/* 6796* "init" - Initialize the framework 6797*/ 6798 6799(function( $, window, undefined ) { 6800 var $html = $( "html" ), 6801 $head = $( "head" ), 6802 $window = $( window ); 6803 6804 // trigger mobileinit event - useful hook for configuring $.mobile settings before they're used 6805 $( window.document ).trigger( "mobileinit" ); 6806 6807 // support conditions 6808 // if device support condition(s) aren't met, leave things as they are -> a basic, usable experience, 6809 // otherwise, proceed with the enhancements 6810 if ( !$.mobile.gradeA() ) { 6811 return; 6812 } 6813 6814 // override ajaxEnabled on platforms that have known conflicts with hash history updates 6815 // or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini) 6816 if ( $.mobile.ajaxBlacklist ) { 6817 $.mobile.ajaxEnabled = false; 6818 } 6819 6820 // add mobile, initial load "rendering" classes to docEl 6821 $html.addClass( "ui-mobile ui-mobile-rendering" ); 6822 6823 // loading div which appears during Ajax requests 6824 // will not appear if $.mobile.loadingMessage is false 6825 var $loader = $( "<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1></h1></div>" ); 6826 6827 $.extend($.mobile, { 6828 // turn on/off page loading message. 6829 showPageLoadingMsg: function() { 6830 if ( $.mobile.loadingMessage ) { 6831 var activeBtn = $( "." + $.mobile.activeBtnClass ).first(); 6832 6833 $loader 6834 .find( "h1" ) 6835 .text( $.mobile.loadingMessage ) 6836 .end() 6837 .appendTo( $.mobile.pageContainer ) 6838 // position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top 6839 .css({ 6840 top: $.support.scrollTop && $window.scrollTop() + $window.height() / 2 || 6841 activeBtn.length && activeBtn.offset().top || 100 6842 }); 6843 } 6844 6845 $html.addClass( "ui-loading" ); 6846 }, 6847 6848 hidePageLoadingMsg: function() { 6849 $html.removeClass( "ui-loading" ); 6850 }, 6851 6852 // find and enhance the pages in the dom and transition to the first page. 6853 initializePage: function() { 6854 // find present pages 6855 var $pages = $( ":jqmData(role='page')" ); 6856 6857 // if no pages are found, create one with body's inner html 6858 if ( !$pages.length ) { 6859 $pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 ); 6860 } 6861 6862 // add dialogs, set data-url attrs 6863 $pages.add( ":jqmData(role='dialog')" ).each(function() { 6864 var $this = $(this); 6865 6866 // unless the data url is already set set it to the pathname 6867 if ( !$this.jqmData("url") ) { 6868 $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search ); 6869 } 6870 }); 6871 6872 // define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback) 6873 $.mobile.firstPage = $pages.first(); 6874 6875 // define page container 6876 $.mobile.pageContainer = $pages.first().parent().addClass( "ui-mobile-viewport" ); 6877 6878 // alert listeners that the pagecontainer has been determined for binding 6879 // to events triggered on it 6880 $window.trigger( "pagecontainercreate" ); 6881 6882 // cue page loading message 6883 $.mobile.showPageLoadingMsg(); 6884 6885 // if hashchange listening is disabled or there's no hash deeplink, change to the first page in the DOM 6886 if ( !$.mobile.hashListeningEnabled || !$.mobile.path.stripHash( location.hash ) ) { 6887 $.mobile.changePage( $.mobile.firstPage, { transition: "none", reverse: true, changeHash: false, fromHashChange: true } ); 6888 }
6889 // otherwise, trigger a hashchange to load a deeplink 6890 else { 6891 $window.trigger( "hashchange", [ true ] ); 6892 } 6893 } 6894 }); 6895 6896 // This function injects a meta viewport tag to prevent scaling. Off by default, on by default when touchOverflow scrolling is enabled 6897 function disableZoom() { 6898 var cont = "user-scalable=no", 6899 meta = $( "meta[name='viewport']" ); 6900 6901 if( meta.length ){ 6902 meta.attr( "content", meta.attr( "content" ) + ", " + cont ); 6903 } 6904 else{ 6905 $( "head" ).prepend( "<meta>", { "name": "viewport", "content": cont } ); 6906 } 6907 } 6908 6909 // if touch-overflow is enabled, disable user scaling, as it creates usability issues 6910 if( $.support.touchOverflow && $.mobile.touchOverflowEnabled && !$.mobile.touchOverflowZoomEnabled ){ 6911 disableZoom(); 6912 } 6913 6914 // initialize events now, after mobileinit has occurred 6915 $.mobile._registerInternalEvents(); 6916 6917 // check which scrollTop value should be used by scrolling to 1 immediately at domready 6918 // then check what the scroll top is. Android will report 0... others 1 6919 // note that this initial scroll won't hide the address bar. It's just for the check. 6920 $(function() { 6921 window.scrollTo( 0, 1 ); 6922 6923 // if defaultHomeScroll hasn't been set yet, see if scrollTop is 1 6924 // it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar) 6925 // so if it's 1, use 0 from now on 6926 $.mobile.defaultHomeScroll = ( !$.support.scrollTop || $(window).scrollTop() === 1 ) ? 0 : 1; 6927 6928 //dom-ready inits 6929 if( $.mobile.autoInitializePage ){ 6930 $.mobile.initializePage(); 6931 } 6932 6933 // window load event 6934 // hide iOS browser chrome on load 6935 $window.load( $.mobile.silentScroll ); 6936 }); 6937})( 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.