· 7 years ago · Sep 20, 2018, 01:26 PM
1/*!
2* jQuery Mobile 1.5.0-pre
3* Git HEAD hash: d04308f591d4d0e58443d6a645d0cc1f599888d2 <> Date: Thu Jun 18 2015 17:52:18 UTC
4* http://jquerymobile.com
5*
6* Copyright 2010, 2015 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
30(function( $, undefined ) {
31 var path, $base, dialogHashKey = "&ui-state=dialog";
32
33 $.mobile.path = path = {
34 uiStateKey: "&ui-state",
35
36 // This scary looking regular expression parses an absolute URL or its relative
37 // variants (protocol, site, document, query, and hash), into the various
38 // components (protocol, host, path, query, fragment, etc that make up the
39 // URL as well as some other commonly used sub-parts. When used with RegExp.exec()
40 // or String.match, it parses the URL into a results array that looks like this:
41 //
42 // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
43 // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
44 // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
45 // [3]: http://jblas:password@mycompany.com:8080
46 // [4]: http:
47 // [5]: //
48 // [6]: jblas:password@mycompany.com:8080
49 // [7]: jblas:password
50 // [8]: jblas
51 // [9]: password
52 // [10]: mycompany.com:8080
53 // [11]: mycompany.com
54 // [12]: 8080
55 // [13]: /mail/inbox
56 // [14]: /mail/
57 // [15]: inbox
58 // [16]: ?msg=1234&type=unread
59 // [17]: #msg-content
60 //
61 urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
62
63 // Abstraction to address xss (Issue #4787) by removing the authority in
64 // browsers that auto-decode it. All references to location.href should be
65 // replaced with a call to this method so that it can be dealt with properly here
66 getLocation: function( url ) {
67 var parsedUrl = this.parseUrl( url || location.href ),
68 uri = url ? parsedUrl : location,
69
70 // Make sure to parse the url or the location object for the hash because using
71 // location.hash is autodecoded in firefox, the rest of the url should be from
72 // the object (location unless we're testing) to avoid the inclusion of the
73 // authority
74 hash = parsedUrl.hash;
75
76 // mimic the browser with an empty string when the hash is empty
77 hash = hash === "#" ? "" : hash;
78
79 return uri.protocol +
80 parsedUrl.doubleSlash +
81 uri.host +
82
83 // The pathname must start with a slash if there's a protocol, because you
84 // can't have a protocol followed by a relative path. Also, it's impossible to
85 // calculate absolute URLs from relative ones if the absolute one doesn't have
86 // a leading "/".
87 ( ( uri.protocol !== "" && uri.pathname.substring( 0, 1 ) !== "/" ) ?
88 "/" : "" ) +
89 uri.pathname +
90 uri.search +
91 hash;
92 },
93
94 //return the original document url
95 getDocumentUrl: function( asParsedObject ) {
96 return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href;
97 },
98
99 parseLocation: function() {
100 return this.parseUrl( this.getLocation() );
101 },
102
103 //Parse a URL into a structure that allows easy access to
104 //all of the URL components by name.
105 parseUrl: function( url ) {
106 // If we're passed an object, we'll assume that it is
107 // a parsed url object and just return it back to the caller.
108 if ( $.type( url ) === "object" ) {
109 return url;
110 }
111
112 var matches = path.urlParseRE.exec( url || "" ) || [];
113
114 // Create an object that allows the caller to access the sub-matches
115 // by name. Note that IE returns an empty string instead of undefined,
116 // like all other browsers do, so we normalize everything so its consistent
117 // no matter what browser we're running on.
118 return {
119 href: matches[ 0 ] || "",
120 hrefNoHash: matches[ 1 ] || "",
121 hrefNoSearch: matches[ 2 ] || "",
122 domain: matches[ 3 ] || "",
123 protocol: matches[ 4 ] || "",
124 doubleSlash: matches[ 5 ] || "",
125 authority: matches[ 6 ] || "",
126 username: matches[ 8 ] || "",
127 password: matches[ 9 ] || "",
128 host: matches[ 10 ] || "",
129 hostname: matches[ 11 ] || "",
130 port: matches[ 12 ] || "",
131 pathname: matches[ 13 ] || "",
132 directory: matches[ 14 ] || "",
133 filename: matches[ 15 ] || "",
134 search: matches[ 16 ] || "",
135 hash: matches[ 17 ] || ""
136 };
137 },
138
139 //Turn relPath into an asbolute path. absPath is
140 //an optional absolute path which describes what
141 //relPath is relative to.
142 makePathAbsolute: function( relPath, absPath ) {
143 var absStack,
144 relStack,
145 i, d;
146
147 if ( relPath && relPath.charAt( 0 ) === "/" ) {
148 return relPath;
149 }
150
151 relPath = relPath || "";
152 absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
153
154 absStack = absPath ? absPath.split( "/" ) : [];
155 relStack = relPath.split( "/" );
156
157 for ( i = 0; i < relStack.length; i++ ) {
158 d = relStack[ i ];
159 switch ( d ) {
160 case ".":
161 break;
162 case "..":
163 if ( absStack.length ) {
164 absStack.pop();
165 }
166 break;
167 default:
168 absStack.push( d );
169 break;
170 }
171 }
172 return "/" + absStack.join( "/" );
173 },
174
175 //Returns true if both urls have the same domain.
176 isSameDomain: function( absUrl1, absUrl2 ) {
177 return path.parseUrl( absUrl1 ).domain.toLowerCase() ===
178 path.parseUrl( absUrl2 ).domain.toLowerCase();
179 },
180
181 //Returns true for any relative variant.
182 isRelativeUrl: function( url ) {
183 // All relative Url variants have one thing in common, no protocol.
184 return path.parseUrl( url ).protocol === "";
185 },
186
187 //Returns true for an absolute url.
188 isAbsoluteUrl: function( url ) {
189 return path.parseUrl( url ).protocol !== "";
190 },
191
192 //Turn the specified realtive URL into an absolute one. This function
193 //can handle all relative variants (protocol, site, document, query, fragment).
194 makeUrlAbsolute: function( relUrl, absUrl ) {
195 if ( !path.isRelativeUrl( relUrl ) ) {
196 return relUrl;
197 }
198
199 if ( absUrl === undefined ) {
200 absUrl = this.documentBase;
201 }
202
203 var relObj = path.parseUrl( relUrl ),
204 absObj = path.parseUrl( absUrl ),
205 protocol = relObj.protocol || absObj.protocol,
206 doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
207 authority = relObj.authority || absObj.authority,
208 hasPath = relObj.pathname !== "",
209 pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
210 search = relObj.search || ( !hasPath && absObj.search ) || "",
211 hash = relObj.hash;
212
213 return protocol + doubleSlash + authority + pathname + search + hash;
214 },
215
216 //Add search (aka query) params to the specified url.
217 addSearchParams: function( url, params ) {
218 var u = path.parseUrl( url ),
219 p = ( typeof params === "object" ) ? $.param( params ) : params,
220 s = u.search || "?";
221 return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
222 },
223
224 convertUrlToDataUrl: function( absUrl ) {
225 var result = absUrl,
226 u = path.parseUrl( absUrl );
227
228 if ( path.isEmbeddedPage( u ) ) {
229 // For embedded pages, remove the dialog hash key as in getFilePath(),
230 // and remove otherwise the Data Url won't match the id of the embedded Page.
231 result = u.hash
232 .split( dialogHashKey )[0]
233 .replace( /^#/, "" )
234 .replace( /\?.*$/, "" );
235 } else if ( path.isSameDomain( u, this.documentBase ) ) {
236 result = u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0];
237 }
238
239 return window.decodeURIComponent( result );
240 },
241
242 //get path from current hash, or from a file path
243 get: function( newPath ) {
244 if ( newPath === undefined ) {
245 newPath = path.parseLocation().hash;
246 }
247 return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, "" );
248 },
249
250 //set location hash to path
251 set: function( path ) {
252 location.hash = path;
253 },
254
255 //test if a given url (string) is a path
256 //NOTE might be exceptionally naive
257 isPath: function( url ) {
258 return ( /\// ).test( url );
259 },
260
261 //return a url path with the window's location protocol/hostname/pathname removed
262 clean: function( url ) {
263 return url.replace( this.documentBase.domain, "" );
264 },
265
266 //just return the url without an initial #
267 stripHash: function( url ) {
268 return url.replace( /^#/, "" );
269 },
270
271 stripQueryParams: function( url ) {
272 return url.replace( /\?.*$/, "" );
273 },
274
275 //remove the preceding hash, any query params, and dialog notations
276 cleanHash: function( hash ) {
277 return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
278 },
279
280 isHashValid: function( hash ) {
281 return ( /^#[^#]+$/ ).test( hash );
282 },
283
284 //check whether a url is referencing the same domain, or an external domain or different protocol
285 //could be mailto, etc
286 isExternal: function( url ) {
287 var u = path.parseUrl( url );
288
289 return !!( u.protocol &&
290 ( u.domain.toLowerCase() !== this.documentUrl.domain.toLowerCase() ) );
291 },
292
293 hasProtocol: function( url ) {
294 return ( /^(:?\w+:)/ ).test( url );
295 },
296
297 isEmbeddedPage: function( url ) {
298 var u = path.parseUrl( url );
299
300 //if the path is absolute, then we need to compare the url against
301 //both the this.documentUrl and the documentBase. The main reason for this
302 //is that links embedded within external documents will refer to the
303 //application document, whereas links embedded within the application
304 //document will be resolved against the document base.
305 if ( u.protocol !== "" ) {
306 return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) );
307 }
308 return ( /^#/ ).test( u.href );
309 },
310
311 squash: function( url, resolutionUrl ) {
312 var href, cleanedUrl, search, stateIndex, docUrl,
313 isPath = this.isPath( url ),
314 uri = this.parseUrl( url ),
315 preservedHash = uri.hash,
316 uiState = "";
317
318 // produce a url against which we can resolve the provided path
319 if ( !resolutionUrl ) {
320 if ( isPath ) {
321 resolutionUrl = path.getLocation();
322 } else {
323 docUrl = path.getDocumentUrl( true );
324 if ( path.isPath( docUrl.hash ) ) {
325 resolutionUrl = path.squash( docUrl.href );
326 } else {
327 resolutionUrl = docUrl.href;
328 }
329 }
330 }
331
332 // If the url is anything but a simple string, remove any preceding hash
333 // eg #foo/bar -> foo/bar
334 // #foo -> #foo
335 cleanedUrl = isPath ? path.stripHash( url ) : url;
336
337 // If the url is a full url with a hash check if the parsed hash is a path
338 // if it is, strip the #, and use it otherwise continue without change
339 cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl;
340
341 // Split the UI State keys off the href
342 stateIndex = cleanedUrl.indexOf( this.uiStateKey );
343
344 // store the ui state keys for use
345 if ( stateIndex > -1 ) {
346 uiState = cleanedUrl.slice( stateIndex );
347 cleanedUrl = cleanedUrl.slice( 0, stateIndex );
348 }
349
350 // make the cleanedUrl absolute relative to the resolution url
351 href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl );
352
353 // grab the search from the resolved url since parsing from
354 // the passed url may not yield the correct result
355 search = this.parseUrl( href ).search;
356
357 // TODO all this crap is terrible, clean it up
358 if ( isPath ) {
359 // reject the hash if it's a path or it's just a dialog key
360 if ( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) {
361 preservedHash = "";
362 }
363
364 // Append the UI State keys where it exists and it's been removed
365 // from the url
366 if ( uiState && preservedHash.indexOf( this.uiStateKey ) === -1) {
367 preservedHash += uiState;
368 }
369
370 // make sure that pound is on the front of the hash
371 if ( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ) {
372 preservedHash = "#" + preservedHash;
373 }
374
375 // reconstruct each of the pieces with the new search string and hash
376 href = path.parseUrl( href );
377 href = href.protocol + href.doubleSlash + href.host + href.pathname + search +
378 preservedHash;
379 } else {
380 href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState;
381 }
382
383 return href;
384 },
385
386 isPreservableHash: function( hash ) {
387 return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0;
388 },
389
390 // Escape weird characters in the hash if it is to be used as a selector
391 hashToSelector: function( hash ) {
392 var hasHash = ( hash.substring( 0, 1 ) === "#" );
393 if ( hasHash ) {
394 hash = hash.substring( 1 );
395 }
396 return ( hasHash ? "#" : "" ) + hash.replace( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$1" );
397 },
398
399 // return the substring of a filepath before the dialogHashKey, for making a server
400 // request
401 getFilePath: function( path ) {
402 return path && path.split( dialogHashKey )[0];
403 },
404
405 // check if the specified url refers to the first page in the main
406 // application document.
407 isFirstPageUrl: function( url ) {
408 // We only deal with absolute paths.
409 var u = path.parseUrl( path.makeUrlAbsolute( url, this.documentBase ) ),
410
411 // Does the url have the same path as the document?
412 samePath = u.hrefNoHash === this.documentUrl.hrefNoHash ||
413 ( this.documentBaseDiffers &&
414 u.hrefNoHash === this.documentBase.hrefNoHash ),
415
416 // Get the first page element.
417 fp = $.mobile.firstPage,
418
419 // Get the id of the first page element if it has one.
420 fpId = fp && fp[0] ? fp[0].id : undefined;
421
422 // The url refers to the first page if the path matches the document and
423 // it either has no hash value, or the hash is exactly equal to the id
424 // of the first page element.
425 return samePath &&
426 ( !u.hash ||
427 u.hash === "#" ||
428 ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
429 },
430
431 // Some embedded browsers, like the web view in Phone Gap, allow
432 // cross-domain XHR requests if the document doing the request was loaded
433 // via the file:// protocol. This is usually to allow the application to
434 // "phone home" and fetch app specific data. We normally let the browser
435 // handle external/cross-domain urls, but if the allowCrossDomainPages
436 // option is true, we will allow cross-domain http/https requests to go
437 // through our page loading logic.
438 isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
439 return $.mobile.allowCrossDomainPages &&
440 (docUrl.protocol === "file:" || docUrl.protocol === "content:") &&
441 reqUrl.search( /^https?:/ ) !== -1;
442 }
443 };
444
445 path.documentUrl = path.parseLocation();
446
447 $base = $( "head" ).find( "base" );
448
449 path.documentBase = $base.length ?
450 path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) :
451 path.documentUrl;
452
453 path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash);
454
455 //return the original document base url
456 path.getDocumentBase = function( asParsedObject ) {
457 return asParsedObject ? $.extend( {}, path.documentBase ) : path.documentBase.href;
458 };
459
460 // DEPRECATED as of 1.4.0 - remove in 1.5.0
461 $.extend( $.mobile, {
462
463 //return the original document url
464 getDocumentUrl: path.getDocumentUrl,
465
466 //return the original document base url
467 getDocumentBase: path.getDocumentBase
468 });
469})( jQuery );
470
471
472
473(function( $, undefined ) {
474
475 var base,
476
477 // Existing base tag?
478 baseElement = $( "head" ).children( "base" ),
479
480 // DEPRECATED as of 1.5.0 and will be removed in 1.6.0. As of 1.6.0 only
481 // base.dynamicBaseEnabled will be checked
482 getDynamicEnabled = function() {
483
484 // If a value has been set at the old, deprecated location, we return that value.
485 // Otherwise we return the value from the new location. We check explicitly for
486 // undefined because true and false are both valid values for dynamicBaseEnabled.
487 if ( $.mobile.dynamicBaseEnabled !== undefined ) {
488 return $.mobile.dynamicBaseEnabled;
489 }
490 return base.dynamicBaseEnabled;
491 };
492
493 // base element management, defined depending on dynamic base tag support
494 // TODO move to external widget
495 base = {
496
497 // Disable the alteration of the dynamic base tag or links
498 dynamicBaseEnabled: true,
499
500 // Make sure base element is defined, for use in routing asset urls that are referenced
501 // in Ajax-requested markup
502 element: function() {
503 if ( !( baseElement && baseElement.length ) ) {
504 baseElement = $( "<base>", { href: $.mobile.path.documentBase.hrefNoSearch } )
505 .prependTo( $( "head" ) );
506 }
507
508 return baseElement;
509 },
510
511 // set the generated BASE element's href to a new page's base path
512 set: function( href ) {
513
514 // We should do nothing if the user wants to manage their url base manually.
515 // Note: Our method of ascertaining whether the user wants to manager their url base
516 // manually is DEPRECATED as of 1.5.0 and will be removed in 1.6.0. As of 1.6.0 the
517 // flag base.dynamicBaseEnabled will be checked, so the function getDynamicEnabled()
518 // will be removed.
519 if ( !getDynamicEnabled() ) {
520 return;
521 }
522
523 // we should use the base tag if we can manipulate it dynamically
524 base.element().attr( "href",
525 $.mobile.path.makeUrlAbsolute( href, $.mobile.path.documentBase ) );
526 },
527
528 // set the generated BASE element's href to a new page's base path
529 reset: function(/* href */) {
530
531 // DEPRECATED as of 1.5.0 and will be removed in 1.6.0. As of 1.6.0 only
532 // base.dynamicBaseEnabled will be checked
533 if ( !getDynamicEnabled() ) {
534 return;
535 }
536
537 base.element().attr( "href", $.mobile.path.documentBase.hrefNoSearch );
538 }
539 };
540
541 $.mobile.base = base;
542
543})( jQuery );
544
545
546/*!
547 * jQuery UI Core c0ab71056b936627e8a7821f03c044aec6280a40
548 * http://jqueryui.com
549 *
550 * Copyright 2013 jQuery Foundation and other contributors
551 * Released under the MIT license.
552 * http://jquery.org/license
553 *
554 * http://api.jqueryui.com/category/ui-core/
555 */
556(function( $, undefined ) {
557
558var uuid = 0,
559 runiqueId = /^ui-id-\d+$/;
560
561// $.ui might exist from components with no dependencies, e.g., $.ui.position
562$.ui = $.ui || {};
563
564$.extend( $.ui, {
565 version: "c0ab71056b936627e8a7821f03c044aec6280a40",
566
567 keyCode: {
568 BACKSPACE: 8,
569 COMMA: 188,
570 DELETE: 46,
571 DOWN: 40,
572 END: 35,
573 ENTER: 13,
574 ESCAPE: 27,
575 HOME: 36,
576 LEFT: 37,
577 PAGE_DOWN: 34,
578 PAGE_UP: 33,
579 PERIOD: 190,
580 RIGHT: 39,
581 SPACE: 32,
582 TAB: 9,
583 UP: 38
584 }
585});
586
587// plugins
588$.fn.extend({
589 focus: (function( orig ) {
590 return function( delay, fn ) {
591 return typeof delay === "number" ?
592 this.each(function() {
593 var elem = this;
594 setTimeout(function() {
595 $( elem ).focus();
596 if ( fn ) {
597 fn.call( elem );
598 }
599 }, delay );
600 }) :
601 orig.apply( this, arguments );
602 };
603 })( $.fn.focus ),
604
605 scrollParent: function() {
606 var scrollParent;
607 if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
608 scrollParent = this.parents().filter(function() {
609 return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
610 }).eq(0);
611 } else {
612 scrollParent = this.parents().filter(function() {
613 return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
614 }).eq(0);
615 }
616
617 return ( /fixed/ ).test( this.css( "position") ) || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent;
618 },
619
620 uniqueId: function() {
621 return this.each(function() {
622 if ( !this.id ) {
623 this.id = "ui-id-" + (++uuid);
624 }
625 });
626 },
627
628 removeUniqueId: function() {
629 return this.each(function() {
630 if ( runiqueId.test( this.id ) ) {
631 $( this ).removeAttr( "id" );
632 }
633 });
634 }
635});
636
637// selectors
638function focusable( element, isTabIndexNotNaN ) {
639 var map, mapName, img,
640 nodeName = element.nodeName.toLowerCase();
641 if ( "area" === nodeName ) {
642 map = element.parentNode;
643 mapName = map.name;
644 if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
645 return false;
646 }
647 img = $( "img[usemap=#" + mapName + "]" )[0];
648 return !!img && visible( img );
649 }
650 return ( /input|select|textarea|button|object/.test( nodeName ) ?
651 !element.disabled :
652 "a" === nodeName ?
653 element.href || isTabIndexNotNaN :
654 isTabIndexNotNaN) &&
655 // the element and all of its ancestors must be visible
656 visible( element );
657}
658
659function visible( element ) {
660 return $.expr.filters.visible( element ) &&
661 !$( element ).parents().addBack().filter(function() {
662 return $.css( this, "visibility" ) === "hidden";
663 }).length;
664}
665
666$.extend( $.expr[ ":" ], {
667 data: $.expr.createPseudo ?
668 $.expr.createPseudo(function( dataName ) {
669 return function( elem ) {
670 return !!$.data( elem, dataName );
671 };
672 }) :
673 // support: jQuery <1.8
674 function( elem, i, match ) {
675 return !!$.data( elem, match[ 3 ] );
676 },
677
678 focusable: function( element ) {
679 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
680 },
681
682 tabbable: function( element ) {
683 var tabIndex = $.attr( element, "tabindex" ),
684 isTabIndexNaN = isNaN( tabIndex );
685 return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
686 }
687});
688
689// support: jQuery <1.8
690if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
691 $.each( [ "Width", "Height" ], function( i, name ) {
692 var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
693 type = name.toLowerCase(),
694 orig = {
695 innerWidth: $.fn.innerWidth,
696 innerHeight: $.fn.innerHeight,
697 outerWidth: $.fn.outerWidth,
698 outerHeight: $.fn.outerHeight
699 };
700
701 function reduce( elem, size, border, margin ) {
702 $.each( side, function() {
703 size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
704 if ( border ) {
705 size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
706 }
707 if ( margin ) {
708 size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
709 }
710 });
711 return size;
712 }
713
714 $.fn[ "inner" + name ] = function( size ) {
715 if ( size === undefined ) {
716 return orig[ "inner" + name ].call( this );
717 }
718
719 return this.each(function() {
720 $( this ).css( type, reduce( this, size ) + "px" );
721 });
722 };
723
724 $.fn[ "outer" + name] = function( size, margin ) {
725 if ( typeof size !== "number" ) {
726 return orig[ "outer" + name ].call( this, size );
727 }
728
729 return this.each(function() {
730 $( this).css( type, reduce( this, size, true, margin ) + "px" );
731 });
732 };
733 });
734}
735
736// support: jQuery <1.8
737if ( !$.fn.addBack ) {
738 $.fn.addBack = function( selector ) {
739 return this.add( selector == null ?
740 this.prevObject : this.prevObject.filter( selector )
741 );
742 };
743}
744
745// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
746if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
747 $.fn.removeData = (function( removeData ) {
748 return function( key ) {
749 if ( arguments.length ) {
750 return removeData.call( this, $.camelCase( key ) );
751 } else {
752 return removeData.call( this );
753 }
754 };
755 })( $.fn.removeData );
756}
757
758
759
760
761
762// deprecated
763$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
764
765$.support.selectstart = "onselectstart" in document.createElement( "div" );
766$.fn.extend({
767 disableSelection: function() {
768 return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
769 ".ui-disableSelection", function( event ) {
770 event.preventDefault();
771 });
772 },
773
774 enableSelection: function() {
775 return this.unbind( ".ui-disableSelection" );
776 },
777
778 zIndex: function( zIndex ) {
779 if ( zIndex !== undefined ) {
780 return this.css( "zIndex", zIndex );
781 }
782
783 if ( this.length ) {
784 var elem = $( this[ 0 ] ), position, value;
785 while ( elem.length && elem[ 0 ] !== document ) {
786 // Ignore z-index if position is set to a value where z-index is ignored by the browser
787 // This makes behavior of this function consistent across browsers
788 // WebKit always returns auto if the element is positioned
789 position = elem.css( "position" );
790 if ( position === "absolute" || position === "relative" || position === "fixed" ) {
791 // IE returns 0 when zIndex is not specified
792 // other browsers return a string
793 // we ignore the case of nested elements with an explicit value of 0
794 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
795 value = parseInt( elem.css( "zIndex" ), 10 );
796 if ( !isNaN( value ) && value !== 0 ) {
797 return value;
798 }
799 }
800 elem = elem.parent();
801 }
802 }
803
804 return 0;
805 }
806});
807
808// $.ui.plugin is deprecated. Use $.widget() extensions instead.
809$.ui.plugin = {
810 add: function( module, option, set ) {
811 var i,
812 proto = $.ui[ module ].prototype;
813 for ( i in set ) {
814 proto.plugins[ i ] = proto.plugins[ i ] || [];
815 proto.plugins[ i ].push( [ option, set[ i ] ] );
816 }
817 },
818 call: function( instance, name, args, allowDisconnected ) {
819 var i,
820 set = instance.plugins[ name ];
821
822 if ( !set ) {
823 return;
824 }
825
826 if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
827 return;
828 }
829
830 for ( i = 0; i < set.length; i++ ) {
831 if ( instance.options[ set[ i ][ 0 ] ] ) {
832 set[ i ][ 1 ].apply( instance.element, args );
833 }
834 }
835 }
836};
837
838})( jQuery );
839
840(function( $, window, undefined ) {
841
842 // Subtract the height of external toolbars from the page height, if the page does not have
843 // internal toolbars of the same type. We take care to use the widget options if we find a
844 // widget instance and the element's data-attributes otherwise.
845 var compensateToolbars = function( page, desiredHeight ) {
846 var pageParent = page.parent(),
847 toolbarsAffectingHeight = [],
848
849 // We use this function to filter fixed toolbars with option updatePagePadding set to
850 // true (which is the default) from our height subtraction, because fixed toolbars with
851 // option updatePagePadding set to true compensate for their presence by adding padding
852 // to the active page. We want to avoid double-counting by also subtracting their
853 // height from the desired page height.
854 noPadders = function() {
855 var theElement = $( this ),
856 widgetOptions = $.mobile.toolbar && theElement.data( "mobile-toolbar" ) ?
857 theElement.toolbar( "option" ) : {
858 position: theElement.attr( "data-" + $.mobile.ns + "position" ),
859 updatePagePadding: ( theElement.attr( "data-" + $.mobile.ns +
860 "update-page-padding" ) !== false )
861 };
862
863 return !( widgetOptions.position === "fixed" &&
864 widgetOptions.updatePagePadding === true );
865 },
866 externalHeaders = pageParent.children( ":jqmData(role='header')" ).filter( noPadders ),
867 internalHeaders = page.children( ":jqmData(role='header')" ),
868 externalFooters = pageParent.children( ":jqmData(role='footer')" ).filter( noPadders ),
869 internalFooters = page.children( ":jqmData(role='footer')" );
870
871 // If we have no internal headers, but we do have external headers, then their height
872 // reduces the page height
873 if ( internalHeaders.length === 0 && externalHeaders.length > 0 ) {
874 toolbarsAffectingHeight = toolbarsAffectingHeight.concat( externalHeaders.toArray() );
875 }
876
877 // If we have no internal footers, but we do have external footers, then their height
878 // reduces the page height
879 if ( internalFooters.length === 0 && externalFooters.length > 0 ) {
880 toolbarsAffectingHeight = toolbarsAffectingHeight.concat( externalFooters.toArray() );
881 }
882
883 $.each( toolbarsAffectingHeight, function( index, value ) {
884 desiredHeight -= $( value ).outerHeight();
885 });
886
887 // Height must be at least zero
888 return Math.max( 0, desiredHeight );
889 };
890
891 $.extend( $.mobile, {
892 // define the window and the document objects
893 window: $( window ),
894 document: $( document ),
895
896 // TODO: Remove and use $.ui.keyCode directly
897 keyCode: $.ui.keyCode,
898
899 // Place to store various widget extensions
900 behaviors: {},
901
902 // Custom logic for giving focus to a page
903 focusPage: function( page ) {
904
905 // First, look for an element explicitly marked for page focus
906 var focusElement = page.find( "[autofocus]" );
907
908 // If we do not find an element with the "autofocus" attribute, look for the page title
909 if ( !focusElement.length ) {
910 focusElement = page.find( ".ui-title" ).eq( 0 );
911 }
912
913 // Finally, fall back to focusing the page itself
914 if ( !focusElement.length ) {
915 focusElement = page;
916 }
917
918 focusElement.focus();
919 },
920
921 // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
922 silentScroll: function( ypos ) {
923 if ( $.type( ypos ) !== "number" ) {
924 ypos = $.mobile.defaultHomeScroll;
925 }
926
927 // prevent scrollstart and scrollstop events
928 $.event.special.scrollstart.enabled = false;
929
930 setTimeout(function() {
931 window.scrollTo( 0, ypos );
932 $.mobile.document.trigger( "silentscroll", { x: 0, y: ypos });
933 }, 20 );
934
935 setTimeout(function() {
936 $.event.special.scrollstart.enabled = true;
937 }, 150 );
938 },
939
940 getClosestBaseUrl: function( ele ) {
941 // Find the closest page and extract out its url.
942 var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ),
943 base = $.mobile.path.documentBase.hrefNoHash;
944
945 if ( !$.mobile.base.dynamicBaseEnabled || !url || !$.mobile.path.isPath( url ) ) {
946 url = base;
947 }
948
949 return $.mobile.path.makeUrlAbsolute( url, base );
950 },
951 removeActiveLinkClass: function( forceRemoval ) {
952 if ( !!$.mobile.activeClickedLink &&
953 ( !$.mobile.activeClickedLink.closest( "." + $.mobile.activePageClass ).length ||
954 forceRemoval ) ) {
955
956 $.mobile.activeClickedLink.removeClass( $.mobile.activeBtnClass );
957 }
958 $.mobile.activeClickedLink = null;
959 },
960
961 // DEPRECATED in 1.4
962 // Find the closest parent with a theme class on it. Note that
963 // we are not using $.fn.closest() on purpose here because this
964 // method gets called quite a bit and we need it to be as fast
965 // as possible.
966 getInheritedTheme: function( el, defaultTheme ) {
967 var e = el[ 0 ],
968 ltr = "",
969 re = /ui-(bar|body|overlay)-([a-z])\b/,
970 c, m;
971 while ( e ) {
972 c = e.className || "";
973 if ( c && ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) {
974 // We found a parent with a theme class
975 // on it so bail from this loop.
976 break;
977 }
978
979 e = e.parentNode;
980 }
981 // Return the theme letter we found, if none, return the
982 // specified default.
983 return ltr || defaultTheme || "a";
984 },
985
986 enhanceable: function( elements ) {
987 return this.haveParents( elements, "enhance" );
988 },
989
990 hijackable: function( elements ) {
991 return this.haveParents( elements, "ajax" );
992 },
993
994 haveParents: function( elements, attr ) {
995 if ( !$.mobile.ignoreContentEnabled ) {
996 return elements;
997 }
998
999 var count = elements.length,
1000 $newSet = $(),
1001 e, $element, excluded,
1002 i, c;
1003
1004 for ( i = 0; i < count; i++ ) {
1005 $element = elements.eq( i );
1006 excluded = false;
1007 e = elements[ i ];
1008
1009 while ( e ) {
1010 c = e.getAttribute ? e.getAttribute( "data-" + $.mobile.ns + attr ) : "";
1011
1012 if ( c === "false" ) {
1013 excluded = true;
1014 break;
1015 }
1016
1017 e = e.parentNode;
1018 }
1019
1020 if ( !excluded ) {
1021 $newSet = $newSet.add( $element );
1022 }
1023 }
1024
1025 return $newSet;
1026 },
1027
1028 getScreenHeight: function() {
1029 // Native innerHeight returns more accurate value for this across platforms,
1030 // jQuery version is here as a normalized fallback for platforms like Symbian
1031 return window.innerHeight || $.mobile.window.height();
1032 },
1033
1034 //simply set the active page's minimum height to screen height, depending on orientation
1035 resetActivePageHeight: function( height ) {
1036 var page = $( "." + $.mobile.activePageClass ),
1037 pageHeight = page.height(),
1038 pageOuterHeight = page.outerHeight( true );
1039
1040 height = compensateToolbars( page,
1041 ( typeof height === "number" ) ? height : $.mobile.getScreenHeight() );
1042
1043 // Remove any previous min-height setting
1044 page.css( "min-height", "" );
1045
1046 // Set the minimum height only if the height as determined by CSS is insufficient
1047 if ( page.height() < height ) {
1048 page.css( "min-height", height - ( pageOuterHeight - pageHeight ) );
1049 }
1050 },
1051
1052 loading: function() {
1053 // If this is the first call to this function, instantiate a loader widget
1054 var loader = this.loading._widget || $( $.mobile.loader.prototype.defaultHtml ).loader(),
1055
1056 // Call the appropriate method on the loader
1057 returnValue = loader.loader.apply( loader, arguments );
1058
1059 // Make sure the loader is retained for future calls to this function.
1060 this.loading._widget = loader;
1061
1062 return returnValue;
1063 }
1064 });
1065
1066 $.addDependents = function( elem, newDependents ) {
1067 var $elem = $( elem ),
1068 dependents = $elem.jqmData( "dependents" ) || $();
1069
1070 $elem.jqmData( "dependents", $( dependents ).add( newDependents ) );
1071 };
1072
1073 // plugins
1074 $.fn.extend({
1075 removeWithDependents: function() {
1076 $.removeWithDependents( this );
1077 },
1078
1079 // Enhance child elements
1080 enhanceWithin: function() {
1081 var index,
1082 widgetElements = {},
1083 keepNative = $.mobile.page.prototype.keepNativeSelector(),
1084 that = this;
1085
1086 // Add no js class to elements
1087 if ( $.mobile.nojs ) {
1088 $.mobile.nojs( this );
1089 }
1090
1091 // Bind links for ajax nav
1092 if ( $.mobile.links ) {
1093 $.mobile.links( this );
1094 }
1095
1096 // Degrade inputs for styleing
1097 if ( $.mobile.degradeInputsWithin ) {
1098 $.mobile.degradeInputsWithin( this );
1099 }
1100
1101 // Run buttonmarkup
1102 if ( $.fn.buttonMarkup ) {
1103 this.find( $.fn.buttonMarkup.initSelector ).not( keepNative )
1104 .jqmEnhanceable().buttonMarkup();
1105 }
1106
1107 // Add classes for fieldContain
1108 if ( $.fn.fieldcontain ) {
1109 this.find( ":jqmData(role='fieldcontain')" ).not( keepNative )
1110 .jqmEnhanceable().fieldcontain();
1111 }
1112
1113 // Enhance widgets
1114 $.each( $.mobile.widgets, function( name, constructor ) {
1115
1116 // If initSelector not false find elements
1117 if ( constructor.initSelector ) {
1118
1119 // Filter elements that should not be enhanced based on parents
1120 var elements = $.mobile.enhanceable( that.find( constructor.initSelector ) );
1121
1122 // If any matching elements remain filter ones with keepNativeSelector
1123 if ( elements.length > 0 ) {
1124
1125 // $.mobile.page.prototype.keepNativeSelector is deprecated this is just for backcompat
1126 // Switch to $.mobile.keepNative in 1.5 which is just a value not a function
1127 elements = elements.not( keepNative );
1128 }
1129
1130 // Enhance whatever is left
1131 if ( elements.length > 0 ) {
1132 widgetElements[ constructor.prototype.widgetName ] = elements;
1133 }
1134 }
1135 });
1136
1137 for ( index in widgetElements ) {
1138 widgetElements[ index ][ index ]();
1139 }
1140
1141 return this;
1142 },
1143
1144 addDependents: function( newDependents ) {
1145 $.addDependents( this, newDependents );
1146 },
1147
1148 // note that this helper doesn't attempt to handle the callback
1149 // or setting of an html element's text, its only purpose is
1150 // to return the html encoded version of the text in all cases. (thus the name)
1151 getEncodedText: function() {
1152 return $( "<a>" ).text( this.text() ).html();
1153 },
1154
1155 // fluent helper function for the mobile namespaced equivalent
1156 jqmEnhanceable: function() {
1157 return $.mobile.enhanceable( this );
1158 },
1159
1160 jqmHijackable: function() {
1161 return $.mobile.hijackable( this );
1162 }
1163 });
1164
1165 $.removeWithDependents = function( nativeElement ) {
1166 var element = $( nativeElement );
1167
1168 ( element.jqmData( "dependents" ) || $() ).remove();
1169 element.remove();
1170 };
1171 $.addDependents = function( nativeElement, newDependents ) {
1172 var element = $( nativeElement ),
1173 dependents = element.jqmData( "dependents" ) || $();
1174
1175 element.jqmData( "dependents", $( dependents ).add( newDependents ) );
1176 };
1177
1178 $.find.matches = function( expr, set ) {
1179 return $.find( expr, null, null, set );
1180 };
1181
1182 $.find.matchesSelector = function( node, expr ) {
1183 return $.find( expr, null, null, [ node ] ).length > 0;
1184 };
1185
1186})( jQuery, this );
1187
1188(function( $, window, undefined ) {
1189 $.extend( $.mobile, {
1190
1191 // Version of the jQuery Mobile Framework
1192 version: "1.5.0-pre",
1193
1194 // Deprecated and no longer used in 1.4 remove in 1.5
1195 // Define the url parameter used for referencing widget-generated sub-pages.
1196 // Translates to example.html&ui-page=subpageIdentifier
1197 // hash segment before &ui-page= is used to make Ajax request
1198 subPageUrlKey: "ui-page",
1199
1200 hideUrlBar: true,
1201
1202 // Keepnative Selector
1203 keepNative: ":jqmData(role='none'), :jqmData(role='nojs')",
1204
1205 // Deprecated in 1.4 remove in 1.5
1206 // Class assigned to page currently in view, and during transitions
1207 activePageClass: "ui-page-active",
1208
1209 // Deprecated in 1.4 remove in 1.5
1210 // Class used for "active" button state, from CSS framework
1211 activeBtnClass: "ui-btn-active",
1212
1213 // Deprecated in 1.4 remove in 1.5
1214 // Class used for "focus" form element state, from CSS framework
1215 focusClass: "ui-focus",
1216
1217 // Automatically handle clicks and form submissions through Ajax, when same-domain
1218 ajaxEnabled: true,
1219
1220 // Automatically load and show pages based on location.hash
1221 hashListeningEnabled: true,
1222
1223 // disable to prevent jquery from bothering with links
1224 linkBindingEnabled: true,
1225
1226 // Set default page transition - 'none' for no transitions
1227 defaultPageTransition: "fade",
1228
1229 // Set maximum window width for transitions to apply - 'false' for no limit
1230 maxTransitionWidth: false,
1231
1232 // Minimum scroll distance that will be remembered when returning to a page
1233 // Deprecated remove in 1.5
1234 minScrollBack: 0,
1235
1236 // Set default dialog transition - 'none' for no transitions
1237 defaultDialogTransition: "pop",
1238
1239 // Error response message - appears when an Ajax page request fails
1240 pageLoadErrorMessage: "Error Loading Page",
1241
1242 // For error messages, which theme does the box use?
1243 pageLoadErrorMessageTheme: "a",
1244
1245 // replace calls to window.history.back with phonegaps navigation helper
1246 // where it is provided on the window object
1247 phonegapNavigationEnabled: false,
1248
1249 //automatically initialize the DOM when it's ready
1250 autoInitializePage: true,
1251
1252 pushStateEnabled: true,
1253
1254 // allows users to opt in to ignoring content by marking a parent element as
1255 // data-ignored
1256 ignoreContentEnabled: false,
1257
1258 // default the property to remove dependency on assignment in init module
1259 pageContainer: $(),
1260
1261 //enable cross-domain page support
1262 allowCrossDomainPages: false,
1263
1264 dialogHashKey: "&ui-state=dialog"
1265 });
1266})( jQuery, this );
1267
1268/*!
1269 * jQuery UI Widget c0ab71056b936627e8a7821f03c044aec6280a40
1270 * http://jqueryui.com
1271 *
1272 * Copyright 2013 jQuery Foundation and other contributors
1273 * Released under the MIT license.
1274 * http://jquery.org/license
1275 *
1276 * http://api.jqueryui.com/jQuery.widget/
1277 */
1278(function( $, undefined ) {
1279
1280var uuid = 0,
1281 slice = Array.prototype.slice,
1282 _cleanData = $.cleanData;
1283$.cleanData = function( elems ) {
1284 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
1285 try {
1286 $( elem ).triggerHandler( "remove" );
1287 // http://bugs.jquery.com/ticket/8235
1288 } catch( e ) {}
1289 }
1290 _cleanData( elems );
1291};
1292
1293$.widget = function( name, base, prototype ) {
1294 var fullName, existingConstructor, constructor, basePrototype,
1295 // proxiedPrototype allows the provided prototype to remain unmodified
1296 // so that it can be used as a mixin for multiple widgets (#8876)
1297 proxiedPrototype = {},
1298 namespace = name.split( "." )[ 0 ];
1299
1300 name = name.split( "." )[ 1 ];
1301 fullName = namespace + "-" + name;
1302
1303 if ( !prototype ) {
1304 prototype = base;
1305 base = $.Widget;
1306 }
1307
1308 // create selector for plugin
1309 $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
1310 return !!$.data( elem, fullName );
1311 };
1312
1313 $[ namespace ] = $[ namespace ] || {};
1314 existingConstructor = $[ namespace ][ name ];
1315 constructor = $[ namespace ][ name ] = function( options, element ) {
1316 // allow instantiation without "new" keyword
1317 if ( !this._createWidget ) {
1318 return new constructor( options, element );
1319 }
1320
1321 // allow instantiation without initializing for simple inheritance
1322 // must use "new" keyword (the code above always passes args)
1323 if ( arguments.length ) {
1324 this._createWidget( options, element );
1325 }
1326 };
1327 // extend with the existing constructor to carry over any static properties
1328 $.extend( constructor, existingConstructor, {
1329 version: prototype.version,
1330 // copy the object used to create the prototype in case we need to
1331 // redefine the widget later
1332 _proto: $.extend( {}, prototype ),
1333 // track widgets that inherit from this widget in case this widget is
1334 // redefined after a widget inherits from it
1335 _childConstructors: []
1336 });
1337
1338 basePrototype = new base();
1339 // we need to make the options hash a property directly on the new instance
1340 // otherwise we'll modify the options hash on the prototype that we're
1341 // inheriting from
1342 basePrototype.options = $.widget.extend( {}, basePrototype.options );
1343 $.each( prototype, function( prop, value ) {
1344 if ( !$.isFunction( value ) ) {
1345 proxiedPrototype[ prop ] = value;
1346 return;
1347 }
1348 proxiedPrototype[ prop ] = (function() {
1349 var _super = function() {
1350 return base.prototype[ prop ].apply( this, arguments );
1351 },
1352 _superApply = function( args ) {
1353 return base.prototype[ prop ].apply( this, args );
1354 };
1355 return function() {
1356 var __super = this._super,
1357 __superApply = this._superApply,
1358 returnValue;
1359
1360 this._super = _super;
1361 this._superApply = _superApply;
1362
1363 returnValue = value.apply( this, arguments );
1364
1365 this._super = __super;
1366 this._superApply = __superApply;
1367
1368 return returnValue;
1369 };
1370 })();
1371 });
1372 constructor.prototype = $.widget.extend( basePrototype, {
1373 // TODO: remove support for widgetEventPrefix
1374 // always use the name + a colon as the prefix, e.g., draggable:start
1375 // don't prefix for widgets that aren't DOM-based
1376 widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
1377 }, proxiedPrototype, {
1378 constructor: constructor,
1379 namespace: namespace,
1380 widgetName: name,
1381 widgetFullName: fullName
1382 });
1383
1384 // If this widget is being redefined then we need to find all widgets that
1385 // are inheriting from it and redefine all of them so that they inherit from
1386 // the new version of this widget. We're essentially trying to replace one
1387 // level in the prototype chain.
1388 if ( existingConstructor ) {
1389 $.each( existingConstructor._childConstructors, function( i, child ) {
1390 var childPrototype = child.prototype;
1391
1392 // redefine the child widget using the same prototype that was
1393 // originally used, but inherit from the new version of the base
1394 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
1395 });
1396 // remove the list of existing child constructors from the old constructor
1397 // so the old child constructors can be garbage collected
1398 delete existingConstructor._childConstructors;
1399 } else {
1400 base._childConstructors.push( constructor );
1401 }
1402
1403 $.widget.bridge( name, constructor );
1404
1405 return constructor;
1406};
1407
1408$.widget.extend = function( target ) {
1409 var input = slice.call( arguments, 1 ),
1410 inputIndex = 0,
1411 inputLength = input.length,
1412 key,
1413 value;
1414 for ( ; inputIndex < inputLength; inputIndex++ ) {
1415 for ( key in input[ inputIndex ] ) {
1416 value = input[ inputIndex ][ key ];
1417 if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
1418 // Clone objects
1419 if ( $.isPlainObject( value ) ) {
1420 target[ key ] = $.isPlainObject( target[ key ] ) ?
1421 $.widget.extend( {}, target[ key ], value ) :
1422 // Don't extend strings, arrays, etc. with objects
1423 $.widget.extend( {}, value );
1424 // Copy everything else by reference
1425 } else {
1426 target[ key ] = value;
1427 }
1428 }
1429 }
1430 }
1431 return target;
1432};
1433
1434$.widget.bridge = function( name, object ) {
1435 var fullName = object.prototype.widgetFullName || name;
1436 $.fn[ name ] = function( options ) {
1437 var isMethodCall = typeof options === "string",
1438 args = slice.call( arguments, 1 ),
1439 returnValue = this;
1440
1441 // allow multiple hashes to be passed on init
1442 options = !isMethodCall && args.length ?
1443 $.widget.extend.apply( null, [ options ].concat(args) ) :
1444 options;
1445
1446 if ( isMethodCall ) {
1447 this.each(function() {
1448 var methodValue,
1449 instance = $.data( this, fullName );
1450 if ( options === "instance" ) {
1451 returnValue = instance;
1452 return false;
1453 }
1454 if ( !instance ) {
1455 return $.error( "cannot call methods on " + name + " prior to initialization; " +
1456 "attempted to call method '" + options + "'" );
1457 }
1458 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
1459 return $.error( "no such method '" + options + "' for " + name + " widget instance" );
1460 }
1461 methodValue = instance[ options ].apply( instance, args );
1462 if ( methodValue !== instance && methodValue !== undefined ) {
1463 returnValue = methodValue && methodValue.jquery ?
1464 returnValue.pushStack( methodValue.get() ) :
1465 methodValue;
1466 return false;
1467 }
1468 });
1469 } else {
1470 this.each(function() {
1471 var instance = $.data( this, fullName );
1472 if ( instance ) {
1473 instance.option( options || {} )._init();
1474 } else {
1475 $.data( this, fullName, new object( options, this ) );
1476 }
1477 });
1478 }
1479
1480 return returnValue;
1481 };
1482};
1483
1484$.Widget = function( /* options, element */ ) {};
1485$.Widget._childConstructors = [];
1486
1487$.Widget.prototype = {
1488 widgetName: "widget",
1489 widgetEventPrefix: "",
1490 defaultElement: "<div>",
1491 options: {
1492 disabled: false,
1493
1494 // callbacks
1495 create: null
1496 },
1497 _createWidget: function( options, element ) {
1498 element = $( element || this.defaultElement || this )[ 0 ];
1499 this.element = $( element );
1500 this.uuid = uuid++;
1501 this.eventNamespace = "." + this.widgetName + this.uuid;
1502 this.options = $.widget.extend( {},
1503 this.options,
1504 this._getCreateOptions(),
1505 options );
1506
1507 this.bindings = $();
1508 this.hoverable = $();
1509 this.focusable = $();
1510
1511 if ( element !== this ) {
1512 $.data( element, this.widgetFullName, this );
1513 this._on( true, this.element, {
1514 remove: function( event ) {
1515 if ( event.target === element ) {
1516 this.destroy();
1517 }
1518 }
1519 });
1520 this.document = $( element.style ?
1521 // element within the document
1522 element.ownerDocument :
1523 // element is window or document
1524 element.document || element );
1525 this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
1526 }
1527
1528 this._create();
1529 this._trigger( "create", null, this._getCreateEventData() );
1530 this._init();
1531 },
1532 _getCreateOptions: $.noop,
1533 _getCreateEventData: $.noop,
1534 _create: $.noop,
1535 _init: $.noop,
1536
1537 destroy: function() {
1538 this._destroy();
1539 // we can probably remove the unbind calls in 2.0
1540 // all event bindings should go through this._on()
1541 this.element
1542 .unbind( this.eventNamespace )
1543 .removeData( this.widgetFullName )
1544 // support: jquery <1.6.3
1545 // http://bugs.jquery.com/ticket/9413
1546 .removeData( $.camelCase( this.widgetFullName ) );
1547 this.widget()
1548 .unbind( this.eventNamespace )
1549 .removeAttr( "aria-disabled" )
1550 .removeClass(
1551 this.widgetFullName + "-disabled " +
1552 "ui-state-disabled" );
1553
1554 // clean up events and states
1555 this.bindings.unbind( this.eventNamespace );
1556 this.hoverable.removeClass( "ui-state-hover" );
1557 this.focusable.removeClass( "ui-state-focus" );
1558 },
1559 _destroy: $.noop,
1560
1561 widget: function() {
1562 return this.element;
1563 },
1564
1565 option: function( key, value ) {
1566 var options = key,
1567 parts,
1568 curOption,
1569 i;
1570
1571 if ( arguments.length === 0 ) {
1572 // don't return a reference to the internal hash
1573 return $.widget.extend( {}, this.options );
1574 }
1575
1576 if ( typeof key === "string" ) {
1577 // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
1578 options = {};
1579 parts = key.split( "." );
1580 key = parts.shift();
1581 if ( parts.length ) {
1582 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
1583 for ( i = 0; i < parts.length - 1; i++ ) {
1584 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
1585 curOption = curOption[ parts[ i ] ];
1586 }
1587 key = parts.pop();
1588 if ( value === undefined ) {
1589 return curOption[ key ] === undefined ? null : curOption[ key ];
1590 }
1591 curOption[ key ] = value;
1592 } else {
1593 if ( value === undefined ) {
1594 return this.options[ key ] === undefined ? null : this.options[ key ];
1595 }
1596 options[ key ] = value;
1597 }
1598 }
1599
1600 this._setOptions( options );
1601
1602 return this;
1603 },
1604 _setOptions: function( options ) {
1605 var key;
1606
1607 for ( key in options ) {
1608 this._setOption( key, options[ key ] );
1609 }
1610
1611 return this;
1612 },
1613 _setOption: function( key, value ) {
1614 this.options[ key ] = value;
1615
1616 if ( key === "disabled" ) {
1617 this.widget()
1618 .toggleClass( this.widgetFullName + "-disabled", !!value );
1619 this.hoverable.removeClass( "ui-state-hover" );
1620 this.focusable.removeClass( "ui-state-focus" );
1621 }
1622
1623 return this;
1624 },
1625
1626 enable: function() {
1627 return this._setOptions({ disabled: false });
1628 },
1629 disable: function() {
1630 return this._setOptions({ disabled: true });
1631 },
1632
1633 _on: function( suppressDisabledCheck, element, handlers ) {
1634 var delegateElement,
1635 instance = this;
1636
1637 // no suppressDisabledCheck flag, shuffle arguments
1638 if ( typeof suppressDisabledCheck !== "boolean" ) {
1639 handlers = element;
1640 element = suppressDisabledCheck;
1641 suppressDisabledCheck = false;
1642 }
1643
1644 // no element argument, shuffle and use this.element
1645 if ( !handlers ) {
1646 handlers = element;
1647 element = this.element;
1648 delegateElement = this.widget();
1649 } else {
1650 // accept selectors, DOM elements
1651 element = delegateElement = $( element );
1652 this.bindings = this.bindings.add( element );
1653 }
1654
1655 $.each( handlers, function( event, handler ) {
1656 function handlerProxy() {
1657 // allow widgets to customize the disabled handling
1658 // - disabled as an array instead of boolean
1659 // - disabled class as method for disabling individual parts
1660 if ( !suppressDisabledCheck &&
1661 ( instance.options.disabled === true ||
1662 $( this ).hasClass( "ui-state-disabled" ) ) ) {
1663 return;
1664 }
1665 return ( typeof handler === "string" ? instance[ handler ] : handler )
1666 .apply( instance, arguments );
1667 }
1668
1669 // copy the guid so direct unbinding works
1670 if ( typeof handler !== "string" ) {
1671 handlerProxy.guid = handler.guid =
1672 handler.guid || handlerProxy.guid || $.guid++;
1673 }
1674
1675 var match = event.match( /^(\w+)\s*(.*)$/ ),
1676 eventName = match[1] + instance.eventNamespace,
1677 selector = match[2];
1678 if ( selector ) {
1679 delegateElement.delegate( selector, eventName, handlerProxy );
1680 } else {
1681 element.bind( eventName, handlerProxy );
1682 }
1683 });
1684 },
1685
1686 _off: function( element, eventName ) {
1687 eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
1688 element.unbind( eventName ).undelegate( eventName );
1689 },
1690
1691 _delay: function( handler, delay ) {
1692 function handlerProxy() {
1693 return ( typeof handler === "string" ? instance[ handler ] : handler )
1694 .apply( instance, arguments );
1695 }
1696 var instance = this;
1697 return setTimeout( handlerProxy, delay || 0 );
1698 },
1699
1700 _hoverable: function( element ) {
1701 this.hoverable = this.hoverable.add( element );
1702 this._on( element, {
1703 mouseenter: function( event ) {
1704 $( event.currentTarget ).addClass( "ui-state-hover" );
1705 },
1706 mouseleave: function( event ) {
1707 $( event.currentTarget ).removeClass( "ui-state-hover" );
1708 }
1709 });
1710 },
1711
1712 _focusable: function( element ) {
1713 this.focusable = this.focusable.add( element );
1714 this._on( element, {
1715 focusin: function( event ) {
1716 $( event.currentTarget ).addClass( "ui-state-focus" );
1717 },
1718 focusout: function( event ) {
1719 $( event.currentTarget ).removeClass( "ui-state-focus" );
1720 }
1721 });
1722 },
1723
1724 _trigger: function( type, event, data ) {
1725 var prop, orig,
1726 callback = this.options[ type ];
1727
1728 data = data || {};
1729 event = $.Event( event );
1730 event.type = ( type === this.widgetEventPrefix ?
1731 type :
1732 this.widgetEventPrefix + type ).toLowerCase();
1733 // the original event may come from any element
1734 // so we need to reset the target on the new event
1735 event.target = this.element[ 0 ];
1736
1737 // copy original event properties over to the new event
1738 orig = event.originalEvent;
1739 if ( orig ) {
1740 for ( prop in orig ) {
1741 if ( !( prop in event ) ) {
1742 event[ prop ] = orig[ prop ];
1743 }
1744 }
1745 }
1746
1747 this.element.trigger( event, data );
1748 return !( $.isFunction( callback ) &&
1749 callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
1750 event.isDefaultPrevented() );
1751 }
1752};
1753
1754$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
1755 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
1756 if ( typeof options === "string" ) {
1757 options = { effect: options };
1758 }
1759 var hasOptions,
1760 effectName = !options ?
1761 method :
1762 options === true || typeof options === "number" ?
1763 defaultEffect :
1764 options.effect || defaultEffect;
1765 options = options || {};
1766 if ( typeof options === "number" ) {
1767 options = { duration: options };
1768 }
1769 hasOptions = !$.isEmptyObject( options );
1770 options.complete = callback;
1771 if ( options.delay ) {
1772 element.delay( options.delay );
1773 }
1774 if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
1775 element[ method ]( options );
1776 } else if ( effectName !== method && element[ effectName ] ) {
1777 element[ effectName ]( options.duration, options.easing, callback );
1778 } else {
1779 element.queue(function( next ) {
1780 $( this )[ method ]();
1781 if ( callback ) {
1782 callback.call( element[ 0 ] );
1783 }
1784 next();
1785 });
1786 }
1787 };
1788});
1789
1790})( jQuery );
1791
1792(function( $, window, undefined ) {
1793 var nsNormalizeDict = {},
1794 oldFind = $.find,
1795 rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
1796 jqmDataRE = /:jqmData\(([^)]*)\)/g;
1797
1798 $.extend( $.mobile, {
1799
1800 // Namespace used framework-wide for data-attrs. Default is no namespace
1801
1802 ns: "",
1803
1804 // Retrieve an attribute from an element and perform some massaging of the value
1805
1806 getAttribute: function( element, key ) {
1807 var data;
1808
1809 element = element.jquery ? element[0] : element;
1810
1811 if ( element && element.getAttribute ) {
1812 data = element.getAttribute( "data-" + $.mobile.ns + key );
1813 }
1814
1815 // Copied from core's src/data.js:dataAttr()
1816 // Convert from a string to a proper data type
1817 try {
1818 data = data === "true" ? true :
1819 data === "false" ? false :
1820 data === "null" ? null :
1821 // Only convert to a number if it doesn't change the string
1822 +data + "" === data ? +data :
1823 rbrace.test( data ) ? JSON.parse( data ) :
1824 data;
1825 } catch( err ) {}
1826
1827 return data;
1828 },
1829
1830 // Expose our cache for testing purposes.
1831 nsNormalizeDict: nsNormalizeDict,
1832
1833 // Take a data attribute property, prepend the namespace
1834 // and then camel case the attribute string. Add the result
1835 // to our nsNormalizeDict so we don't have to do this again.
1836 nsNormalize: function( prop ) {
1837 return nsNormalizeDict[ prop ] ||
1838 ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
1839 },
1840
1841 // Find the closest javascript page element to gather settings data jsperf test
1842 // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
1843 // possibly naive, but it shows that the parsing overhead for *just* the page selector vs
1844 // the page and dialog selector is negligable. This could probably be speed up by
1845 // doing a similar parent node traversal to the one found in the inherited theme code above
1846 closestPageData: function( $target ) {
1847 return $target
1848 .closest( ":jqmData(role='page'), :jqmData(role='dialog')" )
1849 .data( "mobile-page" );
1850 }
1851
1852 });
1853
1854 // Mobile version of data and removeData and hasData methods
1855 // ensures all data is set and retrieved using jQuery Mobile's data namespace
1856 $.fn.jqmData = function( prop, value ) {
1857 var result;
1858 if ( typeof prop !== "undefined" ) {
1859 if ( prop ) {
1860 prop = $.mobile.nsNormalize( prop );
1861 }
1862
1863 // undefined is permitted as an explicit input for the second param
1864 // in this case it returns the value and does not set it to undefined
1865 if ( arguments.length < 2 || value === undefined ) {
1866 result = this.data( prop );
1867 } else {
1868 result = this.data( prop, value );
1869 }
1870 }
1871 return result;
1872 };
1873
1874 $.jqmData = function( elem, prop, value ) {
1875 var result;
1876 if ( typeof prop !== "undefined" ) {
1877 result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value );
1878 }
1879 return result;
1880 };
1881
1882 $.fn.jqmRemoveData = function( prop ) {
1883 return this.removeData( $.mobile.nsNormalize( prop ) );
1884 };
1885
1886 $.jqmRemoveData = function( elem, prop ) {
1887 return $.removeData( elem, $.mobile.nsNormalize( prop ) );
1888 };
1889
1890 $.find = function( selector, context, ret, extra ) {
1891 if ( selector.indexOf( ":jqmData" ) > -1 ) {
1892 selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
1893 }
1894
1895 return oldFind.call( this, selector, context, ret, extra );
1896 };
1897
1898 $.extend( $.find, oldFind );
1899
1900})( jQuery, this );
1901
1902(function( $, undefined ) {
1903
1904var rcapitals = /[A-Z]/g,
1905 replaceFunction = function( c ) {
1906 return "-" + c.toLowerCase();
1907 };
1908
1909$.extend( $.Widget.prototype, {
1910 _getCreateOptions: function() {
1911 var option, value,
1912 elem = this.element[ 0 ],
1913 options = {};
1914
1915 //
1916 if ( !$.mobile.getAttribute( elem, "defaults" ) ) {
1917 for ( option in this.options ) {
1918 value = $.mobile.getAttribute( elem, option.replace( rcapitals, replaceFunction ) );
1919
1920 if ( value != null ) {
1921 options[ option ] = value;
1922 }
1923 }
1924 }
1925
1926 return options;
1927 }
1928});
1929
1930//TODO: Remove in 1.5 for backcompat only
1931$.mobile.widget = $.Widget;
1932
1933})( jQuery );
1934
1935
1936(function( $ ) {
1937 // TODO move loader class down into the widget settings
1938 var loaderClass = "ui-loader", $html = $( "html" );
1939
1940 $.widget( "mobile.loader", {
1941 // NOTE if the global config settings are defined they will override these
1942 // options
1943 options: {
1944 // the theme for the loading message
1945 theme: "a",
1946
1947 // whether the text in the loading message is shown
1948 textVisible: false,
1949
1950 // custom html for the inner content of the loading message
1951 html: "",
1952
1953 // the text to be displayed when the popup is shown
1954 text: "loading"
1955 },
1956
1957 defaultHtml: "<div class='" + loaderClass + "'>" +
1958 "<span class='ui-icon-loading'></span>" +
1959 "<h1></h1>" +
1960 "</div>",
1961
1962 // For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
1963 fakeFixLoader: function() {
1964 var activeBtn = $( "." + $.mobile.activeBtnClass ).first();
1965
1966 this.element
1967 .css({
1968 top: $.support.scrollTop && this.window.scrollTop() + this.window.height() / 2 ||
1969 activeBtn.length && activeBtn.offset().top || 100
1970 });
1971 },
1972
1973 // check position of loader to see if it appears to be "fixed" to center
1974 // if not, use abs positioning
1975 checkLoaderPosition: function() {
1976 var offset = this.element.offset(),
1977 scrollTop = this.window.scrollTop(),
1978 screenHeight = $.mobile.getScreenHeight();
1979
1980 if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) {
1981 this.element.addClass( "ui-loader-fakefix" );
1982 this.fakeFixLoader();
1983 this.window
1984 .unbind( "scroll", this.checkLoaderPosition )
1985 .bind( "scroll", $.proxy( this.fakeFixLoader, this ) );
1986 }
1987 },
1988
1989 resetHtml: function() {
1990 this.element.html( $( this.defaultHtml ).html() );
1991 },
1992
1993 // Turn on/off page loading message. Theme doubles as an object argument
1994 // with the following shape: { theme: '', text: '', html: '', textVisible: '' }
1995 // NOTE that the $.mobile.loading* settings and params past the first are deprecated
1996 // TODO sweet jesus we need to break some of this out
1997 show: function( theme, msgText, textonly ) {
1998 var textVisible, message, loadSettings;
1999
2000 this.resetHtml();
2001
2002 // use the prototype options so that people can set them globally at
2003 // mobile init. Consistency, it's what's for dinner
2004 if ( $.type( theme ) === "object" ) {
2005 loadSettings = $.extend( {}, this.options, theme );
2006
2007 theme = loadSettings.theme;
2008 } else {
2009 loadSettings = this.options;
2010
2011 // here we prefer the theme value passed as a string argument, then
2012 // we prefer the global option because we can't use undefined default
2013 // prototype options, then the prototype option
2014 theme = theme || loadSettings.theme;
2015 }
2016
2017 // set the message text, prefer the param, then the settings object
2018 // then loading message
2019 message = msgText || ( loadSettings.text === false ? "" : loadSettings.text );
2020
2021 // prepare the dom
2022 $html.addClass( "ui-loading" );
2023
2024 textVisible = loadSettings.textVisible;
2025
2026 // add the proper css given the options (theme, text, etc)
2027 // Force text visibility if the second argument was supplied, or
2028 // if the text was explicitly set in the object args
2029 this.element.attr("class", loaderClass +
2030 " ui-corner-all ui-body-" + theme +
2031 " ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) +
2032 ( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) );
2033
2034 // TODO verify that jquery.fn.html is ok to use in both cases here
2035 // this might be overly defensive in preventing unknowing xss
2036 // if the html attribute is defined on the loading settings, use that
2037 // otherwise use the fallbacks from above
2038 if ( loadSettings.html ) {
2039 this.element.html( loadSettings.html );
2040 } else {
2041 this.element.find( "h1" ).text( message );
2042 }
2043
2044 // If the pagecontainer widget has been defined we may use the :mobile-pagecontainer
2045 // and attach to the element on which the pagecontainer widget has been defined. If not,
2046 // we attach to the body.
2047 this.element.appendTo( $.mobile.pagecontainer ?
2048 $( ":mobile-pagecontainer" ) : $( "body" ) );
2049
2050 // check that the loader is visible
2051 this.checkLoaderPosition();
2052
2053 // on scroll check the loader position
2054 this.window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) );
2055 },
2056
2057 hide: function() {
2058 $html.removeClass( "ui-loading" );
2059
2060 if ( this.options.text ) {
2061 this.element.removeClass( "ui-loader-fakefix" );
2062 }
2063
2064 this.window
2065 .unbind( "scroll", this.fakeFixLoader )
2066 .unbind( "scroll", this.checkLoaderPosition );
2067 }
2068 });
2069
2070})(jQuery, this);
2071
2072
2073
2074(function( $, undefined ) {
2075
2076 /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
2077 window.matchMedia = window.matchMedia || (function( doc, undefined ) {
2078
2079 var bool,
2080 docElem = doc.documentElement,
2081 refNode = docElem.firstElementChild || docElem.firstChild,
2082 // fakeBody required for <FF4 when executed in <head>
2083 fakeBody = doc.createElement( "body" ),
2084 div = doc.createElement( "div" );
2085
2086 div.id = "mq-test-1";
2087 div.style.cssText = "position:absolute;top:-100em";
2088 fakeBody.style.background = "none";
2089 fakeBody.appendChild(div);
2090
2091 return function(q){
2092
2093 div.innerHTML = "­<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>";
2094
2095 docElem.insertBefore( fakeBody, refNode );
2096 bool = div.offsetWidth === 42;
2097 docElem.removeChild( fakeBody );
2098
2099 return {
2100 matches: bool,
2101 media: q
2102 };
2103
2104 };
2105
2106 }( document ));
2107
2108 // $.mobile.media uses matchMedia to return a boolean.
2109 $.mobile.media = function( q ) {
2110 var mediaQueryList = window.matchMedia( q );
2111 // Firefox returns null in a hidden iframe
2112 return mediaQueryList && mediaQueryList.matches;
2113 };
2114
2115})(jQuery);
2116
2117 (function( $, undefined ) {
2118 var support = {
2119 touch: "ontouchend" in document
2120 };
2121
2122 $.mobile.support = $.mobile.support || {};
2123 $.extend( $.support, support );
2124 $.extend( $.mobile.support, support );
2125 }( jQuery ));
2126
2127 (function( $, undefined ) {
2128 $.extend( $.support, {
2129 orientation: "orientation" in window && "onorientationchange" in window
2130 });
2131 }( jQuery ));
2132
2133(function( $, undefined ) {
2134
2135// thx Modernizr
2136function propExists( prop ) {
2137 var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
2138 props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ),
2139 v;
2140
2141 for ( v in props ) {
2142 if ( fbCSS[ props[ v ] ] !== undefined ) {
2143 return true;
2144 }
2145 }
2146}
2147
2148var fakeBody = $( "<body>" ).prependTo( "html" ),
2149 fbCSS = fakeBody[ 0 ].style,
2150 vendors = [ "Webkit", "Moz", "O" ],
2151 webos = "palmGetResource" in window, //only used to rule out scrollTop
2152 operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]",
2153 bb = window.blackberry && !propExists( "-webkit-transform" ), //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
2154 nokiaLTE7_3;
2155
2156// inline SVG support test
2157function inlineSVG() {
2158 // Thanks Modernizr & Erik Dahlstrom
2159 var w = window,
2160 svg = !!w.document.createElementNS && !!w.document.createElementNS( "http://www.w3.org/2000/svg", "svg" ).createSVGRect && !( w.opera && navigator.userAgent.indexOf( "Chrome" ) === -1 ),
2161 support = function( data ) {
2162 if ( !( data && svg ) ) {
2163 $( "html" ).addClass( "ui-nosvg" );
2164 }
2165 },
2166 img = new w.Image();
2167
2168 img.onerror = function() {
2169 support( false );
2170 };
2171 img.onload = function() {
2172 support( img.width === 1 && img.height === 1 );
2173 };
2174 img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
2175}
2176
2177function transform3dTest() {
2178 var mqProp = "transform-3d",
2179 // Because the `translate3d` test below throws false positives in Android:
2180 ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ),
2181 el, transforms, t;
2182
2183 if ( ret ) {
2184 return !!ret;
2185 }
2186
2187 el = document.createElement( "div" );
2188 transforms = {
2189 // We’re omitting Opera for the time being; MS uses unprefixed.
2190 "MozTransform": "-moz-transform",
2191 "transform": "transform"
2192 };
2193
2194 fakeBody.append( el );
2195
2196 for ( t in transforms ) {
2197 if ( el.style[ t ] !== undefined ) {
2198 el.style[ t ] = "translate3d( 100px, 1px, 1px )";
2199 ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] );
2200 }
2201 }
2202 return ( !!ret && ret !== "none" );
2203}
2204
2205// Thanks Modernizr
2206function cssPointerEventsTest() {
2207 var element = document.createElement( "x" ),
2208 documentElement = document.documentElement,
2209 getComputedStyle = window.getComputedStyle,
2210 supports;
2211
2212 if ( !( "pointerEvents" in element.style ) ) {
2213 return false;
2214 }
2215
2216 element.style.pointerEvents = "auto";
2217 element.style.pointerEvents = "x";
2218 documentElement.appendChild( element );
2219 supports = getComputedStyle &&
2220 getComputedStyle( element, "" ).pointerEvents === "auto";
2221 documentElement.removeChild( element );
2222 return !!supports;
2223}
2224
2225function boundingRect() {
2226 var div = document.createElement( "div" );
2227 return typeof div.getBoundingClientRect !== "undefined";
2228}
2229
2230// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
2231// allows for inclusion of IE 6+, including Windows Mobile 7
2232$.extend( $.mobile, { browser: {} } );
2233$.mobile.browser.oldIE = (function() {
2234 var v = 3,
2235 div = document.createElement( "div" ),
2236 a = div.all || [];
2237
2238 do {
2239 div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->";
2240 } while( a[0] );
2241
2242 return v > 4 ? v : !v;
2243})();
2244
2245function fixedPosition() {
2246 var w = window,
2247 ua = navigator.userAgent,
2248 platform = navigator.platform,
2249 // Rendering engine is Webkit, and capture major version
2250 wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
2251 wkversion = !!wkmatch && wkmatch[ 1 ],
2252 ffmatch = ua.match( /Fennec\/([0-9]+)/ ),
2253 ffversion = !!ffmatch && ffmatch[ 1 ],
2254 operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ),
2255 omversion = !!operammobilematch && operammobilematch[ 1 ];
2256
2257 if (
2258 // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
2259 ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) ||
2260 // Opera Mini
2261 ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) ||
2262 ( operammobilematch && omversion < 7458 ) ||
2263 //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
2264 ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) ||
2265 // Firefox Mobile before 6.0 -
2266 ( ffversion && ffversion < 6 ) ||
2267 // WebOS less than 3
2268 ( "palmGetResource" in window && wkversion && wkversion < 534 ) ||
2269 // MeeGo
2270 ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) {
2271 return false;
2272 }
2273
2274 return true;
2275}
2276
2277$.extend( $.support, {
2278 // Note, Chrome for iOS has an extremely quirky implementation of popstate.
2279 // We've chosen to take the shortest path to a bug fix here for issue #5426
2280 // See the following link for information about the regex chosen
2281 // https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
2282 pushState: "pushState" in history &&
2283 "replaceState" in history &&
2284 // When running inside a FF iframe, calling replaceState causes an error
2285 !( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) &&
2286 ( window.navigator.userAgent.search(/CriOS/) === -1 ),
2287
2288 mediaquery: $.mobile.media( "only all" ),
2289 cssPseudoElement: !!propExists( "content" ),
2290 touchOverflow: !!propExists( "overflowScrolling" ),
2291 cssTransform3d: transform3dTest(),
2292 boxShadow: !!propExists( "boxShadow" ) && !bb,
2293 fixedPosition: fixedPosition(),
2294 scrollTop: ("pageXOffset" in window ||
2295 "scrollTop" in document.documentElement ||
2296 "scrollTop" in fakeBody[ 0 ]) && !webos && !operamini,
2297
2298 cssPointerEvents: cssPointerEventsTest(),
2299 boundingRect: boundingRect(),
2300 inlineSVG: inlineSVG
2301});
2302
2303fakeBody.remove();
2304
2305// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian)
2306// or that generally work better browsing in regular http for full page refreshes (Opera Mini)
2307// Note: This detection below is used as a last resort.
2308// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible
2309nokiaLTE7_3 = (function() {
2310
2311 var ua = window.navigator.userAgent;
2312
2313 //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older
2314 return ua.indexOf( "Nokia" ) > -1 &&
2315 ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) &&
2316 ua.indexOf( "AppleWebKit" ) > -1 &&
2317 ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ );
2318})();
2319
2320// Support conditions that must be met in order to proceed
2321// default enhanced qualifications are media query support OR IE 7+
2322
2323$.mobile.gradeA = function() {
2324 return ( ( $.support.mediaquery && $.support.cssPseudoElement ) || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 8 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null );
2325};
2326
2327$.mobile.ajaxBlacklist =
2328 // BlackBerry browsers, pre-webkit
2329 window.blackberry && !window.WebKitPoint ||
2330 // Opera Mini
2331 operamini ||
2332 // Symbian webkits pre 7.3
2333 nokiaLTE7_3;
2334
2335// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices
2336// to render the stylesheets when they're referenced before this script, as we'd recommend doing.
2337// This simply reappends the CSS in place, which for some reason makes it apply
2338if ( nokiaLTE7_3 ) {
2339 $(function() {
2340 $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" );
2341 });
2342}
2343
2344// For ruling out shadows via css
2345if ( !$.support.boxShadow ) {
2346 $( "html" ).addClass( "ui-noboxshadow" );
2347}
2348
2349})( jQuery );
2350
2351
2352(function( $, undefined ) {
2353 var $win = $.mobile.window, self,
2354 dummyFnToInitNavigate = function() {
2355 };
2356
2357 $.event.special.beforenavigate = {
2358 setup: function() {
2359 $win.on( "navigate", dummyFnToInitNavigate );
2360 },
2361
2362 teardown: function() {
2363 $win.off( "navigate", dummyFnToInitNavigate );
2364 }
2365 };
2366
2367 $.event.special.navigate = self = {
2368 bound: false,
2369
2370 pushStateEnabled: true,
2371
2372 originalEventName: undefined,
2373
2374 // If pushstate support is present and push state support is defined to
2375 // be true on the mobile namespace.
2376 isPushStateEnabled: function() {
2377 return $.support.pushState &&
2378 $.mobile.pushStateEnabled === true &&
2379 this.isHashChangeEnabled();
2380 },
2381
2382 // !! assumes mobile namespace is present
2383 isHashChangeEnabled: function() {
2384 return $.mobile.hashListeningEnabled === true;
2385 },
2386
2387 // TODO a lot of duplication between popstate and hashchange
2388 popstate: function( event ) {
2389 var newEvent, beforeNavigate, state;
2390
2391 if ( event.isDefaultPrevented() ) {
2392 return;
2393 }
2394
2395 newEvent = new $.Event( "navigate" );
2396 beforeNavigate = new $.Event( "beforenavigate" );
2397 state = event.originalEvent.state || {};
2398
2399 beforeNavigate.originalEvent = event;
2400 $win.trigger( beforeNavigate );
2401
2402 if ( beforeNavigate.isDefaultPrevented() ) {
2403 return;
2404 }
2405
2406 if ( event.historyState ) {
2407 $.extend(state, event.historyState);
2408 }
2409
2410 // Make sure the original event is tracked for the end
2411 // user to inspect incase they want to do something special
2412 newEvent.originalEvent = event;
2413
2414 // NOTE we let the current stack unwind because any assignment to
2415 // location.hash will stop the world and run this event handler. By
2416 // doing this we create a similar behavior to hashchange on hash
2417 // assignment
2418 setTimeout(function() {
2419 $win.trigger( newEvent, {
2420 state: state
2421 });
2422 }, 0);
2423 },
2424
2425 hashchange: function( event /*, data */ ) {
2426 var newEvent = new $.Event( "navigate" ),
2427 beforeNavigate = new $.Event( "beforenavigate" );
2428
2429 beforeNavigate.originalEvent = event;
2430 $win.trigger( beforeNavigate );
2431
2432 if ( beforeNavigate.isDefaultPrevented() ) {
2433 return;
2434 }
2435
2436 // Make sure the original event is tracked for the end
2437 // user to inspect incase they want to do something special
2438 newEvent.originalEvent = event;
2439
2440 // Trigger the hashchange with state provided by the user
2441 // that altered the hash
2442 $win.trigger( newEvent, {
2443 // Users that want to fully normalize the two events
2444 // will need to do history management down the stack and
2445 // add the state to the event before this binding is fired
2446 // TODO consider allowing for the explicit addition of callbacks
2447 // to be fired before this value is set to avoid event timing issues
2448 state: event.hashchangeState || {}
2449 });
2450 },
2451
2452 // TODO We really only want to set this up once
2453 // but I'm not clear if there's a beter way to achieve
2454 // this with the jQuery special event structure
2455 setup: function( /* data, namespaces */ ) {
2456 if ( self.bound ) {
2457 return;
2458 }
2459
2460 self.bound = true;
2461
2462 if ( self.isPushStateEnabled() ) {
2463 self.originalEventName = "popstate";
2464 $win.bind( "popstate.navigate", self.popstate );
2465 } else if ( self.isHashChangeEnabled() ) {
2466 self.originalEventName = "hashchange";
2467 $win.bind( "hashchange.navigate", self.hashchange );
2468 }
2469 }
2470 };
2471})( jQuery );
2472
2473
2474// This plugin is an experiment for abstracting away the touch and mouse
2475// events so that developers don't have to worry about which method of input
2476// the device their document is loaded on supports.
2477//
2478// The idea here is to allow the developer to register listeners for the
2479// basic mouse events, such as mousedown, mousemove, mouseup, and click,
2480// and the plugin will take care of registering the correct listeners
2481// behind the scenes to invoke the listener at the fastest possible time
2482// for that device, while still retaining the order of event firing in
2483// the traditional mouse environment, should multiple handlers be registered
2484// on the same element for different events.
2485//
2486// The current version exposes the following virtual events to jQuery bind methods:
2487// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
2488
2489(function( $, window, document, undefined ) {
2490
2491var dataPropertyName = "virtualMouseBindings",
2492 touchTargetPropertyName = "virtualTouchID",
2493 virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
2494 touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
2495 mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
2496 mouseEventProps = $.event.props.concat( mouseHookProps ),
2497 activeDocHandlers = {},
2498 resetTimerID = 0,
2499 startX = 0,
2500 startY = 0,
2501 didScroll = false,
2502 clickBlockList = [],
2503 blockMouseTriggers = false,
2504 blockTouchTriggers = false,
2505 eventCaptureSupported = "addEventListener" in document,
2506 $document = $( document ),
2507 nextTouchID = 1,
2508 lastTouchID = 0, threshold,
2509 i;
2510
2511$.vmouse = {
2512 moveDistanceThreshold: 10,
2513 clickDistanceThreshold: 10,
2514 resetTimerDuration: 1500
2515};
2516
2517function getNativeEvent( event ) {
2518
2519 while ( event && typeof event.originalEvent !== "undefined" ) {
2520 event = event.originalEvent;
2521 }
2522 return event;
2523}
2524
2525function createVirtualEvent( event, eventType ) {
2526
2527 var t = event.type,
2528 oe, props, ne, prop, ct, touch, i, j, len;
2529
2530 event = $.Event( event );
2531 event.type = eventType;
2532
2533 oe = event.originalEvent;
2534 props = $.event.props;
2535
2536 // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
2537 // https://github.com/jquery/jquery-mobile/issues/3280
2538 if ( t.search( /^(mouse|click)/ ) > -1 ) {
2539 props = mouseEventProps;
2540 }
2541
2542 // copy original event properties over to the new event
2543 // this would happen if we could call $.event.fix instead of $.Event
2544 // but we don't have a way to force an event to be fixed multiple times
2545 if ( oe ) {
2546 for ( i = props.length, prop; i; ) {
2547 prop = props[ --i ];
2548 event[ prop ] = oe[ prop ];
2549 }
2550 }
2551
2552 // make sure that if the mouse and click virtual events are generated
2553 // without a .which one is defined
2554 if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) {
2555 event.which = 1;
2556 }
2557
2558 if ( t.search(/^touch/) !== -1 ) {
2559 ne = getNativeEvent( oe );
2560 t = ne.touches;
2561 ct = ne.changedTouches;
2562 touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined );
2563
2564 if ( touch ) {
2565 for ( j = 0, len = touchEventProps.length; j < len; j++) {
2566 prop = touchEventProps[ j ];
2567 event[ prop ] = touch[ prop ];
2568 }
2569 }
2570 }
2571
2572 return event;
2573}
2574
2575function getVirtualBindingFlags( element ) {
2576
2577 var flags = {},
2578 b, k;
2579
2580 while ( element ) {
2581
2582 b = $.data( element, dataPropertyName );
2583
2584 for ( k in b ) {
2585 if ( b[ k ] ) {
2586 flags[ k ] = flags.hasVirtualBinding = true;
2587 }
2588 }
2589 element = element.parentNode;
2590 }
2591 return flags;
2592}
2593
2594function getClosestElementWithVirtualBinding( element, eventType ) {
2595 var b;
2596 while ( element ) {
2597
2598 b = $.data( element, dataPropertyName );
2599
2600 if ( b && ( !eventType || b[ eventType ] ) ) {
2601 return element;
2602 }
2603 element = element.parentNode;
2604 }
2605 return null;
2606}
2607
2608function enableTouchBindings() {
2609 blockTouchTriggers = false;
2610}
2611
2612function disableTouchBindings() {
2613 blockTouchTriggers = true;
2614}
2615
2616function enableMouseBindings() {
2617 lastTouchID = 0;
2618 clickBlockList.length = 0;
2619 blockMouseTriggers = false;
2620
2621 // When mouse bindings are enabled, our
2622 // touch bindings are disabled.
2623 disableTouchBindings();
2624}
2625
2626function disableMouseBindings() {
2627 // When mouse bindings are disabled, our
2628 // touch bindings are enabled.
2629 enableTouchBindings();
2630}
2631
2632function startResetTimer() {
2633 clearResetTimer();
2634 resetTimerID = setTimeout( function() {
2635 resetTimerID = 0;
2636 enableMouseBindings();
2637 }, $.vmouse.resetTimerDuration );
2638}
2639
2640function clearResetTimer() {
2641 if ( resetTimerID ) {
2642 clearTimeout( resetTimerID );
2643 resetTimerID = 0;
2644 }
2645}
2646
2647function triggerVirtualEvent( eventType, event, flags ) {
2648 var ve;
2649
2650 if ( ( flags && flags[ eventType ] ) ||
2651 ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
2652
2653 ve = createVirtualEvent( event, eventType );
2654
2655 $( event.target).trigger( ve );
2656 }
2657
2658 return ve;
2659}
2660
2661function mouseEventCallback( event ) {
2662 var touchID = $.data( event.target, touchTargetPropertyName ),
2663 ve;
2664
2665 if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) {
2666 ve = triggerVirtualEvent( "v" + event.type, event );
2667 if ( ve ) {
2668 if ( ve.isDefaultPrevented() ) {
2669 event.preventDefault();
2670 }
2671 if ( ve.isPropagationStopped() ) {
2672 event.stopPropagation();
2673 }
2674 if ( ve.isImmediatePropagationStopped() ) {
2675 event.stopImmediatePropagation();
2676 }
2677 }
2678 }
2679}
2680
2681function handleTouchStart( event ) {
2682
2683 var touches = getNativeEvent( event ).touches,
2684 target, flags, t;
2685
2686 if ( touches && touches.length === 1 ) {
2687
2688 target = event.target;
2689 flags = getVirtualBindingFlags( target );
2690
2691 if ( flags.hasVirtualBinding ) {
2692
2693 lastTouchID = nextTouchID++;
2694 $.data( target, touchTargetPropertyName, lastTouchID );
2695
2696 clearResetTimer();
2697
2698 disableMouseBindings();
2699 didScroll = false;
2700
2701 t = getNativeEvent( event ).touches[ 0 ];
2702 startX = t.pageX;
2703 startY = t.pageY;
2704
2705 triggerVirtualEvent( "vmouseover", event, flags );
2706 triggerVirtualEvent( "vmousedown", event, flags );
2707 }
2708 }
2709}
2710
2711function handleScroll( event ) {
2712 if ( blockTouchTriggers ) {
2713 return;
2714 }
2715
2716 if ( !didScroll ) {
2717 triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
2718 }
2719
2720 didScroll = true;
2721 startResetTimer();
2722}
2723
2724function handleTouchMove( event ) {
2725 if ( blockTouchTriggers ) {
2726 return;
2727 }
2728
2729 var t = getNativeEvent( event ).touches[ 0 ],
2730 didCancel = didScroll,
2731 moveThreshold = $.vmouse.moveDistanceThreshold,
2732 flags = getVirtualBindingFlags( event.target );
2733
2734 didScroll = didScroll ||
2735 ( Math.abs( t.pageX - startX ) > moveThreshold ||
2736 Math.abs( t.pageY - startY ) > moveThreshold );
2737
2738 if ( didScroll && !didCancel ) {
2739 triggerVirtualEvent( "vmousecancel", event, flags );
2740 }
2741
2742 triggerVirtualEvent( "vmousemove", event, flags );
2743 startResetTimer();
2744}
2745
2746function handleTouchEnd( event ) {
2747 if ( blockTouchTriggers ) {
2748 return;
2749 }
2750
2751 disableTouchBindings();
2752
2753 var flags = getVirtualBindingFlags( event.target ),
2754 ve, t;
2755 triggerVirtualEvent( "vmouseup", event, flags );
2756
2757 if ( !didScroll ) {
2758 ve = triggerVirtualEvent( "vclick", event, flags );
2759 if ( ve && ve.isDefaultPrevented() ) {
2760 // The target of the mouse events that follow the touchend
2761 // event don't necessarily match the target used during the
2762 // touch. This means we need to rely on coordinates for blocking
2763 // any click that is generated.
2764 t = getNativeEvent( event ).changedTouches[ 0 ];
2765 clickBlockList.push({
2766 touchID: lastTouchID,
2767 x: t.clientX,
2768 y: t.clientY
2769 });
2770
2771 // Prevent any mouse events that follow from triggering
2772 // virtual event notifications.
2773 blockMouseTriggers = true;
2774 }
2775 }
2776 triggerVirtualEvent( "vmouseout", event, flags);
2777 didScroll = false;
2778
2779 startResetTimer();
2780}
2781
2782function hasVirtualBindings( ele ) {
2783 var bindings = $.data( ele, dataPropertyName ),
2784 k;
2785
2786 if ( bindings ) {
2787 for ( k in bindings ) {
2788 if ( bindings[ k ] ) {
2789 return true;
2790 }
2791 }
2792 }
2793 return false;
2794}
2795
2796function dummyMouseHandler() {}
2797
2798function getSpecialEventObject( eventType ) {
2799 var realType = eventType.substr( 1 );
2800
2801 return {
2802 setup: function(/* data, namespace */) {
2803 // If this is the first virtual mouse binding for this element,
2804 // add a bindings object to its data.
2805
2806 if ( !hasVirtualBindings( this ) ) {
2807 $.data( this, dataPropertyName, {} );
2808 }
2809
2810 // If setup is called, we know it is the first binding for this
2811 // eventType, so initialize the count for the eventType to zero.
2812 var bindings = $.data( this, dataPropertyName );
2813 bindings[ eventType ] = true;
2814
2815 // If this is the first virtual mouse event for this type,
2816 // register a global handler on the document.
2817
2818 activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;
2819
2820 if ( activeDocHandlers[ eventType ] === 1 ) {
2821 $document.bind( realType, mouseEventCallback );
2822 }
2823
2824 // Some browsers, like Opera Mini, won't dispatch mouse/click events
2825 // for elements unless they actually have handlers registered on them.
2826 // To get around this, we register dummy handlers on the elements.
2827
2828 $( this ).bind( realType, dummyMouseHandler );
2829
2830 // For now, if event capture is not supported, we rely on mouse handlers.
2831 if ( eventCaptureSupported ) {
2832 // If this is the first virtual mouse binding for the document,
2833 // register our touchstart handler on the document.
2834
2835 activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
2836
2837 if ( activeDocHandlers[ "touchstart" ] === 1 ) {
2838 $document.bind( "touchstart", handleTouchStart )
2839 .bind( "touchend", handleTouchEnd )
2840
2841 // On touch platforms, touching the screen and then dragging your finger
2842 // causes the window content to scroll after some distance threshold is
2843 // exceeded. On these platforms, a scroll prevents a click event from being
2844 // dispatched, and on some platforms, even the touchend is suppressed. To
2845 // mimic the suppression of the click event, we need to watch for a scroll
2846 // event. Unfortunately, some platforms like iOS don't dispatch scroll
2847 // events until *AFTER* the user lifts their finger (touchend). This means
2848 // we need to watch both scroll and touchmove events to figure out whether
2849 // or not a scroll happenens before the touchend event is fired.
2850
2851 .bind( "touchmove", handleTouchMove )
2852 .bind( "scroll", handleScroll );
2853 }
2854 }
2855 },
2856
2857 teardown: function(/* data, namespace */) {
2858 // If this is the last virtual binding for this eventType,
2859 // remove its global handler from the document.
2860
2861 --activeDocHandlers[ eventType ];
2862
2863 if ( !activeDocHandlers[ eventType ] ) {
2864 $document.unbind( realType, mouseEventCallback );
2865 }
2866
2867 if ( eventCaptureSupported ) {
2868 // If this is the last virtual mouse binding in existence,
2869 // remove our document touchstart listener.
2870
2871 --activeDocHandlers[ "touchstart" ];
2872
2873 if ( !activeDocHandlers[ "touchstart" ] ) {
2874 $document.unbind( "touchstart", handleTouchStart )
2875 .unbind( "touchmove", handleTouchMove )
2876 .unbind( "touchend", handleTouchEnd )
2877 .unbind( "scroll", handleScroll );
2878 }
2879 }
2880
2881 var $this = $( this ),
2882 bindings = $.data( this, dataPropertyName );
2883
2884 // teardown may be called when an element was
2885 // removed from the DOM. If this is the case,
2886 // jQuery core may have already stripped the element
2887 // of any data bindings so we need to check it before
2888 // using it.
2889 if ( bindings ) {
2890 bindings[ eventType ] = false;
2891 }
2892
2893 // Unregister the dummy event handler.
2894
2895 $this.unbind( realType, dummyMouseHandler );
2896
2897 // If this is the last virtual mouse binding on the
2898 // element, remove the binding data from the element.
2899
2900 if ( !hasVirtualBindings( this ) ) {
2901 $this.removeData( dataPropertyName );
2902 }
2903 }
2904 };
2905}
2906
2907// Expose our custom events to the jQuery bind/unbind mechanism.
2908
2909for ( i = 0; i < virtualEventNames.length; i++ ) {
2910 $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
2911}
2912
2913// Add a capture click handler to block clicks.
2914// Note that we require event capture support for this so if the device
2915// doesn't support it, we punt for now and rely solely on mouse events.
2916if ( eventCaptureSupported ) {
2917 document.addEventListener( "click", function( e ) {
2918 var cnt = clickBlockList.length,
2919 target = e.target,
2920 x, y, ele, i, o, touchID;
2921
2922 if ( cnt ) {
2923 x = e.clientX;
2924 y = e.clientY;
2925 threshold = $.vmouse.clickDistanceThreshold;
2926
2927 // The idea here is to run through the clickBlockList to see if
2928 // the current click event is in the proximity of one of our
2929 // vclick events that had preventDefault() called on it. If we find
2930 // one, then we block the click.
2931 //
2932 // Why do we have to rely on proximity?
2933 //
2934 // Because the target of the touch event that triggered the vclick
2935 // can be different from the target of the click event synthesized
2936 // by the browser. The target of a mouse/click event that is synthesized
2937 // from a touch event seems to be implementation specific. For example,
2938 // some browsers will fire mouse/click events for a link that is near
2939 // a touch event, even though the target of the touchstart/touchend event
2940 // says the user touched outside the link. Also, it seems that with most
2941 // browsers, the target of the mouse/click event is not calculated until the
2942 // time it is dispatched, so if you replace an element that you touched
2943 // with another element, the target of the mouse/click will be the new
2944 // element underneath that point.
2945 //
2946 // Aside from proximity, we also check to see if the target and any
2947 // of its ancestors were the ones that blocked a click. This is necessary
2948 // because of the strange mouse/click target calculation done in the
2949 // Android 2.1 browser, where if you click on an element, and there is a
2950 // mouse/click handler on one of its ancestors, the target will be the
2951 // innermost child of the touched element, even if that child is no where
2952 // near the point of touch.
2953
2954 ele = target;
2955
2956 while ( ele ) {
2957 for ( i = 0; i < cnt; i++ ) {
2958 o = clickBlockList[ i ];
2959 touchID = 0;
2960
2961 if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
2962 $.data( ele, touchTargetPropertyName ) === o.touchID ) {
2963 // XXX: We may want to consider removing matches from the block list
2964 // instead of waiting for the reset timer to fire.
2965 e.preventDefault();
2966 e.stopPropagation();
2967 return;
2968 }
2969 }
2970 ele = ele.parentNode;
2971 }
2972 }
2973 }, true);
2974}
2975})( jQuery, window, document );
2976
2977
2978(function( $, window, undefined ) {
2979 var $document = $( document ),
2980 supportTouch = $.mobile.support.touch,
2981 touchStartEvent = supportTouch ? "touchstart" : "mousedown",
2982 touchStopEvent = supportTouch ? "touchend" : "mouseup",
2983 touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
2984
2985 // setup new event shortcuts
2986 $.each( ( "touchstart touchmove touchend " +
2987 "tap taphold " +
2988 "swipe swipeleft swiperight" ).split( " " ), function( i, name ) {
2989
2990 $.fn[ name ] = function( fn ) {
2991 return fn ? this.bind( name, fn ) : this.trigger( name );
2992 };
2993
2994 // jQuery < 1.8
2995 if ( $.attrFn ) {
2996 $.attrFn[ name ] = true;
2997 }
2998 });
2999
3000 function triggerCustomEvent( obj, eventType, event, bubble ) {
3001 var originalType = event.type;
3002 event.type = eventType;
3003 if ( bubble ) {
3004 $.event.trigger( event, undefined, obj );
3005 } else {
3006 $.event.dispatch.call( obj, event );
3007 }
3008 event.type = originalType;
3009 }
3010
3011 // also handles taphold
3012 $.event.special.tap = {
3013 tapholdThreshold: 750,
3014 emitTapOnTaphold: true,
3015 setup: function() {
3016 var thisObject = this,
3017 $this = $( thisObject ),
3018 isTaphold = false;
3019
3020 $this.bind( "vmousedown", function( event ) {
3021 isTaphold = false;
3022 if ( event.which && event.which !== 1 ) {
3023 return false;
3024 }
3025
3026 var origTarget = event.target,
3027 timer;
3028
3029 function clearTapTimer() {
3030 clearTimeout( timer );
3031 }
3032
3033 function clearTapHandlers() {
3034 clearTapTimer();
3035
3036 $this.unbind( "vclick", clickHandler )
3037 .unbind( "vmouseup", clearTapTimer );
3038 $document.unbind( "vmousecancel", clearTapHandlers );
3039 }
3040
3041 function clickHandler( event ) {
3042 clearTapHandlers();
3043
3044 // ONLY trigger a 'tap' event if the start target is
3045 // the same as the stop target.
3046 if ( !isTaphold && origTarget === event.target ) {
3047 triggerCustomEvent( thisObject, "tap", event );
3048 } else if ( isTaphold ) {
3049 event.preventDefault();
3050 }
3051 }
3052
3053 $this.bind( "vmouseup", clearTapTimer )
3054 .bind( "vclick", clickHandler );
3055 $document.bind( "vmousecancel", clearTapHandlers );
3056
3057 timer = setTimeout( function() {
3058 if ( !$.event.special.tap.emitTapOnTaphold ) {
3059 isTaphold = true;
3060 }
3061 triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
3062 }, $.event.special.tap.tapholdThreshold );
3063 });
3064 },
3065 teardown: function() {
3066 $( this ).unbind( "vmousedown" ).unbind( "vclick" ).unbind( "vmouseup" );
3067 $document.unbind( "vmousecancel" );
3068 }
3069 };
3070
3071 // Also handles swipeleft, swiperight
3072 $.event.special.swipe = {
3073
3074 // More than this horizontal displacement, and we will suppress scrolling.
3075 scrollSupressionThreshold: 30,
3076
3077 // More time than this, and it isn't a swipe.
3078 durationThreshold: 1000,
3079
3080 // Swipe horizontal displacement must be more than this.
3081 horizontalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,
3082
3083 // Swipe vertical displacement must be less than this.
3084 verticalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,
3085
3086 getLocation: function ( event ) {
3087 var winPageX = window.pageXOffset,
3088 winPageY = window.pageYOffset,
3089 x = event.clientX,
3090 y = event.clientY;
3091
3092 if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) ||
3093 event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) {
3094
3095 // iOS4 clientX/clientY have the value that should have been
3096 // in pageX/pageY. While pageX/page/ have the value 0
3097 x = x - winPageX;
3098 y = y - winPageY;
3099 } else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) {
3100
3101 // Some Android browsers have totally bogus values for clientX/Y
3102 // when scrolling/zooming a page. Detectable since clientX/clientY
3103 // should never be smaller than pageX/pageY minus page scroll
3104 x = event.pageX - winPageX;
3105 y = event.pageY - winPageY;
3106 }
3107
3108 return {
3109 x: x,
3110 y: y
3111 };
3112 },
3113
3114 start: function( event ) {
3115 var data = event.originalEvent.touches ?
3116 event.originalEvent.touches[ 0 ] : event,
3117 location = $.event.special.swipe.getLocation( data );
3118 return {
3119 time: ( new Date() ).getTime(),
3120 coords: [ location.x, location.y ],
3121 origin: $( event.target )
3122 };
3123 },
3124
3125 stop: function( event ) {
3126 var data = event.originalEvent.touches ?
3127 event.originalEvent.touches[ 0 ] : event,
3128 location = $.event.special.swipe.getLocation( data );
3129 return {
3130 time: ( new Date() ).getTime(),
3131 coords: [ location.x, location.y ]
3132 };
3133 },
3134
3135 handleSwipe: function( start, stop, thisObject, origTarget ) {
3136 if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
3137 Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
3138 Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {
3139 var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight";
3140
3141 triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true );
3142 triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true );
3143 return true;
3144 }
3145 return false;
3146
3147 },
3148
3149 // This serves as a flag to ensure that at most one swipe event event is
3150 // in work at any given time
3151 eventInProgress: false,
3152
3153 setup: function() {
3154 var events,
3155 thisObject = this,
3156 $this = $( thisObject ),
3157 context = {};
3158
3159 // Retrieve the events data for this element and add the swipe context
3160 events = $.data( this, "mobile-events" );
3161 if ( !events ) {
3162 events = { length: 0 };
3163 $.data( this, "mobile-events", events );
3164 }
3165 events.length++;
3166 events.swipe = context;
3167
3168 context.start = function( event ) {
3169
3170 // Bail if we're already working on a swipe event
3171 if ( $.event.special.swipe.eventInProgress ) {
3172 return;
3173 }
3174 $.event.special.swipe.eventInProgress = true;
3175
3176 var stop,
3177 start = $.event.special.swipe.start( event ),
3178 origTarget = event.target,
3179 emitted = false;
3180
3181 context.move = function( event ) {
3182 if ( !start || event.isDefaultPrevented() ) {
3183 return;
3184 }
3185
3186 stop = $.event.special.swipe.stop( event );
3187 if ( !emitted ) {
3188 emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget );
3189 if ( emitted ) {
3190
3191 // Reset the context to make way for the next swipe event
3192 $.event.special.swipe.eventInProgress = false;
3193 }
3194 }
3195 // prevent scrolling
3196 if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
3197 event.preventDefault();
3198 }
3199 };
3200
3201 context.stop = function() {
3202 emitted = true;
3203
3204 // Reset the context to make way for the next swipe event
3205 $.event.special.swipe.eventInProgress = false;
3206 $document.off( touchMoveEvent, context.move );
3207 context.move = null;
3208 };
3209
3210 $document.on( touchMoveEvent, context.move )
3211 .one( touchStopEvent, context.stop );
3212 };
3213 $this.on( touchStartEvent, context.start );
3214 },
3215
3216 teardown: function() {
3217 var events, context;
3218
3219 events = $.data( this, "mobile-events" );
3220 if ( events ) {
3221 context = events.swipe;
3222 delete events.swipe;
3223 events.length--;
3224 if ( events.length === 0 ) {
3225 $.removeData( this, "mobile-events" );
3226 }
3227 }
3228
3229 if ( context ) {
3230 if ( context.start ) {
3231 $( this ).off( touchStartEvent, context.start );
3232 }
3233 if ( context.move ) {
3234 $document.off( touchMoveEvent, context.move );
3235 }
3236 if ( context.stop ) {
3237 $document.off( touchStopEvent, context.stop );
3238 }
3239 }
3240 }
3241 };
3242 $.each({
3243 taphold: "tap",
3244 swipeleft: "swipe.left",
3245 swiperight: "swipe.right"
3246 }, function( event, sourceEvent ) {
3247
3248 $.event.special[ event ] = {
3249 setup: function() {
3250 $( this ).bind( sourceEvent, $.noop );
3251 },
3252 teardown: function() {
3253 $( this ).unbind( sourceEvent );
3254 }
3255 };
3256 });
3257
3258})( jQuery, this );
3259
3260
3261(function( $, window, undefined ) {
3262 var scrollEvent = "touchmove scroll";
3263
3264 // setup new event shortcuts
3265 $.each( [ "scrollstart", "scrollstop" ], function( i, name ) {
3266
3267 $.fn[ name ] = function( fn ) {
3268 return fn ? this.bind( name, fn ) : this.trigger( name );
3269 };
3270
3271 // jQuery < 1.8
3272 if ( $.attrFn ) {
3273 $.attrFn[ name ] = true;
3274 }
3275 });
3276
3277 // also handles scrollstop
3278 $.event.special.scrollstart = {
3279
3280 enabled: true,
3281 setup: function() {
3282
3283 var thisObject = this,
3284 $this = $( thisObject ),
3285 scrolling,
3286 timer;
3287
3288 function trigger( event, state ) {
3289 var originalEventType = event.type;
3290
3291 scrolling = state;
3292
3293 event.type = scrolling ? "scrollstart" : "scrollstop";
3294 $.event.dispatch.call( thisObject, event );
3295 event.type = originalEventType;
3296 }
3297
3298 // iPhone triggers scroll after a small delay; use touchmove instead
3299 $this.bind( scrollEvent, function( event ) {
3300
3301 if ( !$.event.special.scrollstart.enabled ) {
3302 return;
3303 }
3304
3305 if ( !scrolling ) {
3306 trigger( event, true );
3307 }
3308
3309 clearTimeout( timer );
3310 timer = setTimeout( function() {
3311 trigger( event, false );
3312 }, 50 );
3313 });
3314 },
3315 teardown: function() {
3316 $( this ).unbind( scrollEvent );
3317 }
3318 };
3319
3320 $.each({
3321 scrollstop: "scrollstart"
3322 }, function( event, sourceEvent ) {
3323
3324 $.event.special[ event ] = {
3325 setup: function() {
3326 $( this ).bind( sourceEvent, $.noop );
3327 },
3328 teardown: function() {
3329 $( this ).unbind( sourceEvent );
3330 }
3331 };
3332 });
3333
3334})( jQuery, this );
3335
3336
3337 // throttled resize event
3338 (function( $ ) {
3339 $.event.special.throttledresize = {
3340 setup: function() {
3341 $( this ).bind( "resize", handler );
3342 },
3343 teardown: function() {
3344 $( this ).unbind( "resize", handler );
3345 }
3346 };
3347
3348 var throttle = 250,
3349 handler = function() {
3350 curr = ( new Date() ).getTime();
3351 diff = curr - lastCall;
3352
3353 if ( diff >= throttle ) {
3354
3355 lastCall = curr;
3356 $( this ).trigger( "throttledresize" );
3357
3358 } else {
3359
3360 if ( heldCall ) {
3361 clearTimeout( heldCall );
3362 }
3363
3364 // Promise a held call will still execute
3365 heldCall = setTimeout( handler, throttle - diff );
3366 }
3367 },
3368 lastCall = 0,
3369 heldCall,
3370 curr,
3371 diff;
3372 })( jQuery );
3373
3374
3375(function( $, window ) {
3376 var win = $( window ),
3377 event_name = "orientationchange",
3378 get_orientation,
3379 last_orientation,
3380 initial_orientation_is_landscape,
3381 initial_orientation_is_default,
3382 portrait_map = { "0": true, "180": true },
3383 ww, wh, landscape_threshold;
3384
3385 // It seems that some device/browser vendors use window.orientation values 0 and 180 to
3386 // denote the "default" orientation. For iOS devices, and most other smart-phones tested,
3387 // the default orientation is always "portrait", but in some Android and RIM based tablets,
3388 // the default orientation is "landscape". The following code attempts to use the window
3389 // dimensions to figure out what the current orientation is, and then makes adjustments
3390 // to the to the portrait_map if necessary, so that we can properly decode the
3391 // window.orientation value whenever get_orientation() is called.
3392 //
3393 // Note that we used to use a media query to figure out what the orientation the browser
3394 // thinks it is in:
3395 //
3396 // initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
3397 //
3398 // but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
3399 // where the browser *ALWAYS* applied the landscape media query. This bug does not
3400 // happen on iPad.
3401
3402 if ( $.support.orientation ) {
3403
3404 // Check the window width and height to figure out what the current orientation
3405 // of the device is at this moment. Note that we've initialized the portrait map
3406 // values to 0 and 180, *AND* we purposely check for landscape so that if we guess
3407 // wrong, , we default to the assumption that portrait is the default orientation.
3408 // We use a threshold check below because on some platforms like iOS, the iPhone
3409 // form-factor can report a larger width than height if the user turns on the
3410 // developer console. The actual threshold value is somewhat arbitrary, we just
3411 // need to make sure it is large enough to exclude the developer console case.
3412
3413 ww = window.innerWidth || win.width();
3414 wh = window.innerHeight || win.height();
3415 landscape_threshold = 50;
3416
3417 initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold;
3418
3419 // Now check to see if the current window.orientation is 0 or 180.
3420 initial_orientation_is_default = portrait_map[ window.orientation ];
3421
3422 // If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
3423 // if the initial orientation is portrait, but window.orientation reports 90 or -90, we
3424 // need to flip our portrait_map values because landscape is the default orientation for
3425 // this device/browser.
3426 if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) {
3427 portrait_map = { "-90": true, "90": true };
3428 }
3429 }
3430
3431 $.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, {
3432 setup: function() {
3433 // If the event is supported natively, return false so that jQuery
3434 // will bind to the event using DOM methods.
3435 if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
3436 return false;
3437 }
3438
3439 // Get the current orientation to avoid initial double-triggering.
3440 last_orientation = get_orientation();
3441
3442 // Because the orientationchange event doesn't exist, simulate the
3443 // event by testing window dimensions on resize.
3444 win.bind( "throttledresize", handler );
3445 },
3446 teardown: function() {
3447 // If the event is not supported natively, return false so that
3448 // jQuery will unbind the event using DOM methods.
3449 if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
3450 return false;
3451 }
3452
3453 // Because the orientationchange event doesn't exist, unbind the
3454 // resize event handler.
3455 win.unbind( "throttledresize", handler );
3456 },
3457 add: function( handleObj ) {
3458 // Save a reference to the bound event handler.
3459 var old_handler = handleObj.handler;
3460
3461 handleObj.handler = function( event ) {
3462 // Modify event object, adding the .orientation property.
3463 event.orientation = get_orientation();
3464
3465 // Call the originally-bound event handler and return its result.
3466 return old_handler.apply( this, arguments );
3467 };
3468 }
3469 });
3470
3471 // If the event is not supported natively, this handler will be bound to
3472 // the window resize event to simulate the orientationchange event.
3473 function handler() {
3474 // Get the current orientation.
3475 var orientation = get_orientation();
3476
3477 if ( orientation !== last_orientation ) {
3478 // The orientation has changed, so trigger the orientationchange event.
3479 last_orientation = orientation;
3480 win.trigger( event_name );
3481 }
3482 }
3483
3484 // Get the current page orientation. This method is exposed publicly, should it
3485 // be needed, as jQuery.event.special.orientationchange.orientation()
3486 $.event.special.orientationchange.orientation = get_orientation = function() {
3487 var isPortrait = true, elem = document.documentElement;
3488
3489 // prefer window orientation to the calculation based on screensize as
3490 // the actual screen resize takes place before or after the orientation change event
3491 // has been fired depending on implementation (eg android 2.3 is before, iphone after).
3492 // More testing is required to determine if a more reliable method of determining the new screensize
3493 // is possible when orientationchange is fired. (eg, use media queries + element + opacity)
3494 if ( $.support.orientation ) {
3495 // if the window orientation registers as 0 or 180 degrees report
3496 // portrait, otherwise landscape
3497 isPortrait = portrait_map[ window.orientation ];
3498 } else {
3499 isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1;
3500 }
3501
3502 return isPortrait ? "portrait" : "landscape";
3503 };
3504
3505 $.fn[ event_name ] = function( fn ) {
3506 return fn ? this.bind( event_name, fn ) : this.trigger( event_name );
3507 };
3508
3509 // jQuery < 1.8
3510 if ( $.attrFn ) {
3511 $.attrFn[ event_name ] = true;
3512 }
3513
3514}( jQuery, this ));
3515
3516
3517
3518
3519(function( $, undefined ) {
3520 $.mobile.History = function( stack, index ) {
3521 this.stack = stack || [];
3522 this.activeIndex = index || 0;
3523 };
3524
3525 $.extend($.mobile.History.prototype, {
3526 getActive: function() {
3527 return this.stack[ this.activeIndex ];
3528 },
3529
3530 getLast: function() {
3531 return this.stack[ this.previousIndex ];
3532 },
3533
3534 getNext: function() {
3535 return this.stack[ this.activeIndex + 1 ];
3536 },
3537
3538 getPrev: function() {
3539 return this.stack[ this.activeIndex - 1 ];
3540 },
3541
3542 // addNew is used whenever a new page is added
3543 add: function( url, data ) {
3544 data = data || {};
3545
3546 //if there's forward history, wipe it
3547 if ( this.getNext() ) {
3548 this.clearForward();
3549 }
3550
3551 // if the hash is included in the data make sure the shape
3552 // is consistent for comparison
3553 if ( data.hash && data.hash.indexOf( "#" ) === -1) {
3554 data.hash = "#" + data.hash;
3555 }
3556
3557 data.url = url;
3558 this.stack.push( data );
3559 this.activeIndex = this.stack.length - 1;
3560 },
3561
3562 //wipe urls ahead of active index
3563 clearForward: function() {
3564 this.stack = this.stack.slice( 0, this.activeIndex + 1 );
3565 },
3566
3567 find: function( url, stack, earlyReturn ) {
3568 stack = stack || this.stack;
3569
3570 var entry, i, length = stack.length, index;
3571
3572 for ( i = 0; i < length; i++ ) {
3573 entry = stack[i];
3574
3575 if ( decodeURIComponent(url) === decodeURIComponent(entry.url) ||
3576 decodeURIComponent(url) === decodeURIComponent(entry.hash) ) {
3577 index = i;
3578
3579 if ( earlyReturn ) {
3580 return index;
3581 }
3582 }
3583 }
3584
3585 return index;
3586 },
3587
3588 _findById: function( id ) {
3589 var stackIndex,
3590 stackLength = this.stack.length;
3591
3592 for ( stackIndex = 0 ; stackIndex < stackLength ; stackIndex++ ) {
3593 if ( this.stack[ stackIndex ].id === id ) {
3594 break;
3595 }
3596 }
3597
3598 return ( stackIndex < stackLength ? stackIndex : undefined );
3599 },
3600
3601 closest: function( url, id ) {
3602 var closest = ( id === undefined ? undefined : this._findById( id ) ),
3603 a = this.activeIndex;
3604
3605 // First, we check whether we've found an entry by id. If so, we're done.
3606 if ( closest !== undefined ) {
3607 return closest;
3608 }
3609
3610 // Failing that take the slice of the history stack before the current index and search
3611 // for a url match. If one is found, we'll avoid avoid looking through forward history
3612 // NOTE the preference for backward history movement is driven by the fact that
3613 // most mobile browsers only have a dedicated back button, and users rarely use
3614 // the forward button in desktop browser anyhow
3615 closest = this.find( url, this.stack.slice(0, a) );
3616
3617 // If nothing was found in backward history check forward. The `true`
3618 // value passed as the third parameter causes the find method to break
3619 // on the first match in the forward history slice. The starting index
3620 // of the slice must then be added to the result to get the element index
3621 // in the original history stack :( :(
3622 //
3623 // TODO this is hyper confusing and should be cleaned up (ugh so bad)
3624 if ( closest === undefined ) {
3625 closest = this.find( url, this.stack.slice(a), true );
3626 closest = closest === undefined ? closest : closest + a;
3627 }
3628
3629 return closest;
3630 },
3631
3632 direct: function( opts ) {
3633 var newActiveIndex = this.closest( opts.url, opts.id ), a = this.activeIndex;
3634
3635 // save new page index, null check to prevent falsey 0 result
3636 // record the previous index for reference
3637 if ( newActiveIndex !== undefined ) {
3638 this.activeIndex = newActiveIndex;
3639 this.previousIndex = a;
3640 }
3641
3642 // invoke callbacks where appropriate
3643 //
3644 // TODO this is also convoluted and confusing
3645 if ( newActiveIndex < a ) {
3646 ( opts.present || opts.back || $.noop )( this.getActive(), "back" );
3647 } else if ( newActiveIndex > a ) {
3648 ( opts.present || opts.forward || $.noop )( this.getActive(), "forward" );
3649 } else if ( newActiveIndex === undefined && opts.missing ) {
3650 opts.missing( this.getActive() );
3651 }
3652 }
3653 });
3654})( jQuery );
3655
3656
3657
3658(function( $, undefined ) {
3659 var path = $.mobile.path,
3660 initialHref = location.href;
3661
3662 $.mobile.Navigator = function( history ) {
3663 this.history = history;
3664 this.ignoreInitialHashChange = true;
3665
3666 $.mobile.window.bind({
3667 "popstate.history": $.proxy( this.popstate, this ),
3668 "hashchange.history": $.proxy( this.hashchange, this )
3669 });
3670 };
3671
3672 $.extend($.mobile.Navigator.prototype, {
3673 historyEntryId: 0,
3674 squash: function( url, data ) {
3675 var state, href, hash = path.isPath(url) ? path.stripHash(url) : url;
3676
3677 href = path.squash( url );
3678
3679 // make sure to provide this information when it isn't explicitly set in the
3680 // data object that was passed to the squash method
3681 state = $.extend({
3682 id: ++this.historyEntryId,
3683 hash: hash,
3684 url: href
3685 }, data);
3686
3687 // replace the current url with the new href and store the state
3688 // Note that in some cases we might be replacing an url with the
3689 // same url. We do this anyways because we need to make sure that
3690 // all of our history entries have a state object associated with
3691 // them. This allows us to work around the case where $.mobile.back()
3692 // is called to transition from an external page to an embedded page.
3693 // In that particular case, a hashchange event is *NOT* generated by the browser.
3694 // Ensuring each history entry has a state object means that onPopState()
3695 // will always trigger our hashchange callback even when a hashchange event
3696 // is not fired.
3697 window.history.replaceState( state, state.title || document.title, href );
3698
3699 // If we haven't yet received the initial popstate, we need to update the reference
3700 // href so that we compare against the correct location
3701 if ( this.ignoreInitialHashChange ) {
3702 initialHref = href;
3703 }
3704
3705 return state;
3706 },
3707
3708 hash: function( url, href ) {
3709 var parsed, loc, hash, resolved;
3710
3711 // Grab the hash for recording. If the passed url is a path
3712 // we used the parsed version of the squashed url to reconstruct,
3713 // otherwise we assume it's a hash and store it directly
3714 parsed = path.parseUrl( url );
3715 loc = path.parseLocation();
3716
3717 if ( loc.pathname + loc.search === parsed.pathname + parsed.search ) {
3718 // If the pathname and search of the passed url is identical to the current loc
3719 // then we must use the hash. Otherwise there will be no event
3720 // eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz"
3721 hash = parsed.hash ? parsed.hash : parsed.pathname + parsed.search;
3722 } else if ( path.isPath(url) ) {
3723 resolved = path.parseUrl( href );
3724 // If the passed url is a path, make it domain relative and remove any trailing hash
3725 hash = resolved.pathname + resolved.search + (path.isPreservableHash( resolved.hash )? resolved.hash.replace( "#", "" ) : "");
3726 } else {
3727 hash = url;
3728 }
3729
3730 return hash;
3731 },
3732
3733 // TODO reconsider name
3734 go: function( url, data, noEvents ) {
3735 var state, href, hash, popstateEvent,
3736 isPopStateEvent = $.event.special.navigate.isPushStateEnabled();
3737
3738 // Get the url as it would look squashed on to the current resolution url
3739 href = path.squash( url );
3740
3741 // sort out what the hash sould be from the url
3742 hash = this.hash( url, href );
3743
3744 // Here we prevent the next hash change or popstate event from doing any
3745 // history management. In the case of hashchange we don't swallow it
3746 // if there will be no hashchange fired (since that won't reset the value)
3747 // and will swallow the following hashchange
3748 if ( noEvents && hash !== path.stripHash(path.parseLocation().hash) ) {
3749 this.preventNextHashChange = noEvents;
3750 }
3751
3752 // IMPORTANT in the case where popstate is supported the event will be triggered
3753 // directly, stopping further execution - ie, interupting the flow of this
3754 // method call to fire bindings at this expression. Below the navigate method
3755 // there is a binding to catch this event and stop its propagation.
3756 //
3757 // We then trigger a new popstate event on the window with a null state
3758 // so that the navigate events can conclude their work properly
3759 //
3760 // if the url is a path we want to preserve the query params that are available on
3761 // the current url.
3762 this.preventHashAssignPopState = true;
3763 window.location.hash = hash;
3764
3765 // If popstate is enabled and the browser triggers `popstate` events when the hash
3766 // is set (this often happens immediately in browsers like Chrome), then the
3767 // this flag will be set to false already. If it's a browser that does not trigger
3768 // a `popstate` on hash assignement or `replaceState` then we need avoid the branch
3769 // that swallows the event created by the popstate generated by the hash assignment
3770 // At the time of this writing this happens with Opera 12 and some version of IE
3771 this.preventHashAssignPopState = false;
3772
3773 state = $.extend({
3774 url: href,
3775 hash: hash,
3776 title: document.title
3777 }, data);
3778
3779 if ( isPopStateEvent ) {
3780 popstateEvent = new $.Event( "popstate" );
3781 popstateEvent.originalEvent = {
3782 type: "popstate",
3783 state: null
3784 };
3785
3786 state.id = ( this.squash( url, state ) || {} ).id;
3787
3788 // Trigger a new faux popstate event to replace the one that we
3789 // caught that was triggered by the hash setting above.
3790 if ( !noEvents ) {
3791 this.ignorePopState = true;
3792 $.mobile.window.trigger( popstateEvent );
3793 }
3794 }
3795
3796 // record the history entry so that the information can be included
3797 // in hashchange event driven navigate events in a similar fashion to
3798 // the state that's provided by popstate
3799 this.history.add( state.url, state );
3800 },
3801
3802 // This binding is intended to catch the popstate events that are fired
3803 // when execution of the `$.navigate` method stops at window.location.hash = url;
3804 // and completely prevent them from propagating. The popstate event will then be
3805 // retriggered after execution resumes
3806 //
3807 // TODO grab the original event here and use it for the synthetic event in the
3808 // second half of the navigate execution that will follow this binding
3809 popstate: function( event ) {
3810 var hash, state;
3811
3812 // Partly to support our test suite which manually alters the support
3813 // value to test hashchange. Partly to prevent all around weirdness
3814 if ( !$.event.special.navigate.isPushStateEnabled() ) {
3815 return;
3816 }
3817
3818 // If this is the popstate triggered by the actual alteration of the hash
3819 // prevent it completely. History is tracked manually
3820 if ( this.preventHashAssignPopState ) {
3821 this.preventHashAssignPopState = false;
3822 event.stopImmediatePropagation();
3823 return;
3824 }
3825
3826 // if this is the popstate triggered after the `replaceState` call in the go
3827 // method, then simply ignore it. The history entry has already been captured
3828 if ( this.ignorePopState ) {
3829 this.ignorePopState = false;
3830 return;
3831 }
3832
3833 // If there is no state, and the history stack length is one were
3834 // probably getting the page load popstate fired by browsers like chrome
3835 // avoid it and set the one time flag to false.
3836 // TODO: Do we really need all these conditions? Comparing location hrefs
3837 // should be sufficient.
3838 if ( !event.originalEvent.state &&
3839 this.history.stack.length === 1 &&
3840 this.ignoreInitialHashChange ) {
3841 this.ignoreInitialHashChange = false;
3842
3843 if ( location.href === initialHref ) {
3844 event.preventDefault();
3845 return;
3846 }
3847 }
3848
3849 // account for direct manipulation of the hash. That is, we will receive a popstate
3850 // when the hash is changed by assignment, and it won't have a state associated. We
3851 // then need to squash the hash. See below for handling of hash assignment that
3852 // matches an existing history entry
3853 // TODO it might be better to only add to the history stack
3854 // when the hash is adjacent to the active history entry
3855 hash = path.parseLocation().hash;
3856 if ( !event.originalEvent.state && hash ) {
3857 // squash the hash that's been assigned on the URL with replaceState
3858 // also grab the resulting state object for storage
3859 state = this.squash( hash );
3860
3861 // record the new hash as an additional history entry
3862 // to match the browser's treatment of hash assignment
3863 this.history.add( state.url, state );
3864
3865 // pass the newly created state information
3866 // along with the event
3867 event.historyState = state;
3868
3869 // do not alter history, we've added a new history entry
3870 // so we know where we are
3871 return;
3872 }
3873
3874 // If all else fails this is a popstate that comes from the back or forward buttons
3875 // make sure to set the state of our history stack properly, and record the directionality
3876 this.history.direct({
3877 id: ( event.originalEvent.state || {} ).id,
3878 url: (event.originalEvent.state || {}).url || hash,
3879
3880 // When the url is either forward or backward in history include the entry
3881 // as data on the event object for merging as data in the navigate event
3882 present: function( historyEntry, direction ) {
3883 // make sure to create a new object to pass down as the navigate event data
3884 event.historyState = $.extend({}, historyEntry);
3885 event.historyState.direction = direction;
3886 }
3887 });
3888 },
3889
3890 // NOTE must bind before `navigate` special event hashchange binding otherwise the
3891 // navigation data won't be attached to the hashchange event in time for those
3892 // bindings to attach it to the `navigate` special event
3893 // TODO add a check here that `hashchange.navigate` is bound already otherwise it's
3894 // broken (exception?)
3895 hashchange: function( event ) {
3896 var history, hash;
3897
3898 // If hashchange listening is explicitly disabled or pushstate is supported
3899 // avoid making use of the hashchange handler.
3900 if (!$.event.special.navigate.isHashChangeEnabled() ||
3901 $.event.special.navigate.isPushStateEnabled() ) {
3902 return;
3903 }
3904
3905 // On occasion explicitly want to prevent the next hash from propagating because we only
3906 // with to alter the url to represent the new state do so here
3907 if ( this.preventNextHashChange ) {
3908 this.preventNextHashChange = false;
3909 event.stopImmediatePropagation();
3910 return;
3911 }
3912
3913 history = this.history;
3914 hash = path.parseLocation().hash;
3915
3916 // If this is a hashchange caused by the back or forward button
3917 // make sure to set the state of our history stack properly
3918 this.history.direct({
3919 url: hash,
3920
3921 // When the url is either forward or backward in history include the entry
3922 // as data on the event object for merging as data in the navigate event
3923 present: function( historyEntry, direction ) {
3924 // make sure to create a new object to pass down as the navigate event data
3925 event.hashchangeState = $.extend({}, historyEntry);
3926 event.hashchangeState.direction = direction;
3927 },
3928
3929 // When we don't find a hash in our history clearly we're aiming to go there
3930 // record the entry as new for future traversal
3931 //
3932 // NOTE it's not entirely clear that this is the right thing to do given that we
3933 // can't know the users intention. It might be better to explicitly _not_
3934 // support location.hash assignment in preference to $.navigate calls
3935 // TODO first arg to add should be the href, but it causes issues in identifying
3936 // embedded pages
3937 missing: function() {
3938 history.add( hash, {
3939 hash: hash,
3940 title: document.title
3941 });
3942 }
3943 });
3944 }
3945 });
3946})( jQuery );
3947
3948
3949
3950(function( $, undefined ) {
3951 // TODO consider queueing navigation activity until previous activities have completed
3952 // so that end users don't have to think about it. Punting for now
3953 // TODO !! move the event bindings into callbacks on the navigate event
3954 $.mobile.navigate = function( url, data, noEvents ) {
3955 $.mobile.navigate.navigator.go( url, data, noEvents );
3956 };
3957
3958 // expose the history on the navigate method in anticipation of full integration with
3959 // existing navigation functionalty that is tightly coupled to the history information
3960 $.mobile.navigate.history = new $.mobile.History();
3961
3962 // instantiate an instance of the navigator for use within the $.navigate method
3963 $.mobile.navigate.navigator = new $.mobile.Navigator( $.mobile.navigate.history );
3964
3965 var loc = $.mobile.path.parseLocation();
3966 $.mobile.navigate.history.add( loc.href, {hash: loc.hash} );
3967})( jQuery );
3968
3969
3970(function( $, undefined ) {
3971 var props = {
3972 "animation": {},
3973 "transition": {}
3974 },
3975 testElement = document.createElement( "a" ),
3976 vendorPrefixes = [ "", "webkit-", "moz-", "o-" ];
3977
3978 $.each( [ "animation", "transition" ], function( i, test ) {
3979
3980 // Get correct name for test
3981 var testName = ( i === 0 ) ? test + "-" + "name" : test;
3982
3983 $.each( vendorPrefixes, function( j, prefix ) {
3984 if ( testElement.style[ $.camelCase( prefix + testName ) ] !== undefined ) {
3985 props[ test ][ "prefix" ] = prefix;
3986 return false;
3987 }
3988 });
3989
3990 // Set event and duration names for later use
3991 props[ test ][ "duration" ] =
3992 $.camelCase( props[ test ][ "prefix" ] + test + "-" + "duration" );
3993 props[ test ][ "event" ] =
3994 $.camelCase( props[ test ][ "prefix" ] + test + "-" + "end" );
3995
3996 // All lower case if not a vendor prop
3997 if ( props[ test ][ "prefix" ] === "" ) {
3998 props[ test ][ "event" ] = props[ test ][ "event" ].toLowerCase();
3999 }
4000 });
4001
4002 // If a valid prefix was found then the it is supported by the browser
4003 $.support.cssTransitions = ( props[ "transition" ][ "prefix" ] !== undefined );
4004 $.support.cssAnimations = ( props[ "animation" ][ "prefix" ] !== undefined );
4005
4006 // Remove the testElement
4007 $( testElement ).remove();
4008
4009 // Animation complete callback
4010 $.fn.animationComplete = function( callback, type, fallbackTime ) {
4011 var timer, duration,
4012 that = this,
4013 eventBinding = function() {
4014
4015 // Clear the timer so we don't call callback twice
4016 clearTimeout( timer );
4017 callback.apply( this, arguments );
4018 },
4019 animationType = ( !type || type === "animation" ) ? "animation" : "transition";
4020
4021 if ( !this.length ) {
4022 return this;
4023 }
4024
4025 // Make sure selected type is supported by browser
4026 if ( ( $.support.cssTransitions && animationType === "transition" ) ||
4027 ( $.support.cssAnimations && animationType === "animation" ) ) {
4028
4029 // If a fallback time was not passed set one
4030 if ( fallbackTime === undefined ) {
4031
4032 // Make sure the was not bound to document before checking .css
4033 if ( this.context !== document ) {
4034
4035 // Parse the durration since its in second multiple by 1000 for milliseconds
4036 // Multiply by 3 to make sure we give the animation plenty of time.
4037 duration = parseFloat(
4038 this.css( props[ animationType ].duration )
4039 ) * 3000;
4040 }
4041
4042 // If we could not read a duration use the default
4043 if ( duration === 0 || duration === undefined || isNaN( duration ) ) {
4044 duration = $.fn.animationComplete.defaultDuration;
4045 }
4046 }
4047
4048 // Sets up the fallback if event never comes
4049 timer = setTimeout( function() {
4050 that
4051 .off( props[ animationType ].event, eventBinding )
4052 .each( function() {
4053 callback.apply( this );
4054 });
4055 }, duration );
4056
4057 // Bind the event
4058 return this.one( props[ animationType ].event, eventBinding );
4059 } else {
4060
4061 // CSS animation / transitions not supported
4062 // Defer execution for consistency between webkit/non webkit
4063 setTimeout( function() {
4064 that.each( function() {
4065 callback.apply( this );
4066 });
4067 }, 0 );
4068 return this;
4069 }
4070 };
4071
4072 // Allow default callback to be configured on mobileInit
4073 $.fn.animationComplete.defaultDuration = 1000;
4074})( jQuery );
4075
4076
4077(function( $, window, undefined ) {
4078
4079 // TODO remove direct references to $.mobile and properties, we should
4080 // favor injection with params to the constructor
4081 $.mobile.Transition = function() {
4082 this.init.apply( this, arguments );
4083 };
4084
4085 $.extend($.mobile.Transition.prototype, {
4086 toPreClass: " ui-page-pre-in",
4087
4088 init: function( name, reverse, $to, $from ) {
4089 $.extend(this, {
4090 name: name,
4091 reverse: reverse,
4092 $to: $to,
4093 $from: $from,
4094 deferred: new $.Deferred()
4095 });
4096 },
4097
4098 cleanFrom: function() {
4099 this.$from
4100 .removeClass( $.mobile.activePageClass + " out in reverse " + this.name )
4101 .height( "" );
4102 },
4103
4104 // NOTE overridden by child object prototypes, noop'd here as defaults
4105 beforeDoneIn: function() {},
4106 beforeDoneOut: function() {},
4107 beforeStartOut: function() {},
4108
4109 doneIn: function() {
4110 this.beforeDoneIn();
4111
4112 this.$to.removeClass( "out in reverse " + this.name ).height( "" );
4113
4114 this.toggleViewportClass();
4115
4116 // In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition
4117 // This ensures we jump to that spot after the fact, if we aren't there already.
4118 if ( $.mobile.window.scrollTop() !== this.toScroll ) {
4119 this.scrollPage();
4120 }
4121 if ( !this.sequential ) {
4122 this.$to.addClass( $.mobile.activePageClass );
4123 }
4124 this.deferred.resolve( this.name, this.reverse, this.$to, this.$from, true );
4125 },
4126
4127 doneOut: function( screenHeight, reverseClass, none, preventFocus ) {
4128 this.beforeDoneOut();
4129 this.startIn( screenHeight, reverseClass, none, preventFocus );
4130 },
4131
4132 hideIn: function( callback ) {
4133 // Prevent flickering in phonegap container: see comments at #4024 regarding iOS
4134 this.$to.css( "z-index", -10 );
4135 callback.call( this );
4136 this.$to.css( "z-index", "" );
4137 },
4138
4139 scrollPage: function() {
4140 // By using scrollTo instead of silentScroll, we can keep things better in order
4141 // Just to be precautios, disable scrollstart listening like silentScroll would
4142 $.event.special.scrollstart.enabled = false;
4143 //if we are hiding the url bar or the page was previously scrolled scroll to hide or return to position
4144 if ( $.mobile.hideUrlBar || this.toScroll !== $.mobile.defaultHomeScroll ) {
4145 window.scrollTo( 0, this.toScroll );
4146 }
4147
4148 // reenable scrollstart listening like silentScroll would
4149 setTimeout( function() {
4150 $.event.special.scrollstart.enabled = true;
4151 }, 150 );
4152 },
4153
4154 startIn: function( screenHeight, reverseClass, none, preventFocus ) {
4155 this.hideIn(function() {
4156 this.$to.addClass( $.mobile.activePageClass + this.toPreClass );
4157
4158 // Send focus to page as it is now display: block
4159 if ( !preventFocus ) {
4160 $.mobile.focusPage( this.$to );
4161 }
4162
4163 // Set to page height
4164 this.$to.height( screenHeight + this.toScroll );
4165
4166 if ( !none ) {
4167 this.scrollPage();
4168 }
4169 });
4170
4171 this.$to
4172 .removeClass( this.toPreClass )
4173 .addClass( this.name + " in " + reverseClass );
4174
4175 if ( !none ) {
4176 this.$to.animationComplete( $.proxy(function() {
4177 this.doneIn();
4178 }, this ));
4179 } else {
4180 this.doneIn();
4181 }
4182
4183 },
4184
4185 startOut: function( screenHeight, reverseClass, none ) {
4186 this.beforeStartOut( screenHeight, reverseClass, none );
4187
4188 // Set the from page's height and start it transitioning out
4189 // Note: setting an explicit height helps eliminate tiling in the transitions
4190 this.$from
4191 .height( screenHeight + $.mobile.window.scrollTop() )
4192 .addClass( this.name + " out" + reverseClass );
4193 },
4194
4195 toggleViewportClass: function() {
4196 $.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + this.name );
4197 },
4198
4199 transition: function( toScroll ) {
4200 // NOTE many of these could be calculated/recorded in the constructor, it's my
4201 // opinion that binding them as late as possible has value with regards to
4202 // better transitions with fewer bugs. Ie, it's not guaranteed that the
4203 // object will be created and transition will be run immediately after as
4204 // it is today. So we wait until transition is invoked to gather the following
4205 var none,
4206 reverseClass = this.reverse ? " reverse" : "",
4207 screenHeight = $.mobile.getScreenHeight(),
4208 maxTransitionOverride = $.mobile.maxTransitionWidth !== false &&
4209 $.mobile.window.width() > $.mobile.maxTransitionWidth;
4210
4211 this.toScroll = ( toScroll ? toScroll : 0 );
4212
4213 none = !$.support.cssTransitions || !$.support.cssAnimations ||
4214 maxTransitionOverride || !this.name || this.name === "none" ||
4215 Math.max( $.mobile.window.scrollTop(), this.toScroll ) >
4216 $.mobile.getMaxScrollForTransition();
4217
4218 this.toggleViewportClass();
4219
4220 if ( this.$from && !none ) {
4221 this.startOut( screenHeight, reverseClass, none );
4222 } else {
4223 this.doneOut( screenHeight, reverseClass, none, true );
4224 }
4225
4226 return this.deferred.promise();
4227 }
4228 });
4229})( jQuery, this );
4230
4231
4232(function( $ ) {
4233
4234 $.mobile.SerialTransition = function() {
4235 this.init.apply(this, arguments);
4236 };
4237
4238 $.extend($.mobile.SerialTransition.prototype, $.mobile.Transition.prototype, {
4239 sequential: true,
4240
4241 beforeDoneOut: function() {
4242 if ( this.$from ) {
4243 this.cleanFrom();
4244 }
4245 },
4246
4247 beforeStartOut: function( screenHeight, reverseClass, none ) {
4248 this.$from.animationComplete($.proxy(function() {
4249 this.doneOut( screenHeight, reverseClass, none );
4250 }, this ));
4251 }
4252 });
4253
4254})( jQuery );
4255
4256
4257(function( $ ) {
4258
4259 $.mobile.ConcurrentTransition = function() {
4260 this.init.apply(this, arguments);
4261 };
4262
4263 $.extend($.mobile.ConcurrentTransition.prototype, $.mobile.Transition.prototype, {
4264 sequential: false,
4265
4266 beforeDoneIn: function() {
4267 if ( this.$from ) {
4268 this.cleanFrom();
4269 }
4270 },
4271
4272 beforeStartOut: function( screenHeight, reverseClass, none ) {
4273 this.doneOut( screenHeight, reverseClass, none );
4274 }
4275 });
4276
4277})( jQuery );
4278
4279
4280(function( $ ) {
4281
4282 // generate the handlers from the above
4283 var defaultGetMaxScrollForTransition = function() {
4284 return $.mobile.getScreenHeight() * 3;
4285 };
4286
4287 //transition handler dictionary for 3rd party transitions
4288 $.mobile.transitionHandlers = {
4289 "sequential": $.mobile.SerialTransition,
4290 "simultaneous": $.mobile.ConcurrentTransition
4291 };
4292
4293 // Make our transition handler the public default.
4294 $.mobile.defaultTransitionHandler = $.mobile.transitionHandlers.sequential;
4295
4296 $.mobile.transitionFallbacks = {};
4297
4298 // If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified
4299 $.mobile._maybeDegradeTransition = function( transition ) {
4300 if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) {
4301 transition = $.mobile.transitionFallbacks[ transition ];
4302 }
4303
4304 return transition;
4305 };
4306
4307 // Set the getMaxScrollForTransition to default if no implementation was set by user
4308 $.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;
4309
4310})( jQuery );
4311
4312/*
4313* fallback transition for flip in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4314*/
4315
4316(function( $, window, undefined ) {
4317
4318$.mobile.transitionFallbacks.flip = "fade";
4319
4320})( jQuery, this );
4321
4322/*
4323* fallback transition for flow in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4324*/
4325
4326(function( $, window, undefined ) {
4327
4328$.mobile.transitionFallbacks.flow = "fade";
4329
4330})( jQuery, this );
4331
4332/*
4333* fallback transition for pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4334*/
4335
4336(function( $, window, undefined ) {
4337
4338$.mobile.transitionFallbacks.pop = "fade";
4339
4340})( jQuery, this );
4341
4342/*
4343* fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4344*/
4345
4346(function( $, window, undefined ) {
4347
4348// Use the simultaneous transitions handler for slide transitions
4349$.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous;
4350
4351// Set the slide transitions's fallback to "fade"
4352$.mobile.transitionFallbacks.slide = "fade";
4353
4354})( jQuery, this );
4355
4356/*
4357* fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4358*/
4359
4360(function( $, window, undefined ) {
4361
4362$.mobile.transitionFallbacks.slidedown = "fade";
4363
4364})( jQuery, this );
4365
4366/*
4367* fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4368*/
4369
4370(function( $, window, undefined ) {
4371
4372// Set the slide transitions's fallback to "fade"
4373$.mobile.transitionFallbacks.slidefade = "fade";
4374
4375})( jQuery, this );
4376
4377/*
4378* fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4379*/
4380
4381(function( $, window, undefined ) {
4382
4383$.mobile.transitionFallbacks.slideup = "fade";
4384
4385})( jQuery, this );
4386
4387/*
4388* fallback transition for turn in non-3D supporting browsers (which tend to handle complex transitions poorly in general
4389*/
4390
4391(function( $, window, undefined ) {
4392
4393$.mobile.transitionFallbacks.turn = "fade";
4394
4395})( jQuery, this );
4396
4397
4398(function( $, undefined ) {
4399$.mobile.widgets = {};
4400
4401var originalWidget = $.widget,
4402
4403 // Record the original, non-mobileinit-modified version of $.mobile.keepNative
4404 // so we can later determine whether someone has modified $.mobile.keepNative
4405 keepNativeFactoryDefault = $.mobile.keepNative;
4406
4407$.widget = (function( orig ) {
4408 return function() {
4409 var constructor = orig.apply( this, arguments ),
4410 name = constructor.prototype.widgetName;
4411
4412 constructor.initSelector = ( ( constructor.prototype.initSelector !== undefined ) ?
4413 constructor.prototype.initSelector : ":jqmData(role='" + name + "')" );
4414
4415 $.mobile.widgets[ name ] = constructor;
4416
4417 return constructor;
4418 };
4419})( $.widget );
4420
4421// Make sure $.widget still has bridge and extend methods
4422$.extend( $.widget, originalWidget );
4423
4424// For backcompat remove in 1.5
4425$.mobile.document.on( "create", function( event ) {
4426 $( event.target ).enhanceWithin();
4427});
4428
4429$.widget( "mobile.page", {
4430 options: {
4431 theme: "a",
4432 domCache: false,
4433
4434 // Deprecated in 1.4 remove in 1.5
4435 keepNativeDefault: $.mobile.keepNative,
4436
4437 // Deprecated in 1.4 remove in 1.5
4438 contentTheme: null,
4439 enhance: true,
4440 enhanced: false
4441 },
4442
4443 // DEPRECATED for > 1.4
4444 // TODO remove at 1.5
4445 _createWidget: function() {
4446 $.Widget.prototype._createWidget.apply( this, arguments );
4447 this._trigger( "init" );
4448 },
4449
4450 _create: function() {
4451 // If false is returned by the callbacks do not create the page
4452 if ( this._trigger( "beforecreate" ) === false ) {
4453 return false;
4454 }
4455
4456 if ( !this.options.enhanced ) {
4457 this._enhance();
4458 }
4459
4460 this._on( this.element, {
4461 pagebeforehide: "removeContainerBackground",
4462 pagebeforeshow: "_handlePageBeforeShow"
4463 });
4464
4465 if ( this.options.enhance ) {
4466 this.element.enhanceWithin();
4467 }
4468 // Dialog widget is deprecated in 1.4 remove this in 1.5
4469 if ( $.mobile.getAttribute( this.element[0], "role" ) === "dialog" && $.mobile.dialog ) {
4470 this.element.dialog();
4471 }
4472 },
4473
4474 _enhance: function () {
4475 var attrPrefix = "data-" + $.mobile.ns,
4476 self = this;
4477
4478 if ( this.options.role ) {
4479 this.element.attr( "data-" + $.mobile.ns + "role", this.options.role );
4480 }
4481
4482 this.element
4483 .attr( "tabindex", "0" )
4484 .addClass( "ui-page ui-page-theme-" + this.options.theme );
4485
4486 // Manipulation of content os Deprecated as of 1.4 remove in 1.5
4487 this.element.find( "[" + attrPrefix + "role='content']" ).each( function() {
4488 var $this = $( this ),
4489 theme = this.getAttribute( attrPrefix + "theme" ) || undefined;
4490 self.options.contentTheme = theme || self.options.contentTheme || ( self.options.dialog && self.options.theme ) || ( self.element.jqmData("role") === "dialog" && self.options.theme );
4491 $this.addClass( "ui-content" );
4492 if ( self.options.contentTheme ) {
4493 $this.addClass( "ui-body-" + ( self.options.contentTheme ) );
4494 }
4495 // Add ARIA role
4496 $this.attr( "role", "main" ).addClass( "ui-content" );
4497 });
4498 },
4499
4500 bindRemove: function( callback ) {
4501 var page = this.element;
4502
4503 // when dom caching is not enabled or the page is embedded bind to remove the page on hide
4504 if ( !page.data( "mobile-page" ).options.domCache &&
4505 page.is( ":jqmData(external-page='true')" ) ) {
4506
4507 // TODO use _on - that is, sort out why it doesn't work in this case
4508 page.bind( "pagehide.remove", callback || function( e, data ) {
4509
4510 //check if this is a same page transition and if so don't remove the page
4511 if( !data.samePage ){
4512 var $this = $( this ),
4513 prEvent = new $.Event( "pageremove" );
4514
4515 $this.trigger( prEvent );
4516
4517 if ( !prEvent.isDefaultPrevented() ) {
4518 $this.removeWithDependents();
4519 }
4520 }
4521 });
4522 }
4523 },
4524
4525 _setOptions: function( o ) {
4526 if ( o.theme !== undefined ) {
4527 this.element.removeClass( "ui-page-theme-" + this.options.theme ).addClass( "ui-page-theme-" + o.theme );
4528 }
4529
4530 if ( o.contentTheme !== undefined ) {
4531 this.element.find( "[data-" + $.mobile.ns + "='content']" ).removeClass( "ui-body-" + this.options.contentTheme )
4532 .addClass( "ui-body-" + o.contentTheme );
4533 }
4534 },
4535
4536 _handlePageBeforeShow: function(/* e */) {
4537 this.setContainerBackground();
4538 },
4539 // Deprecated in 1.4 remove in 1.5
4540 removeContainerBackground: function() {
4541 this.element.closest( ":mobile-pagecontainer" ).pagecontainer({ "theme": "none" });
4542 },
4543 // Deprecated in 1.4 remove in 1.5
4544 // set the page container background to the page theme
4545 setContainerBackground: function( theme ) {
4546 this.element.parent().pagecontainer( { "theme": theme || this.options.theme } );
4547 },
4548 // Deprecated in 1.4 remove in 1.5
4549 keepNativeSelector: function() {
4550 var options = this.options,
4551 keepNative = $.trim( options.keepNative || "" ),
4552 globalValue = $.trim( $.mobile.keepNative ),
4553 optionValue = $.trim( options.keepNativeDefault ),
4554
4555 // Check if $.mobile.keepNative has changed from the factory default
4556 newDefault = ( keepNativeFactoryDefault === globalValue ?
4557 "" : globalValue ),
4558
4559 // If $.mobile.keepNative has not changed, use options.keepNativeDefault
4560 oldDefault = ( newDefault === "" ? optionValue : "" );
4561
4562 // Concatenate keepNative selectors from all sources where the value has
4563 // changed or, if nothing has changed, return the default
4564 return ( ( keepNative ? [ keepNative ] : [] )
4565 .concat( newDefault ? [ newDefault ] : [] )
4566 .concat( oldDefault ? [ oldDefault ] : [] )
4567 .join( ", " ) );
4568 }
4569});
4570})( jQuery );
4571
4572(function( $, undefined ) {
4573
4574 $.widget( "mobile.pagecontainer", {
4575 options: {
4576 theme: "a"
4577 },
4578
4579 initSelector: false,
4580
4581 _create: function() {
4582 this._trigger( "beforecreate" );
4583 this.setLastScrollEnabled = true;
4584
4585 this._on( this.window, {
4586 // disable an scroll setting when a hashchange has been fired,
4587 // this only works because the recording of the scroll position
4588 // is delayed for 100ms after the browser might have changed the
4589 // position because of the hashchange
4590 navigate: "_disableRecordScroll",
4591
4592 // bind to scrollstop for the first page, "pagechange" won't be
4593 // fired in that case
4594 scrollstop: "_delayedRecordScroll"
4595 });
4596
4597 // TODO consider moving the navigation handler OUT of widget into
4598 // some other object as glue between the navigate event and the
4599 // content widget load and change methods
4600 this._on( this.window, { navigate: "_filterNavigateEvents" });
4601
4602 // TODO move from page* events to content* events
4603 this._on({ pagechange: "_afterContentChange" });
4604
4605 // handle initial hashchange from chrome :(
4606 this.window.one( "navigate", $.proxy(function() {
4607 this.setLastScrollEnabled = true;
4608 }, this));
4609 },
4610
4611 _setOptions: function( options ) {
4612 if ( options.theme !== undefined && options.theme !== "none" ) {
4613 this.element.removeClass( "ui-overlay-" + this.options.theme )
4614 .addClass( "ui-overlay-" + options.theme );
4615 } else if ( options.theme !== undefined ) {
4616 this.element.removeClass( "ui-overlay-" + this.options.theme );
4617 }
4618
4619 this._super( options );
4620 },
4621
4622 _disableRecordScroll: function() {
4623 this.setLastScrollEnabled = false;
4624 },
4625
4626 _enableRecordScroll: function() {
4627 this.setLastScrollEnabled = true;
4628 },
4629
4630 // TODO consider the name here, since it's purpose specific
4631 _afterContentChange: function() {
4632 // once the page has changed, re-enable the scroll recording
4633 this.setLastScrollEnabled = true;
4634
4635 // remove any binding that previously existed on the get scroll
4636 // which may or may not be different than the scroll element
4637 // determined for this page previously
4638 this._off( this.window, "scrollstop" );
4639
4640 // determine and bind to the current scoll element which may be the
4641 // window or in the case of touch overflow the element touch overflow
4642 this._on( this.window, { scrollstop: "_delayedRecordScroll" });
4643 },
4644
4645 _recordScroll: function() {
4646 // this barrier prevents setting the scroll value based on
4647 // the browser scrolling the window based on a hashchange
4648 if ( !this.setLastScrollEnabled ) {
4649 return;
4650 }
4651
4652 var active = this._getActiveHistory(),
4653 currentScroll, minScroll, defaultScroll;
4654
4655 if ( active ) {
4656 currentScroll = this._getScroll();
4657 minScroll = this._getMinScroll();
4658 defaultScroll = this._getDefaultScroll();
4659
4660 // Set active page's lastScroll prop. If the location we're
4661 // scrolling to is less than minScrollBack, let it go.
4662 active.lastScroll = currentScroll < minScroll ? defaultScroll : currentScroll;
4663 }
4664 },
4665
4666 _delayedRecordScroll: function() {
4667 setTimeout( $.proxy(this, "_recordScroll"), 100 );
4668 },
4669
4670 _getScroll: function() {
4671 return this.window.scrollTop();
4672 },
4673
4674 _getMinScroll: function() {
4675 return $.mobile.minScrollBack;
4676 },
4677
4678 _getDefaultScroll: function() {
4679 return $.mobile.defaultHomeScroll;
4680 },
4681
4682 _filterNavigateEvents: function( e, data ) {
4683 var url;
4684
4685 if ( e.originalEvent && e.originalEvent.isDefaultPrevented() ) {
4686 return;
4687 }
4688
4689 url = e.originalEvent.type.indexOf( "hashchange" ) > -1 ? data.state.hash : data.state.url;
4690
4691 if ( !url ) {
4692 url = this._getHash();
4693 }
4694
4695 if ( !url || url === "#" || url.indexOf( "#" + $.mobile.path.uiStateKey ) === 0 ) {
4696 url = location.href;
4697 }
4698
4699 this._handleNavigate( url, data.state );
4700 },
4701
4702 _getHash: function() {
4703 return $.mobile.path.parseLocation().hash;
4704 },
4705
4706 // TODO active page should be managed by the container (ie, it should be a property)
4707 getActivePage: function() {
4708 return this.activePage;
4709 },
4710
4711 // TODO the first page should be a property set during _create using the logic
4712 // that currently resides in init
4713 _getInitialContent: function() {
4714 return $.mobile.firstPage;
4715 },
4716
4717 // TODO each content container should have a history object
4718 _getHistory: function() {
4719 return $.mobile.navigate.history;
4720 },
4721
4722 _getActiveHistory: function() {
4723 return this._getHistory().getActive();
4724 },
4725
4726 // TODO the document base should be determined at creation
4727 _getDocumentBase: function() {
4728 return $.mobile.path.documentBase;
4729 },
4730
4731 back: function() {
4732 this.go( -1 );
4733 },
4734
4735 forward: function() {
4736 this.go( 1 );
4737 },
4738
4739 go: function( steps ) {
4740
4741 //if hashlistening is enabled use native history method
4742 if ( $.mobile.hashListeningEnabled ) {
4743 window.history.go( steps );
4744 } else {
4745
4746 //we are not listening to the hash so handle history internally
4747 var activeIndex = $.mobile.navigate.history.activeIndex,
4748 index = activeIndex + parseInt( steps, 10 ),
4749 url = $.mobile.navigate.history.stack[ index ].url,
4750 direction = ( steps >= 1 )? "forward" : "back";
4751
4752 //update the history object
4753 $.mobile.navigate.history.activeIndex = index;
4754 $.mobile.navigate.history.previousIndex = activeIndex;
4755
4756 //change to the new page
4757 this.change( url, { direction: direction, changeHash: false, fromHashChange: true } );
4758 }
4759 },
4760
4761 // TODO rename _handleDestination
4762 _handleDestination: function( to ) {
4763 var history;
4764
4765 // clean the hash for comparison if it's a url
4766 if ( $.type(to) === "string" ) {
4767 to = $.mobile.path.stripHash( to );
4768 }
4769
4770 if ( to ) {
4771 history = this._getHistory();
4772
4773 // At this point, 'to' can be one of 3 things, a cached page
4774 // element from a history stack entry, an id, or site-relative /
4775 // absolute URL. If 'to' is an id, we need to resolve it against
4776 // the documentBase, not the location.href, since the hashchange
4777 // could've been the result of a forward/backward navigation
4778 // that crosses from an external page/dialog to an internal
4779 // page/dialog.
4780 //
4781 // TODO move check to history object or path object?
4782 to = !$.mobile.path.isPath( to ) ? ( $.mobile.path.makeUrlAbsolute( "#" + to, this._getDocumentBase() ) ) : to;
4783 }
4784 return to || this._getInitialContent();
4785 },
4786
4787 // The options by which a given page was reached are stored in the history entry for that
4788 // page. When this function is called, history is already at the new entry. So, when moving
4789 // back, this means we need to consult the old entry and reverse the meaning of the
4790 // options. Otherwise, if we're moving forward, we need to consult the options for the
4791 // current entry.
4792 _optionFromHistory: function( direction, optionName, fallbackValue ) {
4793 var history = this._getHistory(),
4794 entry = ( direction === "back" ? history.getLast() : history.getActive() );
4795
4796 return ( ( entry && entry[ optionName ] ) || fallbackValue );
4797 },
4798
4799 _handleDialog: function( changePageOptions, data ) {
4800 var to, active, activeContent = this.getActivePage();
4801
4802 // If current active page is not a dialog skip the dialog and continue
4803 // in the same direction
4804 // Note: The dialog widget is deprecated as of 1.4.0 and will be removed in 1.5.0.
4805 // Thus, as of 1.5.0 activeContent.data( "mobile-dialog" ) will always evaluate to
4806 // falsy, so the second condition in the if-statement below can be removed altogether.
4807 if ( activeContent && !activeContent.data( "mobile-dialog" ) ) {
4808 // determine if we're heading forward or backward and continue
4809 // accordingly past the current dialog
4810 if ( data.direction === "back" ) {
4811 this.back();
4812 } else {
4813 this.forward();
4814 }
4815
4816 // prevent changePage call
4817 return false;
4818 } else {
4819 // if the current active page is a dialog and we're navigating
4820 // to a dialog use the dialog objected saved in the stack
4821 to = data.pageUrl;
4822 active = this._getActiveHistory();
4823
4824 // make sure to set the role, transition and reversal
4825 // as most of this is lost by the domCache cleaning
4826 $.extend( changePageOptions, {
4827 role: active.role,
4828 transition: this._optionFromHistory( data.direction, "transition",
4829 changePageOptions.transition ),
4830 reverse: data.direction === "back"
4831 });
4832 }
4833
4834 return to;
4835 },
4836
4837 _handleNavigate: function( url, data ) {
4838 //find first page via hash
4839 // TODO stripping the hash twice with handleUrl
4840 var to = $.mobile.path.stripHash( url ), history = this._getHistory(),
4841
4842 // transition is false if it's the first page, undefined
4843 // otherwise (and may be overridden by default)
4844 transition = history.stack.length === 0 ? "none" :
4845 this._optionFromHistory( data.direction, "transition" ),
4846
4847 // default options for the changPage calls made after examining
4848 // the current state of the page and the hash, NOTE that the
4849 // transition is derived from the previous history entry
4850 changePageOptions = {
4851 changeHash: false,
4852 fromHashChange: true,
4853 reverse: data.direction === "back"
4854 };
4855
4856 $.extend( changePageOptions, data, {
4857 transition: transition,
4858 allowSamePageTransition: this._optionFromHistory( data.direction,
4859 "allowSamePageTransition" )
4860 });
4861
4862 // TODO move to _handleDestination ?
4863 // If this isn't the first page, if the current url is a dialog hash
4864 // key, and the initial destination isn't equal to the current target
4865 // page, use the special dialog handling
4866 if ( history.activeIndex > 0 &&
4867 to.indexOf( $.mobile.dialogHashKey ) > -1 ) {
4868
4869 to = this._handleDialog( changePageOptions, data );
4870
4871 if ( to === false ) {
4872 return;
4873 }
4874 }
4875
4876 this._changeContent( this._handleDestination( to ), changePageOptions );
4877 },
4878
4879 _changeContent: function( to, opts ) {
4880 $.mobile.changePage( to, opts );
4881 },
4882
4883 _getBase: function() {
4884 return $.mobile.base;
4885 },
4886
4887 _getNs: function() {
4888 return $.mobile.ns;
4889 },
4890
4891 _enhance: function( content, role ) {
4892 // TODO consider supporting a custom callback, and passing in
4893 // the settings which includes the role
4894 return content.page({ role: role });
4895 },
4896
4897 _include: function( page, settings ) {
4898 // append to page and enhance
4899 page.appendTo( this.element );
4900
4901 // use the page widget to enhance
4902 this._enhance( page, settings.role );
4903
4904 // remove page on hide
4905 page.page( "bindRemove" );
4906 },
4907
4908 _find: function( absUrl ) {
4909 // TODO consider supporting a custom callback
4910 var fileUrl = this._createFileUrl( absUrl ),
4911 dataUrl = this._createDataUrl( absUrl ),
4912 page, initialContent = this._getInitialContent();
4913
4914 // Check to see if the page already exists in the DOM.
4915 // NOTE do _not_ use the :jqmData pseudo selector because parenthesis
4916 // are a valid url char and it breaks on the first occurrence
4917 page = this.element
4918 .children( "[data-" + this._getNs() +
4919 "url='" + $.mobile.path.hashToSelector( dataUrl ) + "']" );
4920
4921 // If we failed to find the page, check to see if the url is a
4922 // reference to an embedded page. If so, it may have been dynamically
4923 // injected by a developer, in which case it would be lacking a
4924 // data-url attribute and in need of enhancement.
4925 if ( page.length === 0 && dataUrl && !$.mobile.path.isPath( dataUrl ) ) {
4926 page = this.element.children( $.mobile.path.hashToSelector("#" + dataUrl) )
4927 .attr( "data-" + this._getNs() + "url", dataUrl )
4928 .jqmData( "url", dataUrl );
4929 }
4930
4931 // If we failed to find a page in the DOM, check the URL to see if it
4932 // refers to the first page in the application. Also check to make sure
4933 // our cached-first-page is actually in the DOM. Some user deployed
4934 // apps are pruning the first page from the DOM for various reasons.
4935 // We check for this case here because we don't want a first-page with
4936 // an id falling through to the non-existent embedded page error case.
4937 if ( page.length === 0 &&
4938 $.mobile.path.isFirstPageUrl( fileUrl ) &&
4939 initialContent &&
4940 initialContent.parent().length ) {
4941 page = $( initialContent );
4942 }
4943
4944 return page;
4945 },
4946
4947 _getLoader: function() {
4948 return $.mobile.loading();
4949 },
4950
4951 _showLoading: function( delay, theme, msg, textonly ) {
4952 // This configurable timeout allows cached pages a brief
4953 // delay to load without showing a message
4954 if ( this._loadMsg ) {
4955 return;
4956 }
4957
4958 this._loadMsg = setTimeout($.proxy(function() {
4959 this._getLoader().loader( "show", theme, msg, textonly );
4960 this._loadMsg = 0;
4961 }, this), delay );
4962 },
4963
4964 _hideLoading: function() {
4965 // Stop message show timer
4966 clearTimeout( this._loadMsg );
4967 this._loadMsg = 0;
4968
4969 // Hide loading message
4970 this._getLoader().loader( "hide" );
4971 },
4972
4973 _showError: function() {
4974 // make sure to remove the current loading message
4975 this._hideLoading();
4976
4977 // show the error message
4978 this._showLoading( 0, $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true );
4979
4980 // hide the error message after a delay
4981 // TODO configuration
4982 setTimeout( $.proxy(this, "_hideLoading"), 1500 );
4983 },
4984
4985 _parse: function( html, fileUrl ) {
4986 // TODO consider allowing customization of this method. It's very JQM specific
4987 var page, all = $( "<div></div>" );
4988
4989 //workaround to allow scripts to execute when included in page divs
4990 all.get( 0 ).innerHTML = html;
4991
4992 page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
4993
4994 //if page elem couldn't be found, create one and insert the body element's contents
4995 if ( !page.length ) {
4996 page = $( "<div data-" + this._getNs() + "role='page'>" +
4997 ( html.split( /<\/?body[^>]*>/gmi )[1] || "" ) +
4998 "</div>" );
4999 }
5000
5001 // TODO tagging a page with external to make sure that embedded pages aren't
5002 // removed by the various page handling code is bad. Having page handling code
5003 // in many places is bad. Solutions post 1.0
5004 page.attr( "data-" + this._getNs() + "url", this._createDataUrl( fileUrl ) )
5005 .attr( "data-" + this._getNs() + "external-page", true );
5006
5007 return page;
5008 },
5009
5010 _setLoadedTitle: function( page, html ) {
5011 //page title regexp
5012 var newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1;
5013
5014 if ( newPageTitle && !page.jqmData("title") ) {
5015 newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
5016 page.jqmData( "title", newPageTitle );
5017 }
5018 },
5019
5020 _createDataUrl: function( absoluteUrl ) {
5021 return $.mobile.path.convertUrlToDataUrl( absoluteUrl );
5022 },
5023
5024 _createFileUrl: function( absoluteUrl ) {
5025 return $.mobile.path.getFilePath( absoluteUrl );
5026 },
5027
5028 _triggerWithDeprecated: function( name, data, page ) {
5029 var deprecatedEvent = $.Event( "page" + name ),
5030 newEvent = $.Event( this.widgetName + name );
5031
5032 // DEPRECATED
5033 // trigger the old deprecated event on the page if it's provided
5034 ( page || this.element ).trigger( deprecatedEvent, data );
5035
5036 // use the widget trigger method for the new content* event
5037 this._trigger( name, newEvent, data );
5038
5039 return {
5040 deprecatedEvent: deprecatedEvent,
5041 event: newEvent
5042 };
5043 },
5044
5045 // TODO it would be nice to split this up more but everything appears to be "one off"
5046 // or require ordering such that other bits are sprinkled in between parts that
5047 // could be abstracted out as a group
5048 _loadSuccess: function( absUrl, triggerData, settings, deferred ) {
5049 var fileUrl = this._createFileUrl( absUrl );
5050
5051 return $.proxy(function( html, textStatus, xhr ) {
5052 //pre-parse html to check for a data-url,
5053 //use it as the new fileUrl, base path, etc
5054 var content,
5055
5056 // TODO handle dialogs again
5057 pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + this._getNs() + "role=[\"']?page[\"']?[^>]*>)" ),
5058
5059 dataUrlRegex = new RegExp( "\\bdata-" + this._getNs() + "url=[\"']?([^\"'>]*)[\"']?" );
5060
5061 // data-url must be provided for the base tag so resource requests
5062 // can be directed to the correct url. loading into a temprorary
5063 // element makes these requests immediately
5064 if ( pageElemRegex.test( html ) &&
5065 RegExp.$1 &&
5066 dataUrlRegex.test( RegExp.$1 ) &&
5067 RegExp.$1 ) {
5068 fileUrl = $.mobile.path.getFilePath( $("<div>" + RegExp.$1 + "</div>").text() );
5069
5070 // We specify that, if a data-url attribute is given on the page div, its value
5071 // must be given non-URL-encoded. However, in this part of the code, fileUrl is
5072 // assumed to be URL-encoded, so we URL-encode the retrieved value here
5073 fileUrl = this.window[ 0 ].encodeURIComponent( fileUrl );
5074 }
5075
5076 //dont update the base tag if we are prefetching
5077 if ( settings.prefetch === undefined ) {
5078 this._getBase().set( fileUrl );
5079 }
5080
5081 content = this._parse( html, fileUrl );
5082
5083 this._setLoadedTitle( content, html );
5084
5085 // Add the content reference and xhr to our triggerData.
5086 triggerData.xhr = xhr;
5087 triggerData.textStatus = textStatus;
5088
5089 // DEPRECATED
5090 triggerData.page = content;
5091
5092 triggerData.content = content;
5093
5094 triggerData.toPage = content;
5095
5096 // If the default behavior is prevented, stop here!
5097 // Note that it is the responsibility of the listener/handler
5098 // that called preventDefault(), to resolve/reject the
5099 // deferred object within the triggerData.
5100 if ( this._triggerWithDeprecated( "load", triggerData ).event.isDefaultPrevented() ) {
5101 return;
5102 }
5103
5104 this._include( content, settings );
5105
5106 // Remove loading message.
5107 if ( settings.showLoadMsg ) {
5108 this._hideLoading();
5109 }
5110
5111 deferred.resolve( absUrl, settings, content );
5112 }, this);
5113 },
5114
5115 _loadDefaults: {
5116 type: "get",
5117 data: undefined,
5118
5119 // DEPRECATED
5120 reloadPage: false,
5121
5122 reload: false,
5123
5124 // By default we rely on the role defined by the @data-role attribute.
5125 role: undefined,
5126
5127 showLoadMsg: false,
5128
5129 // This delay allows loads that pull from browser cache to
5130 // occur without showing the loading message.
5131 loadMsgDelay: 50
5132 },
5133
5134 load: function( url, options ) {
5135 // This function uses deferred notifications to let callers
5136 // know when the content is done loading, or if an error has occurred.
5137 var deferred = ( options && options.deferred ) || $.Deferred(),
5138
5139 // Examining the option "reloadPage" passed by the user is deprecated as of 1.4.0
5140 // and will be removed in 1.5.0.
5141 // Copy option "reloadPage" to "reload", but only if option "reload" is not present
5142 reloadOptionExtension =
5143 ( ( options && options.reload === undefined &&
5144 options.reloadPage !== undefined ) ?
5145 { reload: options.reloadPage } : {} ),
5146
5147 // The default load options with overrides specified by the caller.
5148 settings = $.extend( {}, this._loadDefaults, options, reloadOptionExtension ),
5149
5150 // The DOM element for the content after it has been loaded.
5151 content = null,
5152
5153 // The absolute version of the URL passed into the function. This
5154 // version of the URL may contain dialog/subcontent params in it.
5155 absUrl = $.mobile.path.makeUrlAbsolute( url, this._findBaseWithDefault() ),
5156 fileUrl, dataUrl, pblEvent, triggerData;
5157
5158 // If the caller provided data, and we're using "get" request,
5159 // append the data to the URL.
5160 if ( settings.data && settings.type === "get" ) {
5161 absUrl = $.mobile.path.addSearchParams( absUrl, settings.data );
5162 settings.data = undefined;
5163 }
5164
5165 // If the caller is using a "post" request, reload must be true
5166 if ( settings.data && settings.type === "post" ) {
5167 settings.reload = true;
5168 }
5169
5170 // The absolute version of the URL minus any dialog/subcontent params.
5171 // In otherwords the real URL of the content to be loaded.
5172 fileUrl = this._createFileUrl( absUrl );
5173
5174 // The version of the Url actually stored in the data-url attribute of
5175 // the content. For embedded content, it is just the id of the page. For
5176 // content within the same domain as the document base, it is the site
5177 // relative path. For cross-domain content (Phone Gap only) the entire
5178 // absolute Url is used to load the content.
5179 dataUrl = this._createDataUrl( absUrl );
5180
5181 content = this._find( absUrl );
5182
5183 // If it isn't a reference to the first content and refers to missing
5184 // embedded content reject the deferred and return
5185 if ( content.length === 0 &&
5186 $.mobile.path.isEmbeddedPage(fileUrl) &&
5187 !$.mobile.path.isFirstPageUrl(fileUrl) ) {
5188 deferred.reject( absUrl, settings );
5189 return deferred.promise();
5190 }
5191
5192 // Reset base to the default document base
5193 // TODO figure out why we doe this
5194 this._getBase().reset();
5195
5196 // If the content we are interested in is already in the DOM,
5197 // and the caller did not indicate that we should force a
5198 // reload of the file, we are done. Resolve the deferrred so that
5199 // users can bind to .done on the promise
5200 if ( content.length && !settings.reload ) {
5201 this._enhance( content, settings.role );
5202 deferred.resolve( absUrl, settings, content );
5203
5204 //if we are reloading the content make sure we update
5205 // the base if its not a prefetch
5206 if ( !settings.prefetch ) {
5207 this._getBase().set(url);
5208 }
5209
5210 return deferred.promise();
5211 }
5212
5213 triggerData = {
5214 url: url,
5215 absUrl: absUrl,
5216 toPage: url,
5217 prevPage: options ? options.fromPage : undefined,
5218 dataUrl: dataUrl,
5219 deferred: deferred,
5220 options: settings
5221 };
5222
5223 // Let listeners know we're about to load content.
5224 pblEvent = this._triggerWithDeprecated( "beforeload", triggerData );
5225
5226 // If the default behavior is prevented, stop here!
5227 if ( pblEvent.deprecatedEvent.isDefaultPrevented() ||
5228 pblEvent.event.isDefaultPrevented() ) {
5229 return deferred.promise();
5230 }
5231
5232 if ( settings.showLoadMsg ) {
5233 this._showLoading( settings.loadMsgDelay );
5234 }
5235
5236 // Reset base to the default document base.
5237 // only reset if we are not prefetching
5238 if ( settings.prefetch === undefined ) {
5239 this._getBase().reset();
5240 }
5241
5242 if ( !( $.mobile.allowCrossDomainPages ||
5243 $.mobile.path.isSameDomain($.mobile.path.documentUrl, absUrl ) ) ) {
5244 deferred.reject( absUrl, settings );
5245 return deferred.promise();
5246 }
5247
5248 // Load the new content.
5249 $.ajax({
5250 url: fileUrl,
5251 type: settings.type,
5252 data: settings.data,
5253 contentType: settings.contentType,
5254 dataType: "html",
5255 success: this._loadSuccess( absUrl, triggerData, settings, deferred ),
5256 error: this._loadError( absUrl, triggerData, settings, deferred )
5257 });
5258
5259 return deferred.promise();
5260 },
5261
5262 _loadError: function( absUrl, triggerData, settings, deferred ) {
5263 return $.proxy(function( xhr, textStatus, errorThrown ) {
5264 //set base back to current path
5265 this._getBase().set( $.mobile.path.get() );
5266
5267 // Add error info to our triggerData.
5268 triggerData.xhr = xhr;
5269 triggerData.textStatus = textStatus;
5270 triggerData.errorThrown = errorThrown;
5271
5272 // Clean up internal pending operations like the loader and the transition lock
5273 this._hideLoading();
5274 this._releaseTransitionLock();
5275
5276 // Let listeners know the page load failed.
5277 var plfEvent = this._triggerWithDeprecated( "loadfailed", triggerData );
5278
5279 // If the default behavior is prevented, stop here!
5280 // Note that it is the responsibility of the listener/handler
5281 // that called preventDefault(), to resolve/reject the
5282 // deferred object within the triggerData.
5283 if ( plfEvent.deprecatedEvent.isDefaultPrevented() ||
5284 plfEvent.event.isDefaultPrevented() ) {
5285 return;
5286 }
5287
5288 // Remove loading message.
5289 if ( settings.showLoadMsg ) {
5290 this._showError();
5291 }
5292
5293 deferred.reject( absUrl, settings );
5294 }, this);
5295 },
5296
5297 // TODO move into transition handlers?
5298 _triggerCssTransitionEvents: function( to, from, prefix ) {
5299 var samePage = false;
5300
5301 prefix = prefix || "";
5302
5303 // TODO decide if these events should in fact be triggered on the container
5304 if ( from ) {
5305
5306 //Check if this is a same page transition and tell the handler in page
5307 if( to[0] === from[0] ){
5308 samePage = true;
5309 }
5310
5311 //trigger before show/hide events
5312 // TODO deprecate nextPage in favor of next
5313 this._triggerWithDeprecated( prefix + "hide", {
5314
5315 // Deprecated in 1.4 remove in 1.5
5316 nextPage: to,
5317 toPage: to,
5318 prevPage: from,
5319 samePage: samePage
5320 }, from );
5321 }
5322
5323 // TODO deprecate prevPage in favor of previous
5324 this._triggerWithDeprecated( prefix + "show", {
5325 prevPage: from || $( "" ),
5326 toPage: to
5327 }, to );
5328 },
5329
5330 _performTransition: function( transition, reverse, to, from ) {
5331 var transitionDeferred = $.Deferred();
5332
5333 if ( from ) {
5334 from.removeClass( "ui-page-active" );
5335 }
5336 if ( to ) {
5337 to.addClass( "ui-page-active" );
5338 }
5339 this._delay( function() {
5340 transitionDeferred.resolve( transition, reverse, to, from, false );
5341 }, 0 );
5342
5343 return transitionDeferred.promise();
5344 },
5345
5346 // TODO make private once change has been defined in the widget
5347 _cssTransition: function( to, from, options ) {
5348 var transition = options.transition,
5349 reverse = options.reverse,
5350 deferred = options.deferred,
5351 promise;
5352
5353 this._triggerCssTransitionEvents( to, from, "before" );
5354
5355 // TODO put this in a binding to events *outside* the widget
5356 this._hideLoading();
5357
5358 promise = this._performTransition( transition, reverse, to, from );
5359
5360 promise.done( $.proxy( function() {
5361 this._triggerCssTransitionEvents( to, from );
5362 }, this ));
5363
5364 // TODO temporary accomodation of argument deferred
5365 promise.done(function() {
5366 deferred.resolve.apply( deferred, arguments );
5367 });
5368 },
5369
5370 _releaseTransitionLock: function() {
5371 //release transition lock so navigation is free again
5372 isPageTransitioning = false;
5373 if ( pageTransitionQueue.length > 0 ) {
5374 $.mobile.changePage.apply( null, pageTransitionQueue.pop() );
5375 }
5376 },
5377
5378 _removeActiveLinkClass: function( force ) {
5379 //clear out the active button state
5380 $.mobile.removeActiveLinkClass( force );
5381 },
5382
5383 _loadUrl: function( to, triggerData, settings ) {
5384 // preserve the original target as the dataUrl value will be
5385 // simplified eg, removing ui-state, and removing query params
5386 // from the hash this is so that users who want to use query
5387 // params have access to them in the event bindings for the page
5388 // life cycle See issue #5085
5389 settings.target = to;
5390 settings.deferred = $.Deferred();
5391
5392 this.load( to, settings );
5393
5394 settings.deferred.done($.proxy(function( url, options, content ) {
5395 isPageTransitioning = false;
5396
5397 // store the original absolute url so that it can be provided
5398 // to events in the triggerData of the subsequent changePage call
5399 options.absUrl = triggerData.absUrl;
5400
5401 this.transition( content, triggerData, options );
5402 }, this));
5403
5404 settings.deferred.fail($.proxy(function(/* url, options */) {
5405 this._removeActiveLinkClass( true );
5406 this._releaseTransitionLock();
5407 this._triggerWithDeprecated( "changefailed", triggerData );
5408 }, this));
5409 },
5410
5411 _triggerPageBeforeChange: function( to, triggerData, settings ) {
5412 var returnEvents;
5413
5414 triggerData.prevPage = this.activePage;
5415 $.extend( triggerData, {
5416 toPage: to,
5417 options: settings
5418 });
5419
5420 // NOTE: preserve the original target as the dataUrl value will be
5421 // simplified eg, removing ui-state, and removing query params from
5422 // the hash this is so that users who want to use query params have
5423 // access to them in the event bindings for the page life cycle
5424 // See issue #5085
5425 if ( $.type(to) === "string" ) {
5426 // if the toPage is a string simply convert it
5427 triggerData.absUrl = $.mobile.path.makeUrlAbsolute( to, this._findBaseWithDefault() );
5428 } else {
5429 // if the toPage is a jQuery object grab the absolute url stored
5430 // in the loadPage callback where it exists
5431 triggerData.absUrl = settings.absUrl;
5432 }
5433
5434 // Let listeners know we're about to change the current page.
5435 returnEvents = this._triggerWithDeprecated( "beforechange", triggerData );
5436
5437 // If the default behavior is prevented, stop here!
5438 if ( returnEvents.event.isDefaultPrevented() ||
5439 returnEvents.deprecatedEvent.isDefaultPrevented() ) {
5440 return false;
5441 }
5442
5443 return true;
5444 },
5445
5446 change: function( to, options ) {
5447 // If we are in the midst of a transition, queue the current request.
5448 // We'll call changePage() once we're done with the current transition
5449 // to service the request.
5450 if ( isPageTransitioning ) {
5451 pageTransitionQueue.unshift( arguments );
5452 return;
5453 }
5454
5455 var settings = $.extend( {}, $.mobile.changePage.defaults, options ),
5456 triggerData = {};
5457
5458 // Make sure we have a fromPage.
5459 settings.fromPage = settings.fromPage || this.activePage;
5460
5461 // if the page beforechange default is prevented return early
5462 if ( !this._triggerPageBeforeChange(to, triggerData, settings) ) {
5463 return;
5464 }
5465
5466 // We allow "pagebeforechange" observers to modify the to in
5467 // the trigger data to allow for redirects. Make sure our to is
5468 // updated. We also need to re-evaluate whether it is a string,
5469 // because an object can also be replaced by a string
5470 to = triggerData.toPage;
5471
5472 // If the caller passed us a url, call loadPage()
5473 // to make sure it is loaded into the DOM. We'll listen
5474 // to the promise object it returns so we know when
5475 // it is done loading or if an error occurred.
5476 if ( $.type(to) === "string" ) {
5477 // Set the isPageTransitioning flag to prevent any requests from
5478 // entering this method while we are in the midst of loading a page
5479 // or transitioning.
5480 isPageTransitioning = true;
5481
5482 this._loadUrl( to, triggerData, settings );
5483 } else {
5484 this.transition( to, triggerData, settings );
5485 }
5486 },
5487
5488 transition: function( toPage, triggerData, settings ) {
5489 var fromPage, url, pageUrl, fileUrl,
5490 active, activeIsInitialPage,
5491 historyDir, pageTitle, isDialog,
5492 alreadyThere, newPageTitle,
5493 params, cssTransitionDeferred,
5494 beforeTransition;
5495
5496 // If we are in the midst of a transition, queue the current request.
5497 // We'll call changePage() once we're done with the current transition
5498 // to service the request.
5499 if ( isPageTransitioning ) {
5500 // make sure to only queue the to and settings values so the arguments
5501 // work with a call to the change method
5502 pageTransitionQueue.unshift( [toPage, settings] );
5503 return;
5504 }
5505
5506 // DEPRECATED - this call only, in favor of the before transition
5507 // if the page beforechange default is prevented return early
5508 if ( !this._triggerPageBeforeChange(toPage, triggerData, settings) ) {
5509 return;
5510 }
5511
5512 triggerData.prevPage = settings.fromPage;
5513 // if the (content|page)beforetransition default is prevented return early
5514 // Note, we have to check for both the deprecated and new events
5515 beforeTransition = this._triggerWithDeprecated( "beforetransition", triggerData );
5516 if (beforeTransition.deprecatedEvent.isDefaultPrevented() ||
5517 beforeTransition.event.isDefaultPrevented() ) {
5518 return;
5519 }
5520
5521 // Set the isPageTransitioning flag to prevent any requests from
5522 // entering this method while we are in the midst of loading a page
5523 // or transitioning.
5524 isPageTransitioning = true;
5525
5526 // If we are going to the first-page of the application, we need to make
5527 // sure settings.dataUrl is set to the application document url. This allows
5528 // us to avoid generating a document url with an id hash in the case where the
5529 // first-page of the document has an id attribute specified.
5530 if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
5531 settings.dataUrl = $.mobile.path.documentUrl.hrefNoHash;
5532 }
5533
5534 // The caller passed us a real page DOM element. Update our
5535 // internal state and then trigger a transition to the page.
5536 fromPage = settings.fromPage;
5537 url = ( settings.dataUrl && $.mobile.path.convertUrlToDataUrl(settings.dataUrl) ) ||
5538 toPage.jqmData( "url" );
5539
5540 // The pageUrl var is usually the same as url, except when url is obscured
5541 // as a dialog url. pageUrl always contains the file path
5542 pageUrl = url;
5543 fileUrl = $.mobile.path.getFilePath( url );
5544 active = $.mobile.navigate.history.getActive();
5545 activeIsInitialPage = $.mobile.navigate.history.activeIndex === 0;
5546 historyDir = 0;
5547 pageTitle = document.title;
5548 isDialog = ( settings.role === "dialog" ||
5549 toPage.jqmData( "role" ) === "dialog" ) &&
5550 toPage.jqmData( "dialog" ) !== true;
5551
5552 // By default, we prevent changePage requests when the fromPage and toPage
5553 // are the same element, but folks that generate content
5554 // manually/dynamically and reuse pages want to be able to transition to
5555 // the same page. To allow this, they will need to change the default
5556 // value of allowSamePageTransition to true, *OR*, pass it in as an
5557 // option when they manually call changePage(). It should be noted that
5558 // our default transition animations assume that the formPage and toPage
5559 // are different elements, so they may behave unexpectedly. It is up to
5560 // the developer that turns on the allowSamePageTransitiona option to
5561 // either turn off transition animations, or make sure that an appropriate
5562 // animation transition is used.
5563 if ( fromPage && fromPage[0] === toPage[0] &&
5564 !settings.allowSamePageTransition ) {
5565
5566 isPageTransitioning = false;
5567 this._triggerWithDeprecated( "transition", triggerData );
5568 this._triggerWithDeprecated( "change", triggerData );
5569
5570 // Even if there is no page change to be done, we should keep the
5571 // urlHistory in sync with the hash changes
5572 if ( settings.fromHashChange ) {
5573 $.mobile.navigate.history.direct({ url: url });
5574 }
5575
5576 return;
5577 }
5578
5579 // We need to make sure the page we are given has already been enhanced.
5580 toPage.page({ role: settings.role });
5581
5582 // If the changePage request was sent from a hashChange event, check to
5583 // see if the page is already within the urlHistory stack. If so, we'll
5584 // assume the user hit the forward/back button and will try to match the
5585 // transition accordingly.
5586 if ( settings.fromHashChange ) {
5587 historyDir = settings.direction === "back" ? -1 : 1;
5588 }
5589
5590 // Kill the keyboard.
5591 // XXX_jblas: We need to stop crawling the entire document to kill focus.
5592 // Instead, we should be tracking focus with a delegate()
5593 // handler so we already have the element in hand at this
5594 // point.
5595 // Wrap this in a try/catch block since IE9 throw "Unspecified error" if
5596 // document.activeElement is undefined when we are in an IFrame.
5597 try {
5598 if ( document.activeElement &&
5599 document.activeElement.nodeName.toLowerCase() !== "body" ) {
5600
5601 $( document.activeElement ).blur();
5602 } else {
5603 $( "input:focus, textarea:focus, select:focus" ).blur();
5604 }
5605 } catch( e ) {}
5606
5607 // Record whether we are at a place in history where a dialog used to be -
5608 // if so, do not add a new history entry and do not change the hash either
5609 alreadyThere = false;
5610
5611 // If we're displaying the page as a dialog, we don't want the url
5612 // for the dialog content to be used in the hash. Instead, we want
5613 // to append the dialogHashKey to the url of the current page.
5614 if ( isDialog && active ) {
5615 // on the initial page load active.url is undefined and in that case
5616 // should be an empty string. Moving the undefined -> empty string back
5617 // into urlHistory.addNew seemed imprudent given undefined better
5618 // represents the url state
5619
5620 // If we are at a place in history that once belonged to a dialog, reuse
5621 // this state without adding to urlHistory and without modifying the
5622 // hash. However, if a dialog is already displayed at this point, and
5623 // we're about to display another dialog, then we must add another hash
5624 // and history entry on top so that one may navigate back to the
5625 // original dialog
5626 if ( active.url &&
5627 active.url.indexOf( $.mobile.dialogHashKey ) > -1 &&
5628 this.activePage &&
5629 !this.activePage.hasClass( "ui-dialog" ) &&
5630 $.mobile.navigate.history.activeIndex > 0 ) {
5631
5632 settings.changeHash = false;
5633 alreadyThere = true;
5634 }
5635
5636 // Normally, we tack on a dialog hash key, but if this is the location
5637 // of a stale dialog, we reuse the URL from the entry
5638 url = ( active.url || "" );
5639
5640 // account for absolute urls instead of just relative urls use as hashes
5641 if ( !alreadyThere && url.indexOf("#") > -1 ) {
5642 url += $.mobile.dialogHashKey;
5643 } else {
5644 url += "#" + $.mobile.dialogHashKey;
5645 }
5646 }
5647
5648 // if title element wasn't found, try the page div data attr too
5649 // If this is a deep-link or a reload ( active === undefined ) then just
5650 // use pageTitle
5651 newPageTitle = ( !active ) ? pageTitle : toPage.jqmData( "title" ) ||
5652 toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).text();
5653 if ( !!newPageTitle && pageTitle === document.title ) {
5654 pageTitle = newPageTitle;
5655 }
5656 if ( !toPage.jqmData( "title" ) ) {
5657 toPage.jqmData( "title", pageTitle );
5658 }
5659
5660 // Make sure we have a transition defined.
5661 settings.transition = settings.transition ||
5662 ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) ||
5663 ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
5664
5665 //add page to history stack if it's not back or forward
5666 if ( !historyDir && alreadyThere ) {
5667 $.mobile.navigate.history.getActive().pageUrl = pageUrl;
5668 }
5669
5670 // Set the location hash.
5671 if ( url && !settings.fromHashChange ) {
5672
5673 // rebuilding the hash here since we loose it earlier on
5674 // TODO preserve the originally passed in path
5675 if ( !$.mobile.path.isPath( url ) && url.indexOf( "#" ) < 0 ) {
5676 url = "#" + url;
5677 }
5678
5679 // TODO the property names here are just silly
5680 params = {
5681 allowSamePageTransition: settings.allowSamePageTransition,
5682 transition: settings.transition,
5683 title: pageTitle,
5684 pageUrl: pageUrl,
5685 role: settings.role
5686 };
5687
5688 if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) {
5689 $.mobile.navigate( this.window[ 0 ].encodeURI( url ), params, true);
5690 } else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) {
5691 $.mobile.navigate.history.add( url, params );
5692 }
5693 }
5694
5695 //set page title
5696 document.title = pageTitle;
5697
5698 //set "toPage" as activePage deprecated in 1.4 remove in 1.5
5699 $.mobile.activePage = toPage;
5700
5701 //new way to handle activePage
5702 this.activePage = toPage;
5703
5704 // If we're navigating back in the URL history, set reverse accordingly.
5705 settings.reverse = settings.reverse || historyDir < 0;
5706
5707 cssTransitionDeferred = $.Deferred();
5708
5709 this._cssTransition(toPage, fromPage, {
5710 transition: settings.transition,
5711 reverse: settings.reverse,
5712 deferred: cssTransitionDeferred
5713 });
5714
5715 cssTransitionDeferred.done($.proxy(function( name, reverse, $to, $from, alreadyFocused ) {
5716 $.mobile.removeActiveLinkClass();
5717
5718 //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
5719 if ( settings.duplicateCachedPage ) {
5720 settings.duplicateCachedPage.remove();
5721 }
5722
5723 // despite visibility: hidden addresses issue #2965
5724 // https://github.com/jquery/jquery-mobile/issues/2965
5725 if ( !alreadyFocused ) {
5726 $.mobile.focusPage( toPage );
5727 }
5728
5729 this._releaseTransitionLock();
5730 this._triggerWithDeprecated( "transition", triggerData );
5731 this._triggerWithDeprecated( "change", triggerData );
5732 }, this));
5733 },
5734
5735 // determine the current base url
5736 _findBaseWithDefault: function() {
5737 var closestBase = ( this.activePage &&
5738 $.mobile.getClosestBaseUrl( this.activePage ) );
5739 return closestBase || $.mobile.path.documentBase.hrefNoHash;
5740 }
5741 });
5742
5743 // The following handlers should be bound after mobileinit has been triggered
5744 // the following deferred is resolved in the init file
5745 $.mobile.navreadyDeferred = $.Deferred();
5746
5747 //these variables make all page containers use the same queue and only navigate one at a time
5748 // queue to hold simultanious page transitions
5749 var pageTransitionQueue = [],
5750
5751 // indicates whether or not page is in process of transitioning
5752 isPageTransitioning = false;
5753
5754})( jQuery );
5755
5756(function( $, undefined ) {
5757
5758 // resolved on domready
5759 var domreadyDeferred = $.Deferred(),
5760
5761 // resolved and nulled on window.load()
5762 loadDeferred = $.Deferred(),
5763
5764 // function that resolves the above deferred
5765 pageIsFullyLoaded = function() {
5766
5767 // Resolve and null the deferred
5768 loadDeferred.resolve();
5769 loadDeferred = null;
5770 },
5771
5772 path = $.mobile.path,
5773 documentUrl = path.documentUrl,
5774
5775 // used to track last vclicked element to make sure its value is added to form data
5776 $lastVClicked = null;
5777
5778 /* Event Bindings - hashchange, submit, and click */
5779 function findClosestLink( ele ) {
5780 while ( ele ) {
5781 // Look for the closest element with a nodeName of "a".
5782 // Note that we are checking if we have a valid nodeName
5783 // before attempting to access it. This is because the
5784 // node we get called with could have originated from within
5785 // an embedded SVG document where some symbol instance elements
5786 // don't have nodeName defined on them, or strings are of type
5787 // SVGAnimatedString.
5788 if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
5789 break;
5790 }
5791 ele = ele.parentNode;
5792 }
5793 return ele;
5794 }
5795
5796 $.mobile.loadPage = function( url, opts ) {
5797 var container;
5798
5799 opts = opts || {};
5800 container = ( opts.pageContainer || $.mobile.pageContainer );
5801
5802 // create the deferred that will be supplied to loadPage callers
5803 // and resolved by the content widget's load method
5804 opts.deferred = $.Deferred();
5805
5806 // Preferring to allow exceptions for uninitialized opts.pageContainer
5807 // widgets so we know if we need to force init here for users
5808 container.pagecontainer( "load", url, opts );
5809
5810 // provide the deferred
5811 return opts.deferred.promise();
5812 };
5813
5814 //define vars for interal use
5815
5816 /* internal utility functions */
5817
5818 // NOTE Issue #4950 Android phonegap doesn't navigate back properly
5819 // when a full page refresh has taken place. It appears that hashchange
5820 // and replacestate history alterations work fine but we need to support
5821 // both forms of history traversal in our code that uses backward history
5822 // movement
5823 $.mobile.back = function() {
5824 var nav = window.navigator;
5825
5826 // if the setting is on and the navigator object is
5827 // available use the phonegap navigation capability
5828 if ( this.phonegapNavigationEnabled &&
5829 nav &&
5830 nav.app &&
5831 nav.app.backHistory ) {
5832 nav.app.backHistory();
5833 } else {
5834 $.mobile.pageContainer.pagecontainer( "back" );
5835 }
5836 };
5837
5838 // No-op implementation of transition degradation
5839 $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
5840 return transition;
5841 };
5842
5843 // Exposed $.mobile methods
5844
5845 $.mobile.changePage = function( to, options ) {
5846 $.mobile.pageContainer.pagecontainer( "change", to, options );
5847 };
5848
5849 $.mobile.changePage.defaults = {
5850 transition: undefined,
5851 reverse: false,
5852 changeHash: true,
5853 fromHashChange: false,
5854 role: undefined, // By default we rely on the role defined by the @data-role attribute.
5855 duplicateCachedPage: undefined,
5856 pageContainer: undefined,
5857 showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
5858 dataUrl: undefined,
5859 fromPage: undefined,
5860 allowSamePageTransition: false
5861 };
5862
5863 $.mobile._registerInternalEvents = function() {
5864 var getAjaxFormData = function( $form, calculateOnly ) {
5865 var url, ret = true, formData, vclickedName, method;
5866 if ( !$.mobile.ajaxEnabled ||
5867 // test that the form is, itself, ajax false
5868 $form.is( ":jqmData(ajax='false')" ) ||
5869 // test that $.mobile.ignoreContentEnabled is set and
5870 // the form or one of it's parents is ajax=false
5871 !$form.jqmHijackable().length ||
5872 $form.attr( "target" ) ) {
5873 return false;
5874 }
5875
5876 url = ( $lastVClicked && $lastVClicked.attr( "formaction" ) ) ||
5877 $form.attr( "action" );
5878 method = ( $form.attr( "method" ) || "get" ).toLowerCase();
5879
5880 // If no action is specified, browsers default to using the
5881 // URL of the document containing the form. Since we dynamically
5882 // pull in pages from external documents, the form should submit
5883 // to the URL for the source document of the page containing
5884 // the form.
5885 if ( !url ) {
5886 // Get the @data-url for the page containing the form.
5887 url = $.mobile.getClosestBaseUrl( $form );
5888
5889 // NOTE: If the method is "get", we need to strip off the query string
5890 // because it will get replaced with the new form data. See issue #5710.
5891 if ( method === "get" ) {
5892 url = path.parseUrl( url ).hrefNoSearch;
5893 }
5894
5895 if ( url === path.documentBase.hrefNoHash ) {
5896 // The url we got back matches the document base,
5897 // which means the page must be an internal/embedded page,
5898 // so default to using the actual document url as a browser
5899 // would.
5900 url = documentUrl.hrefNoSearch;
5901 }
5902 }
5903
5904 url = path.makeUrlAbsolute( url, $.mobile.getClosestBaseUrl( $form ) );
5905
5906 if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) {
5907 return false;
5908 }
5909
5910 if ( !calculateOnly ) {
5911 formData = $form.serializeArray();
5912
5913 if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) {
5914 vclickedName = $lastVClicked.attr( "name" );
5915 if ( vclickedName ) {
5916 // Make sure the last clicked element is included in the form
5917 $.each( formData, function( key, value ) {
5918 if ( value.name === vclickedName ) {
5919 // Unset vclickedName - we've found it in the serialized data already
5920 vclickedName = "";
5921 return false;
5922 }
5923 });
5924 if ( vclickedName ) {
5925 formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } );
5926 }
5927 }
5928 }
5929
5930 ret = {
5931 url: url,
5932 options: {
5933 type: method,
5934 data: $.param( formData ),
5935 transition: $form.jqmData( "transition" ),
5936 reverse: $form.jqmData( "direction" ) === "reverse",
5937 reloadPage: true
5938 }
5939 };
5940 }
5941
5942 return ret;
5943 };
5944
5945 //bind to form submit events, handle with Ajax
5946 $.mobile.document.delegate( "form", "submit", function( event ) {
5947 var formData;
5948
5949 if ( !event.isDefaultPrevented() ) {
5950 formData = getAjaxFormData( $( this ) );
5951 if ( formData ) {
5952 $.mobile.changePage( formData.url, formData.options );
5953 event.preventDefault();
5954 }
5955 }
5956 });
5957
5958 //add active state on vclick
5959 $.mobile.document.bind( "vclick", function( event ) {
5960 var theButton, target = event.target;
5961 // if this isn't a left click we don't care. Its important to note
5962 // that when the virtual event is generated it will create the which attr
5963 if ( event.which > 1 || !$.mobile.linkBindingEnabled ) {
5964 return;
5965 }
5966
5967 // Record that this element was clicked, in case we need it for correct
5968 // form submission during the "submit" handler above
5969 $lastVClicked = $( target );
5970
5971 // Try to find a target element to which the active class will be applied
5972 if ( $.data( target, "mobile-button" ) ) {
5973 // If the form will not be submitted via AJAX, do not add active class
5974 if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) {
5975 return;
5976 }
5977 // We will apply the active state to this button widget - the parent
5978 // of the input that was clicked will have the associated data
5979 if ( target.parentNode ) {
5980 target = target.parentNode;
5981 }
5982 } else {
5983 target = findClosestLink( target );
5984 if ( !target ||
5985 ( path.parseUrl( target.getAttribute( "href" ) || "#" ).hash === "#" &&
5986 target.getAttribute( "data-" + $.mobile.ns + "rel" ) !== "back" ) ) {
5987 return;
5988 }
5989
5990 // TODO teach $.mobile.hijackable to operate on raw dom elements so the
5991 // link wrapping can be avoided
5992 if ( !$( target ).jqmHijackable().length ) {
5993 return;
5994 }
5995 }
5996
5997 theButton = $( target ).closest( ".ui-btn" );
5998
5999 if ( theButton.length > 0 &&
6000 !( theButton.hasClass( "ui-state-disabled" ||
6001
6002 // DEPRECATED as of 1.4.0 - remove after 1.4.0 release
6003 // only ui-state-disabled should be present thereafter
6004 theButton.hasClass( "ui-disabled" ) ) ) ) {
6005 $.mobile.removeActiveLinkClass( true );
6006 $.mobile.activeClickedLink = theButton;
6007 $.mobile.activeClickedLink.addClass( $.mobile.activeBtnClass );
6008 }
6009 });
6010
6011 // click routing - direct to HTTP or Ajax, accordingly
6012 $.mobile.document.bind( "click", function( event ) {
6013 if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) {
6014 return;
6015 }
6016
6017 var link = findClosestLink( event.target ),
6018 $link = $( link ),
6019
6020 //remove active link class if external (then it won't be there if you come back)
6021 httpCleanup = function() {
6022 window.setTimeout(function() { $.mobile.removeActiveLinkClass( true ); }, 200 );
6023 },
6024 baseUrl, href,
6025 useDefaultUrlHandling, isExternal,
6026 transition, reverse, role;
6027
6028 // If a button was clicked, clean up the active class added by vclick above
6029 if ( $.mobile.activeClickedLink &&
6030 $.mobile.activeClickedLink[ 0 ] === event.target.parentNode ) {
6031 httpCleanup();
6032 }
6033
6034 // If there is no link associated with the click or its not a left
6035 // click we want to ignore the click
6036 // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
6037 // can be avoided
6038 if ( !link || event.which > 1 || !$link.jqmHijackable().length ) {
6039 return;
6040 }
6041
6042 //if there's a data-rel=back attr, go back in history
6043 if ( $link.is( ":jqmData(rel='back')" ) ) {
6044 $.mobile.back();
6045 return false;
6046 }
6047
6048 baseUrl = $.mobile.getClosestBaseUrl( $link );
6049
6050 //get href, if defined, otherwise default to empty hash
6051 href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
6052
6053 //if ajax is disabled, exit early
6054 if ( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ) {
6055 httpCleanup();
6056 //use default click handling
6057 return;
6058 }
6059
6060 // XXX_jblas: Ideally links to application pages should be specified as
6061 // an url to the application document with a hash that is either
6062 // the site relative path or id to the page. But some of the
6063 // internal code that dynamically generates sub-pages for nested
6064 // lists and select dialogs, just write a hash in the link they
6065 // create. This means the actual URL path is based on whatever
6066 // the current value of the base tag is at the time this code
6067 // is called.
6068 if ( href.search( "#" ) !== -1 &&
6069 !( path.isExternal( href ) && path.isAbsoluteUrl( href ) ) ) {
6070
6071 href = href.replace( /[^#]*#/, "" );
6072 if ( !href ) {
6073 //link was an empty hash meant purely
6074 //for interaction, so we ignore it.
6075 event.preventDefault();
6076 return;
6077 } else if ( path.isPath( href ) ) {
6078 //we have apath so make it the href we want to load.
6079 href = path.makeUrlAbsolute( href, baseUrl );
6080 } else {
6081 //we have a simple id so use the documentUrl as its base.
6082 href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
6083 }
6084 }
6085
6086 // Should we handle this link, or let the browser deal with it?
6087 useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" );
6088
6089 // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
6090 // requests if the document doing the request was loaded via the file:// protocol.
6091 // This is usually to allow the application to "phone home" and fetch app specific
6092 // data. We normally let the browser handle external/cross-domain urls, but if the
6093 // allowCrossDomainPages option is true, we will allow cross-domain http/https
6094 // requests to go through our page loading logic.
6095
6096 //check for protocol or rel and its not an embedded page
6097 //TODO overlap in logic from isExternal, rel=external check should be
6098 // moved into more comprehensive isExternalLink
6099 isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) );
6100
6101 if ( isExternal ) {
6102 httpCleanup();
6103 //use default click handling
6104 return;
6105 }
6106
6107 //use ajax
6108 transition = $link.jqmData( "transition" );
6109 reverse = $link.jqmData( "direction" ) === "reverse" ||
6110 // deprecated - remove by 1.0
6111 $link.jqmData( "back" );
6112
6113 //this may need to be more specific as we use data-rel more
6114 role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
6115
6116 $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } );
6117 event.preventDefault();
6118 });
6119
6120 //prefetch pages when anchors with data-prefetch are encountered
6121 $.mobile.document.delegate( ".ui-page", "pageshow.prefetch", function() {
6122 var urls = [];
6123 $( this ).find( "a:jqmData(prefetch)" ).each(function() {
6124 var $link = $( this ),
6125 url = $link.attr( "href" );
6126
6127 if ( url && $.inArray( url, urls ) === -1 ) {
6128 urls.push( url );
6129
6130 $.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ),prefetch: true } );
6131 }
6132 });
6133 });
6134
6135 // TODO ensure that the navigate binding in the content widget happens at the right time
6136 $.mobile.pageContainer.pagecontainer();
6137
6138 //set page min-heights to be device specific
6139 $.mobile.document.bind( "pageshow", function() {
6140
6141 // We need to wait for window.load to make sure that styles have already been rendered,
6142 // otherwise heights of external toolbars will have the wrong value
6143 if ( loadDeferred ) {
6144 loadDeferred.done( $.mobile.resetActivePageHeight );
6145 } else {
6146 $.mobile.resetActivePageHeight();
6147 }
6148 });
6149 $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );
6150
6151 };//navreadyDeferred done callback
6152
6153 $( function() { domreadyDeferred.resolve(); } );
6154
6155 // Account for the possibility that the load event has already fired
6156 if ( document.readyState === "complete" ) {
6157 pageIsFullyLoaded();
6158 } else {
6159 $.mobile.window.load( pageIsFullyLoaded );
6160 }
6161
6162 $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } );
6163})( jQuery );
6164
6165(function( $, undefined ) {
6166
6167$.mobile.degradeInputs = {
6168 color: false,
6169 date: false,
6170 datetime: false,
6171 "datetime-local": false,
6172 email: false,
6173 month: false,
6174 number: false,
6175 range: "number",
6176 search: "text",
6177 tel: false,
6178 time: false,
6179 url: false,
6180 week: false
6181};
6182// Backcompat remove in 1.5
6183$.mobile.page.prototype.options.degradeInputs = $.mobile.degradeInputs;
6184
6185// Auto self-init widgets
6186$.mobile.degradeInputsWithin = function( target ) {
6187
6188 target = $( target );
6189
6190 // Degrade inputs to avoid poorly implemented native functionality
6191 target.find( "input" ).not( $.mobile.page.prototype.keepNativeSelector() ).each(function() {
6192 var element = $( this ),
6193 type = this.getAttribute( "type" ),
6194 optType = $.mobile.degradeInputs[ type ] || "text",
6195 html, hasType, findstr, repstr;
6196
6197 if ( $.mobile.degradeInputs[ type ] ) {
6198 html = $( "<div>" ).html( element.clone() ).html();
6199 // In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead
6200 hasType = html.indexOf( " type=" ) > -1;
6201 findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/;
6202 repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" );
6203
6204 element.replaceWith( html.replace( findstr, repstr ) );
6205 }
6206 });
6207
6208};
6209
6210})( jQuery );
6211
6212(function( $, window, undefined ) {
6213
6214$.widget( "mobile.page", $.mobile.page, {
6215 options: {
6216
6217 // Accepts left, right and none
6218 closeBtn: "left",
6219 closeBtnText: "Close",
6220 overlayTheme: "a",
6221 corners: true,
6222 dialog: false
6223 },
6224
6225 _create: function() {
6226 this._super();
6227 if ( this.options.dialog ) {
6228
6229 $.extend( this, {
6230 _inner: this.element.children(),
6231 _headerCloseButton: null
6232 });
6233
6234 if ( !this.options.enhanced ) {
6235 this._setCloseBtn( this.options.closeBtn );
6236 }
6237 }
6238 },
6239
6240 _enhance: function() {
6241 this._super();
6242
6243 // Class the markup for dialog styling and wrap interior
6244 if ( this.options.dialog ) {
6245 this.element.addClass( "ui-dialog" )
6246 .wrapInner( $( "<div/>", {
6247
6248 // ARIA role
6249 "role" : "dialog",
6250 "class" : "ui-dialog-contain ui-overlay-shadow" +
6251 ( this.options.corners ? " ui-corner-all" : "" )
6252 }));
6253 }
6254 },
6255
6256 _setOptions: function( options ) {
6257 var closeButtonLocation, closeButtonText,
6258 currentOpts = this.options;
6259
6260 if ( options.corners !== undefined ) {
6261 this._inner.toggleClass( "ui-corner-all", !!options.corners );
6262 }
6263
6264 if ( options.overlayTheme !== undefined ) {
6265 if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) {
6266 currentOpts.overlayTheme = options.overlayTheme;
6267 this._handlePageBeforeShow();
6268 }
6269 }
6270
6271 if ( options.closeBtnText !== undefined ) {
6272 closeButtonLocation = currentOpts.closeBtn;
6273 closeButtonText = options.closeBtnText;
6274 }
6275
6276 if ( options.closeBtn !== undefined ) {
6277 closeButtonLocation = options.closeBtn;
6278 }
6279
6280 if ( closeButtonLocation ) {
6281 this._setCloseBtn( closeButtonLocation, closeButtonText );
6282 }
6283
6284 this._super( options );
6285 },
6286
6287 _handlePageBeforeShow: function () {
6288 if ( this.options.overlayTheme && this.options.dialog ) {
6289 this.removeContainerBackground();
6290 this.setContainerBackground( this.options.overlayTheme );
6291 } else {
6292 this._super();
6293 }
6294 },
6295
6296 _setCloseBtn: function( location, text ) {
6297 var dst,
6298 btn = this._headerCloseButton;
6299
6300 // Sanitize value
6301 location = "left" === location ? "left" : "right" === location ? "right" : "none";
6302
6303 if ( "none" === location ) {
6304 if ( btn ) {
6305 btn.remove();
6306 btn = null;
6307 }
6308 } else if ( btn ) {
6309 btn.removeClass( "ui-btn-left ui-btn-right" ).addClass( "ui-btn-" + location );
6310 if ( text ) {
6311 btn.text( text );
6312 }
6313 } else {
6314 dst = this._inner.find( ":jqmData(role='header')" ).first();
6315 btn = $( "<a></a>", {
6316 "href": "#",
6317 "class": "ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-" + location
6318 })
6319 .attr( "data-" + $.mobile.ns + "rel", "back" )
6320 .text( text || this.options.closeBtnText || "" )
6321 .prependTo( dst );
6322 }
6323
6324 this._headerCloseButton = btn;
6325 }
6326});
6327
6328})( jQuery, this );
6329
6330(function( $, window, undefined ) {
6331
6332$.widget( "mobile.dialog", {
6333 options: {
6334
6335 // Accepts left, right and none
6336 closeBtn: "left",
6337 closeBtnText: "Close",
6338 overlayTheme: "a",
6339 corners: true
6340 },
6341
6342 // Override the theme set by the page plugin on pageshow
6343 _handlePageBeforeShow: function() {
6344 this._isCloseable = true;
6345 if ( this.options.overlayTheme ) {
6346 this.element
6347 .page( "removeContainerBackground" )
6348 .page( "setContainerBackground", this.options.overlayTheme );
6349 }
6350 },
6351
6352 _handlePageBeforeHide: function() {
6353 this._isCloseable = false;
6354 },
6355
6356 // click and submit events:
6357 // - clicks and submits should use the closing transition that the dialog
6358 // opened with unless a data-transition is specified on the link/form
6359 // - if the click was on the close button, or the link has a data-rel="back"
6360 // it'll go back in history naturally
6361 _handleVClickSubmit: function( event ) {
6362 var attrs,
6363 $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" );
6364
6365 if ( $target.length && !$target.jqmData( "transition" ) ) {
6366 attrs = {};
6367 attrs[ "data-" + $.mobile.ns + "transition" ] =
6368 ( $.mobile.navigate.history.getActive() || {} )[ "transition" ] ||
6369 $.mobile.defaultDialogTransition;
6370 attrs[ "data-" + $.mobile.ns + "direction" ] = "reverse";
6371 $target.attr( attrs );
6372 }
6373 },
6374
6375 _create: function() {
6376 var elem = this.element,
6377 opts = this.options;
6378
6379 // Class the markup for dialog styling and wrap interior
6380 elem.addClass( "ui-dialog" )
6381 .wrapInner( $( "<div/>", {
6382
6383 // ARIA role
6384 "role" : "dialog",
6385 "class" : "ui-dialog-contain ui-overlay-shadow" +
6386 ( !!opts.corners ? " ui-corner-all" : "" )
6387 }));
6388
6389 $.extend( this, {
6390 _isCloseable: false,
6391 _inner: elem.children(),
6392 _headerCloseButton: null
6393 });
6394
6395 this._on( elem, {
6396 vclick: "_handleVClickSubmit",
6397 submit: "_handleVClickSubmit",
6398 pagebeforeshow: "_handlePageBeforeShow",
6399 pagebeforehide: "_handlePageBeforeHide"
6400 });
6401
6402 this._setCloseBtn( opts.closeBtn );
6403 },
6404
6405 _setOptions: function( options ) {
6406 var closeButtonLocation, closeButtonText,
6407 currentOpts = this.options;
6408
6409 if ( options.corners !== undefined ) {
6410 this._inner.toggleClass( "ui-corner-all", !!options.corners );
6411 }
6412
6413 if ( options.overlayTheme !== undefined ) {
6414 if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) {
6415 currentOpts.overlayTheme = options.overlayTheme;
6416 this._handlePageBeforeShow();
6417 }
6418 }
6419
6420 if ( options.closeBtnText !== undefined ) {
6421 closeButtonLocation = currentOpts.closeBtn;
6422 closeButtonText = options.closeBtnText;
6423 }
6424
6425 if ( options.closeBtn !== undefined ) {
6426 closeButtonLocation = options.closeBtn;
6427 }
6428
6429 if ( closeButtonLocation ) {
6430 this._setCloseBtn( closeButtonLocation, closeButtonText );
6431 }
6432
6433 this._super( options );
6434 },
6435
6436 _setCloseBtn: function( location, text ) {
6437 var dst,
6438 btn = this._headerCloseButton;
6439
6440 // Sanitize value
6441 location = "left" === location ? "left" : "right" === location ? "right" : "none";
6442
6443 if ( "none" === location ) {
6444 if ( btn ) {
6445 btn.remove();
6446 btn = null;
6447 }
6448 } else if ( btn ) {
6449 btn.removeClass( "ui-btn-left ui-btn-right" ).addClass( "ui-btn-" + location );
6450 if ( text ) {
6451 btn.text( text );
6452 }
6453 } else {
6454 dst = this._inner.find( ":jqmData(role='header')" ).first();
6455 btn = $( "<a></a>", {
6456 "role": "button",
6457 "href": "#",
6458 "class": "ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-" + location
6459 })
6460 .text( text || this.options.closeBtnText || "" )
6461 .prependTo( dst );
6462 this._on( btn, { click: "close" } );
6463 }
6464
6465 this._headerCloseButton = btn;
6466 },
6467
6468 // Close method goes back in history
6469 close: function() {
6470 var hist = $.mobile.navigate.history;
6471
6472 if ( this._isCloseable ) {
6473 this._isCloseable = false;
6474 // If the hash listening is enabled and there is at least one preceding history
6475 // entry it's ok to go back. Initial pages with the dialog hash state are an example
6476 // where the stack check is necessary
6477 if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) {
6478 $.mobile.back();
6479 } else {
6480 $.mobile.pageContainer.pagecontainer( "back" );
6481 }
6482 }
6483 }
6484});
6485
6486})( jQuery, this );
6487
6488(function( $, undefined ) {
6489
6490var rInitialLetter = /([A-Z])/g,
6491
6492 // Construct iconpos class from iconpos value
6493 iconposClass = function( iconpos ) {
6494 return ( "ui-btn-icon-" + ( iconpos === null ? "left" : iconpos ) );
6495 };
6496
6497$.widget( "mobile.collapsible", {
6498 options: {
6499 enhanced: false,
6500 expandCueText: null,
6501 collapseCueText: null,
6502 collapsed: true,
6503 heading: "h1,h2,h3,h4,h5,h6,legend",
6504 collapsedIcon: null,
6505 expandedIcon: null,
6506 iconpos: null,
6507 theme: null,
6508 contentTheme: null,
6509 inset: null,
6510 corners: null,
6511 mini: null
6512 },
6513
6514 _create: function() {
6515 var elem = this.element,
6516 ui = {
6517 accordion: elem
6518 .closest( ":jqmData(role='collapsible-set')," +
6519 ":jqmData(role='collapsibleset')" +
6520 ( $.mobile.collapsibleset ? ", :mobile-collapsibleset" :
6521 "" ) )
6522 .addClass( "ui-collapsible-set" )
6523 };
6524
6525 this._ui = ui;
6526 this._renderedOptions = this._getOptions( this.options );
6527
6528 if ( this.options.enhanced ) {
6529 ui.heading = this.element.children( ".ui-collapsible-heading" );
6530 ui.content = ui.heading.next();
6531 ui.anchor = ui.heading.children();
6532 ui.status = ui.anchor.children( ".ui-collapsible-heading-status" );
6533 } else {
6534 this._enhance( elem, ui );
6535 }
6536
6537 this._on( ui.heading, {
6538 "tap": function() {
6539 ui.heading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
6540 },
6541
6542 "click": function( event ) {
6543 this._handleExpandCollapse( !ui.heading.hasClass( "ui-collapsible-heading-collapsed" ) );
6544 event.preventDefault();
6545 event.stopPropagation();
6546 }
6547 });
6548 },
6549
6550 // Adjust the keys inside options for inherited values
6551 _getOptions: function( options ) {
6552 var key,
6553 accordion = this._ui.accordion,
6554 accordionWidget = this._ui.accordionWidget;
6555
6556 // Copy options
6557 options = $.extend( {}, options );
6558
6559 if ( accordion.length && !accordionWidget ) {
6560 this._ui.accordionWidget =
6561 accordionWidget = accordion.data( "mobile-collapsibleset" );
6562 }
6563
6564 for ( key in options ) {
6565
6566 // Retrieve the option value first from the options object passed in and, if
6567 // null, from the parent accordion or, if that's null too, or if there's no
6568 // parent accordion, then from the defaults.
6569 options[ key ] =
6570 ( options[ key ] != null ) ? options[ key ] :
6571 ( accordionWidget ) ? accordionWidget.options[ key ] :
6572 accordion.length ? $.mobile.getAttribute( accordion[ 0 ],
6573 key.replace( rInitialLetter, "-$1" ).toLowerCase() ):
6574 null;
6575
6576 if ( null == options[ key ] ) {
6577 options[ key ] = $.mobile.collapsible.defaults[ key ];
6578 }
6579 }
6580
6581 return options;
6582 },
6583
6584 _themeClassFromOption: function( prefix, value ) {
6585 return ( value ? ( value === "none" ? "" : prefix + value ) : "" );
6586 },
6587
6588 _enhance: function( elem, ui ) {
6589 var iconclass,
6590 opts = this._renderedOptions,
6591 contentThemeClass = this._themeClassFromOption( "ui-body-", opts.contentTheme );
6592
6593 elem.addClass( "ui-collapsible " +
6594 ( opts.inset ? "ui-collapsible-inset " : "" ) +
6595 ( opts.inset && opts.corners ? "ui-corner-all " : "" ) +
6596 ( contentThemeClass ? "ui-collapsible-themed-content " : "" ) );
6597 ui.originalHeading = elem.children( this.options.heading ).first(),
6598 ui.content = elem
6599 .wrapInner( "<div " +
6600 "class='ui-collapsible-content " +
6601 contentThemeClass + "'></div>" )
6602 .children( ".ui-collapsible-content" ),
6603 ui.heading = ui.originalHeading;
6604
6605 // Replace collapsibleHeading if it's a legend
6606 if ( ui.heading.is( "legend" ) ) {
6607 ui.heading = $( "<div role='heading'>"+ ui.heading.html() +"</div>" );
6608 ui.placeholder = $( "<div><!-- placeholder for legend --></div>" ).insertBefore( ui.originalHeading );
6609 ui.originalHeading.remove();
6610 }
6611
6612 iconclass = ( opts.collapsed ? ( opts.collapsedIcon ? "ui-icon-" + opts.collapsedIcon : "" ):
6613 ( opts.expandedIcon ? "ui-icon-" + opts.expandedIcon : "" ) );
6614
6615 ui.status = $( "<span class='ui-collapsible-heading-status'></span>" );
6616 ui.anchor = ui.heading
6617 .detach()
6618 //modify markup & attributes
6619 .addClass( "ui-collapsible-heading" )
6620 .append( ui.status )
6621 .wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
6622 .find( "a" )
6623 .first()
6624 .addClass( "ui-btn " +
6625 ( iconclass ? iconclass + " " : "" ) +
6626 ( iconclass ? iconposClass( opts.iconpos ) +
6627 " " : "" ) +
6628 this._themeClassFromOption( "ui-btn-", opts.theme ) + " " +
6629 ( opts.mini ? "ui-mini " : "" ) );
6630
6631 //drop heading in before content
6632 ui.heading.insertBefore( ui.content );
6633
6634 this._handleExpandCollapse( this.options.collapsed );
6635
6636 return ui;
6637 },
6638
6639 refresh: function() {
6640 this._applyOptions( this.options );
6641 this._renderedOptions = this._getOptions( this.options );
6642 },
6643
6644 _applyOptions: function( options ) {
6645 var isCollapsed, newTheme, oldTheme, hasCorners, hasIcon,
6646 elem = this.element,
6647 currentOpts = this._renderedOptions,
6648 ui = this._ui,
6649 anchor = ui.anchor,
6650 status = ui.status,
6651 opts = this._getOptions( options );
6652
6653 // First and foremost we need to make sure the collapsible is in the proper
6654 // state, in case somebody decided to change the collapsed option at the
6655 // same time as another option
6656 if ( options.collapsed !== undefined ) {
6657 this._handleExpandCollapse( options.collapsed );
6658 }
6659
6660 isCollapsed = elem.hasClass( "ui-collapsible-collapsed" );
6661
6662 // We only need to apply the cue text for the current state right away.
6663 // The cue text for the alternate state will be stored in the options
6664 // and applied the next time the collapsible's state is toggled
6665 if ( isCollapsed ) {
6666 if ( opts.expandCueText !== undefined ) {
6667 status.text( opts.expandCueText );
6668 }
6669 } else {
6670 if ( opts.collapseCueText !== undefined ) {
6671 status.text( opts.collapseCueText );
6672 }
6673 }
6674
6675 // Update icon
6676
6677 // Is it supposed to have an icon?
6678 hasIcon =
6679
6680 // If the collapsedIcon is being set, consult that
6681 ( opts.collapsedIcon !== undefined ? opts.collapsedIcon !== false :
6682
6683 // Otherwise consult the existing option value
6684 currentOpts.collapsedIcon !== false );
6685
6686
6687 // If any icon-related options have changed, make sure the new icon
6688 // state is reflected by first removing all icon-related classes
6689 // reflecting the current state and then adding all icon-related
6690 // classes for the new state
6691 if ( !( opts.iconpos === undefined &&
6692 opts.collapsedIcon === undefined &&
6693 opts.expandedIcon === undefined ) ) {
6694
6695 // Remove all current icon-related classes
6696 anchor.removeClass( [ iconposClass( currentOpts.iconpos ) ]
6697 .concat( ( currentOpts.expandedIcon ?
6698 [ "ui-icon-" + currentOpts.expandedIcon ] : [] ) )
6699 .concat( ( currentOpts.collapsedIcon ?
6700 [ "ui-icon-" + currentOpts.collapsedIcon ] : [] ) )
6701 .join( " " ) );
6702
6703 // Add new classes if an icon is supposed to be present
6704 if ( hasIcon ) {
6705 anchor.addClass(
6706 [ iconposClass( opts.iconpos !== undefined ?
6707 opts.iconpos : currentOpts.iconpos ) ]
6708 .concat( isCollapsed ?
6709 [ "ui-icon-" + ( opts.collapsedIcon !== undefined ?
6710 opts.collapsedIcon :
6711 currentOpts.collapsedIcon ) ] :
6712 [ "ui-icon-" + ( opts.expandedIcon !== undefined ?
6713 opts.expandedIcon :
6714 currentOpts.expandedIcon ) ] )
6715 .join( " " ) );
6716 }
6717 }
6718
6719 if ( opts.theme !== undefined ) {
6720 oldTheme = this._themeClassFromOption( "ui-btn-", currentOpts.theme );
6721 newTheme = this._themeClassFromOption( "ui-btn-", opts.theme );
6722 anchor.removeClass( oldTheme ).addClass( newTheme );
6723 }
6724
6725 if ( opts.contentTheme !== undefined ) {
6726 oldTheme = this._themeClassFromOption( "ui-body-",
6727 currentOpts.contentTheme );
6728 newTheme = this._themeClassFromOption( "ui-body-",
6729 opts.contentTheme );
6730 ui.content.removeClass( oldTheme ).addClass( newTheme );
6731 }
6732
6733 if ( opts.inset !== undefined ) {
6734 elem.toggleClass( "ui-collapsible-inset", opts.inset );
6735 hasCorners = !!( opts.inset && ( opts.corners || currentOpts.corners ) );
6736 }
6737
6738 if ( opts.corners !== undefined ) {
6739 hasCorners = !!( opts.corners && ( opts.inset || currentOpts.inset ) );
6740 }
6741
6742 if ( hasCorners !== undefined ) {
6743 elem.toggleClass( "ui-corner-all", hasCorners );
6744 }
6745
6746 if ( opts.mini !== undefined ) {
6747 anchor.toggleClass( "ui-mini", opts.mini );
6748 }
6749 },
6750
6751 _setOptions: function( options ) {
6752 this._applyOptions( options );
6753 this._super( options );
6754 this._renderedOptions = this._getOptions( this.options );
6755 },
6756
6757 _handleExpandCollapse: function( isCollapse ) {
6758 var opts = this._renderedOptions,
6759 ui = this._ui;
6760
6761 ui.status.text( isCollapse ? opts.expandCueText : opts.collapseCueText );
6762 ui.heading
6763 .toggleClass( "ui-collapsible-heading-collapsed", isCollapse )
6764 .find( "a" ).first()
6765 .toggleClass( "ui-icon-" + opts.expandedIcon, !isCollapse )
6766
6767 // logic or cause same icon for expanded/collapsed state would remove the ui-icon-class
6768 .toggleClass( "ui-icon-" + opts.collapsedIcon, ( isCollapse || opts.expandedIcon === opts.collapsedIcon ) )
6769 .removeClass( $.mobile.activeBtnClass );
6770
6771 this.element.toggleClass( "ui-collapsible-collapsed", isCollapse );
6772 ui.content
6773 .toggleClass( "ui-collapsible-content-collapsed", isCollapse )
6774 .attr( "aria-hidden", isCollapse )
6775 .trigger( "updatelayout" );
6776 this.options.collapsed = isCollapse;
6777 this._trigger( isCollapse ? "collapse" : "expand" );
6778 },
6779
6780 expand: function() {
6781 this._handleExpandCollapse( false );
6782 },
6783
6784 collapse: function() {
6785 this._handleExpandCollapse( true );
6786 },
6787
6788 _destroy: function() {
6789 var ui = this._ui,
6790 opts = this.options;
6791
6792 if ( opts.enhanced ) {
6793 return;
6794 }
6795
6796 if ( ui.placeholder ) {
6797 ui.originalHeading.insertBefore( ui.placeholder );
6798 ui.placeholder.remove();
6799 ui.heading.remove();
6800 } else {
6801 ui.status.remove();
6802 ui.heading
6803 .removeClass( "ui-collapsible-heading ui-collapsible-heading-collapsed" )
6804 .children()
6805 .contents()
6806 .unwrap();
6807 }
6808
6809 ui.anchor.contents().unwrap();
6810 ui.content.contents().unwrap();
6811 this.element
6812 .removeClass( "ui-collapsible ui-collapsible-collapsed " +
6813 "ui-collapsible-themed-content ui-collapsible-inset ui-corner-all" );
6814 }
6815});
6816
6817// Defaults to be used by all instances of collapsible if per-instance values
6818// are unset or if nothing is specified by way of inheritance from an accordion.
6819// Note that this hash does not contain options "collapsed" or "heading",
6820// because those are not inheritable.
6821$.mobile.collapsible.defaults = {
6822 expandCueText: " click to expand contents",
6823 collapseCueText: " click to collapse contents",
6824 collapsedIcon: "plus",
6825 contentTheme: "inherit",
6826 expandedIcon: "minus",
6827 iconpos: "left",
6828 inset: true,
6829 corners: true,
6830 theme: "inherit",
6831 mini: false
6832};
6833
6834})( jQuery );
6835
6836(function( $, undefined ) {
6837
6838var uiScreenHiddenRegex = /\bui-screen-hidden\b/;
6839function noHiddenClass( elements ) {
6840 var index,
6841 length = elements.length,
6842 result = [];
6843
6844 for ( index = 0; index < length; index++ ) {
6845 if ( !elements[ index ].className.match( uiScreenHiddenRegex ) ) {
6846 result.push( elements[ index ] );
6847 }
6848 }
6849
6850 return $( result );
6851}
6852
6853$.mobile.behaviors.addFirstLastClasses = {
6854 _getVisibles: function( $els, create ) {
6855 var visibles;
6856
6857 if ( create ) {
6858 visibles = noHiddenClass( $els );
6859 } else {
6860 visibles = $els.filter( ":visible" );
6861 if ( visibles.length === 0 ) {
6862 visibles = noHiddenClass( $els );
6863 }
6864 }
6865
6866 return visibles;
6867 },
6868
6869 _addFirstLastClasses: function( $els, $visibles, create ) {
6870 $els.removeClass( "ui-first-child ui-last-child" );
6871 $visibles.eq( 0 ).addClass( "ui-first-child" ).end().last().addClass( "ui-last-child" );
6872 if ( !create ) {
6873 this.element.trigger( "updatelayout" );
6874 }
6875 },
6876
6877 _removeFirstLastClasses: function( $els ) {
6878 $els.removeClass( "ui-first-child ui-last-child" );
6879 }
6880};
6881
6882})( jQuery );
6883
6884(function( $, undefined ) {
6885
6886var childCollapsiblesSelector = ":mobile-collapsible, " + $.mobile.collapsible.initSelector;
6887
6888$.widget( "mobile.collapsibleset", $.extend( {
6889
6890 // The initSelector is deprecated as of 1.4.0. In 1.5.0 we will use
6891 // :jqmData(role='collapsibleset') which will allow us to get rid of the line
6892 // below altogether, because the autoinit will generate such an initSelector
6893 initSelector: ":jqmData(role='collapsible-set'),:jqmData(role='collapsibleset')",
6894
6895 options: $.extend( {
6896 enhanced: false
6897 }, $.mobile.collapsible.defaults ),
6898
6899 _handleCollapsibleExpand: function( event ) {
6900 var closestCollapsible = $( event.target ).closest( ".ui-collapsible" );
6901
6902 if ( closestCollapsible.parent().is( ":mobile-collapsibleset, :jqmData(role='collapsible-set')" ) ) {
6903 closestCollapsible
6904 .siblings( ".ui-collapsible:not(.ui-collapsible-collapsed)" )
6905 .collapsible( "collapse" );
6906 }
6907 },
6908
6909 _create: function() {
6910 var elem = this.element,
6911 opts = this.options;
6912
6913 $.extend( this, {
6914 _classes: ""
6915 });
6916
6917 if ( !opts.enhanced ) {
6918 elem.addClass( "ui-collapsible-set " +
6919 this._themeClassFromOption( "ui-group-theme-", opts.theme ) + " " +
6920 ( opts.corners && opts.inset ? "ui-corner-all " : "" ) );
6921 this.element.find( $.mobile.collapsible.initSelector ).collapsible();
6922 }
6923
6924 this._on( elem, { collapsibleexpand: "_handleCollapsibleExpand" } );
6925 },
6926
6927 _themeClassFromOption: function( prefix, value ) {
6928 return ( value ? ( value === "none" ? "" : prefix + value ) : "" );
6929 },
6930
6931 _init: function() {
6932 this._refresh( true );
6933
6934 // Because the corners are handled by the collapsible itself and the default state is collapsed
6935 // That was causing https://github.com/jquery/jquery-mobile/issues/4116
6936 this.element
6937 .children( childCollapsiblesSelector )
6938 .filter( ":jqmData(collapsed='false')" )
6939 .collapsible( "expand" );
6940 },
6941
6942 _setOptions: function( options ) {
6943 var ret, hasCorners,
6944 elem = this.element,
6945 themeClass = this._themeClassFromOption( "ui-group-theme-", options.theme );
6946
6947 if ( themeClass ) {
6948 elem
6949 .removeClass( this._themeClassFromOption( "ui-group-theme-", this.options.theme ) )
6950 .addClass( themeClass );
6951 }
6952
6953 if ( options.inset !== undefined ) {
6954 hasCorners = !!( options.inset && ( options.corners || this.options.corners ) );
6955 }
6956
6957 if ( options.corners !== undefined ) {
6958 hasCorners = !!( options.corners && ( options.inset || this.options.inset ) );
6959 }
6960
6961 if ( hasCorners !== undefined ) {
6962 elem.toggleClass( "ui-corner-all", hasCorners );
6963 }
6964
6965 ret = this._super( options );
6966 this.element.children( ":mobile-collapsible" ).collapsible( "refresh" );
6967 return ret;
6968 },
6969
6970 _destroy: function() {
6971 var el = this.element;
6972
6973 this._removeFirstLastClasses( el.children( childCollapsiblesSelector ) );
6974 el
6975 .removeClass( "ui-collapsible-set ui-corner-all " +
6976 this._themeClassFromOption( "ui-group-theme-", this.options.theme ) )
6977 .children( ":mobile-collapsible" )
6978 .collapsible( "destroy" );
6979 },
6980
6981 _refresh: function( create ) {
6982 var collapsiblesInSet = this.element.children( childCollapsiblesSelector );
6983
6984 this.element.find( $.mobile.collapsible.initSelector ).not( ".ui-collapsible" ).collapsible();
6985
6986 this._addFirstLastClasses( collapsiblesInSet, this._getVisibles( collapsiblesInSet, create ), create );
6987 },
6988
6989 refresh: function() {
6990 this._refresh( false );
6991 }
6992}, $.mobile.behaviors.addFirstLastClasses ) );
6993
6994})( jQuery );
6995
6996(function( $, undefined ) {
6997
6998// Deprecated in 1.4
6999$.fn.fieldcontain = function(/* options */) {
7000 return this.addClass( "ui-field-contain" );
7001};
7002
7003})( jQuery );
7004
7005(function( $, undefined ) {
7006
7007$.fn.grid = function( options ) {
7008 return this.each(function() {
7009
7010 var $this = $( this ),
7011 o = $.extend({
7012 grid: null
7013 }, options ),
7014 $kids = $this.children(),
7015 gridCols = { solo:1, a:2, b:3, c:4, d:5 },
7016 grid = o.grid,
7017 iterator,
7018 letter;
7019
7020 if ( !grid ) {
7021 if ( $kids.length <= 5 ) {
7022 for ( letter in gridCols ) {
7023 if ( gridCols[ letter ] === $kids.length ) {
7024 grid = letter;
7025 }
7026 }
7027 } else {
7028 grid = "a";
7029 $this.addClass( "ui-grid-duo" );
7030 }
7031 }
7032 iterator = gridCols[grid];
7033
7034 $this.addClass( "ui-grid-" + grid );
7035
7036 $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" );
7037
7038 if ( iterator > 1 ) {
7039 $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" );
7040 }
7041 if ( iterator > 2 ) {
7042 $kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" );
7043 }
7044 if ( iterator > 3 ) {
7045 $kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" );
7046 }
7047 if ( iterator > 4 ) {
7048 $kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" );
7049 }
7050 });
7051};
7052})( jQuery );
7053
7054(function( $, undefined ) {
7055
7056$.widget( "mobile.navbar", {
7057 options: {
7058 iconpos: "top",
7059 grid: null
7060 },
7061
7062 _create: function() {
7063
7064 var $navbar = this.element,
7065 $navbtns = $navbar.find( "a, button" ),
7066 self = this,
7067 iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? this.options.iconpos : undefined;
7068
7069 $navbar.addClass( "ui-navbar" )
7070 .attr( "role", "navigation" )
7071 .find( "ul" )
7072 .jqmEnhanceable()
7073 .grid({ grid: this.options.grid });
7074
7075 $navbtns
7076 .each( function() {
7077 var icon = $.mobile.getAttribute( this, "icon" ),
7078 theme = $.mobile.getAttribute( this, "theme" ),
7079 classes = "ui-btn";
7080
7081 if ( theme ) {
7082 classes += " ui-btn-" + theme;
7083 }
7084 if ( icon ) {
7085 classes += " ui-icon-" + icon + " ui-btn-icon-" + iconpos;
7086 }
7087 $( this ).addClass( classes );
7088 });
7089
7090 $navbar.delegate( "a", "vclick", function( /* event */ ) {
7091 var activeBtn = $( this );
7092
7093 if ( !( activeBtn.hasClass( "ui-state-disabled" ) ||
7094
7095 // DEPRECATED as of 1.4.0 - remove after 1.4.0 release
7096 // only ui-state-disabled should be present thereafter
7097 activeBtn.hasClass( "ui-disabled" ) ||
7098 activeBtn.hasClass( $.mobile.activeBtnClass ) ) ) {
7099
7100 $navbtns.removeClass( $.mobile.activeBtnClass );
7101 activeBtn.addClass( $.mobile.activeBtnClass );
7102
7103 // The code below is a workaround to fix #1181
7104 self.document.one( "pagehide", function() {
7105 activeBtn.removeClass( $.mobile.activeBtnClass );
7106 });
7107 }
7108 });
7109
7110 // Buttons in the navbar with ui-state-persist class should regain their active state before page show
7111 $navbar.closest( ".ui-page" ).bind( "pagebeforeshow", function() {
7112 $navbtns.filter( ".ui-state-persist" ).addClass( $.mobile.activeBtnClass );
7113 });
7114 }
7115});
7116
7117})( jQuery );
7118
7119(function( $, undefined ) {
7120
7121var getAttr = $.mobile.getAttribute;
7122
7123$.widget( "mobile.listview", $.extend( {
7124
7125 options: {
7126 theme: null,
7127 countTheme: null, /* Deprecated in 1.4 */
7128 dividerTheme: null,
7129 icon: "caret-r",
7130 splitIcon: "caret-r",
7131 splitTheme: null,
7132 corners: true,
7133 shadow: true,
7134 inset: false
7135 },
7136
7137 _create: function() {
7138 var t = this,
7139 listviewClasses = "";
7140
7141 listviewClasses += t.options.inset ? " ui-listview-inset" : "";
7142
7143 if ( !!t.options.inset ) {
7144 listviewClasses += t.options.corners ? " ui-corner-all" : "";
7145 listviewClasses += t.options.shadow ? " ui-shadow" : "";
7146 }
7147
7148 // create listview markup
7149 t.element.addClass( " ui-listview" + listviewClasses );
7150
7151 t.refresh( true );
7152 },
7153
7154 // TODO: Remove in 1.5
7155 _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) {
7156 var dict = {};
7157 dict[ lcName ] = dict[ ucName ] = true;
7158 while ( ele ) {
7159 if ( dict[ ele.nodeName ] ) {
7160 return ele;
7161 }
7162 ele = ele[ nextProp ];
7163 }
7164 return null;
7165 },
7166 // TODO: Remove in 1.5
7167 _addThumbClasses: function( containers ) {
7168 var i, img, len = containers.length;
7169 for ( i = 0; i < len; i++ ) {
7170 img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
7171 if ( img.length ) {
7172 $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.hasClass( "ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
7173 }
7174 }
7175 },
7176
7177 _getChildrenByTagName: function( ele, lcName, ucName ) {
7178 var results = [],
7179 dict = {};
7180 dict[ lcName ] = dict[ ucName ] = true;
7181 ele = ele.firstChild;
7182 while ( ele ) {
7183 if ( dict[ ele.nodeName ] ) {
7184 results.push( ele );
7185 }
7186 ele = ele.nextSibling;
7187 }
7188 return $( results );
7189 },
7190
7191 _beforeListviewRefresh: $.noop,
7192 _afterListviewRefresh: $.noop,
7193
7194 refresh: function( create ) {
7195 var buttonClass, pos, numli, item, itemClass, itemTheme, itemIcon, icon, a,
7196 isDivider, startCount, newStartCount, value, last, splittheme, splitThemeClass, spliticon,
7197 altButtonClass, dividerTheme, li,
7198 o = this.options,
7199 $list = this.element,
7200 ol = !!$.nodeName( $list[ 0 ], "ol" ),
7201 start = $list.attr( "start" ),
7202 itemClassDict = {},
7203 countBubbles = $list.find( ".ui-li-count" ),
7204 countTheme = getAttr( $list[ 0 ], "counttheme" ) || this.options.countTheme,
7205 countThemeClass = countTheme ? "ui-body-" + countTheme : "ui-body-inherit";
7206
7207 if ( o.theme ) {
7208 $list.addClass( "ui-group-theme-" + o.theme );
7209 }
7210
7211 // Check if a start attribute has been set while taking a value of 0 into account
7212 if ( ol && ( start || start === 0 ) ) {
7213 startCount = parseInt( start, 10 ) - 1;
7214 $list.css( "counter-reset", "listnumbering " + startCount );
7215 }
7216
7217 this._beforeListviewRefresh();
7218
7219 li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" );
7220
7221 for ( pos = 0, numli = li.length; pos < numli; pos++ ) {
7222 item = li.eq( pos );
7223 itemClass = "";
7224
7225 if ( create || item[ 0 ].className.search( /\bui-li-static\b|\bui-li-divider\b/ ) < 0 ) {
7226 a = this._getChildrenByTagName( item[ 0 ], "a", "A" );
7227 isDivider = ( getAttr( item[ 0 ], "role" ) === "list-divider" );
7228 value = item.attr( "value" );
7229 itemTheme = getAttr( item[ 0 ], "theme" );
7230
7231 if ( a.length && a[ 0 ].className.search( /\bui-btn\b/ ) < 0 && !isDivider ) {
7232 itemIcon = getAttr( item[ 0 ], "icon" );
7233 icon = ( itemIcon === false ) ? false : ( itemIcon || o.icon );
7234
7235 // TODO: Remove in 1.5 together with links.js (links.js / .ui-link deprecated in 1.4)
7236 a.removeClass( "ui-link" );
7237
7238 buttonClass = "ui-btn";
7239
7240 if ( itemTheme ) {
7241 buttonClass += " ui-btn-" + itemTheme;
7242 }
7243
7244 if ( a.length > 1 ) {
7245 itemClass = "ui-li-has-alt";
7246
7247 last = a.last();
7248 splittheme = getAttr( last[ 0 ], "theme" ) || o.splitTheme || getAttr( item[ 0 ], "theme", true );
7249 splitThemeClass = splittheme ? " ui-btn-" + splittheme : "";
7250 spliticon = getAttr( last[ 0 ], "icon" ) || getAttr( item[ 0 ], "icon" ) || o.splitIcon;
7251 altButtonClass = "ui-btn ui-btn-icon-notext ui-icon-" + spliticon + splitThemeClass;
7252
7253 last
7254 .attr( "title", $.trim( last.getEncodedText() ) )
7255 .addClass( altButtonClass )
7256 .empty();
7257
7258 // Reduce to the first anchor, because only the first gets the buttonClass
7259 a = a.first();
7260 } else if ( icon ) {
7261 buttonClass += " ui-btn-icon-right ui-icon-" + icon;
7262 }
7263
7264 // Apply buttonClass to the (first) anchor
7265 a.addClass( buttonClass );
7266 } else if ( isDivider ) {
7267 dividerTheme = ( getAttr( item[ 0 ], "theme" ) || o.dividerTheme || o.theme );
7268
7269 itemClass = "ui-li-divider ui-bar-" + ( dividerTheme ? dividerTheme : "inherit" );
7270
7271 item.attr( "role", "heading" );
7272 } else if ( a.length <= 0 ) {
7273 itemClass = "ui-li-static ui-body-" + ( itemTheme ? itemTheme : "inherit" );
7274 }
7275 if ( ol && value ) {
7276 newStartCount = parseInt( value , 10 ) - 1;
7277
7278 item.css( "counter-reset", "listnumbering " + newStartCount );
7279 }
7280 }
7281
7282 // Instead of setting item class directly on the list item
7283 // at this point in time, push the item into a dictionary
7284 // that tells us what class to set on it so we can do this after this
7285 // processing loop is finished.
7286
7287 if ( !itemClassDict[ itemClass ] ) {
7288 itemClassDict[ itemClass ] = [];
7289 }
7290
7291 itemClassDict[ itemClass ].push( item[ 0 ] );
7292 }
7293
7294 // Set the appropriate listview item classes on each list item.
7295 // The main reason we didn't do this
7296 // in the for-loop above is because we can eliminate per-item function overhead
7297 // by calling addClass() and children() once or twice afterwards. This
7298 // can give us a significant boost on platforms like WP7.5.
7299
7300 for ( itemClass in itemClassDict ) {
7301 $( itemClassDict[ itemClass ] ).addClass( itemClass );
7302 }
7303
7304 countBubbles.each( function() {
7305 $( this ).closest( "li" ).addClass( "ui-li-has-count" );
7306 });
7307 if ( countThemeClass ) {
7308 countBubbles.not( "[class*='ui-body-']" ).addClass( countThemeClass );
7309 }
7310
7311 // Deprecated in 1.4. From 1.5 you have to add class ui-li-has-thumb or ui-li-has-icon to the LI.
7312 this._addThumbClasses( li );
7313 this._addThumbClasses( li.find( ".ui-btn" ) );
7314
7315 this._afterListviewRefresh();
7316
7317 this._addFirstLastClasses( li, this._getVisibles( li, create ), create );
7318 }
7319}, $.mobile.behaviors.addFirstLastClasses ) );
7320
7321})( jQuery );
7322
7323(function( $, undefined ) {
7324
7325function defaultAutodividersSelector( elt ) {
7326 // look for the text in the given element
7327 var text = $.trim( elt.text() ) || null;
7328
7329 if ( !text ) {
7330 return null;
7331 }
7332
7333 // create the text for the divider (first uppercased letter)
7334 text = text.slice( 0, 1 ).toUpperCase();
7335
7336 return text;
7337}
7338
7339$.widget( "mobile.listview", $.mobile.listview, {
7340 options: {
7341 autodividers: false,
7342 autodividersSelector: defaultAutodividersSelector
7343 },
7344
7345 _beforeListviewRefresh: function() {
7346 if ( this.options.autodividers ) {
7347 this._replaceDividers();
7348 this._superApply( arguments );
7349 }
7350 },
7351
7352 _replaceDividers: function() {
7353 var i, lis, li, dividerText,
7354 lastDividerText = null,
7355 list = this.element,
7356 divider;
7357
7358 list.children( "li:jqmData(role='list-divider')" ).remove();
7359
7360 lis = list.children( "li" );
7361
7362 for ( i = 0; i < lis.length ; i++ ) {
7363 li = lis[ i ];
7364 dividerText = this.options.autodividersSelector( $( li ) );
7365
7366 if ( dividerText && lastDividerText !== dividerText ) {
7367 divider = document.createElement( "li" );
7368 divider.appendChild( document.createTextNode( dividerText ) );
7369 divider.setAttribute( "data-" + $.mobile.ns + "role", "list-divider" );
7370 li.parentNode.insertBefore( divider, li );
7371 }
7372
7373 lastDividerText = dividerText;
7374 }
7375 }
7376});
7377
7378})( jQuery );
7379
7380(function( $, undefined ) {
7381
7382var rdivider = /(^|\s)ui-li-divider($|\s)/,
7383 rhidden = /(^|\s)ui-screen-hidden($|\s)/;
7384
7385$.widget( "mobile.listview", $.mobile.listview, {
7386 options: {
7387 hideDividers: false
7388 },
7389
7390 _afterListviewRefresh: function() {
7391 var items, idx, item, hideDivider = true;
7392
7393 this._superApply( arguments );
7394
7395 if ( this.options.hideDividers ) {
7396 items = this._getChildrenByTagName( this.element[ 0 ], "li", "LI" );
7397 for ( idx = items.length - 1 ; idx > -1 ; idx-- ) {
7398 item = items[ idx ];
7399 if ( item.className.match( rdivider ) ) {
7400 if ( hideDivider ) {
7401 item.className = item.className + " ui-screen-hidden";
7402 }
7403 hideDivider = true;
7404 } else {
7405 if ( !item.className.match( rhidden ) ) {
7406 hideDivider = false;
7407 }
7408 }
7409 }
7410 }
7411 }
7412});
7413
7414})( jQuery );
7415
7416(function( $, undefined ) {
7417
7418$.mobile.nojs = function( target ) {
7419 $( ":jqmData(role='nojs')", target ).addClass( "ui-nojs" );
7420};
7421
7422})( jQuery );
7423
7424(function( $, undefined ) {
7425
7426$.mobile.behaviors.formReset = {
7427 _handleFormReset: function() {
7428 this._on( this.element.closest( "form" ), {
7429 reset: function() {
7430 this._delay( "_reset" );
7431 }
7432 });
7433 }
7434};
7435
7436})( jQuery );
7437
7438/*
7439* "checkboxradio" plugin
7440*/
7441
7442(function( $, undefined ) {
7443
7444var escapeId = $.mobile.path.hashToSelector;
7445
7446$.widget( "mobile.checkboxradio", $.extend( {
7447
7448 initSelector: "input:not( :jqmData(role='flipswitch' ) )[type='checkbox'],input[type='radio']:not( :jqmData(role='flipswitch' ))",
7449
7450 options: {
7451 theme: "inherit",
7452
7453 // Deprecated as of 1.5.0
7454 mini: false,
7455 wrapperClass: null,
7456 enhanced: false,
7457 iconpos: "left"
7458
7459 },
7460 _create: function() {
7461 var input = this.element,
7462 o = this.options,
7463 inheritAttr = function( input, dataAttr ) {
7464 return input.jqmData( dataAttr ) ||
7465 input.closest( "form, fieldset" ).jqmData( dataAttr );
7466 },
7467 label = this.options.enhanced ?
7468 {
7469 element: this.element.siblings( "label" ),
7470 isParent: false
7471 } :
7472 this._findLabel(),
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" ) ||
7486 label.element.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
7487
7488 // Deprecated as of 1.5.0
7489 // Establish options
7490 o.mini = inheritAttr( input, "mini" ) || o.mini;
7491
7492 // Expose for other methods
7493 $.extend( this, {
7494 input: input,
7495 label: label.element,
7496 labelIsParent: label.isParent,
7497 inputtype: inputtype,
7498 checkedClass: checkedClass,
7499 uncheckedClass: uncheckedClass
7500 });
7501
7502 if ( !this.options.enhanced ) {
7503 this._enhance();
7504 }
7505
7506 this._on( label.element, {
7507 vmouseover: "_handleLabelVMouseOver",
7508 vclick: "_handleLabelVClick"
7509 });
7510
7511 this._on( input, {
7512 vmousedown: "_cacheVals",
7513 vclick: "_handleInputVClick",
7514 focus: "_handleInputFocus",
7515 blur: "_handleInputBlur"
7516 });
7517
7518 this._handleFormReset();
7519 this.refresh();
7520 },
7521
7522 _findLabel: function() {
7523 var parentLabel, label, isParent,
7524 input = this.element,
7525 labelsList = input[ 0 ].labels;
7526
7527 if( labelsList && labelsList.length > 0 ) {
7528 label = $( labelsList[ 0 ] );
7529 isParent = $.contains( label[ 0 ], input[ 0 ] );
7530 } else {
7531 parentLabel = input.closest( "label" );
7532 isParent = ( parentLabel.length > 0 );
7533
7534 // NOTE: Windows Phone could not find the label through a selector
7535 // filter works though.
7536 label = isParent ? parentLabel :
7537 $( this.document[ 0 ].getElementsByTagName( "label" ) )
7538 .filter( "[for='" + escapeId( input[ 0 ].id ) + "']" )
7539 .first();
7540 }
7541
7542 return {
7543 element: label,
7544 isParent: isParent
7545 };
7546 },
7547
7548 _enhance: function() {
7549 this.label.addClass( "ui-btn ui-corner-all");
7550
7551 if ( this.labelIsParent ) {
7552 this.input.add( this.label ).wrapAll( this._wrapper() );
7553 } else {
7554 //this.element.replaceWith( this.input.add( this.label ).wrapAll( this._wrapper() ) );
7555 this.element.wrap( this._wrapper() );
7556 this.element.parent().prepend( this.label );
7557 }
7558
7559 // Wrap the input + label in a div
7560
7561 this._setOptions({
7562 "theme": this.options.theme,
7563 "iconpos": this.options.iconpos,
7564 "wrapperClass": this.options.wrapperClass,
7565
7566 // Deprecated as of 1.5.0
7567 "mini": this.options.mini
7568 });
7569
7570 },
7571
7572 _wrapper: function() {
7573 return $( "<div class='ui-" + this.inputtype +
7574 ( this.options.disabled ? " ui-state-disabled" : "" ) + "' ></div>" );
7575 },
7576
7577 _handleInputFocus: function() {
7578 this.label.addClass( $.mobile.focusClass );
7579 },
7580
7581 _handleInputBlur: function() {
7582 this.label.removeClass( $.mobile.focusClass );
7583 },
7584
7585 _handleInputVClick: function() {
7586 // Adds checked attribute to checked input when keyboard is used
7587 this.element.prop( "checked", this.element.is( ":checked" ) );
7588 this._getInputSet().not( this.element ).prop( "checked", false );
7589 this._updateAll( true );
7590 },
7591
7592 _handleLabelVMouseOver: function( event ) {
7593 if ( this.label.parent().hasClass( "ui-state-disabled" ) ) {
7594 event.stopPropagation();
7595 }
7596 },
7597
7598 _handleLabelVClick: function( event ) {
7599 var input = this.element;
7600
7601 if ( input.is( ":disabled" ) ) {
7602 event.preventDefault();
7603 return;
7604 }
7605
7606 this._cacheVals();
7607
7608 input.prop( "checked", this.inputtype === "radio" && true || !input.prop( "checked" ) );
7609
7610 // trigger click handler's bound directly to the input as a substitute for
7611 // how label clicks behave normally in the browsers
7612 // TODO: it would be nice to let the browser's handle the clicks and pass them
7613 // through to the associate input. we can swallow that click at the parent
7614 // wrapper element level
7615 input.triggerHandler( "click" );
7616
7617 // Input set for common radio buttons will contain all the radio
7618 // buttons, but will not for checkboxes. clearing the checked status
7619 // of other radios ensures the active button state is applied properly
7620 this._getInputSet().not( input ).prop( "checked", false );
7621
7622 this._updateAll();
7623 return false;
7624 },
7625
7626 _cacheVals: function() {
7627 this._getInputSet().each( function() {
7628 $( this ).attr("data-" + $.mobile.ns + "cacheVal", this.checked );
7629 });
7630 },
7631
7632 // Returns those radio buttons that are supposed to be in the same group as
7633 // this radio button. In the case of a checkbox or a radio lacking a name
7634 // attribute, it returns this.element.
7635 _getInputSet: function() {
7636 var selector, formId,
7637 radio = this.element[ 0 ],
7638 name = radio.name,
7639 form = radio.form,
7640 doc = this.element.parents().last().get( 0 ),
7641
7642 // A radio is always a member of its own group
7643 radios = this.element;
7644
7645 // Only start running selectors if this is an attached radio button with a name
7646 if ( name && this.inputtype === "radio" && doc ) {
7647 selector = "input[type='radio'][name='" + escapeId( name ) + "']";
7648
7649 // If we're inside a form
7650 if ( form ) {
7651 formId = form.getAttribute( "id" );
7652
7653 // If the form has an ID, collect radios scattered throught the document which
7654 // nevertheless are part of the form by way of the value of their form attribute
7655 if ( formId ) {
7656 radios = $( selector + "[form='" + escapeId( formId ) + "']", doc );
7657 }
7658
7659 // Also add to those the radios in the form itself
7660 radios = $( form ).find( selector ).filter( function() {
7661
7662 // Some radios inside the form may belong to some other form by virtue of
7663 // having a form attribute defined on them, so we must filter them out here
7664 return ( this.form === form );
7665 }).add( radios );
7666
7667 // If we're outside a form
7668 } else {
7669
7670 // Collect all those radios which are also outside of a form and match our name
7671 radios = $( selector, doc ).filter( function() {
7672 return !this.form;
7673 });
7674 }
7675 }
7676 return radios;
7677 },
7678
7679 _updateAll: function( changeTriggered ) {
7680 var self = this;
7681
7682 this._getInputSet().each( function() {
7683 var $this = $( this );
7684
7685 if ( ( this.checked || self.inputtype === "checkbox" ) && !changeTriggered ) {
7686 $this.trigger( "change" );
7687 }
7688 })
7689 .checkboxradio( "refresh" );
7690 },
7691
7692 _reset: function() {
7693 this.refresh();
7694 },
7695
7696 // Is the widget supposed to display an icon?
7697 _hasIcon: function() {
7698 var controlgroup, controlgroupWidget,
7699 controlgroupConstructor = $.mobile.controlgroup;
7700
7701 // If the controlgroup widget is defined ...
7702 if ( controlgroupConstructor ) {
7703 controlgroup = this.element.closest(
7704 ":mobile-controlgroup," +
7705 controlgroupConstructor.prototype.initSelector );
7706
7707 // ... and the checkbox is in a controlgroup ...
7708 if ( controlgroup.length > 0 ) {
7709
7710 // ... look for a controlgroup widget instance, and ...
7711 controlgroupWidget = $.data( controlgroup[ 0 ], "mobile-controlgroup" );
7712
7713 // ... if found, decide based on the option value, ...
7714 return ( ( controlgroupWidget ? controlgroupWidget.options.type :
7715
7716 // ... otherwise decide based on the "type" data attribute.
7717 controlgroup.attr( "data-" + $.mobile.ns + "type" ) ) !== "horizontal" );
7718 }
7719 }
7720
7721 // Normally, the widget displays an icon.
7722 return true;
7723 },
7724
7725 refresh: function() {
7726 var isChecked = this.element[ 0 ].checked,
7727 active = $.mobile.activeBtnClass,
7728 iconposClass = "ui-btn-icon-" + this.options.iconpos,
7729 addClasses = [],
7730 removeClasses = [];
7731
7732 if ( this._hasIcon() ) {
7733 removeClasses.push( active );
7734 addClasses.push( iconposClass );
7735 } else {
7736 removeClasses.push( iconposClass );
7737 ( isChecked ? addClasses : removeClasses ).push( active );
7738 }
7739
7740 if ( isChecked ) {
7741 addClasses.push( this.checkedClass );
7742 removeClasses.push( this.uncheckedClass );
7743 } else {
7744 addClasses.push( this.uncheckedClass );
7745 removeClasses.push( this.checkedClass );
7746 }
7747
7748 this.widget().toggleClass( "ui-state-disabled", this.element.prop( "disabled" ) );
7749
7750 this.label
7751 .addClass( addClasses.join( " " ) )
7752 .removeClass( removeClasses.join( " " ) );
7753 },
7754
7755 widget: function() {
7756 return this.label.parent();
7757 },
7758
7759 _setOptions: function( options ) {
7760 var label = this.label,
7761 currentOptions = this.options,
7762 outer = this.widget(),
7763 hasIcon = this._hasIcon();
7764
7765 if ( options.disabled !== undefined ) {
7766 this.input.prop( "disabled", !!options.disabled );
7767 outer.toggleClass( "ui-state-disabled", !!options.disabled );
7768 }
7769
7770 // Deprecated as of 1.5.0
7771 if ( options.mini !== undefined ) {
7772 outer.toggleClass( "ui-mini", !!options.mini );
7773 }
7774 if ( options.theme !== undefined ) {
7775 label
7776 .removeClass( "ui-btn-" + currentOptions.theme )
7777 .addClass( "ui-btn-" + options.theme );
7778 }
7779 if ( options.wrapperClass !== undefined ) {
7780 outer
7781 .removeClass( currentOptions.wrapperClass )
7782 .addClass( options.wrapperClass );
7783 }
7784 if ( options.iconpos !== undefined && hasIcon ) {
7785 label.removeClass( "ui-btn-icon-" + currentOptions.iconpos ).addClass( "ui-btn-icon-" + options.iconpos );
7786 } else if ( !hasIcon ) {
7787 label.removeClass( "ui-btn-icon-" + currentOptions.iconpos );
7788 }
7789 this._super( options );
7790 }
7791
7792}, $.mobile.behaviors.formReset ) );
7793
7794})( jQuery );
7795
7796(function( $, undefined ) {
7797
7798$.widget( "mobile.button", {
7799
7800 initSelector: "input[type='button'], input[type='submit'], input[type='reset']",
7801
7802 options: {
7803 theme: null,
7804 icon: null,
7805 iconpos: "left",
7806 iconshadow: false, /* TODO: Deprecated in 1.4, remove in 1.5. */
7807 corners: true,
7808 shadow: true,
7809 inline: null,
7810 mini: null,
7811 wrapperClass: null,
7812 enhanced: false
7813 },
7814
7815 _create: function() {
7816
7817 if ( this.element.is( ":disabled" ) ) {
7818 this.options.disabled = true;
7819 }
7820
7821 if ( !this.options.enhanced ) {
7822 this._enhance();
7823 }
7824
7825 $.extend( this, {
7826 wrapper: this.element.parent()
7827 });
7828
7829 this._on( {
7830 focus: function() {
7831 this.widget().addClass( $.mobile.focusClass );
7832 },
7833
7834 blur: function() {
7835 this.widget().removeClass( $.mobile.focusClass );
7836 }
7837 });
7838
7839 this.refresh( true );
7840 },
7841
7842 _enhance: function() {
7843 this.element.wrap( this._button() );
7844 },
7845
7846 _button: function() {
7847 var options = this.options,
7848 iconClasses = this._getIconClasses( this.options );
7849
7850 return $("<div class='ui-btn ui-input-btn" +
7851 ( options.wrapperClass ? " " + options.wrapperClass : "" ) +
7852 ( options.theme ? " ui-btn-" + options.theme : "" ) +
7853 ( options.corners ? " ui-corner-all" : "" ) +
7854 ( options.shadow ? " ui-shadow" : "" ) +
7855 ( options.inline ? " ui-btn-inline" : "" ) +
7856 ( options.mini ? " ui-mini" : "" ) +
7857 ( options.disabled ? " ui-state-disabled" : "" ) +
7858 ( iconClasses ? ( " " + iconClasses ) : "" ) +
7859 "' >" + this.element.val() + "</div>" );
7860 },
7861
7862 widget: function() {
7863 return this.wrapper;
7864 },
7865
7866 _destroy: function() {
7867 this.element.insertBefore( this.wrapper );
7868 this.wrapper.remove();
7869 },
7870
7871 _getIconClasses: function( options ) {
7872 return ( options.icon ? ( "ui-icon-" + options.icon +
7873 ( options.iconshadow ? " ui-shadow-icon" : "" ) + /* TODO: Deprecated in 1.4, remove in 1.5. */
7874 " ui-btn-icon-" + options.iconpos ) : "" );
7875 },
7876
7877 _setOptions: function( options ) {
7878 var outer = this.widget();
7879
7880 if ( options.theme !== undefined ) {
7881 outer
7882 .removeClass( this.options.theme )
7883 .addClass( "ui-btn-" + options.theme );
7884 }
7885 if ( options.corners !== undefined ) {
7886 outer.toggleClass( "ui-corner-all", options.corners );
7887 }
7888 if ( options.shadow !== undefined ) {
7889 outer.toggleClass( "ui-shadow", options.shadow );
7890 }
7891 if ( options.inline !== undefined ) {
7892 outer.toggleClass( "ui-btn-inline", options.inline );
7893 }
7894 if ( options.mini !== undefined ) {
7895 outer.toggleClass( "ui-mini", options.mini );
7896 }
7897 if ( options.disabled !== undefined ) {
7898 this.element.prop( "disabled", options.disabled );
7899 outer.toggleClass( "ui-state-disabled", options.disabled );
7900 }
7901
7902 if ( options.icon !== undefined ||
7903 options.iconshadow !== undefined || /* TODO: Deprecated in 1.4, remove in 1.5. */
7904 options.iconpos !== undefined ) {
7905 outer
7906 .removeClass( this._getIconClasses( this.options ) )
7907 .addClass( this._getIconClasses(
7908 $.extend( {}, this.options, options ) ) );
7909 }
7910
7911 this._super( options );
7912 },
7913
7914 refresh: function( create ) {
7915 var originalElement,
7916 isDisabled = this.element.prop( "disabled" );
7917
7918 if ( this.options.icon && this.options.iconpos === "notext" && this.element.attr( "title" ) ) {
7919 this.element.attr( "title", this.element.val() );
7920 }
7921 if ( !create ) {
7922 originalElement = this.element.detach();
7923 $( this.wrapper ).text( this.element.val() ).append( originalElement );
7924 }
7925 if ( this.options.disabled !== isDisabled ) {
7926 this._setOptions({ disabled: isDisabled });
7927 }
7928 }
7929});
7930
7931})( jQuery );
7932
7933(function( $ ) {
7934 var meta = $( "meta[name=viewport]" ),
7935 initialContent = meta.attr( "content" ),
7936 disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no",
7937 enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes",
7938 disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test( initialContent );
7939
7940 $.mobile.zoom = $.extend( {}, {
7941 enabled: !disabledInitially,
7942 locked: false,
7943 disable: function( lock ) {
7944 if ( !disabledInitially && !$.mobile.zoom.locked ) {
7945 meta.attr( "content", disabledZoom );
7946 $.mobile.zoom.enabled = false;
7947 $.mobile.zoom.locked = lock || false;
7948 }
7949 },
7950 enable: function( unlock ) {
7951 if ( !disabledInitially && ( !$.mobile.zoom.locked || unlock === true ) ) {
7952 meta.attr( "content", enabledZoom );
7953 $.mobile.zoom.enabled = true;
7954 $.mobile.zoom.locked = false;
7955 }
7956 },
7957 restore: function() {
7958 if ( !disabledInitially ) {
7959 meta.attr( "content", initialContent );
7960 $.mobile.zoom.enabled = true;
7961 }
7962 }
7963 });
7964
7965}( jQuery ));
7966
7967(function( $, undefined ) {
7968
7969$.widget( "mobile.textinput", {
7970 initSelector: "input[type='text']," +
7971 "input[type='search']," +
7972 ":jqmData(type='search')," +
7973 "input[type='number']," +
7974 ":jqmData(type='number')," +
7975 "input[type='password']," +
7976 "input[type='email']," +
7977 "input[type='url']," +
7978 "input[type='tel']," +
7979 "textarea," +
7980 "input[type='time']," +
7981 "input[type='date']," +
7982 "input[type='month']," +
7983 "input[type='week']," +
7984 "input[type='datetime']," +
7985 "input[type='datetime-local']," +
7986 "input[type='color']," +
7987 "input:not([type])," +
7988 "input[type='file']",
7989
7990 options: {
7991 theme: null,
7992 corners: true,
7993 mini: false,
7994 // This option defaults to true on iOS devices.
7995 preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
7996 wrapperClass: "",
7997 enhanced: false
7998 },
7999
8000 _create: function() {
8001
8002 var options = this.options,
8003 isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ),
8004 isTextarea = this.element[ 0 ].nodeName.toLowerCase() === "textarea",
8005 isRange = this.element.is( "[data-" + ( $.mobile.ns || "" ) + "type='range']" ),
8006 inputNeedsWrap = ( (this.element.is( "input" ) ||
8007 this.element.is( "[data-" + ( $.mobile.ns || "" ) + "type='search']" ) ) &&
8008 !isRange );
8009
8010 if ( this.element.prop( "disabled" ) ) {
8011 options.disabled = true;
8012 }
8013
8014 $.extend( this, {
8015 classes: this._classesFromOptions(),
8016 isSearch: isSearch,
8017 isTextarea: isTextarea,
8018 isRange: isRange,
8019 inputNeedsWrap: inputNeedsWrap
8020 });
8021
8022 this._autoCorrect();
8023
8024 if ( !options.enhanced ) {
8025 this._enhance();
8026 }
8027
8028 this._on( {
8029 "focus": "_handleFocus",
8030 "blur": "_handleBlur"
8031 });
8032
8033 },
8034
8035 refresh: function() {
8036 this.setOptions({
8037 "disabled" : this.element.is( ":disabled" )
8038 });
8039 },
8040
8041 _enhance: function() {
8042 var elementClasses = [];
8043
8044 if ( this.isTextarea ) {
8045 elementClasses.push( "ui-input-text" );
8046 }
8047
8048 if ( this.isTextarea || this.isRange ) {
8049 elementClasses.push( "ui-shadow-inset" );
8050 }
8051
8052 //"search" and "text" input widgets
8053 if ( this.inputNeedsWrap ) {
8054 this.element.wrap( this._wrap() );
8055 } else {
8056 elementClasses = elementClasses.concat( this.classes );
8057 }
8058
8059 this.element.addClass( elementClasses.join( " " ) );
8060 },
8061
8062 widget: function() {
8063 return ( this.inputNeedsWrap ) ? this.element.parent() : this.element;
8064 },
8065
8066 _classesFromOptions: function() {
8067 var options = this.options,
8068 classes = [];
8069
8070 classes.push( "ui-body-" + ( ( options.theme === null ) ? "inherit" : options.theme ) );
8071 if ( options.corners ) {
8072 classes.push( "ui-corner-all" );
8073 }
8074 if ( options.mini ) {
8075 classes.push( "ui-mini" );
8076 }
8077 if ( options.disabled ) {
8078 classes.push( "ui-state-disabled" );
8079 }
8080 if ( options.wrapperClass ) {
8081 classes.push( options.wrapperClass );
8082 }
8083
8084 return classes;
8085 },
8086
8087 _wrap: function() {
8088 return $( "<div class='" +
8089 ( this.isSearch ? "ui-input-search " : "ui-input-text " ) +
8090 this.classes.join( " " ) + " " +
8091 "ui-shadow-inset'></div>" );
8092 },
8093
8094 _autoCorrect: function() {
8095 // XXX: Temporary workaround for issue 785 (Apple bug 8910589).
8096 // Turn off autocorrect and autocomplete on non-iOS 5 devices
8097 // since the popup they use can't be dismissed by the user. Note
8098 // that we test for the presence of the feature by looking for
8099 // the autocorrect property on the input element. We currently
8100 // have no test for iOS 5 or newer so we're temporarily using
8101 // the touchOverflow support flag for jQM 1.0. Yes, I feel dirty.
8102 // - jblas
8103 if ( typeof this.element[0].autocorrect !== "undefined" &&
8104 !$.support.touchOverflow ) {
8105
8106 // Set the attribute instead of the property just in case there
8107 // is code that attempts to make modifications via HTML.
8108 this.element[0].setAttribute( "autocorrect", "off" );
8109 this.element[0].setAttribute( "autocomplete", "off" );
8110 }
8111 },
8112
8113 _handleBlur: function() {
8114 this.widget().removeClass( $.mobile.focusClass );
8115 if ( this.options.preventFocusZoom ) {
8116 $.mobile.zoom.enable( true );
8117 }
8118 },
8119
8120 _handleFocus: function() {
8121 // In many situations, iOS will zoom into the input upon tap, this
8122 // prevents that from happening
8123 if ( this.options.preventFocusZoom ) {
8124 $.mobile.zoom.disable( true );
8125 }
8126 this.widget().addClass( $.mobile.focusClass );
8127 },
8128
8129 _setOptions: function ( options ) {
8130 var outer = this.widget();
8131
8132 this._super( options );
8133
8134 if ( !( options.disabled === undefined &&
8135 options.mini === undefined &&
8136 options.corners === undefined &&
8137 options.theme === undefined &&
8138 options.wrapperClass === undefined ) ) {
8139
8140 outer.removeClass( this.classes.join( " " ) );
8141 this.classes = this._classesFromOptions();
8142 outer.addClass( this.classes.join( " " ) );
8143 }
8144
8145 if ( options.disabled !== undefined ) {
8146 this.element.prop( "disabled", !!options.disabled );
8147 }
8148 },
8149
8150 _destroy: function() {
8151 if ( this.options.enhanced ) {
8152 return;
8153 }
8154 if ( this.inputNeedsWrap ) {
8155 this.element.unwrap();
8156 }
8157 this.element.removeClass( "ui-input-text " + this.classes.join( " " ) );
8158 }
8159});
8160
8161})( jQuery );
8162
8163(function( $, undefined ) {
8164
8165$.widget( "mobile.slider", $.extend( {
8166 initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
8167
8168 widgetEventPrefix: "slide",
8169
8170 options: {
8171 theme: null,
8172 trackTheme: null,
8173 corners: true,
8174 mini: false,
8175 highlight: false
8176 },
8177
8178 _create: function() {
8179
8180 // TODO: Each of these should have comments explain what they're for
8181 var self = this,
8182 control = this.element,
8183 trackTheme = this.options.trackTheme || $.mobile.getAttribute( control[ 0 ], "theme" ),
8184 trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
8185 cornerClass = ( this.options.corners || control.jqmData( "corners" ) ) ? " ui-corner-all" : "",
8186 miniClass = ( this.options.mini || control.jqmData( "mini" ) ) ? " ui-mini" : "",
8187 cType = control[ 0 ].nodeName.toLowerCase(),
8188 isToggleSwitch = ( cType === "select" ),
8189 isRangeslider = control.parent().is( ":jqmData(role='rangeslider')" ),
8190 selectClass = ( isToggleSwitch ) ? "ui-slider-switch" : "",
8191 controlID = control.attr( "id" ),
8192 $label = $( "[for='" + controlID + "']" ),
8193 labelID = $label.attr( "id" ) || controlID + "-label",
8194 min = !isToggleSwitch ? parseFloat( control.attr( "min" ) ) : 0,
8195 max = !isToggleSwitch ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
8196 step = window.parseFloat( control.attr( "step" ) || 1 ),
8197 domHandle = document.createElement( "a" ),
8198 handle = $( domHandle ),
8199 domSlider = document.createElement( "div" ),
8200 slider = $( domSlider ),
8201 valuebg = this.options.highlight && !isToggleSwitch ? (function() {
8202 var bg = document.createElement( "div" );
8203 bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
8204 return $( bg ).prependTo( slider );
8205 })() : false,
8206 options,
8207 wrapper,
8208 j, length,
8209 i, optionsCount, origTabIndex,
8210 side, activeClass, sliderImg;
8211
8212 $label.attr( "id", labelID );
8213 this.isToggleSwitch = isToggleSwitch;
8214
8215 domHandle.setAttribute( "href", "#" );
8216 domSlider.setAttribute( "role", "application" );
8217 domSlider.className = [ this.isToggleSwitch ? "ui-slider ui-slider-track ui-shadow-inset " : "ui-slider-track ui-shadow-inset ", selectClass, trackThemeClass, cornerClass, miniClass ].join( "" );
8218 domHandle.className = "ui-slider-handle";
8219 domSlider.appendChild( domHandle );
8220
8221 handle.attr({
8222 "role": "slider",
8223 "aria-valuemin": min,
8224 "aria-valuemax": max,
8225 "aria-valuenow": this._value(),
8226 "aria-valuetext": this._value(),
8227 "title": this._value(),
8228 "aria-labelledby": labelID
8229 });
8230
8231 $.extend( this, {
8232 slider: slider,
8233 handle: handle,
8234 control: control,
8235 type: cType,
8236 step: step,
8237 max: max,
8238 min: min,
8239 valuebg: valuebg,
8240 isRangeslider: isRangeslider,
8241 dragging: false,
8242 beforeStart: null,
8243 userModified: false,
8244 mouseMoved: false
8245 });
8246
8247 if ( isToggleSwitch ) {
8248 // TODO: restore original tabindex (if any) in a destroy method
8249 origTabIndex = control.attr( "tabindex" );
8250 if ( origTabIndex ) {
8251 handle.attr( "tabindex", origTabIndex );
8252 }
8253 control.attr( "tabindex", "-1" ).focus(function() {
8254 $( this ).blur();
8255 handle.focus();
8256 });
8257
8258 wrapper = document.createElement( "div" );
8259 wrapper.className = "ui-slider-inneroffset";
8260
8261 for ( j = 0, length = domSlider.childNodes.length; j < length; j++ ) {
8262 wrapper.appendChild( domSlider.childNodes[j] );
8263 }
8264
8265 domSlider.appendChild( wrapper );
8266
8267 // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" );
8268
8269 // make the handle move with a smooth transition
8270 handle.addClass( "ui-slider-handle-snapping" );
8271
8272 options = control.find( "option" );
8273
8274 for ( i = 0, optionsCount = options.length; i < optionsCount; i++ ) {
8275 side = !i ? "b" : "a";
8276 activeClass = !i ? "" : " " + $.mobile.activeBtnClass;
8277 sliderImg = document.createElement( "span" );
8278
8279 sliderImg.className = [ "ui-slider-label ui-slider-label-", side, activeClass ].join( "" );
8280 sliderImg.setAttribute( "role", "img" );
8281 sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) );
8282 $( sliderImg ).prependTo( slider );
8283 }
8284
8285 self._labels = $( ".ui-slider-label", slider );
8286
8287 }
8288
8289 // monitor the input for updated values
8290 control.addClass( isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" );
8291
8292 this._on( control, {
8293 "change": "_controlChange",
8294 "keyup": "_controlKeyup",
8295 "blur": "_controlBlur",
8296 "vmouseup": "_controlVMouseUp"
8297 });
8298
8299 slider.bind( "vmousedown", $.proxy( this._sliderVMouseDown, this ) )
8300 .bind( "vclick", false );
8301
8302 // We have to instantiate a new function object for the unbind to work properly
8303 // since the method itself is defined in the prototype (causing it to unbind everything)
8304 this._on( document, { "vmousemove": "_preventDocumentDrag" });
8305 this._on( slider.add( document ), { "vmouseup": "_sliderVMouseUp" });
8306
8307 slider.insertAfter( control );
8308
8309 // wrap in a div for styling purposes
8310 if ( !isToggleSwitch && !isRangeslider ) {
8311 wrapper = "<div class='ui-slider" +
8312 ( this.options.mini ? " ui-mini" : "" ) + "'></div>";
8313
8314 control.add( slider ).wrapAll( wrapper );
8315 }
8316
8317 // bind the handle event callbacks and set the context to the widget instance
8318 this._on( this.handle, {
8319 "vmousedown": "_handleVMouseDown",
8320 "keydown": "_handleKeydown",
8321 "keyup": "_handleKeyup"
8322 });
8323
8324 this.handle.bind( "vclick", false );
8325
8326 this._handleFormReset();
8327
8328 this.refresh( undefined, undefined, true );
8329 },
8330
8331 _setOptions: function( options ) {
8332 if ( options.theme !== undefined ) {
8333 this._setTheme( options.theme );
8334 }
8335
8336 if ( options.trackTheme !== undefined ) {
8337 this._setTrackTheme( options.trackTheme );
8338 }
8339
8340 if ( options.corners !== undefined ) {
8341 this._setCorners( options.corners );
8342 }
8343
8344 if ( options.mini !== undefined ) {
8345 this._setMini( options.mini );
8346 }
8347
8348 if ( options.highlight !== undefined ) {
8349 this._setHighlight( options.highlight );
8350 }
8351
8352 if ( options.disabled !== undefined ) {
8353 this._setDisabled( options.disabled );
8354 }
8355 this._super( options );
8356 },
8357
8358 _controlChange: function( event ) {
8359 // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
8360 if ( this._trigger( "controlchange", event ) === false ) {
8361 return false;
8362 }
8363 if ( !this.mouseMoved ) {
8364 this.refresh( this._value(), true );
8365 }
8366 },
8367
8368 _controlKeyup: function(/* event */) { // necessary?
8369 this.refresh( this._value(), true, true );
8370 },
8371
8372 _controlBlur: function(/* event */) {
8373 this.refresh( this._value(), true );
8374 },
8375
8376 // it appears the clicking the up and down buttons in chrome on
8377 // range/number inputs doesn't trigger a change until the field is
8378 // blurred. Here we check thif the value has changed and refresh
8379 _controlVMouseUp: function(/* event */) {
8380 this._checkedRefresh();
8381 },
8382
8383 // NOTE force focus on handle
8384 _handleVMouseDown: function(/* event */) {
8385 this.handle.focus();
8386 },
8387
8388 _handleKeydown: function( event ) {
8389 var index = this._value();
8390 if ( this.options.disabled ) {
8391 return;
8392 }
8393
8394 // In all cases prevent the default and mark the handle as active
8395 switch ( event.keyCode ) {
8396 case $.mobile.keyCode.HOME:
8397 case $.mobile.keyCode.END:
8398 case $.mobile.keyCode.PAGE_UP:
8399 case $.mobile.keyCode.PAGE_DOWN:
8400 case $.mobile.keyCode.UP:
8401 case $.mobile.keyCode.RIGHT:
8402 case $.mobile.keyCode.DOWN:
8403 case $.mobile.keyCode.LEFT:
8404 event.preventDefault();
8405
8406 if ( !this._keySliding ) {
8407 this._keySliding = true;
8408 this.handle.addClass( "ui-state-active" ); /* TODO: We don't use this class for styling. Do we need to add it? */
8409 }
8410
8411 break;
8412 }
8413
8414 // move the slider according to the keypress
8415 switch ( event.keyCode ) {
8416 case $.mobile.keyCode.HOME:
8417 this.refresh( this.min );
8418 break;
8419 case $.mobile.keyCode.END:
8420 this.refresh( this.max );
8421 break;
8422 case $.mobile.keyCode.PAGE_UP:
8423 case $.mobile.keyCode.UP:
8424 case $.mobile.keyCode.RIGHT:
8425 this.refresh( index + this.step );
8426 break;
8427 case $.mobile.keyCode.PAGE_DOWN:
8428 case $.mobile.keyCode.DOWN:
8429 case $.mobile.keyCode.LEFT:
8430 this.refresh( index - this.step );
8431 break;
8432 }
8433 }, // remove active mark
8434
8435 _handleKeyup: function(/* event */) {
8436 if ( this._keySliding ) {
8437 this._keySliding = false;
8438 this.handle.removeClass( "ui-state-active" ); /* See comment above. */
8439 }
8440 },
8441
8442 _sliderVMouseDown: function( event ) {
8443 // NOTE: we don't do this in refresh because we still want to
8444 // support programmatic alteration of disabled inputs
8445 if ( this.options.disabled || !( event.which === 1 || event.which === 0 || event.which === undefined ) ) {
8446 return false;
8447 }
8448 if ( this._trigger( "beforestart", event ) === false ) {
8449 return false;
8450 }
8451 this.dragging = true;
8452 this.userModified = false;
8453 this.mouseMoved = false;
8454
8455 if ( this.isToggleSwitch ) {
8456 this.beforeStart = this.element[0].selectedIndex;
8457 }
8458
8459 this.refresh( event );
8460 this._trigger( "start" );
8461 return false;
8462 },
8463
8464 _sliderVMouseUp: function() {
8465 if ( this.dragging ) {
8466 this.dragging = false;
8467
8468 if ( this.isToggleSwitch ) {
8469 // make the handle move with a smooth transition
8470 this.handle.addClass( "ui-slider-handle-snapping" );
8471
8472 if ( this.mouseMoved ) {
8473 // this is a drag, change the value only if user dragged enough
8474 if ( this.userModified ) {
8475 this.refresh( this.beforeStart === 0 ? 1 : 0 );
8476 } else {
8477 this.refresh( this.beforeStart );
8478 }
8479 } else {
8480 // this is just a click, change the value
8481 this.refresh( this.beforeStart === 0 ? 1 : 0 );
8482 }
8483 }
8484
8485 this.mouseMoved = false;
8486 this._trigger( "stop" );
8487 return false;
8488 }
8489 },
8490
8491 _preventDocumentDrag: function( event ) {
8492 // NOTE: we don't do this in refresh because we still want to
8493 // support programmatic alteration of disabled inputs
8494 if ( this._trigger( "drag", event ) === false) {
8495 return false;
8496 }
8497 if ( this.dragging && !this.options.disabled ) {
8498
8499 // this.mouseMoved must be updated before refresh() because it will be used in the control "change" event
8500 this.mouseMoved = true;
8501
8502 if ( this.isToggleSwitch ) {
8503 // make the handle move in sync with the mouse
8504 this.handle.removeClass( "ui-slider-handle-snapping" );
8505 }
8506
8507 this.refresh( event );
8508
8509 // only after refresh() you can calculate this.userModified
8510 this.userModified = this.beforeStart !== this.element[0].selectedIndex;
8511 return false;
8512 }
8513 },
8514
8515 _checkedRefresh: function() {
8516 if ( this.value !== this._value() ) {
8517 this.refresh( this._value() );
8518 }
8519 },
8520
8521 _value: function() {
8522 return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat( this.element.val() ) ;
8523 },
8524
8525 _reset: function() {
8526 this.refresh( undefined, false, true );
8527 },
8528
8529 refresh: function( val, isfromControl, preventInputUpdate ) {
8530 // NOTE: we don't return here because we want to support programmatic
8531 // alteration of the input value, which should still update the slider
8532
8533 var self = this,
8534 parentTheme = $.mobile.getAttribute( this.element[ 0 ], "theme" ),
8535 theme = this.options.theme || parentTheme,
8536 themeClass = theme ? " ui-btn-" + theme : "",
8537 trackTheme = this.options.trackTheme || parentTheme,
8538 trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
8539 cornerClass = this.options.corners ? " ui-corner-all" : "",
8540 miniClass = this.options.mini ? " ui-mini" : "",
8541 left, width, data, tol,
8542 pxStep, percent,
8543 control, isInput, optionElements, min, max, step,
8544 newval, valModStep, alignValue, percentPerStep,
8545 handlePercent, aPercent, bPercent,
8546 valueChanged;
8547
8548 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( "" );
8549 if ( this.options.disabled || this.element.prop( "disabled" ) ) {
8550 this.disable();
8551 }
8552
8553 // set the stored value for comparison later
8554 this.value = this._value();
8555 if ( this.options.highlight && !this.isToggleSwitch && this.slider.find( ".ui-slider-bg" ).length === 0 ) {
8556 this.valuebg = (function() {
8557 var bg = document.createElement( "div" );
8558 bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
8559 return $( bg ).prependTo( self.slider );
8560 })();
8561 }
8562 this.handle.addClass( "ui-btn" + themeClass + " ui-shadow" );
8563
8564 control = this.element;
8565 isInput = !this.isToggleSwitch;
8566 optionElements = isInput ? [] : control.find( "option" );
8567 min = isInput ? parseFloat( control.attr( "min" ) ) : 0;
8568 max = isInput ? parseFloat( control.attr( "max" ) ) : optionElements.length - 1;
8569 step = ( isInput && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1;
8570
8571 if ( typeof val === "object" ) {
8572 data = val;
8573 // a slight tolerance helped get to the ends of the slider
8574 tol = 8;
8575
8576 left = this.slider.offset().left;
8577 width = this.slider.width();
8578 pxStep = width/((max-min)/step);
8579 if ( !this.dragging ||
8580 data.pageX < left - tol ||
8581 data.pageX > left + width + tol ) {
8582 return;
8583 }
8584 if ( pxStep > 1 ) {
8585 percent = ( ( data.pageX - left ) / width ) * 100;
8586 } else {
8587 percent = Math.round( ( ( data.pageX - left ) / width ) * 100 );
8588 }
8589 } else {
8590 if ( val == null ) {
8591 val = isInput ? parseFloat( control.val() || 0 ) : control[0].selectedIndex;
8592 }
8593 percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
8594 }
8595
8596 if ( isNaN( percent ) ) {
8597 return;
8598 }
8599
8600 newval = ( percent / 100 ) * ( max - min ) + min;
8601
8602 //from jQuery UI slider, the following source will round to the nearest step
8603 valModStep = ( newval - min ) % step;
8604 alignValue = newval - valModStep;
8605
8606 if ( Math.abs( valModStep ) * 2 >= step ) {
8607 alignValue += ( valModStep > 0 ) ? step : ( -step );
8608 }
8609
8610 percentPerStep = 100/((max-min)/step);
8611 // Since JavaScript has problems with large floats, round
8612 // the final value to 5 digits after the decimal point (see jQueryUI: #4124)
8613 newval = parseFloat( alignValue.toFixed(5) );
8614
8615 if ( typeof pxStep === "undefined" ) {
8616 pxStep = width / ( (max-min) / step );
8617 }
8618 if ( pxStep > 1 && isInput ) {
8619 percent = ( newval - min ) * percentPerStep * ( 1 / step );
8620 }
8621 if ( percent < 0 ) {
8622 percent = 0;
8623 }
8624
8625 if ( percent > 100 ) {
8626 percent = 100;
8627 }
8628
8629 if ( newval < min ) {
8630 newval = min;
8631 }
8632
8633 if ( newval > max ) {
8634 newval = max;
8635 }
8636
8637 this.handle.css( "left", percent + "%" );
8638
8639 this.handle[0].setAttribute( "aria-valuenow", isInput ? newval : optionElements.eq( newval ).attr( "value" ) );
8640
8641 this.handle[0].setAttribute( "aria-valuetext", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
8642
8643 this.handle[0].setAttribute( "title", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
8644
8645 if ( this.valuebg ) {
8646 this.valuebg.css( "width", percent + "%" );
8647 }
8648
8649 // drag the label widths
8650 if ( this._labels ) {
8651 handlePercent = this.handle.width() / this.slider.width() * 100;
8652 aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100;
8653 bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 );
8654
8655 this._labels.each(function() {
8656 var ab = $( this ).hasClass( "ui-slider-label-a" );
8657 $( this ).width( ( ab ? aPercent : bPercent ) + "%" );
8658 });
8659 }
8660
8661 if ( !preventInputUpdate ) {
8662 valueChanged = false;
8663
8664 // update control"s value
8665 if ( isInput ) {
8666 valueChanged = parseFloat( control.val() ) !== newval;
8667 control.val( newval );
8668 } else {
8669 valueChanged = control[ 0 ].selectedIndex !== newval;
8670 control[ 0 ].selectedIndex = newval;
8671 }
8672 if ( this._trigger( "beforechange", val ) === false) {
8673 return false;
8674 }
8675 if ( !isfromControl && valueChanged ) {
8676 control.trigger( "change" );
8677 }
8678 }
8679 },
8680
8681 _setHighlight: function( value ) {
8682 value = !!value;
8683 if ( value ) {
8684 this.options.highlight = !!value;
8685 this.refresh();
8686 } else if ( this.valuebg ) {
8687 this.valuebg.remove();
8688 this.valuebg = false;
8689 }
8690 },
8691
8692 _setTheme: function( value ) {
8693 this.handle
8694 .removeClass( "ui-btn-" + this.options.theme )
8695 .addClass( "ui-btn-" + value );
8696
8697 var currentTheme = this.options.theme ? this.options.theme : "inherit",
8698 newTheme = value ? value : "inherit";
8699
8700 this.control
8701 .removeClass( "ui-body-" + currentTheme )
8702 .addClass( "ui-body-" + newTheme );
8703 },
8704
8705 _setTrackTheme: function( value ) {
8706 var currentTrackTheme = this.options.trackTheme ? this.options.trackTheme : "inherit",
8707 newTrackTheme = value ? value : "inherit";
8708
8709 this.slider
8710 .removeClass( "ui-body-" + currentTrackTheme )
8711 .addClass( "ui-body-" + newTrackTheme );
8712 },
8713
8714 _setMini: function( value ) {
8715 value = !!value;
8716 if ( !this.isToggleSwitch && !this.isRangeslider ) {
8717 this.slider.parent().toggleClass( "ui-mini", value );
8718 this.element.toggleClass( "ui-mini", value );
8719 }
8720 this.slider.toggleClass( "ui-mini", value );
8721 },
8722
8723 _setCorners: function( value ) {
8724 this.slider.toggleClass( "ui-corner-all", value );
8725
8726 if ( !this.isToggleSwitch ) {
8727 this.control.toggleClass( "ui-corner-all", value );
8728 }
8729 },
8730
8731 _setDisabled: function( value ) {
8732 value = !!value;
8733 this.element.prop( "disabled", value );
8734 this.slider
8735 .toggleClass( "ui-state-disabled", value )
8736 .attr( "aria-disabled", value );
8737
8738 this.element.toggleClass( "ui-state-disabled", value );
8739 }
8740
8741}, $.mobile.behaviors.formReset ) );
8742
8743})( jQuery );
8744
8745(function( $, undefined ) {
8746
8747var popup;
8748
8749function getPopup() {
8750 if ( !popup ) {
8751 popup = $( "<div></div>", {
8752 "class": "ui-slider-popup ui-shadow ui-corner-all"
8753 });
8754 }
8755 return popup.clone();
8756}
8757
8758$.widget( "mobile.slider", $.mobile.slider, {
8759 options: {
8760 popupEnabled: false,
8761 showValue: false
8762 },
8763
8764 _create: function() {
8765 this._super();
8766
8767 $.extend( this, {
8768 _currentValue: null,
8769 _popup: null,
8770 _popupVisible: false
8771 });
8772
8773 this._setOption( "popupEnabled", this.options.popupEnabled );
8774
8775 this._on( this.handle.add( this.slider ), { "vmousedown" : "_showPopup" } );
8776 this._on( this.slider.add( this.document ), { "vmouseup" : "_hidePopup" } );
8777 this._refresh();
8778 },
8779
8780 // position the popup centered 5px above the handle
8781 _positionPopup: function() {
8782 var dstOffset = this.handle.offset();
8783
8784 this._popup.offset( {
8785 left: dstOffset.left + ( this.handle.width() - this._popup.width() ) / 2,
8786 top: dstOffset.top - this._popup.outerHeight() - 5
8787 });
8788 },
8789
8790 _setOption: function( key, value ) {
8791 this._super( key, value );
8792
8793 if ( key === "showValue" ) {
8794 this.handle.html( value && !this.options.mini ? this._value() : "" );
8795 } else if ( key === "popupEnabled" ) {
8796 if ( value && !this._popup ) {
8797 this._popup = getPopup()
8798 .addClass( "ui-body-" + ( this.options.theme || "a" ) )
8799 .hide()
8800 .insertBefore( this.element );
8801 }
8802 }
8803 },
8804
8805 // show value on the handle and in popup
8806 refresh: function() {
8807 this._super.apply( this, arguments );
8808 this._refresh();
8809 },
8810
8811 _refresh: function() {
8812 var o = this.options, newValue;
8813
8814 if ( o.popupEnabled ) {
8815 // remove the title attribute from the handle (which is
8816 // responsible for the annoying tooltip); NB we have
8817 // to do it here as the jqm slider sets it every time
8818 // the slider's value changes :(
8819 this.handle.removeAttr( "title" );
8820 }
8821
8822 newValue = this._value();
8823 if ( newValue === this._currentValue ) {
8824 return;
8825 }
8826 this._currentValue = newValue;
8827
8828 if ( o.popupEnabled && this._popup ) {
8829 this._positionPopup();
8830 this._popup.html( newValue );
8831 }
8832
8833 if ( o.showValue && !this.options.mini ) {
8834 this.handle.html( newValue );
8835 }
8836 },
8837
8838 _showPopup: function() {
8839 if ( this.options.popupEnabled && !this._popupVisible ) {
8840 this._popup.show();
8841 this._positionPopup();
8842 this._popupVisible = true;
8843 }
8844 },
8845
8846 _hidePopup: function() {
8847 var o = this.options;
8848
8849 if ( o.popupEnabled && this._popupVisible ) {
8850 if ( o.showValue && !o.mini ) {
8851 this.handle.html( this._value() );
8852 }
8853 this._popup.hide();
8854 this._popupVisible = false;
8855 }
8856 }
8857});
8858
8859})( jQuery );
8860
8861(function( $, undefined ) {
8862
8863var selectorEscapeRegex = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;
8864
8865$.widget( "mobile.flipswitch", $.extend({
8866
8867 options: {
8868 onText: "On",
8869 offText: "Off",
8870 theme: null,
8871 enhanced: false,
8872 wrapperClass: null,
8873 corners: true,
8874 mini: false
8875 },
8876
8877 _create: function() {
8878 var labels;
8879
8880 this.type = this.element[ 0 ].nodeName.toLowerCase();
8881
8882 if ( !this.options.enhanced ) {
8883 this._enhance();
8884 } else {
8885 $.extend( this, {
8886 flipswitch: this.element.parent(),
8887 on: this.element.find( ".ui-flipswitch-on" ).eq( 0 ),
8888 off: this.element.find( ".ui-flipswitch-off" ).eq( 0 )
8889 });
8890 }
8891
8892 this._handleFormReset();
8893
8894 // Transfer tabindex to "on" element and make input unfocusable
8895 this._originalTabIndex = this.element.attr( "tabindex" );
8896 if ( this._originalTabIndex != null ) {
8897 this.on.attr( "tabindex", this._originalTabIndex );
8898 }
8899 this.element.attr( "tabindex", "-1" );
8900 this._on({
8901 "focus" : "_handleInputFocus"
8902 });
8903
8904 if ( this.element.is( ":disabled" ) ) {
8905 this._setOptions({
8906 "disabled": true
8907 });
8908 }
8909
8910 this._on( this.flipswitch, {
8911 "click": "_toggle",
8912 "swipeleft": "_left",
8913 "swiperight": "_right"
8914 });
8915
8916 this._on( this.on, {
8917 "keydown": "_keydown"
8918 });
8919
8920 this._on( {
8921 "change": "refresh"
8922 });
8923
8924 // On iOS we need to prevent default when the label is clicked, otherwise it drops down
8925 // the native select menu. We nevertheless pass the click onto the element like the
8926 // native code would.
8927 if ( this.element[ 0 ].nodeName.toLowerCase() === "select" ) {
8928 labels = this._findLabels();
8929 if ( labels.length ) {
8930 this._on( labels, {
8931 "click": function( event ) {
8932 this.element.click();
8933 event.preventDefault();
8934 }
8935 });
8936 }
8937 }
8938 },
8939
8940 _handleInputFocus: function() {
8941 this.on.focus();
8942 },
8943
8944 widget: function() {
8945 return this.flipswitch;
8946 },
8947
8948 _left: function() {
8949 this.flipswitch.removeClass( "ui-flipswitch-active" );
8950 if ( this.type === "select" ) {
8951 this.element.get( 0 ).selectedIndex = 0;
8952 } else {
8953 this.element.prop( "checked", false );
8954 }
8955 this.element.trigger( "change" );
8956 },
8957
8958 _right: function() {
8959 this.flipswitch.addClass( "ui-flipswitch-active" );
8960 if ( this.type === "select" ) {
8961 this.element.get( 0 ).selectedIndex = 1;
8962 } else {
8963 this.element.prop( "checked", true );
8964 }
8965 this.element.trigger( "change" );
8966 },
8967
8968 _enhance: function() {
8969 var flipswitch = $( "<div>" ),
8970 options = this.options,
8971 element = this.element,
8972 theme = options.theme ? options.theme : "inherit",
8973
8974 // The "on" button is an anchor so it's focusable
8975 on = $( "<a></a>", {
8976 "href": "#"
8977 }),
8978 off = $( "<span></span>" ),
8979 onText = ( this.type === "input" ) ?
8980 options.onText : element.find( "option" ).eq( 1 ).text(),
8981 offText = ( this.type === "input" ) ?
8982 options.offText : element.find( "option" ).eq( 0 ).text();
8983
8984 on
8985 .addClass( "ui-flipswitch-on ui-btn ui-shadow ui-btn-inherit" )
8986 .text( onText );
8987 off
8988 .addClass( "ui-flipswitch-off" )
8989 .text( offText );
8990
8991 flipswitch
8992 .addClass( "ui-flipswitch ui-shadow-inset " +
8993 "ui-bar-" + theme + " " +
8994 ( options.wrapperClass ? options.wrapperClass : "" ) + " " +
8995 ( ( element.is( ":checked" ) ||
8996 element
8997 .find( "option" )
8998 .eq( 1 )
8999 .is( ":selected" ) ) ? "ui-flipswitch-active" : "" ) +
9000 ( element.is(":disabled") ? " ui-state-disabled": "") +
9001 ( options.corners ? " ui-corner-all": "" ) +
9002 ( options.mini ? " ui-mini": "" ) )
9003 .append( on, off );
9004
9005 element
9006 .addClass( "ui-flipswitch-input" )
9007 .after( flipswitch )
9008 .appendTo( flipswitch );
9009
9010 $.extend( this, {
9011 flipswitch: flipswitch,
9012 on: on,
9013 off: off
9014 });
9015 },
9016
9017 _reset: function() {
9018 this.refresh();
9019 },
9020
9021 refresh: function() {
9022 var direction,
9023 existingDirection = this.flipswitch.hasClass( "ui-flipswitch-active" ) ? "_right" : "_left";
9024
9025 if ( this.type === "select" ) {
9026 direction = ( this.element.get( 0 ).selectedIndex > 0 ) ? "_right": "_left";
9027 } else {
9028 direction = this.element.prop( "checked" ) ? "_right": "_left";
9029 }
9030
9031 if ( direction !== existingDirection ) {
9032 this[ direction ]();
9033 }
9034 },
9035
9036 // Copied with modifications from checkboxradio
9037 _findLabels: function() {
9038 var input = this.element[ 0 ],
9039 labelsList = input.labels;
9040
9041 if ( labelsList && labelsList.length ) {
9042 labelsList = $( labelsList );
9043 } else {
9044 labelsList = this.element.closest( "label" );
9045 if ( labelsList.length === 0 ) {
9046
9047 // NOTE: Windows Phone could not find the label through a selector
9048 // filter works though.
9049 labelsList = $( this.document[ 0 ].getElementsByTagName( "label" ) )
9050 .filter( "[for='" +
9051 input.getAttribute( "id" ).replace( selectorEscapeRegex, "\\$1" ) +
9052 "']" );
9053 }
9054 }
9055
9056 return labelsList;
9057 },
9058
9059 _toggle: function() {
9060 var direction = this.flipswitch.hasClass( "ui-flipswitch-active" ) ? "_left" : "_right";
9061
9062 this[ direction ]();
9063 },
9064
9065 _keydown: function( e ) {
9066 if ( e.which === $.mobile.keyCode.LEFT ) {
9067 this._left();
9068 } else if ( e.which === $.mobile.keyCode.RIGHT ) {
9069 this._right();
9070 } else if ( e.which === $.mobile.keyCode.SPACE ) {
9071 this._toggle();
9072 e.preventDefault();
9073 }
9074 },
9075
9076 _setOptions: function( options ) {
9077 if ( options.theme !== undefined ) {
9078 var currentTheme = options.theme ? options.theme : "inherit",
9079 newTheme = options.theme ? options.theme : "inherit";
9080
9081 this.widget()
9082 .removeClass( "ui-bar-" + currentTheme )
9083 .addClass( "ui-bar-" + newTheme );
9084 }
9085 if ( options.onText !== undefined ) {
9086 this.on.text( options.onText );
9087 }
9088 if ( options.offText !== undefined ) {
9089 this.off.text( options.offText );
9090 }
9091 if ( options.disabled !== undefined ) {
9092 this.widget().toggleClass( "ui-state-disabled", options.disabled );
9093 }
9094 if ( options.mini !== undefined ) {
9095 this.widget().toggleClass( "ui-mini", options.mini );
9096 }
9097 if ( options.corners !== undefined ) {
9098 this.widget().toggleClass( "ui-corner-all", options.corners );
9099 }
9100
9101 this._super( options );
9102 },
9103
9104 _destroy: function() {
9105 if ( this.options.enhanced ) {
9106 return;
9107 }
9108 if ( this._originalTabIndex != null ) {
9109 this.element.attr( "tabindex", this._originalTabIndex );
9110 } else {
9111 this.element.removeAttr( "tabindex" );
9112 }
9113 this.on.remove();
9114 this.off.remove();
9115 this.element.unwrap();
9116 this.flipswitch.remove();
9117 this.removeClass( "ui-flipswitch-input" );
9118 }
9119
9120}, $.mobile.behaviors.formReset ) );
9121
9122})( jQuery );
9123
9124(function( $, undefined ) {
9125 $.widget( "mobile.rangeslider", $.extend( {
9126
9127 options: {
9128 theme: null,
9129 trackTheme: null,
9130 corners: true,
9131 mini: false,
9132 highlight: true
9133 },
9134
9135 _create: function() {
9136 var $el = this.element,
9137 elClass = this.options.mini ? "ui-rangeslider ui-mini" : "ui-rangeslider",
9138 _inputFirst = $el.find( "input" ).first(),
9139 _inputLast = $el.find( "input" ).last(),
9140 _label = $el.find( "label" ).first(),
9141 _sliderWidgetFirst = $.data( _inputFirst.get( 0 ), "mobile-slider" ) ||
9142 $.data( _inputFirst.slider().get( 0 ), "mobile-slider" ),
9143 _sliderWidgetLast = $.data( _inputLast.get(0), "mobile-slider" ) ||
9144 $.data( _inputLast.slider().get( 0 ), "mobile-slider" ),
9145 _sliderFirst = _sliderWidgetFirst.slider,
9146 _sliderLast = _sliderWidgetLast.slider,
9147 firstHandle = _sliderWidgetFirst.handle,
9148 _sliders = $( "<div class='ui-rangeslider-sliders' />" ).appendTo( $el );
9149
9150 _inputFirst.addClass( "ui-rangeslider-first" );
9151 _inputLast.addClass( "ui-rangeslider-last" );
9152 $el.addClass( elClass );
9153
9154 _sliderFirst.appendTo( _sliders );
9155 _sliderLast.appendTo( _sliders );
9156 _label.insertBefore( $el );
9157 firstHandle.prependTo( _sliderLast );
9158
9159 $.extend( this, {
9160 _inputFirst: _inputFirst,
9161 _inputLast: _inputLast,
9162 _sliderFirst: _sliderFirst,
9163 _sliderLast: _sliderLast,
9164 _label: _label,
9165 _targetVal: null,
9166 _sliderTarget: false,
9167 _sliders: _sliders,
9168 _proxy: false
9169 });
9170
9171 this.refresh();
9172 this._on( this.element.find( "input.ui-slider-input" ), {
9173 "slidebeforestart": "_slidebeforestart",
9174 "slidestop": "_slidestop",
9175 "slidedrag": "_slidedrag",
9176 "slidebeforechange": "_change",
9177 "blur": "_change",
9178 "keyup": "_change"
9179 });
9180 this._on({
9181 "mousedown":"_change"
9182 });
9183 this._on( this.element.closest( "form" ), {
9184 "reset":"_handleReset"
9185 });
9186 this._on( firstHandle, {
9187 "vmousedown": "_dragFirstHandle"
9188 });
9189 },
9190 _handleReset: function() {
9191 var self = this;
9192 //we must wait for the stack to unwind before updateing other wise sliders will not have updated yet
9193 setTimeout( function() {
9194 self._updateHighlight();
9195 },0);
9196 },
9197
9198 _dragFirstHandle: function( event ) {
9199 //if the first handle is dragged send the event to the first slider
9200 $.data( this._inputFirst.get(0), "mobile-slider" ).dragging = true;
9201 $.data( this._inputFirst.get(0), "mobile-slider" ).refresh( event );
9202 $.data( this._inputFirst.get(0), "mobile-slider" )._trigger( "start" );
9203 return false;
9204 },
9205
9206 _slidedrag: function( event ) {
9207 var first = $( event.target ).is( this._inputFirst ),
9208 otherSlider = ( first ) ? this._inputLast : this._inputFirst;
9209
9210 this._sliderTarget = false;
9211 //if the drag was initiated on an extreme and the other handle is focused send the events to
9212 //the closest handle
9213 if ( ( this._proxy === "first" && first ) || ( this._proxy === "last" && !first ) ) {
9214 $.data( otherSlider.get(0), "mobile-slider" ).dragging = true;
9215 $.data( otherSlider.get(0), "mobile-slider" ).refresh( event );
9216 return false;
9217 }
9218 },
9219
9220 _slidestop: function( event ) {
9221 var first = $( event.target ).is( this._inputFirst );
9222
9223 this._proxy = false;
9224 //this stops dragging of the handle and brings the active track to the front
9225 //this makes clicks on the track go the the last handle used
9226 this.element.find( "input" ).trigger( "vmouseup" );
9227 this._sliderFirst.css( "z-index", first ? 1 : "" );
9228 },
9229
9230 _slidebeforestart: function( event ) {
9231 this._sliderTarget = false;
9232 //if the track is the target remember this and the original value
9233 if ( $( event.originalEvent.target ).hasClass( "ui-slider-track" ) ) {
9234 this._sliderTarget = true;
9235 this._targetVal = $( event.target ).val();
9236 }
9237 },
9238
9239 _setOptions: function( options ) {
9240 if ( options.theme !== undefined ) {
9241 this._setTheme( options.theme );
9242 }
9243
9244 if ( options.trackTheme !== undefined ) {
9245 this._setTrackTheme( options.trackTheme );
9246 }
9247
9248 if ( options.mini !== undefined ) {
9249 this._setMini( options.mini );
9250 }
9251
9252 if ( options.highlight !== undefined ) {
9253 this._setHighlight( options.highlight );
9254 }
9255
9256 if ( options.disabled !== undefined ) {
9257 this._setDisabled( options.disabled );
9258 }
9259
9260 this._super( options );
9261 this.refresh();
9262 },
9263
9264 refresh: function() {
9265 var $el = this.element,
9266 o = this.options;
9267
9268 if ( this._inputFirst.is( ":disabled" ) || this._inputLast.is( ":disabled" ) ) {
9269 this.options.disabled = true;
9270 }
9271
9272 $el.find( "input" ).slider({
9273 theme: o.theme,
9274 trackTheme: o.trackTheme,
9275 disabled: o.disabled,
9276 corners: o.corners,
9277 mini: o.mini,
9278 highlight: o.highlight
9279 }).slider( "refresh" );
9280 this._updateHighlight();
9281 },
9282
9283 _change: function( event ) {
9284 if ( event.type === "keyup" ) {
9285 this._updateHighlight();
9286 return false;
9287 }
9288
9289 var self = this,
9290 min = parseFloat( this._inputFirst.val(), 10 ),
9291 max = parseFloat( this._inputLast.val(), 10 ),
9292 first = $( event.target ).hasClass( "ui-rangeslider-first" ),
9293 thisSlider = first ? this._inputFirst : this._inputLast,
9294 otherSlider = first ? this._inputLast : this._inputFirst;
9295
9296 if ( ( this._inputFirst.val() > this._inputLast.val() && event.type === "mousedown" && !$(event.target).hasClass("ui-slider-handle")) ) {
9297 thisSlider.blur();
9298 } else if ( event.type === "mousedown" ) {
9299 return;
9300 }
9301 if ( min > max && !this._sliderTarget ) {
9302 //this prevents min from being greater than max
9303 thisSlider.val( first ? max: min ).slider( "refresh" );
9304 this._trigger( "normalize" );
9305 } else if ( min > max ) {
9306 //this makes it so clicks on the target on either extreme go to the closest handle
9307 thisSlider.val( this._targetVal ).slider( "refresh" );
9308
9309 //You must wait for the stack to unwind so first slider is updated before updating second
9310 setTimeout( function() {
9311 otherSlider.val( first ? min: max ).slider( "refresh" );
9312 $.data( otherSlider.get(0), "mobile-slider" ).handle.focus();
9313 self._sliderFirst.css( "z-index", first ? "" : 1 );
9314 self._trigger( "normalize" );
9315 }, 0 );
9316 this._proxy = ( first ) ? "first" : "last";
9317 }
9318 //fixes issue where when both _sliders are at min they cannot be adjusted
9319 if ( min === max ) {
9320 $.data( thisSlider.get(0), "mobile-slider" ).handle.css( "z-index", 1 );
9321 $.data( otherSlider.get(0), "mobile-slider" ).handle.css( "z-index", 0 );
9322 } else {
9323 $.data( otherSlider.get(0), "mobile-slider" ).handle.css( "z-index", "" );
9324 $.data( thisSlider.get(0), "mobile-slider" ).handle.css( "z-index", "" );
9325 }
9326
9327 this._updateHighlight();
9328
9329 if ( min >= max ) {
9330 return false;
9331 }
9332 },
9333
9334 _updateHighlight: function() {
9335 var min = parseInt( $.data( this._inputFirst.get(0), "mobile-slider" ).handle.get(0).style.left, 10 ),
9336 max = parseInt( $.data( this._inputLast.get(0), "mobile-slider" ).handle.get(0).style.left, 10 ),
9337 width = (max - min);
9338
9339 this.element.find( ".ui-slider-bg" ).css({
9340 "margin-left": min + "%",
9341 "width": width + "%"
9342 });
9343 },
9344
9345 _setTheme: function( value ) {
9346 this._inputFirst.slider( "option", "theme", value );
9347 this._inputLast.slider( "option", "theme", value );
9348 },
9349
9350 _setTrackTheme: function( value ) {
9351 this._inputFirst.slider( "option", "trackTheme", value );
9352 this._inputLast.slider( "option", "trackTheme", value );
9353 },
9354
9355 _setMini: function( value ) {
9356 this._inputFirst.slider( "option", "mini", value );
9357 this._inputLast.slider( "option", "mini", value );
9358 this.element.toggleClass( "ui-mini", !!value );
9359 },
9360
9361 _setHighlight: function( value ) {
9362 this._inputFirst.slider( "option", "highlight", value );
9363 this._inputLast.slider( "option", "highlight", value );
9364 },
9365
9366 _setDisabled: function( value ) {
9367 this._inputFirst.prop( "disabled", value );
9368 this._inputLast.prop( "disabled", value );
9369 },
9370
9371 _destroy: function() {
9372 this._label.prependTo( this.element );
9373 this.element.removeClass( "ui-rangeslider ui-mini" );
9374 this._inputFirst.after( this._sliderFirst );
9375 this._inputLast.after( this._sliderLast );
9376 this._sliders.remove();
9377 this.element.find( "input" ).removeClass( "ui-rangeslider-first ui-rangeslider-last" ).slider( "destroy" );
9378 }
9379
9380 }, $.mobile.behaviors.formReset ) );
9381
9382})( jQuery );
9383
9384(function( $, undefined ) {
9385
9386 $.widget( "mobile.textinput", $.mobile.textinput, {
9387 options: {
9388 clearBtn: false,
9389 clearBtnText: "Clear text"
9390 },
9391
9392 _create: function() {
9393 this._super();
9394
9395 if ( this.isSearch ) {
9396 this.options.clearBtn = true;
9397 }
9398
9399 if ( !!this.options.clearBtn && this.inputNeedsWrap ) {
9400 this._addClearBtn();
9401 }
9402 },
9403
9404 clearButton: function() {
9405 return $( "<a href='#' tabindex='-1' aria-hidden='true' " +
9406 "class='ui-input-clear ui-btn ui-icon-delete ui-btn-icon-notext ui-corner-all'>" +
9407 "</a>" )
9408 .attr( "title", this.options.clearBtnText )
9409 .text( this.options.clearBtnText );
9410 },
9411
9412 _clearBtnClick: function( event ) {
9413 this.element.val( "" )
9414 .focus()
9415 .trigger( "change" );
9416
9417 this._clearBtn.addClass( "ui-input-clear-hidden" );
9418 event.preventDefault();
9419 },
9420
9421 _addClearBtn: function() {
9422
9423 //Removes default clear button if jqm clear button is ON
9424 this.element.addClass( "ui-textinput-hide-clear" ) ;
9425
9426 if ( !this.options.enhanced ) {
9427 this._enhanceClear();
9428 }
9429
9430 $.extend( this, {
9431 _clearBtn: this.widget().find("a.ui-input-clear")
9432 });
9433
9434 this._bindClearEvents();
9435
9436 this._toggleClear();
9437
9438 },
9439
9440 _enhanceClear: function() {
9441
9442 this.clearButton().appendTo( this.widget() );
9443 this.widget().addClass( "ui-input-has-clear" );
9444
9445 },
9446
9447 _bindClearEvents: function() {
9448
9449 this._on( this._clearBtn, {
9450 "click": "_clearBtnClick"
9451 });
9452
9453 this._on({
9454 "keyup": "_toggleClear",
9455 "change": "_toggleClear",
9456 "input": "_toggleClear",
9457 "focus": "_toggleClear",
9458 "blur": "_toggleClear",
9459 "cut": "_toggleClear",
9460 "paste": "_toggleClear"
9461
9462 });
9463
9464 },
9465
9466 _unbindClear: function() {
9467 this._off( this._clearBtn, "click");
9468 this._off( this.element, "keyup change input focus blur cut paste" );
9469 },
9470
9471 _setOptions: function( options ) {
9472 this._super( options );
9473
9474 if ( options.clearBtn !== undefined &&
9475 !this.element.is( "textarea, :jqmData(type='range')" ) ) {
9476 if ( options.clearBtn ) {
9477 this._addClearBtn();
9478 } else {
9479 this._destroyClear();
9480 }
9481 }
9482
9483 if ( options.clearBtnText !== undefined && this._clearBtn !== undefined ) {
9484 this._clearBtn.text( options.clearBtnText )
9485 .attr("title", options.clearBtnText);
9486 }
9487 },
9488
9489 _toggleClear: function() {
9490 this._delay( "_toggleClearClass", 0 );
9491 },
9492
9493 _toggleClearClass: function() {
9494 this._clearBtn.toggleClass( "ui-input-clear-hidden", !this.element.val() );
9495 },
9496
9497 _destroyClear: function() {
9498 this.widget().removeClass( "ui-input-has-clear" );
9499 this._unbindClear();
9500 this._clearBtn.remove();
9501 },
9502
9503 _destroy: function() {
9504 this._super();
9505 if ( this.options.clearBtn ) {
9506 this._destroyClear();
9507 }
9508 }
9509
9510 });
9511
9512})( jQuery );
9513
9514(function( $, undefined ) {
9515
9516 $.widget( "mobile.textinput", $.mobile.textinput, {
9517 options: {
9518 autogrow:true,
9519 keyupTimeoutBuffer: 100
9520 },
9521
9522 _create: function() {
9523 this._super();
9524
9525 if ( this.options.autogrow && this.isTextarea ) {
9526 this._autogrow();
9527 }
9528 },
9529
9530 _autogrow: function() {
9531 this.element.addClass( "ui-textinput-autogrow" );
9532
9533 this._on({
9534 "keyup": "_timeout",
9535 "change": "_timeout",
9536 "input": "_timeout",
9537 "paste": "_timeout"
9538 });
9539
9540 // Attach to the various you-have-become-visible notifications that the
9541 // various framework elements emit.
9542 // TODO: Remove all but the updatelayout handler once #6426 is fixed.
9543 this._on( true, this.document, {
9544
9545 // TODO: Move to non-deprecated event
9546 "pageshow": "_handleShow",
9547 "popupbeforeposition": "_handleShow",
9548 "updatelayout": "_handleShow",
9549 "panelopen": "_handleShow"
9550 });
9551 },
9552
9553 // Synchronously fix the widget height if this widget's parents are such
9554 // that they show/hide content at runtime. We still need to check whether
9555 // the widget is actually visible in case it is contained inside multiple
9556 // such containers. For example: panel contains collapsible contains
9557 // autogrow textinput. The panel may emit "panelopen" indicating that its
9558 // content has become visible, but the collapsible is still collapsed, so
9559 // the autogrow textarea is still not visible.
9560 _handleShow: function( event ) {
9561 if ( $.contains( event.target, this.element[ 0 ] ) &&
9562 this.element.is( ":visible" ) ) {
9563
9564 if ( event.type !== "popupbeforeposition" ) {
9565 this.element
9566 .addClass( "ui-textinput-autogrow-resize" )
9567 .animationComplete(
9568 $.proxy( function() {
9569 this.element.removeClass( "ui-textinput-autogrow-resize" );
9570 }, this ),
9571 "transition" );
9572 }
9573 this._prepareHeightUpdate();
9574 }
9575 },
9576
9577 _unbindAutogrow: function() {
9578 this.element.removeClass( "ui-textinput-autogrow" );
9579 this._off( this.element, "keyup change input paste" );
9580 this._off( this.document,
9581 "pageshow popupbeforeposition updatelayout panelopen" );
9582 },
9583
9584 keyupTimeout: null,
9585
9586 _prepareHeightUpdate: function( delay ) {
9587 if ( this.keyupTimeout ) {
9588 clearTimeout( this.keyupTimeout );
9589 }
9590 if ( delay === undefined ) {
9591 this._updateHeight();
9592 } else {
9593 this.keyupTimeout = this._delay( "_updateHeight", delay );
9594 }
9595 },
9596
9597 _timeout: function() {
9598 this._prepareHeightUpdate( this.options.keyupTimeoutBuffer );
9599 },
9600
9601 _updateHeight: function() {
9602 var paddingTop, paddingBottom, paddingHeight, scrollHeight, clientHeight,
9603 borderTop, borderBottom, borderHeight, height,
9604 scrollTop = this.window.scrollTop();
9605 this.keyupTimeout = 0;
9606
9607 // IE8 textareas have the onpage property - others do not
9608 if ( !( "onpage" in this.element[ 0 ] ) ) {
9609 this.element.css({
9610 "height": 0,
9611 "min-height": 0,
9612 "max-height": 0
9613 });
9614 }
9615
9616 scrollHeight = this.element[ 0 ].scrollHeight;
9617 clientHeight = this.element[ 0 ].clientHeight;
9618 borderTop = parseFloat( this.element.css( "border-top-width" ) );
9619 borderBottom = parseFloat( this.element.css( "border-bottom-width" ) );
9620 borderHeight = borderTop + borderBottom;
9621 height = scrollHeight + borderHeight + 15;
9622
9623 // Issue 6179: Padding is not included in scrollHeight and
9624 // clientHeight by Firefox if no scrollbar is visible. Because
9625 // textareas use the border-box box-sizing model, padding should be
9626 // included in the new (assigned) height. Because the height is set
9627 // to 0, clientHeight == 0 in Firefox. Therefore, we can use this to
9628 // check if padding must be added.
9629 if ( clientHeight === 0 ) {
9630 paddingTop = parseFloat( this.element.css( "padding-top" ) );
9631 paddingBottom = parseFloat( this.element.css( "padding-bottom" ) );
9632 paddingHeight = paddingTop + paddingBottom;
9633
9634 height += paddingHeight;
9635 }
9636
9637 this.element.css({
9638 "height": height,
9639 "min-height": "",
9640 "max-height": ""
9641 });
9642
9643 this.window.scrollTop( scrollTop );
9644 },
9645
9646 refresh: function() {
9647 if ( this.options.autogrow && this.isTextarea ) {
9648 this._updateHeight();
9649 }
9650 },
9651
9652 _setOptions: function( options ) {
9653
9654 this._super( options );
9655
9656 if ( options.autogrow !== undefined && this.isTextarea ) {
9657 if ( options.autogrow ) {
9658 this._autogrow();
9659 } else {
9660 this._unbindAutogrow();
9661 }
9662 }
9663 }
9664
9665 });
9666})( jQuery );
9667
9668(function( $, undefined ) {
9669
9670$.widget( "mobile.selectmenu", $.extend( {
9671 initSelector: "select:not( :jqmData(role='slider')):not( :jqmData(role='flipswitch') )",
9672
9673 options: {
9674 theme: null,
9675 icon: "caret-d",
9676 iconpos: "right",
9677 inline: false,
9678 corners: true,
9679 shadow: true,
9680 iconshadow: false, /* TODO: Deprecated in 1.4, remove in 1.5. */
9681 overlayTheme: null,
9682 dividerTheme: null,
9683 hidePlaceholderMenuItems: true,
9684 closeText: "Close",
9685 nativeMenu: true,
9686 // This option defaults to true on iOS devices.
9687 preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
9688 mini: false
9689 },
9690
9691 _button: function() {
9692 return $( "<div/>" );
9693 },
9694
9695 _setDisabled: function( value ) {
9696 this.element.prop( "disabled", value );
9697 this.button.attr( "aria-disabled", value );
9698 return this._setOption( "disabled", value );
9699 },
9700
9701 _focusButton : function() {
9702 var self = this;
9703
9704 setTimeout( function() {
9705 self.button.focus();
9706 }, 40);
9707 },
9708
9709 _selectOptions: function() {
9710 return this.select.find( "option" );
9711 },
9712
9713 // setup items that are generally necessary for select menu extension
9714 _preExtension: function() {
9715 var inline = this.options.inline || this.element.jqmData( "inline" ),
9716 mini = this.options.mini || this.element.jqmData( "mini" ),
9717 classes = "";
9718 // 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
9719 /* if ( $el[0].className.length ) {
9720 classes = $el[0].className;
9721 } */
9722 if ( !!~this.element[0].className.indexOf( "ui-btn-left" ) ) {
9723 classes = " ui-btn-left";
9724 }
9725
9726 if ( !!~this.element[0].className.indexOf( "ui-btn-right" ) ) {
9727 classes = " ui-btn-right";
9728 }
9729
9730 if ( inline ) {
9731 classes += " ui-btn-inline";
9732 }
9733 if ( mini ) {
9734 classes += " ui-mini";
9735 }
9736
9737 this.select = this.element
9738 .removeClass( "ui-btn-left ui-btn-right" )
9739 .wrap( "<div class='ui-select" + classes + "'></div>" );
9740 this.selectId = this.select.attr( "id" ) || ( "select-" + this.uuid );
9741 this.buttonId = this.selectId + "-button";
9742 this.label = $( "label[for='"+ $.mobile.path.hashToSelector( this.selectId ) +"']" );
9743 this.isMultiple = this.select[ 0 ].multiple;
9744 },
9745
9746 _destroy: function() {
9747 var wrapper = this.element.parents( ".ui-select" );
9748 if ( wrapper.length > 0 ) {
9749 if ( wrapper.is( ".ui-btn-left, .ui-btn-right" ) ) {
9750 this.element.addClass( wrapper.hasClass( "ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" );
9751 }
9752 this.element.insertAfter( wrapper );
9753 wrapper.remove();
9754 }
9755 },
9756
9757 _create: function() {
9758 this._preExtension();
9759
9760 this.button = this._button();
9761
9762 var options = this.options,
9763
9764 iconpos = options.icon ? ( options.iconpos || this.select.jqmData( "iconpos" ) ) : false,
9765
9766 button = this.button
9767 .insertBefore( this.select )
9768 .attr( "id", this.buttonId )
9769 .addClass( "ui-btn" +
9770 ( options.icon ? ( " ui-icon-" + options.icon + " ui-btn-icon-" + iconpos +
9771 ( options.iconshadow ? " ui-shadow-icon" : "" ) ) : "" ) + /* TODO: Remove in 1.5. */
9772 ( options.theme ? " ui-btn-" + options.theme : "" ) +
9773 ( options.corners ? " ui-corner-all" : "" ) +
9774 ( options.shadow ? " ui-shadow" : "" ) );
9775
9776 this.setButtonText();
9777
9778 // Opera does not properly support opacity on select elements
9779 // In Mini, it hides the element, but not its text
9780 // On the desktop,it seems to do the opposite
9781 // for these reasons, using the nativeMenu option results in a full native select in Opera
9782 if ( options.nativeMenu && window.opera && window.opera.version ) {
9783 button.addClass( "ui-select-nativeonly" );
9784 }
9785
9786 // Add counter for multi selects
9787 if ( this.isMultiple ) {
9788 this.buttonCount = $( "<span>" )
9789 .addClass( "ui-li-count ui-body-inherit" )
9790 .hide()
9791 .appendTo( button.addClass( "ui-li-has-count" ) );
9792 }
9793
9794 // Disable if specified
9795 if ( options.disabled || this.element.prop( "disabled" ) ) {
9796 this.disable();
9797 }
9798
9799 // Events on native select
9800 this._on( this.select, {
9801 change: "refresh"
9802 });
9803
9804 this._handleFormReset();
9805
9806 this._on( this.button, {
9807 keydown: "_handleKeydown"
9808 });
9809
9810 this.build();
9811 },
9812
9813 build: function() {
9814 var self = this;
9815
9816 this.select
9817 .appendTo( self.button )
9818 .bind( "vmousedown", function() {
9819 // Add active class to button
9820 self.button.addClass( $.mobile.activeBtnClass );
9821 })
9822 .bind( "focus", function() {
9823 self.button.addClass( $.mobile.focusClass );
9824 })
9825 .bind( "blur", function() {
9826 self.button.removeClass( $.mobile.focusClass );
9827 })
9828 .bind( "focus vmouseover", function() {
9829 self.button.trigger( "vmouseover" );
9830 })
9831 .bind( "vmousemove", function() {
9832 // Remove active class on scroll/touchmove
9833 self.button.removeClass( $.mobile.activeBtnClass );
9834 })
9835 .bind( "change blur vmouseout", function() {
9836 self.button.trigger( "vmouseout" )
9837 .removeClass( $.mobile.activeBtnClass );
9838 });
9839
9840 // In many situations, iOS will zoom into the select upon tap, this prevents that from happening
9841 self.button.bind( "vmousedown", function() {
9842 if ( self.options.preventFocusZoom ) {
9843 $.mobile.zoom.disable( true );
9844 }
9845 });
9846 self.label.bind( "click focus", function() {
9847 if ( self.options.preventFocusZoom ) {
9848 $.mobile.zoom.disable( true );
9849 }
9850 });
9851 self.select.bind( "focus", function() {
9852 if ( self.options.preventFocusZoom ) {
9853 $.mobile.zoom.disable( true );
9854 }
9855 });
9856 self.button.bind( "mouseup", function() {
9857 if ( self.options.preventFocusZoom ) {
9858 setTimeout(function() {
9859 $.mobile.zoom.enable( true );
9860 }, 0 );
9861 }
9862 });
9863 self.select.bind( "blur", function() {
9864 if ( self.options.preventFocusZoom ) {
9865 $.mobile.zoom.enable( true );
9866 }
9867 });
9868
9869 },
9870
9871 selected: function() {
9872 return this._selectOptions().filter( ":selected" );
9873 },
9874
9875 selectedIndices: function() {
9876 var self = this;
9877
9878 return this.selected().map(function() {
9879 return self._selectOptions().index( this );
9880 }).get();
9881 },
9882
9883 setButtonText: function() {
9884 var self = this,
9885 selected = this.selected(),
9886 text = this.placeholder,
9887 span = $( document.createElement( "span" ) );
9888
9889 this.button.children( "span" ).not( ".ui-li-count" ).remove().end().end().prepend( (function() {
9890 if ( selected.length ) {
9891 text = selected.map(function() {
9892 return $( this ).text();
9893 }).get().join( ", " );
9894 } else {
9895 text = self.placeholder;
9896 }
9897
9898 if ( text ) {
9899 span.text( text );
9900 } else {
9901
9902 // Set the contents to which we write as   to be XHTML compliant - see gh-6699
9903 span.html( " " );
9904 }
9905
9906 // TODO possibly aggregate multiple select option classes
9907 return span
9908 .addClass( self.select.attr( "class" ) )
9909 .addClass( selected.attr( "class" ) )
9910 .removeClass( "ui-screen-hidden" );
9911 })());
9912 },
9913
9914 setButtonCount: function() {
9915 var selected = this.selected();
9916
9917 // multiple count inside button
9918 if ( this.isMultiple ) {
9919 this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
9920 }
9921 },
9922
9923 _handleKeydown: function( /* event */ ) {
9924 this._delay( "_refreshButton" );
9925 },
9926
9927 _reset: function() {
9928 this.refresh();
9929 },
9930
9931 _refreshButton: function() {
9932 this.setButtonText();
9933 this.setButtonCount();
9934 },
9935
9936 refresh: function() {
9937 this._refreshButton();
9938 },
9939
9940 // open and close preserved in native selects
9941 // to simplify users code when looping over selects
9942 open: $.noop,
9943 close: $.noop,
9944
9945 disable: function() {
9946 this._setDisabled( true );
9947 this.button.addClass( "ui-state-disabled" );
9948 },
9949
9950 enable: function() {
9951 this._setDisabled( false );
9952 this.button.removeClass( "ui-state-disabled" );
9953 }
9954}, $.mobile.behaviors.formReset ) );
9955
9956})( jQuery );
9957
9958(function( $, undefined ) {
9959
9960$.mobile.links = function( target ) {
9961
9962 //links within content areas, tests included with page
9963 $( target )
9964 .find( "a" )
9965 .jqmEnhanceable()
9966 .filter( ":jqmData(rel='popup')[href][href!='']" )
9967 .each( function() {
9968 // Accessibility info for popups
9969 var element = this,
9970 idref = element.getAttribute( "href" ).substring( 1 );
9971
9972 if ( idref ) {
9973 element.setAttribute( "aria-haspopup", true );
9974 element.setAttribute( "aria-owns", idref );
9975 element.setAttribute( "aria-expanded", false );
9976 }
9977 })
9978 .end()
9979 .not( ".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
9980 .addClass( "ui-link" );
9981
9982};
9983
9984})( jQuery );
9985
9986
9987(function( $, undefined ) {
9988
9989function pointInRectangle( x, y, windowCoordinates ) {
9990 return ( x >= windowCoordinates.x && x <= windowCoordinates.x + windowCoordinates.cx &&
9991 y >= windowCoordinates.y && y <= windowCoordinates.y + windowCoordinates.cy );
9992}
9993
9994function isOutOfSight( element, windowCoordinates ) {
9995 var offset = element.offset(),
9996 width = element.outerWidth( true ),
9997 height = element.outerHeight( true );
9998
9999 return !(
10000 pointInRectangle( offset.left, offset.top, windowCoordinates ) ||
10001 pointInRectangle( offset.left + width, offset.top, windowCoordinates ) ||
10002 pointInRectangle( offset.left + width, offset.top + height, windowCoordinates ) ||
10003 pointInRectangle( offset.left, offset.top + height, windowCoordinates ) );
10004}
10005
10006function fitSegmentInsideSegment( windowSize, segmentSize, offset, desired ) {
10007 var returnValue = desired;
10008
10009 if ( windowSize < segmentSize ) {
10010 // Center segment if it's bigger than the window
10011 returnValue = offset + ( windowSize - segmentSize ) / 2;
10012 } else {
10013 // Otherwise center it at the desired coordinate while keeping it completely inside the window
10014 returnValue = Math.min( Math.max( offset, desired - segmentSize / 2 ), offset + windowSize - segmentSize );
10015 }
10016
10017 return returnValue;
10018}
10019
10020function getWindowCoordinates( theWindow ) {
10021 return {
10022 x: theWindow.scrollLeft(),
10023 y: theWindow.scrollTop(),
10024 cx: ( theWindow[ 0 ].innerWidth || theWindow.width() ),
10025 cy: ( theWindow[ 0 ].innerHeight || theWindow.height() )
10026 };
10027}
10028
10029$.widget( "mobile.popup", {
10030 options: {
10031 wrapperClass: null,
10032 theme: null,
10033 overlayTheme: null,
10034 shadow: true,
10035 corners: true,
10036 transition: "none",
10037 positionTo: "origin",
10038 tolerance: null,
10039 closeLinkSelector: "a:jqmData(rel='back')",
10040 closeLinkEvents: "click.popup",
10041 navigateEvents: "navigate.popup",
10042 closeEvents: "navigate.popup pagebeforechange.popup",
10043 dismissible: true,
10044 enhanced: false,
10045
10046 // NOTE Windows Phone 7 has a scroll position caching issue that
10047 // requires us to disable popup history management by default
10048 // https://github.com/jquery/jquery-mobile/issues/4784
10049 //
10050 // NOTE this option is modified in _create!
10051 history: !$.mobile.browser.oldIE
10052 },
10053
10054 // When the user depresses the mouse/finger on an element inside the popup while the popup is
10055 // open, we ignore resize events for a short while. This prevents #6961.
10056 _handleDocumentVmousedown: function( theEvent ) {
10057 if ( this._isOpen && $.contains( this._ui.container[ 0 ], theEvent.target ) ) {
10058 this._ignoreResizeEvents();
10059 }
10060 },
10061
10062 _create: function() {
10063 var theElement = this.element,
10064 myId = theElement.attr( "id" ),
10065 currentOptions = this.options;
10066
10067 // We need to adjust the history option to be false if there's no AJAX nav.
10068 // We can't do it in the option declarations because those are run before
10069 // it is determined whether there shall be AJAX nav.
10070 currentOptions.history = currentOptions.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
10071
10072 this._on( this.document, {
10073 "vmousedown": "_handleDocumentVmousedown"
10074 });
10075
10076 // Define instance variables
10077 $.extend( this, {
10078 _scrollTop: 0,
10079 _page: theElement.closest( ".ui-page" ),
10080 _ui: null,
10081 _fallbackTransition: "",
10082 _currentTransition: false,
10083 _prerequisites: null,
10084 _isOpen: false,
10085 _tolerance: null,
10086 _resizeData: null,
10087 _ignoreResizeTo: 0,
10088 _orientationchangeInProgress: false
10089 });
10090
10091 if ( this._page.length === 0 ) {
10092 this._page = $( "body" );
10093 }
10094
10095 if ( currentOptions.enhanced ) {
10096 this._ui = {
10097 container: theElement.parent(),
10098 screen: theElement.parent().prev(),
10099 placeholder: $( this.document[ 0 ].getElementById( myId + "-placeholder" ) )
10100 };
10101 } else {
10102 this._ui = this._enhance( theElement, myId );
10103 this._applyTransition( currentOptions.transition );
10104 }
10105 this
10106 ._setTolerance( currentOptions.tolerance )
10107 ._ui.focusElement = this._ui.container;
10108
10109 // Event handlers
10110 this._on( this._ui.screen, { "vclick": "_eatEventAndClose" } );
10111 this._on( this.window, {
10112 orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
10113 resize: $.proxy( this, "_handleWindowResize" ),
10114 keyup: $.proxy( this, "_handleWindowKeyUp" )
10115 });
10116 this._on( this.document, { "focusin": "_handleDocumentFocusIn" } );
10117 },
10118
10119 _enhance: function( theElement, myId ) {
10120 var currentOptions = this.options,
10121 wrapperClass = currentOptions.wrapperClass,
10122 ui = {
10123 screen: $( "<div class='ui-screen-hidden ui-popup-screen " +
10124 this._themeClassFromOption( "ui-overlay-", currentOptions.overlayTheme ) + "'></div>" ),
10125 placeholder: $( "<div style='display: none;'><!-- placeholder --></div>" ),
10126 container: $( "<div class='ui-popup-container ui-popup-hidden ui-popup-truncate" +
10127 ( wrapperClass ? ( " " + wrapperClass ) : "" ) + "'></div>" )
10128 },
10129 fragment = this.document[ 0 ].createDocumentFragment();
10130
10131 fragment.appendChild( ui.screen[ 0 ] );
10132 fragment.appendChild( ui.container[ 0 ] );
10133
10134 if ( myId ) {
10135 ui.screen.attr( "id", myId + "-screen" );
10136 ui.container.attr( "id", myId + "-popup" );
10137 ui.placeholder
10138 .attr( "id", myId + "-placeholder" )
10139 .html( "<!-- placeholder for " + myId + " -->" );
10140 }
10141
10142 // Apply the proto
10143 this._page[ 0 ].appendChild( fragment );
10144 // Leave a placeholder where the element used to be
10145 ui.placeholder.insertAfter( theElement );
10146 theElement
10147 .detach()
10148 .addClass( "ui-popup " +
10149 this._themeClassFromOption( "ui-body-", currentOptions.theme ) + " " +
10150 ( currentOptions.shadow ? "ui-overlay-shadow " : "" ) +
10151 ( currentOptions.corners ? "ui-corner-all " : "" ) )
10152 .appendTo( ui.container );
10153
10154 return ui;
10155 },
10156
10157 _eatEventAndClose: function( theEvent ) {
10158 theEvent.preventDefault();
10159 theEvent.stopImmediatePropagation();
10160 if ( this.options.dismissible ) {
10161 this.close();
10162 }
10163 return false;
10164 },
10165
10166 // Make sure the screen covers the entire document - CSS is sometimes not
10167 // enough to accomplish this.
10168 _resizeScreen: function() {
10169 var screen = this._ui.screen,
10170 popupHeight = this._ui.container.outerHeight( true ),
10171 screenHeight = screen.removeAttr( "style" ).height(),
10172
10173 // Subtracting 1 here is necessary for an obscure Andrdoid 4.0 bug where
10174 // the browser hangs if the screen covers the entire document :/
10175 documentHeight = this.document.height() - 1;
10176
10177 if ( screenHeight < documentHeight ) {
10178 screen.height( documentHeight );
10179 } else if ( popupHeight > screenHeight ) {
10180 screen.height( popupHeight );
10181 }
10182 },
10183
10184 _handleWindowKeyUp: function( theEvent ) {
10185 if ( this._isOpen && theEvent.keyCode === $.mobile.keyCode.ESCAPE ) {
10186 return this._eatEventAndClose( theEvent );
10187 }
10188 },
10189
10190 _expectResizeEvent: function() {
10191 var windowCoordinates = getWindowCoordinates( this.window );
10192
10193 if ( this._resizeData ) {
10194 if ( windowCoordinates.x === this._resizeData.windowCoordinates.x &&
10195 windowCoordinates.y === this._resizeData.windowCoordinates.y &&
10196 windowCoordinates.cx === this._resizeData.windowCoordinates.cx &&
10197 windowCoordinates.cy === this._resizeData.windowCoordinates.cy ) {
10198 // timeout not refreshed
10199 return false;
10200 } else {
10201 // clear existing timeout - it will be refreshed below
10202 clearTimeout( this._resizeData.timeoutId );
10203 }
10204 }
10205
10206 this._resizeData = {
10207 timeoutId: this._delay( "_resizeTimeout", 200 ),
10208 windowCoordinates: windowCoordinates
10209 };
10210
10211 return true;
10212 },
10213
10214 _resizeTimeout: function() {
10215 if ( this._isOpen ) {
10216 if ( !this._expectResizeEvent() ) {
10217 if ( this._ui.container.hasClass( "ui-popup-hidden" ) ) {
10218 // effectively rapid-open the popup while leaving the screen intact
10219 this._ui.container.removeClass( "ui-popup-hidden ui-popup-truncate" );
10220 this.reposition( { positionTo: "window" } );
10221 this._ignoreResizeEvents();
10222 }
10223
10224 this._resizeScreen();
10225 this._resizeData = null;
10226 this._orientationchangeInProgress = false;
10227 }
10228 } else {
10229 this._resizeData = null;
10230 this._orientationchangeInProgress = false;
10231 }
10232 },
10233
10234 _stopIgnoringResizeEvents: function() {
10235 this._ignoreResizeTo = 0;
10236 },
10237
10238 _ignoreResizeEvents: function() {
10239 if ( this._ignoreResizeTo ) {
10240 clearTimeout( this._ignoreResizeTo );
10241 }
10242 this._ignoreResizeTo = this._delay( "_stopIgnoringResizeEvents", 1000 );
10243 },
10244
10245 _handleWindowResize: function(/* theEvent */) {
10246 if ( this._isOpen && this._ignoreResizeTo === 0 ) {
10247 if ( isOutOfSight( this._ui.container, getWindowCoordinates( this.window ) ) &&
10248 ( this._expectResizeEvent() || this._orientationchangeInProgress ) &&
10249 !this._ui.container.hasClass( "ui-popup-hidden" ) ) {
10250
10251 // effectively rapid-close the popup while leaving the screen intact
10252 this._ui.container
10253 .addClass( "ui-popup-hidden ui-popup-truncate" )
10254 .removeAttr( "style" );
10255 }
10256 }
10257 },
10258
10259 _handleWindowOrientationchange: function(/* theEvent */) {
10260 if ( !this._orientationchangeInProgress && this._isOpen && this._ignoreResizeTo === 0 ) {
10261 this._expectResizeEvent();
10262 this._orientationchangeInProgress = true;
10263 }
10264 },
10265
10266 // When the popup is open, attempting to focus on an element that is not a
10267 // child of the popup will redirect focus to the popup
10268 _handleDocumentFocusIn: function( theEvent ) {
10269 var target,
10270 targetElement = theEvent.target,
10271 ui = this._ui;
10272
10273 if ( !this._isOpen ) {
10274 return;
10275 }
10276
10277 if ( targetElement !== ui.container[ 0 ] ) {
10278 target = $( targetElement );
10279 if ( !$.contains( ui.container[ 0 ], targetElement ) ) {
10280 $( this.document[ 0 ].activeElement ).one( "focus", $.proxy( function() {
10281 this._safelyBlur( targetElement );
10282 }, this ) );
10283 ui.focusElement.focus();
10284 theEvent.preventDefault();
10285 theEvent.stopImmediatePropagation();
10286 return false;
10287 } else if ( ui.focusElement[ 0 ] === ui.container[ 0 ] ) {
10288 ui.focusElement = target;
10289 }
10290 }
10291
10292 this._ignoreResizeEvents();
10293 },
10294
10295 _themeClassFromOption: function( prefix, value ) {
10296 return ( value ? ( value === "none" ? "" : ( prefix + value ) ) : ( prefix + "inherit" ) );
10297 },
10298
10299 _applyTransition: function( value ) {
10300 if ( value ) {
10301 this._ui.container.removeClass( this._fallbackTransition );
10302 if ( value !== "none" ) {
10303 this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
10304 if ( this._fallbackTransition === "none" ) {
10305 this._fallbackTransition = "";
10306 }
10307 this._ui.container.addClass( this._fallbackTransition );
10308 }
10309 }
10310
10311 return this;
10312 },
10313
10314 _setOptions: function( newOptions ) {
10315 var currentOptions = this.options,
10316 theElement = this.element,
10317 screen = this._ui.screen;
10318
10319 if ( newOptions.wrapperClass !== undefined ) {
10320 this._ui.container
10321 .removeClass( currentOptions.wrapperClass )
10322 .addClass( newOptions.wrapperClass );
10323 }
10324
10325 if ( newOptions.theme !== undefined ) {
10326 theElement
10327 .removeClass( this._themeClassFromOption( "ui-body-", currentOptions.theme ) )
10328 .addClass( this._themeClassFromOption( "ui-body-", newOptions.theme ) );
10329 }
10330
10331 if ( newOptions.overlayTheme !== undefined ) {
10332 screen
10333 .removeClass( this._themeClassFromOption( "ui-overlay-", currentOptions.overlayTheme ) )
10334 .addClass( this._themeClassFromOption( "ui-overlay-", newOptions.overlayTheme ) );
10335
10336 if ( this._isOpen ) {
10337 screen.addClass( "in" );
10338 }
10339 }
10340
10341 if ( newOptions.shadow !== undefined ) {
10342 theElement.toggleClass( "ui-overlay-shadow", newOptions.shadow );
10343 }
10344
10345 if ( newOptions.corners !== undefined ) {
10346 theElement.toggleClass( "ui-corner-all", newOptions.corners );
10347 }
10348
10349 if ( newOptions.transition !== undefined ) {
10350 if ( !this._currentTransition ) {
10351 this._applyTransition( newOptions.transition );
10352 }
10353 }
10354
10355 if ( newOptions.tolerance !== undefined ) {
10356 this._setTolerance( newOptions.tolerance );
10357 }
10358
10359 if ( newOptions.disabled !== undefined ) {
10360 if ( newOptions.disabled ) {
10361 this.close();
10362 }
10363 }
10364
10365 return this._super( newOptions );
10366 },
10367
10368 _setTolerance: function( value ) {
10369 var tol = { t: 30, r: 15, b: 30, l: 15 },
10370 ar;
10371
10372 if ( value !== undefined ) {
10373 ar = String( value ).split( "," );
10374
10375 $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
10376
10377 switch( ar.length ) {
10378 // All values are to be the same
10379 case 1:
10380 if ( !isNaN( ar[ 0 ] ) ) {
10381 tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
10382 }
10383 break;
10384
10385 // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
10386 case 2:
10387 if ( !isNaN( ar[ 0 ] ) ) {
10388 tol.t = tol.b = ar[ 0 ];
10389 }
10390 if ( !isNaN( ar[ 1 ] ) ) {
10391 tol.l = tol.r = ar[ 1 ];
10392 }
10393 break;
10394
10395 // The array contains values in the order top, right, bottom, left
10396 case 4:
10397 if ( !isNaN( ar[ 0 ] ) ) {
10398 tol.t = ar[ 0 ];
10399 }
10400 if ( !isNaN( ar[ 1 ] ) ) {
10401 tol.r = ar[ 1 ];
10402 }
10403 if ( !isNaN( ar[ 2 ] ) ) {
10404 tol.b = ar[ 2 ];
10405 }
10406 if ( !isNaN( ar[ 3 ] ) ) {
10407 tol.l = ar[ 3 ];
10408 }
10409 break;
10410
10411 default:
10412 break;
10413 }
10414 }
10415
10416 this._tolerance = tol;
10417 return this;
10418 },
10419
10420 _clampPopupWidth: function( infoOnly ) {
10421 var menuSize,
10422 windowCoordinates = getWindowCoordinates( this.window ),
10423 // rectangle within which the popup must fit
10424 rectangle = {
10425 x: this._tolerance.l,
10426 y: windowCoordinates.y + this._tolerance.t,
10427 cx: windowCoordinates.cx - this._tolerance.l - this._tolerance.r,
10428 cy: windowCoordinates.cy - this._tolerance.t - this._tolerance.b
10429 };
10430
10431 if ( !infoOnly ) {
10432 // Clamp the width of the menu before grabbing its size
10433 this._ui.container.css( "max-width", rectangle.cx );
10434 }
10435
10436 menuSize = {
10437 cx: this._ui.container.outerWidth( true ),
10438 cy: this._ui.container.outerHeight( true )
10439 };
10440
10441 return { rc: rectangle, menuSize: menuSize };
10442 },
10443
10444 _calculateFinalLocation: function( desired, clampInfo ) {
10445 var returnValue,
10446 rectangle = clampInfo.rc,
10447 menuSize = clampInfo.menuSize;
10448
10449 // Center the menu over the desired coordinates, while not going outside
10450 // the window tolerances. This will center wrt. the window if the popup is
10451 // too large.
10452 returnValue = {
10453 left: fitSegmentInsideSegment( rectangle.cx, menuSize.cx, rectangle.x, desired.x ),
10454 top: fitSegmentInsideSegment( rectangle.cy, menuSize.cy, rectangle.y, desired.y )
10455 };
10456
10457 // Make sure the top of the menu is visible
10458 returnValue.top = Math.max( 0, returnValue.top );
10459
10460 // If the height of the menu is smaller than the height of the document
10461 // align the bottom with the bottom of the document
10462
10463 returnValue.top -= Math.min( returnValue.top,
10464 Math.max( 0, returnValue.top + menuSize.cy - this.document.height() ) );
10465
10466 return returnValue;
10467 },
10468
10469 // Try and center the overlay over the given coordinates
10470 _placementCoords: function( desired ) {
10471 return this._calculateFinalLocation( desired, this._clampPopupWidth() );
10472 },
10473
10474 _createPrerequisites: function( screenPrerequisite, containerPrerequisite, whenDone ) {
10475 var prerequisites,
10476 self = this;
10477
10478 // It is important to maintain both the local variable prerequisites and
10479 // self._prerequisites. The local variable remains in the closure of the
10480 // functions which call the callbacks passed in. The comparison between the
10481 // local variable and self._prerequisites is necessary, because once a
10482 // function has been passed to .animationComplete() it will be called next
10483 // time an animation completes, even if that's not the animation whose end
10484 // the function was supposed to catch (for example, if an abort happens
10485 // during the opening animation, the .animationComplete handler is not
10486 // called for that animation anymore, but the handler remains attached, so
10487 // it is called the next time the popup is opened - making it stale.
10488 // Comparing the local variable prerequisites to the widget-level variable
10489 // self._prerequisites ensures that callbacks triggered by a stale
10490 // .animationComplete will be ignored.
10491
10492 prerequisites = {
10493 screen: $.Deferred(),
10494 container: $.Deferred()
10495 };
10496
10497 prerequisites.screen.done( function() {
10498 if ( prerequisites === self._prerequisites ) {
10499 screenPrerequisite();
10500 }
10501 });
10502
10503 prerequisites.container.done( function() {
10504 if ( prerequisites === self._prerequisites ) {
10505 containerPrerequisite();
10506 }
10507 });
10508
10509 $.when( prerequisites.screen, prerequisites.container ).done( function() {
10510 if ( prerequisites === self._prerequisites ) {
10511 self._prerequisites = null;
10512 whenDone();
10513 }
10514 });
10515
10516 self._prerequisites = prerequisites;
10517 },
10518
10519 _animate: function( args ) {
10520 // NOTE before removing the default animation of the screen
10521 // this had an animate callback that would resolve the deferred
10522 // now the deferred is resolved immediately
10523 // TODO remove the dependency on the screen deferred
10524 this._ui.screen
10525 .removeClass( args.classToRemove )
10526 .addClass( args.screenClassToAdd );
10527
10528 args.prerequisites.screen.resolve();
10529
10530 if ( args.transition && args.transition !== "none" ) {
10531 if ( args.applyTransition ) {
10532 this._applyTransition( args.transition );
10533 }
10534 if ( this._fallbackTransition ) {
10535 this._ui.container
10536 .addClass( args.containerClassToAdd )
10537 .removeClass( args.classToRemove )
10538 .animationComplete( $.proxy( args.prerequisites.container, "resolve" ) );
10539 return;
10540 }
10541 }
10542 this._ui.container.removeClass( args.classToRemove );
10543 args.prerequisites.container.resolve();
10544 },
10545
10546 // The desired coordinates passed in will be returned untouched if no reference element can be identified via
10547 // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
10548 // x and y coordinates by specifying the center middle of the window if the coordinates are absent.
10549 // options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector
10550 _desiredCoords: function( openOptions ) {
10551 var offset,
10552 dst = null,
10553 windowCoordinates = getWindowCoordinates( this.window ),
10554 x = openOptions.x,
10555 y = openOptions.y,
10556 pTo = openOptions.positionTo;
10557
10558 // Establish which element will serve as the reference
10559 if ( pTo && pTo !== "origin" ) {
10560 if ( pTo === "window" ) {
10561 x = windowCoordinates.cx / 2 + windowCoordinates.x;
10562 y = windowCoordinates.cy / 2 + windowCoordinates.y;
10563 } else {
10564 try {
10565 dst = $( pTo );
10566 } catch( err ) {
10567 dst = null;
10568 }
10569 if ( dst ) {
10570 dst.filter( ":visible" );
10571 if ( dst.length === 0 ) {
10572 dst = null;
10573 }
10574 }
10575 }
10576 }
10577
10578 // If an element was found, center over it
10579 if ( dst ) {
10580 offset = dst.offset();
10581 x = offset.left + dst.outerWidth() / 2;
10582 y = offset.top + dst.outerHeight() / 2;
10583 }
10584
10585 // Make sure x and y are valid numbers - center over the window
10586 if ( $.type( x ) !== "number" || isNaN( x ) ) {
10587 x = windowCoordinates.cx / 2 + windowCoordinates.x;
10588 }
10589 if ( $.type( y ) !== "number" || isNaN( y ) ) {
10590 y = windowCoordinates.cy / 2 + windowCoordinates.y;
10591 }
10592
10593 return { x: x, y: y };
10594 },
10595
10596 _reposition: function( openOptions ) {
10597 // We only care about position-related parameters for repositioning
10598 openOptions = {
10599 x: openOptions.x,
10600 y: openOptions.y,
10601 positionTo: openOptions.positionTo
10602 };
10603 this._trigger( "beforeposition", undefined, openOptions );
10604 this._ui.container.offset( this._placementCoords( this._desiredCoords( openOptions ) ) );
10605 },
10606
10607 reposition: function( openOptions ) {
10608 if ( this._isOpen ) {
10609 this._reposition( openOptions );
10610 }
10611 },
10612
10613 _safelyBlur: function( currentElement ){
10614 if ( currentElement !== this.window[ 0 ] &&
10615 currentElement.nodeName.toLowerCase() !== "body" ) {
10616 $( currentElement ).blur();
10617 }
10618 },
10619
10620 _openPrerequisitesComplete: function() {
10621 var id = this.element.attr( "id" ),
10622 firstFocus = this._ui.container.find( ":focusable" ).first();
10623
10624 this._ui.container.addClass( "ui-popup-active" );
10625 this._isOpen = true;
10626 this._resizeScreen();
10627
10628 // Check to see if currElement is not a child of the container. If it's not, blur
10629 if ( !$.contains( this._ui.container[ 0 ], this.document[ 0 ].activeElement ) ) {
10630 this._safelyBlur( this.document[ 0 ].activeElement );
10631 }
10632 if ( firstFocus.length > 0 ) {
10633 this._ui.focusElement = firstFocus;
10634 }
10635 this._ignoreResizeEvents();
10636 if ( id ) {
10637 this.document.find( "[aria-haspopup='true'][aria-owns='" +
10638 $.mobile.path.hashToSelector( id ) + "']" ).attr( "aria-expanded", true );
10639 }
10640 this._ui.container.attr( "tabindex", 0 );
10641 this._trigger( "afteropen" );
10642 },
10643
10644 _open: function( options ) {
10645 var openOptions = $.extend( {}, this.options, options ),
10646 // TODO move blacklist to private method
10647 androidBlacklist = ( function() {
10648 var ua = navigator.userAgent,
10649 // Rendering engine is Webkit, and capture major version
10650 wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
10651 wkversion = !!wkmatch && wkmatch[ 1 ],
10652 androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
10653 andversion = !!androidmatch && androidmatch[ 1 ],
10654 chromematch = ua.indexOf( "Chrome" ) > -1;
10655
10656 // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
10657 if ( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
10658 return true;
10659 }
10660 return false;
10661 }());
10662
10663 // Count down to triggering "popupafteropen" - we have two prerequisites:
10664 // 1. The popup window animation completes (container())
10665 // 2. The screen opacity animation completes (screen())
10666 this._createPrerequisites(
10667 $.noop,
10668 $.noop,
10669 $.proxy( this, "_openPrerequisitesComplete" ) );
10670
10671 this._currentTransition = openOptions.transition;
10672 this._applyTransition( openOptions.transition );
10673
10674 this._ui.screen.removeClass( "ui-screen-hidden" );
10675 this._ui.container.removeClass( "ui-popup-truncate" );
10676
10677 // Give applications a chance to modify the contents of the container before it appears
10678 this._reposition( openOptions );
10679
10680 this._ui.container.removeClass( "ui-popup-hidden" );
10681
10682 if ( this.options.overlayTheme && androidBlacklist ) {
10683 /* 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
10684 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
10685 https://github.com/jquery/jquery-mobile/issues/4816
10686 https://github.com/jquery/jquery-mobile/issues/4844
10687 https://github.com/jquery/jquery-mobile/issues/4874
10688 */
10689
10690 // TODO sort out why this._page isn't working
10691 this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
10692 }
10693 this._animate({
10694 additionalCondition: true,
10695 transition: openOptions.transition,
10696 classToRemove: "",
10697 screenClassToAdd: "in",
10698 containerClassToAdd: "in",
10699 applyTransition: false,
10700 prerequisites: this._prerequisites
10701 });
10702 },
10703
10704 _closePrerequisiteScreen: function() {
10705 this._ui.screen
10706 .removeClass( "out" )
10707 .addClass( "ui-screen-hidden" );
10708 },
10709
10710 _closePrerequisiteContainer: function() {
10711 this._ui.container
10712 .removeClass( "reverse out" )
10713 .addClass( "ui-popup-hidden ui-popup-truncate" )
10714 .removeAttr( "style" );
10715 },
10716
10717 _closePrerequisitesDone: function() {
10718 var container = this._ui.container,
10719 id = this.element.attr( "id" );
10720
10721 // remove the global mutex for popups
10722 $.mobile.popup.active = undefined;
10723
10724 // Blur elements inside the container, including the container
10725 $( ":focus", container[ 0 ] ).add( container[ 0 ] ).blur();
10726
10727 if ( id ) {
10728 this.document.find( "[aria-haspopup='true'][aria-owns='" +
10729 $.mobile.path.hashToSelector( id ) + "']" ).attr( "aria-expanded", false );
10730 }
10731
10732 this._ui.container.removeAttr( "tabindex" );
10733
10734 // alert users that the popup is closed
10735 this._trigger( "afterclose" );
10736 },
10737
10738 _close: function( immediate ) {
10739 this._ui.container.removeClass( "ui-popup-active" );
10740 this._page.removeClass( "ui-popup-open" );
10741
10742 this._isOpen = false;
10743
10744 // Count down to triggering "popupafterclose" - we have two prerequisites:
10745 // 1. The popup window reverse animation completes (container())
10746 // 2. The screen opacity animation completes (screen())
10747 this._createPrerequisites(
10748 $.proxy( this, "_closePrerequisiteScreen" ),
10749 $.proxy( this, "_closePrerequisiteContainer" ),
10750 $.proxy( this, "_closePrerequisitesDone" ) );
10751
10752 this._animate( {
10753 additionalCondition: this._ui.screen.hasClass( "in" ),
10754 transition: ( immediate ? "none" : ( this._currentTransition ) ),
10755 classToRemove: "in",
10756 screenClassToAdd: "out",
10757 containerClassToAdd: "reverse out",
10758 applyTransition: true,
10759 prerequisites: this._prerequisites
10760 });
10761 },
10762
10763 _unenhance: function() {
10764 if ( this.options.enhanced ) {
10765 return;
10766 }
10767
10768 // Put the element back to where the placeholder was and remove the "ui-popup" class
10769 this._setOptions( { theme: $.mobile.popup.prototype.options.theme } );
10770 this.element
10771 // Cannot directly insertAfter() - we need to detach() first, because
10772 // insertAfter() will do nothing if the payload div was not attached
10773 // to the DOM at the time the widget was created, and so the payload
10774 // will remain inside the container even after we call insertAfter().
10775 // If that happens and we remove the container a few lines below, we
10776 // will cause an infinite recursion - #5244
10777 .detach()
10778 .insertAfter( this._ui.placeholder )
10779 .removeClass( "ui-popup ui-overlay-shadow ui-corner-all ui-body-inherit" );
10780 this._ui.screen.remove();
10781 this._ui.container.remove();
10782 this._ui.placeholder.remove();
10783 },
10784
10785 _destroy: function() {
10786 if ( $.mobile.popup.active === this ) {
10787 this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) );
10788 this.close();
10789 } else {
10790 this._unenhance();
10791 }
10792
10793 return this;
10794 },
10795
10796 _closePopup: function( theEvent, data ) {
10797 var parsedDst, toUrl,
10798 currentOptions = this.options,
10799 immediate = false;
10800
10801 if ( ( theEvent && theEvent.isDefaultPrevented() ) || $.mobile.popup.active !== this ||
10802 !this._isOpen ) {
10803 return;
10804 }
10805
10806 // restore location on screen
10807 window.scrollTo( 0, this._scrollTop );
10808
10809 if ( theEvent && theEvent.type === "pagebeforechange" && data ) {
10810 // Determine whether we need to rapid-close the popup, or whether we can
10811 // take the time to run the closing transition
10812 if ( typeof data.toPage === "string" ) {
10813 parsedDst = data.toPage;
10814 } else {
10815 parsedDst = data.toPage.jqmData( "url" );
10816 }
10817 parsedDst = $.mobile.path.parseUrl( parsedDst );
10818 toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash;
10819
10820 if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ||
10821 data.options.reloadPage ) {
10822
10823 // Going to a different page - close immediately
10824 immediate = true;
10825 } else {
10826 theEvent.preventDefault();
10827 }
10828 }
10829
10830 // remove nav bindings
10831 this.window.off( currentOptions.closeEvents );
10832 // unbind click handlers added when history is disabled
10833 this.element.undelegate( currentOptions.closeLinkSelector, currentOptions.closeLinkEvents );
10834
10835 this._close( immediate );
10836 },
10837
10838 // any navigation event after a popup is opened should close the popup
10839 // NOTE the pagebeforechange is bound to catch navigation events that don't
10840 // alter the url (eg, dialogs from popups)
10841 _bindContainerClose: function() {
10842 this.window
10843 .on( this.options.closeEvents, $.proxy( this, "_closePopup" ) );
10844 },
10845
10846 widget: function() {
10847 return this._ui.container;
10848 },
10849
10850 // TODO no clear deliniation of what should be here and
10851 // what should be in _open. Seems to be "visual" vs "history" for now
10852 open: function( options ) {
10853 var url, hashkey, activePage, currentIsDialog, hasHash, urlHistory,
10854 self = this,
10855 currentOptions = this.options;
10856
10857 // make sure open is idempotent
10858 if ( $.mobile.popup.active || currentOptions.disabled ) {
10859 return this;
10860 }
10861
10862 // set the global popup mutex
10863 $.mobile.popup.active = this;
10864 this._scrollTop = this.window.scrollTop();
10865
10866 // if history alteration is disabled close on navigate events
10867 // and leave the url as is
10868 if ( !( currentOptions.history ) ) {
10869 self._open( options );
10870 self._bindContainerClose();
10871
10872 // When histoy is disabled we have to grab the data-rel
10873 // back link clicks so we can close the popup instead of
10874 // relying on history to do it for us
10875 self.element
10876 .delegate( currentOptions.closeLinkSelector, currentOptions.closeLinkEvents, function( theEvent ) {
10877 self.close();
10878 theEvent.preventDefault();
10879 });
10880
10881 return this;
10882 }
10883
10884 // cache some values for min/readability
10885 urlHistory = $.mobile.navigate.history;
10886 hashkey = $.mobile.dialogHashKey;
10887 activePage = $.mobile.activePage;
10888 currentIsDialog = ( activePage ? activePage.hasClass( "ui-dialog" ) : false );
10889 this._myUrl = url = urlHistory.getActive().url;
10890 hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 );
10891
10892 if ( hasHash ) {
10893 self._open( options );
10894 self._bindContainerClose();
10895 return this;
10896 }
10897
10898 // if the current url has no dialog hash key proceed as normal
10899 // otherwise, if the page is a dialog simply tack on the hash key
10900 if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ) {
10901 url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey);
10902 } else {
10903 url = $.mobile.path.parseLocation().hash + hashkey;
10904 }
10905
10906 // swallow the the initial navigation event, and bind for the next
10907 this.window.one( "beforenavigate", function( theEvent ) {
10908 theEvent.preventDefault();
10909 self._open( options );
10910 self._bindContainerClose();
10911 });
10912
10913 this.urlAltered = true;
10914 $.mobile.navigate( url, { role: "dialog" } );
10915
10916 return this;
10917 },
10918
10919 close: function() {
10920 // make sure close is idempotent
10921 if ( $.mobile.popup.active !== this ) {
10922 return this;
10923 }
10924
10925 this._scrollTop = this.window.scrollTop();
10926
10927 if ( this.options.history && this.urlAltered ) {
10928 $.mobile.back();
10929 this.urlAltered = false;
10930 } else {
10931 // simulate the nav bindings having fired
10932 this._closePopup();
10933 }
10934
10935 return this;
10936 }
10937});
10938
10939// TODO this can be moved inside the widget
10940$.mobile.popup.handleLink = function( $link ) {
10941 var offset,
10942 path = $.mobile.path,
10943
10944 // NOTE make sure to get only the hash from the href because ie7 (wp7)
10945 // returns the absolute href in this case ruining the element selection
10946 popup = $( path.hashToSelector( path.parseUrl( $link.attr( "href" ) ).hash ) ).first();
10947
10948 if ( popup.length > 0 && popup.data( "mobile-popup" ) ) {
10949 offset = $link.offset();
10950 popup.popup( "open", {
10951 x: offset.left + $link.outerWidth() / 2,
10952 y: offset.top + $link.outerHeight() / 2,
10953 transition: $link.jqmData( "transition" ),
10954 positionTo: $link.jqmData( "position-to" )
10955 });
10956 }
10957
10958 //remove after delay
10959 setTimeout( function() {
10960 $link.removeClass( $.mobile.activeBtnClass );
10961 }, 300 );
10962};
10963
10964// TODO move inside _create
10965$.mobile.document.on( "pagebeforechange", function( theEvent, data ) {
10966 if ( data.options.role === "popup" ) {
10967 $.mobile.popup.handleLink( data.options.link );
10968 theEvent.preventDefault();
10969 }
10970});
10971
10972})( jQuery );
10973
10974/*
10975* custom "selectmenu" plugin
10976*/
10977
10978(function( $, undefined ) {
10979
10980var unfocusableItemSelector = ".ui-disabled,.ui-state-disabled,.ui-li-divider,.ui-screen-hidden,:jqmData(role='placeholder')",
10981 goToAdjacentItem = function( item, target, direction ) {
10982 var adjacent = item[ direction + "All" ]()
10983 .not( unfocusableItemSelector )
10984 .first();
10985
10986 // if there's a previous option, focus it
10987 if ( adjacent.length ) {
10988 target
10989 .blur()
10990 .attr( "tabindex", "-1" );
10991
10992 adjacent.find( "a" ).first().focus();
10993 }
10994 };
10995
10996$.widget( "mobile.selectmenu", $.mobile.selectmenu, {
10997 _create: function() {
10998 var o = this.options;
10999
11000 // Custom selects cannot exist inside popups, so revert the "nativeMenu"
11001 // option to true if a parent is a popup
11002 o.nativeMenu = o.nativeMenu || ( this.element.parents( ":jqmData(role='popup'),:mobile-popup" ).length > 0 );
11003
11004 return this._super();
11005 },
11006
11007 _handleSelectFocus: function() {
11008 this.element.blur();
11009 this.button.focus();
11010 },
11011
11012 _handleKeydown: function( event ) {
11013 this._super( event );
11014 this._handleButtonVclickKeydown( event );
11015 },
11016
11017 _handleButtonVclickKeydown: function( event ) {
11018 if ( this.options.disabled || this.isOpen || this.options.nativeMenu ) {
11019 return;
11020 }
11021
11022 if (event.type === "vclick" ||
11023 event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) {
11024
11025 this._decideFormat();
11026 if ( this.menuType === "overlay" ) {
11027 this.button.attr( "href", "#" + this.popupId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
11028 } else {
11029 this.button.attr( "href", "#" + this.dialogId ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
11030 }
11031 this.isOpen = true;
11032 // Do not prevent default, so the navigation may have a chance to actually open the chosen format
11033 }
11034 },
11035
11036 _handleListFocus: function( e ) {
11037 var params = ( e.type === "focusin" ) ?
11038 { tabindex: "0", event: "vmouseover" }:
11039 { tabindex: "-1", event: "vmouseout" };
11040
11041 $( e.target )
11042 .attr( "tabindex", params.tabindex )
11043 .trigger( params.event );
11044 },
11045
11046 _handleListKeydown: function( event ) {
11047 var target = $( event.target ),
11048 li = target.closest( "li" );
11049
11050 // switch logic based on which key was pressed
11051 switch ( event.keyCode ) {
11052 // up or left arrow keys
11053 case 38:
11054 goToAdjacentItem( li, target, "prev" );
11055 return false;
11056 // down or right arrow keys
11057 case 40:
11058 goToAdjacentItem( li, target, "next" );
11059 return false;
11060 // If enter or space is pressed, trigger click
11061 case 13:
11062 case 32:
11063 target.trigger( "click" );
11064 return false;
11065 }
11066 },
11067
11068 // Focus the button before the page containing the widget replaces the dialog page
11069 _handleBeforeTransition: function( event, data ) {
11070 var focusButton;
11071
11072 if ( data && data.prevPage && data.prevPage[ 0 ] === this.menuPage[ 0 ] ) {
11073 focusButton = $.proxy( function() {
11074 this._delay( function() {
11075 this._focusButton();
11076 });
11077 }, this );
11078
11079 if ( data.options && data.options.transition && data.options.transition !== "none" ) {
11080 data.prevPage.animationComplete( focusButton );
11081 } else {
11082 focusButton();
11083 }
11084 }
11085 },
11086
11087 _handleMenuPageHide: function() {
11088
11089 // After the dialog's done, we may want to trigger change if the value has actually changed
11090 this._delayedTrigger();
11091
11092 // TODO centralize page removal binding / handling in the page plugin.
11093 // Suggestion from @jblas to do refcounting
11094 //
11095 // TODO extremely confusing dependency on the open method where the pagehide.remove
11096 // bindings are stripped to prevent the parent page from disappearing. The way
11097 // we're keeping pages in the DOM right now sucks
11098 //
11099 // rebind the page remove that was unbound in the open function
11100 // to allow for the parent page removal from actions other than the use
11101 // of a dialog sized custom select
11102 //
11103 // doing this here provides for the back button on the custom select dialog
11104 this.thisPage.page( "bindRemove" );
11105 },
11106
11107 _handleHeaderCloseClick: function() {
11108 if ( this.menuType === "overlay" ) {
11109 this.close();
11110 return false;
11111 }
11112 },
11113
11114 _handleListItemClick: function( event ) {
11115 var listItem = $( event.target ).closest( "li" ),
11116
11117 // Index of option tag to be selected
11118 oldIndex = this.select[ 0 ].selectedIndex,
11119 newIndex = $.mobile.getAttribute( listItem, "option-index" ),
11120 option = this._selectOptions().eq( newIndex )[ 0 ];
11121
11122 // Toggle selected status on the tag for multi selects
11123 option.selected = this.isMultiple ? !option.selected : true;
11124
11125 // Toggle checkbox class for multiple selects
11126 if ( this.isMultiple ) {
11127 listItem.find( "a" )
11128 .toggleClass( "ui-checkbox-on", option.selected )
11129 .toggleClass( "ui-checkbox-off", !option.selected );
11130 }
11131
11132 // If it's not a multiple select, trigger change after it has finished closing
11133 if ( !this.isMultiple && oldIndex !== newIndex ) {
11134 this._triggerChange = true;
11135 }
11136
11137 // Trigger change if it's a multiple select
11138 // Hide custom select for single selects only - otherwise focus clicked item
11139 // We need to grab the clicked item the hard way, because the list may have been rebuilt
11140 if ( this.isMultiple ) {
11141 this.select.trigger( "change" );
11142 this.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
11143 .find( "a" ).first().focus();
11144 }
11145 else {
11146 this.close();
11147 }
11148
11149 event.preventDefault();
11150 },
11151
11152 build: function() {
11153 var selectId, popupId, dialogId, label, thisPage, isMultiple, menuId,
11154 themeAttr, overlayTheme, overlayThemeAttr, dividerThemeAttr,
11155 menuPage, listbox, list, header, headerTitle, menuPageContent,
11156 menuPageClose, headerClose,
11157 o = this.options;
11158
11159 if ( o.nativeMenu ) {
11160 return this._super();
11161 }
11162
11163 selectId = this.selectId;
11164 popupId = selectId + "-listbox";
11165 dialogId = selectId + "-dialog";
11166 label = this.label;
11167 thisPage = this.element.closest( ".ui-page" );
11168 isMultiple = this.element[ 0 ].multiple;
11169 menuId = selectId + "-menu";
11170 themeAttr = o.theme ? ( " data-" + $.mobile.ns + "theme='" + o.theme + "'" ) : "";
11171 overlayTheme = o.overlayTheme || o.theme || null;
11172 overlayThemeAttr = overlayTheme ? ( " data-" + $.mobile.ns +
11173 "overlay-theme='" + overlayTheme + "'" ) : "";
11174 dividerThemeAttr = ( o.dividerTheme && this.element.children( "optgroup" ).length > 0 ) ?
11175 ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
11176 menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu'" +
11177 themeAttr + overlayThemeAttr + ">" +
11178 "<div data-" + $.mobile.ns + "role='header'>" +
11179 "<div class='ui-title'></div>"+
11180 "</div>"+
11181 "<div data-" + $.mobile.ns + "role='content'></div>"+
11182 "</div>" )
11183 .attr( "id", dialogId );
11184 listbox = $( "<div" + themeAttr + overlayThemeAttr +
11185 " class='ui-selectmenu'></div>" )
11186 .attr( "id", popupId )
11187 .insertAfter( this.select )
11188 .popup();
11189 list = $( "<ul class='ui-selectmenu-list' role='listbox' aria-labelledby='" +
11190 this.buttonId + "'" + themeAttr + dividerThemeAttr + "></ul>" )
11191 .attr( "id", menuId )
11192 .appendTo( listbox );
11193 header = $( "<div class='ui-header ui-bar-" + ( o.theme ? o.theme : "inherit" ) + "'></div>" ).prependTo( listbox );
11194 headerTitle = $( "<h1 class='ui-title'></h1>" ).appendTo( header );
11195
11196 if ( this.isMultiple ) {
11197 headerClose = $( "<a>", {
11198 "role": "button",
11199 "text": o.closeText,
11200 "href": "#",
11201 "class": "ui-btn ui-corner-all ui-btn-left ui-btn-icon-notext ui-icon-delete"
11202 }).appendTo( header );
11203 }
11204
11205 $.extend( this, {
11206 selectId: selectId,
11207 menuId: menuId,
11208 popupId: popupId,
11209 dialogId: dialogId,
11210 thisPage: thisPage,
11211 menuPage: menuPage,
11212 label: label,
11213 isMultiple: isMultiple,
11214 theme: o.theme,
11215 listbox: listbox,
11216 list: list,
11217 header: header,
11218 headerTitle: headerTitle,
11219 headerClose: headerClose,
11220 menuPageContent: menuPageContent,
11221 menuPageClose: menuPageClose,
11222 placeholder: ""
11223 });
11224
11225 // Create list from select, update state
11226 this.refresh();
11227
11228 if ( this._origTabIndex === undefined ) {
11229 // Map undefined to false, because this._origTabIndex === undefined
11230 // indicates that we have not yet checked whether the select has
11231 // originally had a tabindex attribute, whereas false indicates that
11232 // we have checked the select for such an attribute, and have found
11233 // none present.
11234 this._origTabIndex = ( this.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : this.select.attr( "tabindex" );
11235 }
11236 this.select.attr( "tabindex", "-1" );
11237 this._on( this.select, { focus : "_handleSelectFocus" } );
11238
11239 // Button events
11240 this._on( this.button, {
11241 vclick: "_handleButtonVclickKeydown"
11242 });
11243
11244 // Events for list items
11245 this.list.attr( "role", "listbox" );
11246 this._on( this.list, {
11247 "focusin": "_handleListFocus",
11248 "focusout": "_handleListFocus",
11249 "keydown": "_handleListKeydown",
11250 "click li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)": "_handleListItemClick"
11251 });
11252
11253 // button refocus ensures proper height calculation
11254 // by removing the inline style and ensuring page inclusion
11255 this._on( this.menuPage, { pagehide: "_handleMenuPageHide" } );
11256
11257 // Events on the popup
11258 this._on( this.listbox, { popupafterclose: "_popupClosed" } );
11259
11260 // Close button on small overlays
11261 if ( this.isMultiple ) {
11262 this._on( this.headerClose, { click: "_handleHeaderCloseClick" } );
11263 }
11264
11265 this._on( this.document, { pagecontainerbeforetransition: "_handleBeforeTransition" } );
11266
11267 return this;
11268 },
11269
11270 _popupClosed: function() {
11271 this.close();
11272 this._delayedTrigger();
11273 },
11274
11275 _delayedTrigger: function() {
11276 if ( this._triggerChange ) {
11277 this.element.trigger( "change" );
11278 }
11279 this._triggerChange = false;
11280 },
11281
11282 _isRebuildRequired: function() {
11283 var list = this.list.find( "li" ),
11284 options = this._selectOptions().not( ".ui-screen-hidden" );
11285
11286 // TODO exceedingly naive method to determine difference
11287 // ignores value changes etc in favor of a forcedRebuild
11288 // from the user in the refresh method
11289 return options.text() !== list.text();
11290 },
11291
11292 selected: function() {
11293 return this._selectOptions().filter( ":selected:not( :jqmData(placeholder='true') )" );
11294 },
11295
11296 refresh: function( force ) {
11297 var self, indices;
11298
11299 if ( this.options.nativeMenu ) {
11300 return this._super( force );
11301 }
11302
11303 self = this;
11304 if ( force || this._isRebuildRequired() ) {
11305 self._buildList();
11306 }
11307
11308 indices = this.selectedIndices();
11309
11310 self.setButtonText();
11311 self.setButtonCount();
11312
11313 self.list.find( "li:not(.ui-li-divider)" )
11314 .find( "a" ).removeClass( $.mobile.activeBtnClass ).end()
11315 .attr( "aria-selected", false )
11316 .each(function( i ) {
11317 var item = $( this );
11318 if ( $.inArray( i, indices ) > -1 ) {
11319
11320 // Aria selected attr
11321 item.attr( "aria-selected", true );
11322
11323 // Multiple selects: add the "on" checkbox state to the icon
11324 if ( self.isMultiple ) {
11325 item.find( "a" ).removeClass( "ui-checkbox-off" ).addClass( "ui-checkbox-on" );
11326 } else {
11327 if ( item.hasClass( "ui-screen-hidden" ) ) {
11328 item.next().find( "a" ).addClass( $.mobile.activeBtnClass );
11329 } else {
11330 item.find( "a" ).addClass( $.mobile.activeBtnClass );
11331 }
11332 }
11333 } else if ( self.isMultiple ) {
11334 item.find( "a" ).removeClass( "ui-checkbox-on" ).addClass( "ui-checkbox-off" );
11335 }
11336 });
11337 },
11338
11339 close: function() {
11340 if ( this.options.disabled || !this.isOpen ) {
11341 return;
11342 }
11343
11344 var self = this;
11345
11346 if ( self.menuType === "page" ) {
11347 self.menuPage.dialog( "close" );
11348 self.list.appendTo( self.listbox );
11349 } else {
11350 self.listbox.popup( "close" );
11351 }
11352
11353 self._focusButton();
11354 // allow the dialog to be closed again
11355 self.isOpen = false;
11356 },
11357
11358 open: function() {
11359 this.button.click();
11360 },
11361
11362 _focusMenuItem: function() {
11363 var selector = this.list.find( "a." + $.mobile.activeBtnClass );
11364 if ( selector.length === 0 ) {
11365 selector = this.list.find( "li:not(" + unfocusableItemSelector + ") a.ui-btn" );
11366 }
11367 selector.first().focus();
11368 },
11369
11370 _decideFormat: function() {
11371 var self = this,
11372 $window = this.window,
11373 selfListParent = self.list.parent(),
11374 menuHeight = selfListParent.outerHeight(),
11375 scrollTop = $window.scrollTop(),
11376 btnOffset = self.button.offset().top,
11377 screenHeight = $window.height();
11378
11379 if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
11380
11381 self.menuPage.appendTo( $.mobile.pageContainer ).page();
11382 self.menuPageContent = self.menuPage.find( ".ui-content" );
11383 self.menuPageClose = self.menuPage.find( ".ui-header a" );
11384
11385 // prevent the parent page from being removed from the DOM,
11386 // otherwise the results of selecting a list item in the dialog
11387 // fall into a black hole
11388 self.thisPage.unbind( "pagehide.remove" );
11389
11390 //for WebOS/Opera Mini (set lastscroll using button offset)
11391 if ( scrollTop === 0 && btnOffset > screenHeight ) {
11392 self.thisPage.one( "pagehide", function() {
11393 $( this ).jqmData( "lastScroll", btnOffset );
11394 });
11395 }
11396
11397 self.menuPage.one( {
11398 pageshow: $.proxy( this, "_focusMenuItem" ),
11399 pagehide: $.proxy( this, "close" )
11400 });
11401
11402 self.menuType = "page";
11403 self.menuPageContent.append( self.list );
11404 self.menuPage
11405 .find( "div .ui-title" )
11406 .text( self.label.getEncodedText() || self.placeholder );
11407 } else {
11408 self.menuType = "overlay";
11409
11410 self.listbox.one( { popupafteropen: $.proxy( this, "_focusMenuItem" ) } );
11411 }
11412 },
11413
11414 _buildList: function() {
11415 var self = this,
11416 o = this.options,
11417 placeholder = this.placeholder,
11418 needPlaceholder = true,
11419 dataIcon = "false",
11420 $options, numOptions, select,
11421 dataPrefix = "data-" + $.mobile.ns,
11422 dataIndexAttr = dataPrefix + "option-index",
11423 dataIconAttr = dataPrefix + "icon",
11424 dataRoleAttr = dataPrefix + "role",
11425 dataPlaceholderAttr = dataPrefix + "placeholder",
11426 fragment = document.createDocumentFragment(),
11427 isPlaceholderItem = false,
11428 optGroup,
11429 i,
11430 option, $option, parent, text, anchor, classes,
11431 optLabel, divider, item;
11432
11433 self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
11434 $options = this._selectOptions();
11435 numOptions = $options.length;
11436 select = this.select[ 0 ];
11437
11438 for ( i = 0; i < numOptions;i++, isPlaceholderItem = false) {
11439 option = $options[i];
11440 $option = $( option );
11441
11442 // Do not create options based on ui-screen-hidden select options
11443 if ( $option.hasClass( "ui-screen-hidden" ) ) {
11444 continue;
11445 }
11446
11447 parent = option.parentNode;
11448 classes = [];
11449
11450 // Although using .text() here raises the risk that, when we later paste this into the
11451 // list item we end up pasting possibly malicious things like <script> tags, that risk
11452 // only arises if we do something like $( "<li><a href='#'>" + text + "</a></li>" ). We
11453 // don't do that. We do document.createTextNode( text ) instead, which guarantees that
11454 // whatever we paste in will end up as text, with characters like <, > and & escaped.
11455 text = $option.text();
11456 anchor = document.createElement( "a" );
11457 anchor.setAttribute( "href", "#" );
11458 anchor.appendChild( document.createTextNode( text ) );
11459
11460 // Are we inside an optgroup?
11461 if ( parent !== select && parent.nodeName.toLowerCase() === "optgroup" ) {
11462 optLabel = parent.getAttribute( "label" );
11463 if ( optLabel !== optGroup ) {
11464 divider = document.createElement( "li" );
11465 divider.setAttribute( dataRoleAttr, "list-divider" );
11466 divider.setAttribute( "role", "option" );
11467 divider.setAttribute( "tabindex", "-1" );
11468 divider.appendChild( document.createTextNode( optLabel ) );
11469 fragment.appendChild( divider );
11470 optGroup = optLabel;
11471 }
11472 }
11473
11474 if ( needPlaceholder && ( !option.getAttribute( "value" ) || text.length === 0 || $option.jqmData( "placeholder" ) ) ) {
11475 needPlaceholder = false;
11476 isPlaceholderItem = true;
11477
11478 // If we have identified a placeholder, record the fact that it was
11479 // us who have added the placeholder to the option and mark it
11480 // retroactively in the select as well
11481 if ( null === option.getAttribute( dataPlaceholderAttr ) ) {
11482 this._removePlaceholderAttr = true;
11483 }
11484 option.setAttribute( dataPlaceholderAttr, true );
11485 if ( o.hidePlaceholderMenuItems ) {
11486 classes.push( "ui-screen-hidden" );
11487 }
11488 if ( placeholder !== text ) {
11489 placeholder = self.placeholder = text;
11490 }
11491 }
11492
11493 item = document.createElement( "li" );
11494 if ( option.disabled ) {
11495 classes.push( "ui-state-disabled" );
11496 item.setAttribute( "aria-disabled", true );
11497 }
11498 item.setAttribute( dataIndexAttr, i );
11499 item.setAttribute( dataIconAttr, dataIcon );
11500 if ( isPlaceholderItem ) {
11501 item.setAttribute( dataPlaceholderAttr, true );
11502 }
11503 item.className = classes.join( " " );
11504 item.setAttribute( "role", "option" );
11505 anchor.setAttribute( "tabindex", "-1" );
11506 if ( this.isMultiple ) {
11507 $( anchor ).addClass( "ui-btn ui-checkbox-off ui-btn-icon-right" );
11508 }
11509
11510 item.appendChild( anchor );
11511 fragment.appendChild( item );
11512 }
11513
11514 self.list[0].appendChild( fragment );
11515
11516 // Hide header if it's not a multiselect and there's no placeholder
11517 if ( !this.isMultiple && !placeholder.length ) {
11518 this.header.addClass( "ui-screen-hidden" );
11519 } else {
11520 this.headerTitle.text( this.placeholder );
11521 }
11522
11523 // Now populated, create listview
11524 self.list.listview();
11525 },
11526
11527 _button: function() {
11528 return this.options.nativeMenu ?
11529 this._super() :
11530 $( "<a>", {
11531 "href": "#",
11532 "role": "button",
11533 // TODO value is undefined at creation
11534 "id": this.buttonId,
11535 "aria-haspopup": "true",
11536
11537 // TODO value is undefined at creation
11538 "aria-owns": this.menuId
11539 });
11540 },
11541
11542 _destroy: function() {
11543
11544 if ( !this.options.nativeMenu ) {
11545 this.close();
11546
11547 // Restore the tabindex attribute to its original value
11548 if ( this._origTabIndex !== undefined ) {
11549 if ( this._origTabIndex !== false ) {
11550 this.select.attr( "tabindex", this._origTabIndex );
11551 } else {
11552 this.select.removeAttr( "tabindex" );
11553 }
11554 }
11555
11556 // Remove the placeholder attribute if we were the ones to add it
11557 if ( this._removePlaceholderAttr ) {
11558 this._selectOptions().removeAttr( "data-" + $.mobile.ns + "placeholder" );
11559 }
11560
11561 // Remove the popup
11562 this.listbox.remove();
11563
11564 // Remove the dialog
11565 this.menuPage.remove();
11566 }
11567
11568 // Chain up
11569 this._super();
11570 }
11571});
11572
11573})( jQuery );
11574
11575
11576// buttonMarkup is deprecated as of 1.4.0 and will be removed in 1.5.0.
11577
11578(function( $, undefined ) {
11579
11580// General policy: Do not access data-* attributes except during enhancement.
11581// In all other cases we determine the state of the button exclusively from its
11582// className. That's why optionsToClasses expects a full complement of options,
11583// and the jQuery plugin completes the set of options from the default values.
11584
11585// Map classes to buttonMarkup boolean options - used in classNameToOptions()
11586var reverseBoolOptionMap = {
11587 "ui-shadow" : "shadow",
11588 "ui-corner-all" : "corners",
11589 "ui-btn-inline" : "inline",
11590 "ui-shadow-icon" : "iconshadow", /* TODO: Remove in 1.5 */
11591 "ui-mini" : "mini"
11592 },
11593 getAttrFixed = function() {
11594 var ret = $.mobile.getAttribute.apply( this, arguments );
11595
11596 return ( ret == null ? undefined : ret );
11597 },
11598 capitalLettersRE = /[A-Z]/g;
11599
11600// optionsToClasses:
11601// @options: A complete set of options to convert to class names.
11602// @existingClasses: extra classes to add to the result
11603//
11604// Converts @options to buttonMarkup classes and returns the result as an array
11605// that can be converted to an element's className with .join( " " ). All
11606// possible options must be set inside @options. Use $.fn.buttonMarkup.defaults
11607// to get a complete set and use $.extend to override your choice of options
11608// from that set.
11609function optionsToClasses( options, existingClasses ) {
11610 var classes = existingClasses ? existingClasses : [];
11611
11612 // Add classes to the array - first ui-btn
11613 classes.push( "ui-btn" );
11614
11615 // If there is a theme
11616 if ( options.theme ) {
11617 classes.push( "ui-btn-" + options.theme );
11618 }
11619
11620 // If there's an icon, add the icon-related classes
11621 if ( options.icon ) {
11622 classes = classes.concat([
11623 "ui-icon-" + options.icon,
11624 "ui-btn-icon-" + options.iconpos
11625 ]);
11626 if ( options.iconshadow ) {
11627 classes.push( "ui-shadow-icon" ); /* TODO: Remove in 1.5 */
11628 }
11629 }
11630
11631 // Add the appropriate class for each boolean option
11632 if ( options.inline ) {
11633 classes.push( "ui-btn-inline" );
11634 }
11635 if ( options.shadow ) {
11636 classes.push( "ui-shadow" );
11637 }
11638 if ( options.corners ) {
11639 classes.push( "ui-corner-all" );
11640 }
11641 if ( options.mini ) {
11642 classes.push( "ui-mini" );
11643 }
11644
11645 // Create a string from the array and return it
11646 return classes;
11647}
11648
11649// classNameToOptions:
11650// @classes: A string containing a .className-style space-separated class list
11651//
11652// Loops over @classes and calculates an options object based on the
11653// buttonMarkup-related classes it finds. It records unrecognized classes in an
11654// array.
11655//
11656// Returns: An object containing the following items:
11657//
11658// "options": buttonMarkup options found to be present because of the
11659// presence/absence of corresponding classes
11660//
11661// "unknownClasses": a string containing all the non-buttonMarkup-related
11662// classes found in @classes
11663//
11664// "alreadyEnhanced": A boolean indicating whether the ui-btn class was among
11665// those found to be present
11666function classNameToOptions( classes ) {
11667 var idx, map, unknownClass,
11668 alreadyEnhanced = false,
11669 noIcon = true,
11670 o = {
11671 icon: "",
11672 inline: false,
11673 shadow: false,
11674 corners: false,
11675 iconshadow: false,
11676 mini: false
11677 },
11678 unknownClasses = [];
11679
11680 classes = classes.split( " " );
11681
11682 // Loop over the classes
11683 for ( idx = 0 ; idx < classes.length ; idx++ ) {
11684
11685 // Assume it's an unrecognized class
11686 unknownClass = true;
11687
11688 // Recognize boolean options from the presence of classes
11689 map = reverseBoolOptionMap[ classes[ idx ] ];
11690 if ( map !== undefined ) {
11691 unknownClass = false;
11692 o[ map ] = true;
11693
11694 // Recognize the presence of an icon and establish the icon position
11695 } else if ( classes[ idx ].indexOf( "ui-btn-icon-" ) === 0 ) {
11696 unknownClass = false;
11697 noIcon = false;
11698 o.iconpos = classes[ idx ].substring( 12 );
11699
11700 // Establish which icon is present
11701 } else if ( classes[ idx ].indexOf( "ui-icon-" ) === 0 ) {
11702 unknownClass = false;
11703 o.icon = classes[ idx ].substring( 8 );
11704
11705 // Establish the theme - this recognizes one-letter theme swatch names
11706 } else if ( classes[ idx ].indexOf( "ui-btn-" ) === 0 && classes[ idx ].length === 8 ) {
11707 unknownClass = false;
11708 o.theme = classes[ idx ].substring( 7 );
11709
11710 // Recognize that this element has already been buttonMarkup-enhanced
11711 } else if ( classes[ idx ] === "ui-btn" ) {
11712 unknownClass = false;
11713 alreadyEnhanced = true;
11714 }
11715
11716 // If this class has not been recognized, add it to the list
11717 if ( unknownClass ) {
11718 unknownClasses.push( classes[ idx ] );
11719 }
11720 }
11721
11722 // If a "ui-btn-icon-*" icon position class is absent there cannot be an icon
11723 if ( noIcon ) {
11724 o.icon = "";
11725 }
11726
11727 return {
11728 options: o,
11729 unknownClasses: unknownClasses,
11730 alreadyEnhanced: alreadyEnhanced
11731 };
11732}
11733
11734function camelCase2Hyphenated( c ) {
11735 return "-" + c.toLowerCase();
11736}
11737
11738// $.fn.buttonMarkup:
11739// DOM: gets/sets .className
11740//
11741// @options: options to apply to the elements in the jQuery object
11742// @overwriteClasses: boolean indicating whether to honour existing classes
11743//
11744// Calculates the classes to apply to the elements in the jQuery object based on
11745// the options passed in. If @overwriteClasses is true, it sets the className
11746// property of each element in the jQuery object to the buttonMarkup classes
11747// it calculates based on the options passed in.
11748//
11749// If you wish to preserve any classes that are already present on the elements
11750// inside the jQuery object, including buttonMarkup-related classes that were
11751// added by a previous call to $.fn.buttonMarkup() or during page enhancement
11752// then you should omit @overwriteClasses or set it to false.
11753$.fn.buttonMarkup = function( options, overwriteClasses ) {
11754 var idx, data, el, retrievedOptions, optionKey,
11755 defaults = $.fn.buttonMarkup.defaults;
11756
11757 for ( idx = 0 ; idx < this.length ; idx++ ) {
11758 el = this[ idx ];
11759 data = overwriteClasses ?
11760
11761 // Assume this element is not enhanced and ignore its classes
11762 { alreadyEnhanced: false, unknownClasses: [] } :
11763
11764 // Otherwise analyze existing classes to establish existing options and
11765 // classes
11766 classNameToOptions( el.className );
11767
11768 retrievedOptions = $.extend( {},
11769
11770 // If the element already has the class ui-btn, then we assume that
11771 // it has passed through buttonMarkup before - otherwise, the options
11772 // returned by classNameToOptions do not correctly reflect the state of
11773 // the element
11774 ( data.alreadyEnhanced ? data.options : {} ),
11775
11776 // Finally, apply the options passed in
11777 options );
11778
11779 // If this is the first call on this element, retrieve remaining options
11780 // from the data-attributes
11781 if ( !data.alreadyEnhanced ) {
11782 for ( optionKey in defaults ) {
11783 if ( retrievedOptions[ optionKey ] === undefined ) {
11784 retrievedOptions[ optionKey ] = getAttrFixed( el,
11785 optionKey.replace( capitalLettersRE, camelCase2Hyphenated )
11786 );
11787 }
11788 }
11789 }
11790
11791 el.className = optionsToClasses(
11792
11793 // Merge all the options and apply them as classes
11794 $.extend( {},
11795
11796 // The defaults form the basis
11797 defaults,
11798
11799 // Add the computed options
11800 retrievedOptions
11801 ),
11802
11803 // ... and re-apply any unrecognized classes that were found
11804 data.unknownClasses ).join( " " );
11805 if ( el.tagName.toLowerCase() !== "button" ) {
11806 el.setAttribute( "role", "button" );
11807 }
11808 }
11809
11810 return this;
11811};
11812
11813// buttonMarkup defaults. This must be a complete set, i.e., a value must be
11814// given here for all recognized options
11815$.fn.buttonMarkup.defaults = {
11816 icon: "",
11817 iconpos: "left",
11818 theme: null,
11819 inline: false,
11820 shadow: true,
11821 corners: true,
11822 iconshadow: false, /* TODO: Remove in 1.5. Option deprecated in 1.4. */
11823 mini: false
11824};
11825
11826$.extend( $.fn.buttonMarkup, {
11827 initSelector: "a:jqmData(role='button'), .ui-bar > a, .ui-bar > :jqmData(role='controlgroup') > a, button:not(:jqmData(role='navbar') button)"
11828});
11829
11830})( jQuery );
11831
11832
11833(function( $, undefined ) {
11834
11835$.widget( "mobile.controlgroup", $.extend( {
11836 options: {
11837 enhanced: false,
11838 theme: null,
11839 shadow: false,
11840 corners: true,
11841 excludeInvisible: true,
11842 type: "vertical",
11843 mini: false
11844 },
11845
11846 _create: function() {
11847 var elem = this.element,
11848 opts = this.options,
11849 keepNative = $.mobile.page.prototype.keepNativeSelector();
11850
11851 // Run buttonmarkup
11852 if ( $.fn.buttonMarkup ) {
11853 this.element
11854 .find( $.fn.buttonMarkup.initSelector )
11855 .not( keepNative )
11856 .buttonMarkup();
11857 }
11858 // Enhance child widgets
11859 $.each( this._childWidgets, $.proxy( function( number, widgetName ) {
11860 if ( $.mobile[ widgetName ] ) {
11861 this.element
11862 .find( $.mobile[ widgetName ].initSelector )
11863 .not( keepNative )[ widgetName ]();
11864 }
11865 }, this ));
11866
11867 $.extend( this, {
11868 _ui: null,
11869 _initialRefresh: true
11870 });
11871
11872 if ( opts.enhanced ) {
11873 this._ui = {
11874 groupLegend: elem.children( ".ui-controlgroup-label" ).children(),
11875 childWrapper: elem.children( ".ui-controlgroup-controls" )
11876 };
11877 } else {
11878 this._ui = this._enhance();
11879 }
11880
11881 },
11882
11883 _childWidgets: [ "checkboxradio", "selectmenu", "button" ],
11884
11885 _themeClassFromOption: function( value ) {
11886 return ( value ? ( value === "none" ? "" : "ui-group-theme-" + value ) : "" );
11887 },
11888
11889 _enhance: function() {
11890 var elem = this.element,
11891 opts = this.options,
11892 ui = {
11893 groupLegend: elem.children( "legend" ),
11894 childWrapper: elem
11895 .addClass( "ui-controlgroup " +
11896 "ui-controlgroup-" +
11897 ( opts.type === "horizontal" ? "horizontal" : "vertical" ) + " " +
11898 this._themeClassFromOption( opts.theme ) + " " +
11899 ( opts.corners ? "ui-corner-all " : "" ) +
11900 ( opts.mini ? "ui-mini " : "" ) )
11901 .wrapInner( "<div " +
11902 "class='ui-controlgroup-controls " +
11903 ( opts.shadow === true ? "ui-shadow" : "" ) + "'></div>" )
11904 .children()
11905 };
11906
11907 if ( ui.groupLegend.length > 0 ) {
11908 $( "<div role='heading' class='ui-controlgroup-label'></div>" )
11909 .append( ui.groupLegend )
11910 .prependTo( elem );
11911 }
11912
11913 return ui;
11914 },
11915
11916 _init: function() {
11917 this.refresh();
11918 },
11919
11920 _setOptions: function( options ) {
11921 var callRefresh, returnValue,
11922 elem = this.element;
11923
11924 // Must have one of horizontal or vertical
11925 if ( options.type !== undefined ) {
11926 elem
11927 .removeClass( "ui-controlgroup-horizontal ui-controlgroup-vertical" )
11928 .addClass( "ui-controlgroup-" + ( options.type === "horizontal" ? "horizontal" : "vertical" ) );
11929 callRefresh = true;
11930 }
11931
11932 if ( options.theme !== undefined ) {
11933 elem
11934 .removeClass( this._themeClassFromOption( this.options.theme ) )
11935 .addClass( this._themeClassFromOption( options.theme ) );
11936 }
11937
11938 if ( options.corners !== undefined ) {
11939 elem.toggleClass( "ui-corner-all", options.corners );
11940 }
11941
11942 if ( options.mini !== undefined ) {
11943 elem.toggleClass( "ui-mini", options.mini );
11944 }
11945
11946 if ( options.shadow !== undefined ) {
11947 this._ui.childWrapper.toggleClass( "ui-shadow", options.shadow );
11948 }
11949
11950 if ( options.excludeInvisible !== undefined ) {
11951 this.options.excludeInvisible = options.excludeInvisible;
11952 callRefresh = true;
11953 }
11954
11955 returnValue = this._super( options );
11956
11957 if ( callRefresh ) {
11958 this.refresh();
11959 }
11960
11961 return returnValue;
11962 },
11963
11964 container: function() {
11965 return this._ui.childWrapper;
11966 },
11967
11968 refresh: function() {
11969 var $el = this.container(),
11970 els = $el.find( ".ui-btn" ).not( ".ui-slider-handle" ),
11971 create = this._initialRefresh;
11972 if ( $.mobile.checkboxradio ) {
11973 $el.find( ":mobile-checkboxradio" ).checkboxradio( "refresh" );
11974 }
11975 this._addFirstLastClasses( els,
11976 this.options.excludeInvisible ? this._getVisibles( els, create ) : els,
11977 create );
11978 this._initialRefresh = false;
11979 },
11980
11981 // Caveat: If the legend is not the first child of the controlgroup at enhance
11982 // time, it will be after _destroy().
11983 _destroy: function() {
11984 var ui, buttons,
11985 opts = this.options;
11986
11987 if ( opts.enhanced ) {
11988 return this;
11989 }
11990
11991 ui = this._ui;
11992 buttons = this.element
11993 .removeClass( "ui-controlgroup " +
11994 "ui-controlgroup-horizontal ui-controlgroup-vertical ui-corner-all ui-mini " +
11995 this._themeClassFromOption( opts.theme ) )
11996 .find( ".ui-btn" )
11997 .not( ".ui-slider-handle" );
11998
11999 this._removeFirstLastClasses( buttons );
12000
12001 ui.groupLegend.unwrap();
12002 ui.childWrapper.children().unwrap();
12003 }
12004}, $.mobile.behaviors.addFirstLastClasses ) );
12005
12006})(jQuery);
12007
12008(function( $, undefined ) {
12009
12010 $.widget( "mobile.toolbar", {
12011 initSelector: ":jqmData(role='footer'), :jqmData(role='header')",
12012
12013 options: {
12014 theme: null,
12015 addBackBtn: false,
12016 backBtnTheme: null,
12017 backBtnText: "Back"
12018 },
12019
12020 _create: function() {
12021 var leftbtn, rightbtn,
12022 role = this.element.is( ":jqmData(role='header')" ) ? "header" : "footer",
12023 page = this.element.closest( ".ui-page" );
12024 if ( page.length === 0 ) {
12025 page = false;
12026 this._on( this.document, {
12027 "pageshow": "refresh"
12028 });
12029 }
12030 $.extend( this, {
12031 role: role,
12032 page: page,
12033 leftbtn: leftbtn,
12034 rightbtn: rightbtn
12035 });
12036 this.element.attr( "role", role === "header" ? "banner" : "contentinfo" ).addClass( "ui-" + role );
12037 this.refresh();
12038 this._setOptions( this.options );
12039 },
12040 _setOptions: function( o ) {
12041 if ( o.addBackBtn !== undefined ) {
12042 this._updateBackButton();
12043 }
12044 if ( o.backBtnTheme != null ) {
12045 this.element
12046 .find( ".ui-toolbar-back-btn" )
12047 .addClass( "ui-btn ui-btn-" + o.backBtnTheme );
12048 }
12049 if ( o.backBtnText !== undefined ) {
12050 this.element.find( ".ui-toolbar-back-btn .ui-btn-text" ).text( o.backBtnText );
12051 }
12052 if ( o.theme !== undefined ) {
12053 var currentTheme = this.options.theme ? this.options.theme : "inherit",
12054 newTheme = o.theme ? o.theme : "inherit";
12055
12056 this.element.removeClass( "ui-bar-" + currentTheme ).addClass( "ui-bar-" + newTheme );
12057 }
12058
12059 this._super( o );
12060 },
12061 refresh: function() {
12062 if ( this.role === "header" ) {
12063 this._addHeaderButtonClasses();
12064 }
12065 if ( !this.page ) {
12066 this._setRelative();
12067 if ( this.role === "footer" ) {
12068 this.element.appendTo( "body" );
12069 } else if ( this.role === "header" ) {
12070 this._updateBackButton();
12071 }
12072 }
12073 this._addHeadingClasses();
12074 this._btnMarkup();
12075 },
12076
12077 //we only want this to run on non fixed toolbars so make it easy to override
12078 _setRelative: function() {
12079 $( "[data-"+ $.mobile.ns + "role='page']" ).css({ "position": "relative" });
12080 },
12081
12082 // Deprecated in 1.4. As from 1.5 button classes have to be present in the markup.
12083 _btnMarkup: function() {
12084 this.element
12085 .children( "a" )
12086 .filter( ":not([data-" + $.mobile.ns + "role='none'])" )
12087 .attr( "data-" + $.mobile.ns + "role", "button" );
12088 this.element.trigger( "create" );
12089 },
12090 // Deprecated in 1.4. As from 1.5 ui-btn-left/right classes have to be present in the markup.
12091 _addHeaderButtonClasses: function() {
12092 var headerAnchors = this.element.children( "a, button" );
12093
12094 // Do not mistake a back button for a left toolbar button
12095 this.leftbtn = headerAnchors.hasClass( "ui-btn-left" ) &&
12096 !headerAnchors.hasClass( "ui-toolbar-back-btn" );
12097
12098 this.rightbtn = headerAnchors.hasClass( "ui-btn-right" );
12099
12100 // Filter out right buttons and back buttons
12101 this.leftbtn = this.leftbtn ||
12102 headerAnchors.eq( 0 )
12103 .not( ".ui-btn-right,.ui-toolbar-back-btn" )
12104 .addClass( "ui-btn-left" )
12105 .length;
12106
12107 this.rightbtn = this.rightbtn || headerAnchors.eq( 1 ).addClass( "ui-btn-right" ).length;
12108 },
12109 _updateBackButton: function() {
12110 var backButton,
12111 options = this.options,
12112 theme = options.backBtnTheme || options.theme;
12113
12114 // Retrieve the back button or create a new, empty one
12115 backButton = this._backButton = ( this._backButton || {} );
12116
12117 // We add a back button only if the option to do so is on
12118 if ( this.options.addBackBtn &&
12119
12120 // This must also be a header toolbar
12121 this.role === "header" &&
12122
12123 // There must be multiple pages in the DOM
12124 $( ".ui-page" ).length > 1 &&
12125 ( this.page ?
12126
12127 // If the toolbar is internal the page's URL must differ from the hash
12128 ( this.page[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) !==
12129 $.mobile.path.stripHash( location.hash ) ) :
12130
12131 // Otherwise, if the toolbar is external there must be at least one
12132 // history item to which one can go back
12133 ( $.mobile.navigate && $.mobile.navigate.history &&
12134 $.mobile.navigate.history.activeIndex > 0 ) ) &&
12135
12136 // The toolbar does not have a left button
12137 !this.leftbtn ) {
12138
12139 // Skip back button creation if one is already present
12140 if ( !backButton.attached ) {
12141 this.backButton = backButton.element = ( backButton.element ||
12142 $( "<a role='button' href='#' " +
12143 "class='ui-btn ui-corner-all ui-shadow ui-btn-left " +
12144 ( theme ? "ui-btn-" + theme + " " : "" ) +
12145 "ui-toolbar-back-btn ui-icon-caret-l ui-btn-icon-left' " +
12146 "data-" + $.mobile.ns + "rel='back'>" + options.backBtnText +
12147 "</a>" ) )
12148 .prependTo( this.element );
12149 backButton.attached = true;
12150 }
12151
12152 // If we are not adding a back button, then remove the one present, if any
12153 } else if ( backButton.element ) {
12154 backButton.element.detach();
12155 backButton.attached = false;
12156 }
12157 },
12158 _addHeadingClasses: function() {
12159 this.element.children( "h1, h2, h3, h4, h5, h6" )
12160 .addClass( "ui-title" )
12161 // Regardless of h element number in src, it becomes h1 for the enhanced page
12162 .attr({
12163 "role": "heading",
12164 "aria-level": "1"
12165 });
12166 },
12167 _destroy: function() {
12168 var currentTheme;
12169
12170 this.element.children( "h1, h2, h3, h4, h5, h6" )
12171 .removeClass( "ui-title" )
12172 .removeAttr( "role" )
12173 .removeAttr( "aria-level" );
12174
12175 if ( this.role === "header" ) {
12176 this.element.children( "a, button" )
12177 .removeClass( "ui-btn-left ui-btn-right ui-btn ui-shadow ui-corner-all" );
12178 if ( this.backButton) {
12179 this.backButton.remove();
12180 }
12181 }
12182
12183 currentTheme = this.options.theme ? this.options.theme : "inherit";
12184 this.element.removeClass( "ui-bar-" + currentTheme );
12185
12186 this.element.removeClass( "ui-" + this.role ).removeAttr( "role" );
12187 }
12188 });
12189
12190})( jQuery );
12191
12192(function( $, undefined ) {
12193
12194 $.widget( "mobile.toolbar", $.mobile.toolbar, {
12195 options: {
12196 position:null,
12197 visibleOnPageShow: true,
12198 disablePageZoom: true,
12199 transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown)
12200 fullscreen: false,
12201 tapToggle: true,
12202 tapToggleBlacklist: "a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-flipswitch, .ui-popup, .ui-panel, .ui-panel-dismiss-open",
12203 updatePagePadding: true,
12204 trackPersistentToolbars: true,
12205
12206 // Browser detection! Weeee, here we go...
12207 // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
12208 // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
12209 // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
12210 // The following function serves to rule out some popular browsers with known fixed-positioning issues
12211 // This is a plugin option like any other, so feel free to improve or overwrite it
12212 supportBlacklist: function() {
12213 return !$.support.fixedPosition;
12214 }
12215 },
12216
12217 _create: function() {
12218 this._super();
12219 if ( this.options.position === "fixed" && !this.options.supportBlacklist() ) {
12220 this.pagecontainer = this.element.closest( ".ui-mobile-viewport" );
12221 this._makeFixed();
12222 }
12223 },
12224
12225 _makeFixed: function() {
12226 this.element.addClass( "ui-"+ this.role +"-fixed" );
12227 this.updatePagePadding();
12228 this._addTransitionClass();
12229 this._bindPageEvents();
12230 this._bindToggleHandlers();
12231 },
12232
12233 _setOptions: function( o ) {
12234 if ( o.position === "fixed" && this.options.position !== "fixed" ) {
12235 this._makeFixed();
12236 }
12237 if ( this.options.position === "fixed" && !this.options.supportBlacklist() ) {
12238 var $page = ( !!this.page ) ? this.page : ( $( ".ui-page-active" ).length > 0 ) ? $( ".ui-page-active" ) : $( ".ui-page" ).eq( 0 );
12239
12240 if ( o.fullscreen !== undefined) {
12241 if ( o.fullscreen ) {
12242 this.element.addClass( "ui-"+ this.role +"-fullscreen" );
12243 $page.addClass( "ui-page-" + this.role + "-fullscreen" );
12244 }
12245 // If not fullscreen, add class to page to set top or bottom padding
12246 else {
12247 this.element.removeClass( "ui-"+ this.role +"-fullscreen" );
12248 $page.removeClass( "ui-page-" + this.role + "-fullscreen" ).addClass( "ui-page-" + this.role+ "-fixed" );
12249 }
12250 }
12251 }
12252 this._super(o);
12253 },
12254
12255 _addTransitionClass: function() {
12256 var tclass = this.options.transition;
12257
12258 if ( tclass && tclass !== "none" ) {
12259 // use appropriate slide for header or footer
12260 if ( tclass === "slide" ) {
12261 tclass = this.element.hasClass( "ui-header" ) ? "slidedown" : "slideup";
12262 }
12263
12264 this.element.addClass( tclass );
12265 }
12266 },
12267
12268 _bindPageEvents: function() {
12269 var page = ( !!this.page )? this.element.closest( ".ui-page" ): this.document;
12270 //page event bindings
12271 // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
12272 // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
12273 this._on( page , {
12274 "pagebeforeshow": "_handlePageBeforeShow",
12275 "webkitAnimationStart":"_handleAnimationStart",
12276 "animationstart":"_handleAnimationStart",
12277 "updatelayout": "_handleAnimationStart",
12278 "pageshow": "_handlePageShow",
12279 "pagebeforehide": "_handlePageBeforeHide"
12280 });
12281 },
12282
12283 _handlePageBeforeShow: function( ) {
12284 var o = this.options;
12285 if ( o.disablePageZoom ) {
12286 $.mobile.zoom.disable( true );
12287 }
12288 if ( !o.visibleOnPageShow ) {
12289 this.hide( true );
12290 }
12291 },
12292
12293 _handleAnimationStart: function() {
12294 if ( this.options.updatePagePadding ) {
12295 this.updatePagePadding( ( !!this.page )? this.page: ".ui-page-active" );
12296 }
12297 },
12298
12299 _handlePageShow: function() {
12300 this.updatePagePadding( ( !!this.page )? this.page: ".ui-page-active" );
12301 if ( this.options.updatePagePadding ) {
12302 this._on( this.window, { "throttledresize": "updatePagePadding" } );
12303 }
12304 },
12305
12306 _handlePageBeforeHide: function( e, ui ) {
12307 var o = this.options,
12308 thisFooter, thisHeader, nextFooter, nextHeader;
12309
12310 if ( o.disablePageZoom ) {
12311 $.mobile.zoom.enable( true );
12312 }
12313 if ( o.updatePagePadding ) {
12314 this._off( this.window, "throttledresize" );
12315 }
12316
12317 if ( o.trackPersistentToolbars ) {
12318 thisFooter = $( ".ui-footer-fixed:jqmData(id)", this.page );
12319 thisHeader = $( ".ui-header-fixed:jqmData(id)", this.page );
12320 nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ) || $();
12321 nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage ) || $();
12322
12323 if ( nextFooter.length || nextHeader.length ) {
12324
12325 nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
12326
12327 ui.nextPage.one( "pageshow", function() {
12328 nextHeader.prependTo( this );
12329 nextFooter.appendTo( this );
12330 });
12331 }
12332 }
12333 },
12334
12335 _visible: true,
12336
12337 // This will set the content element's top or bottom padding equal to the toolbar's height
12338 updatePagePadding: function( tbPage ) {
12339 var $el = this.element,
12340 header = ( this.role ==="header" ),
12341 pos = parseFloat( $el.css( header ? "top" : "bottom" ) );
12342
12343 // This behavior only applies to "fixed", not "fullscreen"
12344 if ( this.options.fullscreen ) { return; }
12345 // tbPage argument can be a Page object or an event, if coming from throttled resize.
12346 tbPage = ( tbPage && tbPage.type === undefined && tbPage ) || this.page || $el.closest( ".ui-page" );
12347 tbPage = ( !!this.page )? this.page: ".ui-page-active";
12348 $( tbPage ).css( "padding-" + ( header ? "top" : "bottom" ), $el.outerHeight() + pos );
12349 },
12350
12351 _useTransition: function( notransition ) {
12352 var $win = this.window,
12353 $el = this.element,
12354 scroll = $win.scrollTop(),
12355 elHeight = $el.height(),
12356 pHeight = ( !!this.page )? $el.closest( ".ui-page" ).height():$(".ui-page-active").height(),
12357 viewportHeight = $.mobile.getScreenHeight();
12358
12359 return !notransition &&
12360 ( this.options.transition && this.options.transition !== "none" &&
12361 (
12362 ( this.role === "header" && !this.options.fullscreen && scroll > elHeight ) ||
12363 ( this.role === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
12364 ) || this.options.fullscreen
12365 );
12366 },
12367
12368 show: function( notransition ) {
12369 var hideClass = "ui-fixed-hidden",
12370 $el = this.element;
12371
12372 if ( this._useTransition( notransition ) ) {
12373 this._animationInProgress = "show";
12374 $el
12375 .removeClass( "out " + hideClass )
12376 .addClass( "in" )
12377 .animationComplete( $.proxy( function () {
12378 if ( this._animationInProgress === "show" ) {
12379 this._animationInProgress = false;
12380
12381 $el.removeClass( "in" );
12382 }
12383 }, this ) );
12384 }
12385 else {
12386 $el.removeClass( hideClass );
12387 }
12388 this._visible = true;
12389 },
12390
12391 hide: function( notransition ) {
12392 var hideClass = "ui-fixed-hidden",
12393 $el = this.element,
12394 // if it's a slide transition, our new transitions need the reverse class as well to slide outward
12395 outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" );
12396
12397 if ( this._useTransition( notransition ) ) {
12398 this._animationInProgress = "hide";
12399 $el
12400 .addClass( outclass )
12401 .removeClass( "in" )
12402 .animationComplete( $.proxy( function() {
12403 if ( this._animationInProgress === "hide" ) {
12404 this._animationInProgress = false;
12405
12406 $el.addClass( hideClass ).removeClass( outclass );
12407 }
12408 }, this ) );
12409 }
12410 else {
12411 $el.addClass( hideClass ).removeClass( outclass );
12412 }
12413 this._visible = false;
12414 },
12415
12416 toggle: function() {
12417 this[ this._visible ? "hide" : "show" ]();
12418 },
12419
12420 _bindToggleHandlers: function() {
12421 this._attachToggleHandlersToPage( ( !!this.page ) ? this.page: $( ".ui-page" ) );
12422 },
12423
12424 _attachToggleHandlersToPage: function( page ) {
12425 var self = this,
12426 o = self.options;
12427
12428 // tap toggle
12429 page
12430 .bind( "vclick", function( e ) {
12431 if ( o.tapToggle && !$( e.target ).closest( o.tapToggleBlacklist ).length ) {
12432 self.toggle();
12433 }
12434 });
12435 },
12436
12437 _setRelative: function() {
12438 if( this.options.position !== "fixed" ){
12439 $( "[data-"+ $.mobile.ns + "role='page']" ).css({ "position": "relative" });
12440 }
12441 },
12442
12443 _destroy: function() {
12444 var pageClasses, toolbarClasses, hasFixed, header, hasFullscreen,
12445 page = ( !!this.page ) ? this.page : ( $( ".ui-page-active" ).length > 0 ) ? $( ".ui-page-active" ) : $( ".ui-page" ).eq( 0 );
12446
12447 this._super();
12448 if ( this.options.position === "fixed" ) {
12449 hasFixed = $( "body>.ui-" + this.role + "-fixed" )
12450 .add( page.find( ".ui-" + this.options.role + "-fixed" ) )
12451 .not( this.element ).length > 0;
12452 hasFullscreen = $( "body>.ui-" + this.role + "-fixed" )
12453 .add( page.find( ".ui-" + this.options.role + "-fullscreen" ) )
12454 .not( this.element ).length > 0;
12455 toolbarClasses = "ui-header-fixed ui-footer-fixed ui-header-fullscreen in out" +
12456 " ui-footer-fullscreen fade slidedown slideup ui-fixed-hidden";
12457 this.element.removeClass( toolbarClasses );
12458 if ( !hasFullscreen ) {
12459 pageClasses = "ui-page-" + this.role + "-fullscreen";
12460 }
12461 if ( !hasFixed ) {
12462 header = this.role === "header";
12463 pageClasses += " ui-page-" + this.role + "-fixed";
12464 page.css( "padding-" + ( header ? "top" : "bottom" ), "" );
12465 }
12466 page.removeClass( pageClasses );
12467 }
12468 }
12469
12470 });
12471})( jQuery );
12472
12473
12474( function( $, undefined ) {
12475
12476if ( $.mobileBackcompat !== false ) {
12477
12478 $.widget( "mobile.toolbar", $.mobile.toolbar, {
12479
12480 options: {
12481 hideDuringFocus: "input, textarea, select"
12482 },
12483
12484 _hideDuringFocusData: {
12485 delayShow: 0,
12486 delayHide: 0,
12487 isVisible: true
12488 },
12489
12490 _handlePageFocusinFocusout: function( event ) {
12491 var data = this._hideDuringFocusData;
12492
12493 // This hides the toolbars on a keyboard pop to give more screen room and prevent
12494 // ios bug which positions fixed toolbars in the middle of the screen on pop if the
12495 // input is near the top or bottom of the screen addresses issues #4410 Footer
12496 // navbar moves up when clicking on a textbox in an Android environment and issue
12497 // #4113 Header and footer change their position after keyboard popup - iOS and
12498 // issue #4410 Footer navbar moves up when clicking on a textbox in an Android
12499 // environment
12500 if ( this.options.hideDuringFocus && screen.width < 1025 &&
12501 $( event.target ).is( this.options.hideDuringFocus ) &&
12502 !$( event.target )
12503 .closest( ".ui-header-fixed, .ui-footer-fixed" ).length ) {
12504
12505 // Fix for issue #4724 Moving through form in Mobile Safari with "Next" and
12506 // "Previous" system controls causes fixed position, tap-toggle false Header to
12507 // reveal itself isVisible instead of self._visible because the focusin and
12508 // focusout events fire twice at the same time Also use a delay for hiding the
12509 // toolbars because on Android native browser focusin is direclty followed by a
12510 // focusout when a native selects opens and the other way around when it closes.
12511 if ( event.type === "focusout" && !data.isVisible ) {
12512 data.isVisible = true;
12513
12514 // Wait for the stack to unwind and see if we have jumped to another input
12515 clearTimeout( data.delayHide );
12516 data.delayShow = this._delay( "show", 0 );
12517 } else if ( event.type === "focusin" && !!data.isVisible ) {
12518
12519 // If we have jumped to another input clear the time out to cancel the show
12520 clearTimeout( data.delayShow );
12521 data.isVisible = false;
12522 data.delayHide = this._delay( "hide", 0 );
12523 }
12524 }
12525 },
12526
12527 _attachToggleHandlersToPage: function( page ) {
12528 this._on( page, {
12529 focusin: "_handlePageFocusinFocusout",
12530 focusout: "_handlePageFocusinFocusout"
12531 } );
12532 return this._superApply( arguments );
12533 }
12534
12535 } );
12536
12537}
12538
12539} )( jQuery );
12540
12541(function( $, undefined ) {
12542 $.widget( "mobile.toolbar", $.mobile.toolbar, {
12543
12544 _makeFixed: function() {
12545 this._super();
12546 this._workarounds();
12547 },
12548
12549 //check the browser and version and run needed workarounds
12550 _workarounds: function() {
12551 var ua = navigator.userAgent,
12552 platform = navigator.platform,
12553 // Rendering engine is Webkit, and capture major version
12554 wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
12555 wkversion = !!wkmatch && wkmatch[ 1 ],
12556 os = null,
12557 self = this;
12558 //set the os we are working in if it dosent match one with workarounds return
12559 if ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) {
12560 os = "ios";
12561 } else if ( ua.indexOf( "Android" ) > -1 ) {
12562 os = "android";
12563 } else {
12564 return;
12565 }
12566 //check os version if it dosent match one with workarounds return
12567 if ( os === "ios" ) {
12568 //iOS workarounds
12569 self._bindScrollWorkaround();
12570 } else if ( os === "android" && wkversion && wkversion < 534 ) {
12571 //Android 2.3 run all Android 2.3 workaround
12572 self._bindScrollWorkaround();
12573 self._bindListThumbWorkaround();
12574 } else {
12575 return;
12576 }
12577 },
12578
12579 //Utility class for checking header and footer positions relative to viewport
12580 _viewportOffset: function() {
12581 var $el = this.element,
12582 header = $el.hasClass( "ui-header" ),
12583 offset = Math.abs( $el.offset().top - this.window.scrollTop() );
12584 if ( !header ) {
12585 offset = Math.round( offset - this.window.height() + $el.outerHeight() ) - 60;
12586 }
12587 return offset;
12588 },
12589
12590 //bind events for _triggerRedraw() function
12591 _bindScrollWorkaround: function() {
12592 var self = this;
12593 //bind to scrollstop and check if the toolbars are correctly positioned
12594 this._on( this.window, { scrollstop: function() {
12595 var viewportOffset = self._viewportOffset();
12596 //check if the header is visible and if its in the right place
12597 if ( viewportOffset > 2 && self._visible ) {
12598 self._triggerRedraw();
12599 }
12600 }});
12601 },
12602
12603 //this addresses issue #4250 Persistent footer instability in v1.1 with long select lists in Android 2.3.3
12604 //and issue #3748 Android 2.x: Page transitions broken when fixed toolbars used
12605 //the absolutely positioned thumbnail in a list view causes problems with fixed position buttons above in a nav bar
12606 //setting the li's to -webkit-transform:translate3d(0,0,0); solves this problem to avoide potential issues in other
12607 //platforms we scope this with the class ui-android-2x-fix
12608 _bindListThumbWorkaround: function() {
12609 this.element.closest( ".ui-page" ).addClass( "ui-android-2x-fixed" );
12610 },
12611 //this addresses issues #4337 Fixed header problem after scrolling content on iOS and Android
12612 //and device bugs project issue #1 Form elements can lose click hit area in position: fixed containers.
12613 //this also addresses not on fixed toolbars page in docs
12614 //adding 1px of padding to the bottom then removing it causes a "redraw"
12615 //which positions the toolbars correctly (they will always be visually correct)
12616 _triggerRedraw: function() {
12617 var paddingBottom = parseFloat( $( ".ui-page-active" ).css( "padding-bottom" ) );
12618 //trigger page redraw to fix incorrectly positioned fixed elements
12619 $( ".ui-page-active" ).css( "padding-bottom", ( paddingBottom + 1 ) + "px" );
12620 //if the padding is reset with out a timeout the reposition will not occure.
12621 //this is independent of JQM the browser seems to need the time to react.
12622 setTimeout( function() {
12623 $( ".ui-page-active" ).css( "padding-bottom", paddingBottom + "px" );
12624 }, 0 );
12625 },
12626
12627 destroy: function() {
12628 this._super();
12629 //Remove the class we added to the page previously in android 2.x
12630 this.element.closest( ".ui-page-active" ).removeClass( "ui-android-2x-fix" );
12631 }
12632 });
12633
12634})( jQuery );
12635
12636
12637( function( $, undefined ) {
12638
12639var ieHack = ( $.mobile.browser.oldIE && $.mobile.browser.oldIE <= 8 ),
12640 uiTemplate = $(
12641 "<div class='ui-popup-arrow-guide'></div>" +
12642 "<div class='ui-popup-arrow-container" + ( ieHack ? " ie" : "" ) + "'>" +
12643 "<div class='ui-popup-arrow'></div>" +
12644 "</div>"
12645 );
12646
12647function getArrow() {
12648 var clone = uiTemplate.clone(),
12649 gd = clone.eq( 0 ),
12650 ct = clone.eq( 1 ),
12651 ar = ct.children();
12652
12653 return { arEls: ct.add( gd ), gd: gd, ct: ct, ar: ar };
12654}
12655
12656$.widget( "mobile.popup", $.mobile.popup, {
12657 options: {
12658
12659 arrow: ""
12660 },
12661
12662 _create: function() {
12663 var ar,
12664 ret = this._super();
12665
12666 if ( this.options.arrow ) {
12667 this._ui.arrow = ar = this._addArrow();
12668 }
12669
12670 return ret;
12671 },
12672
12673 _addArrow: function() {
12674 var theme,
12675 opts = this.options,
12676 ar = getArrow();
12677
12678 theme = this._themeClassFromOption( "ui-body-", opts.theme );
12679 ar.ar.addClass( theme + ( opts.shadow ? " ui-overlay-shadow" : "" ) );
12680 ar.arEls.hide().appendTo( this.element );
12681
12682 return ar;
12683 },
12684
12685 _unenhance: function() {
12686 var ar = this._ui.arrow;
12687
12688 if ( ar ) {
12689 ar.arEls.remove();
12690 }
12691
12692 return this._super();
12693 },
12694
12695 // Pretend to show an arrow described by @p and @dir and calculate the
12696 // distance from the desired point. If a best-distance is passed in, return
12697 // the minimum of the one passed in and the one calculated.
12698 _tryAnArrow: function( p, dir, desired, s, best ) {
12699 var result, r, diff, desiredForArrow = {}, tip = {};
12700
12701 // If the arrow has no wiggle room along the edge of the popup, it cannot
12702 // be displayed along the requested edge without it sticking out.
12703 if ( s.arFull[ p.dimKey ] > s.guideDims[ p.dimKey ] ) {
12704 return best;
12705 }
12706
12707 desiredForArrow[ p.fst ] = desired[ p.fst ] +
12708 ( s.arHalf[ p.oDimKey ] + s.menuHalf[ p.oDimKey ] ) * p.offsetFactor -
12709 s.contentBox[ p.fst ] + ( s.clampInfo.menuSize[ p.oDimKey ] - s.contentBox[ p.oDimKey ] ) * p.arrowOffsetFactor;
12710 desiredForArrow[ p.snd ] = desired[ p.snd ];
12711
12712 result = s.result || this._calculateFinalLocation( desiredForArrow, s.clampInfo );
12713 r = { x: result.left, y: result.top };
12714
12715 tip[ p.fst ] = r[ p.fst ] + s.contentBox[ p.fst ] + p.tipOffset;
12716 tip[ p.snd ] = Math.max( result[ p.prop ] + s.guideOffset[ p.prop ] + s.arHalf[ p.dimKey ],
12717 Math.min( result[ p.prop ] + s.guideOffset[ p.prop ] + s.guideDims[ p.dimKey ] - s.arHalf[ p.dimKey ],
12718 desired[ p.snd ] ) );
12719
12720 diff = Math.abs( desired.x - tip.x ) + Math.abs( desired.y - tip.y );
12721 if ( !best || diff < best.diff ) {
12722 // Convert tip offset to coordinates inside the popup
12723 tip[ p.snd ] -= s.arHalf[ p.dimKey ] + result[ p.prop ] + s.contentBox[ p.snd ];
12724 best = { dir: dir, diff: diff, result: result, posProp: p.prop, posVal: tip[ p.snd ] };
12725 }
12726
12727 return best;
12728 },
12729
12730 _getPlacementState: function( clamp ) {
12731 var offset, gdOffset,
12732 ar = this._ui.arrow,
12733 state = {
12734 clampInfo: this._clampPopupWidth( !clamp ),
12735 arFull: { cx: ar.ct.width(), cy: ar.ct.height() },
12736 guideDims: { cx: ar.gd.width(), cy: ar.gd.height() },
12737 guideOffset: ar.gd.offset()
12738 };
12739
12740 offset = this.element.offset();
12741
12742 ar.gd.css( { left: 0, top: 0, right: 0, bottom: 0 } );
12743 gdOffset = ar.gd.offset();
12744 state.contentBox = {
12745 x: gdOffset.left - offset.left,
12746 y: gdOffset.top - offset.top,
12747 cx: ar.gd.width(),
12748 cy: ar.gd.height()
12749 };
12750 ar.gd.removeAttr( "style" );
12751
12752 // The arrow box moves between guideOffset and guideOffset + guideDims - arFull
12753 state.guideOffset = { left: state.guideOffset.left - offset.left, top: state.guideOffset.top - offset.top };
12754 state.arHalf = { cx: state.arFull.cx / 2, cy: state.arFull.cy / 2 };
12755 state.menuHalf = { cx: state.clampInfo.menuSize.cx / 2, cy: state.clampInfo.menuSize.cy / 2 };
12756
12757 return state;
12758 },
12759
12760 _placementCoords: function( desired ) {
12761 var state, best, params, elOffset, bgRef,
12762 optionValue = this.options.arrow,
12763 ar = this._ui.arrow;
12764
12765 if ( !ar ) {
12766 return this._super( desired );
12767 }
12768
12769 ar.arEls.show();
12770
12771 bgRef = {};
12772 state = this._getPlacementState( true );
12773 params = {
12774 "l": { fst: "x", snd: "y", prop: "top", dimKey: "cy", oDimKey: "cx", offsetFactor: 1, tipOffset: -state.arHalf.cx, arrowOffsetFactor: 0 },
12775 "r": { fst: "x", snd: "y", prop: "top", dimKey: "cy", oDimKey: "cx", offsetFactor: -1, tipOffset: state.arHalf.cx + state.contentBox.cx, arrowOffsetFactor: 1 },
12776 "b": { fst: "y", snd: "x", prop: "left", dimKey: "cx", oDimKey: "cy", offsetFactor: -1, tipOffset: state.arHalf.cy + state.contentBox.cy, arrowOffsetFactor: 1 },
12777 "t": { fst: "y", snd: "x", prop: "left", dimKey: "cx", oDimKey: "cy", offsetFactor: 1, tipOffset: -state.arHalf.cy, arrowOffsetFactor: 0 }
12778 };
12779
12780 // Try each side specified in the options to see on which one the arrow
12781 // should be placed such that the distance between the tip of the arrow and
12782 // the desired coordinates is the shortest.
12783 $.each( ( optionValue === true ? "l,t,r,b" : optionValue ).split( "," ),
12784 $.proxy( function( key, value ) {
12785 best = this._tryAnArrow( params[ value ], value, desired, state, best );
12786 }, this ) );
12787
12788 // Could not place the arrow along any of the edges - behave as if showing
12789 // the arrow was turned off.
12790 if ( !best ) {
12791 ar.arEls.hide();
12792 return this._super( desired );
12793 }
12794
12795 // Move the arrow into place
12796 ar.ct
12797 .removeClass( "ui-popup-arrow-l ui-popup-arrow-t ui-popup-arrow-r ui-popup-arrow-b" )
12798 .addClass( "ui-popup-arrow-" + best.dir )
12799 .removeAttr( "style" ).css( best.posProp, best.posVal )
12800 .show();
12801
12802 // Do not move/size the background div on IE, because we use the arrow div for background as well.
12803 if ( !ieHack ) {
12804 elOffset = this.element.offset();
12805 bgRef[ params[ best.dir ].fst ] = ar.ct.offset();
12806 bgRef[ params[ best.dir ].snd ] = {
12807 left: elOffset.left + state.contentBox.x,
12808 top: elOffset.top + state.contentBox.y
12809 };
12810 }
12811
12812 return best.result;
12813 },
12814
12815 _setOptions: function( opts ) {
12816 var newTheme,
12817 oldTheme = this.options.theme,
12818 ar = this._ui.arrow,
12819 ret = this._super( opts );
12820
12821 if ( opts.arrow !== undefined ) {
12822 if ( !ar && opts.arrow ) {
12823 this._ui.arrow = this._addArrow();
12824
12825 // Important to return here so we don't set the same options all over
12826 // again below.
12827 return;
12828 } else if ( ar && !opts.arrow ) {
12829 ar.arEls.remove();
12830 this._ui.arrow = null;
12831 }
12832 }
12833
12834 // Reassign with potentially new arrow
12835 ar = this._ui.arrow;
12836
12837 if ( ar ) {
12838 if ( opts.theme !== undefined ) {
12839 oldTheme = this._themeClassFromOption( "ui-body-", oldTheme );
12840 newTheme = this._themeClassFromOption( "ui-body-", opts.theme );
12841 ar.ar.removeClass( oldTheme ).addClass( newTheme );
12842 }
12843
12844 if ( opts.shadow !== undefined ) {
12845 ar.ar.toggleClass( "ui-overlay-shadow", opts.shadow );
12846 }
12847 }
12848
12849 return ret;
12850 },
12851
12852 _destroy: function() {
12853 var ar = this._ui.arrow;
12854
12855 if ( ar ) {
12856 ar.arEls.remove();
12857 }
12858
12859 return this._super();
12860 }
12861});
12862
12863})( jQuery );
12864
12865
12866(function( $, undefined ) {
12867
12868$.widget( "mobile.panel", {
12869 options: {
12870 classes: {
12871 panel: "ui-panel",
12872 panelOpen: "ui-panel-open",
12873 panelClosed: "ui-panel-closed",
12874 panelFixed: "ui-panel-fixed",
12875 panelInner: "ui-panel-inner",
12876 modal: "ui-panel-dismiss",
12877 modalOpen: "ui-panel-dismiss-open",
12878 pageContainer: "ui-panel-page-container",
12879 pageWrapper: "ui-panel-wrapper",
12880 pageFixedToolbar: "ui-panel-fixed-toolbar",
12881 pageContentPrefix: "ui-panel-page-content", /* Used for wrapper and fixed toolbars position, display and open classes. */
12882 animate: "ui-panel-animate"
12883 },
12884 animate: true,
12885 theme: null,
12886 position: "left",
12887 dismissible: true,
12888 display: "reveal", //accepts reveal, push, overlay
12889 swipeClose: true,
12890 positionFixed: false
12891 },
12892
12893 _closeLink: null,
12894 _parentPage: null,
12895 _page: null,
12896 _modal: null,
12897 _panelInner: null,
12898 _wrapper: null,
12899 _fixedToolbars: null,
12900
12901 _create: function() {
12902 var el = this.element,
12903 parentPage = el.closest( ".ui-page, :jqmData(role='page')" );
12904
12905 // expose some private props to other methods
12906 $.extend( this, {
12907 _closeLink: el.find( ":jqmData(rel='close')" ),
12908 _parentPage: ( parentPage.length > 0 ) ? parentPage : false,
12909 _openedPage: null,
12910 _page: this._getPage,
12911 _panelInner: this._getPanelInner(),
12912 _fixedToolbars: this._getFixedToolbars
12913 });
12914 if ( this.options.display !== "overlay" ){
12915 this._getWrapper();
12916 }
12917 this._addPanelClasses();
12918
12919 // if animating, add the class to do so
12920 if ( $.support.cssTransform3d && !!this.options.animate ) {
12921 this.element.addClass( this.options.classes.animate );
12922 }
12923
12924 this._bindUpdateLayout();
12925 this._bindCloseEvents();
12926 this._bindLinkListeners();
12927 this._bindPageEvents();
12928
12929 if ( !!this.options.dismissible ) {
12930 this._createModal();
12931 }
12932
12933 this._bindSwipeEvents();
12934 },
12935
12936 _getPanelInner: function() {
12937 var panelInner = this.element.find( "." + this.options.classes.panelInner );
12938
12939 if ( panelInner.length === 0 ) {
12940 panelInner = this.element.children().wrapAll( "<div class='" + this.options.classes.panelInner + "' />" ).parent();
12941 }
12942
12943 return panelInner;
12944 },
12945
12946 _createModal: function() {
12947 var self = this,
12948 target = self._parentPage ? self._parentPage.parent() : self.element.parent();
12949
12950 self._modal = $( "<div class='" + self.options.classes.modal + "'></div>" )
12951 .on( "mousedown", function() {
12952 self.close();
12953 })
12954 .appendTo( target );
12955 },
12956
12957 _getPage: function() {
12958 var page = this._openedPage || this._parentPage || $( "." + $.mobile.activePageClass );
12959
12960 return page;
12961 },
12962
12963 _getWrapper: function() {
12964 var wrapper = this._page().find( "." + this.options.classes.pageWrapper );
12965 if ( wrapper.length === 0 ) {
12966 wrapper = this._page().children( ".ui-header:not(.ui-header-fixed), .ui-content:not(.ui-popup), .ui-footer:not(.ui-footer-fixed)" )
12967 .wrapAll( "<div class='" + this.options.classes.pageWrapper + "'></div>" )
12968 .parent();
12969 }
12970
12971 this._wrapper = wrapper;
12972 },
12973
12974 _getFixedToolbars: function() {
12975 var extFixedToolbars = $( "body" ).children( ".ui-header-fixed, .ui-footer-fixed" ),
12976 intFixedToolbars = this._page().find( ".ui-header-fixed, .ui-footer-fixed" ),
12977 fixedToolbars = extFixedToolbars.add( intFixedToolbars ).addClass( this.options.classes.pageFixedToolbar );
12978
12979 return fixedToolbars;
12980 },
12981
12982 _getPosDisplayClasses: function( prefix ) {
12983 return prefix + "-position-" + this.options.position + " " + prefix + "-display-" + this.options.display;
12984 },
12985
12986 _getPanelClasses: function() {
12987 var panelClasses = this.options.classes.panel +
12988 " " + this._getPosDisplayClasses( this.options.classes.panel ) +
12989 " " + this.options.classes.panelClosed +
12990 " " + "ui-body-" + ( this.options.theme ? this.options.theme : "inherit" );
12991
12992 if ( !!this.options.positionFixed ) {
12993 panelClasses += " " + this.options.classes.panelFixed;
12994 }
12995
12996 return panelClasses;
12997 },
12998
12999 _addPanelClasses: function() {
13000 this.element.addClass( this._getPanelClasses() );
13001 },
13002
13003 _handleCloseClick: function( event ) {
13004 if ( !event.isDefaultPrevented() ) {
13005 this.close();
13006 }
13007 },
13008
13009 _bindCloseEvents: function() {
13010 this._on( this._closeLink, {
13011 "click": "_handleCloseClick"
13012 });
13013
13014 this._on({
13015 "click a:jqmData(ajax='false')": "_handleCloseClick"
13016 });
13017 },
13018
13019 _positionPanel: function( scrollToTop ) {
13020 var heightWithMargins, heightWithoutMargins,
13021 self = this,
13022 panelInnerHeight = self._panelInner.outerHeight(),
13023 expand = panelInnerHeight > $.mobile.getScreenHeight();
13024
13025 if ( expand || !self.options.positionFixed ) {
13026 if ( expand ) {
13027 self._unfixPanel();
13028 $.mobile.resetActivePageHeight( panelInnerHeight );
13029 } else if ( !this._parentPage ) {
13030 heightWithMargins = this.element.outerHeight( true );
13031 if ( heightWithMargins < this.document.height() ) {
13032 heightWithoutMargins = this.element.outerHeight();
13033
13034 // Set the panel's total height (including margins) to the document height
13035 this.element.outerHeight( this.document.height() -
13036 ( heightWithMargins - heightWithoutMargins ) );
13037 }
13038 }
13039 if ( scrollToTop === true ) {
13040 this.window[ 0 ].scrollTo( 0, $.mobile.defaultHomeScroll );
13041 }
13042 } else {
13043 self._fixPanel();
13044 }
13045 },
13046
13047 _bindFixListener: function() {
13048 this._on( this.window, { "throttledresize": "_positionPanel" });
13049 },
13050
13051 _unbindFixListener: function() {
13052 this._off( this.window, "throttledresize" );
13053 },
13054
13055 _unfixPanel: function() {
13056 if ( !!this.options.positionFixed && $.support.fixedPosition ) {
13057 this.element.removeClass( this.options.classes.panelFixed );
13058 }
13059 },
13060
13061 _fixPanel: function() {
13062 if ( !!this.options.positionFixed && $.support.fixedPosition ) {
13063 this.element.addClass( this.options.classes.panelFixed );
13064 }
13065 },
13066
13067 _bindUpdateLayout: function() {
13068 var self = this;
13069
13070 self.element.on( "updatelayout", function(/* e */) {
13071 if ( self._open ) {
13072 self._positionPanel();
13073 }
13074 });
13075 },
13076
13077 _bindLinkListeners: function() {
13078 this._on( "body", {
13079 "click a": "_handleClick"
13080 });
13081
13082 },
13083
13084 _handleClick: function( e ) {
13085 var link,
13086 panelId = this.element.attr( "id" );
13087
13088 if ( e.currentTarget.href.split( "#" )[ 1 ] === panelId && panelId !== undefined ) {
13089
13090 e.preventDefault();
13091 link = $( e.target );
13092 if ( link.hasClass( "ui-btn" ) ) {
13093 link.addClass( $.mobile.activeBtnClass );
13094 this.element.one( "panelopen panelclose", function() {
13095 link.removeClass( $.mobile.activeBtnClass );
13096 });
13097 }
13098 this.toggle();
13099 }
13100 },
13101
13102 _handleSwipe: function( event ) {
13103 if ( !event.isDefaultPrevented() ) {
13104 this.close();
13105 }
13106 },
13107
13108 _bindSwipeEvents: function() {
13109 var handler = {};
13110
13111 // Close the panel on swipe if the swipe event's default is not prevented
13112 if ( this.options.swipeClose ) {
13113 handler[ "swipe" + this.options.position ] = "_handleSwipe";
13114 this._on( ( this._modal ? this.element.add( this._modal ) : this.element ), handler );
13115 }
13116 },
13117
13118 _bindPageEvents: function() {
13119 var self = this;
13120
13121 this.document
13122 // Close the panel if another panel on the page opens
13123 .on( "panelbeforeopen", function( e ) {
13124 if ( self._open && e.target !== self.element[ 0 ] ) {
13125 self.close();
13126 }
13127 })
13128 // On escape, close? might need to have a target check too...
13129 .on( "keyup.panel", function( e ) {
13130 if ( e.keyCode === 27 && self._open ) {
13131 self.close();
13132 }
13133 });
13134 if ( !this._parentPage && this.options.display !== "overlay" ) {
13135 this._on( this.document, {
13136 "pageshow": function() {
13137 this._openedPage = null;
13138 this._getWrapper();
13139 }
13140 });
13141 }
13142 // Clean up open panels after page hide
13143 if ( self._parentPage ) {
13144 this.document.on( "pagehide", ":jqmData(role='page')", function() {
13145 if ( self._open ) {
13146 self.close( true );
13147 }
13148 });
13149 } else {
13150 this.document.on( "pagebeforehide", function() {
13151 if ( self._open ) {
13152 self.close( true );
13153 }
13154 });
13155 }
13156 },
13157
13158 // state storage of open or closed
13159 _open: false,
13160 _pageContentOpenClasses: null,
13161 _modalOpenClasses: null,
13162
13163 open: function( immediate ) {
13164 if ( !this._open ) {
13165 var self = this,
13166 o = self.options,
13167
13168 _openPanel = function() {
13169 self._off( self.document , "panelclose" );
13170 self._page().jqmData( "panel", "open" );
13171
13172 if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
13173 self._wrapper.addClass( o.classes.animate );
13174 self._fixedToolbars().addClass( o.classes.animate );
13175 }
13176
13177 if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
13178 ( self._wrapper || self.element )
13179 .animationComplete( complete, "transition" );
13180 } else {
13181 setTimeout( complete, 0 );
13182 }
13183
13184 if ( o.theme && o.display !== "overlay" ) {
13185 self._page().parent()
13186 .addClass( o.classes.pageContainer + "-themed " + o.classes.pageContainer + "-" + o.theme );
13187 }
13188
13189 self.element
13190 .removeClass( o.classes.panelClosed )
13191 .addClass( o.classes.panelOpen );
13192
13193 self._positionPanel( true );
13194
13195 self._pageContentOpenClasses = self._getPosDisplayClasses( o.classes.pageContentPrefix );
13196
13197 if ( o.display !== "overlay" ) {
13198 self._page().parent().addClass( o.classes.pageContainer );
13199 self._wrapper.addClass( self._pageContentOpenClasses );
13200 self._fixedToolbars().addClass( self._pageContentOpenClasses );
13201 }
13202
13203 self._modalOpenClasses = self._getPosDisplayClasses( o.classes.modal ) + " " + o.classes.modalOpen;
13204 if ( self._modal ) {
13205 self._modal
13206 .addClass( self._modalOpenClasses )
13207 .height( Math.max( self._modal.height(), self.document.height() ) );
13208 }
13209 },
13210 complete = function() {
13211
13212 // Bail if the panel was closed before the opening animation has completed
13213 if ( !self._open ) {
13214 return;
13215 }
13216
13217 if ( o.display !== "overlay" ) {
13218 self._wrapper.addClass( o.classes.pageContentPrefix + "-open" );
13219 self._fixedToolbars().addClass( o.classes.pageContentPrefix + "-open" );
13220 }
13221
13222 self._bindFixListener();
13223
13224 self._trigger( "open" );
13225
13226 self._openedPage = self._page();
13227 };
13228
13229 self._trigger( "beforeopen" );
13230
13231 if ( self._page().jqmData( "panel" ) === "open" ) {
13232 self._on( self.document, {
13233 "panelclose": _openPanel
13234 });
13235 } else {
13236 _openPanel();
13237 }
13238
13239 self._open = true;
13240 }
13241 },
13242
13243 close: function( immediate ) {
13244 if ( this._open ) {
13245 var self = this,
13246
13247 // Record what the page is the moment the process of closing begins, because it
13248 // may change by the time the process completes
13249 currentPage = self._page(),
13250 o = this.options,
13251
13252 _closePanel = function() {
13253
13254 self.element.removeClass( o.classes.panelOpen );
13255
13256 if ( o.display !== "overlay" ) {
13257 self._wrapper.removeClass( self._pageContentOpenClasses );
13258 self._fixedToolbars().removeClass( self._pageContentOpenClasses );
13259 }
13260
13261 if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
13262 ( self._wrapper || self.element )
13263 .animationComplete( complete, "transition" );
13264 } else {
13265 setTimeout( complete, 0 );
13266 }
13267
13268 if ( self._modal ) {
13269 self._modal
13270 .removeClass( self._modalOpenClasses )
13271 .height( "" );
13272 }
13273 },
13274 complete = function() {
13275 if ( o.theme && o.display !== "overlay" ) {
13276 currentPage.parent().removeClass( o.classes.pageContainer + "-themed " +
13277 o.classes.pageContainer + "-" + o.theme );
13278 }
13279
13280 self.element.addClass( o.classes.panelClosed );
13281
13282 //scroll to the top
13283 self._positionPanel( true );
13284
13285 if ( o.display !== "overlay" ) {
13286 currentPage.parent().removeClass( o.classes.pageContainer );
13287 self._wrapper.removeClass( o.classes.pageContentPrefix + "-open" );
13288 self._fixedToolbars().removeClass( o.classes.pageContentPrefix + "-open" );
13289 }
13290
13291 if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
13292 self._wrapper.removeClass( o.classes.animate );
13293 self._fixedToolbars().removeClass( o.classes.animate );
13294 }
13295
13296 self._fixPanel();
13297 self._unbindFixListener();
13298 $.mobile.resetActivePageHeight();
13299
13300 currentPage.jqmRemoveData( "panel" );
13301
13302 self._trigger( "close" );
13303
13304 self._openedPage = null;
13305 };
13306
13307 self._trigger( "beforeclose" );
13308
13309 _closePanel();
13310
13311 self._open = false;
13312 }
13313 },
13314
13315 toggle: function() {
13316 this[ this._open ? "close" : "open" ]();
13317 },
13318
13319 _destroy: function() {
13320 var otherPanels,
13321 o = this.options,
13322 multiplePanels = ( $( "body > :mobile-panel" ).length + $.mobile.activePage.find( ":mobile-panel" ).length ) > 1;
13323
13324 if ( o.display !== "overlay" ) {
13325
13326 // remove the wrapper if not in use by another panel
13327 otherPanels = $( "body > :mobile-panel" ).add( $.mobile.activePage.find( ":mobile-panel" ) );
13328 if ( otherPanels.not( ".ui-panel-display-overlay" ).not( this.element ).length === 0 ) {
13329 this._wrapper.children().unwrap();
13330 }
13331
13332 if ( this._open ) {
13333
13334 this._fixedToolbars().removeClass( o.classes.pageContentPrefix + "-open" );
13335
13336 if ( $.support.cssTransform3d && !!o.animate ) {
13337 this._fixedToolbars().removeClass( o.classes.animate );
13338 }
13339
13340 this._page().parent().removeClass( o.classes.pageContainer );
13341
13342 if ( o.theme ) {
13343 this._page().parent().removeClass( o.classes.pageContainer + "-themed " + o.classes.pageContainer + "-" + o.theme );
13344 }
13345 }
13346 }
13347
13348 if ( !multiplePanels ) {
13349
13350 this.document.off( "panelopen panelclose" );
13351
13352 }
13353
13354 if ( this._open ) {
13355 this._page().jqmRemoveData( "panel" );
13356 }
13357
13358 this._panelInner.children().unwrap();
13359
13360 this.element
13361 .removeClass( [ this._getPanelClasses(), o.classes.panelOpen, o.classes.animate ].join( " " ) )
13362 .off( "panelbeforeopen" )
13363 .off( "panelhide" )
13364 .off( "keyup.panel" )
13365 .off( "updatelayout" );
13366
13367 if ( this._modal ) {
13368 this._modal.remove();
13369 }
13370 }
13371});
13372
13373})( jQuery );
13374
13375(function( $, undefined ) {
13376
13377$.widget( "mobile.table", {
13378 options: {
13379 classes: {
13380 table: "ui-table"
13381 },
13382 enhanced: false
13383 },
13384
13385 _create: function() {
13386 if ( !this.options.enhanced ) {
13387 this.element.addClass( this.options.classes.table );
13388 }
13389
13390 // extend here, assign on refresh > _setHeaders
13391 $.extend( this, {
13392
13393 // Expose headers and allHeaders properties on the widget
13394 // headers references the THs within the first TR in the table
13395 headers: undefined,
13396
13397 // allHeaders references headers, plus all THs in the thead, which may
13398 // include several rows, or not
13399 allHeaders: undefined
13400 });
13401
13402 this._refresh( true );
13403 },
13404
13405 _setHeaders: function() {
13406 var trs = this.element.find( "thead tr" );
13407
13408 this.headers = this.element.find( "tr:eq(0)" ).children();
13409 this.allHeaders = this.headers.add( trs.children() );
13410 },
13411
13412 refresh: function() {
13413 this._refresh();
13414 },
13415
13416 rebuild: $.noop,
13417
13418 _refresh: function( /* create */ ) {
13419 var table = this.element,
13420 trs = table.find( "thead tr" );
13421
13422 // updating headers on refresh (fixes #5880)
13423 this._setHeaders();
13424
13425 // Iterate over the trs
13426 trs.each( function() {
13427 var columnCount = 0;
13428
13429 // Iterate over the children of the tr
13430 $( this ).children().each( function() {
13431 var span = parseInt( this.getAttribute( "colspan" ), 10 ),
13432 selector = ":nth-child(" + ( columnCount + 1 ) + ")",
13433 j;
13434
13435 this.setAttribute( "data-" + $.mobile.ns + "colstart", columnCount + 1 );
13436
13437 if ( span ) {
13438 for( j = 0; j < span - 1; j++ ) {
13439 columnCount++;
13440 selector += ", :nth-child(" + ( columnCount + 1 ) + ")";
13441 }
13442 }
13443
13444 // Store "cells" data on header as a reference to all cells in the
13445 // same column as this TH
13446 $( this ).jqmData( "cells", table.find( "tr" ).not( trs.eq( 0 ) ).not( this ).children( selector ) );
13447
13448 columnCount++;
13449 });
13450 });
13451 }
13452});
13453
13454})( jQuery );
13455
13456
13457(function( $, undefined ) {
13458
13459$.widget( "mobile.table", $.mobile.table, {
13460 options: {
13461 mode: "columntoggle",
13462 columnBtnTheme: null,
13463 columnPopupTheme: null,
13464 columnBtnText: "Columns...",
13465 classes: $.extend( $.mobile.table.prototype.options.classes, {
13466 popup: "ui-table-columntoggle-popup",
13467 columnBtn: "ui-table-columntoggle-btn",
13468 priorityPrefix: "ui-table-priority-",
13469 columnToggleTable: "ui-table-columntoggle"
13470 })
13471 },
13472
13473 _create: function() {
13474 this._super();
13475
13476 if ( this.options.mode !== "columntoggle" ) {
13477 return;
13478 }
13479
13480 $.extend( this, {
13481 _menu: null
13482 });
13483
13484 if ( this.options.enhanced ) {
13485 this._menu = $( this.document[ 0 ].getElementById( this._id() + "-popup" ) ).children().first();
13486 this._addToggles( this._menu, true );
13487 } else {
13488 this._menu = this._enhanceColToggle();
13489 this.element.addClass( this.options.classes.columnToggleTable );
13490 }
13491
13492 this._setupEvents();
13493
13494 this._setToggleState();
13495 },
13496
13497 _id: function() {
13498 return ( this.element.attr( "id" ) || ( this.widgetName + this.uuid ) );
13499 },
13500
13501 _setupEvents: function() {
13502 //NOTE: inputs are bound in bindToggles,
13503 // so it can be called on refresh, too
13504
13505 // update column toggles on resize
13506 this._on( this.window, {
13507 throttledresize: "_setToggleState"
13508 });
13509 this._on( this._menu, {
13510 "change input": "_menuInputChange"
13511 });
13512 },
13513
13514 _addToggles: function( menu, keep ) {
13515 var inputs,
13516 checkboxIndex = 0,
13517 opts = this.options,
13518 container = menu.controlgroup( "container" );
13519
13520 // allow update of menu on refresh (fixes #5880)
13521 if ( keep ) {
13522 inputs = menu.find( "input" );
13523 } else {
13524 container.empty();
13525 }
13526
13527 // create the hide/show toggles
13528 this.headers.not( "td" ).each( function() {
13529 var input, cells,
13530 header = $( this ),
13531 priority = $.mobile.getAttribute( this, "priority" );
13532
13533 if ( priority ) {
13534 cells = header.add( header.jqmData( "cells" ) );
13535 cells.addClass( opts.classes.priorityPrefix + priority );
13536
13537 // Make sure the (new?) checkbox is associated with its header via .jqmData() and
13538 // that, vice versa, the header is also associated with the checkbox
13539 input = ( keep ? inputs.eq( checkboxIndex++ ) :
13540 $("<label><input type='checkbox' checked />" +
13541 ( header.children( "abbr" ).first().attr( "title" ) ||
13542 header.text() ) +
13543 "</label>" )
13544 .appendTo( container )
13545 .children( 0 )
13546 .checkboxradio( {
13547 theme: opts.columnPopupTheme
13548 }) )
13549
13550 // Associate the header with the checkbox
13551 .jqmData( "header", header )
13552 .jqmData( "cells", cells );
13553
13554 // Associate the checkbox with the header
13555 header.jqmData( "input", input );
13556 }
13557 });
13558
13559 // set bindings here
13560 if ( !keep ) {
13561 menu.controlgroup( "refresh" );
13562 }
13563 },
13564
13565 _menuInputChange: function( evt ) {
13566 var input = $( evt.target ),
13567 checked = input[ 0 ].checked;
13568
13569 input.jqmData( "cells" )
13570 .toggleClass( "ui-table-cell-hidden", !checked )
13571 .toggleClass( "ui-table-cell-visible", checked );
13572 },
13573
13574 _unlockCells: function( cells ) {
13575 // allow hide/show via CSS only = remove all toggle-locks
13576 cells.removeClass( "ui-table-cell-hidden ui-table-cell-visible");
13577 },
13578
13579 _enhanceColToggle: function() {
13580 var id , menuButton, popup, menu,
13581 table = this.element,
13582 opts = this.options,
13583 ns = $.mobile.ns,
13584 fragment = this.document[ 0 ].createDocumentFragment();
13585
13586 id = this._id() + "-popup";
13587 menuButton = $( "<a href='#" + id + "' " +
13588 "class='" + opts.classes.columnBtn + " ui-btn " +
13589 "ui-btn-" + ( opts.columnBtnTheme || "a" ) +
13590 " ui-corner-all ui-shadow ui-mini' " +
13591 "data-" + ns + "rel='popup'>" + opts.columnBtnText + "</a>" );
13592 popup = $( "<div class='" + opts.classes.popup + "' id='" + id + "'></div>" );
13593 menu = $( "<fieldset></fieldset>" ).controlgroup();
13594
13595 // set extension here, send "false" to trigger build/rebuild
13596 this._addToggles( menu, false );
13597
13598 menu.appendTo( popup );
13599
13600 fragment.appendChild( popup[ 0 ] );
13601 fragment.appendChild( menuButton[ 0 ] );
13602 table.before( fragment );
13603
13604 popup.popup();
13605
13606 return menu;
13607 },
13608
13609 rebuild: function() {
13610 this._super();
13611
13612 if ( this.options.mode === "columntoggle" ) {
13613 // NOTE: rebuild passes "false", while refresh passes "undefined"
13614 // both refresh the table, but inside addToggles, !false will be true,
13615 // so a rebuild call can be indentified
13616 this._refresh( false );
13617 }
13618 },
13619
13620 _refresh: function( create ) {
13621 var headers, hiddenColumns, index;
13622
13623 // Calling _super() here updates this.headers
13624 this._super( create );
13625
13626 if ( !create && this.options.mode === "columntoggle" ) {
13627 headers = this.headers;
13628 hiddenColumns = [];
13629
13630 // Find the index of the column header associated with each old checkbox among the
13631 // post-refresh headers and, if the header is still there, make sure the corresponding
13632 // column will be hidden if the pre-refresh checkbox indicates that the column is
13633 // hidden by recording its index in the array of hidden columns.
13634 this._menu.find( "input" ).each( function() {
13635 var input = $( this ),
13636 header = input.jqmData( "header" ),
13637 index = headers.index( header[ 0 ] );
13638
13639 if ( index > -1 && !input.prop( "checked" ) ) {
13640
13641 // The column header associated with /this/ checkbox is still present in the
13642 // post-refresh table and the checkbox is not checked, so the column associated
13643 // with this column header is currently hidden. Let's record that.
13644 hiddenColumns.push( index );
13645 }
13646 });
13647
13648 // columns not being replaced must be cleared from input toggle-locks
13649 this._unlockCells( this.element.find( ".ui-table-cell-hidden, " +
13650 ".ui-table-cell-visible" ) );
13651
13652 // update columntoggles and cells
13653 this._addToggles( this._menu, create );
13654
13655 // At this point all columns are visible, so uncheck the checkboxes that correspond to
13656 // those columns we've found to be hidden
13657 for ( index = hiddenColumns.length - 1 ; index > -1 ; index-- ) {
13658 headers.eq( hiddenColumns[ index ] ).jqmData( "input" )
13659 .prop( "checked", false )
13660 .checkboxradio( "refresh" )
13661 .trigger( "change" );
13662 }
13663 }
13664 },
13665
13666 _setToggleState: function() {
13667 this._menu.find( "input" ).each( function() {
13668 var checkbox = $( this );
13669
13670 this.checked = checkbox.jqmData( "cells" ).eq( 0 ).css( "display" ) === "table-cell";
13671 checkbox.checkboxradio( "refresh" );
13672 });
13673 },
13674
13675 _destroy: function() {
13676 this._super();
13677 }
13678});
13679
13680})( jQuery );
13681
13682(function( $, undefined ) {
13683
13684$.widget( "mobile.table", $.mobile.table, {
13685 options: {
13686 mode: "reflow",
13687 classes: $.extend( $.mobile.table.prototype.options.classes, {
13688 reflowTable: "ui-table-reflow",
13689 cellLabels: "ui-table-cell-label"
13690 })
13691 },
13692
13693 _create: function() {
13694 this._super();
13695
13696 // If it's not reflow mode, return here.
13697 if ( this.options.mode !== "reflow" ) {
13698 return;
13699 }
13700
13701 if ( !this.options.enhanced ) {
13702 this.element.addClass( this.options.classes.reflowTable );
13703
13704 this._updateReflow();
13705 }
13706 },
13707
13708 rebuild: function() {
13709 this._super();
13710
13711 if ( this.options.mode === "reflow" ) {
13712 this._refresh( false );
13713 }
13714 },
13715
13716 _refresh: function( create ) {
13717 this._super( create );
13718 if ( !create && this.options.mode === "reflow" ) {
13719 this._updateReflow( );
13720 }
13721 },
13722
13723 _updateReflow: function() {
13724 var table = this,
13725 opts = this.options;
13726
13727 // get headers in reverse order so that top-level headers are appended last
13728 $( table.allHeaders.get().reverse() ).each( function() {
13729 var cells = $( this ).jqmData( "cells" ),
13730 colstart = $.mobile.getAttribute( this, "colstart" ),
13731 hierarchyClass = cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top",
13732 contents = $( this ).clone().contents(),
13733 iteration, filter;
13734
13735 if ( hierarchyClass ) {
13736 iteration = parseInt( this.getAttribute( "colspan" ), 10 );
13737 filter = "";
13738
13739 if ( iteration ) {
13740 filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")";
13741 }
13742
13743 table._addLabels( cells.filter( filter ),
13744 opts.classes.cellLabels + hierarchyClass, contents );
13745 } else {
13746 table._addLabels( cells, opts.classes.cellLabels, contents );
13747 }
13748 });
13749 },
13750
13751 _addLabels: function( cells, label, contents ) {
13752 if ( contents.length === 1 && contents[ 0 ].nodeName.toLowerCase() === "abbr" ) {
13753 contents = contents.eq( 0 ).attr( "title" );
13754 }
13755 // .not fixes #6006
13756 cells
13757 .not( ":has(b." + label + ")" )
13758 .prepend( $( "<b class='" + label + "'></b>" ).append( contents ) );
13759 }
13760});
13761
13762})( jQuery );
13763
13764(function( $, undefined ) {
13765
13766// TODO rename filterCallback/deprecate and default to the item itself as the first argument
13767var defaultFilterCallback = function( index, searchValue ) {
13768 return ( ( "" + ( $.mobile.getAttribute( this, "filtertext" ) || $( this ).text() ) )
13769 .toLowerCase().indexOf( searchValue ) === -1 );
13770};
13771
13772$.widget( "mobile.filterable", {
13773
13774 initSelector: ":jqmData(filter='true')",
13775
13776 options: {
13777 filterReveal: false,
13778 filterCallback: defaultFilterCallback,
13779 enhanced: false,
13780 input: null,
13781 children: "> li, > option, > optgroup option, > tbody tr, > .ui-controlgroup-controls > .ui-btn, > .ui-controlgroup-controls > .ui-checkbox, > .ui-controlgroup-controls > .ui-radio"
13782 },
13783
13784 _create: function() {
13785 var opts = this.options;
13786
13787 $.extend( this, {
13788 _search: null,
13789 _timer: 0
13790 });
13791
13792 this._setInput( opts.input );
13793 if ( !opts.enhanced ) {
13794 this._filterItems( ( ( this._search && this._search.val() ) || "" ).toLowerCase() );
13795 }
13796 },
13797
13798 _onKeyUp: function() {
13799 var val, lastval,
13800 search = this._search;
13801
13802 if ( search ) {
13803 val = search.val().toLowerCase(),
13804 lastval = $.mobile.getAttribute( search[ 0 ], "lastval" ) + "";
13805
13806 if ( lastval && lastval === val ) {
13807 // Execute the handler only once per value change
13808 return;
13809 }
13810
13811 if ( this._timer ) {
13812 window.clearTimeout( this._timer );
13813 this._timer = 0;
13814 }
13815
13816 this._timer = this._delay( function() {
13817 if ( this._trigger( "beforefilter", null, { input: search } ) === false ) {
13818 return false;
13819 }
13820
13821 // Change val as lastval for next execution
13822 search[ 0 ].setAttribute( "data-" + $.mobile.ns + "lastval", val );
13823
13824 this._filterItems( val );
13825 this._timer = 0;
13826 }, 250 );
13827 }
13828 },
13829
13830 _getFilterableItems: function() {
13831 var elem = this.element,
13832 children = this.options.children,
13833 items = !children ? { length: 0 }:
13834 $.isFunction( children ) ? children():
13835 children.nodeName ? $( children ):
13836 children.jquery ? children:
13837 this.element.find( children );
13838
13839 if ( items.length === 0 ) {
13840 items = elem.children();
13841 }
13842
13843 return items;
13844 },
13845
13846 _filterItems: function( val ) {
13847 var idx, callback, length, dst,
13848 show = [],
13849 hide = [],
13850 opts = this.options,
13851 filterItems = this._getFilterableItems();
13852
13853 if ( val != null ) {
13854 callback = opts.filterCallback || defaultFilterCallback;
13855 length = filterItems.length;
13856
13857 // Partition the items into those to be hidden and those to be shown
13858 for ( idx = 0 ; idx < length ; idx++ ) {
13859 dst = ( callback.call( filterItems[ idx ], idx, val ) ) ? hide : show;
13860 dst.push( filterItems[ idx ] );
13861 }
13862 }
13863
13864 // If nothing is hidden, then the decision whether to hide or show the items
13865 // is based on the "filterReveal" option.
13866 if ( hide.length === 0 ) {
13867 filterItems[ ( opts.filterReveal && val.length === 0 ) ?
13868 "addClass" : "removeClass" ]( "ui-screen-hidden" );
13869 } else {
13870 $( hide ).addClass( "ui-screen-hidden" );
13871 $( show ).removeClass( "ui-screen-hidden" );
13872 }
13873
13874 this._refreshChildWidget();
13875
13876 this._trigger( "filter", null, {
13877 items: filterItems
13878 });
13879 },
13880
13881 // The Default implementation of _refreshChildWidget attempts to call
13882 // refresh on collapsibleset, controlgroup, selectmenu, or listview
13883 _refreshChildWidget: function() {
13884 var widget, idx,
13885 recognizedWidgets = [ "collapsibleset", "selectmenu", "controlgroup", "listview" ];
13886
13887 for ( idx = recognizedWidgets.length - 1 ; idx > -1 ; idx-- ) {
13888 widget = recognizedWidgets[ idx ];
13889 if ( $.mobile[ widget ] ) {
13890 widget = this.element.data( "mobile-" + widget );
13891 if ( widget && $.isFunction( widget.refresh ) ) {
13892 widget.refresh();
13893 }
13894 }
13895 }
13896 },
13897
13898 // TODO: When the input is not internal, do not even store it in this._search
13899 _setInput: function ( selector ) {
13900 var search = this._search;
13901
13902 // Stop a pending filter operation
13903 if ( this._timer ) {
13904 window.clearTimeout( this._timer );
13905 this._timer = 0;
13906 }
13907
13908 if ( search ) {
13909 this._off( search, "keyup keydown keypress change input" );
13910 search = null;
13911 }
13912
13913 if ( selector ) {
13914 search = selector.jquery ? selector:
13915 selector.nodeName ? $( selector ):
13916 this.document.find( selector );
13917
13918 this._on( search, {
13919 keydown: "_onKeyDown",
13920 keypress: "_onKeyPress",
13921 keyup: "_onKeyUp",
13922 change: "_onKeyUp",
13923 input: "_onKeyUp"
13924 });
13925 }
13926
13927 this._search = search;
13928 },
13929
13930 // Prevent form submission
13931 _onKeyDown: function( event ) {
13932 this._preventKeyPress = false;
13933 if ( event.keyCode === $.ui.keyCode.ENTER ) {
13934 event.preventDefault();
13935 this._preventKeyPress = true;
13936 }
13937 },
13938
13939 _onKeyPress: function( event ) {
13940 if ( this._preventKeyPress ) {
13941 event.preventDefault();
13942 this._preventKeyPress = false;
13943 }
13944 },
13945
13946 _setOptions: function( options ) {
13947 var refilter = !( ( options.filterReveal === undefined ) &&
13948 ( options.filterCallback === undefined ) &&
13949 ( options.children === undefined ) );
13950
13951 this._super( options );
13952
13953 if ( options.input !== undefined ) {
13954 this._setInput( options.input );
13955 refilter = true;
13956 }
13957
13958 if ( refilter ) {
13959 this.refresh();
13960 }
13961 },
13962
13963 _destroy: function() {
13964 var opts = this.options,
13965 items = this._getFilterableItems();
13966
13967 if ( opts.enhanced ) {
13968 items.toggleClass( "ui-screen-hidden", opts.filterReveal );
13969 } else {
13970 items.removeClass( "ui-screen-hidden" );
13971 }
13972 },
13973
13974 refresh: function() {
13975 if ( this._timer ) {
13976 window.clearTimeout( this._timer );
13977 this._timer = 0;
13978 }
13979 this._filterItems( ( ( this._search && this._search.val() ) || "" ).toLowerCase() );
13980 }
13981});
13982
13983})( jQuery );
13984
13985(function( $, undefined ) {
13986
13987// Create a function that will replace the _setOptions function of a widget,
13988// and will pass the options on to the input of the filterable.
13989var replaceSetOptions = function( self, orig ) {
13990 return function( options ) {
13991 orig.call( this, options );
13992 self._syncTextInputOptions( options );
13993 };
13994 },
13995 rDividerListItem = /(^|\s)ui-li-divider(\s|$)/,
13996 origDefaultFilterCallback = $.mobile.filterable.prototype.options.filterCallback;
13997
13998// Override the default filter callback with one that does not hide list dividers
13999$.mobile.filterable.prototype.options.filterCallback = function( index, searchValue ) {
14000 return !this.className.match( rDividerListItem ) &&
14001 origDefaultFilterCallback.call( this, index, searchValue );
14002};
14003
14004$.widget( "mobile.filterable", $.mobile.filterable, {
14005 options: {
14006 filterPlaceholder: "Filter items...",
14007 filterTheme: null
14008 },
14009
14010 _create: function() {
14011 var idx, widgetName,
14012 elem = this.element,
14013 recognizedWidgets = [ "collapsibleset", "selectmenu", "controlgroup", "listview" ],
14014 createHandlers = {};
14015
14016 this._super();
14017
14018 $.extend( this, {
14019 _widget: null
14020 });
14021
14022 for ( idx = recognizedWidgets.length - 1 ; idx > -1 ; idx-- ) {
14023 widgetName = recognizedWidgets[ idx ];
14024 if ( $.mobile[ widgetName ] ) {
14025 if ( this._setWidget( elem.data( "mobile-" + widgetName ) ) ) {
14026 break;
14027 } else {
14028 createHandlers[ widgetName + "create" ] = "_handleCreate";
14029 }
14030 }
14031 }
14032
14033 if ( !this._widget ) {
14034 this._on( elem, createHandlers );
14035 }
14036 },
14037
14038 _handleCreate: function( evt ) {
14039 this._setWidget( this.element.data( "mobile-" + evt.type.substring( 0, evt.type.length - 6 ) ) );
14040 },
14041
14042 _trigger: function( type, event, data ) {
14043 if ( this._widget && this._widget.widgetFullName === "mobile-listview" &&
14044 type === "beforefilter" ) {
14045
14046 // Also trigger listviewbeforefilter if this widget is also a listview
14047 this._widget._trigger( "beforefilter", event, data );
14048 }
14049
14050 // Passing back the response enables calling preventDefault()
14051 return this._super( type, event, data );
14052 },
14053
14054 _setWidget: function( widget ) {
14055 if ( !this._widget && widget ) {
14056 this._widget = widget;
14057 this._widget._setOptions = replaceSetOptions( this, this._widget._setOptions );
14058 }
14059
14060 if ( !!this._widget ) {
14061 this._syncTextInputOptions( this._widget.options );
14062 if ( this._widget.widgetName === "listview" ) {
14063 this._widget.options.hideDividers = true;
14064 this._widget.element.listview( "refresh" );
14065 }
14066 }
14067
14068 return !!this._widget;
14069 },
14070
14071 _isSearchInternal: function() {
14072 return ( this._search && this._search.jqmData( "ui-filterable-" + this.uuid + "-internal" ) );
14073 },
14074
14075 _setInput: function( selector ) {
14076 var opts = this.options,
14077 updatePlaceholder = true,
14078 textinputOpts = {};
14079
14080 if ( !selector ) {
14081 if ( this._isSearchInternal() ) {
14082
14083 // Ignore the call to set a new input if the selector goes to falsy and
14084 // the current textinput is already of the internally generated variety.
14085 return;
14086 } else {
14087
14088 // Generating a new textinput widget. No need to set the placeholder
14089 // further down the function.
14090 updatePlaceholder = false;
14091 selector = $( "<input " +
14092 "data-" + $.mobile.ns + "type='search' " +
14093 "placeholder='" + opts.filterPlaceholder + "'></input>" )
14094 .jqmData( "ui-filterable-" + this.uuid + "-internal", true );
14095 $( "<form class='ui-filterable'></form>" )
14096 .append( selector )
14097 .submit( function( evt ) {
14098 evt.preventDefault();
14099 selector.blur();
14100 })
14101 .insertBefore( this.element );
14102 if ( $.mobile.textinput ) {
14103 if ( this.options.filterTheme != null ) {
14104 textinputOpts[ "theme" ] = opts.filterTheme;
14105 }
14106
14107 selector.textinput( textinputOpts );
14108 }
14109 }
14110 }
14111
14112 this._super( selector );
14113
14114 if ( this._isSearchInternal() && updatePlaceholder ) {
14115 this._search.attr( "placeholder", this.options.filterPlaceholder );
14116 }
14117 },
14118
14119 _setOptions: function( options ) {
14120 var ret = this._super( options );
14121
14122 // Need to set the filterPlaceholder after having established the search input
14123 if ( options.filterPlaceholder !== undefined ) {
14124 if ( this._isSearchInternal() ) {
14125 this._search.attr( "placeholder", options.filterPlaceholder );
14126 }
14127 }
14128
14129 if ( options.filterTheme !== undefined && this._search && $.mobile.textinput ) {
14130 this._search.textinput( "option", "theme", options.filterTheme );
14131 }
14132
14133 return ret;
14134 },
14135
14136 _destroy: function() {
14137 if ( this._isSearchInternal() ) {
14138 this._search.remove();
14139 }
14140 this._super();
14141 },
14142
14143 _syncTextInputOptions: function( options ) {
14144 var idx,
14145 textinputOptions = {};
14146
14147 // We only sync options if the filterable's textinput is of the internally
14148 // generated variety, rather than one specified by the user.
14149 if ( this._isSearchInternal() && $.mobile.textinput ) {
14150
14151 // Apply only the options understood by textinput
14152 for ( idx in $.mobile.textinput.prototype.options ) {
14153 if ( options[ idx ] !== undefined ) {
14154 if ( idx === "theme" && this.options.filterTheme != null ) {
14155 textinputOptions[ idx ] = this.options.filterTheme;
14156 } else {
14157 textinputOptions[ idx ] = options[ idx ];
14158 }
14159 }
14160 }
14161 this._search.textinput( "option", textinputOptions );
14162 }
14163 }
14164});
14165
14166// Instantiate a filterable on a listview that has the data-filter="true" attribute
14167// This is not necessary for static content, because the auto-enhance takes care of instantiating
14168// the filterable upon encountering data-filter="true". However, because of 1.3.x it is expected
14169// that a listview with data-filter="true" will be filterable even if you just instantiate a
14170// listview on it. The extension below ensures that this continues to happen in 1.4.x.
14171$.widget( "mobile.listview", $.mobile.listview, {
14172 options: {
14173 filter: false
14174 },
14175 _create: function() {
14176 if ( this.options.filter === true &&
14177 !this.element.data( "mobile-filterable" ) ) {
14178 this.element.filterable();
14179 }
14180 return this._super();
14181 }
14182});
14183
14184})( jQuery );
14185
14186/*!
14187 * jQuery UI Tabs c0ab71056b936627e8a7821f03c044aec6280a40
14188 * http://jqueryui.com
14189 *
14190 * Copyright 2013 jQuery Foundation and other contributors
14191 * Released under the MIT license.
14192 * http://jquery.org/license
14193 *
14194 * http://api.jqueryui.com/tabs/
14195 *
14196 * Depends:
14197 * jquery.ui.core.js
14198 * jquery.ui.widget.js
14199 */
14200(function( $, undefined ) {
14201
14202var tabId = 0,
14203 rhash = /#.*$/;
14204
14205function getNextTabId() {
14206 return ++tabId;
14207}
14208
14209function isLocal( anchor ) {
14210 // support: IE7
14211 // IE7 doesn't normalize the href property when set via script (#9317)
14212 anchor = anchor.cloneNode( false );
14213
14214 return anchor.hash.length > 1 &&
14215 decodeURIComponent( anchor.href.replace( rhash, "" ) ) ===
14216 decodeURIComponent( location.href.replace( rhash, "" ) );
14217}
14218
14219$.widget( "ui.tabs", {
14220 version: "c0ab71056b936627e8a7821f03c044aec6280a40",
14221 delay: 300,
14222 options: {
14223 active: null,
14224 collapsible: false,
14225 event: "click",
14226 heightStyle: "content",
14227 hide: null,
14228 show: null,
14229
14230 // callbacks
14231 activate: null,
14232 beforeActivate: null,
14233 beforeLoad: null,
14234 load: null
14235 },
14236
14237 _create: function() {
14238 var that = this,
14239 options = this.options;
14240
14241 this.running = false;
14242
14243 this.element
14244 .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
14245 .toggleClass( "ui-tabs-collapsible", options.collapsible )
14246 // Prevent users from focusing disabled tabs via click
14247 .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
14248 if ( $( this ).is( ".ui-state-disabled" ) ) {
14249 event.preventDefault();
14250 }
14251 })
14252 // support: IE <9
14253 // Preventing the default action in mousedown doesn't prevent IE
14254 // from focusing the element, so if the anchor gets focused, blur.
14255 // We don't have to worry about focusing the previously focused
14256 // element since clicking on a non-focusable element should focus
14257 // the body anyway.
14258 .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
14259 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
14260 this.blur();
14261 }
14262 });
14263
14264 this._processTabs();
14265 options.active = this._initialActive();
14266
14267 // Take disabling tabs via class attribute from HTML
14268 // into account and update option properly.
14269 if ( $.isArray( options.disabled ) ) {
14270 options.disabled = $.unique( options.disabled.concat(
14271 $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
14272 return that.tabs.index( li );
14273 })
14274 ) ).sort();
14275 }
14276
14277 // check for length avoids error when initializing empty list
14278 if ( this.options.active !== false && this.anchors.length ) {
14279 this.active = this._findActive( options.active );
14280 } else {
14281 this.active = $();
14282 }
14283
14284 this._refresh();
14285
14286 if ( this.active.length ) {
14287 this.load( options.active );
14288 }
14289 },
14290
14291 _initialActive: function() {
14292 var active = this.options.active,
14293 collapsible = this.options.collapsible,
14294 locationHash = location.hash.substring( 1 );
14295
14296 if ( active === null ) {
14297 // check the fragment identifier in the URL
14298 if ( locationHash ) {
14299 this.tabs.each(function( i, tab ) {
14300 if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
14301 active = i;
14302 return false;
14303 }
14304 });
14305 }
14306
14307 // check for a tab marked active via a class
14308 if ( active === null ) {
14309 active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
14310 }
14311
14312 // no active tab, set to false
14313 if ( active === null || active === -1 ) {
14314 active = this.tabs.length ? 0 : false;
14315 }
14316 }
14317
14318 // handle numbers: negative, out of range
14319 if ( active !== false ) {
14320 active = this.tabs.index( this.tabs.eq( active ) );
14321 if ( active === -1 ) {
14322 active = collapsible ? false : 0;
14323 }
14324 }
14325
14326 // don't allow collapsible: false and active: false
14327 if ( !collapsible && active === false && this.anchors.length ) {
14328 active = 0;
14329 }
14330
14331 return active;
14332 },
14333
14334 _getCreateEventData: function() {
14335 return {
14336 tab: this.active,
14337 panel: !this.active.length ? $() : this._getPanelForTab( this.active )
14338 };
14339 },
14340
14341 _tabKeydown: function( event ) {
14342 var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
14343 selectedIndex = this.tabs.index( focusedTab ),
14344 goingForward = true;
14345
14346 if ( this._handlePageNav( event ) ) {
14347 return;
14348 }
14349
14350 switch ( event.keyCode ) {
14351 case $.ui.keyCode.RIGHT:
14352 case $.ui.keyCode.DOWN:
14353 selectedIndex++;
14354 break;
14355 case $.ui.keyCode.UP:
14356 case $.ui.keyCode.LEFT:
14357 goingForward = false;
14358 selectedIndex--;
14359 break;
14360 case $.ui.keyCode.END:
14361 selectedIndex = this.anchors.length - 1;
14362 break;
14363 case $.ui.keyCode.HOME:
14364 selectedIndex = 0;
14365 break;
14366 case $.ui.keyCode.SPACE:
14367 // Activate only, no collapsing
14368 event.preventDefault();
14369 clearTimeout( this.activating );
14370 this._activate( selectedIndex );
14371 return;
14372 case $.ui.keyCode.ENTER:
14373 // Toggle (cancel delayed activation, allow collapsing)
14374 event.preventDefault();
14375 clearTimeout( this.activating );
14376 // Determine if we should collapse or activate
14377 this._activate( selectedIndex === this.options.active ? false : selectedIndex );
14378 return;
14379 default:
14380 return;
14381 }
14382
14383 // Focus the appropriate tab, based on which key was pressed
14384 event.preventDefault();
14385 clearTimeout( this.activating );
14386 selectedIndex = this._focusNextTab( selectedIndex, goingForward );
14387
14388 // Navigating with control key will prevent automatic activation
14389 if ( !event.ctrlKey ) {
14390 // Update aria-selected immediately so that AT think the tab is already selected.
14391 // Otherwise AT may confuse the user by stating that they need to activate the tab,
14392 // but the tab will already be activated by the time the announcement finishes.
14393 focusedTab.attr( "aria-selected", "false" );
14394 this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
14395
14396 this.activating = this._delay(function() {
14397 this.option( "active", selectedIndex );
14398 }, this.delay );
14399 }
14400 },
14401
14402 _panelKeydown: function( event ) {
14403 if ( this._handlePageNav( event ) ) {
14404 return;
14405 }
14406
14407 // Ctrl+up moves focus to the current tab
14408 if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
14409 event.preventDefault();
14410 this.active.focus();
14411 }
14412 },
14413
14414 // Alt+page up/down moves focus to the previous/next tab (and activates)
14415 _handlePageNav: function( event ) {
14416 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
14417 this._activate( this._focusNextTab( this.options.active - 1, false ) );
14418 return true;
14419 }
14420 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
14421 this._activate( this._focusNextTab( this.options.active + 1, true ) );
14422 return true;
14423 }
14424 },
14425
14426 _findNextTab: function( index, goingForward ) {
14427 var lastTabIndex = this.tabs.length - 1;
14428
14429 function constrain() {
14430 if ( index > lastTabIndex ) {
14431 index = 0;
14432 }
14433 if ( index < 0 ) {
14434 index = lastTabIndex;
14435 }
14436 return index;
14437 }
14438
14439 while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
14440 index = goingForward ? index + 1 : index - 1;
14441 }
14442
14443 return index;
14444 },
14445
14446 _focusNextTab: function( index, goingForward ) {
14447 index = this._findNextTab( index, goingForward );
14448 this.tabs.eq( index ).focus();
14449 return index;
14450 },
14451
14452 _setOption: function( key, value ) {
14453 if ( key === "active" ) {
14454 // _activate() will handle invalid values and update this.options
14455 this._activate( value );
14456 return;
14457 }
14458
14459 if ( key === "disabled" ) {
14460 // don't use the widget factory's disabled handling
14461 this._setupDisabled( value );
14462 return;
14463 }
14464
14465 this._super( key, value);
14466
14467 if ( key === "collapsible" ) {
14468 this.element.toggleClass( "ui-tabs-collapsible", value );
14469 // Setting collapsible: false while collapsed; open first panel
14470 if ( !value && this.options.active === false ) {
14471 this._activate( 0 );
14472 }
14473 }
14474
14475 if ( key === "event" ) {
14476 this._setupEvents( value );
14477 }
14478
14479 if ( key === "heightStyle" ) {
14480 this._setupHeightStyle( value );
14481 }
14482 },
14483
14484 _tabId: function( tab ) {
14485 return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
14486 },
14487
14488 _sanitizeSelector: function( hash ) {
14489 return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
14490 },
14491
14492 refresh: function() {
14493 var options = this.options,
14494 lis = this.tablist.children( ":has(a[href])" );
14495
14496 // get disabled tabs from class attribute from HTML
14497 // this will get converted to a boolean if needed in _refresh()
14498 options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
14499 return lis.index( tab );
14500 });
14501
14502 this._processTabs();
14503
14504 // was collapsed or no tabs
14505 if ( options.active === false || !this.anchors.length ) {
14506 options.active = false;
14507 this.active = $();
14508 // was active, but active tab is gone
14509 } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
14510 // all remaining tabs are disabled
14511 if ( this.tabs.length === options.disabled.length ) {
14512 options.active = false;
14513 this.active = $();
14514 // activate previous tab
14515 } else {
14516 this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
14517 }
14518 // was active, active tab still exists
14519 } else {
14520 // make sure active index is correct
14521 options.active = this.tabs.index( this.active );
14522 }
14523
14524 this._refresh();
14525 },
14526
14527 _refresh: function() {
14528 this._setupDisabled( this.options.disabled );
14529 this._setupEvents( this.options.event );
14530 this._setupHeightStyle( this.options.heightStyle );
14531
14532 this.tabs.not( this.active ).attr({
14533 "aria-selected": "false",
14534 tabIndex: -1
14535 });
14536 this.panels.not( this._getPanelForTab( this.active ) )
14537 .hide()
14538 .attr({
14539 "aria-expanded": "false",
14540 "aria-hidden": "true"
14541 });
14542
14543 // Make sure one tab is in the tab order
14544 if ( !this.active.length ) {
14545 this.tabs.eq( 0 ).attr( "tabIndex", 0 );
14546 } else {
14547 this.active
14548 .addClass( "ui-tabs-active ui-state-active" )
14549 .attr({
14550 "aria-selected": "true",
14551 tabIndex: 0
14552 });
14553 this._getPanelForTab( this.active )
14554 .show()
14555 .attr({
14556 "aria-expanded": "true",
14557 "aria-hidden": "false"
14558 });
14559 }
14560 },
14561
14562 _processTabs: function() {
14563 var that = this;
14564
14565 this.tablist = this._getList()
14566 .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
14567 .attr( "role", "tablist" );
14568
14569 this.tabs = this.tablist.find( "> li:has(a[href])" )
14570 .addClass( "ui-state-default ui-corner-top" )
14571 .attr({
14572 role: "tab",
14573 tabIndex: -1
14574 });
14575
14576 this.anchors = this.tabs.map(function() {
14577 return $( "a", this )[ 0 ];
14578 })
14579 .addClass( "ui-tabs-anchor" )
14580 .attr({
14581 role: "presentation",
14582 tabIndex: -1
14583 });
14584
14585 this.panels = $();
14586
14587 this.anchors.each(function( i, anchor ) {
14588 var selector, panel, panelId,
14589 anchorId = $( anchor ).uniqueId().attr( "id" ),
14590 tab = $( anchor ).closest( "li" ),
14591 originalAriaControls = tab.attr( "aria-controls" );
14592
14593 // inline tab
14594 if ( isLocal( anchor ) ) {
14595 selector = anchor.hash;
14596 panel = that.element.find( that._sanitizeSelector( selector ) );
14597 // remote tab
14598 } else {
14599 panelId = that._tabId( tab );
14600 selector = "#" + panelId;
14601 panel = that.element.find( selector );
14602 if ( !panel.length ) {
14603 panel = that._createPanel( panelId );
14604 panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
14605 }
14606 panel.attr( "aria-live", "polite" );
14607 }
14608
14609 if ( panel.length) {
14610 that.panels = that.panels.add( panel );
14611 }
14612 if ( originalAriaControls ) {
14613 tab.data( "ui-tabs-aria-controls", originalAriaControls );
14614 }
14615 tab.attr({
14616 "aria-controls": selector.substring( 1 ),
14617 "aria-labelledby": anchorId
14618 });
14619 panel.attr( "aria-labelledby", anchorId );
14620 });
14621
14622 this.panels
14623 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
14624 .attr( "role", "tabpanel" );
14625 },
14626
14627 // allow overriding how to find the list for rare usage scenarios (#7715)
14628 _getList: function() {
14629 return this.element.find( "ol,ul" ).eq( 0 );
14630 },
14631
14632 _createPanel: function( id ) {
14633 return $( "<div>" )
14634 .attr( "id", id )
14635 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
14636 .data( "ui-tabs-destroy", true );
14637 },
14638
14639 _setupDisabled: function( disabled ) {
14640 if ( $.isArray( disabled ) ) {
14641 if ( !disabled.length ) {
14642 disabled = false;
14643 } else if ( disabled.length === this.anchors.length ) {
14644 disabled = true;
14645 }
14646 }
14647
14648 // disable tabs
14649 for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
14650 if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
14651 $( li )
14652 .addClass( "ui-state-disabled" )
14653 .attr( "aria-disabled", "true" );
14654 } else {
14655 $( li )
14656 .removeClass( "ui-state-disabled" )
14657 .removeAttr( "aria-disabled" );
14658 }
14659 }
14660
14661 this.options.disabled = disabled;
14662 },
14663
14664 _setupEvents: function( event ) {
14665 var events = {};
14666 if ( event ) {
14667 $.each( event.split(" "), function( index, eventName ) {
14668 events[ eventName ] = "_eventHandler";
14669 });
14670 }
14671
14672 this._off( this.anchors.add( this.tabs ).add( this.panels ) );
14673 // Always prevent the default action, even when disabled
14674 this._on( true, this.anchors, {
14675 click: function( event ) {
14676 event.preventDefault();
14677 }
14678 });
14679 this._on( this.anchors, events );
14680 this._on( this.tabs, { keydown: "_tabKeydown" } );
14681 this._on( this.panels, { keydown: "_panelKeydown" } );
14682
14683 this._focusable( this.tabs );
14684 this._hoverable( this.tabs );
14685 },
14686
14687 _setupHeightStyle: function( heightStyle ) {
14688 var maxHeight,
14689 parent = this.element.parent();
14690
14691 if ( heightStyle === "fill" ) {
14692 maxHeight = parent.height();
14693 maxHeight -= this.element.outerHeight() - this.element.height();
14694
14695 this.element.siblings( ":visible" ).each(function() {
14696 var elem = $( this ),
14697 position = elem.css( "position" );
14698
14699 if ( position === "absolute" || position === "fixed" ) {
14700 return;
14701 }
14702 maxHeight -= elem.outerHeight( true );
14703 });
14704
14705 this.element.children().not( this.panels ).each(function() {
14706 maxHeight -= $( this ).outerHeight( true );
14707 });
14708
14709 this.panels.each(function() {
14710 $( this ).height( Math.max( 0, maxHeight -
14711 $( this ).innerHeight() + $( this ).height() ) );
14712 })
14713 .css( "overflow", "auto" );
14714 } else if ( heightStyle === "auto" ) {
14715 maxHeight = 0;
14716 this.panels.each(function() {
14717 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
14718 }).height( maxHeight );
14719 }
14720 },
14721
14722 _eventHandler: function( event ) {
14723 var options = this.options,
14724 active = this.active,
14725 anchor = $( event.currentTarget ),
14726 tab = anchor.closest( "li" ),
14727 clickedIsActive = tab[ 0 ] === active[ 0 ],
14728 collapsing = clickedIsActive && options.collapsible,
14729 toShow = collapsing ? $() : this._getPanelForTab( tab ),
14730 toHide = !active.length ? $() : this._getPanelForTab( active ),
14731 eventData = {
14732 oldTab: active,
14733 oldPanel: toHide,
14734 newTab: collapsing ? $() : tab,
14735 newPanel: toShow
14736 };
14737
14738 event.preventDefault();
14739
14740 if ( tab.hasClass( "ui-state-disabled" ) ||
14741 // tab is already loading
14742 tab.hasClass( "ui-tabs-loading" ) ||
14743 // can't switch durning an animation
14744 this.running ||
14745 // click on active header, but not collapsible
14746 ( clickedIsActive && !options.collapsible ) ||
14747 // allow canceling activation
14748 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
14749 return;
14750 }
14751
14752 options.active = collapsing ? false : this.tabs.index( tab );
14753
14754 this.active = clickedIsActive ? $() : tab;
14755 if ( this.xhr ) {
14756 this.xhr.abort();
14757 }
14758
14759 if ( !toHide.length && !toShow.length ) {
14760 $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
14761 }
14762
14763 if ( toShow.length ) {
14764 this.load( this.tabs.index( tab ), event );
14765 }
14766 this._toggle( event, eventData );
14767 },
14768
14769 // handles show/hide for selecting tabs
14770 _toggle: function( event, eventData ) {
14771 var that = this,
14772 toShow = eventData.newPanel,
14773 toHide = eventData.oldPanel;
14774
14775 this.running = true;
14776
14777 function complete() {
14778 that.running = false;
14779 that._trigger( "activate", event, eventData );
14780 }
14781
14782 function show() {
14783 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
14784
14785 if ( toShow.length && that.options.show ) {
14786 that._show( toShow, that.options.show, complete );
14787 } else {
14788 toShow.show();
14789 complete();
14790 }
14791 }
14792
14793 // start out by hiding, then showing, then completing
14794 if ( toHide.length && this.options.hide ) {
14795 this._hide( toHide, this.options.hide, function() {
14796 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
14797 show();
14798 });
14799 } else {
14800 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
14801 toHide.hide();
14802 show();
14803 }
14804
14805 toHide.attr({
14806 "aria-expanded": "false",
14807 "aria-hidden": "true"
14808 });
14809 eventData.oldTab.attr( "aria-selected", "false" );
14810 // If we're switching tabs, remove the old tab from the tab order.
14811 // If we're opening from collapsed state, remove the previous tab from the tab order.
14812 // If we're collapsing, then keep the collapsing tab in the tab order.
14813 if ( toShow.length && toHide.length ) {
14814 eventData.oldTab.attr( "tabIndex", -1 );
14815 } else if ( toShow.length ) {
14816 this.tabs.filter(function() {
14817 return $( this ).attr( "tabIndex" ) === 0;
14818 })
14819 .attr( "tabIndex", -1 );
14820 }
14821
14822 toShow.attr({
14823 "aria-expanded": "true",
14824 "aria-hidden": "false"
14825 });
14826 eventData.newTab.attr({
14827 "aria-selected": "true",
14828 tabIndex: 0
14829 });
14830 },
14831
14832 _activate: function( index ) {
14833 var anchor,
14834 active = this._findActive( index );
14835
14836 // trying to activate the already active panel
14837 if ( active[ 0 ] === this.active[ 0 ] ) {
14838 return;
14839 }
14840
14841 // trying to collapse, simulate a click on the current active header
14842 if ( !active.length ) {
14843 active = this.active;
14844 }
14845
14846 anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
14847 this._eventHandler({
14848 target: anchor,
14849 currentTarget: anchor,
14850 preventDefault: $.noop
14851 });
14852 },
14853
14854 _findActive: function( index ) {
14855 return index === false ? $() : this.tabs.eq( index );
14856 },
14857
14858 _getIndex: function( index ) {
14859 // meta-function to give users option to provide a href string instead of a numerical index.
14860 if ( typeof index === "string" ) {
14861 index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
14862 }
14863
14864 return index;
14865 },
14866
14867 _destroy: function() {
14868 if ( this.xhr ) {
14869 this.xhr.abort();
14870 }
14871
14872 this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
14873
14874 this.tablist
14875 .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
14876 .removeAttr( "role" );
14877
14878 this.anchors
14879 .removeClass( "ui-tabs-anchor" )
14880 .removeAttr( "role" )
14881 .removeAttr( "tabIndex" )
14882 .removeUniqueId();
14883
14884 this.tabs.add( this.panels ).each(function() {
14885 if ( $.data( this, "ui-tabs-destroy" ) ) {
14886 $( this ).remove();
14887 } else {
14888 $( this )
14889 .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
14890 "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
14891 .removeAttr( "tabIndex" )
14892 .removeAttr( "aria-live" )
14893 .removeAttr( "aria-busy" )
14894 .removeAttr( "aria-selected" )
14895 .removeAttr( "aria-labelledby" )
14896 .removeAttr( "aria-hidden" )
14897 .removeAttr( "aria-expanded" )
14898 .removeAttr( "role" );
14899 }
14900 });
14901
14902 this.tabs.each(function() {
14903 var li = $( this ),
14904 prev = li.data( "ui-tabs-aria-controls" );
14905 if ( prev ) {
14906 li
14907 .attr( "aria-controls", prev )
14908 .removeData( "ui-tabs-aria-controls" );
14909 } else {
14910 li.removeAttr( "aria-controls" );
14911 }
14912 });
14913
14914 this.panels.show();
14915
14916 if ( this.options.heightStyle !== "content" ) {
14917 this.panels.css( "height", "" );
14918 }
14919 },
14920
14921 enable: function( index ) {
14922 var disabled = this.options.disabled;
14923 if ( disabled === false ) {
14924 return;
14925 }
14926
14927 if ( index === undefined ) {
14928 disabled = false;
14929 } else {
14930 index = this._getIndex( index );
14931 if ( $.isArray( disabled ) ) {
14932 disabled = $.map( disabled, function( num ) {
14933 return num !== index ? num : null;
14934 });
14935 } else {
14936 disabled = $.map( this.tabs, function( li, num ) {
14937 return num !== index ? num : null;
14938 });
14939 }
14940 }
14941 this._setupDisabled( disabled );
14942 },
14943
14944 disable: function( index ) {
14945 var disabled = this.options.disabled;
14946 if ( disabled === true ) {
14947 return;
14948 }
14949
14950 if ( index === undefined ) {
14951 disabled = true;
14952 } else {
14953 index = this._getIndex( index );
14954 if ( $.inArray( index, disabled ) !== -1 ) {
14955 return;
14956 }
14957 if ( $.isArray( disabled ) ) {
14958 disabled = $.merge( [ index ], disabled ).sort();
14959 } else {
14960 disabled = [ index ];
14961 }
14962 }
14963 this._setupDisabled( disabled );
14964 },
14965
14966 load: function( index, event ) {
14967 index = this._getIndex( index );
14968 var that = this,
14969 tab = this.tabs.eq( index ),
14970 anchor = tab.find( ".ui-tabs-anchor" ),
14971 panel = this._getPanelForTab( tab ),
14972 eventData = {
14973 tab: tab,
14974 panel: panel
14975 };
14976
14977 // not remote
14978 if ( isLocal( anchor[ 0 ] ) ) {
14979 return;
14980 }
14981
14982 this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
14983
14984 // support: jQuery <1.8
14985 // jQuery <1.8 returns false if the request is canceled in beforeSend,
14986 // but as of 1.8, $.ajax() always returns a jqXHR object.
14987 if ( this.xhr && this.xhr.statusText !== "canceled" ) {
14988 tab.addClass( "ui-tabs-loading" );
14989 panel.attr( "aria-busy", "true" );
14990
14991 this.xhr
14992 .success(function( response ) {
14993 // support: jQuery <1.8
14994 // http://bugs.jquery.com/ticket/11778
14995 setTimeout(function() {
14996 panel.html( response );
14997 that._trigger( "load", event, eventData );
14998 }, 1 );
14999 })
15000 .complete(function( jqXHR, status ) {
15001 // support: jQuery <1.8
15002 // http://bugs.jquery.com/ticket/11778
15003 setTimeout(function() {
15004 if ( status === "abort" ) {
15005 that.panels.stop( false, true );
15006 }
15007
15008 tab.removeClass( "ui-tabs-loading" );
15009 panel.removeAttr( "aria-busy" );
15010
15011 if ( jqXHR === that.xhr ) {
15012 delete that.xhr;
15013 }
15014 }, 1 );
15015 });
15016 }
15017 },
15018
15019 _ajaxSettings: function( anchor, event, eventData ) {
15020 var that = this;
15021 return {
15022 url: anchor.attr( "href" ),
15023 beforeSend: function( jqXHR, settings ) {
15024 return that._trigger( "beforeLoad", event,
15025 $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
15026 }
15027 };
15028 },
15029
15030 _getPanelForTab: function( tab ) {
15031 var id = $( tab ).attr( "aria-controls" );
15032 return this.element.find( this._sanitizeSelector( "#" + id ) );
15033 }
15034});
15035
15036})( jQuery );
15037
15038(function( $, undefined ) {
15039
15040})( jQuery );
15041
15042(function( $, window ) {
15043
15044 $.mobile.iosorientationfixEnabled = true;
15045
15046 // This fix addresses an iOS bug, so return early if the UA claims it's something else.
15047 var ua = navigator.userAgent,
15048 zoom,
15049 evt, x, y, z, aig;
15050 if ( !( /iPhone|iPad|iPod/.test( navigator.platform ) && /OS [1-5]_[0-9_]* like Mac OS X/i.test( ua ) && ua.indexOf( "AppleWebKit" ) > -1 ) ) {
15051 $.mobile.iosorientationfixEnabled = false;
15052 return;
15053 }
15054
15055 zoom = $.mobile.zoom;
15056
15057 function checkTilt( e ) {
15058 evt = e.originalEvent;
15059 aig = evt.accelerationIncludingGravity;
15060
15061 x = Math.abs( aig.x );
15062 y = Math.abs( aig.y );
15063 z = Math.abs( aig.z );
15064
15065 // If portrait orientation and in one of the danger zones
15066 if ( !window.orientation && ( x > 7 || ( ( z > 6 && y < 8 || z < 8 && y > 6 ) && x > 5 ) ) ) {
15067 if ( zoom.enabled ) {
15068 zoom.disable();
15069 }
15070 } else if ( !zoom.enabled ) {
15071 zoom.enable();
15072 }
15073 }
15074
15075 $.mobile.document.on( "mobileinit", function() {
15076 if ( $.mobile.iosorientationfixEnabled ) {
15077 $.mobile.window
15078 .bind( "orientationchange.iosorientationfix", zoom.enable )
15079 .bind( "devicemotion.iosorientationfix", checkTilt );
15080 }
15081 });
15082
15083}( jQuery, this ));
15084
15085(function( $, undefined ) {
15086
15087$.widget( "mobile.pagecontainer", $.mobile.pagecontainer, {
15088 _getTransitionHandler: function( transition ) {
15089 transition = $.mobile._maybeDegradeTransition( transition );
15090
15091 //find the transition handler for the specified transition. If there
15092 //isn't one in our transitionHandlers dictionary, use the default one.
15093 //call the handler immediately to kick off the transition.
15094 return $.mobile.transitionHandlers[ transition ] || $.mobile.defaultTransitionHandler;
15095 },
15096
15097 _performTransition: function( transition, reverse, to, from ) {
15098 var TransitionHandler = this._getTransitionHandler( transition );
15099
15100 return ( new TransitionHandler( transition, reverse, to, from ) ).transition(
15101 $.mobile.navigate.history.getActive().lastScroll || $.mobile.defaultHomeScroll );
15102 }
15103});
15104
15105})( jQuery );
15106
15107(function( $, window, undefined ) {
15108 var $html = $( "html" ),
15109 $window = $.mobile.window;
15110
15111 //remove initial build class (only present on first pageshow)
15112 function hideRenderingClass() {
15113 $html.removeClass( "ui-mobile-rendering" );
15114 }
15115
15116 // trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
15117 $.mobile.document.trigger( "mobileinit" );
15118
15119 // support conditions
15120 // if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
15121 // otherwise, proceed with the enhancements
15122 if ( !$.mobile.gradeA() ) {
15123 return;
15124 }
15125
15126 // override ajaxEnabled on platforms that have known conflicts with hash history updates
15127 // or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini)
15128 if ( $.mobile.ajaxBlacklist ) {
15129 $.mobile.ajaxEnabled = false;
15130 }
15131
15132 // Add mobile, initial load "rendering" classes to docEl
15133 $html.addClass( "ui-mobile ui-mobile-rendering" );
15134
15135 // This is a fallback. If anything goes wrong (JS errors, etc), or events don't fire,
15136 // this ensures the rendering class is removed after 5 seconds, so content is visible and accessible
15137 setTimeout( hideRenderingClass, 5000 );
15138
15139 $.extend( $.mobile, {
15140 // find and enhance the pages in the dom and transition to the first page.
15141 initializePage: function() {
15142 // find present pages
15143 var path = $.mobile.path,
15144 $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ),
15145 hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ),
15146 theLocation = $.mobile.path.parseLocation(),
15147 hashPage = hash ? document.getElementById( hash ) : undefined;
15148
15149 // if no pages are found, create one with body's inner html
15150 if ( !$pages.length ) {
15151 $pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 );
15152 }
15153
15154 // add dialogs, set data-url attrs
15155 $pages.each(function() {
15156 var $this = $( this );
15157
15158 // unless the data url is already set set it to the pathname
15159 if ( !$this[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) ) {
15160 $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) ||
15161 path.convertUrlToDataUrl( theLocation.pathname + theLocation.search ) );
15162 }
15163 });
15164
15165 // define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
15166 $.mobile.firstPage = $pages.first();
15167
15168 // define page container
15169 $.mobile.pageContainer = $.mobile.firstPage
15170 .parent()
15171 .addClass( "ui-mobile-viewport" )
15172 .pagecontainer();
15173
15174 // initialize navigation events now, after mobileinit has occurred and the page container
15175 // has been created but before the rest of the library is alerted to that fact
15176 $.mobile.navreadyDeferred.resolve();
15177
15178 // alert listeners that the pagecontainer has been determined for binding
15179 // to events triggered on it
15180 $window.trigger( "pagecontainercreate" );
15181
15182 // cue page loading message
15183 $.mobile.loading( "show" );
15184
15185 //remove initial build class (only present on first pageshow)
15186 hideRenderingClass();
15187
15188 // if hashchange listening is disabled, there's no hash deeplink,
15189 // the hash is not valid (contains more than one # or does not start with #)
15190 // or there is no page with that hash, change to the first page in the DOM
15191 // Remember, however, that the hash can also be a path!
15192 if ( ! ( $.mobile.hashListeningEnabled &&
15193 $.mobile.path.isHashValid( location.hash ) &&
15194 ( $( hashPage ).is( ":jqmData(role='page')" ) ||
15195 $.mobile.path.isPath( hash ) ||
15196 hash === $.mobile.dialogHashKey ) ) ) {
15197
15198 // make sure to set initial popstate state if it exists
15199 // so that navigation back to the initial page works properly
15200 if ( $.event.special.navigate.isPushStateEnabled() ) {
15201 $.mobile.navigate.navigator.squash( path.parseLocation().href );
15202 }
15203
15204 $.mobile.changePage( $.mobile.firstPage, {
15205 transition: "none",
15206 reverse: true,
15207 changeHash: false,
15208 fromHashChange: true
15209 });
15210 } else {
15211 // trigger hashchange or navigate to squash and record the correct
15212 // history entry for an initial hash path
15213 if ( !$.event.special.navigate.isPushStateEnabled() ) {
15214 $window.trigger( "hashchange", [true] );
15215 } else {
15216 // TODO figure out how to simplify this interaction with the initial history entry
15217 // at the bottom js/navigate/navigate.js
15218 $.mobile.navigate.history.stack = [];
15219 $.mobile.navigate( $.mobile.path.isPath( location.hash ) ? location.hash : location.href );
15220 }
15221 }
15222 }
15223 });
15224
15225 $(function() {
15226 //Run inlineSVG support test
15227 $.support.inlineSVG();
15228
15229 // check which scrollTop value should be used by scrolling to 1 immediately at domready
15230 // then check what the scroll top is. Android will report 0... others 1
15231 // note that this initial scroll won't hide the address bar. It's just for the check.
15232
15233 // hide iOS browser chrome on load if hideUrlBar is true this is to try and do it as soon as possible
15234 if ( $.mobile.hideUrlBar ) {
15235 window.scrollTo( 0, 1 );
15236 }
15237
15238 // if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
15239 // it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
15240 // so if it's 1, use 0 from now on
15241 $.mobile.defaultHomeScroll = ( !$.support.scrollTop || $.mobile.window.scrollTop() === 1 ) ? 0 : 1;
15242
15243 //dom-ready inits
15244 if ( $.mobile.autoInitializePage ) {
15245 $.mobile.initializePage();
15246 }
15247
15248 // window load event
15249 // hide iOS browser chrome on load if hideUrlBar is true this is as fall back incase we were too early before
15250 if ( $.mobile.hideUrlBar ) {
15251 $window.load( $.mobile.silentScroll );
15252 }
15253
15254 if ( !$.support.cssPointerEvents ) {
15255 // IE and Opera don't support CSS pointer-events: none that we use to disable link-based buttons
15256 // by adding the 'ui-disabled' class to them. Using a JavaScript workaround for those browser.
15257 // https://github.com/jquery/jquery-mobile/issues/3558
15258
15259 // DEPRECATED as of 1.4.0 - remove ui-disabled after 1.4.0 release
15260 // only ui-state-disabled should be present thereafter
15261 $.mobile.document.delegate( ".ui-state-disabled,.ui-disabled", "vclick",
15262 function( e ) {
15263 e.preventDefault();
15264 e.stopImmediatePropagation();
15265 }
15266 );
15267 }
15268 });
15269}( jQuery, this ));
15270
15271
15272}));