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