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