· 8 years ago · Jun 01, 2018, 10:08 PM
1/*!
2* jQuery Mobile 1.4.0
3* Git HEAD hash: f09aae0e035d6805e461a7be246d04a0dbc98f69 <> Date: Thu Dec 19 2013 17:34:22 UTC
4* http://jquerymobile.com
5*
6* Copyright 2010, 2013 jQuery Foundation, Inc. and other contributors
7* Released under the MIT license.
8* http://jquery.org/license
9*
10*/
11
12
13(function ( root, doc, factory ) {
14 if ( typeof define === "function" && define.amd ) {
15 // AMD. Register as an anonymous module.
16 define( [ "jquery" ], function ( $ ) {
17 factory( $, root, doc );
18 return $.mobile;
19 });
20 } else {
21 // Browser globals
22 factory( root.jQuery, root, doc );
23 }
24}( this, document, function ( jQuery, window, document, undefined ) {
25(function( $ ) {
26 $.mobile = {};
27}( jQuery ));
28
29(function( $, window, undefined ) {
30 $.extend( $.mobile, {
31
32 // Version of the jQuery Mobile Framework
33 version: "1.4.0",
34
35 // Deprecated and no longer used in 1.4 remove in 1.5
36 // Define the url parameter used for referencing widget-generated sub-pages.
37 // Translates to example.html&ui-page=subpageIdentifier
38 // hash segment before &ui-page= is used to make Ajax request
39 subPageUrlKey: "ui-page",
40
41 hideUrlBar: true,
42
43 // Keepnative Selector
44 keepNative: ":jqmData(role='none'), :jqmData(role='nojs')",
45
46 // Deprecated in 1.4 remove in 1.5
47 // Class assigned to page currently in view, and during transitions
48 activePageClass: "ui-page-active",
49
50 // Deprecated in 1.4 remove in 1.5
51 // Class used for "active" button state, from CSS framework
52 activeBtnClass: "ui-btn-active",
53
54 // Deprecated in 1.4 remove in 1.5
55 // Class used for "focus" form element state, from CSS framework
56 focusClass: "ui-focus",
57
58 // Automatically handle clicks and form submissions through Ajax, when same-domain
59 ajaxEnabled: true,
60
61 // Automatically load and show pages based on location.hash
62 hashListeningEnabled: true,
63
64 // disable to prevent jquery from bothering with links
65 linkBindingEnabled: true,
66
67 // Set default page transition - 'none' for no transitions
68 defaultPageTransition: "fade",
69
70 // Set maximum window width for transitions to apply - 'false' for no limit
71 maxTransitionWidth: false,
72
73 // Minimum scroll distance that will be remembered when returning to a page
74 // Deprecated remove in 1.5
75 minScrollBack: 0,
76
77 // Set default dialog transition - 'none' for no transitions
78 defaultDialogTransition: "pop",
79
80 // Error response message - appears when an Ajax page request fails
81 pageLoadErrorMessage: "Error Loading Page",
82
83 // For error messages, which theme does the box uses?
84 pageLoadErrorMessageTheme: "a",
85
86 // replace calls to window.history.back with phonegaps navigation helper
87 // where it is provided on the window object
88 phonegapNavigationEnabled: false,
89
90 //automatically initialize the DOM when it's ready
91 autoInitializePage: true,
92
93 pushStateEnabled: true,
94
95 // allows users to opt in to ignoring content by marking a parent element as
96 // data-ignored
97 ignoreContentEnabled: false,
98
99 buttonMarkup: {
100 hoverDelay: 200
101 },
102
103 // disable the alteration of the dynamic base tag or links in the case
104 // that a dynamic base tag isn't supported
105 dynamicBaseEnabled: true,
106
107 // default the property to remove dependency on assignment in init module
108 pageContainer: $(),
109
110 //enable cross-domain page support
111 allowCrossDomainPages: false,
112
113 dialogHashKey: "&ui-state=dialog"
114 });
115})( jQuery, this );
116
117(function( $, window, undefined ) {
118 var nsNormalizeDict = {},
119 oldFind = $.find,
120 rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
121 jqmDataRE = /:jqmData\(([^)]*)\)/g;
122
123 $.extend( $.mobile, {
124
125 // Namespace used framework-wide for data-attrs. Default is no namespace
126
127 ns: "",
128
129 // Retrieve an attribute from an element and perform some massaging of the value
130
131 getAttribute: function( element, key ) {
132 var data;
133
134 element = element.jquery ? element[0] : element;
135
136 if ( element && element.getAttribute ) {
137 data = element.getAttribute( "data-" + $.mobile.ns + key );
138 }
139
140 // Copied from core's src/data.js:dataAttr()
141 // Convert from a string to a proper data type
142 try {
143 data = data === "true" ? true :
144 data === "false" ? false :
145 data === "null" ? null :
146 // Only convert to a number if it doesn't change the string
147 +data + "" === data ? +data :
148 rbrace.test( data ) ? JSON.parse( data ) :
149 data;
150 } catch( err ) {}
151
152 return data;
153 },
154
155 // Expose our cache for testing purposes.
156 nsNormalizeDict: nsNormalizeDict,
157
158 // Take a data attribute property, prepend the namespace
159 // and then camel case the attribute string. Add the result
160 // to our nsNormalizeDict so we don't have to do this again.
161 nsNormalize: function( prop ) {
162 return nsNormalizeDict[ prop ] ||
163 ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
164 },
165
166 // Find the closest javascript page element to gather settings data jsperf test
167 // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
168 // possibly naive, but it shows that the parsing overhead for *just* the page selector vs
169 // the page and dialog selector is negligable. This could probably be speed up by
170 // doing a similar parent node traversal to the one found in the inherited theme code above
171 closestPageData: function( $target ) {
172 return $target
173 .closest( ":jqmData(role='page'), :jqmData(role='dialog')" )
174 .data( "mobile-page" );
175 }
176
177 });
178
179 // Mobile version of data and removeData and hasData methods
180 // ensures all data is set and retrieved using jQuery Mobile's data namespace
181 $.fn.jqmData = function( prop, value ) {
182 var result;
183 if ( typeof prop !== "undefined" ) {
184 if ( prop ) {
185 prop = $.mobile.nsNormalize( prop );
186 }
187
188 // undefined is permitted as an explicit input for the second param
189 // in this case it returns the value and does not set it to undefined
190 if ( arguments.length < 2 || value === undefined ) {
191 result = this.data( prop );
192 } else {
193 result = this.data( prop, value );
194 }
195 }
196 return result;
197 };
198
199 $.jqmData = function( elem, prop, value ) {
200 var result;
201 if ( typeof prop !== "undefined" ) {
202 result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value );
203 }
204 return result;
205 };
206
207 $.fn.jqmRemoveData = function( prop ) {
208 return this.removeData( $.mobile.nsNormalize( prop ) );
209 };
210
211 $.jqmRemoveData = function( elem, prop ) {
212 return $.removeData( elem, $.mobile.nsNormalize( prop ) );
213 };
214
215 $.find = function( selector, context, ret, extra ) {
216 if ( selector.indexOf( ":jqmData" ) > -1 ) {
217 selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
218 }
219
220 return oldFind.call( this, selector, context, ret, extra );
221 };
222
223 $.extend( $.find, oldFind );
224
225})( jQuery, this );
226
227/*!
228 * jQuery UI Core c0ab71056b936627e8a7821f03c044aec6280a40
229 * http://jqueryui.com
230 *
231 * Copyright 2013 jQuery Foundation and other contributors
232 * Released under the MIT license.
233 * http://jquery.org/license
234 *
235 * http://api.jqueryui.com/category/ui-core/
236 */
237(function( $, undefined ) {
238
239var uuid = 0,
240 runiqueId = /^ui-id-\d+$/;
241
242// $.ui might exist from components with no dependencies, e.g., $.ui.position
243$.ui = $.ui || {};
244
245$.extend( $.ui, {
246 version: "c0ab71056b936627e8a7821f03c044aec6280a40",
247
248 keyCode: {
249 BACKSPACE: 8,
250 COMMA: 188,
251 DELETE: 46,
252 DOWN: 40,
253 END: 35,
254 ENTER: 13,
255 ESCAPE: 27,
256 HOME: 36,
257 LEFT: 37,
258 PAGE_DOWN: 34,
259 PAGE_UP: 33,
260 PERIOD: 190,
261 RIGHT: 39,
262 SPACE: 32,
263 TAB: 9,
264 UP: 38
265 }
266});
267
268// plugins
269$.fn.extend({
270 focus: (function( orig ) {
271 return function( delay, fn ) {
272 return typeof delay === "number" ?
273 this.each(function() {
274 var elem = this;
275 setTimeout(function() {
276 $( elem ).focus();
277 if ( fn ) {
278 fn.call( elem );
279 }
280 }, delay );
281 }) :
282 orig.apply( this, arguments );
283 };
284 })( $.fn.focus ),
285
286 scrollParent: function() {
287 var scrollParent;
288 if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
289 scrollParent = this.parents().filter(function() {
290 return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
291 }).eq(0);
292 } else {
293 scrollParent = this.parents().filter(function() {
294 return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
295 }).eq(0);
296 }
297
298 return ( /fixed/ ).test( this.css( "position") ) || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent;
299 },
300
301 uniqueId: function() {
302 return this.each(function() {
303 if ( !this.id ) {
304 this.id = "ui-id-" + (++uuid);
305 }
306 });
307 },
308
309 removeUniqueId: function() {
310 return this.each(function() {
311 if ( runiqueId.test( this.id ) ) {
312 $( this ).removeAttr( "id" );
313 }
314 });
315 }
316});
317
318// selectors
319function focusable( element, isTabIndexNotNaN ) {
320 var map, mapName, img,
321 nodeName = element.nodeName.toLowerCase();
322 if ( "area" === nodeName ) {
323 map = element.parentNode;
324 mapName = map.name;
325 if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
326 return false;
327 }
328 img = $( "img[usemap=#" + mapName + "]" )[0];
329 return !!img && visible( img );
330 }
331 return ( /input|select|textarea|button|object/.test( nodeName ) ?
332 !element.disabled :
333 "a" === nodeName ?
334 element.href || isTabIndexNotNaN :
335 isTabIndexNotNaN) &&
336 // the element and all of its ancestors must be visible
337 visible( element );
338}
339
340function visible( element ) {
341 return $.expr.filters.visible( element ) &&
342 !$( element ).parents().addBack().filter(function() {
343 return $.css( this, "visibility" ) === "hidden";
344 }).length;
345}
346
347$.extend( $.expr[ ":" ], {
348 data: $.expr.createPseudo ?
349 $.expr.createPseudo(function( dataName ) {
350 return function( elem ) {
351 return !!$.data( elem, dataName );
352 };
353 }) :
354 // support: jQuery <1.8
355 function( elem, i, match ) {
356 return !!$.data( elem, match[ 3 ] );
357 },
358
359 focusable: function( element ) {
360 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
361 },
362
363 tabbable: function( element ) {
364 var tabIndex = $.attr( element, "tabindex" ),
365 isTabIndexNaN = isNaN( tabIndex );
366 return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
367 }
368});
369
370// support: jQuery <1.8
371if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
372 $.each( [ "Width", "Height" ], function( i, name ) {
373 var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
374 type = name.toLowerCase(),
375 orig = {
376 innerWidth: $.fn.innerWidth,
377 innerHeight: $.fn.innerHeight,
378 outerWidth: $.fn.outerWidth,
379 outerHeight: $.fn.outerHeight
380 };
381
382 function reduce( elem, size, border, margin ) {
383 $.each( side, function() {
384 size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
385 if ( border ) {
386 size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
387 }
388 if ( margin ) {
389 size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
390 }
391 });
392 return size;
393 }
394
395 $.fn[ "inner" + name ] = function( size ) {
396 if ( size === undefined ) {
397 return orig[ "inner" + name ].call( this );
398 }
399
400 return this.each(function() {
401 $( this ).css( type, reduce( this, size ) + "px" );
402 });
403 };
404
405 $.fn[ "outer" + name] = function( size, margin ) {
406 if ( typeof size !== "number" ) {
407 return orig[ "outer" + name ].call( this, size );
408 }
409
410 return this.each(function() {
411 $( this).css( type, reduce( this, size, true, margin ) + "px" );
412 });
413 };
414 });
415}
416
417// support: jQuery <1.8
418if ( !$.fn.addBack ) {
419 $.fn.addBack = function( selector ) {
420 return this.add( selector == null ?
421 this.prevObject : this.prevObject.filter( selector )
422 );
423 };
424}
425
426// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
427if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
428 $.fn.removeData = (function( removeData ) {
429 return function( key ) {
430 if ( arguments.length ) {
431 return removeData.call( this, $.camelCase( key ) );
432 } else {
433 return removeData.call( this );
434 }
435 };
436 })( $.fn.removeData );
437}
438
439
440
441
442
443// deprecated
444$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
445
446$.support.selectstart = "onselectstart" in document.createElement( "div" );
447$.fn.extend({
448 disableSelection: function() {
449 return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
450 ".ui-disableSelection", function( event ) {
451 event.preventDefault();
452 });
453 },
454
455 enableSelection: function() {
456 return this.unbind( ".ui-disableSelection" );
457 },
458
459 zIndex: function( zIndex ) {
460 if ( zIndex !== undefined ) {
461 return this.css( "zIndex", zIndex );
462 }
463
464 if ( this.length ) {
465 var elem = $( this[ 0 ] ), position, value;
466 while ( elem.length && elem[ 0 ] !== document ) {
467 // Ignore z-index if position is set to a value where z-index is ignored by the browser
468 // This makes behavior of this function consistent across browsers
469 // WebKit always returns auto if the element is positioned
470 position = elem.css( "position" );
471 if ( position === "absolute" || position === "relative" || position === "fixed" ) {
472 // IE returns 0 when zIndex is not specified
473 // other browsers return a string
474 // we ignore the case of nested elements with an explicit value of 0
475 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
476 value = parseInt( elem.css( "zIndex" ), 10 );
477 if ( !isNaN( value ) && value !== 0 ) {
478 return value;
479 }
480 }
481 elem = elem.parent();
482 }
483 }
484
485 return 0;
486 }
487});
488
489// $.ui.plugin is deprecated. Use $.widget() extensions instead.
490$.ui.plugin = {
491 add: function( module, option, set ) {
492 var i,
493 proto = $.ui[ module ].prototype;
494 for ( i in set ) {
495 proto.plugins[ i ] = proto.plugins[ i ] || [];
496 proto.plugins[ i ].push( [ option, set[ i ] ] );
497 }
498 },
499 call: function( instance, name, args, allowDisconnected ) {
500 var i,
501 set = instance.plugins[ name ];
502
503 if ( !set ) {
504 return;
505 }
506
507 if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
508 return;
509 }
510
511 for ( i = 0; i < set.length; i++ ) {
512 if ( instance.options[ set[ i ][ 0 ] ] ) {
513 set[ i ][ 1 ].apply( instance.element, args );
514 }
515 }
516 }
517};
518
519})( jQuery );
520(function( $, window, undefined ) {
521
522 $.extend( $.mobile, {
523 // define the window and the document objects
524 window: $( window ),
525 document: $( document ),
526
527 // TODO: Remove and use $.ui.keyCode directly
528 keyCode: $.ui.keyCode,
529
530 // Place to store various widget extensions
531 behaviors: {},
532
533 // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
534 silentScroll: function( ypos ) {
535 if ( $.type( ypos ) !== "number" ) {
536 ypos = $.mobile.defaultHomeScroll;
537 }
538
539 // prevent scrollstart and scrollstop events
540 $.event.special.scrollstart.enabled = false;
541
542 setTimeout(function() {
543 window.scrollTo( 0, ypos );
544 $.mobile.document.trigger( "silentscroll", { x: 0, y: ypos });
545 }, 20 );
546
547 setTimeout(function() {
548 $.event.special.scrollstart.enabled = true;
549 }, 150 );
550 },
551
552 getClosestBaseUrl: function( ele ) {
553 // Find the closest page and extract out its url.
554 var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ),
555 base = $.mobile.path.documentBase.hrefNoHash;
556
557 if ( !$.mobile.dynamicBaseEnabled || !url || !$.mobile.path.isPath( url ) ) {
558 url = base;
559 }
560
561 return $.mobile.path.makeUrlAbsolute( url, base );
562 },
563 removeActiveLinkClass: function( forceRemoval ) {
564 if ( !!$.mobile.activeClickedLink &&
565 ( !$.mobile.activeClickedLink.closest( "." + $.mobile.activePageClass ).length ||
566 forceRemoval ) ) {
567
568 $.mobile.activeClickedLink.removeClass( $.mobile.activeBtnClass );
569 }
570 $.mobile.activeClickedLink = null;
571 },
572
573 // DEPRECATED in 1.4
574 // Find the closest parent with a theme class on it. Note that
575 // we are not using $.fn.closest() on purpose here because this
576 // method gets called quite a bit and we need it to be as fast
577 // as possible.
578 getInheritedTheme: function( el, defaultTheme ) {
579 var e = el[ 0 ],
580 ltr = "",
581 re = /ui-(bar|body|overlay)-([a-z])\b/,
582 c, m;
583 while ( e ) {
584 c = e.className || "";
585 if ( c && ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) {
586 // We found a parent with a theme class
587 // on it so bail from this loop.
588 break;
589 }
590
591 e = e.parentNode;
592 }
593 // Return the theme letter we found, if none, return the
594 // specified default.
595 return ltr || defaultTheme || "a";
596 },
597
598 enhanceable: function( elements ) {
599 return this.haveParents( elements, "enhance" );
600 },
601
602 hijackable: function( elements ) {
603 return this.haveParents( elements, "ajax" );
604 },
605
606 haveParents: function( elements, attr ) {
607 if ( !$.mobile.ignoreContentEnabled ) {
608 return elements;
609 }
610
611 var count = elements.length,
612 $newSet = $(),
613 e, $element, excluded,
614 i, c;
615
616 for ( i = 0; i < count; i++ ) {
617 $element = elements.eq( i );
618 excluded = false;
619 e = elements[ i ];
620
621 while ( e ) {
622 c = e.getAttribute ? e.getAttribute( "data-" + $.mobile.ns + attr ) : "";
623
624 if ( c === "false" ) {
625 excluded = true;
626 break;
627 }
628
629 e = e.parentNode;
630 }
631
632 if ( !excluded ) {
633 $newSet = $newSet.add( $element );
634 }
635 }
636
637 return $newSet;
638 },
639
640 getScreenHeight: function() {
641 // Native innerHeight returns more accurate value for this across platforms,
642 // jQuery version is here as a normalized fallback for platforms like Symbian
643 return window.innerHeight || $.mobile.window.height();
644 },
645
646 //simply set the active page's minimum height to screen height, depending on orientation
647 resetActivePageHeight: function( height ) {
648 var page = $( "." + $.mobile.activePageClass ),
649 pageHeight = page.height(),
650 pageOuterHeight = page.outerHeight( true );
651
652 height = ( typeof height === "number" ) ? height : $.mobile.getScreenHeight();
653
654 page.css( "min-height", height - ( pageOuterHeight - pageHeight ) );
655 },
656
657 loading: function() {
658 // If this is the first call to this function, instantiate a loader widget
659 var loader = this.loading._widget || $( $.mobile.loader.prototype.defaultHtml ).loader(),
660
661 // Call the appropriate method on the loader
662 returnValue = loader.loader.apply( loader, arguments );
663
664 // Make sure the loader is retained for future calls to this function.
665 this.loading._widget = loader;
666
667 return returnValue;
668 }
669 });
670
671 $.addDependents = function( elem, newDependents ) {
672 var $elem = $( elem ),
673 dependents = $elem.jqmData( "dependents" ) || $();
674
675 $elem.jqmData( "dependents", $( dependents ).add( newDependents ) );
676 };
677
678 // plugins
679 $.fn.extend({
680 removeWithDependents: function() {
681 $.removeWithDependents( this );
682 },
683
684 // Enhance child elements
685 enhanceWithin: function() {
686 var index,
687 widgetElements = {},
688 keepNative = $.mobile.page.prototype.keepNativeSelector(),
689 that = this;
690
691 // Add no js class to elements
692 if ( $.mobile.nojs ) {
693 $.mobile.nojs( this );
694 }
695
696 // Bind links for ajax nav
697 if ( $.mobile.links ) {
698 $.mobile.links( this );
699 }
700
701 // Degrade inputs for styleing
702 if ( $.mobile.degradeInputsWithin ) {
703 $.mobile.degradeInputsWithin( this );
704 }
705
706 // Run buttonmarkup
707 if ( $.fn.buttonMarkup ) {
708 this.find( $.fn.buttonMarkup.initSelector ).not( keepNative )
709 .jqmEnhanceable().buttonMarkup();
710 }
711
712 // Add classes for fieldContain
713 if ( $.fn.fieldcontain ) {
714 this.find( ":jqmData(role='fieldcontain')" ).not( keepNative )
715 .jqmEnhanceable().fieldcontain();
716 }
717
718 // Enhance widgets
719 $.each( $.mobile.widgets, function( name, constructor ) {
720
721 // If initSelector not false find elements
722 if ( constructor.initSelector ) {
723
724 // Filter elements that should not be enhanced based on parents
725 var elements = $.mobile.enhanceable( that.find( constructor.initSelector ) );
726
727 // If any matching elements remain filter ones with keepNativeSelector
728 if ( elements.length > 0 ) {
729
730 // $.mobile.page.prototype.keepNativeSelector is deprecated this is just for backcompat
731 // Switch to $.mobile.keepNative in 1.5 which is just a value not a function
732 elements = elements.not( keepNative );
733 }
734
735 // Enhance whatever is left
736 if ( elements.length > 0 ) {
737 widgetElements[ constructor.prototype.widgetName ] = elements;
738 }
739 }
740 });
741
742 for ( index in widgetElements ) {
743 widgetElements[ index ][ index ]();
744 }
745
746 return this;
747 },
748
749 addDependents: function( newDependents ) {
750 $.addDependents( this, newDependents );
751 },
752
753 // note that this helper doesn't attempt to handle the callback
754 // or setting of an html element's text, its only purpose is
755 // to return the html encoded version of the text in all cases. (thus the name)
756 getEncodedText: function() {
757 return $( "<a>" ).text( this.text() ).html();
758 },
759
760 // fluent helper function for the mobile namespaced equivalent
761 jqmEnhanceable: function() {
762 return $.mobile.enhanceable( this );
763 },
764
765 jqmHijackable: function() {
766 return $.mobile.hijackable( this );
767 }
768 });
769
770 $.removeWithDependents = function( nativeElement ) {
771 var element = $( nativeElement );
772
773 ( element.jqmData( "dependents" ) || $() ).remove();
774 element.remove();
775 };
776 $.addDependents = function( nativeElement, newDependents ) {
777 var element = $( nativeElement ),
778 dependents = element.jqmData( "dependents" ) || $();
779
780 element.jqmData( "dependents", $( dependents ).add( newDependents ) );
781 };
782
783 $.find.matches = function( expr, set ) {
784 return $.find( expr, null, null, set );
785 };
786
787 $.find.matchesSelector = function( node, expr ) {
788 return $.find( expr, null, null, [ node ] ).length > 0;
789 };
790
791})( jQuery, this );
792
793
794/*!
795 * jQuery UI Widget c0ab71056b936627e8a7821f03c044aec6280a40N
796 * http://jqueryui.com
797 *
798 * Copyright 2013 jQuery Foundation and other contributors
799 * Released under the MIT license.
800 * http://jquery.org/license
801 *
802 * http://api.jqueryui.com/jQuery.widget/
803 */
804(function( $, undefined ) {
805
806var uuid = 0,
807 slice = Array.prototype.slice,
808 _cleanData = $.cleanData;
809$.cleanData = function( elems ) {
810 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
811 try {
812 $( elem ).triggerHandler( "remove" );
813 // http://bugs.jquery.com/ticket/8235
814 } catch( e ) {}
815 }
816 _cleanData( elems );
817};
818
819$.widget = function( name, base, prototype ) {
820 var fullName, existingConstructor, constructor, basePrototype,
821 // proxiedPrototype allows the provided prototype to remain unmodified
822 // so that it can be used as a mixin for multiple widgets (#8876)
823 proxiedPrototype = {},
824 namespace = name.split( "." )[ 0 ];
825
826 name = name.split( "." )[ 1 ];
827 fullName = namespace + "-" + name;
828
829 if ( !prototype ) {
830 prototype = base;
831 base = $.Widget;
832 }
833
834 // create selector for plugin
835 $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
836 return !!$.data( elem, fullName );
837 };
838
839 $[ namespace ] = $[ namespace ] || {};
840 existingConstructor = $[ namespace ][ name ];
841 constructor = $[ namespace ][ name ] = function( options, element ) {
842 // allow instantiation without "new" keyword
843 if ( !this._createWidget ) {
844 return new constructor( options, element );
845 }
846
847 // allow instantiation without initializing for simple inheritance
848 // must use "new" keyword (the code above always passes args)
849 if ( arguments.length ) {
850 this._createWidget( options, element );
851 }
852 };
853 // extend with the existing constructor to carry over any static properties
854 $.extend( constructor, existingConstructor, {
855 version: prototype.version,
856 // copy the object used to create the prototype in case we need to
857 // redefine the widget later
858 _proto: $.extend( {}, prototype ),
859 // track widgets that inherit from this widget in case this widget is
860 // redefined after a widget inherits from it
861 _childConstructors: []
862 });
863
864 basePrototype = new base();
865 // we need to make the options hash a property directly on the new instance
866 // otherwise we'll modify the options hash on the prototype that we're
867 // inheriting from
868 basePrototype.options = $.widget.extend( {}, basePrototype.options );
869 $.each( prototype, function( prop, value ) {
870 if ( !$.isFunction( value ) ) {
871 proxiedPrototype[ prop ] = value;
872 return;
873 }
874 proxiedPrototype[ prop ] = (function() {
875 var _super = function() {
876 return base.prototype[ prop ].apply( this, arguments );
877 },
878 _superApply = function( args ) {
879 return base.prototype[ prop ].apply( this, args );
880 };
881 return function() {
882 var __super = this._super,
883 __superApply = this._superApply,
884 returnValue;
885
886 this._super = _super;
887 this._superApply = _superApply;
888
889 returnValue = value.apply( this, arguments );
890
891 this._super = __super;
892 this._superApply = __superApply;
893
894 return returnValue;
895 };
896 })();
897 });
898 constructor.prototype = $.widget.extend( basePrototype, {
899 // TODO: remove support for widgetEventPrefix
900 // always use the name + a colon as the prefix, e.g., draggable:start
901 // don't prefix for widgets that aren't DOM-based
902 widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
903 }, proxiedPrototype, {
904 constructor: constructor,
905 namespace: namespace,
906 widgetName: name,
907 widgetFullName: fullName
908 });
909
910 // If this widget is being redefined then we need to find all widgets that
911 // are inheriting from it and redefine all of them so that they inherit from
912 // the new version of this widget. We're essentially trying to replace one
913 // level in the prototype chain.
914 if ( existingConstructor ) {
915 $.each( existingConstructor._childConstructors, function( i, child ) {
916 var childPrototype = child.prototype;
917
918 // redefine the child widget using the same prototype that was
919 // originally used, but inherit from the new version of the base
920 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
921 });
922 // remove the list of existing child constructors from the old constructor
923 // so the old child constructors can be garbage collected
924 delete existingConstructor._childConstructors;
925 } else {
926 base._childConstructors.push( constructor );
927 }
928
929 $.widget.bridge( name, constructor );
930
931 return constructor;
932};
933
934$.widget.extend = function( target ) {
935 var input = slice.call( arguments, 1 ),
936 inputIndex = 0,
937 inputLength = input.length,
938 key,
939 value;
940 for ( ; inputIndex < inputLength; inputIndex++ ) {
941 for ( key in input[ inputIndex ] ) {
942 value = input[ inputIndex ][ key ];
943 if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
944 // Clone objects
945 if ( $.isPlainObject( value ) ) {
946 target[ key ] = $.isPlainObject( target[ key ] ) ?
947 $.widget.extend( {}, target[ key ], value ) :
948 // Don't extend strings, arrays, etc. with objects
949 $.widget.extend( {}, value );
950 // Copy everything else by reference
951 } else {
952 target[ key ] = value;
953 }
954 }
955 }
956 }
957 return target;
958};
959
960$.widget.bridge = function( name, object ) {
961 var fullName = object.prototype.widgetFullName || name;
962 $.fn[ name ] = function( options ) {
963 var isMethodCall = typeof options === "string",
964 args = slice.call( arguments, 1 ),
965 returnValue = this;
966
967 // allow multiple hashes to be passed on init
968 options = !isMethodCall && args.length ?
969 $.widget.extend.apply( null, [ options ].concat(args) ) :
970 options;
971
972 if ( isMethodCall ) {
973 this.each(function() {
974 var methodValue,
975 instance = $.data( this, fullName );
976 if ( options === "instance" ) {
977 returnValue = instance;
978 return false;
979 }
980 if ( !instance ) {
981 return $.error( "cannot call methods on " + name + " prior to initialization; " +
982 "attempted to call method '" + options + "'" );
983 }
984 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
985 return $.error( "no such method '" + options + "' for " + name + " widget instance" );
986 }
987 methodValue = instance[ options ].apply( instance, args );
988 if ( methodValue !== instance && methodValue !== undefined ) {
989 returnValue = methodValue && methodValue.jquery ?
990 returnValue.pushStack( methodValue.get() ) :
991 methodValue;
992 return false;
993 }
994 });
995 } else {
996 this.each(function() {
997 var instance = $.data( this, fullName );
998 if ( instance ) {
999 instance.option( options || {} )._init();
1000 } else {
1001 $.data( this, fullName, new object( options, this ) );
1002 }
1003 });
1004 }
1005
1006 return returnValue;
1007 };
1008};
1009
1010$.Widget = function( /* options, element */ ) {};
1011$.Widget._childConstructors = [];
1012
1013$.Widget.prototype = {
1014 widgetName: "widget",
1015 widgetEventPrefix: "",
1016 defaultElement: "<div>",
1017 options: {
1018 disabled: false,
1019
1020 // callbacks
1021 create: null
1022 },
1023 _createWidget: function( options, element ) {
1024 element = $( element || this.defaultElement || this )[ 0 ];
1025 this.element = $( element );
1026 this.uuid = uuid++;
1027 this.eventNamespace = "." + this.widgetName + this.uuid;
1028 this.options = $.widget.extend( {},
1029 this.options,
1030 this._getCreateOptions(),
1031 options );
1032
1033 this.bindings = $();
1034 this.hoverable = $();
1035 this.focusable = $();
1036
1037 if ( element !== this ) {
1038 $.data( element, this.widgetFullName, this );
1039 this._on( true, this.element, {
1040 remove: function( event ) {
1041 if ( event.target === element ) {
1042 this.destroy();
1043 }
1044 }
1045 });
1046 this.document = $( element.style ?
1047 // element within the document
1048 element.ownerDocument :
1049 // element is window or document
1050 element.document || element );
1051 this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
1052 }
1053
1054 this._create();
1055 this._trigger( "create", null, this._getCreateEventData() );
1056 this._init();
1057 },
1058 _getCreateOptions: $.noop,
1059 _getCreateEventData: $.noop,
1060 _create: $.noop,
1061 _init: $.noop,
1062
1063 destroy: function() {
1064 this._destroy();
1065 // we can probably remove the unbind calls in 2.0
1066 // all event bindings should go through this._on()
1067 this.element
1068 .unbind( this.eventNamespace )
1069 .removeData( this.widgetFullName )
1070 // support: jquery <1.6.3
1071 // http://bugs.jquery.com/ticket/9413
1072 .removeData( $.camelCase( this.widgetFullName ) );
1073 this.widget()
1074 .unbind( this.eventNamespace )
1075 .removeAttr( "aria-disabled" )
1076 .removeClass(
1077 this.widgetFullName + "-disabled " +
1078 "ui-state-disabled" );
1079
1080 // clean up events and states
1081 this.bindings.unbind( this.eventNamespace );
1082 this.hoverable.removeClass( "ui-state-hover" );
1083 this.focusable.removeClass( "ui-state-focus" );
1084 },
1085 _destroy: $.noop,
1086
1087 widget: function() {
1088 return this.element;
1089 },
1090
1091 option: function( key, value ) {
1092 var options = key,
1093 parts,
1094 curOption,
1095 i;
1096
1097 if ( arguments.length === 0 ) {
1098 // don't return a reference to the internal hash
1099 return $.widget.extend( {}, this.options );
1100 }
1101
1102 if ( typeof key === "string" ) {
1103 // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
1104 options = {};
1105 parts = key.split( "." );
1106 key = parts.shift();
1107 if ( parts.length ) {
1108 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
1109 for ( i = 0; i < parts.length - 1; i++ ) {
1110 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
1111 curOption = curOption[ parts[ i ] ];
1112 }
1113 key = parts.pop();
1114 if ( value === undefined ) {
1115 return curOption[ key ] === undefined ? null : curOption[ key ];
1116 }
1117 curOption[ key ] = value;
1118 } else {
1119 if ( value === undefined ) {
1120 return this.options[ key ] === undefined ? null : this.options[ key ];
1121 }
1122 options[ key ] = value;
1123 }
1124 }
1125
1126 this._setOptions( options );
1127
1128 return this;
1129 },
1130 _setOptions: function( options ) {
1131 var key;
1132
1133 for ( key in options ) {
1134 this._setOption( key, options[ key ] );
1135 }
1136
1137 return this;
1138 },
1139 _setOption: function( key, value ) {
1140 this.options[ key ] = value;
1141
1142 if ( key === "disabled" ) {
1143 this.widget()
1144 .toggleClass( this.widgetFullName + "-disabled", !!value );
1145 this.hoverable.removeClass( "ui-state-hover" );
1146 this.focusable.removeClass( "ui-state-focus" );
1147 }
1148
1149 return this;
1150 },
1151
1152 enable: function() {
1153 return this._setOptions({ disabled: false });
1154 },
1155 disable: function() {
1156 return this._setOptions({ disabled: true });
1157 },
1158
1159 _on: function( suppressDisabledCheck, element, handlers ) {
1160 var delegateElement,
1161 instance = this;
1162
1163 // no suppressDisabledCheck flag, shuffle arguments
1164 if ( typeof suppressDisabledCheck !== "boolean" ) {
1165 handlers = element;
1166 element = suppressDisabledCheck;
1167 suppressDisabledCheck = false;
1168 }
1169
1170 // no element argument, shuffle and use this.element
1171 if ( !handlers ) {
1172 handlers = element;
1173 element = this.element;
1174 delegateElement = this.widget();
1175 } else {
1176 // accept selectors, DOM elements
1177 element = delegateElement = $( element );
1178 this.bindings = this.bindings.add( element );
1179 }
1180
1181 $.each( handlers, function( event, handler ) {
1182 function handlerProxy() {
1183 // allow widgets to customize the disabled handling
1184 // - disabled as an array instead of boolean
1185 // - disabled class as method for disabling individual parts
1186 if ( !suppressDisabledCheck &&
1187 ( instance.options.disabled === true ||
1188 $( this ).hasClass( "ui-state-disabled" ) ) ) {
1189 return;
1190 }
1191 return ( typeof handler === "string" ? instance[ handler ] : handler )
1192 .apply( instance, arguments );
1193 }
1194
1195 // copy the guid so direct unbinding works
1196 if ( typeof handler !== "string" ) {
1197 handlerProxy.guid = handler.guid =
1198 handler.guid || handlerProxy.guid || $.guid++;
1199 }
1200
1201 var match = event.match( /^(\w+)\s*(.*)$/ ),
1202 eventName = match[1] + instance.eventNamespace,
1203 selector = match[2];
1204 if ( selector ) {
1205 delegateElement.delegate( selector, eventName, handlerProxy );
1206 } else {
1207 element.bind( eventName, handlerProxy );
1208 }
1209 });
1210 },
1211
1212 _off: function( element, eventName ) {
1213 eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
1214 element.unbind( eventName ).undelegate( eventName );
1215 },
1216
1217 _delay: function( handler, delay ) {
1218 function handlerProxy() {
1219 return ( typeof handler === "string" ? instance[ handler ] : handler )
1220 .apply( instance, arguments );
1221 }
1222 var instance = this;
1223 return setTimeout( handlerProxy, delay || 0 );
1224 },
1225
1226 _hoverable: function( element ) {
1227 this.hoverable = this.hoverable.add( element );
1228 this._on( element, {
1229 mouseenter: function( event ) {
1230 $( event.currentTarget ).addClass( "ui-state-hover" );
1231 },
1232 mouseleave: function( event ) {
1233 $( event.currentTarget ).removeClass( "ui-state-hover" );
1234 }
1235 });
1236 },
1237
1238 _focusable: function( element ) {
1239 this.focusable = this.focusable.add( element );
1240 this._on( element, {
1241 focusin: function( event ) {
1242 $( event.currentTarget ).addClass( "ui-state-focus" );
1243 },
1244 focusout: function( event ) {
1245 $( event.currentTarget ).removeClass( "ui-state-focus" );
1246 }
1247 });
1248 },
1249
1250 _trigger: function( type, event, data ) {
1251 var prop, orig,
1252 callback = this.options[ type ];
1253
1254 data = data || {};
1255 event = $.Event( event );
1256 event.type = ( type === this.widgetEventPrefix ?
1257 type :
1258 this.widgetEventPrefix + type ).toLowerCase();
1259 // the original event may come from any element
1260 // so we need to reset the target on the new event
1261 event.target = this.element[ 0 ];
1262
1263 // copy original event properties over to the new event
1264 orig = event.originalEvent;
1265 if ( orig ) {
1266 for ( prop in orig ) {
1267 if ( !( prop in event ) ) {
1268 event[ prop ] = orig[ prop ];
1269 }
1270 }
1271 }
1272
1273 this.element.trigger( event, data );
1274 return !( $.isFunction( callback ) &&
1275 callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
1276 event.isDefaultPrevented() );
1277 }
1278};
1279
1280$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
1281 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
1282 if ( typeof options === "string" ) {
1283 options = { effect: options };
1284 }
1285 var hasOptions,
1286 effectName = !options ?
1287 method :
1288 options === true || typeof options === "number" ?
1289 defaultEffect :
1290 options.effect || defaultEffect;
1291 options = options || {};
1292 if ( typeof options === "number" ) {
1293 options = { duration: options };
1294 }
1295 hasOptions = !$.isEmptyObject( options );
1296 options.complete = callback;
1297 if ( options.delay ) {
1298 element.delay( options.delay );
1299 }
1300 if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
1301 element[ method ]( options );
1302 } else if ( effectName !== method && element[ effectName ] ) {
1303 element[ effectName ]( options.duration, options.easing, callback );
1304 } else {
1305 element.queue(function( next ) {
1306 $( this )[ method ]();
1307 if ( callback ) {
1308 callback.call( element[ 0 ] );
1309 }
1310 next();
1311 });
1312 }
1313 };
1314});
1315
1316})( jQuery );
1317
1318(function( $, undefined ) {
1319
1320var rcapitals = /[A-Z]/g,
1321 replaceFunction = function( c ) {
1322 return "-" + c.toLowerCase();
1323 };
1324
1325$.extend( $.Widget.prototype, {
1326 _getCreateOptions: function() {
1327 var option, value,
1328 elem = this.element[ 0 ],
1329 options = {};
1330
1331 //
1332 if ( !$.mobile.getAttribute( elem, "defaults" ) ) {
1333 for ( option in this.options ) {
1334 value = $.mobile.getAttribute( elem, option.replace( rcapitals, replaceFunction ) );
1335
1336 if ( value != null ) {
1337 options[ option ] = value;
1338 }
1339 }
1340 }
1341
1342 return options;
1343 }
1344});
1345
1346//TODO: Remove in 1.5 for backcompat only
1347$.mobile.widget = $.Widget;
1348
1349})( jQuery );
1350
1351
1352(function( $ ) {
1353 // TODO move loader class down into the widget settings
1354 var loaderClass = "ui-loader", $html = $( "html" );
1355
1356 $.widget( "mobile.loader", {
1357 // NOTE if the global config settings are defined they will override these
1358 // options
1359 options: {
1360 // the theme for the loading message
1361 theme: "a",
1362
1363 // whether the text in the loading message is shown
1364 textVisible: false,
1365
1366 // custom html for the inner content of the loading message
1367 html: "",
1368
1369 // the text to be displayed when the popup is shown
1370 text: "loading"
1371 },
1372
1373 defaultHtml: "<div class='" + loaderClass + "'>" +
1374 "<span class='ui-icon-loading'></span>" +
1375 "<h1></h1>" +
1376 "</div>",
1377
1378 // For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
1379 fakeFixLoader: function() {
1380 var activeBtn = $( "." + $.mobile.activeBtnClass ).first();
1381
1382 this.element
1383 .css({
1384 top: $.support.scrollTop && this.window.scrollTop() + this.window.height() / 2 ||
1385 activeBtn.length && activeBtn.offset().top || 100
1386 });
1387 },
1388
1389 // check position of loader to see if it appears to be "fixed" to center
1390 // if not, use abs positioning
1391 checkLoaderPosition: function() {
1392 var offset = this.element.offset(),
1393 scrollTop = this.window.scrollTop(),
1394 screenHeight = $.mobile.getScreenHeight();
1395
1396 if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) {
1397 this.element.addClass( "ui-loader-fakefix" );
1398 this.fakeFixLoader();
1399 this.window
1400 .unbind( "scroll", this.checkLoaderPosition )
1401 .bind( "scroll", $.proxy( this.fakeFixLoader, this ) );
1402 }
1403 },
1404
1405 resetHtml: function() {
1406 this.element.html( $( this.defaultHtml ).html() );
1407 },
1408
1409 // Turn on/off page loading message. Theme doubles as an object argument
1410 // with the following shape: { theme: '', text: '', html: '', textVisible: '' }
1411 // NOTE that the $.mobile.loading* settings and params past the first are deprecated
1412 // TODO sweet jesus we need to break some of this out
1413 show: function( theme, msgText, textonly ) {
1414 var textVisible, message, loadSettings;
1415
1416 this.resetHtml();
1417
1418 // use the prototype options so that people can set them globally at
1419 // mobile init. Consistency, it's what's for dinner
1420 if ( $.type( theme ) === "object" ) {
1421 loadSettings = $.extend( {}, this.options, theme );
1422
1423 theme = loadSettings.theme;
1424 } else {
1425 loadSettings = this.options;
1426
1427 // here we prefer the theme value passed as a string argument, then
1428 // we prefer the global option because we can't use undefined default
1429 // prototype options, then the prototype option
1430 theme = theme || loadSettings.theme;
1431 }
1432
1433 // set the message text, prefer the param, then the settings object
1434 // then loading message
1435 message = msgText || ( loadSettings.text === false ? "" : loadSettings.text );
1436
1437 // prepare the dom
1438 $html.addClass( "ui-loading" );
1439
1440 textVisible = loadSettings.textVisible;
1441
1442 // add the proper css given the options (theme, text, etc)
1443 // Force text visibility if the second argument was supplied, or
1444 // if the text was explicitly set in the object args
1445 this.element.attr("class", loaderClass +
1446 " ui-corner-all ui-body-" + theme +
1447 " ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) +
1448 ( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) );
1449
1450 // TODO verify that jquery.fn.html is ok to use in both cases here
1451 // this might be overly defensive in preventing unknowing xss
1452 // if the html attribute is defined on the loading settings, use that
1453 // otherwise use the fallbacks from above
1454 if ( loadSettings.html ) {
1455 this.element.html( loadSettings.html );
1456 } else {
1457 this.element.find( "h1" ).text( message );
1458 }
1459
1460 // attach the loader to the DOM
1461 this.element.appendTo( $.mobile.pageContainer );
1462
1463 // check that the loader is visible
1464 this.checkLoaderPosition();
1465
1466 // on scroll check the loader position
1467 this.window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) );
1468 },
1469
1470 hide: function() {
1471 $html.removeClass( "ui-loading" );
1472
1473 if ( this.options.text ) {
1474 this.element.removeClass( "ui-loader-fakefix" );
1475 }
1476
1477 $.mobile.window.unbind( "scroll", this.fakeFixLoader );
1478 $.mobile.window.unbind( "scroll", this.checkLoaderPosition );
1479 }
1480 });
1481
1482})(jQuery, this);
1483
1484
1485// Script: jQuery hashchange event
1486//
1487// *Version: 1.3, Last updated: 7/21/2010*
1488//
1489// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
1490// GitHub - http://github.com/cowboy/jquery-hashchange/
1491// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
1492// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
1493//
1494// About: License
1495//
1496// Copyright (c) 2010 "Cowboy" Ben Alman,
1497// Dual licensed under the MIT and GPL licenses.
1498// http://benalman.com/about/license/
1499//
1500// About: Examples
1501//
1502// These working examples, complete with fully commented code, illustrate a few
1503// ways in which this plugin can be used.
1504//
1505// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
1506// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
1507//
1508// About: Support and Testing
1509//
1510// Information about what version or versions of jQuery this plugin has been
1511// tested with, what browsers it has been tested in, and where the unit tests
1512// reside (so you can test it yourself).
1513//
1514// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
1515// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
1516// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
1517// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
1518//
1519// About: Known issues
1520//
1521// While this jQuery hashchange event implementation is quite stable and
1522// robust, there are a few unfortunate browser bugs surrounding expected
1523// hashchange event-based behaviors, independent of any JavaScript
1524// window.onhashchange abstraction. See the following examples for more
1525// information:
1526//
1527// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
1528// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
1529// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
1530// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
1531//
1532// Also note that should a browser natively support the window.onhashchange
1533// event, but not report that it does, the fallback polling loop will be used.
1534//
1535// About: Release History
1536//
1537// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
1538// "removable" for mobile-only development. Added IE6/7 document.title
1539// support. Attempted to make Iframe as hidden as possible by using
1540// techniques from http://www.paciellogroup.com/blog/?p=604. Added
1541// support for the "shortcut" format $(window).hashchange( fn ) and
1542// $(window).hashchange() like jQuery provides for built-in events.
1543// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
1544// lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
1545// and <jQuery.fn.hashchange.src> properties plus document-domain.html
1546// file to address access denied issues when setting document.domain in
1547// IE6/7.
1548// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
1549// from a page on another domain would cause an error in Safari 4. Also,
1550// IE6/7 Iframe is now inserted after the body (this actually works),
1551// which prevents the page from scrolling when the event is first bound.
1552// Event can also now be bound before DOM ready, but it won't be usable
1553// before then in IE6/7.
1554// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
1555// where browser version is incorrectly reported as 8.0, despite
1556// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
1557// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
1558// window.onhashchange functionality into a separate plugin for users
1559// who want just the basic event & back button support, without all the
1560// extra awesomeness that BBQ provides. This plugin will be included as
1561// part of jQuery BBQ, but also be available separately.
1562
1563(function( $, window, undefined ) {
1564 // Reused string.
1565 var str_hashchange = 'hashchange',
1566
1567 // Method / object references.
1568 doc = document,
1569 fake_onhashchange,
1570 special = $.event.special,
1571
1572 // Does the browser support window.onhashchange? Note that IE8 running in
1573 // IE7 compatibility mode reports true for 'onhashchange' in window, even
1574 // though the event isn't supported, so also test document.documentMode.
1575 doc_mode = doc.documentMode,
1576 supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
1577
1578 // Get location.hash (or what you'd expect location.hash to be) sans any
1579 // leading #. Thanks for making this necessary, Firefox!
1580 function get_fragment( url ) {
1581 url = url || location.href;
1582 return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
1583 };
1584
1585 // Method: jQuery.fn.hashchange
1586 //
1587 // Bind a handler to the window.onhashchange event or trigger all bound
1588 // window.onhashchange event handlers. This behavior is consistent with
1589 // jQuery's built-in event handlers.
1590 //
1591 // Usage:
1592 //
1593 // > jQuery(window).hashchange( [ handler ] );
1594 //
1595 // Arguments:
1596 //
1597 // handler - (Function) Optional handler to be bound to the hashchange
1598 // event. This is a "shortcut" for the more verbose form:
1599 // jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
1600 // all bound window.onhashchange event handlers will be triggered. This
1601 // is a shortcut for the more verbose
1602 // jQuery(window).trigger( 'hashchange' ). These forms are described in
1603 // the <hashchange event> section.
1604 //
1605 // Returns:
1606 //
1607 // (jQuery) The initial jQuery collection of elements.
1608
1609 // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
1610 // $(elem).hashchange() for triggering, like jQuery does for built-in events.
1611 $.fn[ str_hashchange ] = function( fn ) {
1612 return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
1613 };
1614
1615 // Property: jQuery.fn.hashchange.delay
1616 //
1617 // The numeric interval (in milliseconds) at which the <hashchange event>
1618 // polling loop executes. Defaults to 50.
1619
1620 // Property: jQuery.fn.hashchange.domain
1621 //
1622 // If you're setting document.domain in your JavaScript, and you want hash
1623 // history to work in IE6/7, not only must this property be set, but you must
1624 // also set document.domain BEFORE jQuery is loaded into the page. This
1625 // property is only applicable if you are supporting IE6/7 (or IE8 operating
1626 // in "IE7 compatibility" mode).
1627 //
1628 // In addition, the <jQuery.fn.hashchange.src> property must be set to the
1629 // path of the included "document-domain.html" file, which can be renamed or
1630 // modified if necessary (note that the document.domain specified must be the
1631 // same in both your main JavaScript as well as in this file).
1632 //
1633 // Usage:
1634 //
1635 // jQuery.fn.hashchange.domain = document.domain;
1636
1637 // Property: jQuery.fn.hashchange.src
1638 //
1639 // If, for some reason, you need to specify an Iframe src file (for example,
1640 // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
1641 // do so using this property. Note that when using this property, history
1642 // won't be recorded in IE6/7 until the Iframe src file loads. This property
1643 // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
1644 // compatibility" mode).
1645 //
1646 // Usage:
1647 //
1648 // jQuery.fn.hashchange.src = 'path/to/file.html';
1649
1650 $.fn[ str_hashchange ].delay = 50;
1651 /*
1652 $.fn[ str_hashchange ].domain = null;
1653 $.fn[ str_hashchange ].src = null;
1654 */
1655
1656 // Event: hashchange event
1657 //
1658 // Fired when location.hash changes. In browsers that support it, the native
1659 // HTML5 window.onhashchange event is used, otherwise a polling loop is
1660 // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
1661 // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
1662 // compatibility" mode), a hidden Iframe is created to allow the back button
1663 // and hash-based history to work.
1664 //
1665 // Usage as described in <jQuery.fn.hashchange>:
1666 //
1667 // > // Bind an event handler.
1668 // > jQuery(window).hashchange( function(e) {
1669 // > var hash = location.hash;
1670 // > ...
1671 // > });
1672 // >
1673 // > // Manually trigger the event handler.
1674 // > jQuery(window).hashchange();
1675 //
1676 // A more verbose usage that allows for event namespacing:
1677 //
1678 // > // Bind an event handler.
1679 // > jQuery(window).bind( 'hashchange', function(e) {
1680 // > var hash = location.hash;
1681 // > ...
1682 // > });
1683 // >
1684 // > // Manually trigger the event handler.
1685 // > jQuery(window).trigger( 'hashchange' );
1686 //
1687 // Additional Notes:
1688 //
1689 // * The polling loop and Iframe are not created until at least one handler
1690 // is actually bound to the 'hashchange' event.
1691 // * If you need the bound handler(s) to execute immediately, in cases where
1692 // a location.hash exists on page load, via bookmark or page refresh for
1693 // example, use jQuery(window).hashchange() or the more verbose
1694 // jQuery(window).trigger( 'hashchange' ).
1695 // * The event can be bound before DOM ready, but since it won't be usable
1696 // before then in IE6/7 (due to the necessary Iframe), recommended usage is
1697 // to bind it inside a DOM ready handler.
1698
1699 // Override existing $.event.special.hashchange methods (allowing this plugin
1700 // to be defined after jQuery BBQ in BBQ's source code).
1701 special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
1702
1703 // Called only when the first 'hashchange' event is bound to window.
1704 setup: function() {
1705 // If window.onhashchange is supported natively, there's nothing to do..
1706 if ( supports_onhashchange ) { return false; }
1707
1708 // Otherwise, we need to create our own. And we don't want to call this
1709 // until the user binds to the event, just in case they never do, since it
1710 // will create a polling loop and possibly even a hidden Iframe.
1711 $( fake_onhashchange.start );
1712 },
1713
1714 // Called only when the last 'hashchange' event is unbound from window.
1715 teardown: function() {
1716 // If window.onhashchange is supported natively, there's nothing to do..
1717 if ( supports_onhashchange ) { return false; }
1718
1719 // Otherwise, we need to stop ours (if possible).
1720 $( fake_onhashchange.stop );
1721 }
1722
1723 });
1724
1725 // fake_onhashchange does all the work of triggering the window.onhashchange
1726 // event for browsers that don't natively support it, including creating a
1727 // polling loop to watch for hash changes and in IE 6/7 creating a hidden
1728 // Iframe to enable back and forward.
1729 fake_onhashchange = (function() {
1730 var self = {},
1731 timeout_id,
1732
1733 // Remember the initial hash so it doesn't get triggered immediately.
1734 last_hash = get_fragment(),
1735
1736 fn_retval = function( val ) { return val; },
1737 history_set = fn_retval,
1738 history_get = fn_retval;
1739
1740 // Start the polling loop.
1741 self.start = function() {
1742 timeout_id || poll();
1743 };
1744
1745 // Stop the polling loop.
1746 self.stop = function() {
1747 timeout_id && clearTimeout( timeout_id );
1748 timeout_id = undefined;
1749 };
1750
1751 // This polling loop checks every $.fn.hashchange.delay milliseconds to see
1752 // if location.hash has changed, and triggers the 'hashchange' event on
1753 // window when necessary.
1754 function poll() {
1755 var hash = get_fragment(),
1756 history_hash = history_get( last_hash );
1757
1758 if ( hash !== last_hash ) {
1759 history_set( last_hash = hash, history_hash );
1760
1761 $(window).trigger( str_hashchange );
1762
1763 } else if ( history_hash !== last_hash ) {
1764 location.href = location.href.replace( /#.*/, '' ) + history_hash;
1765 }
1766
1767 timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
1768 };
1769
1770 // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1771 // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
1772 // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1773 window.attachEvent && !window.addEventListener && !supports_onhashchange && (function() {
1774 // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
1775 // when running in "IE7 compatibility" mode.
1776
1777 var iframe,
1778 iframe_src;
1779
1780 // When the event is bound and polling starts in IE 6/7, create a hidden
1781 // Iframe for history handling.
1782 self.start = function() {
1783 if ( !iframe ) {
1784 iframe_src = $.fn[ str_hashchange ].src;
1785 iframe_src = iframe_src && iframe_src + get_fragment();
1786
1787 // Create hidden Iframe. Attempt to make Iframe as hidden as possible
1788 // by using techniques from http://www.paciellogroup.com/blog/?p=604.
1789 iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
1790
1791 // When Iframe has completely loaded, initialize the history and
1792 // start polling.
1793 .one( 'load', function() {
1794 iframe_src || history_set( get_fragment() );
1795 poll();
1796 })
1797
1798 // Load Iframe src if specified, otherwise nothing.
1799 .attr( 'src', iframe_src || 'javascript:0' )
1800
1801 // Append Iframe after the end of the body to prevent unnecessary
1802 // initial page scrolling (yes, this works).
1803 .insertAfter( 'body' )[0].contentWindow;
1804
1805 // Whenever `document.title` changes, update the Iframe's title to
1806 // prettify the back/next history menu entries. Since IE sometimes
1807 // errors with "Unspecified error" the very first time this is set
1808 // (yes, very useful) wrap this with a try/catch block.
1809 doc.onpropertychange = function() {
1810 try {
1811 if ( event.propertyName === 'title' ) {
1812 iframe.document.title = doc.title;
1813 }
1814 } catch(e) {}
1815 };
1816
1817 }
1818 };
1819
1820 // Override the "stop" method since an IE6/7 Iframe was created. Even
1821 // if there are no longer any bound event handlers, the polling loop
1822 // is still necessary for back/next to work at all!
1823 self.stop = fn_retval;
1824
1825 // Get history by looking at the hidden Iframe's location.hash.
1826 history_get = function() {
1827 return get_fragment( iframe.location.href );
1828 };
1829
1830 // Set a new history item by opening and then closing the Iframe
1831 // document, *then* setting its location.hash. If document.domain has
1832 // been set, update that as well.
1833 history_set = function( hash, history_hash ) {
1834 var iframe_doc = iframe.document,
1835 domain = $.fn[ str_hashchange ].domain;
1836
1837 if ( hash !== history_hash ) {
1838 // Update Iframe with any initial `document.title` that might be set.
1839 iframe_doc.title = doc.title;
1840
1841 // Opening the Iframe's document after it has been closed is what
1842 // actually adds a history entry.
1843 iframe_doc.open();
1844
1845 // Set document.domain for the Iframe document as well, if necessary.
1846 domain && iframe_doc.write( '<script>document.domain="' + domain + '"<\/script>' );
1847
1848 iframe_doc.close();
1849
1850 // Update the Iframe's hash, for great justice.
1851 iframe.location.hash = hash;
1852 }
1853 };
1854
1855 })();
1856 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1857 // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
1858 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1859
1860 return self;
1861 })();
1862
1863})(jQuery,this);
1864
1865(function( $, undefined ) {
1866
1867 /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
1868 window.matchMedia = window.matchMedia || (function( doc, undefined ) {
1869
1870
1871
1872 var bool,
1873 docElem = doc.documentElement,
1874 refNode = docElem.firstElementChild || docElem.firstChild,
1875 // fakeBody required for <FF4 when executed in <head>
1876 fakeBody = doc.createElement( "body" ),
1877 div = doc.createElement( "div" );
1878
1879 div.id = "mq-test-1";
1880 div.style.cssText = "position:absolute;top:-100em";
1881 fakeBody.style.background = "none";
1882 fakeBody.appendChild(div);
1883
1884 return function(q){
1885
1886 div.innerHTML = "­<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>";
1887
1888 docElem.insertBefore( fakeBody, refNode );
1889 bool = div.offsetWidth === 42;
1890 docElem.removeChild( fakeBody );
1891
1892 return {
1893 matches: bool,
1894 media: q
1895 };
1896
1897 };
1898
1899 }( document ));
1900
1901 // $.mobile.media uses matchMedia to return a boolean.
1902 $.mobile.media = function( q ) {
1903 return window.matchMedia( q ).matches;
1904 };
1905
1906})(jQuery);
1907
1908 (function( $, undefined ) {
1909 var support = {
1910 touch: "ontouchend" in document
1911 };
1912
1913 $.mobile.support = $.mobile.support || {};
1914 $.extend( $.support, support );
1915 $.extend( $.mobile.support, support );
1916 }( jQuery ));
1917
1918 (function( $, undefined ) {
1919 $.extend( $.support, {
1920 orientation: "orientation" in window && "onorientationchange" in window
1921 });
1922 }( jQuery ));
1923
1924(function( $, undefined ) {
1925
1926// thx Modernizr
1927function propExists( prop ) {
1928 var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
1929 props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ),
1930 v;
1931
1932 for ( v in props ) {
1933 if ( fbCSS[ props[ v ] ] !== undefined ) {
1934 return true;
1935 }
1936 }
1937}
1938
1939var fakeBody = $( "<body>" ).prependTo( "html" ),
1940 fbCSS = fakeBody[ 0 ].style,
1941 vendors = [ "Webkit", "Moz", "O" ],
1942 webos = "palmGetResource" in window, //only used to rule out scrollTop
1943 opera = window.opera,
1944 operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]",
1945 bb = window.blackberry && !propExists( "-webkit-transform" ), //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
1946 nokiaLTE7_3;
1947
1948function validStyle( prop, value, check_vend ) {
1949 var div = document.createElement( "div" ),
1950 uc = function( txt ) {
1951 return txt.charAt( 0 ).toUpperCase() + txt.substr( 1 );
1952 },
1953 vend_pref = function( vend ) {
1954 if ( vend === "" ) {
1955 return "";
1956 } else {
1957 return "-" + vend.charAt( 0 ).toLowerCase() + vend.substr( 1 ) + "-";
1958 }
1959 },
1960 check_style = function( vend ) {
1961 var vend_prop = vend_pref( vend ) + prop + ": " + value + ";",
1962 uc_vend = uc( vend ),
1963 propStyle = uc_vend + ( uc_vend === "" ? prop : uc( prop ) );
1964
1965 div.setAttribute( "style", vend_prop );
1966
1967 if ( !!div.style[ propStyle ] ) {
1968 ret = true;
1969 }
1970 },
1971 check_vends = check_vend ? check_vend : vendors,
1972 i, ret;
1973
1974 for( i = 0; i < check_vends.length; i++ ) {
1975 check_style( check_vends[i] );
1976 }
1977 return !!ret;
1978}
1979
1980// inline SVG support test
1981function inlineSVG() {
1982 // Thanks Modernizr & Erik Dahlstrom
1983 var w = window,
1984 svg = !!w.document.createElementNS && !!w.document.createElementNS( "http://www.w3.org/2000/svg", "svg" ).createSVGRect && !( w.opera && navigator.userAgent.indexOf( "Chrome" ) === -1 ),
1985 support = function( data ) {
1986 if ( !( data && svg ) ) {
1987 $( "html" ).addClass( "ui-nosvg" );
1988 }
1989 },
1990 img = new w.Image();
1991
1992 img.onerror = function() {
1993 support( false );
1994 };
1995 img.onload = function() {
1996 support( img.width === 1 && img.height === 1 );
1997 };
1998 img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
1999}
2000
2001function transform3dTest() {
2002 var mqProp = "transform-3d",
2003 // Because the `translate3d` test below throws false positives in Android:
2004 ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ),
2005 el, transforms, t;
2006
2007 if ( ret ) {
2008 return !!ret;
2009 }
2010
2011 el = document.createElement( "div" );
2012 transforms = {
2013 // We’re omitting Opera for the time being; MS uses unprefixed.
2014 "MozTransform": "-moz-transform",
2015 "transform": "transform"
2016 };
2017
2018 fakeBody.append( el );
2019
2020 for ( t in transforms ) {
2021 if ( el.style[ t ] !== undefined ) {
2022 el.style[ t ] = "translate3d( 100px, 1px, 1px )";
2023 ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] );
2024 }
2025 }
2026 return ( !!ret && ret !== "none" );
2027}
2028
2029// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting )
2030function baseTagTest() {
2031 var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/",
2032 base = $( "head base" ),
2033 fauxEle = null,
2034 href = "",
2035 link, rebase;
2036
2037 if ( !base.length ) {
2038 base = fauxEle = $( "<base>", { "href": fauxBase }).appendTo( "head" );
2039 } else {
2040 href = base.attr( "href" );
2041 }
2042
2043 link = $( "<a href='testurl' />" ).prependTo( fakeBody );
2044 rebase = link[ 0 ].href;
2045 base[ 0 ].href = href || location.pathname;
2046
2047 if ( fauxEle ) {
2048 fauxEle.remove();
2049 }
2050 return rebase.indexOf( fauxBase ) === 0;
2051}
2052
2053// Thanks Modernizr
2054function cssPointerEventsTest() {
2055 var element = document.createElement( "x" ),
2056 documentElement = document.documentElement,
2057 getComputedStyle = window.getComputedStyle,
2058 supports;
2059
2060 if ( !( "pointerEvents" in element.style ) ) {
2061 return false;
2062 }
2063
2064 element.style.pointerEvents = "auto";
2065 element.style.pointerEvents = "x";
2066 documentElement.appendChild( element );
2067 supports = getComputedStyle &&
2068 getComputedStyle( element, "" ).pointerEvents === "auto";
2069 documentElement.removeChild( element );
2070 return !!supports;
2071}
2072
2073function boundingRect() {
2074 var div = document.createElement( "div" );
2075 return typeof div.getBoundingClientRect !== "undefined";
2076}
2077
2078// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
2079// allows for inclusion of IE 6+, including Windows Mobile 7
2080$.extend( $.mobile, { browser: {} } );
2081$.mobile.browser.oldIE = (function() {
2082 var v = 3,
2083 div = document.createElement( "div" ),
2084 a = div.all || [];
2085
2086 do {
2087 div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->";
2088 } while( a[0] );
2089
2090 return v > 4 ? v : !v;
2091})();
2092
2093function fixedPosition() {
2094 var w = window,
2095 ua = navigator.userAgent,
2096 platform = navigator.platform,
2097 // Rendering engine is Webkit, and capture major version
2098 wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
2099 wkversion = !!wkmatch && wkmatch[ 1 ],
2100 ffmatch = ua.match( /Fennec\/([0-9]+)/ ),
2101 ffversion = !!ffmatch && ffmatch[ 1 ],
2102 operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ),
2103 omversion = !!operammobilematch && operammobilematch[ 1 ];
2104
2105 if (
2106 // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
2107 ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) ||
2108 // Opera Mini
2109 ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) ||
2110 ( operammobilematch && omversion < 7458 ) ||
2111 //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
2112 ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) ||
2113 // Firefox Mobile before 6.0 -
2114 ( ffversion && ffversion < 6 ) ||
2115 // WebOS less than 3
2116 ( "palmGetResource" in window && wkversion && wkversion < 534 ) ||
2117 // MeeGo
2118 ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) {
2119 return false;
2120 }
2121
2122 return true;
2123}
2124
2125$.extend( $.support, {
2126 cssTransitions: "WebKitTransitionEvent" in window ||
2127 validStyle( "transition", "height 100ms linear", [ "Webkit", "Moz", "" ] ) &&
2128 !$.mobile.browser.oldIE && !opera,
2129
2130 // Note, Chrome for iOS has an extremely quirky implementation of popstate.
2131 // We've chosen to take the shortest path to a bug fix here for issue #5426
2132 // See the following link for information about the regex chosen
2133 // https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
2134 pushState: "pushState" in history &&
2135 "replaceState" in history &&
2136 // When running inside a FF iframe, calling replaceState causes an error
2137 !( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) &&
2138 ( window.navigator.userAgent.search(/CriOS/) === -1 ),
2139
2140 mediaquery: $.mobile.media( "only all" ),
2141 cssPseudoElement: !!propExists( "content" ),
2142 touchOverflow: !!propExists( "overflowScrolling" ),
2143 cssTransform3d: transform3dTest(),
2144 cssAnimations: !!propExists( "animationName" ),
2145 boxShadow: !!propExists( "boxShadow" ) && !bb,
2146 fixedPosition: fixedPosition(),
2147 scrollTop: ("pageXOffset" in window ||
2148 "scrollTop" in document.documentElement ||
2149 "scrollTop" in fakeBody[ 0 ]) && !webos && !operamini,
2150
2151 dynamicBaseTag: baseTagTest(),
2152 cssPointerEvents: cssPointerEventsTest(),
2153 boundingRect: boundingRect(),
2154 inlineSVG: inlineSVG
2155});
2156
2157fakeBody.remove();
2158
2159// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian)
2160// or that generally work better browsing in regular http for full page refreshes (Opera Mini)
2161// Note: This detection below is used as a last resort.
2162// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible
2163nokiaLTE7_3 = (function() {
2164
2165 var ua = window.navigator.userAgent;
2166
2167 //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older
2168 return ua.indexOf( "Nokia" ) > -1 &&
2169 ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) &&
2170 ua.indexOf( "AppleWebKit" ) > -1 &&
2171 ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ );
2172})();
2173
2174// Support conditions that must be met in order to proceed
2175// default enhanced qualifications are media query support OR IE 7+
2176
2177$.mobile.gradeA = function() {
2178 return ( ( $.support.mediaquery && $.support.cssPseudoElement ) || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 8 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null );
2179};
2180
2181$.mobile.ajaxBlacklist =
2182 // BlackBerry browsers, pre-webkit
2183 window.blackberry && !window.WebKitPoint ||
2184 // Opera Mini
2185 operamini ||
2186 // Symbian webkits pre 7.3
2187 nokiaLTE7_3;
2188
2189// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices
2190// to render the stylesheets when they're referenced before this script, as we'd recommend doing.
2191// This simply reappends the CSS in place, which for some reason makes it apply
2192if ( nokiaLTE7_3 ) {
2193 $(function() {
2194 $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" );
2195 });
2196}
2197
2198// For ruling out shadows via css
2199if ( !$.support.boxShadow ) {
2200 $( "html" ).addClass( "ui-noboxshadow" );
2201}
2202
2203})( jQuery );
2204
2205
2206(function( $, undefined ) {
2207 var $win = $.mobile.window, self,
2208 dummyFnToInitNavigate = function() {
2209 };
2210
2211 $.event.special.beforenavigate = {
2212 setup: function() {
2213 $win.on( "navigate", dummyFnToInitNavigate );
2214 },
2215
2216 teardown: function() {
2217 $win.off( "navigate", dummyFnToInitNavigate );
2218 }
2219 };
2220
2221 $.event.special.navigate = self = {
2222 bound: false,
2223
2224 pushStateEnabled: true,
2225
2226 originalEventName: undefined,
2227
2228 // If pushstate support is present and push state support is defined to
2229 // be true on the mobile namespace.
2230 isPushStateEnabled: function() {
2231 return $.support.pushState &&
2232 $.mobile.pushStateEnabled === true &&
2233 this.isHashChangeEnabled();
2234 },
2235
2236 // !! assumes mobile namespace is present
2237 isHashChangeEnabled: function() {
2238 return $.mobile.hashListeningEnabled === true;
2239 },
2240
2241 // TODO a lot of duplication between popstate and hashchange
2242 popstate: function( event ) {
2243 var newEvent = new $.Event( "navigate" ),
2244 beforeNavigate = new $.Event( "beforenavigate" ),
2245 state = event.originalEvent.state || {};
2246
2247 beforeNavigate.originalEvent = event;
2248 $win.trigger( beforeNavigate );
2249
2250 if ( beforeNavigate.isDefaultPrevented() ) {
2251 return;
2252 }
2253
2254 if ( event.historyState ) {
2255 $.extend(state, event.historyState);
2256 }
2257
2258 // Make sure the original event is tracked for the end
2259 // user to inspect incase they want to do something special
2260 newEvent.originalEvent = event;
2261
2262 // NOTE we let the current stack unwind because any assignment to
2263 // location.hash will stop the world and run this event handler. By
2264 // doing this we create a similar behavior to hashchange on hash
2265 // assignment
2266 setTimeout(function() {
2267 $win.trigger( newEvent, {
2268 state: state
2269 });
2270 }, 0);
2271 },
2272
2273 hashchange: function( event /*, data */ ) {
2274 var newEvent = new $.Event( "navigate" ),
2275 beforeNavigate = new $.Event( "beforenavigate" );
2276
2277 beforeNavigate.originalEvent = event;
2278 $win.trigger( beforeNavigate );
2279
2280 if ( beforeNavigate.isDefaultPrevented() ) {
2281 return;
2282 }
2283
2284 // Make sure the original event is tracked for the end
2285 // user to inspect incase they want to do something special
2286 newEvent.originalEvent = event;
2287
2288 // Trigger the hashchange with state provided by the user
2289 // that altered the hash
2290 $win.trigger( newEvent, {
2291 // Users that want to fully normalize the two events
2292 // will need to do history management down the stack and
2293 // add the state to the event before this binding is fired
2294 // TODO consider allowing for the explicit addition of callbacks
2295 // to be fired before this value is set to avoid event timing issues
2296 state: event.hashchangeState || {}
2297 });
2298 },
2299
2300 // TODO We really only want to set this up once
2301 // but I'm not clear if there's a beter way to achieve
2302 // this with the jQuery special event structure
2303 setup: function( /* data, namespaces */ ) {
2304 if ( self.bound ) {
2305 return;
2306 }
2307
2308 self.bound = true;
2309
2310 if ( self.isPushStateEnabled() ) {
2311 self.originalEventName = "popstate";
2312 $win.bind( "popstate.navigate", self.popstate );
2313 } else if ( self.isHashChangeEnabled() ) {
2314 self.originalEventName = "hashchange";
2315 $win.bind( "hashchange.navigate", self.hashchange );
2316 }
2317 }
2318 };
2319})( jQuery );
2320
2321
2322
2323(function( $, undefined ) {
2324 var path, $base, dialogHashKey = "&ui-state=dialog";
2325
2326 $.mobile.path = path = {
2327 uiStateKey: "&ui-state",
2328
2329 // This scary looking regular expression parses an absolute URL or its relative
2330 // variants (protocol, site, document, query, and hash), into the various
2331 // components (protocol, host, path, query, fragment, etc that make up the
2332 // URL as well as some other commonly used sub-parts. When used with RegExp.exec()
2333 // or String.match, it parses the URL into a results array that looks like this:
2334 //
2335 // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
2336 // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
2337 // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
2338 // [3]: http://jblas:password@mycompany.com:8080
2339 // [4]: http:
2340 // [5]: //
2341 // [6]: jblas:password@mycompany.com:8080
2342 // [7]: jblas:password
2343 // [8]: jblas
2344 // [9]: password
2345 // [10]: mycompany.com:8080
2346 // [11]: mycompany.com
2347 // [12]: 8080
2348 // [13]: /mail/inbox
2349 // [14]: /mail/
2350 // [15]: inbox
2351 // [16]: ?msg=1234&type=unread
2352 // [17]: #msg-content
2353 //
2354 urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
2355
2356 // Abstraction to address xss (Issue #4787) by removing the authority in
2357 // browsers that auto decode it. All references to location.href should be
2358 // replaced with a call to this method so that it can be dealt with properly here
2359 getLocation: function( url ) {
2360 var uri = url ? this.parseUrl( url ) : location,
2361 hash = this.parseUrl( url || location.href ).hash;
2362
2363 // mimic the browser with an empty string when the hash is empty
2364 hash = hash === "#" ? "" : hash;
2365
2366 // Make sure to parse the url or the location object for the hash because using location.hash
2367 // is autodecoded in firefox, the rest of the url should be from the object (location unless
2368 // we're testing) to avoid the inclusion of the authority
2369 return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
2370 },
2371
2372 //return the original document url
2373 getDocumentUrl: function( asParsedObject ) {
2374 return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href;
2375 },
2376
2377 parseLocation: function() {
2378 return this.parseUrl( this.getLocation() );
2379 },
2380
2381 //Parse a URL into a structure that allows easy access to
2382 //all of the URL components by name.
2383 parseUrl: function( url ) {
2384 // If we're passed an object, we'll assume that it is
2385 // a parsed url object and just return it back to the caller.
2386 if ( $.type( url ) === "object" ) {
2387 return url;
2388 }
2389
2390 var matches = path.urlParseRE.exec( url || "" ) || [];
2391
2392 // Create an object that allows the caller to access the sub-matches
2393 // by name. Note that IE returns an empty string instead of undefined,
2394 // like all other browsers do, so we normalize everything so its consistent
2395 // no matter what browser we're running on.
2396 return {
2397 href: matches[ 0 ] || "",
2398 hrefNoHash: matches[ 1 ] || "",
2399 hrefNoSearch: matches[ 2 ] || "",
2400 domain: matches[ 3 ] || "",
2401 protocol: matches[ 4 ] || "",
2402 doubleSlash: matches[ 5 ] || "",
2403 authority: matches[ 6 ] || "",
2404 username: matches[ 8 ] || "",
2405 password: matches[ 9 ] || "",
2406 host: matches[ 10 ] || "",
2407 hostname: matches[ 11 ] || "",
2408 port: matches[ 12 ] || "",
2409 pathname: matches[ 13 ] || "",
2410 directory: matches[ 14 ] || "",
2411 filename: matches[ 15 ] || "",
2412 search: matches[ 16 ] || "",
2413 hash: matches[ 17 ] || ""
2414 };
2415 },
2416
2417 //Turn relPath into an asbolute path. absPath is
2418 //an optional absolute path which describes what
2419 //relPath is relative to.
2420 makePathAbsolute: function( relPath, absPath ) {
2421 var absStack,
2422 relStack,
2423 i, d;
2424
2425 if ( relPath && relPath.charAt( 0 ) === "/" ) {
2426 return relPath;
2427 }
2428
2429 relPath = relPath || "";
2430 absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
2431
2432 absStack = absPath ? absPath.split( "/" ) : [];
2433 relStack = relPath.split( "/" );
2434
2435 for ( i = 0; i < relStack.length; i++ ) {
2436 d = relStack[ i ];
2437 switch ( d ) {
2438 case ".":
2439 break;
2440 case "..":
2441 if ( absStack.length ) {
2442 absStack.pop();
2443 }
2444 break;
2445 default:
2446 absStack.push( d );
2447 break;
2448 }
2449 }
2450 return "/" + absStack.join( "/" );
2451 },
2452
2453 //Returns true if both urls have the same domain.
2454 isSameDomain: function( absUrl1, absUrl2 ) {
2455 return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
2456 },
2457
2458 //Returns true for any relative variant.
2459 isRelativeUrl: function( url ) {
2460 // All relative Url variants have one thing in common, no protocol.
2461 return path.parseUrl( url ).protocol === "";
2462 },
2463
2464 //Returns true for an absolute url.
2465 isAbsoluteUrl: function( url ) {
2466 return path.parseUrl( url ).protocol !== "";
2467 },
2468
2469 //Turn the specified realtive URL into an absolute one. This function
2470 //can handle all relative variants (protocol, site, document, query, fragment).
2471 makeUrlAbsolute: function( relUrl, absUrl ) {
2472 if ( !path.isRelativeUrl( relUrl ) ) {
2473 return relUrl;
2474 }
2475
2476 if ( absUrl === undefined ) {
2477 absUrl = this.documentBase;
2478 }
2479
2480 var relObj = path.parseUrl( relUrl ),
2481 absObj = path.parseUrl( absUrl ),
2482 protocol = relObj.protocol || absObj.protocol,
2483 doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
2484 authority = relObj.authority || absObj.authority,
2485 hasPath = relObj.pathname !== "",
2486 pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
2487 search = relObj.search || ( !hasPath && absObj.search ) || "",
2488 hash = relObj.hash;
2489
2490 return protocol + doubleSlash + authority + pathname + search + hash;
2491 },
2492
2493 //Add search (aka query) params to the specified url.
2494 addSearchParams: function( url, params ) {
2495 var u = path.parseUrl( url ),
2496 p = ( typeof params === "object" ) ? $.param( params ) : params,
2497 s = u.search || "?";
2498 return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
2499 },
2500
2501 convertUrlToDataUrl: function( absUrl ) {
2502 var u = path.parseUrl( absUrl );
2503 if ( path.isEmbeddedPage( u ) ) {
2504 // For embedded pages, remove the dialog hash key as in getFilePath(),
2505 // and remove otherwise the Data Url won't match the id of the embedded Page.
2506 return u.hash
2507 .split( dialogHashKey )[0]
2508 .replace( /^#/, "" )
2509 .replace( /\?.*$/, "" );
2510 } else if ( path.isSameDomain( u, this.documentBase ) ) {
2511 return u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0];
2512 }
2513
2514 return window.decodeURIComponent(absUrl);
2515 },
2516
2517 //get path from current hash, or from a file path
2518 get: function( newPath ) {
2519 if ( newPath === undefined ) {
2520 newPath = path.parseLocation().hash;
2521 }
2522 return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, "" );
2523 },
2524
2525 //set location hash to path
2526 set: function( path ) {
2527 location.hash = path;
2528 },
2529
2530 //test if a given url (string) is a path
2531 //NOTE might be exceptionally naive
2532 isPath: function( url ) {
2533 return ( /\// ).test( url );
2534 },
2535
2536 //return a url path with the window's location protocol/hostname/pathname removed
2537 clean: function( url ) {
2538 return url.replace( this.documentBase.domain, "" );
2539 },
2540
2541 //just return the url without an initial #
2542 stripHash: function( url ) {
2543 return url.replace( /^#/, "" );
2544 },
2545
2546 stripQueryParams: function( url ) {
2547 return url.replace( /\?.*$/, "" );
2548 },
2549
2550 //remove the preceding hash, any query params, and dialog notations
2551 cleanHash: function( hash ) {
2552 return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
2553 },
2554
2555 isHashValid: function( hash ) {
2556 return ( /^#[^#]+$/ ).test( hash );
2557 },
2558
2559 //check whether a url is referencing the same domain, or an external domain or different protocol
2560 //could be mailto, etc
2561 isExternal: function( url ) {
2562 var u = path.parseUrl( url );
2563 return u.protocol && u.domain !== this.documentUrl.domain ? true : false;
2564 },
2565
2566 hasProtocol: function( url ) {
2567 return ( /^(:?\w+:)/ ).test( url );
2568 },
2569
2570 isEmbeddedPage: function( url ) {
2571 var u = path.parseUrl( url );
2572
2573 //if the path is absolute, then we need to compare the url against
2574 //both the this.documentUrl and the documentBase. The main reason for this
2575 //is that links embedded within external documents will refer to the
2576 //application document, whereas links embedded within the application
2577 //document will be resolved against the document base.
2578 if ( u.protocol !== "" ) {
2579 return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) );
2580 }
2581 return ( /^#/ ).test( u.href );
2582 },
2583
2584 squash: function( url, resolutionUrl ) {
2585 var href, cleanedUrl, search, stateIndex,
2586 isPath = this.isPath( url ),
2587 uri = this.parseUrl( url ),
2588 preservedHash = uri.hash,
2589 uiState = "";
2590
2591 // produce a url against which we can resole the provided path
2592 resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl());
2593
2594 // If the url is anything but a simple string, remove any preceding hash
2595 // eg #foo/bar -> foo/bar
2596 // #foo -> #foo
2597 cleanedUrl = isPath ? path.stripHash( url ) : url;
2598
2599 // If the url is a full url with a hash check if the parsed hash is a path
2600 // if it is, strip the #, and use it otherwise continue without change
2601 cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl;
2602
2603 // Split the UI State keys off the href
2604 stateIndex = cleanedUrl.indexOf( this.uiStateKey );
2605
2606 // store the ui state keys for use
2607 if ( stateIndex > -1 ) {
2608 uiState = cleanedUrl.slice( stateIndex );
2609 cleanedUrl = cleanedUrl.slice( 0, stateIndex );
2610 }
2611
2612 // make the cleanedUrl absolute relative to the resolution url
2613 href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl );
2614
2615 // grab the search from the resolved url since parsing from
2616 // the passed url may not yield the correct result
2617 search = this.parseUrl( href ).search;
2618
2619 // TODO all this crap is terrible, clean it up
2620 if ( isPath ) {
2621 // reject the hash if it's a path or it's just a dialog key
2622 if ( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) {
2623 preservedHash = "";
2624 }
2625
2626 // Append the UI State keys where it exists and it's been removed
2627 // from the url
2628 if ( uiState && preservedHash.indexOf( this.uiStateKey ) === -1) {
2629 preservedHash += uiState;
2630 }
2631
2632 // make sure that pound is on the front of the hash
2633 if ( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ) {
2634 preservedHash = "#" + preservedHash;
2635 }
2636
2637 // reconstruct each of the pieces with the new search string and hash
2638 href = path.parseUrl( href );
2639 href = href.protocol + "//" + href.host + href.pathname + search + preservedHash;
2640 } else {
2641 href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState;
2642 }
2643
2644 return href;
2645 },
2646
2647 isPreservableHash: function( hash ) {
2648 return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0;
2649 },
2650
2651 // Escape weird characters in the hash if it is to be used as a selector
2652 hashToSelector: function( hash ) {
2653 var hasHash = ( hash.substring( 0, 1 ) === "#" );
2654 if ( hasHash ) {
2655 hash = hash.substring( 1 );
2656 }
2657 return ( hasHash ? "#" : "" ) + hash.replace( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$1" );
2658 },
2659
2660 // return the substring of a filepath before the sub-page key, for making
2661 // a server request
2662 getFilePath: function( path ) {
2663 var splitkey = "&" + $.mobile.subPageUrlKey;
2664 return path && path.split( splitkey )[0].split( dialogHashKey )[0];
2665 },
2666
2667 // check if the specified url refers to the first page in the main
2668 // application document.
2669 isFirstPageUrl: function( url ) {
2670 // We only deal with absolute paths.
2671 var u = path.parseUrl( path.makeUrlAbsolute( url, this.documentBase ) ),
2672
2673 // Does the url have the same path as the document?
2674 samePath = u.hrefNoHash === this.documentUrl.hrefNoHash ||
2675 ( this.documentBaseDiffers &&
2676 u.hrefNoHash === this.documentBase.hrefNoHash ),
2677
2678 // Get the first page element.
2679 fp = $.mobile.firstPage,
2680
2681 // Get the id of the first page element if it has one.
2682 fpId = fp && fp[0] ? fp[0].id : undefined;
2683
2684 // The url refers to the first page if the path matches the document and
2685 // it either has no hash value, or the hash is exactly equal to the id
2686 // of the first page element.
2687 return samePath &&
2688 ( !u.hash ||
2689 u.hash === "#" ||
2690 ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
2691 },
2692
2693 // Some embedded browsers, like the web view in Phone Gap, allow
2694 // cross-domain XHR requests if the document doing the request was loaded
2695 // via the file:// protocol. This is usually to allow the application to
2696 // "phone home" and fetch app specific data. We normally let the browser
2697 // handle external/cross-domain urls, but if the allowCrossDomainPages
2698 // option is true, we will allow cross-domain http/https requests to go
2699 // through our page loading logic.
2700 isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
2701 return $.mobile.allowCrossDomainPages &&
2702 (docUrl.protocol === "file:" || docUrl.protocol === "content:") &&
2703 reqUrl.search( /^https?:/ ) !== -1;
2704 }
2705 };
2706
2707 path.documentUrl = path.parseLocation();
2708
2709 $base = $( "head" ).find( "base" );
2710
2711 path.documentBase = $base.length ?
2712 path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) :
2713 path.documentUrl;
2714
2715 path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash);
2716
2717 //return the original document base url
2718 path.getDocumentBase = function( asParsedObject ) {
2719 return asParsedObject ? $.extend( {}, path.documentBase ) : path.documentBase.href;
2720 };
2721
2722 // DEPRECATED as of 1.4.0 - remove in 1.5.0
2723 $.extend( $.mobile, {
2724
2725 //return the original document url
2726 getDocumentUrl: path.getDocumentUrl,
2727
2728 //return the original document base url
2729 getDocumentBase: path.getDocumentBase
2730 });
2731})( jQuery );
2732
2733
2734
2735(function( $, undefined ) {
2736 $.mobile.History = function( stack, index ) {
2737 this.stack = stack || [];
2738 this.activeIndex = index || 0;
2739 };
2740
2741 $.extend($.mobile.History.prototype, {
2742 getActive: function() {
2743 return this.stack[ this.activeIndex ];
2744 },
2745
2746 getLast: function() {
2747 return this.stack[ this.previousIndex ];
2748 },
2749
2750 getNext: function() {
2751 return this.stack[ this.activeIndex + 1 ];
2752 },
2753
2754 getPrev: function() {
2755 return this.stack[ this.activeIndex - 1 ];
2756 },
2757
2758 // addNew is used whenever a new page is added
2759 add: function( url, data ) {
2760 data = data || {};
2761
2762 //if there's forward history, wipe it
2763 if ( this.getNext() ) {
2764 this.clearForward();
2765 }
2766
2767 // if the hash is included in the data make sure the shape
2768 // is consistent for comparison
2769 if ( data.hash && data.hash.indexOf( "#" ) === -1) {
2770 data.hash = "#" + data.hash;
2771 }
2772
2773 data.url = url;
2774 this.stack.push( data );
2775 this.activeIndex = this.stack.length - 1;
2776 },
2777
2778 //wipe urls ahead of active index
2779 clearForward: function() {
2780 this.stack = this.stack.slice( 0, this.activeIndex + 1 );
2781 },
2782
2783 find: function( url, stack, earlyReturn ) {
2784 stack = stack || this.stack;
2785
2786 var entry, i, length = stack.length, index;
2787
2788 for ( i = 0; i < length; i++ ) {
2789 entry = stack[i];
2790
2791 if ( decodeURIComponent(url) === decodeURIComponent(entry.url) ||
2792 decodeURIComponent(url) === decodeURIComponent(entry.hash) ) {
2793 index = i;
2794
2795 if ( earlyReturn ) {
2796 return index;
2797 }
2798 }
2799 }
2800
2801 return index;
2802 },
2803
2804 closest: function( url ) {
2805 var closest, a = this.activeIndex;
2806
2807 // First, take the slice of the history stack before the current index and search
2808 // for a url match. If one is found, we'll avoid avoid looking through forward history
2809 // NOTE the preference for backward history movement is driven by the fact that
2810 // most mobile browsers only have a dedicated back button, and users rarely use
2811 // the forward button in desktop browser anyhow
2812 closest = this.find( url, this.stack.slice(0, a) );
2813
2814 // If nothing was found in backward history check forward. The `true`
2815 // value passed as the third parameter causes the find method to break
2816 // on the first match in the forward history slice. The starting index
2817 // of the slice must then be added to the result to get the element index
2818 // in the original history stack :( :(
2819 //
2820 // TODO this is hyper confusing and should be cleaned up (ugh so bad)
2821 if ( closest === undefined ) {
2822 closest = this.find( url, this.stack.slice(a), true );
2823 closest = closest === undefined ? closest : closest + a;
2824 }
2825
2826 return closest;
2827 },
2828
2829 direct: function( opts ) {
2830 var newActiveIndex = this.closest( opts.url ), a = this.activeIndex;
2831
2832 // save new page index, null check to prevent falsey 0 result
2833 // record the previous index for reference
2834 if ( newActiveIndex !== undefined ) {
2835 this.activeIndex = newActiveIndex;
2836 this.previousIndex = a;
2837 }
2838
2839 // invoke callbacks where appropriate
2840 //
2841 // TODO this is also convoluted and confusing
2842 if ( newActiveIndex < a ) {
2843 ( opts.present || opts.back || $.noop )( this.getActive(), "back" );
2844 } else if ( newActiveIndex > a ) {
2845 ( opts.present || opts.forward || $.noop )( this.getActive(), "forward" );
2846 } else if ( newActiveIndex === undefined && opts.missing ) {
2847 opts.missing( this.getActive() );
2848 }
2849 }
2850 });
2851})( jQuery );
2852
2853
2854
2855(function( $, undefined ) {
2856 var path = $.mobile.path,
2857 initialHref = location.href;
2858
2859 $.mobile.Navigator = function( history ) {
2860 this.history = history;
2861 this.ignoreInitialHashChange = true;
2862
2863 $.mobile.window.bind({
2864 "popstate.history": $.proxy( this.popstate, this ),
2865 "hashchange.history": $.proxy( this.hashchange, this )
2866 });
2867 };
2868
2869 $.extend($.mobile.Navigator.prototype, {
2870 squash: function( url, data ) {
2871 var state, href, hash = path.isPath(url) ? path.stripHash(url) : url;
2872
2873 href = path.squash( url );
2874
2875 // make sure to provide this information when it isn't explicitly set in the
2876 // data object that was passed to the squash method
2877 state = $.extend({
2878 hash: hash,
2879 url: href
2880 }, data);
2881
2882 // replace the current url with the new href and store the state
2883 // Note that in some cases we might be replacing an url with the
2884 // same url. We do this anyways because we need to make sure that
2885 // all of our history entries have a state object associated with
2886 // them. This allows us to work around the case where $.mobile.back()
2887 // is called to transition from an external page to an embedded page.
2888 // In that particular case, a hashchange event is *NOT* generated by the browser.
2889 // Ensuring each history entry has a state object means that onPopState()
2890 // will always trigger our hashchange callback even when a hashchange event
2891 // is not fired.
2892 window.history.replaceState( state, state.title || document.title, href );
2893
2894 return state;
2895 },
2896
2897 hash: function( url, href ) {
2898 var parsed, loc, hash, resolved;
2899
2900 // Grab the hash for recording. If the passed url is a path
2901 // we used the parsed version of the squashed url to reconstruct,
2902 // otherwise we assume it's a hash and store it directly
2903 parsed = path.parseUrl( url );
2904 loc = path.parseLocation();
2905
2906 if ( loc.pathname + loc.search === parsed.pathname + parsed.search ) {
2907 // If the pathname and search of the passed url is identical to the current loc
2908 // then we must use the hash. Otherwise there will be no event
2909 // eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz"
2910 hash = parsed.hash ? parsed.hash : parsed.pathname + parsed.search;
2911 } else if ( path.isPath(url) ) {
2912 resolved = path.parseUrl( href );
2913 // If the passed url is a path, make it domain relative and remove any trailing hash
2914 hash = resolved.pathname + resolved.search + (path.isPreservableHash( resolved.hash )? resolved.hash.replace( "#", "" ) : "");
2915 } else {
2916 hash = url;
2917 }
2918
2919 return hash;
2920 },
2921
2922 // TODO reconsider name
2923 go: function( url, data, noEvents ) {
2924 var state, href, hash, popstateEvent,
2925 isPopStateEvent = $.event.special.navigate.isPushStateEnabled();
2926
2927 // Get the url as it would look squashed on to the current resolution url
2928 href = path.squash( url );
2929
2930 // sort out what the hash sould be from the url
2931 hash = this.hash( url, href );
2932
2933 // Here we prevent the next hash change or popstate event from doing any
2934 // history management. In the case of hashchange we don't swallow it
2935 // if there will be no hashchange fired (since that won't reset the value)
2936 // and will swallow the following hashchange
2937 if ( noEvents && hash !== path.stripHash(path.parseLocation().hash) ) {
2938 this.preventNextHashChange = noEvents;
2939 }
2940
2941 // IMPORTANT in the case where popstate is supported the event will be triggered
2942 // directly, stopping further execution - ie, interupting the flow of this
2943 // method call to fire bindings at this expression. Below the navigate method
2944 // there is a binding to catch this event and stop its propagation.
2945 //
2946 // We then trigger a new popstate event on the window with a null state
2947 // so that the navigate events can conclude their work properly
2948 //
2949 // if the url is a path we want to preserve the query params that are available on
2950 // the current url.
2951 this.preventHashAssignPopState = true;
2952 window.location.hash = hash;
2953
2954 // If popstate is enabled and the browser triggers `popstate` events when the hash
2955 // is set (this often happens immediately in browsers like Chrome), then the
2956 // this flag will be set to false already. If it's a browser that does not trigger
2957 // a `popstate` on hash assignement or `replaceState` then we need avoid the branch
2958 // that swallows the event created by the popstate generated by the hash assignment
2959 // At the time of this writing this happens with Opera 12 and some version of IE
2960 this.preventHashAssignPopState = false;
2961
2962 state = $.extend({
2963 url: href,
2964 hash: hash,
2965 title: document.title
2966 }, data);
2967
2968 if ( isPopStateEvent ) {
2969 popstateEvent = new $.Event( "popstate" );
2970 popstateEvent.originalEvent = {
2971 type: "popstate",
2972 state: null
2973 };
2974
2975 this.squash( url, state );
2976
2977 // Trigger a new faux popstate event to replace the one that we
2978 // caught that was triggered by the hash setting above.
2979 if ( !noEvents ) {
2980 this.ignorePopState = true;
2981 $.mobile.window.trigger( popstateEvent );
2982 }
2983 }
2984
2985 // record the history entry so that the information can be included
2986 // in hashchange event driven navigate events in a similar fashion to
2987 // the state that's provided by popstate
2988 this.history.add( state.url, state );
2989 },
2990
2991 // This binding is intended to catch the popstate events that are fired
2992 // when execution of the `$.navigate` method stops at window.location.hash = url;
2993 // and completely prevent them from propagating. The popstate event will then be
2994 // retriggered after execution resumes
2995 //
2996 // TODO grab the original event here and use it for the synthetic event in the
2997 // second half of the navigate execution that will follow this binding
2998 popstate: function( event ) {
2999 var hash, state;
3000
3001 // Partly to support our test suite which manually alters the support
3002 // value to test hashchange. Partly to prevent all around weirdness
3003 if ( !$.event.special.navigate.isPushStateEnabled() ) {
3004 return;
3005 }
3006
3007 // If this is the popstate triggered by the actual alteration of the hash
3008 // prevent it completely. History is tracked manually
3009 if ( this.preventHashAssignPopState ) {
3010 this.preventHashAssignPopState = false;
3011 event.stopImmediatePropagation();
3012 return;
3013 }
3014
3015 // if this is the popstate triggered after the `replaceState` call in the go
3016 // method, then simply ignore it. The history entry has already been captured
3017 if ( this.ignorePopState ) {
3018 this.ignorePopState = false;
3019 return;
3020 }
3021
3022 // If there is no state, and the history stack length is one were
3023 // probably getting the page load popstate fired by browsers like chrome
3024 // avoid it and set the one time flag to false.
3025 // TODO: Do we really need all these conditions? Comparing location hrefs
3026 // should be sufficient.
3027 if ( !event.originalEvent.state &&
3028 this.history.stack.length === 1 &&
3029 this.ignoreInitialHashChange ) {
3030 this.ignoreInitialHashChange = false;
3031
3032 if ( location.href === initialHref ) {
3033 event.preventDefault();
3034 return;
3035 }
3036 }
3037
3038 // account for direct manipulation of the hash. That is, we will receive a popstate
3039 // when the hash is changed by assignment, and it won't have a state associated. We
3040 // then need to squash the hash. See below for handling of hash assignment that
3041 // matches an existing history entry
3042 // TODO it might be better to only add to the history stack
3043 // when the hash is adjacent to the active history entry
3044 hash = path.parseLocation().hash;
3045 if ( !event.originalEvent.state && hash ) {
3046 // squash the hash that's been assigned on the URL with replaceState
3047 // also grab the resulting state object for storage
3048 state = this.squash( hash );
3049
3050 // record the new hash as an additional history entry
3051 // to match the browser's treatment of hash assignment
3052 this.history.add( state.url, state );
3053
3054 // pass the newly created state information
3055 // along with the event
3056 event.historyState = state;
3057
3058 // do not alter history, we've added a new history entry
3059 // so we know where we are
3060 return;
3061 }
3062
3063 // If all else fails this is a popstate that comes from the back or forward buttons
3064 // make sure to set the state of our history stack properly, and record the directionality
3065 this.history.direct({
3066 url: (event.originalEvent.state || {}).url || hash,
3067
3068 // When the url is either forward or backward in history include the entry
3069 // as data on the event object for merging as data in the navigate event
3070 present: function( historyEntry, direction ) {
3071 // make sure to create a new object to pass down as the navigate event data
3072 event.historyState = $.extend({}, historyEntry);
3073 event.historyState.direction = direction;
3074 }
3075 });
3076 },
3077
3078 // NOTE must bind before `navigate` special event hashchange binding otherwise the
3079 // navigation data won't be attached to the hashchange event in time for those
3080 // bindings to attach it to the `navigate` special event
3081 // TODO add a check here that `hashchange.navigate` is bound already otherwise it's
3082 // broken (exception?)
3083 hashchange: function( event ) {
3084 var history, hash;
3085
3086 // If hashchange listening is explicitly disabled or pushstate is supported
3087 // avoid making use of the hashchange handler.
3088 if (!$.event.special.navigate.isHashChangeEnabled() ||
3089 $.event.special.navigate.isPushStateEnabled() ) {
3090 return;
3091 }
3092
3093 // On occasion explicitly want to prevent the next hash from propogating because we only
3094 // with to alter the url to represent the new state do so here
3095 if ( this.preventNextHashChange ) {
3096 this.preventNextHashChange = false;
3097 event.stopImmediatePropagation();
3098 return;
3099 }
3100
3101 history = this.history;
3102 hash = path.parseLocation().hash;
3103
3104 // If this is a hashchange caused by the back or forward button
3105 // make sure to set the state of our history stack properly
3106 this.history.direct({
3107 url: hash,
3108
3109 // When the url is either forward or backward in history include the entry
3110 // as data on the event object for merging as data in the navigate event
3111 present: function( historyEntry, direction ) {
3112 // make sure to create a new object to pass down as the navigate event data
3113 event.hashchangeState = $.extend({}, historyEntry);
3114 event.hashchangeState.direction = direction;
3115 },
3116
3117 // When we don't find a hash in our history clearly we're aiming to go there
3118 // record the entry as new for future traversal
3119 //
3120 // NOTE it's not entirely clear that this is the right thing to do given that we
3121 // can't know the users intention. It might be better to explicitly _not_
3122 // support location.hash assignment in preference to $.navigate calls
3123 // TODO first arg to add should be the href, but it causes issues in identifying
3124 // embeded pages
3125 missing: function() {
3126 history.add( hash, {
3127 hash: hash,
3128 title: document.title
3129 });
3130 }
3131 });
3132 }
3133 });
3134})( jQuery );
3135
3136
3137
3138(function( $, undefined ) {
3139 // TODO consider queueing navigation activity until previous activities have completed
3140 // so that end users don't have to think about it. Punting for now
3141 // TODO !! move the event bindings into callbacks on the navigate event
3142 $.mobile.navigate = function( url, data, noEvents ) {
3143 $.mobile.navigate.navigator.go( url, data, noEvents );
3144 };
3145
3146 // expose the history on the navigate method in anticipation of full integration with
3147 // existing navigation functionalty that is tightly coupled to the history information
3148 $.mobile.navigate.history = new $.mobile.History();
3149
3150 // instantiate an instance of the navigator for use within the $.navigate method
3151 $.mobile.navigate.navigator = new $.mobile.Navigator( $.mobile.navigate.history );
3152
3153 var loc = $.mobile.path.parseLocation();
3154 $.mobile.navigate.history.add( loc.href, {hash: loc.hash} );
3155})( jQuery );
3156
3157
3158// This plugin is an experiment for abstracting away the touch and mouse
3159// events so that developers don't have to worry about which method of input
3160// the device their document is loaded on supports.
3161//
3162// The idea here is to allow the developer to register listeners for the
3163// basic mouse events, such as mousedown, mousemove, mouseup, and click,
3164// and the plugin will take care of registering the correct listeners
3165// behind the scenes to invoke the listener at the fastest possible time
3166// for that device, while still retaining the order of event firing in
3167// the traditional mouse environment, should multiple handlers be registered
3168// on the same element for different events.
3169//
3170// The current version exposes the following virtual events to jQuery bind methods:
3171// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
3172
3173(function( $, window, document, undefined ) {
3174
3175var dataPropertyName = "virtualMouseBindings",
3176 touchTargetPropertyName = "virtualTouchID",
3177 virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
3178 touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
3179 mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
3180 mouseEventProps = $.event.props.concat( mouseHookProps ),
3181 activeDocHandlers = {},
3182 resetTimerID = 0,
3183 startX = 0,
3184 startY = 0,
3185 didScroll = false,
3186 clickBlockList = [],
3187 blockMouseTriggers = false,
3188 blockTouchTriggers = false,
3189 eventCaptureSupported = "addEventListener" in document,
3190 $document = $( document ),
3191 nextTouchID = 1,
3192 lastTouchID = 0, threshold,
3193 i;
3194
3195$.vmouse = {
3196 moveDistanceThreshold: 10,
3197 clickDistanceThreshold: 10,
3198 resetTimerDuration: 1500
3199};
3200
3201function getNativeEvent( event ) {
3202
3203 while ( event && typeof event.originalEvent !== "undefined" ) {
3204 event = event.originalEvent;
3205 }
3206 return event;
3207}
3208
3209function createVirtualEvent( event, eventType ) {
3210
3211 var t = event.type,
3212 oe, props, ne, prop, ct, touch, i, j, len;
3213
3214 event = $.Event( event );
3215 event.type = eventType;
3216
3217 oe = event.originalEvent;
3218 props = $.event.props;
3219
3220 // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
3221 // https://github.com/jquery/jquery-mobile/issues/3280
3222 if ( t.search( /^(mouse|click)/ ) > -1 ) {
3223 props = mouseEventProps;
3224 }
3225
3226 // copy original event properties over to the new event
3227 // this would happen if we could call $.event.fix instead of $.Event
3228 // but we don't have a way to force an event to be fixed multiple times
3229 if ( oe ) {
3230 for ( i = props.length, prop; i; ) {
3231 prop = props[ --i ];
3232 event[ prop ] = oe[ prop ];
3233 }
3234 }
3235
3236 // make sure that if the mouse and click virtual events are generated
3237 // without a .which one is defined
3238 if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) {
3239 event.which = 1;
3240 }
3241
3242 if ( t.search(/^touch/) !== -1 ) {
3243 ne = getNativeEvent( oe );
3244 t = ne.touches;
3245 ct = ne.changedTouches;
3246 touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined );
3247
3248 if ( touch ) {
3249 for ( j = 0, len = touchEventProps.length; j < len; j++) {
3250 prop = touchEventProps[ j ];
3251 event[ prop ] = touch[ prop ];
3252 }
3253 }
3254 }
3255
3256 return event;
3257}
3258
3259function getVirtualBindingFlags( element ) {
3260
3261 var flags = {},
3262 b, k;
3263
3264 while ( element ) {
3265
3266 b = $.data( element, dataPropertyName );
3267
3268 for ( k in b ) {
3269 if ( b[ k ] ) {
3270 flags[ k ] = flags.hasVirtualBinding = true;
3271 }
3272 }
3273 element = element.parentNode;
3274 }
3275 return flags;
3276}
3277
3278function getClosestElementWithVirtualBinding( element, eventType ) {
3279 var b;
3280 while ( element ) {
3281
3282 b = $.data( element, dataPropertyName );
3283
3284 if ( b && ( !eventType || b[ eventType ] ) ) {
3285 return element;
3286 }
3287 element = element.parentNode;
3288 }
3289 return null;
3290}
3291
3292function enableTouchBindings() {
3293 blockTouchTriggers = false;
3294}
3295
3296function disableTouchBindings() {
3297 blockTouchTriggers = true;
3298}
3299
3300function enableMouseBindings() {
3301 lastTouchID = 0;
3302 clickBlockList.length = 0;
3303 blockMouseTriggers = false;
3304
3305 // When mouse bindings are enabled, our
3306 // touch bindings are disabled.
3307 disableTouchBindings();
3308}
3309
3310function disableMouseBindings() {
3311 // When mouse bindings are disabled, our
3312 // touch bindings are enabled.
3313 enableTouchBindings();
3314}
3315
3316function startResetTimer() {
3317 clearResetTimer();
3318 resetTimerID = setTimeout( function() {
3319 resetTimerID = 0;
3320 enableMouseBindings();
3321 }, $.vmouse.resetTimerDuration );
3322}
3323
3324function clearResetTimer() {
3325 if ( resetTimerID ) {
3326 clearTimeout( resetTimerID );
3327 resetTimerID = 0;
3328 }
3329}
3330
3331function triggerVirtualEvent( eventType, event, flags ) {
3332 var ve;
3333
3334 if ( ( flags && flags[ eventType ] ) ||
3335 ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
3336
3337 ve = createVirtualEvent( event, eventType );
3338
3339 $( event.target).trigger( ve );
3340 }
3341
3342 return ve;
3343}
3344
3345function mouseEventCallback( event ) {
3346 var touchID = $.data( event.target, touchTargetPropertyName ),
3347 ve;
3348
3349 if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) {
3350 ve = triggerVirtualEvent( "v" + event.type, event );
3351 if ( ve ) {
3352 if ( ve.isDefaultPrevented() ) {
3353 event.preventDefault();
3354 }
3355 if ( ve.isPropagationStopped() ) {
3356 event.stopPropagation();
3357 }
3358 if ( ve.isImmediatePropagationStopped() ) {
3359 event.stopImmediatePropagation();
3360 }
3361 }
3362 }
3363}
3364
3365function handleTouchStart( event ) {
3366
3367 var touches = getNativeEvent( event ).touches,
3368 target, flags, t;
3369
3370 if ( touches && touches.length === 1 ) {
3371
3372 target = event.target;
3373 flags = getVirtualBindingFlags( target );
3374
3375 if ( flags.hasVirtualBinding ) {
3376
3377 lastTouchID = nextTouchID++;
3378 $.data( target, touchTargetPropertyName, lastTouchID );
3379
3380 clearResetTimer();
3381
3382 disableMouseBindings();
3383 didScroll = false;
3384
3385 t = getNativeEvent( event ).touches[ 0 ];
3386 startX = t.pageX;
3387 startY = t.pageY;
3388
3389 triggerVirtualEvent( "vmouseover", event, flags );
3390 triggerVirtualEvent( "vmousedown", event, flags );
3391 }
3392 }
3393}
3394
3395function handleScroll( event ) {
3396 if ( blockTouchTriggers ) {
3397 return;
3398 }
3399
3400 if ( !didScroll ) {
3401 triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
3402 }
3403
3404 didScroll = true;
3405 startResetTimer();
3406}
3407
3408function handleTouchMove( event ) {
3409 if ( blockTouchTriggers ) {
3410 return;
3411 }
3412
3413 var t = getNativeEvent( event ).touches[ 0 ],
3414 didCancel = didScroll,
3415 moveThreshold = $.vmouse.moveDistanceThreshold,
3416 flags = getVirtualBindingFlags( event.target );
3417
3418 didScroll = didScroll ||
3419 ( Math.abs( t.pageX - startX ) > moveThreshold ||
3420 Math.abs( t.pageY - startY ) > moveThreshold );
3421
3422 if ( didScroll && !didCancel ) {
3423 triggerVirtualEvent( "vmousecancel", event, flags );
3424 }
3425
3426 triggerVirtualEvent( "vmousemove", event, flags );
3427 startResetTimer();
3428}
3429
3430function handleTouchEnd( event ) {
3431 if ( blockTouchTriggers ) {
3432 return;
3433 }
3434
3435 disableTouchBindings();
3436
3437 var flags = getVirtualBindingFlags( event.target ),
3438 ve, t;
3439 triggerVirtualEvent( "vmouseup", event, flags );
3440
3441 if ( !didScroll ) {
3442 ve = triggerVirtualEvent( "vclick", event, flags );
3443 if ( ve && ve.isDefaultPrevented() ) {
3444 // The target of the mouse events that follow the touchend
3445 // event don't necessarily match the target used during the
3446 // touch. This means we need to rely on coordinates for blocking
3447 // any click that is generated.
3448 t = getNativeEvent( event ).changedTouches[ 0 ];
3449 clickBlockList.push({
3450 touchID: lastTouchID,
3451 x: t.clientX,
3452 y: t.clientY
3453 });
3454
3455 // Prevent any mouse events that follow from triggering
3456 // virtual event notifications.
3457 blockMouseTriggers = true;
3458 }
3459 }
3460 triggerVirtualEvent( "vmouseout", event, flags);
3461 didScroll = false;
3462
3463 startResetTimer();
3464}
3465
3466function hasVirtualBindings( ele ) {
3467 var bindings = $.data( ele, dataPropertyName ),
3468 k;
3469
3470 if ( bindings ) {
3471 for ( k in bindings ) {
3472 if ( bindings[ k ] ) {
3473 return true;
3474 }
3475 }
3476 }
3477 return false;
3478}
3479
3480function dummyMouseHandler() {}
3481
3482function getSpecialEventObject( eventType ) {
3483 var realType = eventType.substr( 1 );
3484
3485 return {
3486 setup: function(/* data, namespace */) {
3487 // If this is the first virtual mouse binding for this element,
3488 // add a bindings object to its data.
3489
3490 if ( !hasVirtualBindings( this ) ) {
3491 $.data( this, dataPropertyName, {} );
3492 }
3493
3494 // If setup is called, we know it is the first binding for this
3495 // eventType, so initialize the count for the eventType to zero.
3496 var bindings = $.data( this, dataPropertyName );
3497 bindings[ eventType ] = true;
3498
3499 // If this is the first virtual mouse event for this type,
3500 // register a global handler on the document.
3501
3502 activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;
3503
3504 if ( activeDocHandlers[ eventType ] === 1 ) {
3505 $document.bind( realType, mouseEventCallback );
3506 }
3507
3508 // Some browsers, like Opera Mini, won't dispatch mouse/click events
3509 // for elements unless they actually have handlers registered on them.
3510 // To get around this, we register dummy handlers on the elements.
3511
3512 $( this ).bind( realType, dummyMouseHandler );
3513
3514 // For now, if event capture is not supported, we rely on mouse handlers.
3515 if ( eventCaptureSupported ) {
3516 // If this is the first virtual mouse binding for the document,
3517 // register our touchstart handler on the document.
3518
3519 activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
3520
3521 if ( activeDocHandlers[ "touchstart" ] === 1 ) {
3522 $document.bind( "touchstart", handleTouchStart )
3523 .bind( "touchend", handleTouchEnd )
3524
3525 // On touch platforms, touching the screen and then dragging your finger
3526 // causes the window content to scroll after some distance threshold is
3527 // exceeded. On these platforms, a scroll prevents a click event from being
3528 // dispatched, and on some platforms, even the touchend is suppressed. To
3529 // mimic the suppression of the click event, we need to watch for a scroll
3530 // event. Unfortunately, some platforms like iOS don't dispatch scroll
3531 // events until *AFTER* the user lifts their finger (touchend). This means
3532 // we need to watch both scroll and touchmove events to figure out whether
3533 // or not a scroll happenens before the touchend event is fired.
3534
3535 .bind( "touchmove", handleTouchMove )
3536 .bind( "scroll", handleScroll );
3537 }
3538 }
3539 },
3540
3541 teardown: function(/* data, namespace */) {
3542 // If this is the last virtual binding for this eventType,
3543 // remove its global handler from the document.
3544
3545 --activeDocHandlers[ eventType ];
3546
3547 if ( !activeDocHandlers[ eventType ] ) {
3548 $document.unbind( realType, mouseEventCallback );
3549 }
3550
3551 if ( eventCaptureSupported ) {
3552 // If this is the last virtual mouse binding in existence,
3553 // remove our document touchstart listener.
3554
3555 --activeDocHandlers[ "touchstart" ];
3556
3557 if ( !activeDocHandlers[ "touchstart" ] ) {
3558 $document.unbind( "touchstart", handleTouchStart )
3559 .unbind( "touchmove", handleTouchMove )
3560 .unbind( "touchend", handleTouchEnd )
3561 .unbind( "scroll", handleScroll );
3562 }
3563 }
3564
3565 var $this = $( this ),
3566 bindings = $.data( this, dataPropertyName );
3567
3568 // teardown may be called when an element was
3569 // removed from the DOM. If this is the case,
3570 // jQuery core may have already stripped the element
3571 // of any data bindings so we need to check it before
3572 // using it.
3573 if ( bindings ) {
3574 bindings[ eventType ] = false;
3575 }
3576
3577 // Unregister the dummy event handler.
3578
3579 $this.unbind( realType, dummyMouseHandler );
3580
3581 // If this is the last virtual mouse binding on the
3582 // element, remove the binding data from the element.
3583
3584 if ( !hasVirtualBindings( this ) ) {
3585 $this.removeData( dataPropertyName );
3586 }
3587 }
3588 };
3589}
3590
3591// Expose our custom events to the jQuery bind/unbind mechanism.
3592
3593for ( i = 0; i < virtualEventNames.length; i++ ) {
3594 $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
3595}
3596
3597// Add a capture click handler to block clicks.
3598// Note that we require event capture support for this so if the device
3599// doesn't support it, we punt for now and rely solely on mouse events.
3600if ( eventCaptureSupported ) {
3601 document.addEventListener( "click", function( e ) {
3602 var cnt = clickBlockList.length,
3603 target = e.target,
3604 x, y, ele, i, o, touchID;
3605
3606 if ( cnt ) {
3607 x = e.clientX;
3608 y = e.clientY;
3609 threshold = $.vmouse.clickDistanceThreshold;
3610
3611 // The idea here is to run through the clickBlockList to see if
3612 // the current click event is in the proximity of one of our
3613 // vclick events that had preventDefault() called on it. If we find
3614 // one, then we block the click.
3615 //
3616 // Why do we have to rely on proximity?
3617 //
3618 // Because the target of the touch event that triggered the vclick
3619 // can be different from the target of the click event synthesized
3620 // by the browser. The target of a mouse/click event that is synthesized
3621 // from a touch event seems to be implementation specific. For example,
3622 // some browsers will fire mouse/click events for a link that is near
3623 // a touch event, even though the target of the touchstart/touchend event
3624 // says the user touched outside the link. Also, it seems that with most
3625 // browsers, the target of the mouse/click event is not calculated until the
3626 // time it is dispatched, so if you replace an element that you touched
3627 // with another element, the target of the mouse/click will be the new
3628 // element underneath that point.
3629 //
3630 // Aside from proximity, we also check to see if the target and any
3631 // of its ancestors were the ones that blocked a click. This is necessary
3632 // because of the strange mouse/click target calculation done in the
3633 // Android 2.1 browser, where if you click on an element, and there is a
3634 // mouse/click handler on one of its ancestors, the target will be the
3635 // innermost child of the touched element, even if that child is no where
3636 // near the point of touch.
3637
3638 ele = target;
3639
3640 while ( ele ) {
3641 for ( i = 0; i < cnt; i++ ) {
3642 o = clickBlockList[ i ];
3643 touchID = 0;
3644
3645 if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
3646 $.data( ele, touchTargetPropertyName ) === o.touchID ) {
3647 // XXX: We may want to consider removing matches from the block list
3648 // instead of waiting for the reset timer to fire.
3649 e.preventDefault();
3650 e.stopPropagation();
3651 return;
3652 }
3653 }
3654 ele = ele.parentNode;
3655 }
3656 }
3657 }, true);
3658}
3659})( jQuery, window, document );
3660
3661
3662(function( $, window, undefined ) {
3663 var $document = $( document ),
3664 supportTouch = $.mobile.support.touch,
3665 scrollEvent = "touchmove scroll",
3666 touchStartEvent = supportTouch ? "touchstart" : "mousedown",
3667 touchStopEvent = supportTouch ? "touchend" : "mouseup",
3668 touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
3669
3670 // setup new event shortcuts
3671 $.each( ( "touchstart touchmove touchend " +
3672 "tap taphold " +
3673 "swipe swipeleft swiperight " +
3674 "scrollstart scrollstop" ).split( " " ), function( i, name ) {
3675
3676 $.fn[ name ] = function( fn ) {
3677 return fn ? this.bind( name, fn ) : this.trigger( name );
3678 };
3679
3680 // jQuery < 1.8
3681 if ( $.attrFn ) {
3682 $.attrFn[ name ] = true;
3683 }
3684 });
3685
3686 function triggerCustomEvent( obj, eventType, event ) {
3687 var originalType = event.type;
3688 event.type = eventType;
3689 $.event.dispatch.call( obj, event );
3690 event.type = originalType;
3691 }
3692
3693 // also handles scrollstop
3694 $.event.special.scrollstart = {
3695
3696 enabled: true,
3697 setup: function() {
3698
3699 var thisObject = this,
3700 $this = $( thisObject ),
3701 scrolling,
3702 timer;
3703
3704 function trigger( event, state ) {
3705 scrolling = state;
3706 triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
3707 }
3708
3709 // iPhone triggers scroll after a small delay; use touchmove instead
3710 $this.bind( scrollEvent, function( event ) {
3711
3712 if ( !$.event.special.scrollstart.enabled ) {
3713 return;
3714 }
3715
3716 if ( !scrolling ) {
3717 trigger( event, true );
3718 }
3719
3720 clearTimeout( timer );
3721 timer = setTimeout( function() {
3722 trigger( event, false );
3723 }, 50 );
3724 });
3725 },
3726 teardown: function() {
3727 $( this ).unbind( scrollEvent );
3728 }
3729 };
3730
3731 // also handles taphold
3732 $.event.special.tap = {
3733 tapholdThreshold: 750,
3734 emitTapOnTaphold: true,
3735 setup: function() {
3736 var thisObject = this,
3737 $this = $( thisObject ),
3738 isTaphold = false;
3739
3740 $this.bind( "vmousedown", function( event ) {
3741 isTaphold = false;
3742 if ( event.which && event.which !== 1 ) {
3743 return false;
3744 }
3745
3746 var origTarget = event.target,
3747 timer;
3748
3749 function clearTapTimer() {
3750 clearTimeout( timer );
3751 }
3752
3753 function clearTapHandlers() {
3754 clearTapTimer();
3755
3756 $this.unbind( "vclick", clickHandler )
3757 .unbind( "vmouseup", clearTapTimer );
3758 $document.unbind( "vmousecancel", clearTapHandlers );
3759 }
3760
3761 function clickHandler( event ) {
3762 clearTapHandlers();
3763
3764 // ONLY trigger a 'tap' event if the start target is
3765 // the same as the stop target.
3766 if ( !isTaphold && origTarget === event.target ) {
3767 triggerCustomEvent( thisObject, "tap", event );
3768 } else if ( isTaphold ) {
3769 event.stopPropagation();
3770 }
3771 }
3772
3773 $this.bind( "vmouseup", clearTapTimer )
3774 .bind( "vclick", clickHandler );
3775 $document.bind( "vmousecancel", clearTapHandlers );
3776
3777 timer = setTimeout( function() {
3778 if ( !$.event.special.tap.emitTapOnTaphold ) {
3779 isTaphold = true;
3780 }
3781 triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
3782 }, $.event.special.tap.tapholdThreshold );
3783 });
3784 },
3785 teardown: function() {
3786 $( this ).unbind( "vmousedown" ).unbind( "vclick" ).unbind( "vmouseup" );
3787 $document.unbind( "vmousecancel" );
3788 }
3789 };
3790
3791 // also handles swipeleft, swiperight
3792 $.event.special.swipe = {
3793 scrollSupressionThreshold: 30, // More than this horizontal displacement, and we will suppress scrolling.
3794
3795 durationThreshold: 1000, // More time than this, and it isn't a swipe.
3796
3797 horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this.
3798
3799 verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this.
3800
3801 start: function( event ) {
3802 var data = event.originalEvent.touches ?
3803 event.originalEvent.touches[ 0 ] : event;
3804 return {
3805 time: ( new Date() ).getTime(),
3806 coords: [ data.pageX, data.pageY ],
3807 origin: $( event.target )
3808 };
3809 },
3810
3811 stop: function( event ) {
3812 var data = event.originalEvent.touches ?
3813 event.originalEvent.touches[ 0 ] : event;
3814 return {
3815 time: ( new Date() ).getTime(),
3816 coords: [ data.pageX, data.pageY ]
3817 };
3818 },
3819
3820 handleSwipe: function( start, stop, thisObject, origTarget ) {
3821 if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
3822 Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
3823 Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {
3824 var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight";
3825
3826 triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }) );
3827 triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ) );
3828 return true;
3829 }
3830 return false;
3831
3832 },
3833
3834 setup: function() {
3835 var thisObject = this,
3836 $this = $( thisObject );
3837
3838 $this.bind( touchStartEvent, function( event ) {
3839 var stop,
3840 start = $.event.special.swipe.start( event ),
3841 origTarget = event.target,
3842 emitted = false;
3843
3844 function moveHandler( event ) {
3845 if ( !start ) {
3846 return;
3847 }
3848
3849 stop = $.event.special.swipe.stop( event );
3850 if ( !emitted ) {
3851 emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget );
3852 }
3853 // prevent scrolling
3854 if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
3855 event.preventDefault();
3856 }
3857 }
3858
3859 $this.bind( touchMoveEvent, moveHandler )
3860 .one( touchStopEvent, function() {
3861 emitted = true;
3862 $this.unbind( touchMoveEvent, moveHandler );
3863 });
3864 });
3865 },
3866
3867 teardown: function() {
3868 $( this ).unbind( touchStartEvent ).unbind( touchMoveEvent ).unbind( touchStopEvent );
3869 }
3870 };
3871 $.each({
3872 scrollstop: "scrollstart",
3873 taphold: "tap",
3874 swipeleft: "swipe",
3875 swiperight: "swipe"
3876 }, function( event, sourceEvent ) {
3877
3878 $.event.special[ event ] = {
3879 setup: function() {
3880 $( this ).bind( sourceEvent, $.noop );
3881 },
3882 teardown: function() {
3883 $( this ).unbind( sourceEvent );
3884 }
3885 };
3886 });
3887
3888})( jQuery, this );
3889
3890
3891 // throttled resize event
3892 (function( $ ) {
3893 $.event.special.throttledresize = {
3894 setup: function() {
3895 $( this ).bind( "resize", handler );
3896 },
3897 teardown: function() {
3898 $( this ).unbind( "resize", handler );
3899 }
3900 };
3901
3902 var throttle = 250,
3903 handler = function() {
3904 curr = ( new Date() ).getTime();
3905 diff = curr - lastCall;
3906
3907 if ( diff >= throttle ) {
3908
3909 lastCall = curr;
3910 $( this ).trigger( "throttledresize" );
3911
3912 } else {
3913
3914 if ( heldCall ) {
3915 clearTimeout( heldCall );
3916 }
3917
3918 // Promise a held call will still execute
3919 heldCall = setTimeout( handler, throttle - diff );
3920 }
3921 },
3922 lastCall = 0,
3923 heldCall,
3924 curr,
3925 diff;
3926 })( jQuery );
3927
3928
3929(function( $, window ) {
3930 var win = $( window ),
3931 event_name = "orientationchange",
3932 get_orientation,
3933 last_orientation,
3934 initial_orientation_is_landscape,
3935 initial_orientation_is_default,
3936 portrait_map = { "0": true, "180": true },
3937 ww, wh, landscape_threshold;
3938
3939 // It seems that some device/browser vendors use window.orientation values 0 and 180 to
3940 // denote the "default" orientation. For iOS devices, and most other smart-phones tested,
3941 // the default orientation is always "portrait", but in some Android and RIM based tablets,
3942 // the default orientation is "landscape". The following code attempts to use the window
3943 // dimensions to figure out what the current orientation is, and then makes adjustments
3944 // to the to the portrait_map if necessary, so that we can properly decode the
3945 // window.orientation value whenever get_orientation() is called.
3946 //
3947 // Note that we used to use a media query to figure out what the orientation the browser
3948 // thinks it is in:
3949 //
3950 // initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
3951 //
3952 // but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
3953 // where the browser *ALWAYS* applied the landscape media query. This bug does not
3954 // happen on iPad.
3955
3956 if ( $.support.orientation ) {
3957
3958 // Check the window width and height to figure out what the current orientation
3959 // of the device is at this moment. Note that we've initialized the portrait map
3960 // values to 0 and 180, *AND* we purposely check for landscape so that if we guess
3961 // wrong, , we default to the assumption that portrait is the default orientation.
3962 // We use a threshold check below because on some platforms like iOS, the iPhone
3963 // form-factor can report a larger width than height if the user turns on the
3964 // developer console. The actual threshold value is somewhat arbitrary, we just
3965 // need to make sure it is large enough to exclude the developer console case.
3966
3967 ww = window.innerWidth || win.width();
3968 wh = window.innerHeight || win.height();
3969 landscape_threshold = 50;
3970
3971 initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold;
3972
3973 // Now check to see if the current window.orientation is 0 or 180.
3974 initial_orientation_is_default = portrait_map[ window.orientation ];
3975
3976 // If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
3977 // if the initial orientation is portrait, but window.orientation reports 90 or -90, we
3978 // need to flip our portrait_map values because landscape is the default orientation for
3979 // this device/browser.
3980 if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) {
3981 portrait_map = { "-90": true, "90": true };
3982 }
3983 }
3984
3985 $.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, {
3986 setup: function() {
3987 // If the event is supported natively, return false so that jQuery
3988 // will bind to the event using DOM methods.
3989 if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
3990 return false;
3991 }
3992
3993 // Get the current orientation to avoid initial double-triggering.
3994 last_orientation = get_orientation();
3995
3996 // Because the orientationchange event doesn't exist, simulate the
3997 // event by testing window dimensions on resize.
3998 win.bind( "throttledresize", handler );
3999 },
4000 teardown: function() {
4001 // If the event is not supported natively, return false so that
4002 // jQuery will unbind the event using DOM methods.
4003 if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
4004 return false;
4005 }
4006
4007 // Because the orientationchange event doesn't exist, unbind the
4008 // resize event handler.
4009 win.unbind( "throttledresize", handler );
4010 },
4011 add: function( handleObj ) {
4012 // Save a reference to the bound event handler.
4013 var old_handler = handleObj.handler;
4014
4015 handleObj.handler = function( event ) {
4016 // Modify event object, adding the .orientation property.
4017 event.orientation = get_orientation();
4018
4019 // Call the originally-bound event handler and return its result.
4020 return old_handler.apply( this, arguments );
4021 };
4022 }
4023 });
4024
4025 // If the event is not supported natively, this handler will be bound to
4026 // the window resize event to simulate the orientationchange event.
4027 function handler() {
4028 // Get the current orientation.
4029 var orientation = get_orientation();
4030
4031 if ( orientation !== last_orientation ) {
4032 // The orientation has changed, so trigger the orientationchange event.
4033 last_orientation = orientation;
4034 win.trigger( event_name );
4035 }
4036 }
4037
4038 // Get the current page orientation. This method is exposed publicly, should it
4039 // be needed, as jQuery.event.special.orientationchange.orientation()
4040 $.event.special.orientationchange.orientation = get_orientation = function() {
4041 var isPortrait = true, elem = document.documentElement;
4042
4043 // prefer window orientation to the calculation based on screensize as
4044 // the actual screen resize takes place before or after the orientation change event
4045 // has been fired depending on implementation (eg android 2.3 is before, iphone after).
4046 // More testing is required to determine if a more reliable method of determining the new screensize
4047 // is possible when orientationchange is fired. (eg, use media queries + element + opacity)
4048 if ( $.support.orientation ) {
4049 // if the window orientation registers as 0 or 180 degrees report
4050 // portrait, otherwise landscape
4051 isPortrait = portrait_map[ window.orientation ];
4052 } else {
4053 isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1;
4054 }
4055
4056 return isPortrait ? "portrait" : "landscape";
4057 };
4058
4059 $.fn[ event_name ] = function( fn ) {
4060 return fn ? this.bind( event_name, fn ) : this.trigger( event_name );
4061 };
4062
4063 // jQuery < 1.8
4064 if ( $.attrFn ) {
4065 $.attrFn[ event_name ] = true;
4066 }
4067
4068}( jQuery, this ));
4069
4070
4071
4072
4073(function( $, undefined ) {
4074
4075 // existing base tag?
4076 var baseElement = $( "head" ).children( "base" ),
4077
4078 // base element management, defined depending on dynamic base tag support
4079 // TODO move to external widget
4080 base = {
4081
4082 // define base element, for use in routing asset urls that are referenced
4083 // in Ajax-requested markup
4084 element: ( baseElement.length ? baseElement :
4085 $( "<base>", { href: $.mobile.path.documentBase.hrefNoHash } ).prependTo( $( "head" ) ) ),
4086
4087 linkSelector: "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]",
4088
4089 // set the generated BASE element's href to a new page's base path
4090 set: function( href ) {
4091
4092 // we should do nothing if the user wants to manage their url base
4093 // manually
4094 if ( !$.mobile.dynamicBaseEnabled ) {
4095 return;
4096 }
4097
4098 // we should use the base tag if we can manipulate it dynamically
4099 if ( $.support.dynamicBaseTag ) {
4100 base.element.attr( "href",
4101 $.mobile.path.makeUrlAbsolute( href, $.mobile.path.documentBase ) );
4102 }
4103 },
4104
4105 rewrite: function( href, page ) {
4106 var newPath = $.mobile.path.get( href );
4107
4108 page.find( base.linkSelector ).each(function( i, link ) {
4109 var thisAttr = $( link ).is( "[href]" ) ? "href" :
4110 $( link ).is( "[src]" ) ? "src" : "action",
4111 thisUrl = $( link ).attr( thisAttr );
4112
4113 // XXX_jblas: We need to fix this so that it removes the document
4114 // base URL, and then prepends with the new page URL.
4115 // if full path exists and is same, chop it - helps IE out
4116 thisUrl = thisUrl.replace( location.protocol + "//" +
4117 location.host + location.pathname, "" );
4118
4119 if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
4120 $( link ).attr( thisAttr, newPath + thisUrl );
4121 }
4122 });
4123 },
4124
4125 // set the generated BASE element's href to a new page's base path
4126 reset: function(/* href */) {
4127 base.element.attr( "href", $.mobile.path.documentBase.hrefNoSearch );
4128 }
4129 };
4130
4131 $.mobile.base = base;
4132
4133})( jQuery );
4134
4135
4136(function( $, undefined ) {
4137$.mobile.widgets = {};
4138
4139var originalWidget = $.widget,
4140
4141 // Record the original, non-mobileinit-modified version of $.mobile.keepNative
4142 // so we can later determine whether someone has modified $.mobile.keepNative
4143 keepNativeFactoryDefault = $.mobile.keepNative;
4144
4145$.widget = (function( orig ) {
4146 return function() {
4147 var constructor = orig.apply( this, arguments ),
4148 name = constructor.prototype.widgetName;
4149
4150 constructor.initSelector = ( ( constructor.prototype.initSelector !== undefined ) ?
4151 constructor.prototype.initSelector : ":jqmData(role='" + name + "')" );
4152
4153 $.mobile.widgets[ name ] = constructor;
4154
4155 return constructor;
4156 };
4157})( $.widget );
4158
4159// Make sure $.widget still has bridge and extend methods
4160$.extend( $.widget, originalWidget );
4161
4162// For backcompat remove in 1.5
4163$.mobile.document.on( "create", function( event ) {
4164 $( event.target ).enhanceWithin();
4165});
4166
4167$.widget( "mobile.page", {
4168 options: {
4169 theme: "a",
4170 domCache: false,
4171
4172 // Deprecated in 1.4 remove in 1.5
4173 keepNativeDefault: $.mobile.keepNative,
4174
4175 // Deprecated in 1.4 remove in 1.5
4176 contentTheme: null,
4177 enhanced: false
4178 },
4179
4180 // DEPRECATED for > 1.4
4181 // TODO remove at 1.5
4182 _createWidget: function() {
4183 $.Widget.prototype._createWidget.apply( this, arguments );
4184 this._trigger( "init" );
4185 },
4186
4187 _create: function() {
4188 // If false is returned by the callbacks do not create the page
4189 if ( this._trigger( "beforecreate" ) === false ) {
4190 return false;
4191 }
4192
4193 if ( !this.options.enhanced ) {
4194 this._enhance();
4195 }
4196
4197 this._on( this.element, {
4198 pagebeforehide: "removeContainerBackground",
4199 pagebeforeshow: "_handlePageBeforeShow"
4200 });
4201
4202 this.element.enhanceWithin();
4203 // Dialog widget is deprecated in 1.4 remove this in 1.5
4204 if ( $.mobile.getAttribute( this.element[0], "role" ) === "dialog" && $.mobile.dialog ) {
4205 this.element.dialog();
4206 }
4207 },
4208
4209 _enhance: function () {
4210 var attrPrefix = "data-" + $.mobile.ns,
4211 self = this;
4212
4213 if ( this.options.role ) {
4214 this.element.attr( "data-" + $.mobile.ns + "role", this.options.role );
4215 }
4216
4217 this.element
4218 .attr( "tabindex", "0" )
4219 .addClass( "ui-page ui-page-theme-" + this.options.theme );
4220
4221 // Manipulation of content os Deprecated as of 1.4 remove in 1.5
4222 this.element.find( "[" + attrPrefix + "role='content']" ).each( function() {
4223 var $this = $( this ),
4224 theme = this.getAttribute( attrPrefix + "theme" ) || undefined;
4225 self.options.contentTheme = theme || self.options.contentTheme || ( self.options.dialog && self.options.theme ) || ( self.element.jqmData("role") === "dialog" && self.options.theme );
4226 $this.addClass( "ui-content" );
4227 if ( self.options.contentTheme ) {
4228 $this.addClass( "ui-body-" + ( self.options.contentTheme ) );
4229 }
4230 // Add ARIA role
4231 $this.attr( "role", "main" ).addClass( "ui-content" );
4232 });
4233 },
4234
4235 bindRemove: function( callback ) {
4236 var page = this.element;
4237
4238 // when dom caching is not enabled or the page is embedded bind to remove the page on hide
4239 if ( !page.data( "mobile-page" ).options.domCache &&
4240 page.is( ":jqmData(external-page='true')" ) ) {
4241
4242 // TODO use _on - that is, sort out why it doesn't work in this case
4243 page.bind( "pagehide.remove", callback || function( e, data ) {
4244
4245 //check if this is a same page transition and if so don't remove the page
4246 if( !data.samePage ){
4247 var $this = $( this ),
4248 prEvent = new $.Event( "pageremove" );
4249
4250 $this.trigger( prEvent );
4251
4252 if ( !prEvent.isDefaultPrevented() ) {
4253 $this.removeWithDependents();
4254 }
4255 }
4256 });
4257 }
4258 },
4259
4260 _setOptions: function( o ) {
4261 if ( o.theme !== undefined ) {
4262 this.element.removeClass( "ui-body-" + this.options.theme ).addClass( "ui-body-" + o.theme );
4263 }
4264
4265 if ( o.contentTheme !== undefined ) {
4266 this.element.find( "[data-" + $.mobile.ns + "='content']" ).removeClass( "ui-body-" + this.options.contentTheme )
4267 .addClass( "ui-body-" + o.contentTheme );
4268 }
4269 },
4270
4271 _handlePageBeforeShow: function(/* e */) {
4272 this.setContainerBackground();
4273 },
4274 // Deprecated in 1.4 remove in 1.5
4275 removeContainerBackground: function() {
4276 this.element.closest( ":mobile-pagecontainer" ).pagecontainer({ "theme": "none" });
4277 },
4278 // Deprecated in 1.4 remove in 1.5
4279 // set the page container background to the page theme
4280 setContainerBackground: function( theme ) {
4281 this.element.parent().pagecontainer( { "theme": theme || this.options.theme } );
4282 },
4283 // Deprecated in 1.4 remove in 1.5
4284 keepNativeSelector: function() {
4285 var options = this.options,
4286 keepNative = $.trim( options.keepNative || "" ),
4287 globalValue = $.trim( $.mobile.keepNative ),
4288 optionValue = $.trim( options.keepNativeDefault ),
4289
4290 // Check if $.mobile.keepNative has changed from the factory default
4291 newDefault = ( keepNativeFactoryDefault === globalValue ?
4292 "" : globalValue ),
4293
4294 // If $.mobile.keepNative has not changed, use options.keepNativeDefault
4295 oldDefault = ( newDefault === "" ? optionValue : "" );
4296
4297 // Concatenate keepNative selectors from all sources where the value has
4298 // changed or, if nothing has changed, return the default
4299 return ( ( keepNative ? [ keepNative ] : [] )
4300 .concat( newDefault ? [ newDefault ] : [] )
4301 .concat( oldDefault ? [ oldDefault ] : [] )
4302 .join( ", " ) );
4303 }
4304});
4305})( jQuery );
4306
4307(function( $, undefined ) {
4308
4309 $.widget( "mobile.pagecontainer", {
4310 options: {
4311 theme: "a"
4312 },
4313
4314 initSelector: false,
4315
4316 _create: function() {
4317 this.setLastScrollEnabled = true;
4318
4319 // TODO consider moving the navigation handler OUT of widget into
4320 // some other object as glue between the navigate event and the
4321 // content widget load and change methods
4322 this._on( this.window, { navigate: "_filterNavigateEvents" });
4323
4324 this._on( this.window, {
4325 // disable an scroll setting when a hashchange has been fired,
4326 // this only works because the recording of the scroll position
4327 // is delayed for 100ms after the browser might have changed the
4328 // position because of the hashchange
4329 navigate: "_disableRecordScroll",
4330
4331 // bind to scrollstop for the first page, "pagechange" won't be
4332 // fired in that case
4333 scrollstop: "_delayedRecordScroll"
4334 });
4335
4336 // TODO move from page* events to content* events
4337 this._on({ pagechange: "_afterContentChange" });
4338
4339 // handle initial hashchange from chrome :(
4340 this.window.one( "navigate", $.proxy(function() {
4341 this.setLastScrollEnabled = true;
4342 }, this));
4343 },
4344
4345 _setOptions: function( options ) {
4346 if ( options.theme !== undefined && options.theme !== "none" ) {
4347 this.element.removeClass( "ui-overlay-" + this.options.theme )
4348 .addClass( "ui-overlay-" + options.theme );
4349 } else if ( options.theme !== undefined ) {
4350 this.element.removeClass( "ui-overlay-" + this.options.theme );
4351 }
4352
4353 this._super( options );
4354 },
4355
4356 _disableRecordScroll: function() {
4357 this.setLastScrollEnabled = false;
4358 },
4359
4360 _enableRecordScroll: function() {
4361 this.setLastScrollEnabled = true;
4362 },
4363
4364 // TODO consider the name here, since it's purpose specific
4365 _afterContentChange: function() {
4366 // once the page has changed, re-enable the scroll recording
4367 this.setLastScrollEnabled = true;
4368
4369 // remove any binding that previously existed on the get scroll
4370 // which may or may not be different than the scroll element
4371 // determined for this page previously
4372 this._off( this.window, "scrollstop" );
4373
4374 // determine and bind to the current scoll element which may be the
4375 // window or in the case of touch overflow the element touch overflow
4376 this._on( this.window, { scrollstop: "_delayedRecordScroll" });
4377 },
4378
4379 _recordScroll: function() {
4380 // this barrier prevents setting the scroll value based on
4381 // the browser scrolling the window based on a hashchange
4382 if ( !this.setLastScrollEnabled ) {
4383 return;
4384 }
4385
4386 var active = this._getActiveHistory(),
4387 currentScroll, minScroll, defaultScroll;
4388
4389 if ( active ) {
4390 currentScroll = this._getScroll();
4391 minScroll = this._getMinScroll();
4392 defaultScroll = this._getDefaultScroll();
4393
4394 // Set active page's lastScroll prop. If the location we're
4395 // scrolling to is less than minScrollBack, let it go.
4396 active.lastScroll = currentScroll < minScroll ? defaultScroll : currentScroll;
4397 }
4398 },
4399
4400 _delayedRecordScroll: function() {
4401 setTimeout( $.proxy(this, "_recordScroll"), 100 );
4402 },
4403
4404 _getScroll: function() {
4405 return this.window.scrollTop();
4406 },
4407
4408 _getMinScroll: function() {
4409 return $.mobile.minScrollBack;
4410 },
4411
4412 _getDefaultScroll: function() {
4413 return $.mobile.defaultHomeScroll;
4414 },
4415
4416 _filterNavigateEvents: function( e, data ) {
4417 var url;
4418
4419 if ( e.originalEvent && e.originalEvent.isDefaultPrevented() ) {
4420 return;
4421 }
4422
4423 url = e.originalEvent.type.indexOf( "hashchange" ) > -1 ? data.state.hash : data.state.url;
4424
4425 if ( !url ) {
4426 url = this._getHash();
4427 }
4428
4429 if ( !url || url === "#" || url.indexOf( "#" + $.mobile.path.uiStateKey ) === 0 ) {
4430 url = location.href;
4431 }
4432
4433 this._handleNavigate( url, data.state );
4434 },
4435
4436 _getHash: function() {
4437 return $.mobile.path.parseLocation().hash;
4438 },
4439
4440 // TODO active page should be managed by the container (ie, it should be a property)
4441 getActivePage: function() {
4442 return this.activePage;
4443 },
4444
4445 // TODO the first page should be a property set during _create using the logic
4446 // that currently resides in init
4447 _getInitialContent: function() {
4448 return $.mobile.firstPage;
4449 },
4450
4451 // TODO each content container should have a history object
4452 _getHistory: function() {
4453 return $.mobile.navigate.history;
4454 },
4455
4456 // TODO use _getHistory
4457 _getActiveHistory: function() {
4458 return $.mobile.navigate.history.getActive();
4459 },
4460
4461 // TODO the document base should be determined at creation
4462 _getDocumentBase: function() {
4463 return $.mobile.path.documentBase;
4464 },
4465
4466 back: function() {
4467 this.go( -1 );
4468 },
4469
4470 forward: function() {
4471 this.go( 1 );
4472 },
4473
4474 go: function( steps ) {
4475
4476 //if hashlistening is enabled use native history method
4477 if ( $.mobile.hashListeningEnabled ) {
4478 window.history.go( steps );
4479 } else {
4480
4481 //we are not listening to the hash so handle history internally
4482 var activeIndex = $.mobile.navigate.history.activeIndex,
4483 index = activeIndex + parseInt( steps, 10 ),
4484 url = $.mobile.navigate.history.stack[ index ].url,
4485 direction = ( steps >= 1 )? "forward" : "back";
4486
4487 //update the history object
4488 $.mobile.navigate.history.activeIndex = index;
4489 $.mobile.navigate.history.previousIndex = activeIndex;
4490
4491 //change to the new page
4492 this.change( url, { direction: direction, changeHash: false, fromHashChange: true } );
4493 }
4494 },
4495
4496 // TODO rename _handleDestination
4497 _handleDestination: function( to ) {
4498 var history;
4499
4500 // clean the hash for comparison if it's a url
4501 if ( $.type(to) === "string" ) {
4502 to = $.mobile.path.stripHash( to );
4503 }
4504
4505 if ( to ) {
4506 history = this._getHistory();
4507
4508 // At this point, 'to' can be one of 3 things, a cached page
4509 // element from a history stack entry, an id, or site-relative /
4510 // absolute URL. If 'to' is an id, we need to resolve it against
4511 // the documentBase, not the location.href, since the hashchange
4512 // could've been the result of a forward/backward navigation
4513 // that crosses from an external page/dialog to an internal
4514 // page/dialog.
4515 //
4516 // TODO move check to history object or path object?
4517 to = !$.mobile.path.isPath( to ) ? ( $.mobile.path.makeUrlAbsolute( "#" + to, this._getDocumentBase() ) ) : to;
4518
4519 // If we're about to go to an initial URL that contains a
4520 // reference to a non-existent internal page, go to the first
4521 // page instead. We know that the initial hash refers to a
4522 // non-existent page, because the initial hash did not end
4523 // up in the initial history entry
4524 // TODO move check to history object?
4525 if ( to === $.mobile.path.makeUrlAbsolute( "#" + history.initialDst, this._getDocumentBase() ) &&
4526 history.stack.length &&
4527 history.stack[0].url !== history.initialDst.replace( $.mobile.dialogHashKey, "" ) ) {
4528 to = this._getInitialContent();
4529 }
4530 }
4531 return to || this._getInitialContent();
4532 },
4533
4534 _handleDialog: function( changePageOptions, data ) {
4535 var to, active, activeContent = this.getActivePage();
4536
4537 // If current active page is not a dialog skip the dialog and continue
4538 // in the same direction
4539 if ( activeContent && !activeContent.hasClass( "ui-dialog" ) ) {
4540 // determine if we're heading forward or backward and continue
4541 // accordingly past the current dialog
4542 if ( data.direction === "back" ) {
4543 this.back();
4544 } else {
4545 this.forward();
4546 }
4547
4548 // prevent changePage call
4549 return false;
4550 } else {
4551 // if the current active page is a dialog and we're navigating
4552 // to a dialog use the dialog objected saved in the stack
4553 to = data.pageUrl;
4554 active = this._getActiveHistory();
4555
4556 // make sure to set the role, transition and reversal
4557 // as most of this is lost by the domCache cleaning
4558 $.extend( changePageOptions, {
4559 role: active.role,
4560 transition: active.transition,
4561 reverse: data.direction === "back"
4562 });
4563 }
4564
4565 return to;
4566 },
4567
4568 _handleNavigate: function( url, data ) {
4569 //find first page via hash
4570 // TODO stripping the hash twice with handleUrl
4571 var to = $.mobile.path.stripHash( url ), history = this._getHistory(),
4572
4573 // transition is false if it's the first page, undefined
4574 // otherwise (and may be overridden by default)
4575 transition = history.stack.length === 0 ? "none" : undefined,
4576
4577 // default options for the changPage calls made after examining
4578 // the current state of the page and the hash, NOTE that the
4579 // transition is derived from the previous history entry
4580 changePageOptions = {
4581 changeHash: false,
4582 fromHashChange: true,
4583 reverse: data.direction === "back"
4584 };
4585
4586 $.extend( changePageOptions, data, {
4587 transition: ( history.getLast() || {} ).transition || transition
4588 });
4589
4590 // TODO move to _handleDestination ?
4591 // If this isn't the first page, if the current url is a dialog hash
4592 // key, and the initial destination isn't equal to the current target
4593 // page, use the special dialog handling
4594 if ( history.activeIndex > 0 &&
4595 to.indexOf( $.mobile.dialogHashKey ) > -1 &&
4596 history.initialDst !== to ) {
4597
4598 to = this._handleDialog( changePageOptions, data );
4599
4600 if ( to === false ) {
4601 return;
4602 }
4603 }
4604
4605 this._changeContent( this._handleDestination( to ), changePageOptions );
4606 },
4607
4608 _changeContent: function( to, opts ) {
4609 $.mobile.changePage( to, opts );
4610 },
4611
4612 _getBase: function() {
4613 return $.mobile.base;
4614 },
4615
4616 _getNs: function() {
4617 return $.mobile.ns;
4618 },
4619
4620 _enhance: function( content, role ) {
4621 // TODO consider supporting a custom callback, and passing in
4622 // the settings which includes the role
4623 return content.page({ role: role });
4624 },
4625
4626 _include: function( page, settings ) {
4627 // append to page and enhance
4628 page.appendTo( this.element );
4629
4630 // use the page widget to enhance
4631 this._enhance( page, settings.role );
4632
4633 // remove page on hide
4634 page.page( "bindRemove" );
4635 },
4636
4637 _find: function( absUrl ) {
4638 // TODO consider supporting a custom callback
4639 var fileUrl = this._createFileUrl( absUrl ),
4640 dataUrl = this._createDataUrl( absUrl ),
4641 page, initialContent = this._getInitialContent();
4642
4643 // Check to see if the page already exists in the DOM.
4644 // NOTE do _not_ use the :jqmData pseudo selector because parenthesis
4645 // are a valid url char and it breaks on the first occurence
4646 page = this.element
4647 .children( "[data-" + this._getNs() +"url='" + dataUrl + "']" );
4648
4649 // If we failed to find the page, check to see if the url is a
4650 // reference to an embedded page. If so, it may have been dynamically
4651 // injected by a developer, in which case it would be lacking a
4652 // data-url attribute and in need of enhancement.
4653 if ( page.length === 0 && dataUrl && !$.mobile.path.isPath( dataUrl ) ) {
4654 page = this.element.children( $.mobile.path.hashToSelector("#" + dataUrl) )
4655 .attr( "data-" + this._getNs() + "url", dataUrl )
4656 .jqmData( "url", dataUrl );
4657 }
4658
4659 // If we failed to find a page in the DOM, check the URL to see if it
4660 // refers to the first page in the application. Also check to make sure
4661 // our cached-first-page is actually in the DOM. Some user deployed
4662 // apps are pruning the first page from the DOM for various reasons.
4663 // We check for this case here because we don't want a first-page with
4664 // an id falling through to the non-existent embedded page error case.
4665 if ( page.length === 0 &&
4666 $.mobile.path.isFirstPageUrl( fileUrl ) &&
4667 initialContent &&
4668 initialContent.parent().length ) {
4669 page = $( initialContent );
4670 }
4671
4672 return page;
4673 },
4674
4675 _getLoader: function() {
4676 return $.mobile.loading();
4677 },
4678
4679 _showLoading: function( delay, theme, msg, textonly ) {
4680 // This configurable timeout allows cached pages a brief
4681 // delay to load without showing a message
4682 if ( this._loadMsg ) {
4683 return;
4684 }
4685
4686 this._loadMsg = setTimeout($.proxy(function() {
4687 this._getLoader().loader( "show", theme, msg, textonly );
4688 this._loadMsg = 0;
4689 }, this), delay );
4690 },
4691
4692 _hideLoading: function() {
4693 // Stop message show timer
4694 clearTimeout( this._loadMsg );
4695 this._loadMsg = 0;
4696
4697 // Hide loading message
4698 this._getLoader().loader( "hide" );
4699 },
4700
4701 _showError: function() {
4702 // make sure to remove the current loading message
4703 this._hideLoading();
4704
4705 // show the error message
4706 this._showLoading( 0, $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true );
4707
4708 // hide the error message after a delay
4709 // TODO configuration
4710 setTimeout( $.proxy(this, "_hideLoading"), 1500 );
4711 },
4712
4713 _parse: function( html, fileUrl ) {
4714 // TODO consider allowing customization of this method. It's very JQM specific
4715 var page, all = $( "<div></div>" );
4716
4717 //workaround to allow scripts to execute when included in page divs
4718 all.get( 0 ).innerHTML = html;
4719
4720 page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
4721
4722 //if page elem couldn't be found, create one and insert the body element's contents
4723 if ( !page.length ) {
4724 page = $( "<div data-" + this._getNs() + "role='page'>" +
4725 ( html.split( /<\/?body[^>]*>/gmi )[1] || "" ) +
4726 "</div>" );
4727 }
4728
4729 // TODO tagging a page with external to make sure that embedded pages aren't
4730 // removed by the various page handling code is bad. Having page handling code
4731 // in many places is bad. Solutions post 1.0
4732 page.attr( "data-" + this._getNs() + "url", $.mobile.path.convertUrlToDataUrl(fileUrl) )
4733 .attr( "data-" + this._getNs() + "external-page", true );
4734
4735 return page;
4736 },
4737
4738 _setLoadedTitle: function( page, html ) {
4739 //page title regexp
4740 var newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1;
4741
4742 if ( newPageTitle && !page.jqmData("title") ) {
4743 newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
4744 page.jqmData( "title", newPageTitle );
4745 }
4746 },
4747
4748 _isRewritableBaseTag: function() {
4749 return $.mobile.dynamicBaseEnabled && !$.support.dynamicBaseTag;
4750 },
4751
4752 _createDataUrl: function( absoluteUrl ) {
4753 return $.mobile.path.convertUrlToDataUrl( absoluteUrl );
4754 },
4755
4756 _createFileUrl: function( absoluteUrl ) {
4757 return $.mobile.path.getFilePath( absoluteUrl );
4758 },
4759
4760 _triggerWithDeprecated: function( name, data, page ) {
4761 var deprecatedEvent = $.Event( "page" + name ),
4762 newEvent = $.Event( this.widgetName + name );
4763
4764 // DEPRECATED
4765 // trigger the old deprecated event on the page if it's provided
4766 ( page || this.element ).trigger( deprecatedEvent, data );
4767
4768 // use the widget trigger method for the new content* event
4769 this.element.trigger( newEvent, data );
4770
4771 return {
4772 deprecatedEvent: deprecatedEvent,
4773 event: newEvent
4774 };
4775 },
4776
4777 // TODO it would be nice to split this up more but everything appears to be "one off"
4778 // or require ordering such that other bits are sprinkled in between parts that
4779 // could be abstracted out as a group
4780 _loadSuccess: function( absUrl, triggerData, settings, deferred ) {
4781 var fileUrl = this._createFileUrl( absUrl ),
4782 dataUrl = this._createDataUrl( absUrl );
4783
4784 return $.proxy(function( html, textStatus, xhr ) {
4785 //pre-parse html to check for a data-url,
4786 //use it as the new fileUrl, base path, etc
4787 var content,
4788
4789 // TODO handle dialogs again
4790 pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + this._getNs() + "role=[\"']?page[\"']?[^>]*>)" ),
4791
4792 dataUrlRegex = new RegExp( "\\bdata-" + this._getNs() + "url=[\"']?([^\"'>]*)[\"']?" );
4793
4794 // data-url must be provided for the base tag so resource requests
4795 // can be directed to the correct url. loading into a temprorary
4796 // element makes these requests immediately
4797 if ( pageElemRegex.test( html ) &&
4798 RegExp.$1 &&
4799 dataUrlRegex.test( RegExp.$1 ) &&
4800 RegExp.$1 ) {
4801 fileUrl = $.mobile.path.getFilePath( $("<div>" + RegExp.$1 + "</div>").text() );
4802 }
4803
4804 //dont update the base tag if we are prefetching
4805 if ( settings.prefetch === undefined ) {
4806 this._getBase().set( fileUrl );
4807 }
4808
4809 content = this._parse( html, fileUrl );
4810
4811 this._setLoadedTitle( content, html );
4812
4813 // Add the content reference and xhr to our triggerData.
4814 triggerData.xhr = xhr;
4815 triggerData.textStatus = textStatus;
4816
4817 // DEPRECATED
4818 triggerData.page = content;
4819
4820 triggerData.content = content;
4821
4822 // If the default behavior is prevented, stop here!
4823 // Note that it is the responsibility of the listener/handler
4824 // that called preventDefault(), to resolve/reject the
4825 // deferred object within the triggerData.
4826 if ( !this._trigger( "load", undefined, triggerData ) ) {
4827 return;
4828 }
4829
4830 // rewrite src and href attrs to use a base url if the base tag won't work
4831 if ( this._isRewritableBaseTag() && content ) {
4832 this._getBase().rewrite( fileUrl, content );
4833 }
4834
4835 this._include( content, settings );
4836
4837 // Enhancing the content may result in new dialogs/sub content being inserted
4838 // into the DOM. If the original absUrl refers to a sub-content, that is the
4839 // real content we are interested in.
4840 if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
4841 content = this.element.children( "[data-" + this._getNs() +"url='" + dataUrl + "']" );
4842 }
4843
4844 // Remove loading message.
4845 if ( settings.showLoadMsg ) {
4846 this._hideLoading();
4847 }
4848
4849 // BEGIN DEPRECATED ---------------------------------------------------
4850 // Let listeners know the content loaded successfully.
4851 this.element.trigger( "pageload" );
4852 // END DEPRECATED -----------------------------------------------------
4853
4854 deferred.resolve( absUrl, settings, content );
4855 }, this);
4856 },
4857
4858 _loadDefaults: {
4859 type: "get",
4860 data: undefined,
4861
4862 // DEPRECATED
4863 reloadPage: false,
4864
4865 reload: false,
4866
4867 // By default we rely on the role defined by the @data-role attribute.
4868 role: undefined,
4869
4870 showLoadMsg: false,
4871
4872 // This delay allows loads that pull from browser cache to
4873 // occur without showing the loading message.
4874 loadMsgDelay: 50
4875 },
4876
4877 load: function( url, options ) {
4878 // This function uses deferred notifications to let callers
4879 // know when the content is done loading, or if an error has occurred.
4880 var deferred = ( options && options.deferred ) || $.Deferred(),
4881
4882 // The default load options with overrides specified by the caller.
4883 settings = $.extend( {}, this._loadDefaults, options ),
4884
4885 // The DOM element for the content after it has been loaded.
4886 content = null,
4887
4888 // The absolute version of the URL passed into the function. This
4889 // version of the URL may contain dialog/subcontent params in it.
4890 absUrl = $.mobile.path.makeUrlAbsolute( url, this._findBaseWithDefault() ),
4891 fileUrl, dataUrl, pblEvent, triggerData;
4892
4893 // DEPRECATED reloadPage
4894 settings.reload = settings.reloadPage;
4895
4896 // If the caller provided data, and we're using "get" request,
4897 // append the data to the URL.
4898 if ( settings.data && settings.type === "get" ) {
4899 absUrl = $.mobile.path.addSearchParams( absUrl, settings.data );
4900 settings.data = undefined;
4901 }
4902
4903 // If the caller is using a "post" request, reload must be true
4904 if ( settings.data && settings.type === "post" ) {
4905 settings.reload = true;
4906 }
4907
4908 // The absolute version of the URL minus any dialog/subcontent params.
4909 // In otherwords the real URL of the content to be loaded.
4910 fileUrl = this._createFileUrl( absUrl );
4911
4912 // The version of the Url actually stored in the data-url attribute of
4913 // the content. For embedded content, it is just the id of the page. For
4914 // content within the same domain as the document base, it is the site
4915 // relative path. For cross-domain content (Phone Gap only) the entire
4916 // absolute Url is used to load the content.
4917 dataUrl = this._createDataUrl( absUrl );
4918
4919 content = this._find( absUrl );
4920
4921 // If it isn't a reference to the first content and refers to missing
4922 // embedded content reject the deferred and return
4923 if ( content.length === 0 &&
4924 $.mobile.path.isEmbeddedPage(fileUrl) &&
4925 !$.mobile.path.isFirstPageUrl(fileUrl) ) {
4926 deferred.reject( absUrl, settings );
4927 return;
4928 }
4929
4930 // Reset base to the default document base
4931 // TODO figure out why we doe this
4932 this._getBase().reset();
4933
4934 // If the content we are interested in is already in the DOM,
4935 // and the caller did not indicate that we should force a
4936 // reload of the file, we are done. Resolve the deferrred so that
4937 // users can bind to .done on the promise
4938 if ( content.length && !settings.reload ) {
4939 this._enhance( content, settings.role );
4940 deferred.resolve( absUrl, settings, content );
4941
4942 //if we are reloading the content make sure we update
4943 // the base if its not a prefetch
4944 if ( !settings.prefetch ) {
4945 this._getBase().set(url);
4946 }
4947
4948 return;
4949 }
4950
4951 triggerData = {
4952 url: url,
4953 absUrl: absUrl,
4954 dataUrl: dataUrl,
4955 deferred: deferred,
4956 options: settings
4957 };
4958
4959 // Let listeners know we're about to load content.
4960 pblEvent = this._triggerWithDeprecated( "beforeload", triggerData );
4961
4962 // If the default behavior is prevented, stop here!
4963 if ( pblEvent.deprecatedEvent.isDefaultPrevented() ||
4964 pblEvent.event.isDefaultPrevented() ) {
4965 return;
4966 }
4967
4968 if ( settings.showLoadMsg ) {
4969 this._showLoading( settings.loadMsgDelay );
4970 }
4971
4972 // Reset base to the default document base.
4973 // only reset if we are not prefetching
4974 if ( settings.prefetch === undefined ) {
4975 this._getBase().reset();
4976 }
4977
4978 if ( !( $.mobile.allowCrossDomainPages ||
4979 $.mobile.path.isSameDomain($.mobile.path.documentUrl, absUrl ) ) ) {
4980 deferred.reject( absUrl, settings );
4981 return;
4982 }
4983
4984 // Load the new content.
4985 $.ajax({
4986 url: fileUrl,
4987 type: settings.type,
4988 data: settings.data,
4989 contentType: settings.contentType,
4990 dataType: "html",
4991 success: this._loadSuccess( absUrl, triggerData, settings, deferred ),
4992 error: this._loadError( absUrl, triggerData, settings, deferred )
4993 });
4994 },
4995
4996 _loadError: function( absUrl, triggerData, settings, deferred ) {
4997 return $.proxy(function( xhr, textStatus, errorThrown ) {
4998 //set base back to current path
4999 this._getBase().set( $.mobile.path.get() );
5000
5001 // Add error info to our triggerData.
5002 triggerData.xhr = xhr;
5003 triggerData.textStatus = textStatus;
5004 triggerData.errorThrown = errorThrown;
5005
5006 // Let listeners know the page load failed.
5007 var plfEvent = this._triggerWithDeprecated( "loadfailed", triggerData );
5008
5009 // If the default behavior is prevented, stop here!
5010 // Note that it is the responsibility of the listener/handler
5011 // that called preventDefault(), to resolve/reject the
5012 // deferred object within the triggerData.
5013 if ( plfEvent.deprecatedEvent.isDefaultPrevented() ||
5014 plfEvent.event.isDefaultPrevented() ) {
5015 return;
5016 }
5017
5018 // Remove loading message.
5019 if ( settings.showLoadMsg ) {
5020 this._showError();
5021 }
5022
5023 deferred.reject( absUrl, settings );
5024 }, this);
5025 },
5026
5027 _getTransitionHandler: function( transition ) {
5028 transition = $.mobile._maybeDegradeTransition( transition );
5029
5030 //find the transition handler for the specified transition. If there
5031 //isn't one in our transitionHandlers dictionary, use the default one.
5032 //call the handler immediately to kick-off the transition.
5033 return $.mobile.transitionHandlers[ transition ] || $.mobile.defaultTransitionHandler;
5034 },
5035
5036 // TODO move into transition handlers?
5037 _triggerCssTransitionEvents: function( to, from, prefix ) {
5038 var samePage = false;
5039
5040 prefix = prefix || "";
5041
5042 // TODO decide if these events should in fact be triggered on the container
5043 if ( from ) {
5044
5045 //Check if this is a same page transition and tell the handler in page
5046 if( to[0] === from[0] ){
5047 samePage = true;
5048 }
5049
5050 //trigger before show/hide events
5051 // TODO deprecate nextPage in favor of next
5052 this._triggerWithDeprecated( prefix + "hide", { nextPage: to, samePage: samePage }, from );
5053 }
5054
5055 // TODO deprecate prevPage in favor of previous
5056 this._triggerWithDeprecated( prefix + "show", { prevPage: from || $( "" ) }, to );
5057 },
5058
5059 // TODO make private once change has been defined in the widget
5060 _cssTransition: function( to, from, options ) {
5061 var transition = options.transition,
5062 reverse = options.reverse,
5063 deferred = options.deferred,
5064 TransitionHandler,
5065 promise;
5066
5067 this._triggerCssTransitionEvents( to, from, "before" );
5068
5069 // TODO put this in a binding to events *outside* the widget
5070 this._hideLoading();
5071
5072 TransitionHandler = this._getTransitionHandler( transition );
5073
5074 promise = ( new TransitionHandler( transition, reverse, to, from ) ).transition();
5075
5076 // TODO temporary accomodation of argument deferred
5077 promise.done(function() {
5078 deferred.resolve.apply( deferred, arguments );
5079 });
5080
5081 promise.done($.proxy(function() {
5082 this._triggerCssTransitionEvents( to, from );
5083 }, this));
5084 },
5085
5086 _releaseTransitionLock: function() {
5087 //release transition lock so navigation is free again
5088 isPageTransitioning = false;
5089 if ( pageTransitionQueue.length > 0 ) {
5090 $.mobile.changePage.apply( null, pageTransitionQueue.pop() );
5091 }
5092 },
5093
5094 _removeActiveLinkClass: function( force ) {
5095 //clear out the active button state
5096 $.mobile.removeActiveLinkClass( force );
5097 },
5098
5099 _loadUrl: function( to, triggerData, settings ) {
5100 // preserve the original target as the dataUrl value will be
5101 // simplified eg, removing ui-state, and removing query params
5102 // from the hash this is so that users who want to use query
5103 // params have access to them in the event bindings for the page
5104 // life cycle See issue #5085
5105 settings.target = to;
5106 settings.deferred = $.Deferred();
5107
5108 this.load( to, settings );
5109
5110 settings.deferred.done($.proxy(function( url, options, content ) {
5111 isPageTransitioning = false;
5112
5113 // store the original absolute url so that it can be provided
5114 // to events in the triggerData of the subsequent changePage call
5115 options.absUrl = triggerData.absUrl;
5116
5117 this.transition( content, triggerData, options );
5118 }, this));
5119
5120 settings.deferred.fail($.proxy(function(/* url, options */) {
5121 this._removeActiveLinkClass( true );
5122 this._releaseTransitionLock();
5123 this._triggerWithDeprecated( "changefailed", triggerData );
5124 }, this));
5125 },
5126
5127 _triggerPageBeforeChange: function( to, triggerData, settings ) {
5128 var pbcEvent = new $.Event( "pagebeforechange" );
5129
5130 $.extend(triggerData, { toPage: to, options: settings });
5131
5132 // NOTE: preserve the original target as the dataUrl value will be
5133 // simplified eg, removing ui-state, and removing query params from
5134 // the hash this is so that users who want to use query params have
5135 // access to them in the event bindings for the page life cycle
5136 // See issue #5085
5137 if ( $.type(to) === "string" ) {
5138 // if the toPage is a string simply convert it
5139 triggerData.absUrl = $.mobile.path.makeUrlAbsolute( to, this._findBaseWithDefault() );
5140 } else {
5141 // if the toPage is a jQuery object grab the absolute url stored
5142 // in the loadPage callback where it exists
5143 triggerData.absUrl = settings.absUrl;
5144 }
5145
5146 // Let listeners know we're about to change the current page.
5147 this.element.trigger( pbcEvent, triggerData );
5148
5149 // If the default behavior is prevented, stop here!
5150 if ( pbcEvent.isDefaultPrevented() ) {
5151 return false;
5152 }
5153
5154 return true;
5155 },
5156
5157 change: function( to, options ) {
5158 // If we are in the midst of a transition, queue the current request.
5159 // We'll call changePage() once we're done with the current transition
5160 // to service the request.
5161 if ( isPageTransitioning ) {
5162 pageTransitionQueue.unshift( arguments );
5163 return;
5164 }
5165
5166 var settings = $.extend( {}, $.mobile.changePage.defaults, options ),
5167 triggerData = {};
5168
5169 // Make sure we have a fromPage.
5170 settings.fromPage = settings.fromPage || this.activePage;
5171
5172 // if the page beforechange default is prevented return early
5173 if ( !this._triggerPageBeforeChange(to, triggerData, settings) ) {
5174 return;
5175 }
5176
5177 // We allow "pagebeforechange" observers to modify the to in
5178 // the trigger data to allow for redirects. Make sure our to is
5179 // updated. We also need to re-evaluate whether it is a string,
5180 // because an object can also be replaced by a string
5181 to = triggerData.toPage;
5182
5183 // If the caller passed us a url, call loadPage()
5184 // to make sure it is loaded into the DOM. We'll listen
5185 // to the promise object it returns so we know when
5186 // it is done loading or if an error ocurred.
5187 if ( $.type(to) === "string" ) {
5188 // Set the isPageTransitioning flag to prevent any requests from
5189 // entering this method while we are in the midst of loading a page
5190 // or transitioning.
5191 isPageTransitioning = true;
5192
5193 this._loadUrl( to, triggerData, settings );
5194 } else {
5195 this.transition( to, triggerData, settings );
5196 }
5197 },
5198
5199 transition: function( toPage, triggerData, settings ) {
5200 var fromPage, url, pageUrl, fileUrl,
5201 active, activeIsInitialPage,
5202 historyDir, pageTitle, isDialog,
5203 alreadyThere, newPageTitle,
5204 params, cssTransitionDeferred,
5205 beforeTransition;
5206
5207 // If we are in the midst of a transition, queue the current request.
5208 // We'll call changePage() once we're done with the current transition
5209 // to service the request.
5210 if ( isPageTransitioning ) {
5211 // make sure to only queue the to and settings values so the arguments
5212 // work with a call to the change method
5213 pageTransitionQueue.unshift( [toPage, settings] );
5214 return;
5215 }
5216
5217 // DEPRECATED - this call only, in favor of the before transition
5218 // if the page beforechange default is prevented return early
5219 if ( !this._triggerPageBeforeChange(toPage, triggerData, settings) ) {
5220 return;
5221 }
5222
5223 // if the (content|page)beforetransition default is prevented return early
5224 // Note, we have to check for both the deprecated and new events
5225 beforeTransition = this._triggerWithDeprecated( "beforetransition", triggerData );
5226 if (beforeTransition.deprecatedEvent.isDefaultPrevented() ||
5227 beforeTransition.event.isDefaultPrevented() ) {
5228 return;
5229 }
5230
5231 // Set the isPageTransitioning flag to prevent any requests from
5232 // entering this method while we are in the midst of loading a page
5233 // or transitioning.
5234 isPageTransitioning = true;
5235
5236 // If we are going to the first-page of the application, we need to make
5237 // sure settings.dataUrl is set to the application document url. This allows
5238 // us to avoid generating a document url with an id hash in the case where the
5239 // first-page of the document has an id attribute specified.
5240 if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
5241 settings.dataUrl = $.mobile.path.documentUrl.hrefNoHash;
5242 }
5243
5244 // The caller passed us a real page DOM element. Update our
5245 // internal state and then trigger a transition to the page.
5246 fromPage = settings.fromPage;
5247 url = ( settings.dataUrl && $.mobile.path.convertUrlToDataUrl(settings.dataUrl) ) ||
5248 toPage.jqmData( "url" );
5249
5250 // The pageUrl var is usually the same as url, except when url is obscured
5251 // as a dialog url. pageUrl always contains the file path
5252 pageUrl = url;
5253 fileUrl = $.mobile.path.getFilePath( url );
5254 active = $.mobile.navigate.history.getActive();
5255 activeIsInitialPage = $.mobile.navigate.history.activeIndex === 0;
5256 historyDir = 0;
5257 pageTitle = document.title;
5258 isDialog = ( settings.role === "dialog" ||
5259 toPage.jqmData( "role" ) === "dialog" ) &&
5260 toPage.jqmData( "dialog" ) !== true;
5261
5262 // By default, we prevent changePage requests when the fromPage and toPage
5263 // are the same element, but folks that generate content
5264 // manually/dynamically and reuse pages want to be able to transition to
5265 // the same page. To allow this, they will need to change the default
5266 // value of allowSamePageTransition to true, *OR*, pass it in as an
5267 // option when they manually call changePage(). It should be noted that
5268 // our default transition animations assume that the formPage and toPage
5269 // are different elements, so they may behave unexpectedly. It is up to
5270 // the developer that turns on the allowSamePageTransitiona option to
5271 // either turn off transition animations, or make sure that an appropriate
5272 // animation transition is used.
5273 if ( fromPage && fromPage[0] === toPage[0] &&
5274 !settings.allowSamePageTransition ) {
5275
5276 isPageTransitioning = false;
5277 this._triggerWithDeprecated( "transition", triggerData );
5278 this.element.trigger( "pagechange", triggerData );
5279
5280 // Even if there is no page change to be done, we should keep the
5281 // urlHistory in sync with the hash changes
5282 if ( settings.fromHashChange ) {
5283 $.mobile.navigate.history.direct({ url: url });
5284 }
5285
5286 return;
5287 }
5288
5289 // We need to make sure the page we are given has already been enhanced.
5290 toPage.page({ role: settings.role });
5291
5292 // If the changePage request was sent from a hashChange event, check to
5293 // see if the page is already within the urlHistory stack. If so, we'll
5294 // assume the user hit the forward/back button and will try to match the
5295 // transition accordingly.
5296 if ( settings.fromHashChange ) {
5297 historyDir = settings.direction === "back" ? -1 : 1;
5298 }
5299
5300 // Kill the keyboard.
5301 // XXX_jblas: We need to stop crawling the entire document to kill focus.
5302 // Instead, we should be tracking focus with a delegate()
5303 // handler so we already have the element in hand at this
5304 // point.
5305 // Wrap this in a try/catch block since IE9 throw "Unspecified error" if
5306 // document.activeElement is undefined when we are in an IFrame.
5307 try {
5308 if ( document.activeElement &&
5309 document.activeElement.nodeName.toLowerCase() !== "body" ) {
5310
5311 $( document.activeElement ).blur();
5312 } else {
5313 $( "input:focus, textarea:focus, select:focus" ).blur();
5314 }
5315 } catch( e ) {}
5316
5317 // Record whether we are at a place in history where a dialog used to be -
5318 // if so, do not add a new history entry and do not change the hash either
5319 alreadyThere = false;
5320
5321 // If we're displaying the page as a dialog, we don't want the url
5322 // for the dialog content to be used in the hash. Instead, we want
5323 // to append the dialogHashKey to the url of the current page.
5324 if ( isDialog && active ) {
5325 // on the initial page load active.url is undefined and in that case
5326 // should be an empty string. Moving the undefined -> empty string back
5327 // into urlHistory.addNew seemed imprudent given undefined better
5328 // represents the url state
5329
5330 // If we are at a place in history that once belonged to a dialog, reuse
5331 // this state without adding to urlHistory and without modifying the
5332 // hash. However, if a dialog is already displayed at this point, and
5333 // we're about to display another dialog, then we must add another hash
5334 // and history entry on top so that one may navigate back to the
5335 // original dialog
5336 if ( active.url &&
5337 active.url.indexOf( $.mobile.dialogHashKey ) > -1 &&
5338 this.activePage &&
5339 !this.activePage.hasClass( "ui-dialog" ) &&
5340 $.mobile.navigate.history.activeIndex > 0 ) {
5341
5342 settings.changeHash = false;
5343 alreadyThere = true;
5344 }
5345
5346 // Normally, we tack on a dialog hash key, but if this is the location
5347 // of a stale dialog, we reuse the URL from the entry
5348 url = ( active.url || "" );
5349
5350 // account for absolute urls instead of just relative urls use as hashes
5351 if ( !alreadyThere && url.indexOf("#") > -1 ) {
5352 url += $.mobile.dialogHashKey;
5353 } else {
5354 url += "#" + $.mobile.dialogHashKey;
5355 }
5356
5357 // tack on another dialogHashKey if this is the same as the initial hash
5358 // this makes sure that a history entry is created for this dialog
5359 if ( $.mobile.navigate.history.activeIndex === 0 && url === $.mobile.navigate.history.initialDst ) {
5360 url += $.mobile.dialogHashKey;
5361 }
5362 }
5363
5364 // if title element wasn't found, try the page div data attr too
5365 // If this is a deep-link or a reload ( active === undefined ) then just
5366 // use pageTitle
5367 newPageTitle = ( !active ) ? pageTitle : toPage.jqmData( "title" ) ||
5368 toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).text();
5369 if ( !!newPageTitle && pageTitle === document.title ) {
5370 pageTitle = newPageTitle;
5371 }
5372 if ( !toPage.jqmData( "title" ) ) {
5373 toPage.jqmData( "title", pageTitle );
5374 }
5375
5376 // Make sure we have a transition defined.
5377 settings.transition = settings.transition ||
5378 ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) ||
5379 ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
5380
5381 //add page to history stack if it's not back or forward
5382 if ( !historyDir && alreadyThere ) {
5383 $.mobile.navigate.history.getActive().pageUrl = pageUrl;
5384 }
5385
5386 // Set the location hash.
5387 if ( url && !settings.fromHashChange ) {
5388
5389 // rebuilding the hash here since we loose it earlier on
5390 // TODO preserve the originally passed in path
5391 if ( !$.mobile.path.isPath( url ) && url.indexOf( "#" ) < 0 ) {
5392 url = "#" + url;
5393 }
5394
5395 // TODO the property names here are just silly
5396 params = {
5397 transition: settings.transition,
5398 title: pageTitle,
5399 pageUrl: pageUrl,
5400 role: settings.role
5401 };
5402
5403 if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) {
5404 $.mobile.navigate( url, params, true);
5405 } else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) {
5406 $.mobile.navigate.history.add( url, params );
5407 }
5408 }
5409
5410 //set page title
5411 document.title = pageTitle;
5412
5413 //set "toPage" as activePage deprecated in 1.4 remove in 1.5
5414 $.mobile.activePage = toPage;
5415
5416 //new way to handle activePage
5417 this.activePage = toPage;
5418
5419 // If we're navigating back in the URL history, set reverse accordingly.
5420 settings.reverse = settings.reverse || historyDir < 0;
5421
5422 cssTransitionDeferred = $.Deferred();
5423
5424 this._cssTransition(toPage, fromPage, {
5425 transition: settings.transition,
5426 reverse: settings.reverse,
5427 deferred: cssTransitionDeferred
5428 });
5429
5430 cssTransitionDeferred.done($.proxy(function( name, reverse, $to, $from, alreadyFocused ) {
5431 $.mobile.removeActiveLinkClass();
5432
5433 //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
5434 if ( settings.duplicateCachedPage ) {
5435 settings.duplicateCachedPage.remove();
5436 }
5437
5438 // despite visibility: hidden addresses issue #2965
5439 // https://github.com/jquery/jquery-mobile/issues/2965
5440 if ( !alreadyFocused ) {
5441 $.mobile.focusPage( toPage );
5442 }
5443
5444 this._releaseTransitionLock();
5445 this.element.trigger( "pagechange", triggerData );
5446 this._triggerWithDeprecated( "transition", triggerData );
5447 }, this));
5448 },
5449
5450 // determine the current base url
5451 _findBaseWithDefault: function() {
5452 var closestBase = ( this.activePage &&
5453 $.mobile.getClosestBaseUrl( this.activePage ) );
5454 return closestBase || $.mobile.path.documentBase.hrefNoHash;
5455 }
5456 });
5457
5458 // The following handlers should be bound after mobileinit has been triggered
5459 // the following deferred is resolved in the init file
5460 $.mobile.navreadyDeferred = $.Deferred();
5461
5462 //these variables make all page containers use the same queue and only navigate one at a time
5463 // queue to hold simultanious page transitions
5464 var pageTransitionQueue = [],
5465
5466 // indicates whether or not page is in process of transitioning
5467 isPageTransitioning = false;
5468
5469})( jQuery );
5470
5471(function( $, undefined ) {
5472
5473 // resolved on domready
5474 var domreadyDeferred = $.Deferred(),
5475 documentUrl = $.mobile.path.documentUrl,
5476
5477 // used to track last vclicked element to make sure its value is added to form data
5478 $lastVClicked = null;
5479
5480 /* Event Bindings - hashchange, submit, and click */
5481 function findClosestLink( ele ) {
5482 while ( ele ) {
5483 // Look for the closest element with a nodeName of "a".
5484 // Note that we are checking if we have a valid nodeName
5485 // before attempting to access it. This is because the
5486 // node we get called with could have originated from within
5487 // an embedded SVG document where some symbol instance elements
5488 // don't have nodeName defined on them, or strings are of type
5489 // SVGAnimatedString.
5490 if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
5491 break;
5492 }
5493 ele = ele.parentNode;
5494 }
5495 return ele;
5496 }
5497
5498 $.mobile.loadPage = function( url, opts ) {
5499 var container;
5500
5501 opts = opts || {};
5502 container = ( opts.pageContainer || $.mobile.pageContainer );
5503
5504 // create the deferred that will be supplied to loadPage callers
5505 // and resolved by the content widget's load method
5506 opts.deferred = $.Deferred();
5507
5508 // Preferring to allow exceptions for uninitialized opts.pageContainer
5509 // widgets so we know if we need to force init here for users
5510 container.pagecontainer( "load", url, opts );
5511
5512 // provide the deferred
5513 return opts.deferred.promise();
5514 };
5515
5516 //define vars for interal use
5517
5518 /* internal utility functions */
5519
5520 // NOTE Issue #4950 Android phonegap doesn't navigate back properly
5521 // when a full page refresh has taken place. It appears that hashchange
5522 // and replacestate history alterations work fine but we need to support
5523 // both forms of history traversal in our code that uses backward history
5524 // movement
5525 $.mobile.back = function() {
5526 var nav = window.navigator;
5527
5528 // if the setting is on and the navigator object is
5529 // available use the phonegap navigation capability
5530 if ( this.phonegapNavigationEnabled &&
5531 nav &&
5532 nav.app &&
5533 nav.app.backHistory ) {
5534 nav.app.backHistory();
5535 } else {
5536 $.mobile.pageContainer.pagecontainer( "back" );
5537 }
5538 };
5539
5540 //direct focus to the page title, or otherwise first focusable element
5541 $.mobile.focusPage = function ( page ) {
5542 var autofocus = page.find( "[autofocus]" ),
5543 pageTitle = page.find( ".ui-title:eq(0)" );
5544
5545 if ( autofocus.length ) {
5546 autofocus.focus();
5547 return;
5548 }
5549
5550 if ( pageTitle.length ) {
5551 pageTitle.focus();
5552 } else{
5553 page.focus();
5554 }
5555 };
5556
5557 // No-op implementation of transition degradation
5558 $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
5559 return transition;
5560 };
5561
5562 /* exposed $.mobile methods */
5563
5564 //animation complete callback
5565 $.fn.animationComplete = function( callback ) {
5566 if ( $.support.cssTransitions ) {
5567 return $( this ).one( "webkitAnimationEnd animationend", callback );
5568 }
5569 else{
5570 // defer execution for consistency between webkit/non webkit
5571 setTimeout( callback, 0 );
5572 return $( this );
5573 }
5574 };
5575
5576 $.mobile.changePage = function( to, options ) {
5577 $.mobile.pageContainer.pagecontainer( "change", to, options );
5578 };
5579
5580 $.mobile.changePage.defaults = {
5581 transition: undefined,
5582 reverse: false,
5583 changeHash: true,
5584 fromHashChange: false,
5585 role: undefined, // By default we rely on the role defined by the @data-role attribute.
5586 duplicateCachedPage: undefined,
5587 pageContainer: undefined,
5588 showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
5589 dataUrl: undefined,
5590 fromPage: undefined,
5591 allowSamePageTransition: false
5592 };
5593
5594 $.mobile._registerInternalEvents = function() {
5595 var getAjaxFormData = function( $form, calculateOnly ) {
5596 var url, ret = true, formData, vclickedName, method;
5597 if ( !$.mobile.ajaxEnabled ||
5598 // test that the form is, itself, ajax false
5599 $form.is( ":jqmData(ajax='false')" ) ||
5600 // test that $.mobile.ignoreContentEnabled is set and
5601 // the form or one of it's parents is ajax=false
5602 !$form.jqmHijackable().length ||
5603 $form.attr( "target" ) ) {
5604 return false;
5605 }
5606
5607 url = ( $lastVClicked && $lastVClicked.attr( "formaction" ) ) ||
5608 $form.attr( "action" );
5609 method = ( $form.attr( "method" ) || "get" ).toLowerCase();
5610
5611 // If no action is specified, browsers default to using the
5612 // URL of the document containing the form. Since we dynamically
5613 // pull in pages from external documents, the form should submit
5614 // to the URL for the source document of the page containing
5615 // the form.
5616 if ( !url ) {
5617 // Get the @data-url for the page containing the form.
5618 url = $.mobile.getClosestBaseUrl( $form );
5619
5620 // NOTE: If the method is "get", we need to strip off the query string
5621 // because it will get replaced with the new form data. See issue #5710.
5622 if ( method === "get" ) {
5623 url = $.mobile.path.parseUrl( url ).hrefNoSearch;
5624 }
5625
5626 if ( url === $.mobile.path.documentBase.hrefNoHash ) {
5627 // The url we got back matches the document base,
5628 // which means the page must be an internal/embedded page,
5629 // so default to using the actual document url as a browser
5630 // would.
5631 url = documentUrl.hrefNoSearch;
5632 }
5633 }
5634
5635 url = $.mobile.path.makeUrlAbsolute( url, $.mobile.getClosestBaseUrl( $form ) );
5636
5637 if ( ( $.mobile.path.isExternal( url ) && !$.mobile.path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) {
5638 return false;
5639 }
5640
5641 if ( !calculateOnly ) {
5642 formData = $form.serializeArray();
5643
5644 if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) {
5645 vclickedName = $lastVClicked.attr( "name" );
5646 if ( vclickedName ) {
5647 // Make sure the last clicked element is included in the form
5648 $.each( formData, function( key, value ) {
5649 if ( value.name === vclickedName ) {
5650 // Unset vclickedName - we've found it in the serialized data already
5651 vclickedName = "";
5652 return false;
5653 }
5654 });
5655 if ( vclickedName ) {
5656 formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } );
5657 }
5658 }
5659 }
5660
5661 ret = {
5662 url: url,
5663 options: {
5664 type: method,
5665 data: $.param( formData ),
5666 transition: $form.jqmData( "transition" ),
5667 reverse: $form.jqmData( "direction" ) === "reverse",
5668 reloadPage: true
5669 }
5670 };
5671 }
5672
5673 return ret;
5674 };
5675
5676 //bind to form submit events, handle with Ajax
5677 $.mobile.document.delegate( "form", "submit", function( event ) {
5678 var formData;
5679
5680 if ( !event.isDefaultPrevented() ) {
5681 formData = getAjaxFormData( $( this ) );
5682 if ( formData ) {
5683 $.mobile.changePage( formData.url, formData.options );
5684 event.preventDefault();
5685 }
5686 }
5687 });
5688
5689 //add active state on vclick
5690 $.mobile.document.bind( "vclick", function( event ) {
5691 var $btn, btnEls, target = event.target, needClosest = false;
5692 // if this isn't a left click we don't care. Its important to note
5693 // that when the virtual event is generated it will create the which attr
5694 if ( event.which > 1 || !$.mobile.linkBindingEnabled ) {
5695 return;
5696 }
5697
5698 // Record that this element was clicked, in case we need it for correct
5699 // form submission during the "submit" handler above
5700 $lastVClicked = $( target );
5701
5702 // Try to find a target element to which the active class will be applied
5703 if ( $.data( target, "mobile-button" ) ) {
5704 // If the form will not be submitted via AJAX, do not add active class
5705 if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) {
5706 return;
5707 }
5708 // We will apply the active state to this button widget - the parent
5709 // of the input that was clicked will have the associated data
5710 if ( target.parentNode ) {
5711 target = target.parentNode;
5712 }
5713 } else {
5714 target = findClosestLink( target );
5715 if ( !( target && $.mobile.path.parseUrl( target.getAttribute( "href" ) || "#" ).hash !== "#" ) ) {
5716 return;
5717 }
5718
5719 // TODO teach $.mobile.hijackable to operate on raw dom elements so the
5720 // link wrapping can be avoided
5721 if ( !$( target ).jqmHijackable().length ) {
5722 return;
5723 }
5724 }
5725
5726 // Avoid calling .closest by using the data set during .buttonMarkup()
5727 // List items have the button data in the parent of the element clicked
5728 if ( !!~target.className.indexOf( "ui-link-inherit" ) ) {
5729 if ( target.parentNode ) {
5730 btnEls = $.data( target.parentNode, "buttonElements" );
5731 }
5732 // Otherwise, look for the data on the target itself
5733 } else {
5734 btnEls = $.data( target, "buttonElements" );
5735 }
5736 // If found, grab the button's outer element
5737 if ( btnEls ) {
5738 target = btnEls.outer;
5739 } else {
5740 needClosest = true;
5741 }
5742
5743 $btn = $( target );
5744 // If the outer element wasn't found by the our heuristics, use .closest()
5745 if ( needClosest ) {
5746 $btn = $btn.closest( ".ui-btn" );
5747 }
5748
5749 if ( $btn.length > 0 &&
5750 !( $btn.hasClass( "ui-state-disabled" ||
5751
5752 // DEPRECATED as of 1.4.0 - remove after 1.4.0 release
5753 // only ui-state-disabled should be present thereafter
5754 $btn.hasClass( "ui-disabled" ) ) ) ) {
5755 $.mobile.removeActiveLinkClass( true );
5756 $.mobile.activeClickedLink = $btn;
5757 $.mobile.activeClickedLink.addClass( $.mobile.activeBtnClass );
5758 }
5759 });
5760
5761 // click routing - direct to HTTP or Ajax, accordingly
5762 $.mobile.document.bind( "click", function( event ) {
5763 if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) {
5764 return;
5765 }
5766
5767 var link = findClosestLink( event.target ),
5768 $link = $( link ),
5769
5770 //remove active link class if external (then it won't be there if you come back)
5771 httpCleanup = function() {
5772 window.setTimeout(function() { $.mobile.removeActiveLinkClass( true ); }, 200 );
5773 },
5774 baseUrl, href,
5775 useDefaultUrlHandling, isExternal,
5776 transition, reverse, role;
5777
5778 // If a button was clicked, clean up the active class added by vclick above
5779 if ( $.mobile.activeClickedLink &&
5780 $.mobile.activeClickedLink[ 0 ] === event.target.parentNode ) {
5781 httpCleanup();
5782 }
5783
5784 // If there is no link associated with the click or its not a left
5785 // click we want to ignore the click
5786 // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
5787 // can be avoided
5788 if ( !link || event.which > 1 || !$link.jqmHijackable().length ) {
5789 return;
5790 }
5791
5792 //if there's a data-rel=back attr, go back in history
5793 if ( $link.is( ":jqmData(rel='back')" ) ) {
5794 $.mobile.back();
5795 return false;
5796 }
5797
5798 baseUrl = $.mobile.getClosestBaseUrl( $link );
5799
5800 //get href, if defined, otherwise default to empty hash
5801 href = $.mobile.path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
5802
5803 //if ajax is disabled, exit early
5804 if ( !$.mobile.ajaxEnabled && !$.mobile.path.isEmbeddedPage( href ) ) {
5805 httpCleanup();
5806 //use default click handling
5807 return;
5808 }
5809
5810 // XXX_jblas: Ideally links to application pages should be specified as
5811 // an url to the application document with a hash that is either
5812 // the site relative path or id to the page. But some of the
5813 // internal code that dynamically generates sub-pages for nested
5814 // lists and select dialogs, just write a hash in the link they
5815 // create. This means the actual URL path is based on whatever
5816 // the current value of the base tag is at the time this code
5817 // is called. For now we are just assuming that any url with a
5818 // hash in it is an application page reference.
5819 if ( href.search( "#" ) !== -1 ) {
5820 href = href.replace( /[^#]*#/, "" );
5821 if ( !href ) {
5822 //link was an empty hash meant purely
5823 //for interaction, so we ignore it.
5824 event.preventDefault();
5825 return;
5826 } else if ( $.mobile.path.isPath( href ) ) {
5827 //we have apath so make it the href we want to load.
5828 href = $.mobile.path.makeUrlAbsolute( href, baseUrl );
5829 } else {
5830 //we have a simple id so use the documentUrl as its base.
5831 href = $.mobile.path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
5832 }
5833 }
5834
5835 // Should we handle this link, or let the browser deal with it?
5836 useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" );
5837
5838 // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
5839 // requests if the document doing the request was loaded via the file:// protocol.
5840 // This is usually to allow the application to "phone home" and fetch app specific
5841 // data. We normally let the browser handle external/cross-domain urls, but if the
5842 // allowCrossDomainPages option is true, we will allow cross-domain http/https
5843 // requests to go through our page loading logic.
5844
5845 //check for protocol or rel and its not an embedded page
5846 //TODO overlap in logic from isExternal, rel=external check should be
5847 // moved into more comprehensive isExternalLink
5848 isExternal = useDefaultUrlHandling || ( $.mobile.path.isExternal( href ) && !$.mobile.path.isPermittedCrossDomainRequest( documentUrl, href ) );
5849
5850 if ( isExternal ) {
5851 httpCleanup();
5852 //use default click handling
5853 return;
5854 }
5855
5856 //use ajax
5857 transition = $link.jqmData( "transition" );
5858 reverse = $link.jqmData( "direction" ) === "reverse" ||
5859 // deprecated - remove by 1.0
5860 $link.jqmData( "back" );
5861
5862 //this may need to be more specific as we use data-rel more
5863 role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
5864
5865 $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } );
5866 event.preventDefault();
5867 });
5868
5869 //prefetch pages when anchors with data-prefetch are encountered
5870 $.mobile.document.delegate( ".ui-page", "pageshow.prefetch", function() {
5871 var urls = [];
5872 $( this ).find( "a:jqmData(prefetch)" ).each(function() {
5873 var $link = $( this ),
5874 url = $link.attr( "href" );
5875
5876 if ( url && $.inArray( url, urls ) === -1 ) {
5877 urls.push( url );
5878
5879 $.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ),prefetch: true } );
5880 }
5881 });
5882 });
5883
5884 // TODO ensure that the navigate binding in the content widget happens at the right time
5885 $.mobile.pageContainer.pagecontainer();
5886
5887 //set page min-heights to be device specific
5888 $.mobile.document.bind( "pageshow", $.mobile.resetActivePageHeight );
5889 $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );
5890
5891 };//navreadyDeferred done callback
5892
5893 $( function() { domreadyDeferred.resolve(); } );
5894
5895 $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } );
5896})( jQuery );
5897
5898
5899(function( $, window, undefined ) {
5900
5901 // TODO remove direct references to $.mobile and properties, we should
5902 // favor injection with params to the constructor
5903 $.mobile.Transition = function() {
5904 this.init.apply( this, arguments );
5905 };
5906
5907 $.extend($.mobile.Transition.prototype, {
5908 toPreClass: " ui-page-pre-in",
5909
5910 init: function( name, reverse, $to, $from ) {
5911 $.extend(this, {
5912 name: name,
5913 reverse: reverse,
5914 $to: $to,
5915 $from: $from,
5916 deferred: new $.Deferred()
5917 });
5918 },
5919
5920 cleanFrom: function() {
5921 this.$from
5922 .removeClass( $.mobile.activePageClass + " out in reverse " + this.name )
5923 .height( "" );
5924 },
5925
5926 // NOTE overridden by child object prototypes, noop'd here as defaults
5927 beforeDoneIn: function() {},
5928 beforeDoneOut: function() {},
5929 beforeStartOut: function() {},
5930
5931 doneIn: function() {
5932 this.beforeDoneIn();
5933
5934 this.$to.removeClass( "out in reverse " + this.name ).height( "" );
5935
5936 this.toggleViewportClass();
5937
5938 // In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition
5939 // This ensures we jump to that spot after the fact, if we aren't there already.
5940 if ( $.mobile.window.scrollTop() !== this.toScroll ) {
5941 this.scrollPage();
5942 }
5943 if ( !this.sequential ) {
5944 this.$to.addClass( $.mobile.activePageClass );
5945 }
5946 this.deferred.resolve( this.name, this.reverse, this.$to, this.$from, true );
5947 },
5948
5949 doneOut: function( screenHeight, reverseClass, none, preventFocus ) {
5950 this.beforeDoneOut();
5951 this.startIn( screenHeight, reverseClass, none, preventFocus );
5952 },
5953
5954 hideIn: function( callback ) {
5955 // Prevent flickering in phonegap container: see comments at #4024 regarding iOS
5956 this.$to.css( "z-index", -10 );
5957 callback.call( this );
5958 this.$to.css( "z-index", "" );
5959 },
5960
5961 scrollPage: function() {
5962 // By using scrollTo instead of silentScroll, we can keep things better in order
5963 // Just to be precautios, disable scrollstart listening like silentScroll would
5964 $.event.special.scrollstart.enabled = false;
5965 //if we are hiding the url bar or the page was previously scrolled scroll to hide or return to position
5966 if ( $.mobile.hideUrlBar || this.toScroll !== $.mobile.defaultHomeScroll ) {
5967 window.scrollTo( 0, this.toScroll );
5968 }
5969
5970 // reenable scrollstart listening like silentScroll would
5971 setTimeout( function() {
5972 $.event.special.scrollstart.enabled = true;
5973 }, 150 );
5974 },
5975
5976 startIn: function( screenHeight, reverseClass, none, preventFocus ) {
5977 this.hideIn(function() {
5978 this.$to.addClass( $.mobile.activePageClass + this.toPreClass );
5979
5980 // Send focus to page as it is now display: block
5981 if ( !preventFocus ) {
5982 $.mobile.focusPage( this.$to );
5983 }
5984
5985 // Set to page height
5986 this.$to.height( screenHeight + this.toScroll );
5987
5988 if ( !none ) {
5989 this.scrollPage();
5990 }
5991 });
5992
5993 if ( !none ) {
5994 this.$to.animationComplete( $.proxy(function() {
5995 this.doneIn();
5996 }, this ));
5997 }
5998
5999 this.$to
6000 .removeClass( this.toPreClass )
6001 .addClass( this.name + " in " + reverseClass );
6002
6003 if ( none ) {
6004 this.doneIn();
6005 }
6006
6007 },
6008
6009 startOut: function( screenHeight, reverseClass, none ) {
6010 this.beforeStartOut( screenHeight, reverseClass, none );
6011
6012 // Set the from page's height and start it transitioning out
6013 // Note: setting an explicit height helps eliminate tiling in the transitions
6014 this.$from
6015 .height( screenHeight + $.mobile.window.scrollTop() )
6016 .addClass( this.name + " out" + reverseClass );
6017 },
6018
6019 toggleViewportClass: function() {
6020 $.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + this.name );
6021 },
6022
6023 transition: function() {
6024 // NOTE many of these could be calculated/recorded in the constructor, it's my
6025 // opinion that binding them as late as possible has value with regards to
6026 // better transitions with fewer bugs. Ie, it's not guaranteed that the
6027 // object will be created and transition will be run immediately after as
6028 // it is today. So we wait until transition is invoked to gather the following
6029 var reverseClass = this.reverse ? " reverse" : "",
6030 screenHeight = $.mobile.getScreenHeight(),
6031 maxTransitionOverride = $.mobile.maxTransitionWidth !== false && $.mobile.window.width() > $.mobile.maxTransitionWidth,
6032 none = !$.support.cssTransitions || !$.support.cssAnimations || maxTransitionOverride || !this.name || this.name === "none" || Math.max( $.mobile.window.scrollTop(), this.toScroll ) > $.mobile.getMaxScrollForTransition();
6033
6034 this.toScroll = $.mobile.navigate.history.getActive().lastScroll || $.mobile.defaultHomeScroll;
6035 this.toggleViewportClass();
6036
6037 if ( this.$from && !none ) {
6038 this.startOut( screenHeight, reverseClass, none );
6039 } else {
6040 this.doneOut( screenHeight, reverseClass, none, true );
6041 }
6042
6043 return this.deferred.promise();
6044 }
6045 });
6046})( jQuery, this );
6047
6048
6049(function( $ ) {
6050
6051 $.mobile.SerialTransition = function() {
6052 this.init.apply(this, arguments);
6053 };
6054
6055 $.extend($.mobile.SerialTransition.prototype, $.mobile.Transition.prototype, {
6056 sequential: true,
6057
6058 beforeDoneOut: function() {
6059 if ( this.$from ) {
6060 this.cleanFrom();
6061 }
6062 },
6063
6064 beforeStartOut: function( screenHeight, reverseClass, none ) {
6065 this.$from.animationComplete($.proxy(function() {
6066 this.doneOut( screenHeight, reverseClass, none );
6067 }, this ));
6068 }
6069 });
6070
6071})( jQuery );
6072
6073
6074(function( $ ) {
6075
6076 $.mobile.ConcurrentTransition = function() {
6077 this.init.apply(this, arguments);
6078 };
6079
6080 $.extend($.mobile.ConcurrentTransition.prototype, $.mobile.Transition.prototype, {
6081 sequential: false,
6082
6083 beforeDoneIn: function() {
6084 if ( this.$from ) {
6085 this.cleanFrom();
6086 }
6087 },
6088
6089 beforeStartOut: function( screenHeight, reverseClass, none ) {
6090 this.doneOut( screenHeight, reverseClass, none );
6091 }
6092 });
6093
6094})( jQuery );
6095
6096
6097(function( $ ) {
6098
6099 // generate the handlers from the above
6100 var defaultGetMaxScrollForTransition = function() {
6101 return $.mobile.getScreenHeight() * 3;
6102 };
6103
6104 //transition handler dictionary for 3rd party transitions
6105 $.mobile.transitionHandlers = {
6106 "sequential": $.mobile.SerialTransition,
6107 "simultaneous": $.mobile.ConcurrentTransition
6108 };
6109
6110 // Make our transition handler the public default.
6111 $.mobile.defaultTransitionHandler = $.mobile.transitionHandlers.sequential;
6112
6113 $.mobile.transitionFallbacks = {};
6114
6115 // If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified
6116 $.mobile._maybeDegradeTransition = function( transition ) {
6117 if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) {
6118 transition = $.mobile.transitionFallbacks[ transition ];
6119 }
6120
6121 return transition;
6122 };
6123
6124 // Set the getMaxScrollForTransition to default if no implementation was set by user
6125 $.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;
6126
6127})( jQuery );
6128
6129/*
6130* fallback transition for flip in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6131*/
6132
6133(function( $, window, undefined ) {
6134
6135$.mobile.transitionFallbacks.flip = "fade";
6136
6137})( jQuery, this );
6138
6139/*
6140* fallback transition for flow in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6141*/
6142
6143(function( $, window, undefined ) {
6144
6145$.mobile.transitionFallbacks.flow = "fade";
6146
6147})( jQuery, this );
6148
6149/*
6150* fallback transition for pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6151*/
6152
6153(function( $, window, undefined ) {
6154
6155$.mobile.transitionFallbacks.pop = "fade";
6156
6157})( jQuery, this );
6158
6159/*
6160* fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6161*/
6162
6163(function( $, window, undefined ) {
6164
6165// Use the simultaneous transitions handler for slide transitions
6166$.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous;
6167
6168// Set the slide transitions's fallback to "fade"
6169$.mobile.transitionFallbacks.slide = "fade";
6170
6171})( jQuery, this );
6172
6173/*
6174* fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6175*/
6176
6177(function( $, window, undefined ) {
6178
6179$.mobile.transitionFallbacks.slidedown = "fade";
6180
6181})( jQuery, this );
6182
6183/*
6184* fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6185*/
6186
6187(function( $, window, undefined ) {
6188
6189// Set the slide transitions's fallback to "fade"
6190$.mobile.transitionFallbacks.slidefade = "fade";
6191
6192})( jQuery, this );
6193
6194/*
6195* fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6196*/
6197
6198(function( $, window, undefined ) {
6199
6200$.mobile.transitionFallbacks.slideup = "fade";
6201
6202})( jQuery, this );
6203
6204/*
6205* fallback transition for turn in non-3D supporting browsers (which tend to handle complex transitions poorly in general
6206*/
6207
6208(function( $, window, undefined ) {
6209
6210$.mobile.transitionFallbacks.turn = "fade";
6211
6212})( jQuery, this );
6213
6214
6215(function( $, undefined ) {
6216
6217$.mobile.degradeInputs = {
6218 color: false,
6219 date: false,
6220 datetime: false,
6221 "datetime-local": false,
6222 email: false,
6223 month: false,
6224 number: false,
6225 range: "number",
6226 search: "text",
6227 tel: false,
6228 time: false,
6229 url: false,
6230 week: false
6231};
6232// Backcompat remove in 1.5
6233$.mobile.page.prototype.options.degradeInputs = $.mobile.degradeInputs;
6234
6235// Auto self-init widgets
6236$.mobile.degradeInputsWithin = function( target ) {
6237
6238 target = $( target );
6239
6240 // Degrade inputs to avoid poorly implemented native functionality
6241 target.find( "input" ).not( $.mobile.page.prototype.keepNativeSelector() ).each(function() {
6242 var element = $( this ),
6243 type = this.getAttribute( "type" ),
6244 optType = $.mobile.degradeInputs[ type ] || "text",
6245 html, hasType, findstr, repstr;
6246
6247 if ( $.mobile.degradeInputs[ type ] ) {
6248 html = $( "<div>" ).html( element.clone() ).html();
6249 // In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead
6250 hasType = html.indexOf( " type=" ) > -1;
6251 findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/;
6252 repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" );
6253
6254 element.replaceWith( html.replace( findstr, repstr ) );
6255 }
6256 });
6257
6258};
6259
6260})( jQuery );
6261
6262(function( $, window, undefined ) {
6263
6264$.widget( "mobile.page", $.mobile.page, {
6265 options: {
6266
6267 // Accepts left, right and none
6268 closeBtn: "left",
6269 closeBtnText: "Close",
6270 overlayTheme: "a",
6271 corners: true,
6272 dialog: false
6273 },
6274
6275 _create: function() {
6276 this._super();
6277 if ( this.options.dialog ) {
6278
6279 $.extend( this, {
6280 _inner: this.element.children(),
6281 _headerCloseButton: null
6282 });
6283
6284 if ( !this.options.enhanced ) {
6285 this._setCloseBtn( this.options.closeBtn );
6286 }
6287 }
6288 },
6289
6290 _enhance: function() {
6291 this._super();
6292
6293 // Class the markup for dialog styling and wrap interior
6294 if ( this.options.dialog ) {
6295 this.element.addClass( "ui-dialog" )
6296 .wrapInner( $( "<div/>", {
6297
6298 // ARIA role
6299 "role" : "dialog",
6300 "class" : "ui-dialog-contain ui-overlay-shadow" +
6301 ( this.options.corners ? " ui-corner-all" : "" )
6302 }));
6303 }
6304 },
6305
6306 _setOptions: function( options ) {
6307 var closeButtonLocation, closeButtonText,
6308 currentOpts = this.options;
6309
6310 if ( options.corners !== undefined ) {
6311 this._inner.toggleClass( "ui-corner-all", !!options.corners );
6312 }
6313
6314 if ( options.overlayTheme !== undefined ) {
6315 if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) {
6316 currentOpts.overlayTheme = options.overlayTheme;
6317 this._handlePageBeforeShow();
6318 }
6319 }
6320
6321 if ( options.closeBtnText !== undefined ) {
6322 closeButtonLocation = currentOpts.closeBtn;
6323 closeButtonText = options.closeBtnText;
6324 }
6325
6326 if ( options.closeBtn !== undefined ) {
6327 closeButtonLocation = options.closeBtn;
6328 }
6329
6330 if ( closeButtonLocation ) {
6331 this._setCloseBtn( closeButtonLocation, closeButtonText );
6332 }
6333
6334 this._super( options );
6335 },
6336
6337 _handlePageBeforeShow: function () {
6338 if ( this.options.overlayTheme && this.options.dialog ) {
6339 this.removeContainerBackground();
6340 this.setContainerBackground( this.options.overlayTheme );
6341 } else {
6342 this._super();
6343 }
6344 },
6345
6346 _setCloseBtn: function( location, text ) {
6347 var dst,
6348 btn = this._headerCloseButton;
6349
6350 // Sanitize value
6351 location = "left" === location ? "left" : "right" === location ? "right" : "none";
6352
6353 if ( "none" === location ) {
6354 if ( btn ) {
6355 btn.remove();
6356 btn = null;
6357 }
6358 } else if ( btn ) {
6359 btn.removeClass( "ui-btn-left ui-btn-right" ).addClass( "ui-btn-" + location );
6360 if ( text ) {
6361 btn.text( text );
6362 }
6363 } else {
6364 dst = this._inner.find( ":jqmData(role='header')" ).first();
6365 btn = $( "<a></a>", {
6366 "href": "#",
6367 "class": "ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-" + location
6368 })
6369 .attr( "data-" + $.mobile.ns + "rel", "back" )
6370 .text( text || this.options.closeBtnText || "" )
6371 .prependTo( dst );
6372 this._on( btn, { click: "close" } );
6373 }
6374
6375 this._headerCloseButton = btn;
6376 }
6377});
6378
6379})( jQuery, this );
6380
6381(function( $, window, undefined ) {
6382
6383$.widget( "mobile.dialog", {
6384 options: {
6385
6386 // Accepts left, right and none
6387 closeBtn: "left",
6388 closeBtnText: "Close",
6389 overlayTheme: "a",
6390 corners: true
6391 },
6392
6393 // Override the theme set by the page plugin on pageshow
6394 _handlePageBeforeShow: function() {
6395 this._isCloseable = true;
6396 if ( this.options.overlayTheme ) {
6397 this.element
6398 .page( "removeContainerBackground" )
6399 .page( "setContainerBackground", this.options.overlayTheme );
6400 }
6401 },
6402
6403 _handlePageBeforeHide: function() {
6404 this._isCloseable = false;
6405 },
6406
6407 // click and submit events:
6408 // - clicks and submits should use the closing transition that the dialog
6409 // opened with unless a data-transition is specified on the link/form
6410 // - if the click was on the close button, or the link has a data-rel="back"
6411 // it'll go back in history naturally
6412 _handleVClickSubmit: function( event ) {
6413 var attrs,
6414 $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" );
6415
6416 if ( $target.length && !$target.jqmData( "transition" ) ) {
6417 attrs = {};
6418 attrs[ "data-" + $.mobile.ns + "transition" ] =
6419 ( $.mobile.navigate.history.getActive() || {} )[ "transition" ] ||
6420 $.mobile.defaultDialogTransition;
6421 attrs[ "data-" + $.mobile.ns + "direction" ] = "reverse";
6422 $target.attr( attrs );
6423 }
6424 },
6425
6426 _create: function() {
6427 var elem = this.element,
6428 opts = this.options;
6429
6430 // Class the markup for dialog styling and wrap interior
6431 elem.addClass( "ui-dialog" )
6432 .wrapInner( $( "<div/>", {
6433
6434 // ARIA role
6435 "role" : "dialog",
6436 "class" : "ui-dialog-contain ui-overlay-shadow" +
6437 ( !!opts.corners ? " ui-corner-all" : "" )
6438 }));
6439
6440 $.extend( this, {
6441 _isCloseable: false,
6442 _inner: elem.children(),
6443 _headerCloseButton: null
6444 });
6445
6446 this._on( elem, {
6447 vclick: "_handleVClickSubmit",
6448 submit: "_handleVClickSubmit",
6449 pagebeforeshow: "_handlePageBeforeShow",
6450 pagebeforehide: "_handlePageBeforeHide"
6451 });
6452
6453 this._setCloseBtn( opts.closeBtn );
6454 },
6455
6456 _setOptions: function( options ) {
6457 var closeButtonLocation, closeButtonText,
6458 currentOpts = this.options;
6459
6460 if ( options.corners !== undefined ) {
6461 this._inner.toggleClass( "ui-corner-all", !!options.corners );
6462 }
6463
6464 if ( options.overlayTheme !== undefined ) {
6465 if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) {
6466 currentOpts.overlayTheme = options.overlayTheme;
6467 this._handlePageBeforeShow();
6468 }
6469 }
6470
6471 if ( options.closeBtnText !== undefined ) {
6472 closeButtonLocation = currentOpts.closeBtn;
6473 closeButtonText = options.closeBtnText;
6474 }
6475
6476 if ( options.closeBtn !== undefined ) {
6477 closeButtonLocation = options.closeBtn;
6478 }
6479
6480 if ( closeButtonLocation ) {
6481 this._setCloseBtn( closeButtonLocation, closeButtonText );
6482 }
6483
6484 this._super( options );
6485 },
6486
6487 _setCloseBtn: function( location, text ) {
6488 var dst,
6489 btn = this._headerCloseButton;
6490
6491 // Sanitize value
6492 location = "left" === location ? "left" : "right" === location ? "right" : "none";
6493
6494 if ( "none" === location ) {
6495 if ( btn ) {
6496 btn.remove();
6497 btn = null;
6498 }
6499 } else if ( btn ) {
6500 btn.removeClass( "ui-btn-left ui-btn-right" ).addClass( "ui-btn-" + location );
6501 if ( text ) {
6502 btn.text( text );
6503 }
6504 } else {
6505 dst = this._inner.find( ":jqmData(role='header')" ).first();
6506 btn = $( "<a></a>", {
6507 "role": "button",
6508 "href": "#",
6509 "class": "ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-" + location
6510 })
6511 .text( text || this.options.closeBtnText || "" )
6512 .prependTo( dst );
6513 this._on( btn, { click: "close" } );
6514 }
6515
6516 this._headerCloseButton = btn;
6517 },
6518
6519 // Close method goes back in history
6520 close: function() {
6521 var hist = $.mobile.navigate.history;
6522
6523 if ( this._isCloseable ) {
6524 this._isCloseable = false;
6525 // If the hash listening is enabled and there is at least one preceding history
6526 // entry it's ok to go back. Initial pages with the dialog hash state are an example
6527 // where the stack check is necessary
6528 if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) {
6529 $.mobile.back();
6530 } else {
6531 $.mobile.pageContainer.pagecontainer( "back" );
6532 }
6533 }
6534 }
6535});
6536
6537})( jQuery, this );
6538
6539(function( $, undefined ) {
6540
6541var rInitialLetter = /([A-Z])/g;
6542
6543$.widget( "mobile.collapsible", {
6544 options: {
6545 enhanced: false,
6546 expandCueText: null,
6547 collapseCueText: null,
6548 collapsed: true,
6549 heading: "h1,h2,h3,h4,h5,h6,legend",
6550 collapsedIcon: null,
6551 expandedIcon: null,
6552 iconpos: null,
6553 theme: null,
6554 contentTheme: null,
6555 inset: null,
6556 corners: null,
6557 mini: null
6558 },
6559
6560 _create: function() {
6561 var elem = this.element,
6562 ui = {
6563 accordion: elem
6564 .closest( ":jqmData(role='collapsible-set')" +
6565 ( $.mobile.collapsibleset ? ", :mobile-collapsibleset" : "" ) )
6566 .addClass( "ui-collapsible-set" )
6567 };
6568
6569 $.extend( this, {
6570 _ui: ui
6571 });
6572
6573 if ( this.options.enhanced ) {
6574 ui.heading = $( ".ui-collapsible-heading", this.element[ 0 ] );
6575 ui.content = ui.heading.next();
6576 ui.anchor = $( "a", ui.heading[ 0 ] ).first();
6577 ui.status = ui.anchor.children( ".ui-collapsible-heading-status" );
6578 } else {
6579 this._enhance( elem, ui );
6580 }
6581
6582 this._on( ui.heading, {
6583 "tap": function() {
6584 ui.heading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
6585 },
6586
6587 "click": function( event ) {
6588 this._handleExpandCollapse( !ui.heading.hasClass( "ui-collapsible-heading-collapsed" ) );
6589 event.preventDefault();
6590 event.stopPropagation();
6591 }
6592 });
6593 },
6594
6595 // Adjust the keys inside options for inherited values
6596 _getOptions: function( options ) {
6597 var key,
6598 accordion = this._ui.accordion,
6599 accordionWidget = this._ui.accordionWidget;
6600
6601 // Copy options
6602 options = $.extend( {}, options );
6603
6604 if ( accordion.length && !accordionWidget ) {
6605 this._ui.accordionWidget =
6606 accordionWidget = accordion.data( "mobile-collapsibleset" );
6607 }
6608
6609 for ( key in options ) {
6610
6611 // Retrieve the option value first from the options object passed in and, if
6612 // null, from the parent accordion or, if that's null too, or if there's no
6613 // parent accordion, then from the defaults.
6614 options[ key ] =
6615 ( options[ key ] != null ) ? options[ key ] :
6616 ( accordionWidget ) ? accordionWidget.options[ key ] :
6617 accordion.length ? $.mobile.getAttribute( accordion[ 0 ],
6618 key.replace( rInitialLetter, "-$1" ).toLowerCase() ):
6619 null;
6620
6621 if ( null == options[ key ] ) {
6622 options[ key ] = $.mobile.collapsible.defaults[ key ];
6623 }
6624 }
6625
6626 return options;
6627 },
6628
6629 _themeClassFromOption: function( prefix, value ) {
6630 return ( value ? ( value === "none" ? "" : prefix + value ) : "" );
6631 },
6632
6633 _enhance: function( elem, ui ) {
6634 var iconclass,
6635 opts = this._getOptions( this.options ),
6636 contentThemeClass = this._themeClassFromOption( "ui-body-", opts.contentTheme );
6637
6638 elem.addClass( "ui-collapsible " +
6639 ( opts.inset ? "ui-collapsible-inset " : "" ) +
6640 ( opts.inset && opts.corners ? "ui-corner-all " : "" ) +
6641 ( contentThemeClass ? "ui-collapsible-themed-content " : "" ) );
6642 ui.originalHeading = elem.children( this.options.heading ).first(),
6643 ui.content = elem
6644 .wrapInner( "<div " +
6645 "class='ui-collapsible-content " +
6646 contentThemeClass + "'></div>" )
6647 .children( ".ui-collapsible-content" ),
6648 ui.heading = ui.originalHeading;
6649
6650 // Replace collapsibleHeading if it's a legend
6651 if ( ui.heading.is( "legend" ) ) {
6652 ui.heading = $( "<div role='heading'>"+ ui.heading.html() +"</div>" );
6653 ui.placeholder = $( "<div><!-- placeholder for legend --></div>" ).insertBefore( ui.originalHeading );
6654 ui.originalHeading.remove();
6655 }
6656
6657 iconclass = ( opts.collapsed ? ( opts.collapsedIcon ? "ui-icon-" + opts.collapsedIcon : "" ):
6658 ( opts.expandedIcon ? "ui-icon-" + opts.expandedIcon : "" ) );
6659
6660 ui.status = $( "<span class='ui-collapsible-heading-status'></span>" );
6661 ui.anchor = ui.heading
6662 .detach()
6663 //modify markup & attributes
6664 .addClass( "ui-collapsible-heading" )
6665 .append( ui.status )
6666 .wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
6667 .find( "a" )
6668 .first()
6669 .addClass( "ui-btn " +
6670 ( iconclass ? iconclass + " " : "" ) +
6671 ( iconclass ? ( "ui-btn-icon-" +
6672 ( opts.iconpos === "right" ? "right" : "left" ) ) +
6673 " " : "" ) +
6674 this._themeClassFromOption( "ui-btn-", opts.theme ) + " " +
6675 ( opts.mini ? "ui-mini " : "" ) );
6676
6677 //drop heading in before content
6678 ui.heading.insertBefore( ui.content );
6679
6680 this._handleExpandCollapse( this.options.collapsed );
6681
6682 return ui;
6683 },
6684
6685 refresh: function() {
6686 var key, options = {};
6687
6688 for ( key in $.mobile.collapsible.defaults ) {
6689 options[ key ] = this.options[ key ];
6690 }
6691
6692 this._setOptions( options );
6693 },
6694
6695 _setOptions: function( options ) {
6696 var isCollapsed, newTheme, oldTheme, hasCorners,
6697 elem = this.element,
6698 currentOpts = this._getOptions( this.options ),
6699 ui = this._ui,
6700 anchor = ui.anchor,
6701 status = ui.status,
6702 opts = this._getOptions( options );
6703
6704 // First and foremost we need to make sure the collapsible is in the proper
6705 // state, in case somebody decided to change the collapsed option at the
6706 // same time as another option
6707 if ( options.collapsed !== undefined ) {
6708 this._handleExpandCollapse( options.collapsed );
6709 }
6710
6711 isCollapsed = elem.hasClass( "ui-collapsible-collapsed" );
6712
6713 // Only options referring to the current state need to be applied right away
6714 // It is enough to store options covering the alternate in this.options.
6715 if ( isCollapsed ) {
6716 if ( opts.expandCueText !== undefined ) {
6717 status.text( opts.expandCueText );
6718 }
6719 if ( opts.collapsedIcon !== undefined ) {
6720 if ( currentOpts.collapsedIcon ) {
6721 anchor.removeClass( "ui-icon-" + currentOpts.collapsedIcon );
6722 }
6723 if ( opts.collapsedIcon ) {
6724 anchor.addClass( "ui-icon-" + opts.collapsedIcon );
6725 }
6726 }
6727 } else {
6728 if ( opts.collapseCueText !== undefined ) {
6729 status.text( opts.collapseCueText );
6730 }
6731 if ( opts.expandedIcon !== undefined ) {
6732 if ( currentOpts.expandedIcon ) {
6733 anchor.removeClass( "ui-icon-" + currentOpts.expandedIcon );
6734 }
6735 if ( opts.expandedIcon ) {
6736 anchor.addClass( "ui-icon-" + opts.expandedIcon );
6737 }
6738 }
6739 }
6740
6741 if ( opts.iconpos !== undefined ) {
6742 anchor.removeClass( "ui-btn-icon-" + ( currentOpts.iconPos === "right" ? "right" : "left" ) );
6743 anchor.addClass( "ui-btn-icon-" + ( opts.iconPos === "right" ? "right" : "left" ) );
6744 }
6745
6746 if ( opts.theme !== undefined ) {
6747 oldTheme = this._themeClassFromOption( "ui-btn-", currentOpts.theme );
6748 newTheme = this._themeClassFromOption( "ui-btn-", opts.theme );
6749 anchor.removeClass( oldTheme ).addClass( newTheme );
6750 }
6751
6752 if ( opts.contentTheme !== undefined ) {
6753 oldTheme = this._themeClassFromOption( "ui-body-", currentOpts.theme );
6754 newTheme = this._themeClassFromOption( "ui-body-", opts.theme );
6755 ui.content.removeClass( oldTheme ).addClass( newTheme );
6756 }
6757
6758 if ( opts.inset !== undefined ) {
6759 elem.toggleClass( "ui-collapsible-inset", opts.inset );
6760 hasCorners = !!( opts.inset && ( opts.corners || currentOpts.corners ) );
6761 }
6762
6763 if ( opts.corners !== undefined ) {
6764 hasCorners = !!( opts.corners && ( opts.inset || currentOpts.inset ) );
6765 }
6766
6767 if ( hasCorners !== undefined ) {
6768 elem.toggleClass( "ui-corner-all", hasCorners );
6769 }
6770
6771 if ( opts.mini !== undefined ) {
6772 anchor.toggleClass( "ui-mini", opts.mini );
6773 }
6774
6775 this._super( options );
6776 },
6777
6778 _handleExpandCollapse: function( isCollapse ) {
6779 var opts = this._getOptions( this.options ),
6780 ui = this._ui;
6781
6782 ui.status.text( isCollapse ? opts.expandCueText : opts.collapseCueText );
6783 ui.heading
6784 .toggleClass( "ui-collapsible-heading-collapsed", isCollapse )
6785 .find( "a" ).first()
6786 .toggleClass( "ui-icon-" + opts.expandedIcon, !isCollapse )
6787
6788 // logic or cause same icon for expanded/collapsed state would remove the ui-icon-class
6789 .toggleClass( "ui-icon-" + opts.collapsedIcon, ( isCollapse || opts.expandedIcon === opts.collapsedIcon ) )
6790 .removeClass( $.mobile.activeBtnClass );
6791
6792 this.element.toggleClass( "ui-collapsible-collapsed", isCollapse );
6793 ui.content
6794 .toggleClass( "ui-collapsible-content-collapsed", isCollapse )
6795 .attr( "aria-hidden", isCollapse )
6796 .trigger( "updatelayout" );
6797 this.options.collapsed = isCollapse;
6798 this._trigger( isCollapse ? "collapse" : "expand" );
6799 },
6800
6801 expand: function() {
6802 this._handleExpandCollapse( false );
6803 },
6804
6805 collapse: function() {
6806 this._handleExpandCollapse( true );
6807 },
6808
6809 _destroy: function() {
6810 var ui = this._ui,
6811 opts = this.options;
6812
6813 if ( opts.enhanced ) {
6814 return;
6815 }
6816
6817 if ( ui.placeholder ) {
6818 ui.originalHeading.insertBefore( ui.placeholder );
6819 ui.placeholder.remove();
6820 ui.heading.remove();
6821 } else {
6822 ui.status.remove();
6823 ui.heading
6824 .removeClass( "ui-collapsible-heading ui-collapsible-heading-collapsed" )
6825 .children()
6826 .contents()
6827 .unwrap();
6828 }
6829
6830 ui.anchor.contents().unwrap();
6831 ui.content.contents().unwrap();
6832 this.element
6833 .removeClass( "ui-collapsible ui-collapsible-collapsed " +
6834 "ui-collapsible-themed-content ui-collapsible-inset ui-corner-all" );
6835 }
6836});
6837
6838// Defaults to be used by all instances of collapsible if per-instance values
6839// are unset or if nothing is specified by way of inheritance from an accordion.
6840// Note that this hash does not contain options "collapsed" or "heading",
6841// because those are not inheritable.
6842$.mobile.collapsible.defaults = {
6843 expandCueText: " click to expand contents",
6844 collapseCueText: " click to collapse contents",
6845 collapsedIcon: "plus",
6846 contentTheme: "inherit",
6847 expandedIcon: "minus",
6848 iconpos: "left",
6849 inset: true,
6850 corners: true,
6851 theme: "inherit",
6852 mini: false
6853};
6854
6855})( jQuery );
6856
6857(function( $, undefined ) {
6858
6859$.mobile.behaviors.addFirstLastClasses = {
6860 _getVisibles: function( $els, create ) {
6861 var visibles;
6862
6863 if ( create ) {
6864 visibles = $els.not( ".ui-screen-hidden" );
6865 } else {
6866 visibles = $els.filter( ":visible" );
6867 if ( visibles.length === 0 ) {
6868 visibles = $els.not( ".ui-screen-hidden" );
6869 }
6870 }
6871
6872 return visibles;
6873 },
6874
6875 _addFirstLastClasses: function( $els, $visibles, create ) {
6876 $els.removeClass( "ui-first-child ui-last-child" );
6877 $visibles.eq( 0 ).addClass( "ui-first-child" ).end().last().addClass( "ui-last-child" );
6878 if ( !create ) {
6879 this.element.trigger( "updatelayout" );
6880 }
6881 },
6882
6883 _removeFirstLastClasses: function( $els ) {
6884 $els.removeClass( "ui-first-child ui-last-child" );
6885 }
6886};
6887
6888})( jQuery );
6889
6890(function( $, undefined ) {
6891
6892var childCollapsiblesSelector = ":mobile-collapsible, " + $.mobile.collapsible.initSelector;
6893
6894$.widget( "mobile.collapsibleset", $.extend( {
6895
6896 // The initSelector is deprecated as of 1.4.0. In 1.5.0 we will use
6897 // :jqmData(role='collapsibleset') which will allow us to get rid of the line
6898 // below altogether, because the autoinit will generate such an initSelector
6899 initSelector: ":jqmData(role='collapsible-set'),:jqmData(role='collapsibleset')",
6900
6901 options: $.extend( {
6902 enhanced: false
6903 }, $.mobile.collapsible.defaults ),
6904
6905 _handleCollapsibleExpand: function( event ) {
6906 var closestCollapsible = $( event.target ).closest( ".ui-collapsible" );
6907
6908 if ( closestCollapsible.parent().is( ":mobile-collapsibleset, :jqmData(role='collapsible-set')" ) ) {
6909 closestCollapsible
6910 .siblings( ".ui-collapsible:not(.ui-collapsible-collapsed)" )
6911 .collapsible( "collapse" );
6912 }
6913 },
6914
6915 _create: function() {
6916 var elem = this.element,
6917 opts = this.options;
6918
6919 $.extend( this, {
6920 _classes: ""
6921 });
6922
6923 if ( !opts.enhanced ) {
6924 elem.addClass( "ui-collapsible-set " +
6925 this._themeClassFromOption( "ui-group-theme-", opts.theme ) + " " +
6926 ( opts.corners && opts.inset ? "ui-corner-all " : "" ) );
6927 this.element.find( $.mobile.collapsible.initSelector ).collapsible();
6928 }
6929
6930 this._on( elem, { collapsibleexpand: "_handleCollapsibleExpand" } );
6931 },
6932
6933 _themeClassFromOption: function( prefix, value ) {
6934 return ( value ? ( value === "none" ? "" : prefix + value ) : "" );
6935 },
6936
6937 _init: function() {
6938 this._refresh( true );
6939
6940 // Because the corners are handled by the collapsible itself and the default state is collapsed
6941 // That was causing https://github.com/jquery/jquery-mobile/issues/4116
6942 this.element
6943 .children( childCollapsiblesSelector )
6944 .filter( ":jqmData(collapsed='false')" )
6945 .collapsible( "expand" );
6946 },
6947
6948 _setOptions: function( options ) {
6949 var ret, hasCorners,
6950 elem = this.element,
6951 themeClass = this._themeClassFromOption( "ui-group-theme-", options.theme );
6952
6953 if ( themeClass ) {
6954 elem
6955 .removeClass( this._themeClassFromOption( "ui-group-theme-", this.options.theme ) )
6956 .addClass( themeClass );
6957 }
6958
6959 if ( options.inset !== undefined ) {
6960 hasCorners = !!( options.inset && ( options.corners || this.options.corners ) );
6961 }
6962
6963 if ( options.corners !== undefined ) {
6964 hasCorners = !!( options.corners && ( options.inset || this.options.inset ) );
6965 }
6966
6967 if ( hasCorners !== undefined ) {
6968 elem.toggleClass( "ui-corner-all", hasCorners );
6969 }
6970
6971 ret = this._super( options );
6972 this.element.children( ":mobile-collapsible" ).collapsible( "refresh" );
6973 return ret;
6974 },
6975
6976 _destroy: function() {
6977 var el = this.element;
6978
6979 this._removeFirstLastClasses( el.children( childCollapsiblesSelector ) );
6980 el
6981 .removeClass( "ui-collapsible-set ui-corner-all " +
6982 this._themeClassFromOption( "ui-group-theme-", this.options.theme ) )
6983 .children( ":mobile-collapsible" )
6984 .collapsible( "destroy" );
6985 },
6986
6987 _refresh: function( create ) {
6988 var collapsiblesInSet = this.element.children( childCollapsiblesSelector );
6989
6990 this.element.find( $.mobile.collapsible.initSelector ).not( ".ui-collapsible" ).collapsible();
6991
6992 this._addFirstLastClasses( collapsiblesInSet, this._getVisibles( collapsiblesInSet, create ), create );
6993 },
6994
6995 refresh: function() {
6996 this._refresh( false );
6997 }
6998}, $.mobile.behaviors.addFirstLastClasses ) );
6999
7000})( jQuery );
7001
7002(function( $, undefined ) {
7003
7004// Deprecated in 1.4
7005$.fn.fieldcontain = function(/* options */) {
7006 return this.addClass( "ui-field-contain" );
7007};
7008
7009})( jQuery );
7010
7011(function( $, undefined ) {
7012
7013$.fn.grid = function( options ) {
7014 return this.each(function() {
7015
7016 var $this = $( this ),
7017 o = $.extend({
7018 grid: null
7019 }, options ),
7020 $kids = $this.children(),
7021 gridCols = { solo:1, a:2, b:3, c:4, d:5 },
7022 grid = o.grid,
7023 iterator,
7024 letter;
7025
7026 if ( !grid ) {
7027 if ( $kids.length <= 5 ) {
7028 for ( letter in gridCols ) {
7029 if ( gridCols[ letter ] === $kids.length ) {
7030 grid = letter;
7031 }
7032 }
7033 } else {
7034 grid = "a";
7035 $this.addClass( "ui-grid-duo" );
7036 }
7037 }
7038 iterator = gridCols[grid];
7039
7040 $this.addClass( "ui-grid-" + grid );
7041
7042 $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" );
7043
7044 if ( iterator > 1 ) {
7045 $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" );
7046 }
7047 if ( iterator > 2 ) {
7048 $kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" );
7049 }
7050 if ( iterator > 3 ) {
7051 $kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" );
7052 }
7053 if ( iterator > 4 ) {
7054 $kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" );
7055 }
7056 });
7057};
7058})( jQuery );
7059
7060(function( $, undefined ) {
7061
7062$.widget( "mobile.navbar", {
7063 options: {
7064 iconpos: "top",
7065 grid: null
7066 },
7067
7068 _create: function() {
7069
7070 var $navbar = this.element,
7071 $navbtns = $navbar.find( "a" ),
7072 iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? this.options.iconpos : undefined;
7073
7074 $navbar.addClass( "ui-navbar" )
7075 .attr( "role", "navigation" )
7076 .find( "ul" )
7077 .jqmEnhanceable()
7078 .grid({ grid: this.options.grid });
7079
7080 $navbtns
7081 .each( function() {
7082 var icon = $.mobile.getAttribute( this, "icon" ),
7083 theme = $.mobile.getAttribute( this, "theme" ),
7084 classes = "ui-btn";
7085
7086 if ( theme ) {
7087 classes += " ui-btn-" + theme;
7088 }
7089 if ( icon ) {
7090 classes += " ui-icon-" + icon + " ui-btn-icon-" + iconpos;
7091 }
7092 $( this ).addClass( classes );
7093 });
7094
7095 $navbar.delegate( "a", "vclick", function( /* event */ ) {
7096 var activeBtn = $( this );
7097
7098 if ( !( activeBtn.hasClass( "ui-state-disabled" ) ||
7099
7100 // DEPRECATED as of 1.4.0 - remove after 1.4.0 release
7101 // only ui-state-disabled should be present thereafter
7102 activeBtn.hasClass( "ui-disabled" ) ||
7103 activeBtn.hasClass( $.mobile.activeBtnClass ) ) ) {
7104
7105 $navbtns.removeClass( $.mobile.activeBtnClass );
7106 activeBtn.addClass( $.mobile.activeBtnClass );
7107
7108 // The code below is a workaround to fix #1181
7109 $( document ).one( "pagehide", function() {
7110 activeBtn.removeClass( $.mobile.activeBtnClass );
7111 });
7112 }
7113 });
7114
7115 // Buttons in the navbar with ui-state-persist class should regain their active state before page show
7116 $navbar.closest( ".ui-page" ).bind( "pagebeforeshow", function() {
7117 $navbtns.filter( ".ui-state-persist" ).addClass( $.mobile.activeBtnClass );
7118 });
7119 }
7120});
7121
7122})( jQuery );
7123
7124(function( $, undefined ) {
7125
7126var getAttr = $.mobile.getAttribute;
7127
7128$.widget( "mobile.listview", $.extend( {
7129
7130 options: {
7131 theme: null,
7132 countTheme: null, /* Deprecated in 1.4 */
7133 dividerTheme: null,
7134 icon: "carat-r",
7135 splitIcon: "carat-r",
7136 splitTheme: null,
7137 corners: true,
7138 shadow: true,
7139 inset: false
7140 },
7141
7142 _create: function() {
7143 var t = this,
7144 listviewClasses = "";
7145
7146 listviewClasses += t.options.inset ? " ui-listview-inset" : "";
7147
7148 if ( !!t.options.inset ) {
7149 listviewClasses += t.options.corners ? " ui-corner-all" : "";
7150 listviewClasses += t.options.shadow ? " ui-shadow" : "";
7151 }
7152
7153 // create listview markup
7154 t.element.addClass( " ui-listview" + listviewClasses );
7155
7156 t.refresh( true );
7157 },
7158
7159 // TODO: Remove in 1.5
7160 _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) {
7161 var dict = {};
7162 dict[ lcName ] = dict[ ucName ] = true;
7163 while ( ele ) {
7164 if ( dict[ ele.nodeName ] ) {
7165 return ele;
7166 }
7167 ele = ele[ nextProp ];
7168 }
7169 return null;
7170 },
7171 // TODO: Remove in 1.5
7172 _addThumbClasses: function( containers ) {
7173 var i, img, len = containers.length;
7174 for ( i = 0; i < len; i++ ) {
7175 img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
7176 if ( img.length ) {
7177 $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.hasClass( "ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
7178 }
7179 }
7180 },
7181
7182 _getChildrenByTagName: function( ele, lcName, ucName ) {
7183 var results = [],
7184 dict = {};
7185 dict[ lcName ] = dict[ ucName ] = true;
7186 ele = ele.firstChild;
7187 while ( ele ) {
7188 if ( dict[ ele.nodeName ] ) {
7189 results.push( ele );
7190 }
7191 ele = ele.nextSibling;
7192 }
7193 return $( results );
7194 },
7195
7196 _beforeListviewRefresh: $.noop,
7197 _afterListviewRefresh: $.noop,
7198
7199 refresh: function( create ) {
7200 var buttonClass, pos, numli, item, itemClass, itemTheme, itemIcon, icon, a,
7201 isDivider, startCount, newStartCount, value, last, splittheme, splitThemeClass, spliticon,
7202 altButtonClass, dividerTheme, li,
7203 o = this.options,
7204 $list = this.element,
7205 ol = !!$.nodeName( $list[ 0 ], "ol" ),
7206 start = $list.attr( "start" ),
7207 itemClassDict = {},
7208 countBubbles = $list.find( ".ui-li-count" ),
7209 countTheme = getAttr( $list[ 0 ], "counttheme" ) || this.options.countTheme,
7210 countThemeClass = countTheme ? "ui-body-" + countTheme : "ui-body-inherit";
7211
7212 if ( o.theme ) {
7213 $list.addClass( "ui-group-theme-" + o.theme );
7214 }
7215
7216 // Check if a start attribute has been set while taking a value of 0 into account
7217 if ( ol && ( start || start === 0 ) ) {
7218 startCount = parseInt( start, 10 ) - 1;
7219 $list.css( "counter-reset", "listnumbering " + startCount );
7220 }
7221
7222 this._beforeListviewRefresh();
7223
7224 li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" );
7225
7226 for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
7227 item = li.eq( pos );
7228 itemClass = "";
7229
7230 if ( create || item[ 0 ].className.search( /\bui-li-static\b|\bui-li-divider\b/ ) < 0 ) {
7231 a = this._getChildrenByTagName( item[ 0 ], "a", "A" );
7232 isDivider = ( getAttr( item[ 0 ], "role" ) === "list-divider" );
7233 value = item.attr( "value" );
7234 itemTheme = getAttr( item[ 0 ], "theme" );
7235
7236 if ( a.length && a[ 0 ].className.search( /\bui-btn\b/ ) < 0 && !isDivider ) {
7237 itemIcon = getAttr( item[ 0 ], "icon" );
7238 icon = ( itemIcon === false ) ? false : ( itemIcon || o.icon );
7239
7240 // TODO: Remove in 1.5 together with links.js (links.js / .ui-link deprecated in 1.4)
7241 a.removeClass( "ui-link" );
7242
7243 buttonClass = "ui-btn";
7244
7245 if ( itemTheme ) {
7246 buttonClass += " ui-btn-" + itemTheme;
7247 }
7248
7249 if ( a.length > 1 ) {
7250 itemClass = "ui-li-has-alt";
7251
7252 last = a.last();
7253 splittheme = getAttr( last[ 0 ], "theme" ) || o.splitTheme || getAttr( item[ 0 ], "theme", true );
7254 splitThemeClass = splittheme ? " ui-btn-" + splittheme : "";
7255 spliticon = getAttr( last[ 0 ], "icon" ) || getAttr( item[ 0 ], "icon" ) || o.splitIcon;
7256 altButtonClass = "ui-btn ui-btn-icon-notext ui-icon-" + spliticon + splitThemeClass;
7257
7258 last
7259 .attr( "title", $.trim( last.getEncodedText() ) )
7260 .addClass( altButtonClass )
7261 .empty();
7262 } else if ( icon ) {
7263 buttonClass += " ui-btn-icon-right ui-icon-" + icon;
7264 }
7265
7266 a.first().addClass( buttonClass );
7267 } else if ( isDivider ) {
7268 dividerTheme = ( getAttr( item[ 0 ], "theme" ) || o.dividerTheme || o.theme );
7269
7270 itemClass = "ui-li-divider ui-bar-" + ( dividerTheme ? dividerTheme : "inherit" );
7271
7272 item.attr( "role", "heading" );
7273 } else if ( a.length <= 0 ) {
7274 itemClass = "ui-li-static ui-body-" + ( itemTheme ? itemTheme : "inherit" );
7275 }
7276 if ( ol && value ) {
7277 newStartCount = parseInt( value , 10 ) - 1;
7278
7279 item.css( "counter-reset", "listnumbering " + newStartCount );
7280 }
7281 }
7282
7283 // Instead of setting item class directly on the list item
7284 // at this point in time, push the item into a dictionary
7285 // that tells us what class to set on it so we can do this after this
7286 // processing loop is finished.
7287
7288 if ( !itemClassDict[ itemClass ] ) {
7289 itemClassDict[ itemClass ] = [];
7290 }
7291
7292 itemClassDict[ itemClass ].push( item[ 0 ] );
7293 }
7294
7295 // Set the appropriate listview item classes on each list item.
7296 // The main reason we didn't do this
7297 // in the for-loop above is because we can eliminate per-item function overhead
7298 // by calling addClass() and children() once or twice afterwards. This
7299 // can give us a significant boost on platforms like WP7.5.
7300
7301 for ( itemClass in itemClassDict ) {
7302 $( itemClassDict[ itemClass ] ).addClass( itemClass );
7303 }
7304
7305 countBubbles.each( function() {
7306 $( this ).closest( "li" ).addClass( "ui-li-has-count" );
7307 });
7308 if ( countThemeClass ) {
7309 countBubbles.addClass( countThemeClass );
7310 }
7311
7312 // Deprecated in 1.4. From 1.5 you have to add class ui-li-has-thumb or ui-li-has-icon to the LI.
7313 this._addThumbClasses( li );
7314 this._addThumbClasses( li.find( ".ui-btn" ) );
7315
7316 this._afterListviewRefresh();
7317
7318 this._addFirstLastClasses( li, this._getVisibles( li, create ), create );
7319 }
7320}, $.mobile.behaviors.addFirstLastClasses ) );
7321
7322})( jQuery );
7323
7324(function( $, undefined ) {
7325
7326function defaultAutodividersSelector( elt ) {
7327 // look for the text in the given element
7328 var text = $.trim( elt.text() ) || null;
7329
7330 if ( !text ) {
7331 return null;
7332 }
7333
7334 // create the text for the divider (first uppercased letter)
7335 text = text.slice( 0, 1 ).toUpperCase();
7336
7337 return text;
7338}
7339
7340$.widget( "mobile.listview", $.mobile.listview, {
7341 options: {
7342 autodividers: false,
7343 autodividersSelector: defaultAutodividersSelector
7344 },
7345
7346 _beforeListviewRefresh: function() {
7347 if ( this.options.autodividers ) {
7348 this._replaceDividers();
7349 this._superApply( arguments );
7350 }
7351 },
7352
7353 _replaceDividers: function() {
7354 var i, lis, li, dividerText,
7355 lastDividerText = null,
7356 list = this.element,
7357 divider;
7358
7359 list.children( "li:jqmData(role='list-divider')" ).remove();
7360
7361 lis = list.children( "li" );
7362
7363 for ( i = 0; i < lis.length ; i++ ) {
7364 li = lis[ i ];
7365 dividerText = this.options.autodividersSelector( $( li ) );
7366
7367 if ( dividerText && lastDividerText !== dividerText ) {
7368 divider = document.createElement( "li" );
7369 divider.appendChild( document.createTextNode( dividerText ) );
7370 divider.setAttribute( "data-" + $.mobile.ns + "role", "list-divider" );
7371 li.parentNode.insertBefore( divider, li );
7372 }
7373
7374 lastDividerText = dividerText;
7375 }
7376 }
7377});
7378
7379})( jQuery );
7380
7381(function( $, undefined ) {
7382
7383var rdivider = /(^|\s)ui-li-divider($|\s)/,
7384 rhidden = /(^|\s)ui-screen-hidden($|\s)/;
7385
7386$.widget( "mobile.listview", $.mobile.listview, {
7387 options: {
7388 hideDividers: false
7389 },
7390
7391 _afterListviewRefresh: function() {
7392 var items, idx, item, hideDivider = true;
7393
7394 this._superApply( arguments );
7395
7396 if ( this.options.hideDividers ) {
7397 items = this._getChildrenByTagName( this.element[ 0 ], "li", "LI" );
7398 for ( idx = items.length - 1 ; idx > -1 ; idx-- ) {
7399 item = items[ idx ];
7400 if ( item.className.match( rdivider ) ) {
7401 if ( hideDivider ) {
7402 item.className = item.className + " ui-screen-hidden";
7403 }
7404 hideDivider = true;
7405 } else {
7406 if ( !item.className.match( rhidden ) ) {
7407 hideDivider = false;
7408 }
7409 }
7410 }
7411 }
7412 }
7413});
7414
7415})( jQuery );
7416
7417(function( $, undefined ) {
7418
7419$.mobile.nojs = function( target ) {
7420 $( ":jqmData(role='nojs')", target ).addClass( "ui-nojs" );
7421};
7422
7423})( jQuery );
7424
7425(function( $, undefined ) {
7426
7427$.mobile.behaviors.formReset = {
7428 _handleFormReset: function() {
7429 this._on( this.element.closest( "form" ), {
7430 reset: function() {
7431 this._delay( "_reset" );
7432 }
7433 });
7434 }
7435};
7436
7437})( jQuery );
7438
7439/*
7440* "checkboxradio" plugin
7441*/
7442
7443(function( $, undefined ) {
7444
7445$.widget( "mobile.checkboxradio", $.extend( {
7446
7447 initSelector: "input:not( :jqmData(role='flipswitch' ) )[type='checkbox'],input[type='radio']:not( :jqmData(role='flipswitch' ))",
7448
7449 options: {
7450 theme: "inherit",
7451 mini: false,
7452 wrapperClass: null,
7453 enhanced: false,
7454 iconpos: "left"
7455
7456 },
7457 _create: function() {
7458 var input = this.element,
7459 o = this.options,
7460 inheritAttr = function( input, dataAttr ) {
7461 return input.jqmData( dataAttr ) ||
7462 input.closest( "form, fieldset" ).jqmData( dataAttr );
7463 },
7464 // NOTE: Windows Phone could not find the label through a selector
7465 // filter works though.
7466 parentLabel = input.closest( "label" ),
7467 label = parentLabel.length ? parentLabel :
7468 input
7469 .closest( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" )
7470 .find( "label" )
7471 .filter( "[for='" + $.mobile.path.hashToSelector( input[0].id ) + "']" )
7472 .first(),
7473 inputtype = input[0].type,
7474 checkedClass = "ui-" + inputtype + "-on",
7475 uncheckedClass = "ui-" + inputtype + "-off";
7476
7477 if ( inputtype !== "checkbox" && inputtype !== "radio" ) {
7478 return;
7479 }
7480
7481 if ( this.element[0].disabled ) {
7482 this.options.disabled = true;
7483 }
7484
7485 o.iconpos = inheritAttr( input, "iconpos" ) || label.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
7486
7487 // Establish options
7488 o.mini = inheritAttr( input, "mini" ) || o.mini;
7489
7490 // Expose for other methods
7491 $.extend( this, {
7492 input: input,
7493 label: label,
7494 parentLabel: parentLabel,
7495 inputtype: inputtype,
7496 checkedClass: checkedClass,
7497 uncheckedClass: uncheckedClass
7498 });
7499
7500 if ( !this.options.enhanced ) {
7501 this._enhance();
7502 }
7503
7504 this._on( label, {
7505 vmouseover: "_handleLabelVMouseOver",
7506 vclick: "_handleLabelVClick"
7507 });
7508
7509 this._on( input, {
7510 vmousedown: "_cacheVals",
7511 vclick: "_handleInputVClick",
7512 focus: "_handleInputFocus",
7513 blur: "_handleInputBlur"
7514 });
7515
7516 this._handleFormReset();
7517 this.refresh();
7518 },
7519
7520 _enhance: function() {
7521 this.label.addClass( "ui-btn ui-corner-all");
7522
7523 if ( this.parentLabel.length > 0 ) {
7524 this.input.add( this.label ).wrapAll( this._wrapper() );
7525 } else {
7526 //this.element.replaceWith( this.input.add( this.label ).wrapAll( this._wrapper() ) );
7527 this.element.wrap( this._wrapper() );
7528 this.element.parent().prepend( this.label );
7529 }
7530
7531 // Wrap the input + label in a div
7532
7533 this._setOptions({
7534 "theme": this.options.theme,
7535 "iconpos": this.options.iconpos,
7536 "mini": this.options.mini
7537 });
7538
7539 },
7540
7541 _wrapper: function() {
7542 return $( "<div class='" +
7543 ( this.options.wrapperClass ? this.options.wrapperClass : "" ) +
7544 " ui-" + this.inputtype +
7545 ( this.options.disabled ? " ui-state-disabled" : "" ) + "' >" );
7546 },
7547
7548 _handleInputFocus: function() {
7549 this.label.addClass( $.mobile.focusClass );
7550 },
7551
7552 _handleInputBlur: function() {
7553 this.label.removeClass( $.mobile.focusClass );
7554 },
7555
7556 _handleInputVClick: function() {
7557 var $this = this.element;
7558
7559 // Adds checked attribute to checked input when keyboard is used
7560 if ( $this.is( ":checked" ) ) {
7561
7562 $this.prop( "checked", true);
7563 this._getInputSet().not( $this ).prop( "checked", false );
7564 } else {
7565 $this.prop( "checked", false );
7566 }
7567
7568 this._updateAll();
7569 },
7570
7571 _handleLabelVMouseOver: function( event ) {
7572 if ( this.label.parent().hasClass( "ui-state-disabled" ) ) {
7573 event.stopPropagation();
7574 }
7575 },
7576
7577 _handleLabelVClick: function( event ) {
7578 var input = this.element;
7579
7580 if ( input.is( ":disabled" ) ) {
7581 event.preventDefault();
7582 return;
7583 }
7584
7585 this._cacheVals();
7586
7587 input.prop( "checked", this.inputtype === "radio" && true || !input.prop( "checked" ) );
7588
7589 // trigger click handler's bound directly to the input as a substitute for
7590 // how label clicks behave normally in the browsers
7591 // TODO: it would be nice to let the browser's handle the clicks and pass them
7592 // through to the associate input. we can swallow that click at the parent
7593 // wrapper element level
7594 input.triggerHandler( "click" );
7595
7596 // Input set for common radio buttons will contain all the radio
7597 // buttons, but will not for checkboxes. clearing the checked status
7598 // of other radios ensures the active button state is applied properly
7599 this._getInputSet().not( input ).prop( "checked", false );
7600
7601 this._updateAll();
7602 return false;
7603 },
7604
7605 _cacheVals: function() {
7606 this._getInputSet().each( function() {
7607 $( this ).attr("data-" + $.mobile.ns + "cacheVal", this.checked );
7608 });
7609 },
7610
7611 //returns either a set of radios with the same name attribute, or a single checkbox
7612 _getInputSet: function() {
7613 if ( this.inputtype === "checkbox" ) {
7614 return this.element;
7615 }
7616
7617 return this.element.closest( "form, :jqmData(role='page'), :jqmData(role='dialog')" )
7618 .find( "input[name='" + this.element[ 0 ].name + "'][type='" + this.inputtype + "']" );
7619 },
7620
7621 _updateAll: function() {
7622 var self = this;
7623
7624 this._getInputSet().each( function() {
7625 var $this = $( this );
7626
7627 if ( this.checked || self.inputtype === "checkbox" ) {
7628 $this.trigger( "change" );
7629 }
7630 })
7631 .checkboxradio( "refresh" );
7632 },
7633
7634 _reset: function() {
7635 this.refresh();
7636 },
7637
7638 // Is the widget supposed to display an icon?
7639 _hasIcon: function() {
7640 var controlgroup, controlgroupWidget,
7641 controlgroupConstructor = $.mobile.controlgroup;
7642
7643 // If the controlgroup widget is defined ...
7644 if ( controlgroupConstructor ) {
7645 controlgroup = this.element.closest(
7646 ":mobile-controlgroup," +
7647 controlgroupConstructor.prototype.initSelector );
7648
7649 // ... and the checkbox is in a controlgroup ...
7650 if ( controlgroup.length > 0 ) {
7651
7652 // ... look for a controlgroup widget instance, and ...
7653 controlgroupWidget = $.data( controlgroup[ 0 ], "mobile-controlgroup" );
7654
7655 // ... if found, decide based on the option value, ...
7656 return ( ( controlgroupWidget ? controlgroupWidget.options.type :
7657
7658 // ... otherwise decide based on the "type" data attribute.
7659 controlgroup.attr( "data-" + $.mobile.ns + "type" ) ) !== "horizontal" );
7660 }
7661 }
7662
7663 // Normally, the widget displays an icon.
7664 return true;
7665 },
7666
7667 refresh: function() {
7668 var hasIcon = this._hasIcon(),
7669 isChecked = this.element[ 0 ].checked,
7670 active = $.mobile.activeBtnClass,
7671 iconposClass = "ui-btn-icon-" + this.options.iconpos,
7672 addClasses = [],
7673 removeClasses = [];
7674
7675 if ( hasIcon ) {
7676 removeClasses.push( active );
7677 addClasses.push( iconposClass );
7678 } else {
7679 removeClasses.push( iconposClass );
7680 ( isChecked ? addClasses : removeClasses ).push( active );
7681 }
7682
7683 if ( isChecked ) {
7684 addClasses.push( this.checkedClass );
7685 removeClasses.push( this.uncheckedClass );
7686 } else {
7687 addClasses.push( this.uncheckedClass );
7688 removeClasses.push( this.checkedClass );
7689 }
7690
7691 this.label
7692 .addClass( addClasses.join( " " ) )
7693 .removeClass( removeClasses.join( " " ) );
7694 },
7695
7696 widget: function() {
7697 return this.label.parent();
7698 },
7699
7700 _setOptions: function( options ) {
7701 var label = this.label,
7702 currentOptions = this.options,
7703 outer = this.widget(),
7704 hasIcon = this._hasIcon();
7705
7706 if ( options.disabled !== undefined ) {
7707 this.input.prop( "disabled", !!options.disabled );
7708 outer.toggleClass( "ui-state-disabled", !!options.disabled );
7709 }
7710 if ( options.mini !== undefined ) {
7711 outer.toggleClass( "ui-mini", !!options.mini );
7712 }
7713 if ( options.theme !== undefined ) {
7714 label
7715 .removeClass( "ui-btn-" + currentOptions.theme )
7716 .addClass( "ui-btn-" + options.theme );
7717 }
7718 if ( options.wrapperClass !== undefined ) {
7719 outer
7720 .removeClass( currentOptions.wrapperClass )
7721 .addClass( options.wrapperClass );
7722 }
7723 if ( options.iconpos !== undefined && hasIcon ) {
7724 label.removeClass( "ui-btn-icon-" + currentOptions.iconpos ).addClass( "ui-btn-icon-" + options.iconpos );
7725 } else if ( !hasIcon ) {
7726 label.removeClass( "ui-btn-icon-" + currentOptions.iconpos );
7727 }
7728 this._super( options );
7729 }
7730
7731}, $.mobile.behaviors.formReset ) );
7732
7733})( jQuery );
7734
7735(function( $, undefined ) {
7736
7737$.widget( "mobile.button", {
7738
7739 initSelector: "input[type='button'], input[type='submit'], input[type='reset']",
7740
7741 options: {
7742 theme: null,
7743 icon: null,
7744 iconpos: "left",
7745 iconshadow: false, /* TODO: Deprecated in 1.4, remove in 1.5. */
7746 corners: true,
7747 shadow: true,
7748 inline: null,
7749 mini: null,
7750 wrapperClass: null,
7751 enhanced: false
7752 },
7753
7754 _create: function() {
7755
7756 if ( this.element.is( ":disabled" ) ) {
7757 this.options.disabled = true;
7758 }
7759
7760 if ( !this.options.enhanced ) {
7761 this._enhance();
7762 }
7763
7764 $.extend( this, {
7765 wrapper: this.element.parent()
7766 });
7767
7768 this._on( {
7769 focus: function() {
7770 this.widget().addClass( $.mobile.focusClass );
7771 },
7772
7773 blur: function() {
7774 this.widget().removeClass( $.mobile.focusClass );
7775 }
7776 });
7777
7778 this.refresh( true );
7779 },
7780
7781 _enhance: function() {
7782 this.element.wrap( this._button() );
7783 },
7784
7785 _button: function() {
7786 var options = this.options,
7787 iconClasses = this._getIconClasses( this.options );
7788
7789 return $("<div class='ui-btn ui-input-btn" +
7790 ( options.wrapperClass ? " " + options.wrapperClass : "" ) +
7791 ( options.theme ? " ui-btn-" + options.theme : "" ) +
7792 ( options.corners ? " ui-corner-all" : "" ) +
7793 ( options.shadow ? " ui-shadow" : "" ) +
7794 ( options.inline ? " ui-btn-inline" : "" ) +
7795 ( options.mini ? " ui-mini" : "" ) +
7796 ( options.disabled ? " ui-state-disabled" : "" ) +
7797 ( iconClasses ? ( " " + iconClasses ) : "" ) +
7798 "' >" + this.element.val() + "</div>" );
7799 },
7800
7801 widget: function() {
7802 return this.wrapper;
7803 },
7804
7805 _destroy: function() {
7806 this.element.insertBefore( this.button );
7807 this.button.remove();
7808 },
7809
7810 _getIconClasses: function( options ) {
7811 return ( options.icon ? ( "ui-icon-" + options.icon +
7812 ( options.iconshadow ? " ui-shadow-icon" : "" ) + /* TODO: Deprecated in 1.4, remove in 1.5. */
7813 " ui-btn-icon-" + options.iconpos ) : "" );
7814 },
7815
7816 _setOptions: function( options ) {
7817 var outer = this.widget();
7818
7819 if ( options.theme !== undefined ) {
7820 outer
7821 .removeClass( this.options.theme )
7822 .addClass( "ui-btn-" + options.theme );
7823 }
7824 if ( options.corners !== undefined ) {
7825 outer.toggleClass( "ui-corner-all", options.corners );
7826 }
7827 if ( options.shadow !== undefined ) {
7828 outer.toggleClass( "ui-shadow", options.shadow );
7829 }
7830 if ( options.inline !== undefined ) {
7831 outer.toggleClass( "ui-btn-inline", options.inline );
7832 }
7833 if ( options.mini !== undefined ) {
7834 outer.toggleClass( "ui-mini", options.mini );
7835 }
7836 if ( options.disabled !== undefined ) {
7837 this.element.prop( "disabled", options.disabled );
7838 outer.toggleClass( "ui-state-disabled", options.disabled );
7839 }
7840
7841 if ( options.icon !== undefined ||
7842 options.iconshadow !== undefined || /* TODO: Deprecated in 1.4, remove in 1.5. */
7843 options.iconpos !== undefined ) {
7844 outer
7845 .removeClass( this._getIconClasses( this.options ) )
7846 .addClass( this._getIconClasses(
7847 $.extend( {}, this.options, options ) ) );
7848 }
7849
7850 this._super( options );
7851 },
7852
7853 refresh: function( create ) {
7854 if ( this.options.icon && this.options.iconpos === "notext" && this.element.attr( "title" ) ) {
7855 this.element.attr( "title", this.element.val() );
7856 }
7857 if ( !create ) {
7858 var originalElement = this.element.detach();
7859 $( this.wrapper ).text( this.element.val() ).append( originalElement );
7860 }
7861 }
7862});
7863
7864})( jQuery );
7865
7866(function( $ ) {
7867 var meta = $( "meta[name=viewport]" ),
7868 initialContent = meta.attr( "content" ),
7869 disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no",
7870 enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes",
7871 disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test( initialContent );
7872
7873 $.mobile.zoom = $.extend( {}, {
7874 enabled: !disabledInitially,
7875 locked: false,
7876 disable: function( lock ) {
7877 if ( !disabledInitially && !$.mobile.zoom.locked ) {
7878 meta.attr( "content", disabledZoom );
7879 $.mobile.zoom.enabled = false;
7880 $.mobile.zoom.locked = lock || false;
7881 }
7882 },
7883 enable: function( unlock ) {
7884 if ( !disabledInitially && ( !$.mobile.zoom.locked || unlock === true ) ) {
7885 meta.attr( "content", enabledZoom );
7886 $.mobile.zoom.enabled = true;
7887 $.mobile.zoom.locked = false;
7888 }
7889 },
7890 restore: function() {
7891 if ( !disabledInitially ) {
7892 meta.attr( "content", initialContent );
7893 $.mobile.zoom.enabled = true;
7894 }
7895 }
7896 });
7897
7898}( jQuery ));
7899
7900(function( $, undefined ) {
7901
7902$.widget( "mobile.textinput", {
7903 initSelector: "input[type='text']," +
7904 "input[type='search']," +
7905 ":jqmData(type='search')," +
7906 "input[type='number']," +
7907 ":jqmData(type='number')," +
7908 "input[type='password']," +
7909 "input[type='email']," +
7910 "input[type='url']," +
7911 "input[type='tel']," +
7912 "textarea," +
7913 "input[type='time']," +
7914 "input[type='date']," +
7915 "input[type='month']," +
7916 "input[type='week']," +
7917 "input[type='datetime']," +
7918 "input[type='datetime-local']," +
7919 "input[type='color']," +
7920 "input:not([type])," +
7921 "input[type='file']",
7922
7923 options: {
7924 theme: null,
7925 corners: true,
7926 mini: false,
7927 // This option defaults to true on iOS devices.
7928 preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
7929 wrapperClass: "",
7930 enhanced: false
7931 },
7932
7933 _create: function() {
7934
7935 var options = this.options,
7936 isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ),
7937 isTextarea = this.element[ 0 ].tagName === "TEXTAREA",
7938 isRange = this.element.is( "[data-" + ( $.mobile.ns || "" ) + "type='range']" ),
7939 inputNeedsWrap = ( (this.element.is( "input" ) ||
7940 this.element.is( "[data-" + ( $.mobile.ns || "" ) + "type='search']" ) ) &&
7941 !isRange );
7942
7943 if ( this.element.prop( "disabled" ) ) {
7944 options.disabled = true;
7945 }
7946
7947 $.extend( this, {
7948 classes: this._classesFromOptions(),
7949 isSearch: isSearch,
7950 isTextarea: isTextarea,
7951 isRange: isRange,
7952 inputNeedsWrap: inputNeedsWrap
7953 });
7954
7955 this._autoCorrect();
7956
7957 if ( !options.enhanced ) {
7958 this._enhance();
7959 }
7960
7961 this._on( {
7962 "focus": "_handleFocus",
7963 "blur": "_handleBlur"
7964 });
7965
7966 },
7967
7968 refresh: function() {
7969 this.setOptions({
7970 "disabled" : this.element.is( ":disabled" )
7971 });
7972 },
7973
7974 _enhance: function() {
7975 var elementClasses = [];
7976
7977 if ( this.isTextarea ) {
7978 elementClasses.push( "ui-input-text" );
7979 }
7980
7981 if ( this.isTextarea || this.isRange ) {
7982 elementClasses.push( "ui-shadow-inset" );
7983 }
7984
7985 //"search" and "text" input widgets
7986 if ( this.inputNeedsWrap ) {
7987 this.element.wrap( this._wrap() );
7988 } else {
7989 elementClasses = elementClasses.concat( this.classes );
7990 }
7991
7992 this.element.addClass( elementClasses.join( " " ) );
7993 },
7994
7995 widget: function() {
7996 return ( this.inputNeedsWrap ) ? this.element.parent() : this.element;
7997 },
7998
7999 _classesFromOptions: function() {
8000 var options = this.options,
8001 classes = [];
8002
8003 classes.push( "ui-body-" + ( ( options.theme === null ) ? "inherit" : options.theme ) );
8004 if ( options.corners ) {
8005 classes.push( "ui-corner-all" );
8006 }
8007 if ( options.mini ) {
8008 classes.push( "ui-mini" );
8009 }
8010 if ( options.disabled ) {
8011 classes.push( "ui-state-disabled" );
8012 }
8013 if ( options.wrapperClass ) {
8014 classes.push( options.wrapperClass );
8015 }
8016
8017 return classes;
8018 },
8019
8020 _wrap: function() {
8021 return $( "<div class='" +
8022 ( this.isSearch ? "ui-input-search " : "ui-input-text " ) +
8023 this.classes.join( " " ) + " " +
8024 "ui-shadow-inset'></div>" );
8025 },
8026
8027 _autoCorrect: function() {
8028 // XXX: Temporary workaround for issue 785 (Apple bug 8910589).
8029 // Turn off autocorrect and autocomplete on non-iOS 5 devices
8030 // since the popup they use can't be dismissed by the user. Note
8031 // that we test for the presence of the feature by looking for
8032 // the autocorrect property on the input element. We currently
8033 // have no test for iOS 5 or newer so we're temporarily using
8034 // the touchOverflow support flag for jQM 1.0. Yes, I feel dirty.
8035 // - jblas
8036 if ( typeof this.element[0].autocorrect !== "undefined" &&
8037 !$.support.touchOverflow ) {
8038
8039 // Set the attribute instead of the property just in case there
8040 // is code that attempts to make modifications via HTML.
8041 this.element[0].setAttribute( "autocorrect", "off" );
8042 this.element[0].setAttribute( "autocomplete", "off" );
8043 }
8044 },
8045
8046 _handleBlur: function() {
8047 this.widget().removeClass( $.mobile.focusClass );
8048 if ( this.options.preventFocusZoom ) {
8049 $.mobile.zoom.enable( true );
8050 }
8051 },
8052
8053 _handleFocus: function() {
8054 // In many situations, iOS will zoom into the input upon tap, this
8055 // prevents that from happening
8056 if ( this.options.preventFocusZoom ) {
8057 $.mobile.zoom.disable( true );
8058 }
8059 this.widget().addClass( $.mobile.focusClass );
8060 },
8061
8062 _setOptions: function ( options ) {
8063 var outer = this.widget();
8064
8065 this._super( options );
8066
8067 if ( !( options.disabled === undefined &&
8068 options.mini === undefined &&
8069 options.corners === undefined &&
8070 options.theme === undefined &&
8071 options.wrapperClass === undefined ) ) {
8072
8073 outer.removeClass( this.classes.join( " " ) );
8074 this.classes = this._classesFromOptions();
8075 outer.addClass( this.classes.join( " " ) );
8076 }
8077
8078 if ( options.disabled !== undefined ) {
8079 this.element.prop( "disabled", !!options.disabled );
8080 }
8081 },
8082
8083 _destroy: function() {
8084 if ( this.options.enhanced ) {
8085 return;
8086 }
8087 if ( this.inputNeedsWrap ) {
8088 this.element.unwrap();
8089 }
8090 this.element.removeClass( "ui-input-text " + this.classes.join( " " ) );
8091 }
8092});
8093
8094})( jQuery );
8095
8096(function( $, undefined ) {
8097
8098$.widget( "mobile.slider", $.extend( {
8099 initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
8100
8101 widgetEventPrefix: "slide",
8102
8103 options: {
8104 theme: null,
8105 trackTheme: null,
8106 corners: true,
8107 mini: false,
8108 highlight: false
8109 },
8110
8111 _create: function() {
8112
8113 // TODO: Each of these should have comments explain what they're for
8114 var self = this,
8115 control = this.element,
8116 trackTheme = this.options.trackTheme || $.mobile.getAttribute( control[ 0 ], "theme" ),
8117 trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
8118 cornerClass = ( this.options.corners || control.jqmData( "corners" ) ) ? " ui-corner-all" : "",
8119 miniClass = ( this.options.mini || control.jqmData( "mini" ) ) ? " ui-mini" : "",
8120 cType = control[ 0 ].nodeName.toLowerCase(),
8121 isToggleSwitch = ( cType === "select" ),
8122 isRangeslider = control.parent().is( ":jqmData(role='rangeslider')" ),
8123 selectClass = ( isToggleSwitch ) ? "ui-slider-switch" : "",
8124 controlID = control.attr( "id" ),
8125 $label = $( "[for='" + controlID + "']" ),
8126 labelID = $label.attr( "id" ) || controlID + "-label",
8127 min = !isToggleSwitch ? parseFloat( control.attr( "min" ) ) : 0,
8128 max = !isToggleSwitch ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
8129 step = window.parseFloat( control.attr( "step" ) || 1 ),
8130 domHandle = document.createElement( "a" ),
8131 handle = $( domHandle ),
8132 domSlider = document.createElement( "div" ),
8133 slider = $( domSlider ),
8134 valuebg = this.options.highlight && !isToggleSwitch ? (function() {
8135 var bg = document.createElement( "div" );
8136 bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
8137 return $( bg ).prependTo( slider );
8138 })() : false,
8139 options,
8140 wrapper,
8141 j, length,
8142 i, optionsCount, origTabIndex,
8143 side, activeClass, sliderImg;
8144
8145 $label.attr( "id", labelID );
8146 this.isToggleSwitch = isToggleSwitch;
8147
8148 domHandle.setAttribute( "href", "#" );
8149 domSlider.setAttribute( "role", "application" );
8150 domSlider.className = [ this.isToggleSwitch ? "ui-slider ui-slider-track ui-shadow-inset " : "ui-slider-track ui-shadow-inset ", selectClass, trackThemeClass, cornerClass, miniClass ].join( "" );
8151 domHandle.className = "ui-slider-handle";
8152 domSlider.appendChild( domHandle );
8153
8154 handle.attr({
8155 "role": "slider",
8156 "aria-valuemin": min,
8157 "aria-valuemax": max,
8158 "aria-valuenow": this._value(),
8159 "aria-valuetext": this._value(),
8160 "title": this._value(),
8161 "aria-labelledby": labelID
8162 });
8163
8164 $.extend( this, {
8165 slider: slider,
8166 handle: handle,
8167 control: control,
8168 type: cType,
8169 step: step,
8170 max: max,
8171 min: min,
8172 valuebg: valuebg,
8173 isRangeslider: isRangeslider,
8174 dragging: false,
8175 beforeStart: null,
8176 userModified: false,
8177 mouseMoved: false
8178 });
8179
8180 if ( isToggleSwitch ) {
8181 // TODO: restore original tabindex (if any) in a destroy method
8182 origTabIndex = control.attr( "tabindex" );
8183 if ( origTabIndex ) {
8184 handle.attr( "tabindex", origTabIndex );
8185 }
8186 control.attr( "tabindex", "-1" ).focus(function() {
8187 $( this ).blur();
8188 handle.focus();
8189 });
8190
8191 wrapper = document.createElement( "div" );
8192 wrapper.className = "ui-slider-inneroffset";
8193
8194 for ( j = 0, length = domSlider.childNodes.length; j < length; j++ ) {
8195 wrapper.appendChild( domSlider.childNodes[j] );
8196 }
8197
8198 domSlider.appendChild( wrapper );
8199
8200 // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" );
8201
8202 // make the handle move with a smooth transition
8203 handle.addClass( "ui-slider-handle-snapping" );
8204
8205 options = control.find( "option" );
8206
8207 for ( i = 0, optionsCount = options.length; i < optionsCount; i++ ) {
8208 side = !i ? "b" : "a";
8209 activeClass = !i ? "" : " " + $.mobile.activeBtnClass;
8210 sliderImg = document.createElement( "span" );
8211
8212 sliderImg.className = [ "ui-slider-label ui-slider-label-", side, activeClass ].join( "" );
8213 sliderImg.setAttribute( "role", "img" );
8214 sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) );
8215 $( sliderImg ).prependTo( slider );
8216 }
8217
8218 self._labels = $( ".ui-slider-label", slider );
8219
8220 }
8221
8222 // monitor the input for updated values
8223 control.addClass( isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" );
8224
8225 this._on( control, {
8226 "change": "_controlChange",
8227 "keyup": "_controlKeyup",
8228 "blur": "_controlBlur",
8229 "vmouseup": "_controlVMouseUp"
8230 });
8231
8232 slider.bind( "vmousedown", $.proxy( this._sliderVMouseDown, this ) )
8233 .bind( "vclick", false );
8234
8235 // We have to instantiate a new function object for the unbind to work properly
8236 // since the method itself is defined in the prototype (causing it to unbind everything)
8237 this._on( document, { "vmousemove": "_preventDocumentDrag" });
8238 this._on( slider.add( document ), { "vmouseup": "_sliderVMouseUp" });
8239
8240 slider.insertAfter( control );
8241
8242 // wrap in a div for styling purposes
8243 if ( !isToggleSwitch && !isRangeslider ) {
8244 wrapper = this.options.mini ? "<div class='ui-slider ui-mini'>" : "<div class='ui-slider'>";
8245
8246 control.add( slider ).wrapAll( wrapper );
8247 }
8248
8249 // bind the handle event callbacks and set the context to the widget instance
8250 this._on( this.handle, {
8251 "vmousedown": "_handleVMouseDown",
8252 "keydown": "_handleKeydown",
8253 "keyup": "_handleKeyup"
8254 });
8255
8256 this.handle.bind( "vclick", false );
8257
8258 this._handleFormReset();
8259
8260 this.refresh( undefined, undefined, true );
8261 },
8262
8263 _setOptions: function( options ) {
8264 if ( options.theme !== undefined ) {
8265 this._setTheme( options.theme );
8266 }
8267
8268 if ( options.trackTheme !== undefined ) {
8269 this._setTrackTheme( options.trackTheme );
8270 }
8271
8272 if ( options.corners !== undefined ) {
8273 this._setCorners( options.corners );
8274 }
8275
8276 if ( options.mini !== undefined ) {
8277 this._setMini( options.mini );
8278 }
8279
8280 if ( options.highlight !== undefined ) {
8281 this._setHighlight( options.highlight );
8282 }
8283
8284 if ( options.disabled !== undefined ) {
8285 this._setDisabled( options.disabled );
8286 }
8287 this._super( options );
8288 },
8289
8290 _controlChange: function( event ) {
8291 // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
8292 if ( this._trigger( "controlchange", event ) === false ) {
8293 return false;
8294 }
8295 if ( !this.mouseMoved ) {
8296 this.refresh( this._value(), true );
8297 }
8298 },
8299
8300 _controlKeyup: function(/* event */) { // necessary?
8301 this.refresh( this._value(), true, true );
8302 },
8303
8304 _controlBlur: function(/* event */) {
8305 this.refresh( this._value(), true );
8306 },
8307
8308 // it appears the clicking the up and down buttons in chrome on
8309 // range/number inputs doesn't trigger a change until the field is
8310 // blurred. Here we check thif the value has changed and refresh
8311 _controlVMouseUp: function(/* event */) {
8312 this._checkedRefresh();
8313 },
8314
8315 // NOTE force focus on handle
8316 _handleVMouseDown: function(/* event */) {
8317 this.handle.focus();
8318 },
8319
8320 _handleKeydown: function( event ) {
8321 var index = this._value();
8322 if ( this.options.disabled ) {
8323 return;
8324 }
8325
8326 // In all cases prevent the default and mark the handle as active
8327 switch ( event.keyCode ) {
8328 case $.mobile.keyCode.HOME:
8329 case $.mobile.keyCode.END:
8330 case $.mobile.keyCode.PAGE_UP:
8331 case $.mobile.keyCode.PAGE_DOWN:
8332 case $.mobile.keyCode.UP:
8333 case $.mobile.keyCode.RIGHT:
8334 case $.mobile.keyCode.DOWN:
8335 case $.mobile.keyCode.LEFT:
8336 event.preventDefault();
8337
8338 if ( !this._keySliding ) {
8339 this._keySliding = true;
8340 this.handle.addClass( "ui-state-active" ); /* TODO: We don't use this class for styling. Do we need to add it? */
8341 }
8342
8343 break;
8344 }
8345
8346 // move the slider according to the keypress
8347 switch ( event.keyCode ) {
8348 case $.mobile.keyCode.HOME:
8349 this.refresh( this.min );
8350 break;
8351 case $.mobile.keyCode.END:
8352 this.refresh( this.max );
8353 break;
8354 case $.mobile.keyCode.PAGE_UP:
8355 case $.mobile.keyCode.UP:
8356 case $.mobile.keyCode.RIGHT:
8357 this.refresh( index + this.step );
8358 break;
8359 case $.mobile.keyCode.PAGE_DOWN:
8360 case $.mobile.keyCode.DOWN:
8361 case $.mobile.keyCode.LEFT:
8362 this.refresh( index - this.step );
8363 break;
8364 }
8365 }, // remove active mark
8366
8367 _handleKeyup: function(/* event */) {
8368 if ( this._keySliding ) {
8369 this._keySliding = false;
8370 this.handle.removeClass( "ui-state-active" ); /* See comment above. */
8371 }
8372 },
8373
8374 _sliderVMouseDown: function( event ) {
8375 // NOTE: we don't do this in refresh because we still want to
8376 // support programmatic alteration of disabled inputs
8377 if ( this.options.disabled || !( event.which === 1 || event.which === 0 || event.which === undefined ) ) {
8378 return false;
8379 }
8380 if ( this._trigger( "beforestart", event ) === false ) {
8381 return false;
8382 }
8383 this.dragging = true;
8384 this.userModified = false;
8385 this.mouseMoved = false;
8386
8387 if ( this.isToggleSwitch ) {
8388 this.beforeStart = this.element[0].selectedIndex;
8389 }
8390
8391 this.refresh( event );
8392 this._trigger( "start" );
8393 return false;
8394 },
8395
8396 _sliderVMouseUp: function() {
8397 if ( this.dragging ) {
8398 this.dragging = false;
8399
8400 if ( this.isToggleSwitch ) {
8401 // make the handle move with a smooth transition
8402 this.handle.addClass( "ui-slider-handle-snapping" );
8403
8404 if ( this.mouseMoved ) {
8405 // this is a drag, change the value only if user dragged enough
8406 if ( this.userModified ) {
8407 this.refresh( this.beforeStart === 0 ? 1 : 0 );
8408 } else {
8409 this.refresh( this.beforeStart );
8410 }
8411 } else {
8412 // this is just a click, change the value
8413 this.refresh( this.beforeStart === 0 ? 1 : 0 );
8414 }
8415 }
8416
8417 this.mouseMoved = false;
8418 this._trigger( "stop" );
8419 return false;
8420 }
8421 },
8422
8423 _preventDocumentDrag: function( event ) {
8424 // NOTE: we don't do this in refresh because we still want to
8425 // support programmatic alteration of disabled inputs
8426 if ( this._trigger( "drag", event ) === false) {
8427 return false;
8428 }
8429 if ( this.dragging && !this.options.disabled ) {
8430
8431 // this.mouseMoved must be updated before refresh() because it will be used in the control "change" event
8432 this.mouseMoved = true;
8433
8434 if ( this.isToggleSwitch ) {
8435 // make the handle move in sync with the mouse
8436 this.handle.removeClass( "ui-slider-handle-snapping" );
8437 }
8438
8439 this.refresh( event );
8440
8441 // only after refresh() you can calculate this.userModified
8442 this.userModified = this.beforeStart !== this.element[0].selectedIndex;
8443 return false;
8444 }
8445 },
8446
8447 _checkedRefresh: function() {
8448 if ( this.value !== this._value() ) {
8449 this.refresh( this._value() );
8450 }
8451 },
8452
8453 _value: function() {
8454 return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat( this.element.val() ) ;
8455 },
8456
8457 _reset: function() {
8458 this.refresh( undefined, false, true );
8459 },
8460
8461 refresh: function( val, isfromControl, preventInputUpdate ) {
8462 // NOTE: we don't return here because we want to support programmatic
8463 // alteration of the input value, which should still update the slider
8464
8465 var self = this,
8466 parentTheme = $.mobile.getAttribute( this.element[ 0 ], "theme" ),
8467 theme = this.options.theme || parentTheme,
8468 themeClass = theme ? " ui-btn-" + theme : "",
8469 trackTheme = this.options.trackTheme || parentTheme,
8470 trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
8471 cornerClass = this.options.corners ? " ui-corner-all" : "",
8472 miniClass = this.options.mini ? " ui-mini" : "",
8473 left, width, data, tol,
8474 pxStep, percent,
8475 control, isInput, optionElements, min, max, step,
8476 newval, valModStep, alignValue, percentPerStep,
8477 handlePercent, aPercent, bPercent,
8478 valueChanged;
8479
8480 self.slider[0].className = [ this.isToggleSwitch ? "ui-slider ui-slider-switch ui-slider-track ui-shadow-inset" : "ui-slider-track ui-shadow-inset", trackThemeClass, cornerClass, miniClass ].join( "" );
8481 if ( this.options.disabled || this.element.prop( "disabled" ) ) {
8482 this.disable();
8483 }
8484
8485 // set the stored value for comparison later
8486 this.value = this._value();
8487 if ( this.options.highlight && !this.isToggleSwitch && this.slider.find( ".ui-slider-bg" ).length === 0 ) {
8488 this.valuebg = (function() {
8489 var bg = document.createElement( "div" );
8490 bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
8491 return $( bg ).prependTo( self.slider );
8492 })();
8493 }
8494 this.handle.addClass( "ui-btn" + themeClass + " ui-shadow" );
8495
8496 control = this.element;
8497 isInput = !this.isToggleSwitch;
8498 optionElements = isInput ? [] : control.find( "option" );
8499 min = isInput ? parseFloat( control.attr( "min" ) ) : 0;
8500 max = isInput ? parseFloat( control.attr( "max" ) ) : optionElements.length - 1;
8501 step = ( isInput && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1;
8502
8503 if ( typeof val === "object" ) {
8504 data = val;
8505 // a slight tolerance helped get to the ends of the slider
8506 tol = 8;
8507
8508 left = this.slider.offset().left;
8509 width = this.slider.width();
8510 pxStep = width/((max-min)/step);
8511 if ( !this.dragging ||
8512 data.pageX < left - tol ||
8513 data.pageX > left + width + tol ) {
8514 return;
8515 }
8516 if ( pxStep > 1 ) {
8517 percent = ( ( data.pageX - left ) / width ) * 100;
8518 } else {
8519 percent = Math.round( ( ( data.pageX - left ) / width ) * 100 );
8520 }
8521 } else {
8522 if ( val == null ) {
8523 val = isInput ? parseFloat( control.val() || 0 ) : control[0].selectedIndex;
8524 }
8525 percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
8526 }
8527
8528 if ( isNaN( percent ) ) {
8529 return;
8530 }
8531
8532 newval = ( percent / 100 ) * ( max - min ) + min;
8533
8534 //from jQuery UI slider, the following source will round to the nearest step
8535 valModStep = ( newval - min ) % step;
8536 alignValue = newval - valModStep;
8537
8538 if ( Math.abs( valModStep ) * 2 >= step ) {
8539 alignValue += ( valModStep > 0 ) ? step : ( -step );
8540 }
8541
8542 percentPerStep = 100/((max-min)/step);
8543 // Since JavaScript has problems with large floats, round
8544 // the final value to 5 digits after the decimal point (see jQueryUI: #4124)
8545 newval = parseFloat( alignValue.toFixed(5) );
8546
8547 if ( typeof pxStep === "undefined" ) {
8548 pxStep = width / ( (max-min) / step );
8549 }
8550 if ( pxStep > 1 && isInput ) {
8551 percent = ( newval - min ) * percentPerStep * ( 1 / step );
8552 }
8553 if ( percent < 0 ) {
8554 percent = 0;
8555 }
8556
8557 if ( percent > 100 ) {
8558 percent = 100;
8559 }
8560
8561 if ( newval < min ) {
8562 newval = min;
8563 }
8564
8565 if ( newval > max ) {
8566 newval = max;
8567 }
8568
8569 this.handle.css( "left", percent + "%" );
8570
8571 this.handle[0].setAttribute( "aria-valuenow", isInput ? newval : optionElements.eq( newval ).attr( "value" ) );
8572
8573 this.handle[0].setAttribute( "aria-valuetext", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
8574
8575 this.handle[0].setAttribute( "title", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
8576
8577 if ( this.valuebg ) {
8578 this.valuebg.css( "width", percent + "%" );
8579 }
8580
8581 // drag the label widths
8582 if ( this._labels ) {
8583 handlePercent = this.handle.width() / this.slider.width() * 100;
8584 aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100;
8585 bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 );
8586
8587 this._labels.each(function() {
8588 var ab = $( this ).hasClass( "ui-slider-label-a" );
8589 $( this ).width( ( ab ? aPercent : bPercent ) + "%" );
8590 });
8591 }
8592
8593 if ( !preventInputUpdate ) {
8594 valueChanged = false;
8595
8596 // update control"s value
8597 if ( isInput ) {
8598 valueChanged = control.val() !== newval;
8599 control.val( newval );
8600 } else {
8601 valueChanged = control[ 0 ].selectedIndex !== newval;
8602 control[ 0 ].selectedIndex = newval;
8603 }
8604 if ( this._trigger( "beforechange", val ) === false) {
8605 return false;
8606 }
8607 if ( !isfromControl && valueChanged ) {
8608 control.trigger( "change" );
8609 }
8610 }
8611 },
8612
8613 _setHighlight: function( value ) {
8614 value = !!value;
8615 if ( value ) {
8616 this.options.highlight = !!value;
8617 this.refresh();
8618 } else if ( this.valuebg ) {
8619 this.valuebg.remove();
8620 this.valuebg = false;
8621 }
8622 },
8623
8624 _setTheme: function( value ) {
8625 this.handle
8626 .removeClass( "ui-btn-" + this.options.theme )
8627 .addClass( "ui-btn-" + value );
8628
8629 var currentTheme = this.options.theme ? this.options.theme : "inherit",
8630 newTheme = value ? value : "inherit";
8631
8632 this.control
8633 .removeClass( "ui-body-" + currentTheme )
8634 .addClass( "ui-body-" + newTheme );
8635 },
8636
8637 _setTrackTheme: function( value ) {
8638 var currentTrackTheme = this.options.trackTheme ? this.options.trackTheme : "inherit",
8639 newTrackTheme = value ? value : "inherit";
8640
8641 this.slider
8642 .removeClass( "ui-body-" + currentTrackTheme )
8643 .addClass( "ui-body-" + newTrackTheme );
8644 },
8645
8646 _setMini: function( value ) {
8647 value = !!value;
8648 if ( !this.isToggleSwitch && !this.isRangeslider ) {
8649 this.slider.parent().toggleClass( "ui-mini", value );
8650 this.element.toggleClass( "ui-mini", value );
8651 }
8652 this.slider.toggleClass( "ui-mini", value );
8653 },
8654
8655 _setCorners: function( value ) {
8656 this.slider.toggleClass( "ui-corner-all", value );
8657
8658 if ( !this.isToggleSwitch ) {
8659 this.control.toggleClass( "ui-corner-all", value );
8660 }
8661 },
8662
8663 _setDisabled: function( value ) {
8664 value = !!value;
8665 this.element.prop( "disabled", value );
8666 this.slider.toggleClass( "ui-state-disabled" ).attr( "aria-disabled", value );
8667 }
8668
8669}, $.mobile.behaviors.formReset ) );
8670
8671})( jQuery );
8672
8673(function( $, undefined ) {
8674
8675var popup;
8676
8677function getPopup() {
8678 if ( !popup ) {
8679 popup = $( "<div></div>", {
8680 "class": "ui-slider-popup ui-shadow ui-corner-all"
8681 });
8682 }
8683 return popup.clone();
8684}
8685
8686$.widget( "mobile.slider", $.mobile.slider, {
8687 options: {
8688 popupEnabled: false,
8689 showValue: false
8690 },
8691
8692 _create: function() {
8693 this._super();
8694
8695 $.extend( this, {
8696 _currentValue: null,
8697 _popup: null,
8698 _popupVisible: false
8699 });
8700
8701 this._setOption( "popupEnabled", this.options.popupEnabled );
8702
8703 this._on( this.handle, { "vmousedown" : "_showPopup" } );
8704 this._on( this.slider.add( this.document ), { "vmouseup" : "_hidePopup" } );
8705 this._refresh();
8706 },
8707
8708 // position the popup centered 5px above the handle
8709 _positionPopup: function() {
8710 var dstOffset = this.handle.offset();
8711
8712 this._popup.offset( {
8713 left: dstOffset.left + ( this.handle.width() - this._popup.width() ) / 2,
8714 top: dstOffset.top - this._popup.outerHeight() - 5
8715 });
8716 },
8717
8718 _setOption: function( key, value ) {
8719 this._super( key, value );
8720
8721 if ( key === "showValue" ) {
8722 this.handle.html( value && !this.options.mini ? this._value() : "" );
8723 } else if ( key === "popupEnabled" ) {
8724 if ( value && !this._popup ) {
8725 this._popup = getPopup()
8726 .addClass( "ui-body-" + ( this.options.theme || "a" ) )
8727 .insertBefore( this.element );
8728 }
8729 }
8730 },
8731
8732 // show value on the handle and in popup
8733 refresh: function() {
8734 this._super.apply( this, arguments );
8735 this._refresh();
8736 },
8737
8738 _refresh: function() {
8739 var o = this.options, newValue;
8740
8741 if ( o.popupEnabled ) {
8742 // remove the title attribute from the handle (which is
8743 // responsible for the annoying tooltip); NB we have
8744 // to do it here as the jqm slider sets it every time
8745 // the slider's value changes :(
8746 this.handle.removeAttr( "title" );
8747 }
8748
8749 newValue = this._value();
8750 if ( newValue === this._currentValue ) {
8751 return;
8752 }
8753 this._currentValue = newValue;
8754
8755 if ( o.popupEnabled && this._popup ) {
8756 this._positionPopup();
8757 this._popup.html( newValue );
8758 } else if ( o.showValue && !this.options.mini ) {
8759 this.handle.html( newValue );
8760 }
8761 },
8762
8763 _showPopup: function() {
8764 if ( this.options.popupEnabled && !this._popupVisible ) {
8765 this.handle.html( "" );
8766 this._popup.show();
8767 this._positionPopup();
8768 this._popupVisible = true;
8769 }
8770 },
8771
8772 _hidePopup: function() {
8773 var o = this.options;
8774
8775 if ( o.popupEnabled && this._popupVisible ) {
8776 if ( o.showValue && !o.mini ) {
8777 this.handle.html( this._value() );
8778 }
8779 this._popup.hide();
8780 this._popupVisible = false;
8781 }
8782 }
8783});
8784
8785})( jQuery );
8786
8787(function( $, undefined ) {
8788
8789$.widget( "mobile.flipswitch", $.extend({
8790
8791 options: {
8792 onText: "On",
8793 offText: "Off",
8794 theme: null,
8795 enhanced: false,
8796 wrapperClass: null,
8797 corners: true,
8798 mini: false
8799 },
8800
8801 _create: function() {
8802 if ( !this.options.enhanced ) {
8803 this._enhance();
8804 } else {
8805 $.extend( this, {
8806 flipswitch: this.element.parent(),
8807 on: this.element.find( ".ui-flipswitch-on" ).eq( 0 ),
8808 off: this.element.find( ".ui-flipswitch-off" ).eq(0),
8809 type: this.element.get( 0 ).tagName
8810 });
8811 }
8812
8813 this._handleFormReset();
8814
8815 if ( this.element.is( ":disabled" ) ) {
8816 this._setOptions({
8817 "disabled": true
8818 });
8819 }
8820
8821 this._on( this.flipswitch, {
8822 "click": "_toggle",
8823 "swipeleft": "_left",
8824 "swiperight": "_right"
8825 });
8826
8827 this._on( this.on, {
8828 "keydown": "_keydown"
8829 });
8830
8831 this._on( {
8832 "change": "refresh"
8833 });
8834 },
8835
8836 widget: function() {
8837 return this.flipswitch;
8838 },
8839
8840 _left: function() {
8841 this.flipswitch.removeClass( "ui-flipswitch-active" );
8842 if ( this.type === "SELECT" ) {
8843 this.element.get( 0 ).selectedIndex = 0;
8844 } else {
8845 this.element.prop( "checked", false );
8846 }
8847 this.element.trigger( "change" );
8848 },
8849
8850 _right: function() {
8851 this.flipswitch.addClass( "ui-flipswitch-active" );
8852 if ( this.type === "SELECT" ) {
8853 this.element.get( 0 ).selectedIndex = 1;
8854 } else {
8855 this.element.prop( "checked", true );
8856 }
8857 this.element.trigger( "change" );
8858 },
8859
8860 _enhance: function() {
8861 var flipswitch = $( "<div>" ),
8862 options = this.options,
8863 element = this.element,
8864 theme = options.theme ? options.theme : "inherit",
8865 on = $( "<span></span>", { tabindex: 1 } ),
8866 off = $( "<span></span>" ),
8867 type = element.get( 0 ).tagName,
8868 onText = ( type === "INPUT" ) ?
8869 options.onText : element.find( "option" ).eq( 1 ).text(),
8870 offText = ( type === "INPUT" ) ?
8871 options.offText : element.find( "option" ).eq( 0 ).text();
8872
8873 on
8874 .addClass( "ui-flipswitch-on ui-btn ui-shadow ui-btn-inherit" )
8875 .text( onText );
8876 off
8877 .addClass( "ui-flipswitch-off" )
8878 .text( offText );
8879
8880 flipswitch
8881 .addClass( "ui-flipswitch ui-shadow-inset " +
8882 "ui-bar-" + theme + " " +
8883 ( options.wrapperClass ? options.wrapperClass : "" ) + " " +
8884 ( ( element.is( ":checked" ) ||
8885 element
8886 .find( "option" )
8887 .eq( 1 )
8888 .is( ":selected" ) ) ? "ui-flipswitch-active" : "" ) +
8889 ( element.is(":disabled") ? " ui-state-disabled": "") +
8890 ( options.corners ? " ui-corner-all": "" ) +
8891 ( options.mini ? " ui-mini": "" ) )
8892 .append( on, off );
8893
8894 element
8895 .addClass( "ui-flipswitch-input" )
8896 .after( flipswitch )
8897 .appendTo( flipswitch );
8898
8899 $.extend( this, {
8900 flipswitch: flipswitch,
8901 on: on,
8902 off: off,
8903 type: type
8904 });
8905 },
8906
8907 _reset: function() {
8908 this.refresh();
8909 },
8910
8911 refresh: function() {
8912 var direction,
8913 existingDirection = this.flipswitch.hasClass( "ui-flipswitch-active" ) ? "_right" : "_left";
8914
8915 if ( this.type === "SELECT" ) {
8916 direction = ( this.element.get( 0 ).selectedIndex > 0 ) ? "_right": "_left";
8917 } else {
8918 direction = this.element.prop( "checked" ) ? "_right": "_left";
8919 }
8920
8921 if ( direction !== existingDirection ) {
8922 this[ direction ]();
8923 }
8924 },
8925
8926 _toggle: function() {
8927 var direction = this.flipswitch.hasClass( "ui-flipswitch-active" ) ? "_left" : "_right";
8928
8929 this[ direction ]();
8930 },
8931
8932 _keydown: function( e ) {
8933 if ( e.which === $.mobile.keyCode.LEFT ) {
8934 this._left();
8935 } else if ( e.which === $.mobile.keyCode.RIGHT ) {
8936 this._right();
8937 } else if ( e.which === $.mobile.keyCode.SPACE ) {
8938 this._toggle();
8939 e.preventDefault();
8940 }
8941 },
8942
8943 _setOptions: function( options ) {
8944 if ( options.theme !== undefined ) {
8945 var currentTheme = options.theme ? options.theme : "inherit",
8946 newTheme = options.theme ? options.theme : "inherit";
8947
8948 this.widget()
8949 .removeClass( "ui-bar-" + currentTheme )
8950 .addClass( "ui-bar-" + newTheme );
8951 }
8952 if ( options.onText !== undefined ) {
8953 this.on.text( options.onText );
8954 }
8955 if ( options.offText !== undefined ) {
8956 this.off.text( options.offText );
8957 }
8958 if ( options.disabled !== undefined ) {
8959 this.widget().toggleClass( "ui-state-disabled", options.disabled );
8960 }
8961 if ( options.mini !== undefined ) {
8962 this.widget().toggleClass( "ui-mini", options.mini );
8963 }
8964 if ( options.corners !== undefined ) {
8965 this.widget().toggleClass( "ui-corner-all", options.corners );
8966 }
8967
8968 this._super( options );
8969 },
8970
8971 _destroy: function() {
8972 if ( this.options.enhanced ) {
8973 return;
8974 }
8975 this.on.remove();
8976 this.off.remove();
8977 this.element.unwrap();
8978 this.flipswitch.remove();
8979 this.removeClass( "ui-flipswitch-input" );
8980 }
8981
8982}, $.mobile.behaviors.formReset ) );
8983
8984})( jQuery );
8985
8986(function( $, undefined ) {
8987 $.widget( "mobile.rangeslider", $.extend( {
8988
8989 options: {
8990 theme: null,
8991 trackTheme: null,
8992 corners: true,
8993 mini: false,
8994 highlight: true
8995 },
8996
8997 _create: function() {
8998 var $el = this.element,
8999 elClass = this.options.mini ? "ui-rangeslider ui-mini" : "ui-rangeslider",
9000 _inputFirst = $el.find( "input" ).first(),
9001 _inputLast = $el.find( "input" ).last(),
9002 _label = $el.find( "label" ).first(),
9003 _sliderWidgetFirst = $.data( _inputFirst.get( 0 ), "mobile-slider" ) ||
9004 $.data( _inputFirst.slider().get( 0 ), "mobile-slider" ),
9005 _sliderWidgetLast = $.data( _inputLast.get(0), "mobile-slider" ) ||
9006 $.data( _inputLast.slider().get( 0 ), "mobile-slider" ),
9007 _sliderFirst = _sliderWidgetFirst.slider,
9008 _sliderLast = _sliderWidgetLast.slider,
9009 firstHandle = _sliderWidgetFirst.handle,
9010 _sliders = $( "<div class='ui-rangeslider-sliders' />" ).appendTo( $el );
9011
9012 _inputFirst.addClass( "ui-rangeslider-first" );
9013 _inputLast.addClass( "ui-rangeslider-last" );
9014 $el.addClass( elClass );
9015
9016 _sliderFirst.appendTo( _sliders );
9017 _sliderLast.appendTo( _sliders );
9018 _label.insertBefore( $el );
9019 firstHandle.prependTo( _sliderLast );
9020
9021 $.extend( this, {
9022 _inputFirst: _inputFirst,
9023 _inputLast: _inputLast,
9024 _sliderFirst: _sliderFirst,
9025 _sliderLast: _sliderLast,
9026 _label: _label,
9027 _targetVal: null,
9028 _sliderTarget: false,
9029 _sliders: _sliders,
9030 _proxy: false
9031 });
9032
9033 this.refresh();
9034 this._on( this.element.find( "input.ui-slider-input" ), {
9035 "slidebeforestart": "_slidebeforestart",
9036 "slidestop": "_slidestop",
9037 "slidedrag": "_slidedrag",
9038 "slidebeforechange": "_change",
9039 "blur": "_change",
9040 "keyup": "_change"
9041 });
9042 this._on({
9043 "mousedown":"_change"
9044 });
9045 this._on( this.element.closest( "form" ), {
9046 "reset":"_handleReset"
9047 });
9048 this._on( firstHandle, {
9049 "vmousedown": "_dragFirstHandle"
9050 });
9051 },
9052 _handleReset: function() {
9053 var self = this;
9054 //we must wait for the stack to unwind before updateing other wise sliders will not have updated yet
9055 setTimeout( function() {
9056 self._updateHighlight();
9057 },0);
9058 },
9059
9060 _dragFirstHandle: function( event ) {
9061 //if the first handle is dragged send the event to the first slider
9062 $.data( this._inputFirst.get(0), "mobile-slider" ).dragging = true;
9063 $.data( this._inputFirst.get(0), "mobile-slider" ).refresh( event );
9064 return false;
9065 },
9066
9067 _slidedrag: function( event ) {
9068 var first = $( event.target ).is( this._inputFirst ),
9069 otherSlider = ( first ) ? this._inputLast : this._inputFirst;
9070
9071 this._sliderTarget = false;
9072 //if the drag was initiated on an extreme and the other handle is focused send the events to
9073 //the closest handle
9074 if ( ( this._proxy === "first" && first ) || ( this._proxy === "last" && !first ) ) {
9075 $.data( otherSlider.get(0), "mobile-slider" ).dragging = true;
9076 $.data( otherSlider.get(0), "mobile-slider" ).refresh( event );
9077 return false;
9078 }
9079 },
9080
9081 _slidestop: function( event ) {
9082 var first = $( event.target ).is( this._inputFirst );
9083
9084 this._proxy = false;
9085 //this stops dragging of the handle and brings the active track to the front
9086 //this makes clicks on the track go the the last handle used
9087 this.element.find( "input" ).trigger( "vmouseup" );
9088 this._sliderFirst.css( "z-index", first ? 1 : "" );
9089 },
9090
9091 _slidebeforestart: function( event ) {
9092 this._sliderTarget = false;
9093 //if the track is the target remember this and the original value
9094 if ( $( event.originalEvent.target ).hasClass( "ui-slider-track" ) ) {
9095 this._sliderTarget = true;
9096 this._targetVal = $( event.target ).val();
9097 }
9098 },
9099
9100 _setOptions: function( options ) {
9101 if ( options.theme !== undefined ) {
9102 this._setTheme( options.theme );
9103 }
9104
9105 if ( options.trackTheme !== undefined ) {
9106 this._setTrackTheme( options.trackTheme );
9107 }
9108
9109 if ( options.mini !== undefined ) {
9110 this._setMini( options.mini );
9111 }
9112
9113 if ( options.highlight !== undefined ) {
9114 this._setHighlight( options.highlight );
9115 }
9116 this._super( options );
9117 this.refresh();
9118 },
9119
9120 refresh: function() {
9121 var $el = this.element,
9122 o = this.options;
9123
9124 if ( this._inputFirst.is( ":disabled" ) || this._inputLast.is( ":disabled" ) ) {
9125 this.options.disabled = true;
9126 }
9127
9128 $el.find( "input" ).slider({
9129 theme: o.theme,
9130 trackTheme: o.trackTheme,
9131 disabled: o.disabled,
9132 corners: o.corners,
9133 mini: o.mini,
9134 highlight: o.highlight
9135 }).slider( "refresh" );
9136 this._updateHighlight();
9137 },
9138
9139 _change: function( event ) {
9140 if ( event.type === "keyup" ) {
9141 this._updateHighlight();
9142 return false;
9143 }
9144
9145 var self = this,
9146 min = parseFloat( this._inputFirst.val(), 10 ),
9147 max = parseFloat( this._inputLast.val(), 10 ),
9148 first = $( event.target ).hasClass( "ui-rangeslider-first" ),
9149 thisSlider = first ? this._inputFirst : this._inputLast,
9150 otherSlider = first ? this._inputLast : this._inputFirst;
9151
9152 if ( ( this._inputFirst.val() > this._inputLast.val() && event.type === "mousedown" && !$(event.target).hasClass("ui-slider-handle")) ) {
9153 thisSlider.blur();
9154 } else if ( event.type === "mousedown" ) {
9155 return;
9156 }
9157 if ( min > max && !this._sliderTarget ) {
9158 //this prevents min from being greater then max
9159 thisSlider.val( first ? max: min ).slider( "refresh" );
9160 this._trigger( "normalize" );
9161 } else if ( min > max ) {
9162 //this makes it so clicks on the target on either extreme go to the closest handle
9163 thisSlider.val( this._targetVal ).slider( "refresh" );
9164
9165 //You must wait for the stack to unwind so first slider is updated before updating second
9166 setTimeout( function() {
9167 otherSlider.val( first ? min: max ).slider( "refresh" );
9168 $.data( otherSlider.get(0), "mobile-slider" ).handle.focus();
9169 self._sliderFirst.css( "z-index", first ? "" : 1 );
9170 self._trigger( "normalize" );
9171 }, 0 );
9172 this._proxy = ( first ) ? "first" : "last";
9173 }
9174 //fixes issue where when both _sliders are at min they cannot be adjusted
9175 if ( min === max ) {
9176 $.data( thisSlider.get(0), "mobile-slider" ).handle.css( "z-index", 1 );
9177 $.data( otherSlider.get(0), "mobile-slider" ).handle.css( "z-index", 0 );
9178 } else {
9179 $.data( otherSlider.get(0), "mobile-slider" ).handle.css( "z-index", "" );
9180 $.data( thisSlider.get(0), "mobile-slider" ).handle.css( "z-index", "" );
9181 }
9182
9183 this._updateHighlight();
9184
9185 if ( min >= max ) {
9186 return false;
9187 }
9188 },
9189
9190 _updateHighlight: function() {
9191 var min = parseInt( $.data( this._inputFirst.get(0), "mobile-slider" ).handle.get(0).style.left, 10 ),
9192 max = parseInt( $.data( this._inputLast.get(0), "mobile-slider" ).handle.get(0).style.left, 10 ),
9193 width = (max - min);
9194
9195 this.element.find( ".ui-slider-bg" ).css({
9196 "margin-left": min + "%",
9197 "width": width + "%"
9198 });
9199 },
9200
9201 _setTheme: function( value ) {
9202 this._inputFirst.slider( "option", "theme", value );
9203 this._inputLast.slider( "option", "theme", value );
9204 },
9205
9206 _setTrackTheme: function( value ) {
9207 this._inputFirst.slider( "option", "trackTheme", value );
9208 this._inputLast.slider( "option", "trackTheme", value );
9209 },
9210
9211 _setMini: function( value ) {
9212 this._inputFirst.slider( "option", "mini", value );
9213 this._inputLast.slider( "option", "mini", value );
9214 this.element.toggleClass( "ui-mini", !!value );
9215 },
9216
9217 _setHighlight: function( value ) {
9218 this._inputFirst.slider( "option", "highlight", value );
9219 this._inputLast.slider( "option", "highlight", value );
9220 },
9221
9222 _destroy: function() {
9223 this._label.prependTo( this.element );
9224 this.element.removeClass( "ui-rangeslider ui-mini" );
9225 this._inputFirst.after( this._sliderFirst );
9226 this._inputLast.after( this._sliderLast );
9227 this._sliders.remove();
9228 this.element.find( "input" ).removeClass( "ui-rangeslider-first ui-rangeslider-last" ).slider( "destroy" );
9229 }
9230
9231 }, $.mobile.behaviors.formReset ) );
9232
9233})( jQuery );
9234
9235(function( $, undefined ) {
9236
9237 $.widget( "mobile.textinput", $.mobile.textinput, {
9238 options: {
9239 clearBtn: false,
9240 clearBtnText: "Clear text"
9241 },
9242
9243 _create: function() {
9244 this._super();
9245
9246 if ( !!this.options.clearBtn || this.isSearch ) {
9247 this._addClearBtn();
9248 }
9249 },
9250
9251 clearButton: function() {
9252
9253 return $( "<a href='#' class='ui-input-clear ui-btn ui-icon-delete ui-btn-icon-notext ui-corner-all" +
9254 "' title='" + this.options.clearBtnText + "'>" + this.options.clearBtnText + "</a>" );
9255
9256 },
9257
9258 _clearBtnClick: function( event ) {
9259 this.element.val( "" )
9260 .focus()
9261 .trigger( "change" );
9262
9263 this._clearBtn.addClass( "ui-input-clear-hidden" );
9264 event.preventDefault();
9265 },
9266
9267 _addClearBtn: function() {
9268
9269 if ( !this.options.enhanced ) {
9270 this._enhanceClear();
9271 }
9272
9273 $.extend( this, {
9274 _clearBtn: this.widget().find("a.ui-input-clear")
9275 });
9276
9277 this._bindClearEvents();
9278
9279 this._toggleClear();
9280
9281 },
9282
9283 _enhanceClear: function() {
9284
9285 this.clearButton().appendTo( this.widget() );
9286 this.widget().addClass( "ui-input-has-clear" );
9287
9288 },
9289
9290 _bindClearEvents: function() {
9291
9292 this._on( this._clearBtn, {
9293 "click": "_clearBtnClick"
9294 });
9295
9296 this._on({
9297 "keyup": "_toggleClear",
9298 "change": "_toggleClear",
9299 "input": "_toggleClear",
9300 "focus": "_toggleClear",
9301 "blur": "_toggleClear",
9302 "cut": "_toggleClear",
9303 "paste": "_toggleClear"
9304
9305 });
9306
9307 },
9308
9309 _unbindClear: function() {
9310 this._off( this._clearBtn, "click");
9311 this._off( this.element, "keyup change input focus blur cut paste" );
9312 },
9313
9314 _setOptions: function( options ) {
9315 this._super( options );
9316
9317 if ( options.clearbtn !== undefined && !this.element.is( "textarea, :jqmData(type='range')" ) ) {
9318 if ( options.clearBtn ) {
9319 this._addClearBtn();
9320 } else {
9321 this._destroyClear();
9322 }
9323 }
9324
9325 if ( options.clearBtnText !== undefined && this._clearBtn !== undefined ) {
9326 this._clearBtn.text( options.clearBtnText );
9327 }
9328 },
9329
9330 _toggleClear: function() {
9331 this._delay( "_toggleClearClass", 0 );
9332 },
9333
9334 _toggleClearClass: function() {
9335 this._clearBtn.toggleClass( "ui-input-clear-hidden", !this.element.val() );
9336 },
9337
9338 _destroyClear: function() {
9339 this.element.removeClass( "ui-input-has-clear" );
9340 this._unbindClear()._clearBtn.remove();
9341 },
9342
9343 _destroy: function() {
9344 this._super();
9345 this._destroyClear();
9346 }
9347
9348 });
9349
9350})( jQuery );
9351
9352(function( $, undefined ) {
9353
9354 $.widget( "mobile.textinput", $.mobile.textinput, {
9355 options: {
9356 autogrow:true,
9357 keyupTimeoutBuffer: 100
9358 },
9359
9360 _create: function() {
9361 this._super();
9362
9363 if ( this.options.autogrow && this.isTextarea ) {
9364 this._autogrow();
9365 }
9366 },
9367
9368 _autogrow: function() {
9369 this._on({
9370 "keyup": "_timeout",
9371 "change": "_timeout",
9372 "input": "_timeout",
9373 "paste": "_timeout",
9374 });
9375
9376 // Attach to the various you-have-become-visible notifications that the
9377 // various framework elements emit.
9378 // TODO: Remove all but the updatelayout handler once #6426 is fixed.
9379 this._on( true, this.document, {
9380
9381 // TODO: Move to non-deprecated event
9382 "pageshow": "_handleShow",
9383 "popupbeforeposition": "_handleShow",
9384 "updatelayout": "_handleShow",
9385 "panelopen": "_handleShow"
9386 });
9387 },
9388
9389 // Synchronously fix the widget height if this widget's parents are such
9390 // that they show/hide content at runtime. We still need to check whether
9391 // the widget is actually visible in case it is contained inside multiple
9392 // such containers. For example: panel contains collapsible contains
9393 // autogrow textinput. The panel may emit "panelopen" indicating that its
9394 // content has become visible, but the collapsible is still collapsed, so
9395 // the autogrow textarea is still not visible.
9396 _handleShow: function( event ) {
9397 if ( $.contains( event.target, this.element[ 0 ] ) &&
9398 this.element.is( ":visible" ) ) {
9399
9400 if ( event.type !== "popupbeforeposition" ) {
9401 this.element
9402 .addClass( "ui-textinput-autogrow-resize" )
9403 .one( "transitionend webkitTransitionEnd oTransitionEnd",
9404 $.proxy( function() {
9405 this.element.removeClass( "ui-textinput-autogrow-resize" );
9406 }, this ) );
9407 }
9408 this._prepareHeightUpdate();
9409 }
9410 },
9411
9412 _unbindAutogrow: function() {
9413 this._off( this.element, "keyup change input paste" );
9414 this._off( this.document,
9415 "pageshow popupbeforeposition updatelayout panelopen" );
9416 },
9417
9418 keyupTimeout: null,
9419
9420 _prepareHeightUpdate: function( delay ) {
9421 if ( this.keyupTimeout ) {
9422 clearTimeout( this.keyupTimeout );
9423 }
9424 if ( delay === undefined ) {
9425 this._updateHeight();
9426 } else {
9427 this.keyupTimeout = this._delay( "_updateHeight", delay );
9428 }
9429 },
9430
9431 _timeout: function() {
9432 this._prepareHeightUpdate( this.options.keyupTimeoutBuffer );
9433 },
9434
9435 _updateHeight: function() {
9436
9437 this.keyupTimeout = 0;
9438
9439 this.element.css({
9440 "height": 0,
9441 "min-height": 0,
9442 "max-height": 0
9443 });
9444
9445 var paddingTop, paddingBottom, paddingHeight,
9446 scrollHeight = this.element[ 0 ].scrollHeight,
9447 clientHeight = this.element[ 0 ].clientHeight,
9448 borderTop = parseFloat( this.element.css( "border-top-width" ) ),
9449 borderBottom = parseFloat( this.element.css( "border-bottom-width" ) ),
9450 borderHeight = borderTop + borderBottom,
9451 height = scrollHeight + borderHeight + 15;
9452
9453 // Issue 6179: Padding is not included in scrollHeight and
9454 // clientHeight by Firefox if no scrollbar is visible. Because
9455 // textareas use the border-box box-sizing model, padding should be
9456 // included in the new (assigned) height. Because the height is set
9457 // to 0, clientHeight == 0 in Firefox. Therefore, we can use this to
9458 // check if padding must be added.
9459 if ( clientHeight === 0 ) {
9460 paddingTop = parseFloat( this.element.css( "padding-top" ) );
9461 paddingBottom = parseFloat( this.element.css( "padding-bottom" ) );
9462 paddingHeight = paddingTop + paddingBottom;
9463
9464 height += paddingHeight;
9465 }
9466
9467 this.element.css({
9468 "height": height,
9469 "min-height": "",
9470 "max-height": ""
9471 });
9472 },
9473
9474 refresh: function() {
9475 if ( this.options.autogrow && this.isTextarea ) {
9476 this._updateHeight();
9477 }
9478 },
9479
9480 _setOptions: function( options ) {
9481
9482 this._super( options );
9483
9484 if ( options.autogrow !== undefined && this.isTextarea ) {
9485 if ( options.autogrow ) {
9486 this._autogrow();
9487 } else {
9488 this._unbindAutogrow();
9489 }
9490 }
9491 }
9492
9493 });
9494})( jQuery );
9495
9496(function( $, undefined ) {
9497
9498$.widget( "mobile.selectmenu", $.extend( {
9499 initSelector: "select:not( :jqmData(role='slider')):not( :jqmData(role='flipswitch') )",
9500
9501 options: {
9502 theme: null,
9503 icon: "carat-d",
9504 iconpos: "right",
9505 inline: false,
9506 corners: true,
9507 shadow: true,
9508 iconshadow: false, /* TODO: Deprecated in 1.4, remove in 1.5. */
9509 overlayTheme: null,
9510 dividerTheme: null,
9511 hidePlaceholderMenuItems: true,
9512 closeText: "Close",
9513 nativeMenu: true,
9514 // This option defaults to true on iOS devices.
9515 preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
9516 mini: false
9517 },
9518
9519 _button: function() {
9520 return $( "<div/>" );
9521 },
9522
9523 _setDisabled: function( value ) {
9524 this.element.attr( "disabled", value );
9525 this.button.attr( "aria-disabled", value );
9526 return this._setOption( "disabled", value );
9527 },
9528
9529 _focusButton : function() {
9530 var self = this;
9531
9532 setTimeout( function() {
9533 self.button.focus();
9534 }, 40);
9535 },
9536
9537 _selectOptions: function() {
9538 return this.select.find( "option" );
9539 },
9540
9541 // setup items that are generally necessary for select menu extension
9542 _preExtension: function() {
9543 var inline = this.options.inline || this.element.jqmData( "inline" ),
9544 mini = this.options.mini || this.element.jqmData( "mini" ),
9545 classes = "";
9546 // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577
9547 /* if ( $el[0].className.length ) {
9548 classes = $el[0].className;
9549 } */
9550 if ( !!~this.element[0].className.indexOf( "ui-btn-left" ) ) {
9551 classes = " ui-btn-left";
9552 }
9553
9554 if ( !!~this.element[0].className.indexOf( "ui-btn-right" ) ) {
9555 classes = " ui-btn-right";
9556 }
9557
9558 if ( inline ) {
9559 classes += " ui-btn-inline";
9560 }
9561 if ( mini ) {
9562 classes += " ui-mini";
9563 }
9564
9565 this.select = this.element.removeClass( "ui-btn-left ui-btn-right" ).wrap( "<div class='ui-select" + classes + "'>" );
9566 this.selectId = this.select.attr( "id" ) || ( "select-" + this.uuid );
9567 this.buttonId = this.selectId + "-button";
9568 this.label = $( "label[for='"+ this.selectId +"']" );
9569 this.isMultiple = this.select[ 0 ].multiple;
9570 },
9571
9572 _destroy: function() {
9573 var wrapper = this.element.parents( ".ui-select" );
9574 if ( wrapper.length > 0 ) {
9575 if ( wrapper.is( ".ui-btn-left, .ui-btn-right" ) ) {
9576 this.element.addClass( wrapper.hasClass( "ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" );
9577 }
9578 this.element.insertAfter( wrapper );
9579 wrapper.remove();
9580 }
9581 },
9582
9583 _create: function() {
9584 this._preExtension();
9585
9586 this.button = this._button();
9587
9588 var self = this,
9589
9590 options = this.options,
9591
9592 iconpos = options.icon ? ( options.iconpos || this.select.jqmData( "iconpos" ) ) : false,
9593
9594 button = this.button
9595 .insertBefore( this.select )
9596 .attr( "id", this.buttonId )
9597 .addClass( "ui-btn" +
9598 ( options.icon ? ( " ui-icon-" + options.icon + " ui-btn-icon-" + iconpos +
9599 ( options.iconshadow ? " ui-shadow-icon" : "" ) ) : "" ) + /* TODO: Remove in 1.5. */
9600 ( options.theme ? " ui-btn-" + options.theme : "" ) +
9601 ( options.corners ? " ui-corner-all" : "" ) +
9602 ( options.shadow ? " ui-shadow" : "" ) );
9603
9604 this.setButtonText();
9605
9606 // Opera does not properly support opacity on select elements
9607 // In Mini, it hides the element, but not its text
9608 // On the desktop,it seems to do the opposite
9609 // for these reasons, using the nativeMenu option results in a full native select in Opera
9610 if ( options.nativeMenu && window.opera && window.opera.version ) {
9611 button.addClass( "ui-select-nativeonly" );
9612 }
9613
9614 // Add counter for multi selects
9615 if ( this.isMultiple ) {
9616 this.buttonCount = $( "<span>" )
9617 .addClass( "ui-li-count ui-body-inherit" )
9618 .hide()
9619 .appendTo( button.addClass( "ui-li-has-count" ) );
9620 }
9621
9622 // Disable if specified
9623 if ( options.disabled || this.element.attr( "disabled" )) {
9624 this.disable();
9625 }
9626
9627 // Events on native select
9628 this.select.change(function() {
9629 self.refresh();
9630
9631 if ( !!options.nativeMenu ) {
9632 this.blur();
9633 }
9634 });
9635
9636 this._handleFormReset();
9637
9638 this._on( this.button, {
9639 keydown: "_handleKeydown"
9640 });
9641
9642 this.build();
9643 },
9644
9645 build: function() {
9646 var self = this;
9647
9648 this.select
9649 .appendTo( self.button )
9650 .bind( "vmousedown", function() {
9651 // Add active class to button
9652 self.button.addClass( $.mobile.activeBtnClass );
9653 })
9654 .bind( "focus", function() {
9655 self.button.addClass( $.mobile.focusClass );
9656 })
9657 .bind( "blur", function() {
9658 self.button.removeClass( $.mobile.focusClass );
9659 })
9660 .bind( "focus vmouseover", function() {
9661 self.button.trigger( "vmouseover" );
9662 })
9663 .bind( "vmousemove", function() {
9664 // Remove active class on scroll/touchmove
9665 self.button.removeClass( $.mobile.activeBtnClass );
9666 })
9667 .bind( "change blur vmouseout", function() {
9668 self.button.trigger( "vmouseout" )
9669 .removeClass( $.mobile.activeBtnClass );
9670 });
9671
9672 // In many situations, iOS will zoom into the select upon tap, this prevents that from happening
9673 self.button.bind( "vmousedown", function() {
9674 if ( self.options.preventFocusZoom ) {
9675 $.mobile.zoom.disable( true );
9676 }
9677 });
9678 self.label.bind( "click focus", function() {
9679 if ( self.options.preventFocusZoom ) {
9680 $.mobile.zoom.disable( true );
9681 }
9682 });
9683 self.select.bind( "focus", function() {
9684 if ( self.options.preventFocusZoom ) {
9685 $.mobile.zoom.disable( true );
9686 }
9687 });
9688 self.button.bind( "mouseup", function() {
9689 if ( self.options.preventFocusZoom ) {
9690 setTimeout(function() {
9691 $.mobile.zoom.enable( true );
9692 }, 0 );
9693 }
9694 });
9695 self.select.bind( "blur", function() {
9696 if ( self.options.preventFocusZoom ) {
9697 $.mobile.zoom.enable( true );
9698 }
9699 });
9700
9701 },
9702
9703 selected: function() {
9704 return this._selectOptions().filter( ":selected" );
9705 },
9706
9707 selectedIndices: function() {
9708 var self = this;
9709
9710 return this.selected().map(function() {
9711 return self._selectOptions().index( this );
9712 }).get();
9713 },
9714
9715 setButtonText: function() {
9716 var self = this,
9717 selected = this.selected(),
9718 text = this.placeholder,
9719 span = $( document.createElement( "span" ) );
9720
9721 this.button.children( "span" ).not( ".ui-li-count" ).remove().end().end().prepend( (function() {
9722 if ( selected.length ) {
9723 text = selected.map(function() {
9724 return $( this ).text();
9725 }).get().join( ", " );
9726 } else {
9727 text = self.placeholder;
9728 }
9729
9730 if ( text ) {
9731 span.text( text );
9732 } else {
9733 span.html( " " );
9734 }
9735
9736 // TODO possibly aggregate multiple select option classes
9737 return span
9738 .addClass( self.select.attr( "class" ) )
9739 .addClass( selected.attr( "class" ) )
9740 .removeClass( "ui-screen-hidden" );
9741 })());
9742 },
9743
9744 setButtonCount: function() {
9745 var selected = this.selected();
9746
9747 // multiple count inside button
9748 if ( this.isMultiple ) {
9749 this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
9750 }
9751 },
9752
9753 _handleKeydown: function( /* event */ ) {
9754 this._delay( "_refreshButton" );
9755 },
9756
9757 _reset: function() {
9758 this.refresh();
9759 },
9760
9761 _refreshButton: function() {
9762 this.setButtonText();
9763 this.setButtonCount();
9764 },
9765
9766 refresh: function() {
9767 this._refreshButton();
9768 },
9769
9770 // open and close preserved in native selects
9771 // to simplify users code when looping over selects
9772 open: $.noop,
9773 close: $.noop,
9774
9775 disable: function() {
9776 this._setDisabled( true );
9777 this.button.addClass( "ui-state-disabled" );
9778 },
9779
9780 enable: function() {
9781 this._setDisabled( false );
9782 this.button.removeClass( "ui-state-disabled" );
9783 }
9784}, $.mobile.behaviors.formReset ) );
9785
9786})( jQuery );
9787
9788(function( $, undefined ) {
9789
9790$.mobile.links = function( target ) {
9791
9792 //links within content areas, tests included with page
9793 $( target )
9794 .find( "a" )
9795 .jqmEnhanceable()
9796 .filter( ":jqmData(rel='popup')[href][href!='']" )
9797 .each( function() {
9798 // Accessibility info for popups
9799 var element = this,
9800 idref = element.getAttribute( "href" ).substring( 1 );
9801
9802 if ( idref ) {
9803 element.setAttribute( "aria-haspopup", true );
9804 element.setAttribute( "aria-owns", idref );
9805 element.setAttribute( "aria-expanded", false );
9806 }
9807 })
9808 .end()
9809 .not( ".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
9810 .addClass( "ui-link" );
9811
9812};
9813
9814})( jQuery );
9815
9816
9817(function( $, undefined ) {
9818
9819function fitSegmentInsideSegment( windowSize, segmentSize, offset, desired ) {
9820 var returnValue = desired;
9821
9822 if ( windowSize < segmentSize ) {
9823 // Center segment if it's bigger than the window
9824 returnValue = offset + ( windowSize - segmentSize ) / 2;
9825 } else {
9826 // Otherwise center it at the desired coordinate while keeping it completely inside the window
9827 returnValue = Math.min( Math.max( offset, desired - segmentSize / 2 ), offset + windowSize - segmentSize );
9828 }
9829
9830 return returnValue;
9831}
9832
9833function getWindowCoordinates( theWindow ) {
9834 return {
9835 x: theWindow.scrollLeft(),
9836 y: theWindow.scrollTop(),
9837 cx: ( theWindow[ 0 ].innerWidth || theWindow.width() ),
9838 cy: ( theWindow[ 0 ].innerHeight || theWindow.height() )
9839 };
9840}
9841
9842$.widget( "mobile.popup", {
9843 options: {
9844 wrapperClass: null,
9845 theme: null,
9846 overlayTheme: null,
9847 shadow: true,
9848 corners: true,
9849 transition: "none",
9850 positionTo: "origin",
9851 tolerance: null,
9852 closeLinkSelector: "a:jqmData(rel='back')",
9853 closeLinkEvents: "click.popup",
9854 navigateEvents: "navigate.popup",
9855 closeEvents: "navigate.popup pagebeforechange.popup",
9856 dismissible: true,
9857 enhanced: false,
9858
9859 // NOTE Windows Phone 7 has a scroll position caching issue that
9860 // requires us to disable popup history management by default
9861 // https://github.com/jquery/jquery-mobile/issues/4784
9862 //
9863 // NOTE this option is modified in _create!
9864 history: !$.mobile.browser.oldIE
9865 },
9866
9867 _create: function() {
9868 var theElement = this.element,
9869 myId = theElement.attr( "id" ),
9870 currentOptions = this.options;
9871
9872 // We need to adjust the history option to be false if there's no AJAX nav.
9873 // We can't do it in the option declarations because those are run before
9874 // it is determined whether there shall be AJAX nav.
9875 currentOptions.history = currentOptions.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
9876
9877 // Define instance variables
9878 $.extend( this, {
9879 _scrollTop: 0,
9880 _page: theElement.closest( ".ui-page" ),
9881 _ui: null,
9882 _fallbackTransition: "",
9883 _currentTransition: false,
9884 _prerequisites: null,
9885 _isOpen: false,
9886 _tolerance: null,
9887 _resizeData: null,
9888 _ignoreResizeTo: 0,
9889 _orientationchangeInProgress: false
9890 });
9891
9892 if ( this._page.length === 0 ) {
9893 this._page = $( "body" );
9894 }
9895
9896 if ( currentOptions.enhanced ) {
9897 this._ui = {
9898 container: theElement.parent(),
9899 screen: theElement.parent().prev(),
9900 placeholder: $( this.document[ 0 ].getElementById( myId + "-placeholder" ) )
9901 };
9902 } else {
9903 this._ui = this._enhance( theElement, myId );
9904 this._applyTransition( currentOptions.transition );
9905 }
9906 this
9907 ._setTolerance( currentOptions.tolerance )
9908 ._ui.focusElement = this._ui.container;
9909
9910 // Event handlers
9911 this._on( this._ui.screen, { "vclick": "_eatEventAndClose" } );
9912 this._on( this.window, {
9913 orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
9914 resize: $.proxy( this, "_handleWindowResize" ),
9915 keyup: $.proxy( this, "_handleWindowKeyUp" )
9916 });
9917 this._on( this.document, { "focusin": "_handleDocumentFocusIn" } );
9918 },
9919
9920 _enhance: function( theElement, myId ) {
9921 var currentOptions = this.options,
9922 wrapperClass = currentOptions.wrapperClass,
9923 ui = {
9924 screen: $( "<div class='ui-screen-hidden ui-popup-screen " +
9925 this._themeClassFromOption( "ui-overlay-", currentOptions.overlayTheme ) + "'></div>" ),
9926 placeholder: $( "<div style='display: none;'><!-- placeholder --></div>" ),
9927 container: $( "<div class='ui-popup-container ui-popup-hidden ui-popup-truncate" +
9928 ( wrapperClass ? ( " " + wrapperClass ) : "" ) + "'></div>" )
9929 },
9930 fragment = this.document[ 0 ].createDocumentFragment();
9931
9932 fragment.appendChild( ui.screen[ 0 ] );
9933 fragment.appendChild( ui.container[ 0 ] );
9934
9935 if ( myId ) {
9936 ui.screen.attr( "id", myId + "-screen" );
9937 ui.container.attr( "id", myId + "-popup" );
9938 ui.placeholder
9939 .attr( "id", myId + "-placeholder" )
9940 .html( "<!-- placeholder for " + myId + " -->" );
9941 }
9942
9943 // Apply the proto
9944 this._page[ 0 ].appendChild( fragment );
9945 // Leave a placeholder where the element used to be
9946 ui.placeholder.insertAfter( theElement );
9947 theElement
9948 .detach()
9949 .addClass( "ui-popup " +
9950 this._themeClassFromOption( "ui-body-", currentOptions.theme ) + " " +
9951 ( currentOptions.shadow ? "ui-overlay-shadow " : "" ) +
9952 ( currentOptions.corners ? "ui-corner-all " : "" ) )
9953 .appendTo( ui.container );
9954
9955 return ui;
9956 },
9957
9958 _eatEventAndClose: function( theEvent ) {
9959 theEvent.preventDefault();
9960 theEvent.stopImmediatePropagation();
9961 if ( this.options.dismissible ) {
9962 this.close();
9963 }
9964 return false;
9965 },
9966
9967 // Make sure the screen covers the entire document - CSS is sometimes not
9968 // enough to accomplish this.
9969 _resizeScreen: function() {
9970 var screen = this._ui.screen,
9971 popupHeight = this._ui.container.outerHeight( true ),
9972 screenHeight = screen.removeAttr( "style" ).height(),
9973
9974 // Subtracting 1 here is necessary for an obscure Andrdoid 4.0 bug where
9975 // the browser hangs if the screen covers the entire document :/
9976 documentHeight = this.document.height() - 1;
9977
9978 if ( screenHeight < documentHeight ) {
9979 screen.height( documentHeight );
9980 } else if ( popupHeight > screenHeight ) {
9981 screen.height( popupHeight );
9982 }
9983 },
9984
9985 _handleWindowKeyUp: function( theEvent ) {
9986 if ( this._isOpen && theEvent.keyCode === $.mobile.keyCode.ESCAPE ) {
9987 return this._eatEventAndClose( theEvent );
9988 }
9989 },
9990
9991 _expectResizeEvent: function() {
9992 var windowCoordinates = getWindowCoordinates( this.window );
9993
9994 if ( this._resizeData ) {
9995 if ( windowCoordinates.x === this._resizeData.windowCoordinates.x &&
9996 windowCoordinates.y === this._resizeData.windowCoordinates.y &&
9997 windowCoordinates.cx === this._resizeData.windowCoordinates.cx &&
9998 windowCoordinates.cy === this._resizeData.windowCoordinates.cy ) {
9999 // timeout not refreshed
10000 return false;
10001 } else {
10002 // clear existing timeout - it will be refreshed below
10003 clearTimeout( this._resizeData.timeoutId );
10004 }
10005 }
10006
10007 this._resizeData = {
10008 timeoutId: this._delay( "_resizeTimeout", 200 ),
10009 windowCoordinates: windowCoordinates
10010 };
10011
10012 return true;
10013 },
10014
10015 _resizeTimeout: function() {
10016 if ( this._isOpen ) {
10017 if ( !this._expectResizeEvent() ) {
10018 if ( this._ui.container.hasClass( "ui-popup-hidden" ) ) {
10019 // effectively rapid-open the popup while leaving the screen intact
10020 this._ui.container.removeClass( "ui-popup-hidden ui-popup-truncate" );
10021 this.reposition( { positionTo: "window" } );
10022 this._ignoreResizeEvents();
10023 }
10024
10025 this._resizeScreen();
10026 this._resizeData = null;
10027 this._orientationchangeInProgress = false;
10028 }
10029 } else {
10030 this._resizeData = null;
10031 this._orientationchangeInProgress = false;
10032 }
10033 },
10034
10035 _stopIgnoringResizeEvents: function() {
10036 this._ignoreResizeTo = 0;
10037 },
10038
10039 _ignoreResizeEvents: function() {
10040 if ( this._ignoreResizeTo ) {
10041 clearTimeout( this._ignoreResizeTo );
10042 }
10043 this._ignoreResizeTo = this._delay( "_stopIgnoringResizeEvents", 1000 );
10044 },
10045
10046 _handleWindowResize: function(/* theEvent */) {
10047 if ( this._isOpen && this._ignoreResizeTo === 0 ) {
10048 if ( ( this._expectResizeEvent() || this._orientationchangeInProgress ) &&
10049 !this._ui.container.hasClass( "ui-popup-hidden" ) ) {
10050 // effectively rapid-close the popup while leaving the screen intact
10051 this._ui.container
10052 .addClass( "ui-popup-hidden ui-popup-truncate" )
10053 .removeAttr( "style" );
10054 }
10055 }
10056 },
10057
10058 _handleWindowOrientationchange: function(/* theEvent */) {
10059 if ( !this._orientationchangeInProgress && this._isOpen && this._ignoreResizeTo === 0 ) {
10060 this._expectResizeEvent();
10061 this._orientationchangeInProgress = true;
10062 }
10063 },
10064
10065 // When the popup is open, attempting to focus on an element that is not a
10066 // child of the popup will redirect focus to the popup
10067 _handleDocumentFocusIn: function( theEvent ) {
10068 var target,
10069 targetElement = theEvent.target,
10070 ui = this._ui;
10071
10072 if ( !this._isOpen ) {
10073 return;
10074 }
10075
10076 if ( targetElement !== ui.container[ 0 ] ) {
10077 target = $( targetElement );
10078 if ( 0 === target.parents().filter( ui.container[ 0 ] ).length ) {
10079 $( this.document[ 0 ].activeElement ).one( "focus", function(/* theEvent */) {
10080 target.blur();
10081 });
10082 ui.focusElement.focus();
10083 theEvent.preventDefault();
10084 theEvent.stopImmediatePropagation();
10085 return false;
10086 } else if ( ui.focusElement[ 0 ] === ui.container[ 0 ] ) {
10087 ui.focusElement = target;
10088 }
10089 }
10090
10091 this._ignoreResizeEvents();
10092 },
10093
10094 _themeClassFromOption: function( prefix, value ) {
10095 return ( value ? ( value === "none" ? "" : ( prefix + value ) ) : ( prefix + "inherit" ) );
10096 },
10097
10098 _applyTransition: function( value ) {
10099 if ( value ) {
10100 this._ui.container.removeClass( this._fallbackTransition );
10101 if ( value !== "none" ) {
10102 this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
10103 if ( this._fallbackTransition === "none" ) {
10104 this._fallbackTransition = "";
10105 }
10106 this._ui.container.addClass( this._fallbackTransition );
10107 }
10108 }
10109
10110 return this;
10111 },
10112
10113 _setOptions: function( newOptions ) {
10114 var currentOptions = this.options,
10115 theElement = this.element,
10116 screen = this._ui.screen;
10117
10118 if ( newOptions.wrapperClass !== undefined ) {
10119 this._ui.container
10120 .removeClass( currentOptions.wrapperClass )
10121 .addClass( newOptions.wrapperClass );
10122 }
10123
10124 if ( newOptions.theme !== undefined ) {
10125 theElement
10126 .removeClass( this._themeClassFromOption( "ui-body-", currentOptions.theme ) )
10127 .addClass( this._themeClassFromOption( "ui-body-", newOptions.theme ) );
10128 }
10129
10130 if ( newOptions.overlayTheme !== undefined ) {
10131 screen
10132 .removeClass( this._themeClassFromOption( "ui-overlay-", currentOptions.overlayTheme ) )
10133 .addClass( this._themeClassFromOption( "ui-overlay-", newOptions.overlayTheme ) );
10134
10135 if ( this._isOpen ) {
10136 screen.addClass( "in" );
10137 }
10138 }
10139
10140 if ( newOptions.shadow !== undefined ) {
10141 theElement.toggleClass( "ui-overlay-shadow", newOptions.shadow );
10142 }
10143
10144 if ( newOptions.corners !== undefined ) {
10145 theElement.toggleClass( "ui-corner-all", newOptions.corners );
10146 }
10147
10148 if ( newOptions.transition !== undefined ) {
10149 if ( !this._currentTransition ) {
10150 this._applyTransition( newOptions.transition );
10151 }
10152 }
10153
10154 if ( newOptions.tolerance !== undefined ) {
10155 this._setTolerance( newOptions.tolerance );
10156 }
10157
10158 if ( newOptions.disabled !== undefined ) {
10159 if ( newOptions.disabled ) {
10160 this.close();
10161 }
10162 }
10163
10164 return this._super( newOptions );
10165 },
10166
10167 _setTolerance: function( value ) {
10168 var tol = { t: 30, r: 15, b: 30, l: 15 },
10169 ar;
10170
10171 if ( value !== undefined ) {
10172 ar = String( value ).split( "," );
10173
10174 $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
10175
10176 switch( ar.length ) {
10177 // All values are to be the same
10178 case 1:
10179 if ( !isNaN( ar[ 0 ] ) ) {
10180 tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
10181 }
10182 break;
10183
10184 // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
10185 case 2:
10186 if ( !isNaN( ar[ 0 ] ) ) {
10187 tol.t = tol.b = ar[ 0 ];
10188 }
10189 if ( !isNaN( ar[ 1 ] ) ) {
10190 tol.l = tol.r = ar[ 1 ];
10191 }
10192 break;
10193
10194 // The array contains values in the order top, right, bottom, left
10195 case 4:
10196 if ( !isNaN( ar[ 0 ] ) ) {
10197 tol.t = ar[ 0 ];
10198 }
10199 if ( !isNaN( ar[ 1 ] ) ) {
10200 tol.r = ar[ 1 ];
10201 }
10202 if ( !isNaN( ar[ 2 ] ) ) {
10203 tol.b = ar[ 2 ];
10204 }
10205 if ( !isNaN( ar[ 3 ] ) ) {
10206 tol.l = ar[ 3 ];
10207 }
10208 break;
10209
10210 default:
10211 break;
10212 }
10213 }
10214
10215 this._tolerance = tol;
10216 return this;
10217 },
10218
10219 _clampPopupWidth: function( infoOnly ) {
10220 var menuSize,
10221 windowCoordinates = getWindowCoordinates( this.window ),
10222 // rectangle within which the popup must fit
10223 rectangle = {
10224 x: this._tolerance.l,
10225 y: windowCoordinates.y + this._tolerance.t,
10226 cx: windowCoordinates.cx - this._tolerance.l - this._tolerance.r,
10227 cy: windowCoordinates.cy - this._tolerance.t - this._tolerance.b
10228 };
10229
10230 if ( !infoOnly ) {
10231 // Clamp the width of the menu before grabbing its size
10232 this._ui.container.css( "max-width", rectangle.cx );
10233 }
10234
10235 menuSize = {
10236 cx: this._ui.container.outerWidth( true ),
10237 cy: this._ui.container.outerHeight( true )
10238 };
10239
10240 return { rc: rectangle, menuSize: menuSize };
10241 },
10242
10243 _calculateFinalLocation: function( desired, clampInfo ) {
10244 var returnValue,
10245 rectangle = clampInfo.rc,
10246 menuSize = clampInfo.menuSize;
10247
10248 // Center the menu over the desired coordinates, while not going outside
10249 // the window tolerances. This will center wrt. the window if the popup is
10250 // too large.
10251 returnValue = {
10252 left: fitSegmentInsideSegment( rectangle.cx, menuSize.cx, rectangle.x, desired.x ),
10253 top: fitSegmentInsideSegment( rectangle.cy, menuSize.cy, rectangle.y, desired.y )
10254 };
10255
10256 // Make sure the top of the menu is visible
10257 returnValue.top = Math.max( 0, returnValue.top );
10258
10259 // If the height of the menu is smaller than the height of the document
10260 // align the bottom with the bottom of the document
10261
10262 returnValue.top -= Math.min( returnValue.top,
10263 Math.max( 0, returnValue.top + menuSize.cy - this.document.height() ) );
10264
10265 return returnValue;
10266 },
10267
10268 // Try and center the overlay over the given coordinates
10269 _placementCoords: function( desired ) {
10270 return this._calculateFinalLocation( desired, this._clampPopupWidth() );
10271 },
10272
10273 _createPrerequisites: function( screenPrerequisite, containerPrerequisite, whenDone ) {
10274 var prerequisites,
10275 self = this;
10276
10277 // It is important to maintain both the local variable prerequisites and
10278 // self._prerequisites. The local variable remains in the closure of the
10279 // functions which call the callbacks passed in. The comparison between the
10280 // local variable and self._prerequisites is necessary, because once a
10281 // function has been passed to .animationComplete() it will be called next
10282 // time an animation completes, even if that's not the animation whose end
10283 // the function was supposed to catch (for example, if an abort happens
10284 // during the opening animation, the .animationComplete handler is not
10285 // called for that animation anymore, but the handler remains attached, so
10286 // it is called the next time the popup is opened - making it stale.
10287 // Comparing the local variable prerequisites to the widget-level variable
10288 // self._prerequisites ensures that callbacks triggered by a stale
10289 // .animationComplete will be ignored.
10290
10291 prerequisites = {
10292 screen: $.Deferred(),
10293 container: $.Deferred()
10294 };
10295
10296 prerequisites.screen.then( function() {
10297 if ( prerequisites === self._prerequisites ) {
10298 screenPrerequisite();
10299 }
10300 });
10301
10302 prerequisites.container.then( function() {
10303 if ( prerequisites === self._prerequisites ) {
10304 containerPrerequisite();
10305 }
10306 });
10307
10308 $.when( prerequisites.screen, prerequisites.container ).done( function() {
10309 if ( prerequisites === self._prerequisites ) {
10310 self._prerequisites = null;
10311 whenDone();
10312 }
10313 });
10314
10315 self._prerequisites = prerequisites;
10316 },
10317
10318 _animate: function( args ) {
10319 // NOTE before removing the default animation of the screen
10320 // this had an animate callback that would resolve the deferred
10321 // now the deferred is resolved immediately
10322 // TODO remove the dependency on the screen deferred
10323 this._ui.screen
10324 .removeClass( args.classToRemove )
10325 .addClass( args.screenClassToAdd );
10326
10327 args.prerequisites.screen.resolve();
10328
10329 if ( args.transition && args.transition !== "none" ) {
10330 if ( args.applyTransition ) {
10331 this._applyTransition( args.transition );
10332 }
10333 if ( this._fallbackTransition ) {
10334 this._ui.container
10335 .animationComplete( $.proxy( args.prerequisites.container, "resolve" ) )
10336 .addClass( args.containerClassToAdd )
10337 .removeClass( args.classToRemove );
10338 return;
10339 }
10340 }
10341 this._ui.container.removeClass( args.classToRemove );
10342 args.prerequisites.container.resolve();
10343 },
10344
10345 // The desired coordinates passed in will be returned untouched if no reference element can be identified via
10346 // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
10347 // x and y coordinates by specifying the center middle of the window if the coordinates are absent.
10348 // options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector
10349 _desiredCoords: function( openOptions ) {
10350 var offset,
10351 dst = null,
10352 windowCoordinates = getWindowCoordinates( this.window ),
10353 x = openOptions.x,
10354 y = openOptions.y,
10355 pTo = openOptions.positionTo;
10356
10357 // Establish which element will serve as the reference
10358 if ( pTo && pTo !== "origin" ) {
10359 if ( pTo === "window" ) {
10360 x = windowCoordinates.cx / 2 + windowCoordinates.x;
10361 y = windowCoordinates.cy / 2 + windowCoordinates.y;
10362 } else {
10363 try {
10364 dst = $( pTo );
10365 } catch( err ) {
10366 dst = null;
10367 }
10368 if ( dst ) {
10369 dst.filter( ":visible" );
10370 if ( dst.length === 0 ) {
10371 dst = null;
10372 }
10373 }
10374 }
10375 }
10376
10377 // If an element was found, center over it
10378 if ( dst ) {
10379 offset = dst.offset();
10380 x = offset.left + dst.outerWidth() / 2;
10381 y = offset.top + dst.outerHeight() / 2;
10382 }
10383
10384 // Make sure x and y are valid numbers - center over the window
10385 if ( $.type( x ) !== "number" || isNaN( x ) ) {
10386 x = windowCoordinates.cx / 2 + windowCoordinates.x;
10387 }
10388 if ( $.type( y ) !== "number" || isNaN( y ) ) {
10389 y = windowCoordinates.cy / 2 + windowCoordinates.y;
10390 }
10391
10392 return { x: x, y: y };
10393 },
10394
10395 _reposition: function( openOptions ) {
10396 // We only care about position-related parameters for repositioning
10397 openOptions = {
10398 x: openOptions.x,
10399 y: openOptions.y,
10400 positionTo: openOptions.positionTo
10401 };
10402 this._trigger( "beforeposition", undefined, openOptions );
10403 this._ui.container.offset( this._placementCoords( this._desiredCoords( openOptions ) ) );
10404 },
10405
10406 reposition: function( openOptions ) {
10407 if ( this._isOpen ) {
10408 this._reposition( openOptions );
10409 }
10410 },
10411
10412 _openPrerequisitesComplete: function() {
10413 var id = this.element.attr( "id" );
10414
10415 this._ui.container.addClass( "ui-popup-active" );
10416 this._isOpen = true;
10417 this._resizeScreen();
10418 this._ui.container.attr( "tabindex", "0" ).focus();
10419 this._ignoreResizeEvents();
10420 if ( id ) {
10421 this.document.find( "[aria-haspopup='true'][aria-owns='" + id + "']" ).attr( "aria-expanded", true );
10422 }
10423 this._trigger( "afteropen" );
10424 },
10425
10426 _open: function( options ) {
10427 var openOptions = $.extend( {}, this.options, options ),
10428 // TODO move blacklist to private method
10429 androidBlacklist = ( function() {
10430 var ua = navigator.userAgent,
10431 // Rendering engine is Webkit, and capture major version
10432 wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
10433 wkversion = !!wkmatch && wkmatch[ 1 ],
10434 androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
10435 andversion = !!androidmatch && androidmatch[ 1 ],
10436 chromematch = ua.indexOf( "Chrome" ) > -1;
10437
10438 // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
10439 if ( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
10440 return true;
10441 }
10442 return false;
10443 }());
10444
10445 // Count down to triggering "popupafteropen" - we have two prerequisites:
10446 // 1. The popup window animation completes (container())
10447 // 2. The screen opacity animation completes (screen())
10448 this._createPrerequisites(
10449 $.noop,
10450 $.noop,
10451 $.proxy( this, "_openPrerequisitesComplete" ) );
10452
10453 this._currentTransition = openOptions.transition;
10454 this._applyTransition( openOptions.transition );
10455
10456 this._ui.screen.removeClass( "ui-screen-hidden" );
10457 this._ui.container.removeClass( "ui-popup-truncate" );
10458
10459 // Give applications a chance to modify the contents of the container before it appears
10460 this._reposition( openOptions );
10461
10462 this._ui.container.removeClass( "ui-popup-hidden" );
10463
10464 if ( this.options.overlayTheme && androidBlacklist ) {
10465 /* TODO: The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser: https://github.com/scottjehl/Device-Bugs/issues/3
10466 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
10467 https://github.com/jquery/jquery-mobile/issues/4816
10468 https://github.com/jquery/jquery-mobile/issues/4844
10469 https://github.com/jquery/jquery-mobile/issues/4874
10470 */
10471
10472 // TODO sort out why this._page isn't working
10473 this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
10474 }
10475 this._animate({
10476 additionalCondition: true,
10477 transition: openOptions.transition,
10478 classToRemove: "",
10479 screenClassToAdd: "in",
10480 containerClassToAdd: "in",
10481 applyTransition: false,
10482 prerequisites: this._prerequisites
10483 });
10484 },
10485
10486 _closePrerequisiteScreen: function() {
10487 this._ui.screen
10488 .removeClass( "out" )
10489 .addClass( "ui-screen-hidden" );
10490 },
10491
10492 _closePrerequisiteContainer: function() {
10493 this._ui.container
10494 .removeClass( "reverse out" )
10495 .addClass( "ui-popup-hidden ui-popup-truncate" )
10496 .removeAttr( "style" );
10497 },
10498
10499 _closePrerequisitesDone: function() {
10500 var container = this._ui.container,
10501 id = this.element.attr( "id" );
10502
10503 container.removeAttr( "tabindex" );
10504
10505 // remove the global mutex for popups
10506 $.mobile.popup.active = undefined;
10507
10508 // Blur elements inside the container, including the container
10509 $( ":focus", container[ 0 ] ).add( container[ 0 ] ).blur();
10510
10511 if ( id ) {
10512 this.document.find( "[aria-haspopup='true'][aria-owns='" + id + "']" ).attr( "aria-expanded", false );
10513 }
10514
10515 // alert users that the popup is closed
10516 this._trigger( "afterclose" );
10517 },
10518
10519 _close: function( immediate ) {
10520 this._ui.container.removeClass( "ui-popup-active" );
10521 this._page.removeClass( "ui-popup-open" );
10522
10523 this._isOpen = false;
10524
10525 // Count down to triggering "popupafterclose" - we have two prerequisites:
10526 // 1. The popup window reverse animation completes (container())
10527 // 2. The screen opacity animation completes (screen())
10528 this._createPrerequisites(
10529 $.proxy( this, "_closePrerequisiteScreen" ),
10530 $.proxy( this, "_closePrerequisiteContainer" ),
10531 $.proxy( this, "_closePrerequisitesDone" ) );
10532
10533 this._animate( {
10534 additionalCondition: this._ui.screen.hasClass( "in" ),
10535 transition: ( immediate ? "none" : ( this._currentTransition ) ),
10536 classToRemove: "in",
10537 screenClassToAdd: "out",
10538 containerClassToAdd: "reverse out",
10539 applyTransition: true,
10540 prerequisites: this._prerequisites
10541 });
10542 },
10543
10544 _unenhance: function() {
10545 if ( this.options.enhanced ) {
10546 return;
10547 }
10548
10549 // Put the element back to where the placeholder was and remove the "ui-popup" class
10550 this._setOptions( { theme: $.mobile.popup.prototype.options.theme } );
10551 this.element
10552 // Cannot directly insertAfter() - we need to detach() first, because
10553 // insertAfter() will do nothing if the payload div was not attached
10554 // to the DOM at the time the widget was created, and so the payload
10555 // will remain inside the container even after we call insertAfter().
10556 // If that happens and we remove the container a few lines below, we
10557 // will cause an infinite recursion - #5244
10558 .detach()
10559 .insertAfter( this._ui.placeholder )
10560 .removeClass( "ui-popup ui-overlay-shadow ui-corner-all ui-body-inherit" );
10561 this._ui.screen.remove();
10562 this._ui.container.remove();
10563 this._ui.placeholder.remove();
10564 },
10565
10566 _destroy: function() {
10567 if ( $.mobile.popup.active === this ) {
10568 this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) );
10569 this.close();
10570 } else {
10571 this._unenhance();
10572 }
10573
10574 return this;
10575 },
10576
10577 _closePopup: function( theEvent, data ) {
10578 var parsedDst, toUrl,
10579 currentOptions = this.options,
10580 immediate = false;
10581
10582 if ( ( theEvent && theEvent.isDefaultPrevented() ) || $.mobile.popup.active !== this ) {
10583 return;
10584 }
10585
10586 // restore location on screen
10587 window.scrollTo( 0, this._scrollTop );
10588
10589 if ( theEvent && theEvent.type === "pagebeforechange" && data ) {
10590 // Determine whether we need to rapid-close the popup, or whether we can
10591 // take the time to run the closing transition
10592 if ( typeof data.toPage === "string" ) {
10593 parsedDst = data.toPage;
10594 } else {
10595 parsedDst = data.toPage.jqmData( "url" );
10596 }
10597 parsedDst = $.mobile.path.parseUrl( parsedDst );
10598 toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash;
10599
10600 if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ) {
10601 // Going to a different page - close immediately
10602 immediate = true;
10603 } else {
10604 theEvent.preventDefault();
10605 }
10606 }
10607
10608 // remove nav bindings
10609 this.window.off( currentOptions.closeEvents );
10610 // unbind click handlers added when history is disabled
10611 this.element.undelegate( currentOptions.closeLinkSelector, currentOptions.closeLinkEvents );
10612
10613 this._close( immediate );
10614 },
10615
10616 // any navigation event after a popup is opened should close the popup
10617 // NOTE the pagebeforechange is bound to catch navigation events that don't
10618 // alter the url (eg, dialogs from popups)
10619 _bindContainerClose: function() {
10620 this.window
10621 .on( this.options.closeEvents, $.proxy( this, "_closePopup" ) );
10622 },
10623
10624 widget: function() {
10625 return this._ui.container;
10626 },
10627
10628 // TODO no clear deliniation of what should be here and
10629 // what should be in _open. Seems to be "visual" vs "history" for now
10630 open: function( options ) {
10631 var url, hashkey, activePage, currentIsDialog, hasHash, urlHistory,
10632 self = this,
10633 currentOptions = this.options;
10634
10635 // make sure open is idempotent
10636 if ( $.mobile.popup.active || currentOptions.disabled ) {
10637 return this;
10638 }
10639
10640 // set the global popup mutex
10641 $.mobile.popup.active = this;
10642 this._scrollTop = this.window.scrollTop();
10643
10644 // if history alteration is disabled close on navigate events
10645 // and leave the url as is
10646 if ( !( currentOptions.history ) ) {
10647 self._open( options );
10648 self._bindContainerClose();
10649
10650 // When histoy is disabled we have to grab the data-rel
10651 // back link clicks so we can close the popup instead of
10652 // relying on history to do it for us
10653 self.element
10654 .delegate( currentOptions.closeLinkSelector, currentOptions.closeLinkEvents, function( theEvent ) {
10655 self.close();
10656 theEvent.preventDefault();
10657 });
10658
10659 return this;
10660 }
10661
10662 // cache some values for min/readability
10663 urlHistory = $.mobile.navigate.history;
10664 hashkey = $.mobile.dialogHashKey;
10665 activePage = $.mobile.activePage;
10666 currentIsDialog = ( activePage ? activePage.hasClass( "ui-dialog" ) : false );
10667 this._myUrl = url = urlHistory.getActive().url;
10668 hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 );
10669
10670 if ( hasHash ) {
10671 self._open( options );
10672 self._bindContainerClose();
10673 return this;
10674 }
10675
10676 // if the current url has no dialog hash key proceed as normal
10677 // otherwise, if the page is a dialog simply tack on the hash key
10678 if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ) {
10679 url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey);
10680 } else {
10681 url = $.mobile.path.parseLocation().hash + hashkey;
10682 }
10683
10684 // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
10685 if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
10686 url += hashkey;
10687 }
10688
10689 // swallow the the initial navigation event, and bind for the next
10690 this.window.one( "beforenavigate", function( theEvent ) {
10691 theEvent.preventDefault();
10692 self._open( options );
10693 self._bindContainerClose();
10694 });
10695
10696 this.urlAltered = true;
10697 $.mobile.navigate( url, { role: "dialog" } );
10698
10699 return this;
10700 },
10701
10702 close: function() {
10703 // make sure close is idempotent
10704 if ( $.mobile.popup.active !== this ) {
10705 return this;
10706 }
10707
10708 this._scrollTop = this.window.scrollTop();
10709
10710 if ( this.options.history && this.urlAltered ) {
10711 $.mobile.back();
10712 this.urlAltered = false;
10713 } else {
10714 // simulate the nav bindings having fired
10715 this._closePopup();
10716 }
10717
10718 return this;
10719 }
10720});
10721
10722// TODO this can be moved inside the widget
10723$.mobile.popup.handleLink = function( $link ) {
10724 var offset,
10725 path = $.mobile.path,
10726
10727 // NOTE make sure to get only the hash from the href because ie7 (wp7)
10728 // returns the absolute href in this case ruining the element selection
10729 popup = $( path.hashToSelector( path.parseUrl( $link.attr( "href" ) ).hash ) ).first();
10730
10731 if ( popup.length > 0 && popup.data( "mobile-popup" ) ) {
10732 offset = $link.offset();
10733 popup.popup( "open", {
10734 x: offset.left + $link.outerWidth() / 2,
10735 y: offset.top + $link.outerHeight() / 2,
10736 transition: $link.jqmData( "transition" ),
10737 positionTo: $link.jqmData( "position-to" )
10738 });
10739 }
10740
10741 //remove after delay
10742 setTimeout( function() {
10743 $link.removeClass( $.mobile.activeBtnClass );
10744 }, 300 );
10745};
10746
10747// TODO move inside _create
10748$.mobile.document.on( "pagebeforechange", function( theEvent, data ) {
10749 if ( data.options.role === "popup" ) {
10750 $.mobile.popup.handleLink( data.options.link );
10751 theEvent.preventDefault();
10752 }
10753});
10754
10755})( jQuery );
10756
10757/*
10758* custom "selectmenu" plugin
10759*/
10760
10761(function( $, undefined ) {
10762
10763var unfocusableItemSelector = ".ui-disabled,.ui-state-disabled,.ui-li-divider,.ui-screen-hidden,:jqmData(role='placeholder')",
10764 goToAdjacentItem = function( item, target, direction ) {
10765 var adjacent = item[ direction + "All" ]()
10766 .not( unfocusableItemSelector )
10767 .first();
10768
10769 // if there's a previous option, focus it
10770 if ( adjacent.length ) {
10771 target
10772 .blur()
10773 .attr( "tabindex", "-1" );
10774
10775 adjacent.find( "a" ).first().focus();
10776 }
10777 };
10778
10779$.widget( "mobile.selectmenu", $.mobile.selectmenu, {
10780 _create: function() {
10781 var o = this.options;
10782
10783 // Custom selects cannot exist inside popups, so revert the "nativeMenu"
10784 // option to true if a parent is a popup
10785 o.nativeMenu = o.nativeMenu || ( this.element.parents( ":jqmData(role='popup'),:mobile-popup" ).length > 0 );
10786
10787 return this._super();
10788 },
10789
10790 _handleSelectFocus: function() {
10791 this.element.blur();
10792 this.button.focus();
10793 },
10794
10795 _handleKeydown: function( event ) {
10796 this._super( event );
10797 this._handleButtonVclickKeydown( event );
10798 },
10799
10800 _handleButtonVclickKeydown: function( event ) {
10801 if ( this.options.disabled || this.isOpen ) {
10802 return;
10803 }
10804
10805 if (event.type === "vclick" ||
10806 event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {
10807
10808 this._decideFormat();
10809 if ( this.menuType === "overlay" ) {
10810 this.button.attr( "href", "#" + this.popupId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
10811 } else {
10812 this.button.attr( "href", "#" + this.dialogId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
10813 }
10814 this.isOpen = true;
10815 // Do not prevent default, so the navigation may have a chance to actually open the chosen format
10816 }
10817 },
10818
10819 _handleListFocus: function( e ) {
10820 var params = ( e.type === "focusin" ) ?
10821 { tabindex: "0", event: "vmouseover" }:
10822 { tabindex: "-1", event: "vmouseout" };
10823
10824 $( e.target )
10825 .attr( "tabindex", params.tabindex )
10826 .trigger( params.event );
10827 },
10828
10829 _handleListKeydown: function( event ) {
10830 var target = $( event.target ),
10831 li = target.closest( "li" );
10832
10833 // switch logic based on which key was pressed
10834 switch ( event.keyCode ) {
10835 // up or left arrow keys
10836 case 38:
10837 goToAdjacentItem( li, target, "prev" );
10838 return false;
10839 // down or right arrow keys
10840 case 40:
10841 goToAdjacentItem( li, target, "next" );
10842 return false;
10843 // If enter or space is pressed, trigger click
10844 case 13:
10845 case 32:
10846 target.trigger( "click" );
10847 return false;
10848 }
10849 },
10850
10851 _handleMenuPageHide: function() {
10852 // TODO centralize page removal binding / handling in the page plugin.
10853 // Suggestion from @jblas to do refcounting
10854 //
10855 // TODO extremely confusing dependency on the open method where the pagehide.remove
10856 // bindings are stripped to prevent the parent page from disappearing. The way
10857 // we're keeping pages in the DOM right now sucks
10858 //
10859 // rebind the page remove that was unbound in the open function
10860 // to allow for the parent page removal from actions other than the use
10861 // of a dialog sized custom select
10862 //
10863 // doing this here provides for the back button on the custom select dialog
10864 this.thisPage.page( "bindRemove" );
10865 },
10866
10867 _handleHeaderCloseClick: function() {
10868 if ( this.menuType === "overlay" ) {
10869 this.close();
10870 return false;
10871 }
10872 },
10873
10874 build: function() {
10875 var selectId, popupId, dialogId, label, thisPage, isMultiple, menuId, themeAttr, overlayThemeAttr,
10876 dividerThemeAttr, menuPage, listbox, list, header, headerTitle, menuPageContent, menuPageClose, headerClose, self,
10877 o = this.options;
10878
10879 if ( o.nativeMenu ) {
10880 return this._super();
10881 }
10882
10883 self = this;
10884 selectId = this.selectId;
10885 popupId = selectId + "-listbox";
10886 dialogId = selectId + "-dialog";
10887 label = this.label;
10888 thisPage = this.element.closest( ".ui-page" );
10889 isMultiple = this.element[ 0 ].multiple;
10890 menuId = selectId + "-menu";
10891 themeAttr = o.theme ? ( " data-" + $.mobile.ns + "theme='" + o.theme + "'" ) : "";
10892 overlayThemeAttr = o.overlayTheme ? ( " data-" + $.mobile.ns + "theme='" + o.overlayTheme + "'" ) : "";
10893 dividerThemeAttr = ( o.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
10894 menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogId + "'" + themeAttr + overlayThemeAttr + ">" +
10895 "<div data-" + $.mobile.ns + "role='header'>" +
10896 "<div class='ui-title'>" + label.getEncodedText() + "</div>"+
10897 "</div>"+
10898 "<div data-" + $.mobile.ns + "role='content'></div>"+
10899 "</div>" );
10900 listbox = $( "<div id='" + popupId + "' class='ui-selectmenu'>" ).insertAfter( this.select ).popup({ theme: o.overlayTheme });
10901 list = $( "<ul class='ui-selectmenu-list' id='" + menuId + "' role='listbox' aria-labelledby='" + this.buttonId + "'" + themeAttr + dividerThemeAttr + ">" ).appendTo( listbox );
10902 header = $( "<div class='ui-header ui-bar-" + ( o.theme ? o.theme : "inherit" ) + "'>" ).prependTo( listbox );
10903 headerTitle = $( "<h1 class='ui-title'>" ).appendTo( header );
10904
10905 if ( this.isMultiple ) {
10906 headerClose = $( "<a>", {
10907 "role": "button",
10908 "text": o.closeText,
10909 "href": "#",
10910 "class": "ui-btn ui-corner-all ui-btn-left ui-btn-icon-notext ui-icon-delete"
10911 }).appendTo( header );
10912 }
10913
10914 $.extend( this, {
10915 selectId: selectId,
10916 menuId: menuId,
10917 popupId: popupId,
10918 dialogId: dialogId,
10919 thisPage: thisPage,
10920 menuPage: menuPage,
10921 label: label,
10922 isMultiple: isMultiple,
10923 theme: o.theme,
10924 listbox: listbox,
10925 list: list,
10926 header: header,
10927 headerTitle: headerTitle,
10928 headerClose: headerClose,
10929 menuPageContent: menuPageContent,
10930 menuPageClose: menuPageClose,
10931 placeholder: ""
10932 });
10933
10934 // Create list from select, update state
10935 this.refresh();
10936
10937 if ( this._origTabIndex === undefined ) {
10938 // Map undefined to false, because this._origTabIndex === undefined
10939 // indicates that we have not yet checked whether the select has
10940 // originally had a tabindex attribute, whereas false indicates that
10941 // we have checked the select for such an attribute, and have found
10942 // none present.
10943 this._origTabIndex = ( this.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : this.select.attr( "tabindex" );
10944 }
10945 this.select.attr( "tabindex", "-1" );
10946 this._on( this.select, { focus : "_handleSelectFocus" } );
10947
10948 // Button events
10949 this._on( this.button, {
10950 vclick: "_handleButtonVclickKeydown"
10951 });
10952
10953 // Events for list items
10954 this.list.attr( "role", "listbox" );
10955 this._on( this.list, {
10956 focusin : "_handleListFocus",
10957 focusout : "_handleListFocus",
10958 keydown: "_handleListKeydown"
10959 });
10960 this.list
10961 .delegate( "li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)", "click", function( event ) {
10962
10963 // index of option tag to be selected
10964 var oldIndex = self.select[ 0 ].selectedIndex,
10965 newIndex = $.mobile.getAttribute( this, "option-index" ),
10966 option = self._selectOptions().eq( newIndex )[ 0 ];
10967
10968 // toggle selected status on the tag for multi selects
10969 option.selected = self.isMultiple ? !option.selected : true;
10970
10971 // toggle checkbox class for multiple selects
10972 if ( self.isMultiple ) {
10973 $( this ).find( "a" )
10974 .toggleClass( "ui-checkbox-on", option.selected )
10975 .toggleClass( "ui-checkbox-off", !option.selected );
10976 }
10977
10978 // trigger change if value changed
10979 if ( self.isMultiple || oldIndex !== newIndex ) {
10980 self.select.trigger( "change" );
10981 }
10982
10983 // hide custom select for single selects only - otherwise focus clicked item
10984 // We need to grab the clicked item the hard way, because the list may have been rebuilt
10985 if ( self.isMultiple ) {
10986 self.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
10987 .find( "a" ).first().focus();
10988 }
10989 else {
10990 self.close();
10991 }
10992
10993 event.preventDefault();
10994 });
10995
10996 // button refocus ensures proper height calculation
10997 // by removing the inline style and ensuring page inclusion
10998 this._on( this.menuPage, { pagehide: "_handleMenuPageHide" } );
10999
11000 // Events on the popup
11001 this._on( this.listbox, { popupafterclose: "close" } );
11002
11003 // Close button on small overlays
11004 if ( this.isMultiple ) {
11005 this._on( this.headerClose, { click: "_handleHeaderCloseClick" } );
11006 }
11007
11008 return this;
11009 },
11010
11011 _isRebuildRequired: function() {
11012 var list = this.list.find( "li" ),
11013 options = this._selectOptions().not( ".ui-screen-hidden" );
11014
11015 // TODO exceedingly naive method to determine difference
11016 // ignores value changes etc in favor of a forcedRebuild
11017 // from the user in the refresh method
11018 return options.text() !== list.text();
11019 },
11020
11021 selected: function() {
11022 return this._selectOptions().filter( ":selected:not( :jqmData(placeholder='true') )" );
11023 },
11024
11025 refresh: function( force ) {
11026 var self, indices;
11027
11028 if ( this.options.nativeMenu ) {
11029 return this._super( force );
11030 }
11031
11032 self = this;
11033 if ( force || this._isRebuildRequired() ) {
11034 self._buildList();
11035 }
11036
11037 indices = this.selectedIndices();
11038
11039 self.setButtonText();
11040 self.setButtonCount();
11041
11042 self.list.find( "li:not(.ui-li-divider)" )
11043 .find( "a" ).removeClass( $.mobile.activeBtnClass ).end()
11044 .attr( "aria-selected", false )
11045 .each(function( i ) {
11046
11047 if ( $.inArray( i, indices ) > -1 ) {
11048 var item = $( this );
11049
11050 // Aria selected attr
11051 item.attr( "aria-selected", true );
11052
11053 // Multiple selects: add the "on" checkbox state to the icon
11054 if ( self.isMultiple ) {
11055 item.find( "a" ).removeClass( "ui-checkbox-off" ).addClass( "ui-checkbox-on" );
11056 } else {
11057 if ( item.hasClass( "ui-screen-hidden" ) ) {
11058 item.next().find( "a" ).addClass( $.mobile.activeBtnClass );
11059 } else {
11060 item.find( "a" ).addClass( $.mobile.activeBtnClass );
11061 }
11062 }
11063 }
11064 });
11065 },
11066
11067 close: function() {
11068 if ( this.options.disabled || !this.isOpen ) {
11069 return;
11070 }
11071
11072 var self = this;
11073
11074 if ( self.menuType === "page" ) {
11075 self.menuPage.dialog( "close" );
11076 self.list.appendTo( self.listbox );
11077 } else {
11078 self.listbox.popup( "close" );
11079 }
11080
11081 self._focusButton();
11082 // allow the dialog to be closed again
11083 self.isOpen = false;
11084 },
11085
11086 open: function() {
11087 this.button.click();
11088 },
11089
11090 _focusMenuItem: function() {
11091 var selector = this.list.find( "a." + $.mobile.activeBtnClass );
11092 if ( selector.length === 0 ) {
11093 selector = this.list.find( "li:not(" + unfocusableItemSelector + ") a.ui-btn" );
11094 }
11095 selector.first().focus();
11096 },
11097
11098 _decideFormat: function() {
11099 var self = this,
11100 $window = this.window,
11101 selfListParent = self.list.parent(),
11102 menuHeight = selfListParent.outerHeight(),
11103 scrollTop = $window.scrollTop(),
11104 btnOffset = self.button.offset().top,
11105 screenHeight = $window.height();
11106
11107 if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
11108
11109 self.menuPage.appendTo( $.mobile.pageContainer ).page();
11110 self.menuPageContent = self.menuPage.find( ".ui-content" );
11111 self.menuPageClose = self.menuPage.find( ".ui-header a" );
11112
11113 // prevent the parent page from being removed from the DOM,
11114 // otherwise the results of selecting a list item in the dialog
11115 // fall into a black hole
11116 self.thisPage.unbind( "pagehide.remove" );
11117
11118 //for WebOS/Opera Mini (set lastscroll using button offset)
11119 if ( scrollTop === 0 && btnOffset > screenHeight ) {
11120 self.thisPage.one( "pagehide", function() {
11121 $( this ).jqmData( "lastScroll", btnOffset );
11122 });
11123 }
11124
11125 self.menuPage.one( {
11126 pageshow: $.proxy( this, "_focusMenuItem" ),
11127 pagehide: $.proxy( this, "close" )
11128 });
11129
11130 self.menuType = "page";
11131 self.menuPageContent.append( self.list );
11132 self.menuPage.find( "div .ui-title" ).text( self.label.text() );
11133 } else {
11134 self.menuType = "overlay";
11135
11136 self.listbox.one( { popupafteropen: $.proxy( this, "_focusMenuItem" ) } );
11137 }
11138 },
11139
11140 _buildList: function() {
11141 var self = this,
11142 o = this.options,
11143 placeholder = this.placeholder,
11144 needPlaceholder = true,
11145 dataIcon = "false",
11146 $options, numOptions, select,
11147 dataPrefix = "data-" + $.mobile.ns,
11148 dataIndexAttr = dataPrefix + "option-index",
11149 dataIconAttr = dataPrefix + "icon",
11150 dataRoleAttr = dataPrefix + "role",
11151 dataPlaceholderAttr = dataPrefix + "placeholder",
11152 fragment = document.createDocumentFragment(),
11153 isPlaceholderItem = false,
11154 optGroup,
11155 i,
11156 option, $option, parent, text, anchor, classes,
11157 optLabel, divider, item;
11158
11159 self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
11160 $options = this._selectOptions();
11161 numOptions = $options.length;
11162 select = this.select[ 0 ];
11163
11164 for ( i = 0; i < numOptions;i++, isPlaceholderItem = false) {
11165 option = $options[i];
11166 $option = $( option );
11167
11168 // Do not create options based on ui-screen-hidden select options
11169 if ( $option.hasClass( "ui-screen-hidden" ) ) {
11170 continue;
11171 }
11172
11173 parent = option.parentNode;
11174 text = $option.text();
11175 anchor = document.createElement( "a" );
11176 classes = [];
11177
11178 anchor.setAttribute( "href", "#" );
11179 anchor.appendChild( document.createTextNode( text ) );
11180
11181 // Are we inside an optgroup?
11182 if ( parent !== select && parent.nodeName.toLowerCase() === "optgroup" ) {
11183 optLabel = parent.getAttribute( "label" );
11184 if ( optLabel !== optGroup ) {
11185 divider = document.createElement( "li" );
11186 divider.setAttribute( dataRoleAttr, "list-divider" );
11187 divider.setAttribute( "role", "option" );
11188 divider.setAttribute( "tabindex", "-1" );
11189 divider.appendChild( document.createTextNode( optLabel ) );
11190 fragment.appendChild( divider );
11191 optGroup = optLabel;
11192 }
11193 }
11194
11195 if ( needPlaceholder && ( !option.getAttribute( "value" ) || text.length === 0 || $option.jqmData( "placeholder" ) ) ) {
11196 needPlaceholder = false;
11197 isPlaceholderItem = true;
11198
11199 // If we have identified a placeholder, record the fact that it was
11200 // us who have added the placeholder to the option and mark it
11201 // retroactively in the select as well
11202 if ( null === option.getAttribute( dataPlaceholderAttr ) ) {
11203 this._removePlaceholderAttr = true;
11204 }
11205 option.setAttribute( dataPlaceholderAttr, true );
11206 if ( o.hidePlaceholderMenuItems ) {
11207 classes.push( "ui-screen-hidden" );
11208 }
11209 if ( placeholder !== text ) {
11210 placeholder = self.placeholder = text;
11211 }
11212 }
11213
11214 item = document.createElement( "li" );
11215 if ( option.disabled ) {
11216 classes.push( "ui-state-disabled" );
11217 item.setAttribute( "aria-disabled", true );
11218 }
11219 item.setAttribute( dataIndexAttr, i );
11220 item.setAttribute( dataIconAttr, dataIcon );
11221 if ( isPlaceholderItem ) {
11222 item.setAttribute( dataPlaceholderAttr, true );
11223 }
11224 item.className = classes.join( " " );
11225 item.setAttribute( "role", "option" );
11226 anchor.setAttribute( "tabindex", "-1" );
11227 if ( this.isMultiple ) {
11228 $( anchor ).addClass( "ui-btn ui-checkbox-off ui-btn-icon-right" );
11229 }
11230
11231 item.appendChild( anchor );
11232 fragment.appendChild( item );
11233 }
11234
11235 self.list[0].appendChild( fragment );
11236
11237 // Hide header if it's not a multiselect and there's no placeholder
11238 if ( !this.isMultiple && !placeholder.length ) {
11239 this.header.addClass( "ui-screen-hidden" );
11240 } else {
11241 this.headerTitle.text( this.placeholder );
11242 }
11243
11244 // Now populated, create listview
11245 self.list.listview();
11246 },
11247
11248 _button: function() {
11249 return this.options.nativeMenu ?
11250 this._super() :
11251 $( "<a>", {
11252 "href": "#",
11253 "role": "button",
11254 // TODO value is undefined at creation
11255 "id": this.buttonId,
11256 "aria-haspopup": "true",
11257
11258 // TODO value is undefined at creation
11259 "aria-owns": this.menuId
11260 });
11261 },
11262
11263 _destroy: function() {
11264
11265 if ( !this.options.nativeMenu ) {
11266 this.close();
11267
11268 // Restore the tabindex attribute to its original value
11269 if ( this._origTabIndex !== undefined ) {
11270 if ( this._origTabIndex !== false ) {
11271 this.select.attr( "tabindex", this._origTabIndex );
11272 } else {
11273 this.select.removeAttr( "tabindex" );
11274 }
11275 }
11276
11277 // Remove the placeholder attribute if we were the ones to add it
11278 if ( this._removePlaceholderAttr ) {
11279 this._selectOptions().removeAttr( "data-" + $.mobile.ns + "placeholder" );
11280 }
11281
11282 // Remove the popup
11283 this.listbox.remove();
11284
11285 // Remove the dialog
11286 this.menuPage.remove();
11287 }
11288
11289 // Chain up
11290 this._super();
11291 }
11292});
11293
11294})( jQuery );
11295
11296
11297// buttonMarkup is deprecated as of 1.4.0 and will be removed in 1.5.0.
11298
11299(function( $, undefined ) {
11300
11301
11302// General policy: Do not access data-* attributes except during enhancement.
11303// In all other cases we determine the state of the button exclusively from its
11304// className. That's why optionsToClasses expects a full complement of options,
11305// and the jQuery plugin completes the set of options from the default values.
11306
11307// Map classes to buttonMarkup boolean options - used in classNameToOptions()
11308var reverseBoolOptionMap = {
11309 "ui-shadow" : "shadow",
11310 "ui-corner-all" : "corners",
11311 "ui-btn-inline" : "inline",
11312 "ui-shadow-icon" : "iconshadow", /* TODO: Remove in 1.5 */
11313 "ui-mini" : "mini"
11314 },
11315 getAttrFixed = function() {
11316 var ret = $.mobile.getAttribute.apply( this, arguments );
11317
11318 return ( ret == null ? undefined : ret );
11319 },
11320 capitalLettersRE = /[A-Z]/g;
11321
11322// optionsToClasses:
11323// @options: A complete set of options to convert to class names.
11324// @existingClasses: extra classes to add to the result
11325//
11326// Converts @options to buttonMarkup classes and returns the result as an array
11327// that can be converted to an element's className with .join( " " ). All
11328// possible options must be set inside @options. Use $.fn.buttonMarkup.defaults
11329// to get a complete set and use $.extend to override your choice of options
11330// from that set.
11331function optionsToClasses( options, existingClasses ) {
11332 var classes = existingClasses ? existingClasses : [];
11333
11334 // Add classes to the array - first ui-btn
11335 classes.push( "ui-btn" );
11336
11337 // If there is a theme
11338 if ( options.theme ) {
11339 classes.push( "ui-btn-" + options.theme );
11340 }
11341
11342 // If there's an icon, add the icon-related classes
11343 if ( options.icon ) {
11344 classes = classes.concat([
11345 "ui-icon-" + options.icon,
11346 "ui-btn-icon-" + options.iconpos
11347 ]);
11348 if ( options.iconshadow ) {
11349 classes.push( "ui-shadow-icon" ); /* TODO: Remove in 1.5 */
11350 }
11351 }
11352
11353 // Add the appropriate class for each boolean option
11354 if ( options.inline ) {
11355 classes.push( "ui-btn-inline" );
11356 }
11357 if ( options.shadow ) {
11358 classes.push( "ui-shadow" );
11359 }
11360 if ( options.corners ) {
11361 classes.push( "ui-corner-all" );
11362 }
11363 if ( options.mini ) {
11364 classes.push( "ui-mini" );
11365 }
11366
11367 // Create a string from the array and return it
11368 return classes;
11369}
11370
11371// classNameToOptions:
11372// @classes: A string containing a .className-style space-separated class list
11373//
11374// Loops over @classes and calculates an options object based on the
11375// buttonMarkup-related classes it finds. It records unrecognized classes in an
11376// array.
11377//
11378// Returns: An object containing the following items:
11379//
11380// "options": buttonMarkup options found to be present because of the
11381// presence/absence of corresponding classes
11382//
11383// "unknownClasses": a string containing all the non-buttonMarkup-related
11384// classes found in @classes
11385//
11386// "alreadyEnhanced": A boolean indicating whether the ui-btn class was among
11387// those found to be present
11388function classNameToOptions( classes ) {
11389 var idx, map, unknownClass,
11390 alreadyEnhanced = false,
11391 noIcon = true,
11392 o = {
11393 icon: "",
11394 inline: false,
11395 shadow: false,
11396 corners: false,
11397 iconshadow: false,
11398 mini: false
11399 },
11400 unknownClasses = [];
11401
11402 classes = classes.split( " " );
11403
11404 // Loop over the classes
11405 for ( idx = 0 ; idx < classes.length ; idx++ ) {
11406
11407 // Assume it's an unrecognized class
11408 unknownClass = true;
11409
11410 // Recognize boolean options from the presence of classes
11411 map = reverseBoolOptionMap[ classes[ idx ] ];
11412 if ( map !== undefined ) {
11413 unknownClass = false;
11414 o[ map ] = true;
11415
11416 // Recognize the presence of an icon and establish the icon position
11417 } else if ( classes[ idx ].indexOf( "ui-btn-icon-" ) === 0 ) {
11418 unknownClass = false;
11419 noIcon = false;
11420 o.iconpos = classes[ idx ].substring( 12 );
11421
11422 // Establish which icon is present
11423 } else if ( classes[ idx ].indexOf( "ui-icon-" ) === 0 ) {
11424 unknownClass = false;
11425 o.icon = classes[ idx ].substring( 8 );
11426
11427 // Establish the theme - this recognizes one-letter theme swatch names
11428 } else if ( classes[ idx ].indexOf( "ui-btn-" ) === 0 && classes[ idx ].length === 8 ) {
11429 unknownClass = false;
11430 o.theme = classes[ idx ].substring( 7 );
11431
11432 // Recognize that this element has already been buttonMarkup-enhanced
11433 } else if ( classes[ idx ] === "ui-btn" ) {
11434 unknownClass = false;
11435 alreadyEnhanced = true;
11436 }
11437
11438 // If this class has not been recognized, add it to the list
11439 if ( unknownClass ) {
11440 unknownClasses.push( classes[ idx ] );
11441 }
11442 }
11443
11444 // If a "ui-btn-icon-*" icon position class is absent there cannot be an icon
11445 if ( noIcon ) {
11446 o.icon = "";
11447 }
11448
11449 return {
11450 options: o,
11451 unknownClasses: unknownClasses,
11452 alreadyEnhanced: alreadyEnhanced
11453 };
11454}
11455
11456function camelCase2Hyphenated( c ) {
11457 return "-" + c.toLowerCase();
11458}
11459
11460// $.fn.buttonMarkup:
11461// DOM: gets/sets .className
11462//
11463// @options: options to apply to the elements in the jQuery object
11464// @overwriteClasses: boolean indicating whether to honour existing classes
11465//
11466// Calculates the classes to apply to the elements in the jQuery object based on
11467// the options passed in. If @overwriteClasses is true, it sets the className
11468// property of each element in the jQuery object to the buttonMarkup classes
11469// it calculates based on the options passed in.
11470//
11471// If you wish to preserve any classes that are already present on the elements
11472// inside the jQuery object, including buttonMarkup-related classes that were
11473// added by a previous call to $.fn.buttonMarkup() or during page enhancement
11474// then you should omit @overwriteClasses or set it to false.
11475$.fn.buttonMarkup = function( options, overwriteClasses ) {
11476 var idx, data, el, retrievedOptions, optionKey,
11477 defaults = $.fn.buttonMarkup.defaults;
11478
11479 for ( idx = 0 ; idx < this.length ; idx++ ) {
11480 el = this[ idx ];
11481 data = overwriteClasses ?
11482
11483 // Assume this element is not enhanced and ignore its classes
11484 { alreadyEnhanced: false, unknownClasses: [] } :
11485
11486 // Otherwise analyze existing classes to establish existing options and
11487 // classes
11488 classNameToOptions( el.className );
11489
11490 retrievedOptions = $.extend( {},
11491
11492 // If the element already has the class ui-btn, then we assume that
11493 // it has passed through buttonMarkup before - otherwise, the options
11494 // returned by classNameToOptions do not correctly reflect the state of
11495 // the element
11496 ( data.alreadyEnhanced ? data.options : {} ),
11497
11498 // Finally, apply the options passed in
11499 options );
11500
11501 // If this is the first call on this element, retrieve remaining options
11502 // from the data-attributes
11503 if ( !data.alreadyEnhanced ) {
11504 for ( optionKey in defaults ) {
11505 if ( retrievedOptions[ optionKey ] === undefined ) {
11506 retrievedOptions[ optionKey ] = getAttrFixed( el,
11507 optionKey.replace( capitalLettersRE, camelCase2Hyphenated )
11508 );
11509 }
11510 }
11511 }
11512
11513 el.className = optionsToClasses(
11514
11515 // Merge all the options and apply them as classes
11516 $.extend( {},
11517
11518 // The defaults form the basis
11519 defaults,
11520
11521 // Add the computed options
11522 retrievedOptions
11523 ),
11524
11525 // ... and re-apply any unrecognized classes that were found
11526 data.unknownClasses ).join( " " );
11527 if ( el.tagName.toLowerCase() !== "button" ) {
11528 el.setAttribute( "role", "button" );
11529 }
11530 }
11531
11532 return this;
11533};
11534
11535// buttonMarkup defaults. This must be a complete set, i.e., a value must be
11536// given here for all recognized options
11537$.fn.buttonMarkup.defaults = {
11538 icon: "",
11539 iconpos: "left",
11540 theme: null,
11541 inline: false,
11542 shadow: true,
11543 corners: true,
11544 iconshadow: false, /* TODO: Remove in 1.5. Option deprecated in 1.4. */
11545 mini: false
11546};
11547
11548$.extend( $.fn.buttonMarkup, {
11549 initSelector: "a:jqmData(role='button'), .ui-bar > a, .ui-bar > :jqmData(role='controlgroup') > a, button"
11550});
11551
11552})( jQuery );
11553
11554
11555(function( $, undefined ) {
11556
11557$.widget( "mobile.controlgroup", $.extend( {
11558 options: {
11559 enhanced: false,
11560 theme: null,
11561 shadow: false,
11562 corners: true,
11563 excludeInvisible: true,
11564 type: "vertical",
11565 mini: false
11566 },
11567
11568 _create: function() {
11569 var elem = this.element,
11570 opts = this.options;
11571
11572 // Run buttonmarkup
11573 if ( $.fn.buttonMarkup ) {
11574 this.element.find( $.fn.buttonMarkup.initSelector ).buttonMarkup();
11575 }
11576 // Enhance child widgets
11577 $.each( this._childWidgets, $.proxy( function( number, widgetName ) {
11578 if ( $.mobile[ widgetName ] ) {
11579 this.element.find( $.mobile[ widgetName ].initSelector ).not( $.mobile.page.prototype.keepNativeSelector() )[ widgetName ]();
11580 }
11581 }, this ));
11582
11583 $.extend( this, {
11584 _ui: null,
11585 _initialRefresh: true
11586 });
11587
11588 if ( opts.enhanced ) {
11589 this._ui = {
11590 groupLegend: elem.children( ".ui-controlgroup-label" ).children(),
11591 childWrapper: elem.children( ".ui-controlgroup-controls" )
11592 };
11593 } else {
11594 this._ui = this._enhance();
11595 }
11596
11597 },
11598
11599 _childWidgets: [ "checkboxradio", "selectmenu", "button" ],
11600
11601 _themeClassFromOption: function( value ) {
11602 return ( value ? ( value === "none" ? "" : "ui-group-theme-" + value ) : "" );
11603 },
11604
11605 _enhance: function() {
11606 var elem = this.element,
11607 opts = this.options,
11608 ui = {
11609 groupLegend: elem.children( "legend" ),
11610 childWrapper: elem
11611 .addClass( "ui-controlgroup " +
11612 "ui-controlgroup-" +
11613 ( opts.type === "horizontal" ? "horizontal" : "vertical" ) + " " +
11614 this._themeClassFromOption( opts.theme ) + " " +
11615 ( opts.corners ? "ui-corner-all " : "" ) +
11616 ( opts.mini ? "ui-mini " : "" ) )
11617 .wrapInner( "<div " +
11618 "class='ui-controlgroup-controls " +
11619 ( opts.shadow === true ? "ui-shadow" : "" ) + "'></div>" )
11620 .children()
11621 };
11622
11623 if ( ui.groupLegend.length > 0 ) {
11624 $( "<div role='heading' class='ui-controlgroup-label'></div>" )
11625 .append( ui.groupLegend )
11626 .prependTo( elem );
11627 }
11628
11629 return ui;
11630 },
11631
11632 _init: function() {
11633 this.refresh();
11634 },
11635
11636 _setOptions: function( options ) {
11637 var callRefresh, returnValue,
11638 elem = this.element;
11639
11640 // Must have one of horizontal or vertical
11641 if ( options.type !== undefined ) {
11642 elem
11643 .removeClass( "ui-controlgroup-horizontal ui-controlgroup-vertical" )
11644 .addClass( "ui-controlgroup-" + ( options.type === "horizontal" ? "horizontal" : "vertical" ) );
11645 callRefresh = true;
11646 }
11647
11648 if ( options.theme !== undefined ) {
11649 elem
11650 .removeClass( this._themeClassFromOption( this.options.theme ) )
11651 .addClass( this._themeClassFromOption( options.theme ) );
11652 }
11653
11654 if ( options.corners !== undefined ) {
11655 elem.toggleClass( "ui-corner-all", options.corners );
11656 }
11657
11658 if ( options.mini !== undefined ) {
11659 elem.toggleClass( "ui-mini", options.mini );
11660 }
11661
11662 if ( options.shadow !== undefined ) {
11663 this._ui.childWrapper.toggleClass( "ui-shadow", options.shadow );
11664 }
11665
11666 if ( options.excludeInvisible !== undefined ) {
11667 this.options.excludeInvisible = options.excludeInvisible;
11668 callRefresh = true;
11669 }
11670
11671 returnValue = this._super( options );
11672
11673 if ( callRefresh ) {
11674 this.refresh();
11675 }
11676
11677 return returnValue;
11678 },
11679
11680 container: function() {
11681 return this._ui.childWrapper;
11682 },
11683
11684 refresh: function() {
11685 var $el = this.container(),
11686 els = $el.find( ".ui-btn" ).not( ".ui-slider-handle" ),
11687 create = this._initialRefresh;
11688 if ( $.mobile.checkboxradio ) {
11689 $el.find( ":mobile-checkboxradio" ).checkboxradio( "refresh" );
11690 }
11691 this._addFirstLastClasses( els,
11692 this.options.excludeInvisible ? this._getVisibles( els, create ) : els,
11693 create );
11694 this._initialRefresh = false;
11695 },
11696
11697 // Caveat: If the legend is not the first child of the controlgroup at enhance
11698 // time, it will be after _destroy().
11699 _destroy: function() {
11700 var ui, buttons,
11701 opts = this.options;
11702
11703 if ( opts.enhanced ) {
11704 return this;
11705 }
11706
11707 ui = this._ui;
11708 buttons = this.element
11709 .removeClass( "ui-controlgroup " +
11710 "ui-controlgroup-horizontal ui-controlgroup-vertical ui-corner-all ui-mini " +
11711 this._themeClassFromOption( opts.theme ) )
11712 .find( ".ui-btn" )
11713 .not( ".ui-slider-handle" );
11714
11715 this._removeFirstLastClasses( buttons );
11716
11717 ui.groupLegend.unwrap();
11718 ui.childWrapper.children().unwrap();
11719 }
11720}, $.mobile.behaviors.addFirstLastClasses ) );
11721
11722})(jQuery);
11723
11724(function( $, undefined ) {
11725
11726 $.widget( "mobile.toolbar", {
11727 initSelector: ":jqmData(role='footer'), :jqmData(role='header')",
11728
11729 options: {
11730 theme: null,
11731 addBackBtn: false,
11732 backBtnTheme: null,
11733 backBtnText: "Back"
11734 },
11735
11736 _create: function() {
11737 var leftbtn, rightbtn,
11738 role = this.element.is( ":jqmData(role='header')" ) ? "header" : "footer",
11739 page = this.element.closest( ".ui-page" );
11740 if ( page.length === 0 ) {
11741 page = false;
11742 this._on( this.document, {
11743 "pageshow": "refresh"
11744 });
11745 }
11746 $.extend( this, {
11747 role: role,
11748 page: page,
11749 leftbtn: leftbtn,
11750 rightbtn: rightbtn,
11751 backBtn: null
11752 });
11753 this.element.attr( "role", role === "header" ? "banner" : "contentinfo" ).addClass( "ui-" + role );
11754 this.refresh();
11755 this._setOptions( this.options );
11756 },
11757 _setOptions: function( o ) {
11758 if ( o.addBackBtn !== undefined ) {
11759 if ( this.options.addBackBtn &&
11760 this.role === "header" &&
11761 $( ".ui-page" ).length > 1 &&
11762 this.page[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) !== $.mobile.path.stripHash( location.hash ) &&
11763 !this.leftbtn ) {
11764 this._addBackButton();
11765 } else {
11766 this.element.find( ".ui-toolbar-back-btn" ).remove();
11767 }
11768 }
11769 if ( o.backBtnTheme != null ) {
11770 this.element
11771 .find( ".ui-toolbar-back-btn" )
11772 .addClass( "ui-btn ui-btn-" + o.backBtnTheme );
11773 }
11774 if ( o.backBtnText !== undefined ) {
11775 this.element.find( ".ui-toolbar-back-btn .ui-btn-text" ).text( o.backBtnText );
11776 }
11777 if ( o.theme !== undefined ) {
11778 var currentTheme = this.options.theme ? this.options.theme : "inherit",
11779 newTheme = o.theme ? o.theme : "inherit";
11780
11781 this.element.removeClass( "ui-bar-" + currentTheme ).addClass( "ui-bar-" + newTheme );
11782 }
11783
11784 this._super( o );
11785 },
11786 refresh: function() {
11787 if ( this.role === "header" ) {
11788 this._addHeaderButtonClasses();
11789 }
11790 if ( !this.page ) {
11791 this._setRelative();
11792 if ( this.role === "footer" ) {
11793 this.element.appendTo( "body" );
11794 }
11795 }
11796 this._addHeadingClasses();
11797 this._btnMarkup();
11798 },
11799
11800 //we only want this to run on non fixed toolbars so make it easy to override
11801 _setRelative: function() {
11802 $( "[data-"+ $.mobile.ns + "role='page']" ).css({ "position": "relative" });
11803 },
11804
11805 // Deprecated in 1.4. As from 1.5 button classes have to be present in the markup.
11806 _btnMarkup: function() {
11807 this.element.children( "a" ).attr( "data-" + $.mobile.ns + "role", "button" );
11808 this.element.trigger( "create" );
11809 },
11810 // Deprecated in 1.4. As from 1.5 ui-btn-left/right classes have to be present in the markup.
11811 _addHeaderButtonClasses: function() {
11812 var $headeranchors = this.element.children( "a, button" );
11813 this.leftbtn = $headeranchors.hasClass( "ui-btn-left" );
11814 this.rightbtn = $headeranchors.hasClass( "ui-btn-right" );
11815
11816 this.leftbtn = this.leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
11817
11818 this.rightbtn = this.rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
11819
11820 },
11821 _addBackButton: function() {
11822 var theme,
11823 options = this.options;
11824
11825 if ( !this.backBtn ) {
11826 theme = options.backBtnTheme || options.theme;
11827 this.backBtn = $( "<a role='button' href='javascript:void(0);' " +
11828 "class='ui-btn ui-corner-all ui-shadow ui-btn-left " +
11829 ( theme ? "ui-btn-" + theme + " " : "" ) +
11830 "ui-toolbar-back-btn ui-icon-carat-l ui-btn-icon-left' " +
11831 "data-" + $.mobile.ns + "rel='back'>" + options.backBtnText + "</a>" )
11832 .prependTo( this.element );
11833 }
11834 },
11835 _addHeadingClasses: function() {
11836 this.element.children( "h1, h2, h3, h4, h5, h6" )
11837 .addClass( "ui-title" )
11838 // Regardless of h element number in src, it becomes h1 for the enhanced page
11839 .attr({
11840 "role": "heading",
11841 "aria-level": "1"
11842 });
11843 }
11844 });
11845
11846})( jQuery );
11847
11848(function( $, undefined ) {
11849
11850 $.widget( "mobile.toolbar", $.mobile.toolbar, {
11851 options: {
11852 position:null,
11853 visibleOnPageShow: true,
11854 disablePageZoom: true,
11855 transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown)
11856 fullscreen: false,
11857 tapToggle: true,
11858 tapToggleBlacklist: "a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-flipswitch, .ui-popup, .ui-panel, .ui-panel-dismiss-open",
11859 hideDuringFocus: "input, textarea, select",
11860 updatePagePadding: true,
11861 trackPersistentToolbars: true,
11862
11863 // Browser detection! Weeee, here we go...
11864 // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
11865 // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
11866 // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
11867 // The following function serves to rule out some popular browsers with known fixed-positioning issues
11868 // This is a plugin option like any other, so feel free to improve or overwrite it
11869 supportBlacklist: function() {
11870 return !$.support.fixedPosition;
11871 }
11872 },
11873
11874 _create: function() {
11875 this._super();
11876 if ( this.options.position === "fixed" && !this.options.supportBlacklist() ) {
11877 this._makeFixed();
11878 }
11879 },
11880
11881 _makeFixed: function() {
11882 this.element.addClass( "ui-"+ this.role +"-fixed" );
11883 this.updatePagePadding();
11884 this._addTransitionClass();
11885 this._bindPageEvents();
11886 this._bindToggleHandlers();
11887 this._setOptions( this.options );
11888 },
11889
11890 _setOptions: function( o ) {
11891 if ( o.position === "fixed" && this.options.position !== "fixed" ) {
11892 this._makeFixed();
11893 }
11894 if ( this.options.position === "fixed" && !this.options.supportBlacklist() ) {
11895 var $page = ( !!this.page )? this.page: ( $(".ui-page-active").length > 0 )? $(".ui-page-active"): $(".ui-page").eq(0);
11896
11897 if ( o.fullscreen !== undefined) {
11898 if ( o.fullscreen ) {
11899 this.element.addClass( "ui-"+ this.role +"-fullscreen" );
11900 $page.addClass( "ui-page-" + this.role + "-fullscreen" );
11901 }
11902 // If not fullscreen, add class to page to set top or bottom padding
11903 else {
11904 this.element.removeClass( "ui-"+ this.role +"-fullscreen" );
11905 $page.removeClass( "ui-page-" + this.role + "-fullscreen" ).addClass( "ui-page-" + this.role+ "-fixed" );
11906 }
11907 }
11908 }
11909 this._super(o);
11910 },
11911
11912 _addTransitionClass: function() {
11913 var tclass = this.options.transition;
11914
11915 if ( tclass && tclass !== "none" ) {
11916 // use appropriate slide for header or footer
11917 if ( tclass === "slide" ) {
11918 tclass = this.element.hasClass( "ui-header" ) ? "slidedown" : "slideup";
11919 }
11920
11921 this.element.addClass( tclass );
11922 }
11923 },
11924
11925 _bindPageEvents: function() {
11926 var page = ( !!this.page )? this.element.closest( ".ui-page" ): this.document;
11927 //page event bindings
11928 // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
11929 // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
11930 this._on( page , {
11931 "pagebeforeshow": "_handlePageBeforeShow",
11932 "webkitAnimationStart":"_handleAnimationStart",
11933 "animationstart":"_handleAnimationStart",
11934 "updatelayout": "_handleAnimationStart",
11935 "pageshow": "_handlePageShow",
11936 "pagebeforehide": "_handlePageBeforeHide"
11937 });
11938 },
11939
11940 _handlePageBeforeShow: function( ) {
11941 var o = this.options;
11942 if ( o.disablePageZoom ) {
11943 $.mobile.zoom.disable( true );
11944 }
11945 if ( !o.visibleOnPageShow ) {
11946 this.hide( true );
11947 }
11948 },
11949
11950 _handleAnimationStart: function() {
11951 if ( this.options.updatePagePadding ) {
11952 this.updatePagePadding( ( !!this.page )? this.page: ".ui-page-active" );
11953 }
11954 },
11955
11956 _handlePageShow: function() {
11957 this.updatePagePadding( ( !!this.page )? this.page: ".ui-page-active" );
11958 if ( this.options.updatePagePadding ) {
11959 this._on( this.window, { "throttledresize": "updatePagePadding" } );
11960 }
11961 },
11962
11963 _handlePageBeforeHide: function( e, ui ) {
11964 var o = this.options,
11965 thisFooter, thisHeader, nextFooter, nextHeader;
11966
11967 if ( o.disablePageZoom ) {
11968 $.mobile.zoom.enable( true );
11969 }
11970 if ( o.updatePagePadding ) {
11971 this._off( this.window, "throttledresize" );
11972 }
11973
11974 if ( o.trackPersistentToolbars ) {
11975 thisFooter = $( ".ui-footer-fixed:jqmData(id)", this.page );
11976 thisHeader = $( ".ui-header-fixed:jqmData(id)", this.page );
11977 nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ) || $();
11978 nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage ) || $();
11979
11980 if ( nextFooter.length || nextHeader.length ) {
11981
11982 nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
11983
11984 ui.nextPage.one( "pageshow", function() {
11985 nextHeader.prependTo( this );
11986 nextFooter.appendTo( this );
11987 });
11988 }
11989 }
11990 },
11991
11992 _visible: true,
11993
11994 // This will set the content element's top or bottom padding equal to the toolbar's height
11995 updatePagePadding: function( tbPage ) {
11996 var $el = this.element,
11997 header = ( this.role ==="header" ),
11998 pos = parseFloat( $el.css( header ? "top" : "bottom" ) );
11999
12000 // This behavior only applies to "fixed", not "fullscreen"
12001 if ( this.options.fullscreen ) { return; }
12002 // tbPage argument can be a Page object or an event, if coming from throttled resize.
12003 tbPage = ( tbPage && tbPage.type === undefined && tbPage ) || this.page || $el.closest( ".ui-page" );
12004 tbPage = ( !!this.page )? this.page: ".ui-page-active";
12005 $( tbPage ).css( "padding-" + ( header ? "top" : "bottom" ), $el.outerHeight() + pos );
12006 },
12007
12008 _useTransition: function( notransition ) {
12009 var $win = this.window,
12010 $el = this.element,
12011 scroll = $win.scrollTop(),
12012 elHeight = $el.height(),
12013 pHeight = ( !!this.page )? $el.closest( ".ui-page" ).height():$(".ui-page-active").height(),
12014 viewportHeight = $.mobile.getScreenHeight();
12015
12016 return !notransition &&
12017 ( this.options.transition && this.options.transition !== "none" &&
12018 (
12019 ( this.role === "header" && !this.options.fullscreen && scroll > elHeight ) ||
12020 ( this.role === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
12021 ) || this.options.fullscreen
12022 );
12023 },
12024
12025 show: function( notransition ) {
12026 var hideClass = "ui-fixed-hidden",
12027 $el = this.element;
12028
12029 if ( this._useTransition( notransition ) ) {
12030 $el
12031 .removeClass( "out " + hideClass )
12032 .addClass( "in" )
12033 .animationComplete(function () {
12034 $el.removeClass( "in" );
12035 });
12036 }
12037 else {
12038 $el.removeClass( hideClass );
12039 }
12040 this._visible = true;
12041 },
12042
12043 hide: function( notransition ) {
12044 var hideClass = "ui-fixed-hidden",
12045 $el = this.element,
12046 // if it's a slide transition, our new transitions need the reverse class as well to slide outward
12047 outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" );
12048
12049 if ( this._useTransition( notransition ) ) {
12050 $el
12051 .addClass( outclass )
12052 .removeClass( "in" )
12053 .animationComplete(function() {
12054 $el.addClass( hideClass ).removeClass( outclass );
12055 });
12056 }
12057 else {
12058 $el.addClass( hideClass ).removeClass( outclass );
12059 }
12060 this._visible = false;
12061 },
12062
12063 toggle: function() {
12064 this[ this._visible ? "hide" : "show" ]();
12065 },
12066
12067 _bindToggleHandlers: function() {
12068 var self = this,
12069 o = self.options,
12070 delayShow, delayHide,
12071 isVisible = true,
12072 page = ( !!this.page )? this.page: $(".ui-page");
12073
12074 // tap toggle
12075 page
12076 .bind( "vclick", function( e ) {
12077 if ( o.tapToggle && !$( e.target ).closest( o.tapToggleBlacklist ).length ) {
12078 self.toggle();
12079 }
12080 })
12081 .bind( "focusin focusout", function( e ) {
12082 //this hides the toolbars on a keyboard pop to give more screen room and prevent ios bug which
12083 //positions fixed toolbars in the middle of the screen on pop if the input is near the top or
12084 //bottom of the screen addresses issues #4410 Footer navbar moves up when clicking on a textbox in an Android environment
12085 //and issue #4113 Header and footer change their position after keyboard popup - iOS
12086 //and issue #4410 Footer navbar moves up when clicking on a textbox in an Android environment
12087 if ( screen.width < 1025 && $( e.target ).is( o.hideDuringFocus ) && !$( e.target ).closest( ".ui-header-fixed, .ui-footer-fixed" ).length ) {
12088 //Fix for issue #4724 Moving through form in Mobile Safari with "Next" and "Previous" system
12089 //controls causes fixed position, tap-toggle false Header to reveal itself
12090 // isVisible instead of self._visible because the focusin and focusout events fire twice at the same time
12091 // Also use a delay for hiding the toolbars because on Android native browser focusin is direclty followed
12092 // by a focusout when a native selects opens and the other way around when it closes.
12093 if ( e.type === "focusout" && !isVisible ) {
12094 isVisible = true;
12095 //wait for the stack to unwind and see if we have jumped to another input
12096 clearTimeout( delayHide );
12097 delayShow = setTimeout( function() {
12098 self.show();
12099 }, 0 );
12100 } else if ( e.type === "focusin" && !!isVisible ) {
12101 //if we have jumped to another input clear the time out to cancel the show.
12102 clearTimeout( delayShow );
12103 isVisible = false;
12104 delayHide = setTimeout( function() {
12105 self.hide();
12106 }, 0 );
12107 }
12108 }
12109 });
12110 },
12111
12112 _setRelative: function() {
12113 if( this.options.position !== "fixed" ){
12114 $( "[data-"+ $.mobile.ns + "role='page']" ).css({ "position": "relative" });
12115 }
12116 },
12117
12118 _destroy: function() {
12119 var $el = this.element,
12120 header = $el.hasClass( "ui-header" );
12121
12122 $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), "" );
12123 $el.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" );
12124 $el.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
12125 }
12126
12127 });
12128})( jQuery );
12129
12130(function( $, undefined ) {
12131 $.widget( "mobile.toolbar", $.mobile.toolbar, {
12132
12133 _makeFixed: function() {
12134 this._super();
12135 this._workarounds();
12136 },
12137
12138 //check the browser and version and run needed workarounds
12139 _workarounds: function() {
12140 var ua = navigator.userAgent,
12141 platform = navigator.platform,
12142 // Rendering engine is Webkit, and capture major version
12143 wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
12144 wkversion = !!wkmatch && wkmatch[ 1 ],
12145 os = null,
12146 self = this;
12147 //set the os we are working in if it dosent match one with workarounds return
12148 if ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) {
12149 os = "ios";
12150 } else if ( ua.indexOf( "Android" ) > -1 ) {
12151 os = "android";
12152 } else {
12153 return;
12154 }
12155 //check os version if it dosent match one with workarounds return
12156 if ( os === "ios" ) {
12157 //iOS workarounds
12158 self._bindScrollWorkaround();
12159 } else if ( os === "android" && wkversion && wkversion < 534 ) {
12160 //Android 2.3 run all Android 2.3 workaround
12161 self._bindScrollWorkaround();
12162 self._bindListThumbWorkaround();
12163 } else {
12164 return;
12165 }
12166 },
12167
12168 //Utility class for checking header and footer positions relative to viewport
12169 _viewportOffset: function() {
12170 var $el = this.element,
12171 header = $el.hasClass( "ui-header" ),
12172 offset = Math.abs( $el.offset().top - this.window.scrollTop() );
12173 if ( !header ) {
12174 offset = Math.round( offset - this.window.height() + $el.outerHeight() ) - 60;
12175 }
12176 return offset;
12177 },
12178
12179 //bind events for _triggerRedraw() function
12180 _bindScrollWorkaround: function() {
12181 var self = this;
12182 //bind to scrollstop and check if the toolbars are correctly positioned
12183 this._on( this.window, { scrollstop: function() {
12184 var viewportOffset = self._viewportOffset();
12185 //check if the header is visible and if its in the right place
12186 if ( viewportOffset > 2 && self._visible ) {
12187 self._triggerRedraw();
12188 }
12189 }});
12190 },
12191
12192 //this addresses issue #4250 Persistent footer instability in v1.1 with long select lists in Android 2.3.3
12193 //and issue #3748 Android 2.x: Page transitions broken when fixed toolbars used
12194 //the absolutely positioned thumbnail in a list view causes problems with fixed position buttons above in a nav bar
12195 //setting the li's to -webkit-transform:translate3d(0,0,0); solves this problem to avoide potential issues in other
12196 //platforms we scope this with the class ui-android-2x-fix
12197 _bindListThumbWorkaround: function() {
12198 this.element.closest( ".ui-page" ).addClass( "ui-android-2x-fixed" );
12199 },
12200 //this addresses issues #4337 Fixed header problem after scrolling content on iOS and Android
12201 //and device bugs project issue #1 Form elements can lose click hit area in position: fixed containers.
12202 //this also addresses not on fixed toolbars page in docs
12203 //adding 1px of padding to the bottom then removing it causes a "redraw"
12204 //which positions the toolbars correctly (they will always be visually correct)
12205 _triggerRedraw: function() {
12206 var paddingBottom = parseFloat( $( ".ui-page-active" ).css( "padding-bottom" ) );
12207 //trigger page redraw to fix incorrectly positioned fixed elements
12208 $( ".ui-page-active" ).css( "padding-bottom", ( paddingBottom + 1 ) + "px" );
12209 //if the padding is reset with out a timeout the reposition will not occure.
12210 //this is independant of JQM the browser seems to need the time to react.
12211 setTimeout( function() {
12212 $( ".ui-page-active" ).css( "padding-bottom", paddingBottom + "px" );
12213 }, 0 );
12214 },
12215
12216 destroy: function() {
12217 this._super();
12218 //Remove the class we added to the page previously in android 2.x
12219 this.element.closest( ".ui-page-active" ).removeClass( "ui-android-2x-fix" );
12220 }
12221 });
12222
12223})( jQuery );
12224
12225
12226( function( $, undefined ) {
12227
12228var ieHack = ( $.mobile.browser.oldIE && $.mobile.browser.oldIE <= 8 ),
12229 uiTemplate = $(
12230 "<div class='ui-popup-arrow-guide'></div>" +
12231 "<div class='ui-popup-arrow-container" + ( ieHack ? " ie" : "" ) + "'>" +
12232 "<div class='ui-popup-arrow'>" +
12233 "<div class='ui-popup-arrow-background'></div>" +
12234 "</div>" +
12235 "</div>"
12236 ),
12237 // Needed for transforming coordinates from screen to arrow background
12238 txFactor = Math.sqrt( 2 ) / 2;
12239
12240function getArrow() {
12241 var clone = uiTemplate.clone(),
12242 gd = clone.eq( 0 ),
12243 ct = clone.eq( 1 ),
12244 ar = ct.children(),
12245 bg = ar.children();
12246
12247 return { arEls: ct.add( gd ), gd: gd, ct: ct, ar: ar, bg: bg };
12248}
12249
12250$.widget( "mobile.popup", $.mobile.popup, {
12251 options: {
12252
12253 arrow: ""
12254 },
12255
12256 _create: function() {
12257 var ar,
12258 ret = this._super();
12259
12260 if ( this.options.arrow ) {
12261 this._ui.arrow = ar = this._addArrow();
12262 }
12263
12264 return ret;
12265 },
12266
12267 _addArrow: function() {
12268 var theme,
12269 opts = this.options,
12270 ar = getArrow();
12271
12272 theme = this._themeClassFromOption( "ui-body-", opts.theme );
12273 ar.ar.addClass( theme + ( opts.shadow ? " ui-overlay-shadow" : "" ) );
12274 ar.bg.addClass( theme );
12275 ar.arEls.hide().appendTo( this.element );
12276
12277 return ar;
12278 },
12279
12280 _unenhance: function() {
12281 var ar = this._ui.arrow;
12282
12283 if ( ar ) {
12284 ar.arEls.remove();
12285 }
12286
12287 return this._super();
12288 },
12289
12290 // Pretend to show an arrow described by @p and @dir and calculate the
12291 // distance from the desired point. If a best-distance is passed in, return
12292 // the minimum of the one passed in and the one calculated.
12293 _tryAnArrow: function( p, dir, desired, s, best ) {
12294 var result, r, diff, desiredForArrow = {}, tip = {};
12295
12296 // If the arrow has no wiggle room along the edge of the popup, it cannot
12297 // be displayed along the requested edge without it sticking out.
12298 if ( s.arFull[ p.dimKey ] > s.guideDims[ p.dimKey ] ) {
12299 return best;
12300 }
12301
12302 desiredForArrow[ p.fst ] = desired[ p.fst ] +
12303 ( s.arHalf[ p.oDimKey ] + s.menuHalf[ p.oDimKey ] ) * p.offsetFactor -
12304 s.contentBox[ p.fst ] + ( s.clampInfo.menuSize[ p.oDimKey ] - s.contentBox[ p.oDimKey ] ) * p.arrowOffsetFactor;
12305 desiredForArrow[ p.snd ] = desired[ p.snd ];
12306
12307 result = s.result || this._calculateFinalLocation( desiredForArrow, s.clampInfo );
12308 r = { x: result.left, y: result.top };
12309
12310 tip[ p.fst ] = r[ p.fst ] + s.contentBox[ p.fst ] + p.tipOffset;
12311 tip[ p.snd ] = Math.max( result[ p.prop ] + s.guideOffset[ p.prop ] + s.arHalf[ p.dimKey ],
12312 Math.min( result[ p.prop ] + s.guideOffset[ p.prop ] + s.guideDims[ p.dimKey ] - s.arHalf[ p.dimKey ],
12313 desired[ p.snd ] ) );
12314
12315 diff = Math.abs( desired.x - tip.x ) + Math.abs( desired.y - tip.y );
12316 if ( !best || diff < best.diff ) {
12317 // Convert tip offset to coordinates inside the popup
12318 tip[ p.snd ] -= s.arHalf[ p.dimKey ] + result[ p.prop ] + s.contentBox[ p.snd ];
12319 best = { dir: dir, diff: diff, result: result, posProp: p.prop, posVal: tip[ p.snd ] };
12320 }
12321
12322 return best;
12323 },
12324
12325 _getPlacementState: function( clamp ) {
12326 var offset, gdOffset,
12327 ar = this._ui.arrow,
12328 state = {
12329 clampInfo: this._clampPopupWidth( !clamp ),
12330 arFull: { cx: ar.ct.width(), cy: ar.ct.height() },
12331 guideDims: { cx: ar.gd.width(), cy: ar.gd.height() },
12332 guideOffset: ar.gd.offset()
12333 };
12334
12335 offset = this.element.offset();
12336
12337 ar.gd.css( { left: 0, top: 0, right: 0, bottom: 0 } );
12338 gdOffset = ar.gd.offset();
12339 state.contentBox = {
12340 x: gdOffset.left - offset.left,
12341 y: gdOffset.top - offset.top,
12342 cx: ar.gd.width(),
12343 cy: ar.gd.height()
12344 };
12345 ar.gd.removeAttr( "style" );
12346
12347 // The arrow box moves between guideOffset and guideOffset + guideDims - arFull
12348 state.guideOffset = { left: state.guideOffset.left - offset.left, top: state.guideOffset.top - offset.top };
12349 state.arHalf = { cx: state.arFull.cx / 2, cy: state.arFull.cy / 2 };
12350 state.menuHalf = { cx: state.clampInfo.menuSize.cx / 2, cy: state.clampInfo.menuSize.cy / 2 };
12351
12352 return state;
12353 },
12354
12355 _placementCoords: function( desired ) {
12356 var state, best, params, bgOffset, elOffset, diff, bgRef,
12357 optionValue = this.options.arrow,
12358 ar = this._ui.arrow;
12359
12360 if ( !ar ) {
12361 return this._super( desired );
12362 }
12363
12364 ar.arEls.show();
12365
12366 bgRef = {};
12367 state = this._getPlacementState( true );
12368 params = {
12369 "l": { fst: "x", snd: "y", prop: "top", dimKey: "cy", oDimKey: "cx", offsetFactor: 1, tipOffset: -state.arHalf.cx, arrowOffsetFactor: 0 },
12370 "r": { fst: "x", snd: "y", prop: "top", dimKey: "cy", oDimKey: "cx", offsetFactor: -1, tipOffset: state.arHalf.cx + state.contentBox.cx, arrowOffsetFactor: 1 },
12371 "b": { fst: "y", snd: "x", prop: "left", dimKey: "cx", oDimKey: "cy", offsetFactor: -1, tipOffset: state.arHalf.cy + state.contentBox.cy, arrowOffsetFactor: 1 },
12372 "t": { fst: "y", snd: "x", prop: "left", dimKey: "cx", oDimKey: "cy", offsetFactor: 1, tipOffset: -state.arHalf.cy, arrowOffsetFactor: 0 }
12373 };
12374
12375 // Try each side specified in the options to see on which one the arrow
12376 // should be placed such that the distance between the tip of the arrow and
12377 // the desired coordinates is the shortest.
12378 $.each( ( optionValue === true ? "l,t,r,b" : optionValue ).split( "," ),
12379 $.proxy( function( key, value ) {
12380 best = this._tryAnArrow( params[ value ], value, desired, state, best );
12381 }, this ) );
12382
12383 // Could not place the arrow along any of the edges - behave as if showing
12384 // the arrow was turned off.
12385 if ( !best ) {
12386 ar.arEls.hide();
12387 return this._super( desired );
12388 }
12389
12390 // Move the arrow into place
12391 ar.ct
12392 .removeClass( "ui-popup-arrow-l ui-popup-arrow-t ui-popup-arrow-r ui-popup-arrow-b" )
12393 .addClass( "ui-popup-arrow-" + best.dir )
12394 .removeAttr( "style" ).css( best.posProp, best.posVal )
12395 .show();
12396
12397 // Do not move/size the background div on IE, because we use the arrow div for background as well.
12398 if ( !ieHack ) {
12399 elOffset = this.element.offset();
12400 bgRef[ params[ best.dir ].fst ] = ar.ct.offset();
12401 bgRef[ params[ best.dir ].snd ] = {
12402 left: elOffset.left + state.contentBox.x,
12403 top: elOffset.top + state.contentBox.y
12404 };
12405 bgOffset = ar.bg
12406 .removeAttr( "style" )
12407 .css( ( "cx" === params[ best.dir ].dimKey ? "width" : "height" ), state.contentBox[ params[ best.dir ].dimKey ] )
12408 .offset();
12409 diff = { dx: bgRef.x.left - bgOffset.left, dy: bgRef.y.top - bgOffset.top };
12410 ar.bg.css( { left: txFactor * diff.dy + txFactor * diff.dx, top: txFactor * diff.dy - txFactor * diff.dx } );
12411 }
12412
12413 return best.result;
12414 },
12415
12416 _setOptions: function( opts ) {
12417 var newTheme,
12418 oldTheme = this.options.theme,
12419 ar = this._ui.arrow,
12420 ret = this._super( opts );
12421
12422 if ( opts.arrow !== undefined ) {
12423 if ( !ar && opts.arrow ) {
12424 this._ui.arrow = this._addArrow();
12425
12426 // Important to return here so we don't set the same options all over
12427 // again below.
12428 return;
12429 } else if ( ar && !opts.arrow ) {
12430 ar.arEls.remove();
12431 this._ui.arrow = null;
12432 }
12433 }
12434
12435 // Reassign with potentially new arrow
12436 ar = this._ui.arrow;
12437
12438 if ( ar ) {
12439 if ( opts.theme !== undefined ) {
12440 oldTheme = this._themeClassFromOption( "ui-body-", oldTheme );
12441 newTheme = this._themeClassFromOption( "ui-body-", opts.theme );
12442 ar.ar.removeClass( oldTheme ).addClass( newTheme );
12443 ar.bg.removeClass( oldTheme ).addClass( newTheme );
12444 }
12445
12446 if ( opts.shadow !== undefined ) {
12447 ar.ar.toggleClass( "ui-overlay-shadow", opts.shadow );
12448 }
12449 }
12450
12451 return ret;
12452 },
12453
12454 _destroy: function() {
12455 var ar = this._ui.arrow;
12456
12457 if ( ar ) {
12458 ar.arEls.remove();
12459 }
12460
12461 return this._super();
12462 }
12463});
12464
12465})( jQuery );
12466
12467
12468(function( $, undefined ) {
12469
12470$.widget( "mobile.panel", {
12471 options: {
12472 classes: {
12473 panel: "ui-panel",
12474 panelOpen: "ui-panel-open",
12475 panelClosed: "ui-panel-closed",
12476 panelFixed: "ui-panel-fixed",
12477 panelInner: "ui-panel-inner",
12478 modal: "ui-panel-dismiss",
12479 modalOpen: "ui-panel-dismiss-open",
12480 pageContainer: "ui-panel-page-container",
12481 pageWrapper: "ui-panel-wrapper",
12482 pageFixedToolbar: "ui-panel-fixed-toolbar",
12483 pageContentPrefix: "ui-panel-page-content", /* Used for wrapper and fixed toolbars position, display and open classes. */
12484 animate: "ui-panel-animate"
12485 },
12486 animate: true,
12487 theme: null,
12488 position: "left",
12489 dismissible: true,
12490 display: "reveal", //accepts reveal, push, overlay
12491 swipeClose: true,
12492 positionFixed: false
12493 },
12494
12495 _panelID: null,
12496 _closeLink: null,
12497 _parentPage: null,
12498 _page: null,
12499 _modal: null,
12500 _panelInner: null,
12501 _wrapper: null,
12502 _fixedToolbars: null,
12503
12504 _create: function() {
12505 var el = this.element,
12506 parentPage = el.closest( ":jqmData(role='page')" );
12507
12508 // expose some private props to other methods
12509 $.extend( this, {
12510 _panelID: el.attr( "id" ),
12511 _closeLink: el.find( ":jqmData(rel='close')" ),
12512 _parentPage: ( parentPage.length > 0 ) ? parentPage : false,
12513 _page: this._getPage,
12514 _panelInner: this._getPanelInner(),
12515 _wrapper: this._getWrapper,
12516 _fixedToolbars: this._getFixedToolbars
12517 });
12518
12519 this._addPanelClasses();
12520
12521 // if animating, add the class to do so
12522 if ( $.support.cssTransform3d && !!this.options.animate ) {
12523 this.element.addClass( this.options.classes.animate );
12524 }
12525
12526 this._bindUpdateLayout();
12527 this._bindCloseEvents();
12528 this._bindLinkListeners();
12529 this._bindPageEvents();
12530
12531 if ( !!this.options.dismissible ) {
12532 this._createModal();
12533 }
12534
12535 this._bindSwipeEvents();
12536 },
12537
12538 _getPanelInner: function() {
12539 var panelInner = this.element.find( "." + this.options.classes.panelInner );
12540
12541 if ( panelInner.length === 0 ) {
12542 panelInner = this.element.children().wrapAll( "<div class='" + this.options.classes.panelInner + "' />" ).parent();
12543 }
12544
12545 return panelInner;
12546 },
12547
12548 _createModal: function() {
12549 var self = this,
12550 target = self._parentPage ? self._parentPage.parent() : self.element.parent();
12551
12552 self._modal = $( "<div class='" + self.options.classes.modal + "' data-panelid='" + self._panelID + "'></div>" )
12553 .on( "mousedown", function() {
12554 self.close();
12555 })
12556 .appendTo( target );
12557 },
12558
12559 _getPage: function() {
12560 var page = this._parentPage ? this._parentPage : $( "." + $.mobile.activePageClass );
12561
12562 return page;
12563 },
12564
12565 _getWrapper: function() {
12566 var wrapper = this._page().find( "." + this.options.classes.pageWrapper );
12567
12568 if ( wrapper.length === 0 ) {
12569 wrapper = this._page().children( ".ui-header:not(.ui-header-fixed), .ui-content:not(.ui-popup), .ui-footer:not(.ui-footer-fixed)" )
12570 .wrapAll( "<div class='" + this.options.classes.pageWrapper + "'></div>" )
12571 .parent();
12572 }
12573
12574 return wrapper;
12575 },
12576
12577 _getFixedToolbars: function() {
12578 var extFixedToolbars = $( "body" ).children( ".ui-header-fixed, .ui-footer-fixed" ),
12579 intFixedToolbars = this._page().find( ".ui-header-fixed, .ui-footer-fixed" ),
12580 fixedToolbars = extFixedToolbars.add( intFixedToolbars ).addClass( this.options.classes.pageFixedToolbar );
12581
12582 return fixedToolbars;
12583 },
12584
12585 _getPosDisplayClasses: function( prefix ) {
12586 return prefix + "-position-" + this.options.position + " " + prefix + "-display-" + this.options.display;
12587 },
12588
12589 _getPanelClasses: function() {
12590 var panelClasses = this.options.classes.panel +
12591 " " + this._getPosDisplayClasses( this.options.classes.panel ) +
12592 " " + this.options.classes.panelClosed +
12593 " " + "ui-body-" + ( this.options.theme ? this.options.theme : "inherit" );
12594
12595 if ( !!this.options.positionFixed ) {
12596 panelClasses += " " + this.options.classes.panelFixed;
12597 }
12598
12599 return panelClasses;
12600 },
12601
12602 _addPanelClasses: function() {
12603 this.element.addClass( this._getPanelClasses() );
12604 },
12605
12606 _bindCloseEvents: function() {
12607 var self = this;
12608
12609 self._closeLink.on( "click.panel" , function( e ) {
12610 e.preventDefault();
12611 self.close();
12612 return false;
12613 });
12614 self.element.on( "click.panel" , "a:jqmData(ajax='false')", function(/* e */) {
12615 self.close();
12616 });
12617 },
12618
12619 _positionPanel: function() {
12620 var self = this,
12621 panelInnerHeight = self._panelInner.outerHeight(),
12622 expand = panelInnerHeight > $.mobile.getScreenHeight();
12623
12624 if ( expand || !self.options.positionFixed ) {
12625 if ( expand ) {
12626 self._unfixPanel();
12627 $.mobile.resetActivePageHeight( panelInnerHeight );
12628 }
12629 window.scrollTo( 0, $.mobile.defaultHomeScroll );
12630 } else {
12631 self._fixPanel();
12632 }
12633 },
12634
12635 _bindFixListener: function() {
12636 this._on( $( window ), { "throttledresize": "_positionPanel" });
12637 },
12638
12639 _unbindFixListener: function() {
12640 this._off( $( window ), "throttledresize" );
12641 },
12642
12643 _unfixPanel: function() {
12644 if ( !!this.options.positionFixed && $.support.fixedPosition ) {
12645 this.element.removeClass( this.options.classes.panelFixed );
12646 }
12647 },
12648
12649 _fixPanel: function() {
12650 if ( !!this.options.positionFixed && $.support.fixedPosition ) {
12651 this.element.addClass( this.options.classes.panelFixed );
12652 }
12653 },
12654
12655 _bindUpdateLayout: function() {
12656 var self = this;
12657
12658 self.element.on( "updatelayout", function(/* e */) {
12659 if ( self._open ) {
12660 self._positionPanel();
12661 }
12662 });
12663 },
12664
12665 _bindLinkListeners: function() {
12666 this._on( "body", {
12667 "click a": "_handleClick"
12668 });
12669
12670 },
12671
12672 _handleClick: function( e ) {
12673 if ( e.currentTarget.href.split( "#" )[ 1 ] === this._panelID && this._panelID !== undefined ) {
12674 e.preventDefault();
12675 var link = $( e.target );
12676 if ( link.hasClass( "ui-btn" ) ) {
12677 link.addClass( $.mobile.activeBtnClass );
12678 this.element.one( "panelopen panelclose", function() {
12679 link.removeClass( $.mobile.activeBtnClass );
12680 });
12681 }
12682 this.toggle();
12683 return false;
12684 }
12685 },
12686
12687 _bindSwipeEvents: function() {
12688 var self = this,
12689 area = self._modal ? self.element.add( self._modal ) : self.element;
12690
12691 // on swipe, close the panel
12692 if ( !!self.options.swipeClose ) {
12693 if ( self.options.position === "left" ) {
12694 area.on( "swipeleft.panel", function(/* e */) {
12695 self.close();
12696 });
12697 } else {
12698 area.on( "swiperight.panel", function(/* e */) {
12699 self.close();
12700 });
12701 }
12702 }
12703 },
12704
12705 _bindPageEvents: function() {
12706 var self = this;
12707
12708 this.document
12709 // Close the panel if another panel on the page opens
12710 .on( "panelbeforeopen", function( e ) {
12711 if ( self._open && e.target !== self.element[ 0 ] ) {
12712 self.close();
12713 }
12714 })
12715 // On escape, close? might need to have a target check too...
12716 .on( "keyup.panel", function( e ) {
12717 if ( e.keyCode === 27 && self._open ) {
12718 self.close();
12719 }
12720 });
12721
12722 // Clean up open panels after page hide
12723 if ( self._parentPage ) {
12724 this.document.on( "pagehide", ":jqmData(role='page')", function() {
12725 if ( self._open ) {
12726 self.close( true );
12727 }
12728 });
12729 } else {
12730 this.document.on( "pagebeforehide", function() {
12731 if ( self._open ) {
12732 self.close( true );
12733 }
12734 });
12735 }
12736 },
12737
12738 // state storage of open or closed
12739 _open: false,
12740 _pageContentOpenClasses: null,
12741 _modalOpenClasses: null,
12742
12743 open: function( immediate ) {
12744 if ( !this._open ) {
12745 var self = this,
12746 o = self.options,
12747
12748 _openPanel = function() {
12749 self.document.off( "panelclose" );
12750 self._page().jqmData( "panel", "open" );
12751
12752 if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
12753 self._wrapper().addClass( o.classes.animate );
12754 self._fixedToolbars().addClass( o.classes.animate );
12755 }
12756
12757 if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
12758 self.document.on( self._transitionEndEvents, complete );
12759 } else {
12760 setTimeout( complete, 0 );
12761 }
12762
12763 if ( o.theme && o.display !== "overlay" ) {
12764 self._page().parent()
12765 .addClass( o.classes.pageContainer + "-themed " + o.classes.pageContainer + "-" + o.theme );
12766 }
12767
12768 self.element
12769 .removeClass( o.classes.panelClosed )
12770 .addClass( o.classes.panelOpen );
12771
12772 self._positionPanel();
12773
12774 self._pageContentOpenClasses = self._getPosDisplayClasses( o.classes.pageContentPrefix );
12775
12776 if ( o.display !== "overlay" ) {
12777 self._page().parent().addClass( o.classes.pageContainer );
12778 self._wrapper().addClass( self._pageContentOpenClasses );
12779 self._fixedToolbars().addClass( self._pageContentOpenClasses );
12780 }
12781
12782 self._modalOpenClasses = self._getPosDisplayClasses( o.classes.modal ) + " " + o.classes.modalOpen;
12783 if ( self._modal ) {
12784 self._modal
12785 .addClass( self._modalOpenClasses )
12786 .height( Math.max( self._modal.height(), self.document.height() ) );
12787 }
12788 },
12789 complete = function() {
12790 self.document.off( self._transitionEndEvents, complete );
12791
12792 if ( o.display !== "overlay" ) {
12793 self._wrapper().addClass( o.classes.pageContentPrefix + "-open" );
12794 self._fixedToolbars().addClass( o.classes.pageContentPrefix + "-open" );
12795 }
12796
12797 self._bindFixListener();
12798
12799 self._trigger( "open" );
12800 };
12801
12802 self._trigger( "beforeopen" );
12803
12804 if ( self._page().jqmData( "panel" ) === "open" ) {
12805 self.document.on( "panelclose", function() {
12806 _openPanel();
12807 });
12808 } else {
12809 _openPanel();
12810 }
12811
12812 self._open = true;
12813 }
12814 },
12815
12816 close: function( immediate ) {
12817 if ( this._open ) {
12818 var self = this,
12819 o = this.options,
12820
12821 _closePanel = function() {
12822 if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
12823 self.document.on( self._transitionEndEvents, complete );
12824 } else {
12825 setTimeout( complete, 0 );
12826 }
12827
12828 self.element.removeClass( o.classes.panelOpen );
12829
12830 if ( o.display !== "overlay" ) {
12831 self._wrapper().removeClass( self._pageContentOpenClasses );
12832 self._fixedToolbars().removeClass( self._pageContentOpenClasses );
12833 }
12834
12835 if ( self._modal ) {
12836 self._modal.removeClass( self._modalOpenClasses );
12837 }
12838 },
12839 complete = function() {
12840 self.document.off( self._transitionEndEvents, complete );
12841
12842 if ( o.theme && o.display !== "overlay" ) {
12843 self._page().parent().removeClass( o.classes.pageContainer + "-themed " + o.classes.pageContainer + "-" + o.theme );
12844 }
12845
12846 self.element.addClass( o.classes.panelClosed );
12847
12848 if ( o.display !== "overlay" ) {
12849 self._page().parent().removeClass( o.classes.pageContainer );
12850 self._wrapper().removeClass( o.classes.pageContentPrefix + "-open" );
12851 self._fixedToolbars().removeClass( o.classes.pageContentPrefix + "-open" );
12852 }
12853
12854 if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
12855 self._wrapper().removeClass( o.classes.animate );
12856 self._fixedToolbars().removeClass( o.classes.animate );
12857 }
12858
12859 self._fixPanel();
12860 self._unbindFixListener();
12861 $.mobile.resetActivePageHeight();
12862
12863 self._page().jqmRemoveData( "panel" );
12864
12865 self._trigger( "close" );
12866 };
12867
12868 self._trigger( "beforeclose" );
12869
12870 _closePanel();
12871
12872 self._open = false;
12873 }
12874 },
12875
12876 toggle: function() {
12877 this[ this._open ? "close" : "open" ]();
12878 },
12879
12880 _transitionEndEvents: "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd",
12881
12882 _destroy: function() {
12883 var otherPanels,
12884 o = this.options,
12885 multiplePanels = ( $( "body > :mobile-panel" ).length + $.mobile.activePage.find( ":mobile-panel" ).length ) > 1;
12886
12887 if ( o.display !== "overlay" ) {
12888
12889 // remove the wrapper if not in use by another panel
12890 otherPanels = $( "body > :mobile-panel" ).add( $.mobile.activePage.find( ":mobile-panel" ) );
12891 if ( otherPanels.not( ".ui-panel-display-overlay" ).not( this.element ).length === 0 ) {
12892 this._wrapper().children().unwrap();
12893 }
12894
12895 if ( this._open ) {
12896
12897 this._fixedToolbars().removeClass( o.classes.pageContentPrefix + "-open" );
12898
12899 if ( $.support.cssTransform3d && !!o.animate ) {
12900 this._fixedToolbars().removeClass( o.classes.animate );
12901 }
12902
12903 this._page().parent().removeClass( o.classes.pageContainer );
12904
12905 if ( o.theme ) {
12906 this._page().parent().removeClass( o.classes.pageContainer + "-themed " + o.classes.pageContainer + "-" + o.theme );
12907 }
12908 }
12909 }
12910
12911 if ( !multiplePanels ) {
12912
12913 this.document.off( "panelopen panelclose" );
12914
12915 if ( this._open ) {
12916 this.document.off( this._transitionEndEvents );
12917 $.mobile.resetActivePageHeight();
12918 }
12919 }
12920
12921 if ( this._open ) {
12922 this._page().jqmRemoveData( "panel" );
12923 }
12924
12925 this._panelInner.children().unwrap();
12926
12927 this.element
12928 .removeClass( [ this._getPanelClasses(), o.classes.panelOpen, o.classes.animate ].join( " " ) )
12929 .off( "swipeleft.panel swiperight.panel" )
12930 .off( "panelbeforeopen" )
12931 .off( "panelhide" )
12932 .off( "keyup.panel" )
12933 .off( "updatelayout" )
12934 .off( this._transitionEndEvents );
12935
12936 this._closeLink.off( "click.panel" );
12937
12938 if ( this._modal ) {
12939 this._modal.remove();
12940 }
12941 }
12942});
12943
12944})( jQuery );
12945
12946(function( $, undefined ) {
12947
12948$.widget( "mobile.table", {
12949 options: {
12950 classes: {
12951 table: "ui-table"
12952 },
12953 enhanced: false
12954 },
12955
12956 _create: function() {
12957 if ( !this.options.enhanced ) {
12958 this.element.addClass( this.options.classes.table );
12959 }
12960
12961 // extend here, assign on refresh > _setHeaders
12962 $.extend( this, {
12963
12964 // Expose headers and allHeaders properties on the widget
12965 // headers references the THs within the first TR in the table
12966 headers: undefined,
12967
12968 // allHeaders references headers, plus all THs in the thead, which may
12969 // include several rows, or not
12970 allHeaders: undefined
12971 });
12972
12973 this._refresh( true );
12974 },
12975
12976 _setHeaders: function() {
12977 var trs = this.element.find( "thead tr" );
12978
12979 this.headers = this.element.find( "tr:eq(0)" ).children();
12980 this.allHeaders = this.headers.add( trs.children() );
12981 },
12982
12983 refresh: function() {
12984 this._refresh();
12985 },
12986
12987 rebuild: $.noop,
12988
12989 _refresh: function( /* create */ ) {
12990 var table = this.element,
12991 trs = table.find( "thead tr" );
12992
12993 // updating headers on refresh (fixes #5880)
12994 this._setHeaders();
12995
12996 // Iterate over the trs
12997 trs.each( function() {
12998 var columnCount = 0;
12999
13000 // Iterate over the children of the tr
13001 $( this ).children().each( function() {
13002 var span = parseInt( this.getAttribute( "colspan" ), 10 ),
13003 selector = ":nth-child(" + ( columnCount + 1 ) + ")",
13004 j;
13005
13006 this.setAttribute( "data-" + $.mobile.ns + "colstart", columnCount + 1 );
13007
13008 if ( span ) {
13009 for( j = 0; j < span - 1; j++ ) {
13010 columnCount++;
13011 selector += ", :nth-child(" + ( columnCount + 1 ) + ")";
13012 }
13013 }
13014
13015 // Store "cells" data on header as a reference to all cells in the
13016 // same column as this TH
13017 $( this ).jqmData( "cells", table.find( "tr" ).not( trs.eq( 0 ) ).not( this ).children( selector ) );
13018
13019 columnCount++;
13020 });
13021 });
13022 }
13023});
13024
13025})( jQuery );
13026
13027
13028(function( $, undefined ) {
13029
13030$.widget( "mobile.table", $.mobile.table, {
13031 options: {
13032 mode: "columntoggle",
13033 columnBtnTheme: null,
13034 columnPopupTheme: null,
13035 columnBtnText: "Columns...",
13036 classes: $.extend( $.mobile.table.prototype.options.classes, {
13037 popup: "ui-table-columntoggle-popup",
13038 columnBtn: "ui-table-columntoggle-btn",
13039 priorityPrefix: "ui-table-priority-",
13040 columnToggleTable: "ui-table-columntoggle"
13041 })
13042 },
13043
13044 _create: function() {
13045 this._super();
13046
13047 if ( this.options.mode !== "columntoggle" ) {
13048 return;
13049 }
13050
13051 $.extend( this, {
13052 _menu: null
13053 });
13054
13055 if ( this.options.enhanced ) {
13056 this._menu = $( this.document[ 0 ].getElementById( this._id() + "-popup" ) ).children().first();
13057 this._addToggles( this._menu, true );
13058 this._bindToggles( this._menu );
13059 } else {
13060 this._menu = this._enhanceColToggle();
13061 this.element.addClass( this.options.classes.columnToggleTable );
13062 }
13063
13064 this._setupEvents();
13065
13066 this._setToggleState();
13067 },
13068
13069 _id: function() {
13070 return ( this.element.attr( "id" ) || ( this.widgetName + this.uuid ) );
13071 },
13072
13073 _setupEvents: function() {
13074 //NOTE: inputs are bound in bindToggles,
13075 // so it can be called on refresh, too
13076
13077 // update column toggles on resize
13078 this._on( this.window, {
13079 throttledresize: "_setToggleState"
13080 });
13081 },
13082
13083 _bindToggles: function( menu ) {
13084 var inputs = menu.find( "input" );
13085
13086 this._on( inputs, {
13087 change: "_menuInputChange"
13088 });
13089 },
13090
13091 _addToggles: function( menu, keep ) {
13092 var inputs,
13093 checkboxIndex = 0,
13094 opts = this.options,
13095 container = menu.controlgroup( "container" );
13096
13097 // allow update of menu on refresh (fixes #5880)
13098 if ( keep ) {
13099 inputs = menu.find( "input" );
13100 } else {
13101 container.empty();
13102 }
13103
13104 // create the hide/show toggles
13105 this.headers.not( "td" ).each( function() {
13106 var header = $( this ),
13107 priority = $.mobile.getAttribute( this, "priority" ),
13108 cells = header.add( header.jqmData( "cells" ) );
13109
13110 if ( priority ) {
13111 cells.addClass( opts.classes.priorityPrefix + priority );
13112
13113 ( keep ? inputs.eq( checkboxIndex++ ) :
13114 $("<label><input type='checkbox' checked />" +
13115 ( header.children( "abbr" ).first().attr( "title" ) ||
13116 header.text() ) +
13117 "</label>" )
13118 .appendTo( container )
13119 .children( 0 )
13120 .checkboxradio( {
13121 theme: opts.columnPopupTheme
13122 }) )
13123 .jqmData( "cells", cells );
13124 }
13125 });
13126
13127 // set bindings here
13128 if ( !keep ) {
13129 menu.controlgroup( "refresh" );
13130 this._bindToggles( menu );
13131 }
13132 },
13133
13134 _menuInputChange: function( evt ) {
13135 var input = $( evt.target ),
13136 checked = input[ 0 ].checked;
13137
13138 input.jqmData( "cells" )
13139 .toggleClass( "ui-table-cell-hidden", !checked )
13140 .toggleClass( "ui-table-cell-visible", checked );
13141
13142 if ( input[ 0 ].getAttribute( "locked" ) ) {
13143 input.removeAttr( "locked" );
13144
13145 this._unlockCells( input.jqmData( "cells" ) );
13146 } else {
13147 input.attr( "locked", true );
13148 }
13149 },
13150
13151 _unlockCells: function( cells ) {
13152 // allow hide/show via CSS only = remove all toggle-locks
13153 cells.removeClass( "ui-table-cell-hidden ui-table-cell-visible");
13154 },
13155
13156 _enhanceColToggle: function() {
13157 var id , menuButton, popup, menu,
13158 table = this.element,
13159 opts = this.options,
13160 ns = $.mobile.ns,
13161 fragment = this.document[ 0 ].createDocumentFragment();
13162
13163 id = this._id() + "-popup";
13164 menuButton = $( "<a href='#" + id + "' " +
13165 "class='" + opts.classes.columnBtn + " ui-btn " +
13166 "ui-btn-" + ( opts.columnBtnTheme || "a" ) +
13167 " ui-corner-all ui-shadow ui-mini' " +
13168 "data-" + ns + "rel='popup'>" + opts.columnBtnText + "</a>" );
13169 popup = $( "<div class='" + opts.classes.popup + "' id='" + id + "'></div>" );
13170 menu = $( "<fieldset></fieldset>" ).controlgroup();
13171
13172 // set extension here, send "false" to trigger build/rebuild
13173 this._addToggles( menu, false );
13174
13175 menu.appendTo( popup );
13176
13177 fragment.appendChild( popup[ 0 ] );
13178 fragment.appendChild( menuButton[ 0 ] );
13179 table.before( fragment );
13180
13181 popup.popup();
13182
13183 return menu;
13184 },
13185
13186 rebuild: function() {
13187 this._super();
13188
13189 if ( this.options.mode === "columntoggle" ) {
13190 // NOTE: rebuild passes "false", while refresh passes "undefined"
13191 // both refresh the table, but inside addToggles, !false will be true,
13192 // so a rebuild call can be indentified
13193 this._refresh( false );
13194 }
13195 },
13196
13197 _refresh: function( create ) {
13198 this._super( create );
13199
13200 if ( !create && this.options.mode === "columntoggle" ) {
13201 // columns not being replaced must be cleared from input toggle-locks
13202 this._unlockCells( this.allHeaders );
13203
13204 // update columntoggles and cells
13205 this._addToggles( this._menu, create );
13206
13207 // check/uncheck
13208 this._setToggleState();
13209 }
13210 },
13211
13212 _setToggleState: function() {
13213 this._menu.find( "input" ).each( function() {
13214 var checkbox = $( this );
13215
13216 this.checked = checkbox.jqmData( "cells" ).eq( 0 ).css( "display" ) === "table-cell";
13217 checkbox.checkboxradio( "refresh" );
13218 });
13219 },
13220
13221 _destroy: function() {
13222 this._super();
13223 }
13224});
13225
13226})( jQuery );
13227
13228(function( $, undefined ) {
13229
13230$.widget( "mobile.table", $.mobile.table, {
13231 options: {
13232 mode: "reflow",
13233 classes: $.extend( $.mobile.table.prototype.options.classes, {
13234 reflowTable: "ui-table-reflow",
13235 cellLabels: "ui-table-cell-label"
13236 })
13237 },
13238
13239 _create: function() {
13240 this._super();
13241
13242 // If it's not reflow mode, return here.
13243 if ( this.options.mode !== "reflow" ) {
13244 return;
13245 }
13246
13247 if ( !this.options.enhanced ) {
13248 this.element.addClass( this.options.classes.reflowTable );
13249
13250 this._updateReflow();
13251 }
13252 },
13253
13254 rebuild: function() {
13255 this._super();
13256
13257 if ( this.options.mode === "reflow" ) {
13258 this._refresh( false );
13259 }
13260 },
13261
13262 _refresh: function( create ) {
13263 this._super( create );
13264 if ( !create && this.options.mode === "reflow" ) {
13265 this._updateReflow( );
13266 }
13267 },
13268
13269 _updateReflow: function() {
13270 var table = this,
13271 opts = this.options;
13272
13273 // get headers in reverse order so that top-level headers are appended last
13274 $( table.allHeaders.get().reverse() ).each( function() {
13275 var cells = $( this ).jqmData( "cells" ),
13276 colstart = $.mobile.getAttribute( this, "colstart" ),
13277 hierarchyClass = cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top",
13278 text = $( this ).text(),
13279 iteration, filter;
13280
13281 if ( text !== "" ) {
13282
13283 if ( hierarchyClass ) {
13284 iteration = parseInt( this.getAttribute( "colspan" ), 10 );
13285 filter = "";
13286
13287 if ( iteration ) {
13288 filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")";
13289 }
13290
13291 table._addLabels( cells.filter( filter ), opts.classes.cellLabels + hierarchyClass, text );
13292 } else {
13293 table._addLabels( cells, opts.classes.cellLabels, text );
13294 }
13295
13296 }
13297 });
13298 },
13299
13300 _addLabels: function( cells, label, text ) {
13301 // .not fixes #6006
13302 cells.not( ":has(b." + label + ")" ).prepend( "<b class='" + label + "'>" + text + "</b>" );
13303 }
13304});
13305
13306})( jQuery );
13307
13308(function( $, undefined ) {
13309
13310// TODO rename filterCallback/deprecate and default to the item itself as the first argument
13311var defaultFilterCallback = function( index, searchValue ) {
13312 return ( ( "" + ( $.mobile.getAttribute( this, "filtertext" ) || $( this ).text() ) )
13313 .toLowerCase().indexOf( searchValue ) === -1 );
13314};
13315
13316$.widget( "mobile.filterable", {
13317
13318 initSelector: ":jqmData(filter='true')",
13319
13320 options: {
13321 filterReveal: false,
13322 filterCallback: defaultFilterCallback,
13323 enhanced: false,
13324 input: null,
13325 children: "> li, > option, > optgroup option, > tbody tr, > .ui-controlgroup-controls > .ui-btn, > .ui-controlgroup-controls > .ui-checkbox, > .ui-controlgroup-controls > .ui-radio"
13326 },
13327
13328 _create: function() {
13329 var opts = this.options;
13330
13331 $.extend( this, {
13332 _search: null,
13333 _timer: 0
13334 });
13335
13336 this._setInput( opts.input );
13337 if ( !opts.enhanced ) {
13338 this._filterItems( ( ( this._search && this._search.val() ) || "" ).toLowerCase() );
13339 }
13340 },
13341
13342 _onKeyUp: function() {
13343 var val, lastval,
13344 search = this._search;
13345
13346 if ( search ) {
13347 val = search.val().toLowerCase(),
13348 lastval = $.mobile.getAttribute( search[ 0 ], "lastval" ) + "";
13349
13350 if ( lastval && lastval === val ) {
13351 // Execute the handler only once per value change
13352 return;
13353 }
13354
13355 if ( this._timer ) {
13356 window.clearTimeout( this._timer );
13357 this._timer = 0;
13358 }
13359
13360 this._timer = this._delay( function() {
13361 this._trigger( "beforefilter", "beforefilter", { input: search } );
13362
13363 // Change val as lastval for next execution
13364 search[ 0 ].setAttribute( "data-" + $.mobile.ns + "lastval", val );
13365
13366 this._filterItems( val );
13367 this._timer = 0;
13368 }, 250 );
13369 }
13370 },
13371
13372 _getFilterableItems: function() {
13373 var elem = this.element,
13374 children = this.options.children,
13375 items = !children ? { length: 0 }:
13376 $.isFunction( children ) ? children():
13377 children.nodeName ? $( children ):
13378 children.jquery ? children:
13379 this.element.find( children );
13380
13381 if ( items.length === 0 ) {
13382 items = elem.children();
13383 }
13384
13385 return items;
13386 },
13387
13388 _filterItems: function( val ) {
13389 var idx, callback, length, dst,
13390 show = [],
13391 hide = [],
13392 opts = this.options,
13393 filterItems = this._getFilterableItems();
13394
13395 if ( val != null ) {
13396 callback = opts.filterCallback || defaultFilterCallback;
13397 length = filterItems.length;
13398
13399 // Partition the items into those to be hidden and those to be shown
13400 for ( idx = 0 ; idx < length ; idx++ ) {
13401 dst = ( callback.call( filterItems[ idx ], idx, val ) ) ? hide : show;
13402 dst.push( filterItems[ idx ] );
13403 }
13404 }
13405
13406 // If nothing is hidden, then the decision whether to hide or show the items
13407 // is based on the "filterReveal" option.
13408 if ( hide.length === 0 ) {
13409 filterItems[ opts.filterReveal ? "addClass" : "removeClass" ]( "ui-screen-hidden" );
13410 } else {
13411 $( hide ).addClass( "ui-screen-hidden" );
13412 $( show ).removeClass( "ui-screen-hidden" );
13413 }
13414
13415 this._refreshChildWidget();
13416 },
13417
13418 // The Default implementation of _refreshChildWidget attempts to call
13419 // refresh on collapsibleset, controlgroup, selectmenu, or listview
13420 _refreshChildWidget: function() {
13421 var widget, idx,
13422 recognizedWidgets = [ "collapsibleset", "selectmenu", "controlgroup", "listview" ];
13423
13424 for ( idx = recognizedWidgets.length - 1 ; idx > -1 ; idx-- ) {
13425 widget = recognizedWidgets[ idx ];
13426 if ( $.mobile[ widget ] ) {
13427 widget = this.element.data( "mobile-" + widget );
13428 if ( widget && $.isFunction( widget.refresh ) ) {
13429 widget.refresh();
13430 }
13431 }
13432 }
13433 },
13434
13435 // TODO: When the input is not internal, do not even store it in this._search
13436 _setInput: function ( selector ) {
13437 var search = this._search;
13438
13439 // Stop a pending filter operation
13440 if ( this._timer ) {
13441 window.clearTimeout( this._timer );
13442 this._timer = 0;
13443 }
13444
13445 if ( search ) {
13446 this._off( search, "keyup change input" );
13447 search = null;
13448 }
13449
13450 if ( selector ) {
13451 search = selector.jquery ? selector:
13452 selector.nodeName ? $( selector ):
13453 this.document.find( selector );
13454
13455 this._on( search, {
13456 keyup: "_onKeyUp",
13457 change: "_onKeyUp",
13458 input: "_onKeyUp"
13459 });
13460 }
13461
13462 this._search = search;
13463 },
13464
13465 _setOptions: function( options ) {
13466 var refilter = !( ( options.filterReveal === undefined ) &&
13467 ( options.filterCallback === undefined ) &&
13468 ( options.children === undefined ) );
13469
13470 this._super( options );
13471
13472 if ( options.input !== undefined ) {
13473 this._setInput( options.input );
13474 refilter = true;
13475 }
13476
13477 if ( refilter ) {
13478 this.refresh();
13479 }
13480 },
13481
13482 _destroy: function() {
13483 var opts = this.options,
13484 items = this._getFilterableItems();
13485
13486 if ( opts.enhanced ) {
13487 items.toggleClass( "ui-screen-hidden", opts.filterReveal );
13488 } else {
13489 items.removeClass( "ui-screen-hidden" );
13490 }
13491 },
13492
13493 refresh: function() {
13494 if ( this._timer ) {
13495 window.clearTimeout( this._timer );
13496 this._timer = 0;
13497 }
13498 this._filterItems( ( ( this._search && this._search.val() ) || "" ).toLowerCase() );
13499 }
13500});
13501
13502})( jQuery );
13503
13504(function( $, undefined ) {
13505
13506// Create a function that will replace the _setOptions function of a widget,
13507// and will pass the options on to the input of the filterable.
13508var replaceSetOptions = function( self, orig ) {
13509 return function( options ) {
13510 orig.call( this, options );
13511 self._syncTextInputOptions( options );
13512 };
13513 },
13514 rDividerListItem = /(^|\s)ui-li-divider(\s|$)/,
13515 origDefaultFilterCallback = $.mobile.filterable.prototype.options.filterCallback;
13516
13517// Override the default filter callback with one that does not hide list dividers
13518$.mobile.filterable.prototype.options.filterCallback = function( index, searchValue ) {
13519 return !this.className.match( rDividerListItem ) &&
13520 origDefaultFilterCallback.call( this, index, searchValue );
13521};
13522
13523$.widget( "mobile.filterable", $.mobile.filterable, {
13524 options: {
13525 filterPlaceholder: "Filter items...",
13526 filterTheme: null
13527 },
13528
13529 _create: function() {
13530 var idx, widgetName,
13531 elem = this.element,
13532 recognizedWidgets = [ "collapsibleset", "selectmenu", "controlgroup", "listview" ],
13533 createHandlers = {};
13534
13535 this._super();
13536
13537 $.extend( this, {
13538 _widget: null
13539 });
13540
13541 for ( idx = recognizedWidgets.length - 1 ; idx > -1 ; idx-- ) {
13542 widgetName = recognizedWidgets[ idx ];
13543 if ( $.mobile[ widgetName ] ) {
13544 if ( this._setWidget( elem.data( "mobile-" + widgetName ) ) ) {
13545 break;
13546 } else {
13547 createHandlers[ widgetName + "create" ] = "_handleCreate";
13548 }
13549 }
13550 }
13551
13552 if ( !this._widget ) {
13553 this._on( elem, createHandlers );
13554 }
13555 },
13556
13557 _handleCreate: function( evt ) {
13558 this._setWidget( this.element.data( "mobile-" + evt.type.substring( 0, evt.type.length - 6 ) ) );
13559 },
13560
13561 _setWidget: function( widget ) {
13562 if ( !this._widget && widget ) {
13563 this._widget = widget;
13564 this._widget._setOptions = replaceSetOptions( this, this._widget._setOptions );
13565 }
13566
13567 if ( !!this._widget ) {
13568 this._syncTextInputOptions( this._widget.options );
13569 if ( this._widget.widgetName === "listview" ) {
13570 this._widget.options.hidedividers = true;
13571 this._widget.element.listview( "refresh" );
13572 }
13573 }
13574
13575 return !!this._widget;
13576 },
13577
13578 _isSearchInternal: function() {
13579 return ( this._search && this._search.jqmData( "ui-filterable-" + this.uuid + "-internal" ) );
13580 },
13581
13582 _setInput: function( selector ) {
13583 var opts = this.options,
13584 updatePlaceholder = true,
13585 textinputOpts = {};
13586
13587 if ( !selector ) {
13588 if ( this._isSearchInternal() ) {
13589
13590 // Ignore the call to set a new input if the selector goes to falsy and
13591 // the current textinput is already of the internally generated variety.
13592 return;
13593 } else {
13594
13595 // Generating a new textinput widget. No need to set the placeholder
13596 // further down the function.
13597 updatePlaceholder = false;
13598 selector = $( "<input " +
13599 "data-" + $.mobile.ns + "type='search' " +
13600 "placeholder='" + opts.filterPlaceholder + "'></input>" )
13601 .jqmData( "ui-filterable-" + this.uuid + "-internal", true );
13602 $( "<form class='ui-filterable'></form>" )
13603 .append( selector )
13604 .submit( function( evt ) {
13605 evt.preventDefault();
13606 selector.blur();
13607 })
13608 .insertBefore( this.element );
13609 if ( $.mobile.textinput ) {
13610 if ( this.options.filterTheme != null ) {
13611 textinputOpts[ "theme" ] = opts.filterTheme;
13612 }
13613
13614 selector.textinput( textinputOpts );
13615 }
13616 }
13617 }
13618
13619 this._super( selector );
13620
13621 if ( this._isSearchInternal() && updatePlaceholder ) {
13622 this._search.attr( "placeholder", this.options.filterPlaceholder );
13623 }
13624 },
13625
13626 _setOptions: function( options ) {
13627 var ret = this._super( options );
13628
13629 // Need to set the filterPlaceholder after having established the search input
13630 if ( options.filterPlaceholder !== undefined ) {
13631 if ( this._isSearchInternal() ) {
13632 this._search.attr( "placeholder", options.filterPlaceholder );
13633 }
13634 }
13635
13636 if ( options.filterTheme !== undefined && this._search && $.mobile.textinput ) {
13637 this._search.textinput( "option", "theme", options.filterTheme );
13638 }
13639
13640 return ret;
13641 },
13642
13643 _destroy: function() {
13644 if ( this._isSearchInternal() ) {
13645 this._search.remove();
13646 }
13647 this._super();
13648 },
13649
13650 _syncTextInputOptions: function( options ) {
13651 var idx,
13652 textinputOptions = {};
13653
13654 // We only sync options if the filterable's textinput is of the internally
13655 // generated variety, rather than one specified by the user.
13656 if ( this._isSearchInternal() && $.mobile.textinput ) {
13657
13658 // Apply only the options understood by textinput
13659 for ( idx in $.mobile.textinput.prototype.options ) {
13660 if ( options[ idx ] !== undefined ) {
13661 if ( idx === "theme" && this.options.filterTheme != null ) {
13662 textinputOptions[ idx ] = this.options.filterTheme;
13663 } else {
13664 textinputOptions[ idx ] = options[ idx ];
13665 }
13666 }
13667 }
13668 this._search.textinput( "option", textinputOptions );
13669 }
13670 }
13671});
13672
13673})( jQuery );
13674
13675/*!
13676 * jQuery UI Tabs fadf2b312a05040436451c64bbfaf4814bc62c56
13677 * http://jqueryui.com
13678 *
13679 * Copyright 2013 jQuery Foundation and other contributors
13680 * Released under the MIT license.
13681 * http://jquery.org/license
13682 *
13683 * http://api.jqueryui.com/tabs/
13684 *
13685 * Depends:
13686 * jquery.ui.core.js
13687 * jquery.ui.widget.js
13688 */
13689(function( $, undefined ) {
13690
13691var tabId = 0,
13692 rhash = /#.*$/;
13693
13694function getNextTabId() {
13695 return ++tabId;
13696}
13697
13698function isLocal( anchor ) {
13699 return anchor.hash.length > 1 &&
13700 decodeURIComponent( anchor.href.replace( rhash, "" ) ) ===
13701 decodeURIComponent( location.href.replace( rhash, "" ) );
13702}
13703
13704$.widget( "ui.tabs", {
13705 version: "fadf2b312a05040436451c64bbfaf4814bc62c56",
13706 delay: 300,
13707 options: {
13708 active: null,
13709 collapsible: false,
13710 event: "click",
13711 heightStyle: "content",
13712 hide: null,
13713 show: null,
13714
13715 // callbacks
13716 activate: null,
13717 beforeActivate: null,
13718 beforeLoad: null,
13719 load: null
13720 },
13721
13722 _create: function() {
13723 var that = this,
13724 options = this.options;
13725
13726 this.running = false;
13727
13728 this.element
13729 .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
13730 .toggleClass( "ui-tabs-collapsible", options.collapsible )
13731 // Prevent users from focusing disabled tabs via click
13732 .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
13733 if ( $( this ).is( ".ui-state-disabled" ) ) {
13734 event.preventDefault();
13735 }
13736 })
13737 // support: IE <9
13738 // Preventing the default action in mousedown doesn't prevent IE
13739 // from focusing the element, so if the anchor gets focused, blur.
13740 // We don't have to worry about focusing the previously focused
13741 // element since clicking on a non-focusable element should focus
13742 // the body anyway.
13743 .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
13744 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
13745 this.blur();
13746 }
13747 });
13748
13749 this._processTabs();
13750 options.active = this._initialActive();
13751
13752 // Take disabling tabs via class attribute from HTML
13753 // into account and update option properly.
13754 if ( $.isArray( options.disabled ) ) {
13755 options.disabled = $.unique( options.disabled.concat(
13756 $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
13757 return that.tabs.index( li );
13758 })
13759 ) ).sort();
13760 }
13761
13762 // check for length avoids error when initializing empty list
13763 if ( this.options.active !== false && this.anchors.length ) {
13764 this.active = this._findActive( options.active );
13765 } else {
13766 this.active = $();
13767 }
13768
13769 this._refresh();
13770
13771 if ( this.active.length ) {
13772 this.load( options.active );
13773 }
13774 },
13775
13776 _initialActive: function() {
13777 var active = this.options.active,
13778 collapsible = this.options.collapsible,
13779 locationHash = location.hash.substring( 1 );
13780
13781 if ( active === null ) {
13782 // check the fragment identifier in the URL
13783 if ( locationHash ) {
13784 this.tabs.each(function( i, tab ) {
13785 if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
13786 active = i;
13787 return false;
13788 }
13789 });
13790 }
13791
13792 // check for a tab marked active via a class
13793 if ( active === null ) {
13794 active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
13795 }
13796
13797 // no active tab, set to false
13798 if ( active === null || active === -1 ) {
13799 active = this.tabs.length ? 0 : false;
13800 }
13801 }
13802
13803 // handle numbers: negative, out of range
13804 if ( active !== false ) {
13805 active = this.tabs.index( this.tabs.eq( active ) );
13806 if ( active === -1 ) {
13807 active = collapsible ? false : 0;
13808 }
13809 }
13810
13811 // don't allow collapsible: false and active: false
13812 if ( !collapsible && active === false && this.anchors.length ) {
13813 active = 0;
13814 }
13815
13816 return active;
13817 },
13818
13819 _getCreateEventData: function() {
13820 return {
13821 tab: this.active,
13822 panel: !this.active.length ? $() : this._getPanelForTab( this.active )
13823 };
13824 },
13825
13826 _tabKeydown: function( event ) {
13827 var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
13828 selectedIndex = this.tabs.index( focusedTab ),
13829 goingForward = true;
13830
13831 if ( this._handlePageNav( event ) ) {
13832 return;
13833 }
13834
13835 switch ( event.keyCode ) {
13836 case $.ui.keyCode.RIGHT:
13837 case $.ui.keyCode.DOWN:
13838 selectedIndex++;
13839 break;
13840 case $.ui.keyCode.UP:
13841 case $.ui.keyCode.LEFT:
13842 goingForward = false;
13843 selectedIndex--;
13844 break;
13845 case $.ui.keyCode.END:
13846 selectedIndex = this.anchors.length - 1;
13847 break;
13848 case $.ui.keyCode.HOME:
13849 selectedIndex = 0;
13850 break;
13851 case $.ui.keyCode.SPACE:
13852 // Activate only, no collapsing
13853 event.preventDefault();
13854 clearTimeout( this.activating );
13855 this._activate( selectedIndex );
13856 return;
13857 case $.ui.keyCode.ENTER:
13858 // Toggle (cancel delayed activation, allow collapsing)
13859 event.preventDefault();
13860 clearTimeout( this.activating );
13861 // Determine if we should collapse or activate
13862 this._activate( selectedIndex === this.options.active ? false : selectedIndex );
13863 return;
13864 default:
13865 return;
13866 }
13867
13868 // Focus the appropriate tab, based on which key was pressed
13869 event.preventDefault();
13870 clearTimeout( this.activating );
13871 selectedIndex = this._focusNextTab( selectedIndex, goingForward );
13872
13873 // Navigating with control key will prevent automatic activation
13874 if ( !event.ctrlKey ) {
13875 // Update aria-selected immediately so that AT think the tab is already selected.
13876 // Otherwise AT may confuse the user by stating that they need to activate the tab,
13877 // but the tab will already be activated by the time the announcement finishes.
13878 focusedTab.attr( "aria-selected", "false" );
13879 this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
13880
13881 this.activating = this._delay(function() {
13882 this.option( "active", selectedIndex );
13883 }, this.delay );
13884 }
13885 },
13886
13887 _panelKeydown: function( event ) {
13888 if ( this._handlePageNav( event ) ) {
13889 return;
13890 }
13891
13892 // Ctrl+up moves focus to the current tab
13893 if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
13894 event.preventDefault();
13895 this.active.focus();
13896 }
13897 },
13898
13899 // Alt+page up/down moves focus to the previous/next tab (and activates)
13900 _handlePageNav: function( event ) {
13901 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
13902 this._activate( this._focusNextTab( this.options.active - 1, false ) );
13903 return true;
13904 }
13905 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
13906 this._activate( this._focusNextTab( this.options.active + 1, true ) );
13907 return true;
13908 }
13909 },
13910
13911 _findNextTab: function( index, goingForward ) {
13912 var lastTabIndex = this.tabs.length - 1;
13913
13914 function constrain() {
13915 if ( index > lastTabIndex ) {
13916 index = 0;
13917 }
13918 if ( index < 0 ) {
13919 index = lastTabIndex;
13920 }
13921 return index;
13922 }
13923
13924 while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
13925 index = goingForward ? index + 1 : index - 1;
13926 }
13927
13928 return index;
13929 },
13930
13931 _focusNextTab: function( index, goingForward ) {
13932 index = this._findNextTab( index, goingForward );
13933 this.tabs.eq( index ).focus();
13934 return index;
13935 },
13936
13937 _setOption: function( key, value ) {
13938 if ( key === "active" ) {
13939 // _activate() will handle invalid values and update this.options
13940 this._activate( value );
13941 return;
13942 }
13943
13944 if ( key === "disabled" ) {
13945 // don't use the widget factory's disabled handling
13946 this._setupDisabled( value );
13947 return;
13948 }
13949
13950 this._super( key, value);
13951
13952 if ( key === "collapsible" ) {
13953 this.element.toggleClass( "ui-tabs-collapsible", value );
13954 // Setting collapsible: false while collapsed; open first panel
13955 if ( !value && this.options.active === false ) {
13956 this._activate( 0 );
13957 }
13958 }
13959
13960 if ( key === "event" ) {
13961 this._setupEvents( value );
13962 }
13963
13964 if ( key === "heightStyle" ) {
13965 this._setupHeightStyle( value );
13966 }
13967 },
13968
13969 _tabId: function( tab ) {
13970 return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
13971 },
13972
13973 _sanitizeSelector: function( hash ) {
13974 return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
13975 },
13976
13977 refresh: function() {
13978 var options = this.options,
13979 lis = this.tablist.children( ":has(a[href])" );
13980
13981 // get disabled tabs from class attribute from HTML
13982 // this will get converted to a boolean if needed in _refresh()
13983 options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
13984 return lis.index( tab );
13985 });
13986
13987 this._processTabs();
13988
13989 // was collapsed or no tabs
13990 if ( options.active === false || !this.anchors.length ) {
13991 options.active = false;
13992 this.active = $();
13993 // was active, but active tab is gone
13994 } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
13995 // all remaining tabs are disabled
13996 if ( this.tabs.length === options.disabled.length ) {
13997 options.active = false;
13998 this.active = $();
13999 // activate previous tab
14000 } else {
14001 this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
14002 }
14003 // was active, active tab still exists
14004 } else {
14005 // make sure active index is correct
14006 options.active = this.tabs.index( this.active );
14007 }
14008
14009 this._refresh();
14010 },
14011
14012 _refresh: function() {
14013 this._setupDisabled( this.options.disabled );
14014 this._setupEvents( this.options.event );
14015 this._setupHeightStyle( this.options.heightStyle );
14016
14017 this.tabs.not( this.active ).attr({
14018 "aria-selected": "false",
14019 tabIndex: -1
14020 });
14021 this.panels.not( this._getPanelForTab( this.active ) )
14022 .hide()
14023 .attr({
14024 "aria-expanded": "false",
14025 "aria-hidden": "true"
14026 });
14027
14028 // Make sure one tab is in the tab order
14029 if ( !this.active.length ) {
14030 this.tabs.eq( 0 ).attr( "tabIndex", 0 );
14031 } else {
14032 this.active
14033 .addClass( "ui-tabs-active ui-state-active" )
14034 .attr({
14035 "aria-selected": "true",
14036 tabIndex: 0
14037 });
14038 this._getPanelForTab( this.active )
14039 .show()
14040 .attr({
14041 "aria-expanded": "true",
14042 "aria-hidden": "false"
14043 });
14044 }
14045 },
14046
14047 _processTabs: function() {
14048 var that = this;
14049
14050 this.tablist = this._getList()
14051 .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
14052 .attr( "role", "tablist" );
14053
14054 this.tabs = this.tablist.find( "> li:has(a[href])" )
14055 .addClass( "ui-state-default ui-corner-top" )
14056 .attr({
14057 role: "tab",
14058 tabIndex: -1
14059 });
14060
14061 this.anchors = this.tabs.map(function() {
14062 return $( "a", this )[ 0 ];
14063 })
14064 .addClass( "ui-tabs-anchor" )
14065 .attr({
14066 role: "presentation",
14067 tabIndex: -1
14068 });
14069
14070 this.panels = $();
14071
14072 this.anchors.each(function( i, anchor ) {
14073 var selector, panel, panelId,
14074 anchorId = $( anchor ).uniqueId().attr( "id" ),
14075 tab = $( anchor ).closest( "li" ),
14076 originalAriaControls = tab.attr( "aria-controls" );
14077
14078 // inline tab
14079 if ( isLocal( anchor ) ) {
14080 selector = anchor.hash;
14081 panel = that.element.find( that._sanitizeSelector( selector ) );
14082 // remote tab
14083 } else {
14084 panelId = that._tabId( tab );
14085 selector = "#" + panelId;
14086 panel = that.element.find( selector );
14087 if ( !panel.length ) {
14088 panel = that._createPanel( panelId );
14089 panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
14090 }
14091 panel.attr( "aria-live", "polite" );
14092 }
14093
14094 if ( panel.length) {
14095 that.panels = that.panels.add( panel );
14096 }
14097 if ( originalAriaControls ) {
14098 tab.data( "ui-tabs-aria-controls", originalAriaControls );
14099 }
14100 tab.attr({
14101 "aria-controls": selector.substring( 1 ),
14102 "aria-labelledby": anchorId
14103 });
14104 panel.attr( "aria-labelledby", anchorId );
14105 });
14106
14107 this.panels
14108 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
14109 .attr( "role", "tabpanel" );
14110 },
14111
14112 // allow overriding how to find the list for rare usage scenarios (#7715)
14113 _getList: function() {
14114 return this.element.find( "ol,ul" ).eq( 0 );
14115 },
14116
14117 _createPanel: function( id ) {
14118 return $( "<div>" )
14119 .attr( "id", id )
14120 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
14121 .data( "ui-tabs-destroy", true );
14122 },
14123
14124 _setupDisabled: function( disabled ) {
14125 if ( $.isArray( disabled ) ) {
14126 if ( !disabled.length ) {
14127 disabled = false;
14128 } else if ( disabled.length === this.anchors.length ) {
14129 disabled = true;
14130 }
14131 }
14132
14133 // disable tabs
14134 for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
14135 if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
14136 $( li )
14137 .addClass( "ui-state-disabled" )
14138 .attr( "aria-disabled", "true" );
14139 } else {
14140 $( li )
14141 .removeClass( "ui-state-disabled" )
14142 .removeAttr( "aria-disabled" );
14143 }
14144 }
14145
14146 this.options.disabled = disabled;
14147 },
14148
14149 _setupEvents: function( event ) {
14150 var events = {
14151 click: function( event ) {
14152 event.preventDefault();
14153 }
14154 };
14155 if ( event ) {
14156 $.each( event.split(" "), function( index, eventName ) {
14157 events[ eventName ] = "_eventHandler";
14158 });
14159 }
14160
14161 this._off( this.anchors.add( this.tabs ).add( this.panels ) );
14162 this._on( this.anchors, events );
14163 this._on( this.tabs, { keydown: "_tabKeydown" } );
14164 this._on( this.panels, { keydown: "_panelKeydown" } );
14165
14166 this._focusable( this.tabs );
14167 this._hoverable( this.tabs );
14168 },
14169
14170 _setupHeightStyle: function( heightStyle ) {
14171 var maxHeight,
14172 parent = this.element.parent();
14173
14174 if ( heightStyle === "fill" ) {
14175 maxHeight = parent.height();
14176 maxHeight -= this.element.outerHeight() - this.element.height();
14177
14178 this.element.siblings( ":visible" ).each(function() {
14179 var elem = $( this ),
14180 position = elem.css( "position" );
14181
14182 if ( position === "absolute" || position === "fixed" ) {
14183 return;
14184 }
14185 maxHeight -= elem.outerHeight( true );
14186 });
14187
14188 this.element.children().not( this.panels ).each(function() {
14189 maxHeight -= $( this ).outerHeight( true );
14190 });
14191
14192 this.panels.each(function() {
14193 $( this ).height( Math.max( 0, maxHeight -
14194 $( this ).innerHeight() + $( this ).height() ) );
14195 })
14196 .css( "overflow", "auto" );
14197 } else if ( heightStyle === "auto" ) {
14198 maxHeight = 0;
14199 this.panels.each(function() {
14200 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
14201 }).height( maxHeight );
14202 }
14203 },
14204
14205 _eventHandler: function( event ) {
14206 var options = this.options,
14207 active = this.active,
14208 anchor = $( event.currentTarget ),
14209 tab = anchor.closest( "li" ),
14210 clickedIsActive = tab[ 0 ] === active[ 0 ],
14211 collapsing = clickedIsActive && options.collapsible,
14212 toShow = collapsing ? $() : this._getPanelForTab( tab ),
14213 toHide = !active.length ? $() : this._getPanelForTab( active ),
14214 eventData = {
14215 oldTab: active,
14216 oldPanel: toHide,
14217 newTab: collapsing ? $() : tab,
14218 newPanel: toShow
14219 };
14220
14221 event.preventDefault();
14222
14223 if ( tab.hasClass( "ui-state-disabled" ) ||
14224 // tab is already loading
14225 tab.hasClass( "ui-tabs-loading" ) ||
14226 // can't switch durning an animation
14227 this.running ||
14228 // click on active header, but not collapsible
14229 ( clickedIsActive && !options.collapsible ) ||
14230 // allow canceling activation
14231 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
14232 return;
14233 }
14234
14235 options.active = collapsing ? false : this.tabs.index( tab );
14236
14237 this.active = clickedIsActive ? $() : tab;
14238 if ( this.xhr ) {
14239 this.xhr.abort();
14240 }
14241
14242 if ( !toHide.length && !toShow.length ) {
14243 $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
14244 }
14245
14246 if ( toShow.length ) {
14247 this.load( this.tabs.index( tab ), event );
14248 }
14249 this._toggle( event, eventData );
14250 },
14251
14252 // handles show/hide for selecting tabs
14253 _toggle: function( event, eventData ) {
14254 var that = this,
14255 toShow = eventData.newPanel,
14256 toHide = eventData.oldPanel;
14257
14258 this.running = true;
14259
14260 function complete() {
14261 that.running = false;
14262 that._trigger( "activate", event, eventData );
14263 }
14264
14265 function show() {
14266 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
14267
14268 if ( toShow.length && that.options.show ) {
14269 that._show( toShow, that.options.show, complete );
14270 } else {
14271 toShow.show();
14272 complete();
14273 }
14274 }
14275
14276 // start out by hiding, then showing, then completing
14277 if ( toHide.length && this.options.hide ) {
14278 this._hide( toHide, this.options.hide, function() {
14279 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
14280 show();
14281 });
14282 } else {
14283 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
14284 toHide.hide();
14285 show();
14286 }
14287
14288 toHide.attr({
14289 "aria-expanded": "false",
14290 "aria-hidden": "true"
14291 });
14292 eventData.oldTab.attr( "aria-selected", "false" );
14293 // If we're switching tabs, remove the old tab from the tab order.
14294 // If we're opening from collapsed state, remove the previous tab from the tab order.
14295 // If we're collapsing, then keep the collapsing tab in the tab order.
14296 if ( toShow.length && toHide.length ) {
14297 eventData.oldTab.attr( "tabIndex", -1 );
14298 } else if ( toShow.length ) {
14299 this.tabs.filter(function() {
14300 return $( this ).attr( "tabIndex" ) === 0;
14301 })
14302 .attr( "tabIndex", -1 );
14303 }
14304
14305 toShow.attr({
14306 "aria-expanded": "true",
14307 "aria-hidden": "false"
14308 });
14309 eventData.newTab.attr({
14310 "aria-selected": "true",
14311 tabIndex: 0
14312 });
14313 },
14314
14315 _activate: function( index ) {
14316 var anchor,
14317 active = this._findActive( index );
14318
14319 // trying to activate the already active panel
14320 if ( active[ 0 ] === this.active[ 0 ] ) {
14321 return;
14322 }
14323
14324 // trying to collapse, simulate a click on the current active header
14325 if ( !active.length ) {
14326 active = this.active;
14327 }
14328
14329 anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
14330 this._eventHandler({
14331 target: anchor,
14332 currentTarget: anchor,
14333 preventDefault: $.noop
14334 });
14335 },
14336
14337 _findActive: function( index ) {
14338 return index === false ? $() : this.tabs.eq( index );
14339 },
14340
14341 _getIndex: function( index ) {
14342 // meta-function to give users option to provide a href string instead of a numerical index.
14343 if ( typeof index === "string" ) {
14344 index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
14345 }
14346
14347 return index;
14348 },
14349
14350 _destroy: function() {
14351 if ( this.xhr ) {
14352 this.xhr.abort();
14353 }
14354
14355 this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
14356
14357 this.tablist
14358 .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
14359 .removeAttr( "role" );
14360
14361 this.anchors
14362 .removeClass( "ui-tabs-anchor" )
14363 .removeAttr( "role" )
14364 .removeAttr( "tabIndex" )
14365 .removeUniqueId();
14366
14367 this.tabs.add( this.panels ).each(function() {
14368 if ( $.data( this, "ui-tabs-destroy" ) ) {
14369 $( this ).remove();
14370 } else {
14371 $( this )
14372 .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
14373 "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
14374 .removeAttr( "tabIndex" )
14375 .removeAttr( "aria-live" )
14376 .removeAttr( "aria-busy" )
14377 .removeAttr( "aria-selected" )
14378 .removeAttr( "aria-labelledby" )
14379 .removeAttr( "aria-hidden" )
14380 .removeAttr( "aria-expanded" )
14381 .removeAttr( "role" );
14382 }
14383 });
14384
14385 this.tabs.each(function() {
14386 var li = $( this ),
14387 prev = li.data( "ui-tabs-aria-controls" );
14388 if ( prev ) {
14389 li
14390 .attr( "aria-controls", prev )
14391 .removeData( "ui-tabs-aria-controls" );
14392 } else {
14393 li.removeAttr( "aria-controls" );
14394 }
14395 });
14396
14397 this.panels.show();
14398
14399 if ( this.options.heightStyle !== "content" ) {
14400 this.panels.css( "height", "" );
14401 }
14402 },
14403
14404 enable: function( index ) {
14405 var disabled = this.options.disabled;
14406 if ( disabled === false ) {
14407 return;
14408 }
14409
14410 if ( index === undefined ) {
14411 disabled = false;
14412 } else {
14413 index = this._getIndex( index );
14414 if ( $.isArray( disabled ) ) {
14415 disabled = $.map( disabled, function( num ) {
14416 return num !== index ? num : null;
14417 });
14418 } else {
14419 disabled = $.map( this.tabs, function( li, num ) {
14420 return num !== index ? num : null;
14421 });
14422 }
14423 }
14424 this._setupDisabled( disabled );
14425 },
14426
14427 disable: function( index ) {
14428 var disabled = this.options.disabled;
14429 if ( disabled === true ) {
14430 return;
14431 }
14432
14433 if ( index === undefined ) {
14434 disabled = true;
14435 } else {
14436 index = this._getIndex( index );
14437 if ( $.inArray( index, disabled ) !== -1 ) {
14438 return;
14439 }
14440 if ( $.isArray( disabled ) ) {
14441 disabled = $.merge( [ index ], disabled ).sort();
14442 } else {
14443 disabled = [ index ];
14444 }
14445 }
14446 this._setupDisabled( disabled );
14447 },
14448
14449 load: function( index, event ) {
14450 index = this._getIndex( index );
14451 var that = this,
14452 tab = this.tabs.eq( index ),
14453 anchor = tab.find( ".ui-tabs-anchor" ),
14454 panel = this._getPanelForTab( tab ),
14455 eventData = {
14456 tab: tab,
14457 panel: panel
14458 };
14459
14460 // not remote
14461 if ( isLocal( anchor[ 0 ] ) ) {
14462 return;
14463 }
14464
14465 this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
14466
14467 // support: jQuery <1.8
14468 // jQuery <1.8 returns false if the request is canceled in beforeSend,
14469 // but as of 1.8, $.ajax() always returns a jqXHR object.
14470 if ( this.xhr && this.xhr.statusText !== "canceled" ) {
14471 tab.addClass( "ui-tabs-loading" );
14472 panel.attr( "aria-busy", "true" );
14473
14474 this.xhr
14475 .success(function( response ) {
14476 // support: jQuery <1.8
14477 // http://bugs.jquery.com/ticket/11778
14478 setTimeout(function() {
14479 panel.html( response );
14480 that._trigger( "load", event, eventData );
14481 }, 1 );
14482 })
14483 .complete(function( jqXHR, status ) {
14484 // support: jQuery <1.8
14485 // http://bugs.jquery.com/ticket/11778
14486 setTimeout(function() {
14487 if ( status === "abort" ) {
14488 that.panels.stop( false, true );
14489 }
14490
14491 tab.removeClass( "ui-tabs-loading" );
14492 panel.removeAttr( "aria-busy" );
14493
14494 if ( jqXHR === that.xhr ) {
14495 delete that.xhr;
14496 }
14497 }, 1 );
14498 });
14499 }
14500 },
14501
14502 _ajaxSettings: function( anchor, event, eventData ) {
14503 var that = this;
14504 return {
14505 url: anchor.attr( "href" ),
14506 beforeSend: function( jqXHR, settings ) {
14507 return that._trigger( "beforeLoad", event,
14508 $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
14509 }
14510 };
14511 },
14512
14513 _getPanelForTab: function( tab ) {
14514 var id = $( tab ).attr( "aria-controls" );
14515 return this.element.find( this._sanitizeSelector( "#" + id ) );
14516 }
14517});
14518
14519})( jQuery );
14520
14521(function( $, undefined ) {
14522
14523})( jQuery );
14524
14525(function( $, window ) {
14526
14527 $.mobile.iosorientationfixEnabled = true;
14528
14529 // This fix addresses an iOS bug, so return early if the UA claims it's something else.
14530 var ua = navigator.userAgent,
14531 zoom,
14532 evt, x, y, z, aig;
14533 if ( !( /iPhone|iPad|iPod/.test( navigator.platform ) && /OS [1-5]_[0-9_]* like Mac OS X/i.test( ua ) && ua.indexOf( "AppleWebKit" ) > -1 ) ) {
14534 $.mobile.iosorientationfixEnabled = false;
14535 return;
14536 }
14537
14538 zoom = $.mobile.zoom;
14539
14540 function checkTilt( e ) {
14541 evt = e.originalEvent;
14542 aig = evt.accelerationIncludingGravity;
14543
14544 x = Math.abs( aig.x );
14545 y = Math.abs( aig.y );
14546 z = Math.abs( aig.z );
14547
14548 // If portrait orientation and in one of the danger zones
14549 if ( !window.orientation && ( x > 7 || ( ( z > 6 && y < 8 || z < 8 && y > 6 ) && x > 5 ) ) ) {
14550 if ( zoom.enabled ) {
14551 zoom.disable();
14552 }
14553 } else if ( !zoom.enabled ) {
14554 zoom.enable();
14555 }
14556 }
14557
14558 $.mobile.document.on( "mobileinit", function() {
14559 if ( $.mobile.iosorientationfixEnabled ) {
14560 $.mobile.window
14561 .bind( "orientationchange.iosorientationfix", zoom.enable )
14562 .bind( "devicemotion.iosorientationfix", checkTilt );
14563 }
14564 });
14565
14566}( jQuery, this ));
14567
14568(function( $, window, undefined ) {
14569 var $html = $( "html" ),
14570 $window = $.mobile.window;
14571
14572 //remove initial build class (only present on first pageshow)
14573 function hideRenderingClass() {
14574 $html.removeClass( "ui-mobile-rendering" );
14575 }
14576
14577 // trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
14578 $( window.document ).trigger( "mobileinit" );
14579
14580 // support conditions
14581 // if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
14582 // otherwise, proceed with the enhancements
14583 if ( !$.mobile.gradeA() ) {
14584 return;
14585 }
14586
14587 // override ajaxEnabled on platforms that have known conflicts with hash history updates
14588 // or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini)
14589 if ( $.mobile.ajaxBlacklist ) {
14590 $.mobile.ajaxEnabled = false;
14591 }
14592
14593 // Add mobile, initial load "rendering" classes to docEl
14594 $html.addClass( "ui-mobile ui-mobile-rendering" );
14595
14596 // This is a fallback. If anything goes wrong (JS errors, etc), or events don't fire,
14597 // this ensures the rendering class is removed after 5 seconds, so content is visible and accessible
14598 setTimeout( hideRenderingClass, 5000 );
14599
14600 $.extend( $.mobile, {
14601 // find and enhance the pages in the dom and transition to the first page.
14602 initializePage: function() {
14603 // find present pages
14604 var path = $.mobile.path,
14605 $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ),
14606 hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ),
14607 hashPage = document.getElementById( hash );
14608
14609 // if no pages are found, create one with body's inner html
14610 if ( !$pages.length ) {
14611 $pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 );
14612 }
14613
14614 // add dialogs, set data-url attrs
14615 $pages.each(function() {
14616 var $this = $( this );
14617
14618 // unless the data url is already set set it to the pathname
14619 if ( !$this[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) ) {
14620 $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search );
14621 }
14622 });
14623
14624 // define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
14625 $.mobile.firstPage = $pages.first();
14626
14627 // define page container
14628 $.mobile.pageContainer = $.mobile.firstPage
14629 .parent()
14630 .addClass( "ui-mobile-viewport" )
14631 .pagecontainer();
14632
14633 // initialize navigation events now, after mobileinit has occurred and the page container
14634 // has been created but before the rest of the library is alerted to that fact
14635 $.mobile.navreadyDeferred.resolve();
14636
14637 // alert listeners that the pagecontainer has been determined for binding
14638 // to events triggered on it
14639 $window.trigger( "pagecontainercreate" );
14640
14641 // cue page loading message
14642 $.mobile.loading( "show" );
14643
14644 //remove initial build class (only present on first pageshow)
14645 hideRenderingClass();
14646
14647 // if hashchange listening is disabled, there's no hash deeplink,
14648 // the hash is not valid (contains more than one # or does not start with #)
14649 // or there is no page with that hash, change to the first page in the DOM
14650 // Remember, however, that the hash can also be a path!
14651 if ( ! ( $.mobile.hashListeningEnabled &&
14652 $.mobile.path.isHashValid( location.hash ) &&
14653 ( $( hashPage ).is( ":jqmData(role='page')" ) ||
14654 $.mobile.path.isPath( hash ) ||
14655 hash === $.mobile.dialogHashKey ) ) ) {
14656
14657 // Store the initial destination
14658 if ( $.mobile.path.isHashValid( location.hash ) ) {
14659 $.mobile.navigate.history.initialDst = hash.replace( "#", "" );
14660 }
14661
14662 // make sure to set initial popstate state if it exists
14663 // so that navigation back to the initial page works properly
14664 if ( $.event.special.navigate.isPushStateEnabled() ) {
14665 $.mobile.navigate.navigator.squash( path.parseLocation().href );
14666 }
14667
14668 $.mobile.changePage( $.mobile.firstPage, {
14669 transition: "none",
14670 reverse: true,
14671 changeHash: false,
14672 fromHashChange: true
14673 });
14674 } else {
14675 // trigger hashchange or navigate to squash and record the correct
14676 // history entry for an initial hash path
14677 if ( !$.event.special.navigate.isPushStateEnabled() ) {
14678 $window.trigger( "hashchange", [true] );
14679 } else {
14680 // TODO figure out how to simplify this interaction with the initial history entry
14681 // at the bottom js/navigate/navigate.js
14682 $.mobile.navigate.history.stack = [];
14683 $.mobile.navigate( $.mobile.path.isPath( location.hash ) ? location.hash : location.href );
14684 }
14685 }
14686 }
14687 });
14688
14689 $(function() {
14690 //Run inlineSVG support test
14691 $.support.inlineSVG();
14692
14693 // check which scrollTop value should be used by scrolling to 1 immediately at domready
14694 // then check what the scroll top is. Android will report 0... others 1
14695 // note that this initial scroll won't hide the address bar. It's just for the check.
14696
14697 // hide iOS browser chrome on load if hideUrlBar is true this is to try and do it as soon as possible
14698 if ( $.mobile.hideUrlBar ) {
14699 window.scrollTo( 0, 1 );
14700 }
14701
14702 // if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
14703 // it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
14704 // so if it's 1, use 0 from now on
14705 $.mobile.defaultHomeScroll = ( !$.support.scrollTop || $.mobile.window.scrollTop() === 1 ) ? 0 : 1;
14706
14707 //dom-ready inits
14708 if ( $.mobile.autoInitializePage ) {
14709 $.mobile.initializePage();
14710 }
14711
14712 // window load event
14713 // hide iOS browser chrome on load if hideUrlBar is true this is as fall back incase we were too early before
14714 if ( $.mobile.hideUrlBar ) {
14715 $window.load( $.mobile.silentScroll );
14716 }
14717
14718 if ( !$.support.cssPointerEvents ) {
14719 // IE and Opera don't support CSS pointer-events: none that we use to disable link-based buttons
14720 // by adding the 'ui-disabled' class to them. Using a JavaScript workaround for those browser.
14721 // https://github.com/jquery/jquery-mobile/issues/3558
14722
14723 // DEPRECATED as of 1.4.0 - remove ui-disabled after 1.4.0 release
14724 // only ui-state-disabled should be present thereafter
14725 $.mobile.document.delegate( ".ui-state-disabled,.ui-disabled", "vclick",
14726 function( e ) {
14727 e.preventDefault();
14728 e.stopImmediatePropagation();
14729 }
14730 );
14731 }
14732 });
14733}( jQuery, this ));
14734
14735
14736}));