jquery-mobile-rails-assets 1.4.1 → 1.4.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 14d90189b04e73974d202af3acf5cad4b2fa975f
4
- data.tar.gz: 8e8159b89d1abc2ab9528dd8cad665b33485c0aa
2
+ SHA256:
3
+ metadata.gz: d3720f92443e18e8dc329074ff206af8ba81851a55c37e788d6718ccb19c6820
4
+ data.tar.gz: 94d2f332b90756803484e2901ddd427a1c91aefac0b517482ba70a91819854f3
5
5
  SHA512:
6
- metadata.gz: 0d8e2479efec2c29d21b83cf2e9ba2e09d32e591db672ddfddd574715bdad8c5c3bd08302f18f4b933025bf7fbec2efcb478ea15a29971cbaa6e6a20e75458ea
7
- data.tar.gz: fa870275e2fbf6dcaaea1c4fae7d53c2be587b693989771588272cb1341d1b01e65a52b8593587512568e2db7f282173628f07892ca4b450d73bb02815cd5c6e
6
+ metadata.gz: 6c151d54f05af4b2c1e748647ab4235dacbe0a049cf53e32a36e4f9f23bbbc8ac7cc84ff246d2f68e58991fb011d70df3450558e80a0d9e36db2195b9f574b5d
7
+ data.tar.gz: 653c6183a311046e5e981e1accc4bd702a55bc474fec084b7a670aff88619320c21c2ce4e0bbc8500901da7869f542ffcd2baabfe1783c18a3057b12eacbe55c
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # jQuery Mobile for Rails
1
+ # THIS GEM IS NOT LONGER UPDATED
2
+
3
+ Suggest you check out the [jquery_mobile_rails gem](https://rubygems.org/gems/jquery_mobile_rails) instead.
4
+
5
+ ## Intro
2
6
 
3
7
  This gem vendors the jQuery Mobile assets for Rails 3.1 and greater.
4
8
  The files will be added to the asset pipeline and available for you to use.
@@ -15,13 +19,13 @@ In your Gemfile add:
15
19
  gem 'jquery-mobile-rails-assets'
16
20
  ```
17
21
 
18
- You can then include it in your app by adding the following to your javascript file:
22
+ You can then include it in your app by adding the following to your javascript application file:
19
23
 
20
24
  ```javascript
21
25
  //= require jquery.mobile
22
26
  ```
23
27
 
24
- And to the css file:
28
+ And to the css application file:
25
29
 
26
30
  ```css
27
31
  *= require jquery.mobile
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * jQuery Mobile 1.4.1
3
- * Git HEAD hash: 18c1e32bfc4e0e92756dedc105d799131607f5bb <> Date: Wed Feb 12 2014 22:15:20 UTC
2
+ * jQuery Mobile 1.4.5
3
+ * Git HEAD hash: 68e55e78b292634d3991c795f06f5e37a512decc <> Date: Fri Oct 31 2014 17:33:30 UTC
4
4
  * http://jquerymobile.com
5
5
  *
6
- * Copyright 2010, 2014 jQuery Foundation, Inc. and other contributors
6
+ * Copyright 2010, 2014 jQuery Foundation, Inc. and othercontributors
7
7
  * Released under the MIT license.
8
8
  * http://jquery.org/license
9
9
  *
@@ -26,204 +26,6 @@
26
26
  $.mobile = {};
27
27
  }( jQuery ));
28
28
 
29
- (function( $, window, undefined ) {
30
- $.extend( $.mobile, {
31
-
32
- // Version of the jQuery Mobile Framework
33
- version: "1.4.1",
34
-
35
- // Deprecated and no longer used in 1.4 remove in 1.5
36
- // Define the url parameter used for referencing widget-generated sub-pages.
37
- // Translates to example.html&ui-page=subpageIdentifier
38
- // hash segment before &ui-page= is used to make Ajax request
39
- subPageUrlKey: "ui-page",
40
-
41
- hideUrlBar: true,
42
-
43
- // Keepnative Selector
44
- keepNative: ":jqmData(role='none'), :jqmData(role='nojs')",
45
-
46
- // Deprecated in 1.4 remove in 1.5
47
- // Class assigned to page currently in view, and during transitions
48
- activePageClass: "ui-page-active",
49
-
50
- // Deprecated in 1.4 remove in 1.5
51
- // Class used for "active" button state, from CSS framework
52
- activeBtnClass: "ui-btn-active",
53
-
54
- // Deprecated in 1.4 remove in 1.5
55
- // Class used for "focus" form element state, from CSS framework
56
- focusClass: "ui-focus",
57
-
58
- // Automatically handle clicks and form submissions through Ajax, when same-domain
59
- ajaxEnabled: true,
60
-
61
- // Automatically load and show pages based on location.hash
62
- hashListeningEnabled: true,
63
-
64
- // disable to prevent jquery from bothering with links
65
- linkBindingEnabled: true,
66
-
67
- // Set default page transition - 'none' for no transitions
68
- defaultPageTransition: "fade",
69
-
70
- // Set maximum window width for transitions to apply - 'false' for no limit
71
- maxTransitionWidth: false,
72
-
73
- // Minimum scroll distance that will be remembered when returning to a page
74
- // Deprecated remove in 1.5
75
- minScrollBack: 0,
76
-
77
- // Set default dialog transition - 'none' for no transitions
78
- defaultDialogTransition: "pop",
79
-
80
- // Error response message - appears when an Ajax page request fails
81
- pageLoadErrorMessage: "Error Loading Page",
82
-
83
- // For error messages, which theme does the box uses?
84
- pageLoadErrorMessageTheme: "a",
85
-
86
- // replace calls to window.history.back with phonegaps navigation helper
87
- // where it is provided on the window object
88
- phonegapNavigationEnabled: false,
89
-
90
- //automatically initialize the DOM when it's ready
91
- autoInitializePage: true,
92
-
93
- pushStateEnabled: true,
94
-
95
- // allows users to opt in to ignoring content by marking a parent element as
96
- // data-ignored
97
- ignoreContentEnabled: false,
98
-
99
- buttonMarkup: {
100
- hoverDelay: 200
101
- },
102
-
103
- // disable the alteration of the dynamic base tag or links in the case
104
- // that a dynamic base tag isn't supported
105
- dynamicBaseEnabled: true,
106
-
107
- // default the property to remove dependency on assignment in init module
108
- pageContainer: $(),
109
-
110
- //enable cross-domain page support
111
- allowCrossDomainPages: false,
112
-
113
- dialogHashKey: "&ui-state=dialog"
114
- });
115
- })( jQuery, this );
116
-
117
- (function( $, window, undefined ) {
118
- var nsNormalizeDict = {},
119
- oldFind = $.find,
120
- rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
121
- jqmDataRE = /:jqmData\(([^)]*)\)/g;
122
-
123
- $.extend( $.mobile, {
124
-
125
- // Namespace used framework-wide for data-attrs. Default is no namespace
126
-
127
- ns: "",
128
-
129
- // Retrieve an attribute from an element and perform some massaging of the value
130
-
131
- getAttribute: function( element, key ) {
132
- var data;
133
-
134
- element = element.jquery ? element[0] : element;
135
-
136
- if ( element && element.getAttribute ) {
137
- data = element.getAttribute( "data-" + $.mobile.ns + key );
138
- }
139
-
140
- // Copied from core's src/data.js:dataAttr()
141
- // Convert from a string to a proper data type
142
- try {
143
- data = data === "true" ? true :
144
- data === "false" ? false :
145
- data === "null" ? null :
146
- // Only convert to a number if it doesn't change the string
147
- +data + "" === data ? +data :
148
- rbrace.test( data ) ? JSON.parse( data ) :
149
- data;
150
- } catch( err ) {}
151
-
152
- return data;
153
- },
154
-
155
- // Expose our cache for testing purposes.
156
- nsNormalizeDict: nsNormalizeDict,
157
-
158
- // Take a data attribute property, prepend the namespace
159
- // and then camel case the attribute string. Add the result
160
- // to our nsNormalizeDict so we don't have to do this again.
161
- nsNormalize: function( prop ) {
162
- return nsNormalizeDict[ prop ] ||
163
- ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
164
- },
165
-
166
- // Find the closest javascript page element to gather settings data jsperf test
167
- // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
168
- // possibly naive, but it shows that the parsing overhead for *just* the page selector vs
169
- // the page and dialog selector is negligable. This could probably be speed up by
170
- // doing a similar parent node traversal to the one found in the inherited theme code above
171
- closestPageData: function( $target ) {
172
- return $target
173
- .closest( ":jqmData(role='page'), :jqmData(role='dialog')" )
174
- .data( "mobile-page" );
175
- }
176
-
177
- });
178
-
179
- // Mobile version of data and removeData and hasData methods
180
- // ensures all data is set and retrieved using jQuery Mobile's data namespace
181
- $.fn.jqmData = function( prop, value ) {
182
- var result;
183
- if ( typeof prop !== "undefined" ) {
184
- if ( prop ) {
185
- prop = $.mobile.nsNormalize( prop );
186
- }
187
-
188
- // undefined is permitted as an explicit input for the second param
189
- // in this case it returns the value and does not set it to undefined
190
- if ( arguments.length < 2 || value === undefined ) {
191
- result = this.data( prop );
192
- } else {
193
- result = this.data( prop, value );
194
- }
195
- }
196
- return result;
197
- };
198
-
199
- $.jqmData = function( elem, prop, value ) {
200
- var result;
201
- if ( typeof prop !== "undefined" ) {
202
- result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value );
203
- }
204
- return result;
205
- };
206
-
207
- $.fn.jqmRemoveData = function( prop ) {
208
- return this.removeData( $.mobile.nsNormalize( prop ) );
209
- };
210
-
211
- $.jqmRemoveData = function( elem, prop ) {
212
- return $.removeData( elem, $.mobile.nsNormalize( prop ) );
213
- };
214
-
215
- $.find = function( selector, context, ret, extra ) {
216
- if ( selector.indexOf( ":jqmData" ) > -1 ) {
217
- selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
218
- }
219
-
220
- return oldFind.call( this, selector, context, ret, extra );
221
- };
222
-
223
- $.extend( $.find, oldFind );
224
-
225
- })( jQuery, this );
226
-
227
29
  /*!
228
30
  * jQuery UI Core c0ab71056b936627e8a7821f03c044aec6280a40
229
31
  * http://jqueryui.com
@@ -520,6 +322,55 @@ $.ui.plugin = {
520
322
 
521
323
  (function( $, window, undefined ) {
522
324
 
325
+ // Subtract the height of external toolbars from the page height, if the page does not have
326
+ // internal toolbars of the same type. We take care to use the widget options if we find a
327
+ // widget instance and the element's data-attributes otherwise.
328
+ var compensateToolbars = function( page, desiredHeight ) {
329
+ var pageParent = page.parent(),
330
+ toolbarsAffectingHeight = [],
331
+
332
+ // We use this function to filter fixed toolbars with option updatePagePadding set to
333
+ // true (which is the default) from our height subtraction, because fixed toolbars with
334
+ // option updatePagePadding set to true compensate for their presence by adding padding
335
+ // to the active page. We want to avoid double-counting by also subtracting their
336
+ // height from the desired page height.
337
+ noPadders = function() {
338
+ var theElement = $( this ),
339
+ widgetOptions = $.mobile.toolbar && theElement.data( "mobile-toolbar" ) ?
340
+ theElement.toolbar( "option" ) : {
341
+ position: theElement.attr( "data-" + $.mobile.ns + "position" ),
342
+ updatePagePadding: ( theElement.attr( "data-" + $.mobile.ns +
343
+ "update-page-padding" ) !== false )
344
+ };
345
+
346
+ return !( widgetOptions.position === "fixed" &&
347
+ widgetOptions.updatePagePadding === true );
348
+ },
349
+ externalHeaders = pageParent.children( ":jqmData(role='header')" ).filter( noPadders ),
350
+ internalHeaders = page.children( ":jqmData(role='header')" ),
351
+ externalFooters = pageParent.children( ":jqmData(role='footer')" ).filter( noPadders ),
352
+ internalFooters = page.children( ":jqmData(role='footer')" );
353
+
354
+ // If we have no internal headers, but we do have external headers, then their height
355
+ // reduces the page height
356
+ if ( internalHeaders.length === 0 && externalHeaders.length > 0 ) {
357
+ toolbarsAffectingHeight = toolbarsAffectingHeight.concat( externalHeaders.toArray() );
358
+ }
359
+
360
+ // If we have no internal footers, but we do have external footers, then their height
361
+ // reduces the page height
362
+ if ( internalFooters.length === 0 && externalFooters.length > 0 ) {
363
+ toolbarsAffectingHeight = toolbarsAffectingHeight.concat( externalFooters.toArray() );
364
+ }
365
+
366
+ $.each( toolbarsAffectingHeight, function( index, value ) {
367
+ desiredHeight -= $( value ).outerHeight();
368
+ });
369
+
370
+ // Height must be at least zero
371
+ return Math.max( 0, desiredHeight );
372
+ };
373
+
523
374
  $.extend( $.mobile, {
524
375
  // define the window and the document objects
525
376
  window: $( window ),
@@ -650,9 +501,16 @@ $.ui.plugin = {
650
501
  pageHeight = page.height(),
651
502
  pageOuterHeight = page.outerHeight( true );
652
503
 
653
- height = ( typeof height === "number" ) ? height : $.mobile.getScreenHeight();
504
+ height = compensateToolbars( page,
505
+ ( typeof height === "number" ) ? height : $.mobile.getScreenHeight() );
506
+
507
+ // Remove any previous min-height setting
508
+ page.css( "min-height", "" );
654
509
 
655
- page.css( "min-height", height - ( pageOuterHeight - pageHeight ) );
510
+ // Set the minimum height only if the height as determined by CSS is insufficient
511
+ if ( page.height() < height ) {
512
+ page.css( "min-height", height - ( pageOuterHeight - pageHeight ) );
513
+ }
656
514
  },
657
515
 
658
516
  loading: function() {
@@ -791,6 +649,93 @@ $.ui.plugin = {
791
649
 
792
650
  })( jQuery, this );
793
651
 
652
+ (function( $, window, undefined ) {
653
+ $.extend( $.mobile, {
654
+
655
+ // Version of the jQuery Mobile Framework
656
+ version: "1.4.5",
657
+
658
+ // Deprecated and no longer used in 1.4 remove in 1.5
659
+ // Define the url parameter used for referencing widget-generated sub-pages.
660
+ // Translates to example.html&ui-page=subpageIdentifier
661
+ // hash segment before &ui-page= is used to make Ajax request
662
+ subPageUrlKey: "ui-page",
663
+
664
+ hideUrlBar: true,
665
+
666
+ // Keepnative Selector
667
+ keepNative: ":jqmData(role='none'), :jqmData(role='nojs')",
668
+
669
+ // Deprecated in 1.4 remove in 1.5
670
+ // Class assigned to page currently in view, and during transitions
671
+ activePageClass: "ui-page-active",
672
+
673
+ // Deprecated in 1.4 remove in 1.5
674
+ // Class used for "active" button state, from CSS framework
675
+ activeBtnClass: "ui-btn-active",
676
+
677
+ // Deprecated in 1.4 remove in 1.5
678
+ // Class used for "focus" form element state, from CSS framework
679
+ focusClass: "ui-focus",
680
+
681
+ // Automatically handle clicks and form submissions through Ajax, when same-domain
682
+ ajaxEnabled: true,
683
+
684
+ // Automatically load and show pages based on location.hash
685
+ hashListeningEnabled: true,
686
+
687
+ // disable to prevent jquery from bothering with links
688
+ linkBindingEnabled: true,
689
+
690
+ // Set default page transition - 'none' for no transitions
691
+ defaultPageTransition: "fade",
692
+
693
+ // Set maximum window width for transitions to apply - 'false' for no limit
694
+ maxTransitionWidth: false,
695
+
696
+ // Minimum scroll distance that will be remembered when returning to a page
697
+ // Deprecated remove in 1.5
698
+ minScrollBack: 0,
699
+
700
+ // Set default dialog transition - 'none' for no transitions
701
+ defaultDialogTransition: "pop",
702
+
703
+ // Error response message - appears when an Ajax page request fails
704
+ pageLoadErrorMessage: "Error Loading Page",
705
+
706
+ // For error messages, which theme does the box use?
707
+ pageLoadErrorMessageTheme: "a",
708
+
709
+ // replace calls to window.history.back with phonegaps navigation helper
710
+ // where it is provided on the window object
711
+ phonegapNavigationEnabled: false,
712
+
713
+ //automatically initialize the DOM when it's ready
714
+ autoInitializePage: true,
715
+
716
+ pushStateEnabled: true,
717
+
718
+ // allows users to opt in to ignoring content by marking a parent element as
719
+ // data-ignored
720
+ ignoreContentEnabled: false,
721
+
722
+ buttonMarkup: {
723
+ hoverDelay: 200
724
+ },
725
+
726
+ // disable the alteration of the dynamic base tag or links in the case
727
+ // that a dynamic base tag isn't supported
728
+ dynamicBaseEnabled: true,
729
+
730
+ // default the property to remove dependency on assignment in init module
731
+ pageContainer: $(),
732
+
733
+ //enable cross-domain page support
734
+ allowCrossDomainPages: false,
735
+
736
+ dialogHashKey: "&ui-state=dialog"
737
+ });
738
+ })( jQuery, this );
794
739
 
795
740
  /*!
796
741
  * jQuery UI Widget c0ab71056b936627e8a7821f03c044aec6280a40
@@ -1316,6 +1261,116 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
1316
1261
 
1317
1262
  })( jQuery );
1318
1263
 
1264
+ (function( $, window, undefined ) {
1265
+ var nsNormalizeDict = {},
1266
+ oldFind = $.find,
1267
+ rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
1268
+ jqmDataRE = /:jqmData\(([^)]*)\)/g;
1269
+
1270
+ $.extend( $.mobile, {
1271
+
1272
+ // Namespace used framework-wide for data-attrs. Default is no namespace
1273
+
1274
+ ns: "",
1275
+
1276
+ // Retrieve an attribute from an element and perform some massaging of the value
1277
+
1278
+ getAttribute: function( element, key ) {
1279
+ var data;
1280
+
1281
+ element = element.jquery ? element[0] : element;
1282
+
1283
+ if ( element && element.getAttribute ) {
1284
+ data = element.getAttribute( "data-" + $.mobile.ns + key );
1285
+ }
1286
+
1287
+ // Copied from core's src/data.js:dataAttr()
1288
+ // Convert from a string to a proper data type
1289
+ try {
1290
+ data = data === "true" ? true :
1291
+ data === "false" ? false :
1292
+ data === "null" ? null :
1293
+ // Only convert to a number if it doesn't change the string
1294
+ +data + "" === data ? +data :
1295
+ rbrace.test( data ) ? JSON.parse( data ) :
1296
+ data;
1297
+ } catch( err ) {}
1298
+
1299
+ return data;
1300
+ },
1301
+
1302
+ // Expose our cache for testing purposes.
1303
+ nsNormalizeDict: nsNormalizeDict,
1304
+
1305
+ // Take a data attribute property, prepend the namespace
1306
+ // and then camel case the attribute string. Add the result
1307
+ // to our nsNormalizeDict so we don't have to do this again.
1308
+ nsNormalize: function( prop ) {
1309
+ return nsNormalizeDict[ prop ] ||
1310
+ ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
1311
+ },
1312
+
1313
+ // Find the closest javascript page element to gather settings data jsperf test
1314
+ // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
1315
+ // possibly naive, but it shows that the parsing overhead for *just* the page selector vs
1316
+ // the page and dialog selector is negligable. This could probably be speed up by
1317
+ // doing a similar parent node traversal to the one found in the inherited theme code above
1318
+ closestPageData: function( $target ) {
1319
+ return $target
1320
+ .closest( ":jqmData(role='page'), :jqmData(role='dialog')" )
1321
+ .data( "mobile-page" );
1322
+ }
1323
+
1324
+ });
1325
+
1326
+ // Mobile version of data and removeData and hasData methods
1327
+ // ensures all data is set and retrieved using jQuery Mobile's data namespace
1328
+ $.fn.jqmData = function( prop, value ) {
1329
+ var result;
1330
+ if ( typeof prop !== "undefined" ) {
1331
+ if ( prop ) {
1332
+ prop = $.mobile.nsNormalize( prop );
1333
+ }
1334
+
1335
+ // undefined is permitted as an explicit input for the second param
1336
+ // in this case it returns the value and does not set it to undefined
1337
+ if ( arguments.length < 2 || value === undefined ) {
1338
+ result = this.data( prop );
1339
+ } else {
1340
+ result = this.data( prop, value );
1341
+ }
1342
+ }
1343
+ return result;
1344
+ };
1345
+
1346
+ $.jqmData = function( elem, prop, value ) {
1347
+ var result;
1348
+ if ( typeof prop !== "undefined" ) {
1349
+ result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value );
1350
+ }
1351
+ return result;
1352
+ };
1353
+
1354
+ $.fn.jqmRemoveData = function( prop ) {
1355
+ return this.removeData( $.mobile.nsNormalize( prop ) );
1356
+ };
1357
+
1358
+ $.jqmRemoveData = function( elem, prop ) {
1359
+ return $.removeData( elem, $.mobile.nsNormalize( prop ) );
1360
+ };
1361
+
1362
+ $.find = function( selector, context, ret, extra ) {
1363
+ if ( selector.indexOf( ":jqmData" ) > -1 ) {
1364
+ selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
1365
+ }
1366
+
1367
+ return oldFind.call( this, selector, context, ret, extra );
1368
+ };
1369
+
1370
+ $.extend( $.find, oldFind );
1371
+
1372
+ })( jQuery, this );
1373
+
1319
1374
  (function( $, undefined ) {
1320
1375
 
1321
1376
  var rcapitals = /[A-Z]/g,
@@ -1458,8 +1513,11 @@ $.mobile.widget = $.Widget;
1458
1513
  this.element.find( "h1" ).text( message );
1459
1514
  }
1460
1515
 
1461
- // attach the loader to the DOM
1462
- this.element.appendTo( $.mobile.pageContainer );
1516
+ // If the pagecontainer widget has been defined we may use the :mobile-pagecontainer
1517
+ // and attach to the element on which the pagecontainer widget has been defined. If not,
1518
+ // we attach to the body.
1519
+ this.element.appendTo( $.mobile.pagecontainer ?
1520
+ $( ":mobile-pagecontainer" ) : $( "body" ) );
1463
1521
 
1464
1522
  // check that the loader is visible
1465
1523
  this.checkLoaderPosition();
@@ -1475,8 +1533,8 @@ $.mobile.widget = $.Widget;
1475
1533
  this.element.removeClass( "ui-loader-fakefix" );
1476
1534
  }
1477
1535
 
1478
- $.mobile.window.unbind( "scroll", this.fakeFixLoader );
1479
- $.mobile.window.unbind( "scroll", this.checkLoaderPosition );
1536
+ this.window.unbind( "scroll", this.fakeFixLoader );
1537
+ this.window.unbind( "scroll", this.checkLoaderPosition );
1480
1538
  }
1481
1539
  });
1482
1540
 
@@ -1855,7 +1913,7 @@ $.mobile.widget = $.Widget;
1855
1913
  iframe_doc.open();
1856
1914
 
1857
1915
  // Set document.domain for the Iframe document as well, if necessary.
1858
- domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
1916
+ domain && iframe_doc.write( '\x3cscript>document.domain="' + domain + '"\x3c/script>' );
1859
1917
 
1860
1918
  iframe_doc.close();
1861
1919
 
@@ -1874,6 +1932,7 @@ $.mobile.widget = $.Widget;
1874
1932
 
1875
1933
  })(jQuery,this);
1876
1934
 
1935
+
1877
1936
  (function( $, undefined ) {
1878
1937
 
1879
1938
  /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
@@ -2326,19 +2385,34 @@ if ( !$.support.boxShadow ) {
2326
2385
  urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
2327
2386
 
2328
2387
  // Abstraction to address xss (Issue #4787) by removing the authority in
2329
- // browsers that auto decode it. All references to location.href should be
2388
+ // browsers that auto-decode it. All references to location.href should be
2330
2389
  // replaced with a call to this method so that it can be dealt with properly here
2331
2390
  getLocation: function( url ) {
2332
- var uri = url ? this.parseUrl( url ) : location,
2333
- hash = this.parseUrl( url || location.href ).hash;
2391
+ var parsedUrl = this.parseUrl( url || location.href ),
2392
+ uri = url ? parsedUrl : location,
2393
+
2394
+ // Make sure to parse the url or the location object for the hash because using
2395
+ // location.hash is autodecoded in firefox, the rest of the url should be from
2396
+ // the object (location unless we're testing) to avoid the inclusion of the
2397
+ // authority
2398
+ hash = parsedUrl.hash;
2334
2399
 
2335
2400
  // mimic the browser with an empty string when the hash is empty
2336
2401
  hash = hash === "#" ? "" : hash;
2337
2402
 
2338
- // Make sure to parse the url or the location object for the hash because using location.hash
2339
- // is autodecoded in firefox, the rest of the url should be from the object (location unless
2340
- // we're testing) to avoid the inclusion of the authority
2341
- return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
2403
+ return uri.protocol +
2404
+ parsedUrl.doubleSlash +
2405
+ uri.host +
2406
+
2407
+ // The pathname must start with a slash if there's a protocol, because you
2408
+ // can't have a protocol followed by a relative path. Also, it's impossible to
2409
+ // calculate absolute URLs from relative ones if the absolute one doesn't have
2410
+ // a leading "/".
2411
+ ( ( uri.protocol !== "" && uri.pathname.substring( 0, 1 ) !== "/" ) ?
2412
+ "/" : "" ) +
2413
+ uri.pathname +
2414
+ uri.search +
2415
+ hash;
2342
2416
  },
2343
2417
 
2344
2418
  //return the original document url
@@ -2424,7 +2498,8 @@ if ( !$.support.boxShadow ) {
2424
2498
 
2425
2499
  //Returns true if both urls have the same domain.
2426
2500
  isSameDomain: function( absUrl1, absUrl2 ) {
2427
- return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
2501
+ return path.parseUrl( absUrl1 ).domain.toLowerCase() ===
2502
+ path.parseUrl( absUrl2 ).domain.toLowerCase();
2428
2503
  },
2429
2504
 
2430
2505
  //Returns true for any relative variant.
@@ -2471,19 +2546,21 @@ if ( !$.support.boxShadow ) {
2471
2546
  },
2472
2547
 
2473
2548
  convertUrlToDataUrl: function( absUrl ) {
2474
- var u = path.parseUrl( absUrl );
2549
+ var result = absUrl,
2550
+ u = path.parseUrl( absUrl );
2551
+
2475
2552
  if ( path.isEmbeddedPage( u ) ) {
2476
2553
  // For embedded pages, remove the dialog hash key as in getFilePath(),
2477
2554
  // and remove otherwise the Data Url won't match the id of the embedded Page.
2478
- return u.hash
2555
+ result = u.hash
2479
2556
  .split( dialogHashKey )[0]
2480
2557
  .replace( /^#/, "" )
2481
2558
  .replace( /\?.*$/, "" );
2482
2559
  } else if ( path.isSameDomain( u, this.documentBase ) ) {
2483
- return u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0];
2560
+ result = u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0];
2484
2561
  }
2485
2562
 
2486
- return window.decodeURIComponent(absUrl);
2563
+ return window.decodeURIComponent( result );
2487
2564
  },
2488
2565
 
2489
2566
  //get path from current hash, or from a file path
@@ -2532,7 +2609,9 @@ if ( !$.support.boxShadow ) {
2532
2609
  //could be mailto, etc
2533
2610
  isExternal: function( url ) {
2534
2611
  var u = path.parseUrl( url );
2535
- return u.protocol && u.domain !== this.documentUrl.domain ? true : false;
2612
+
2613
+ return !!( u.protocol &&
2614
+ ( u.domain.toLowerCase() !== this.documentUrl.domain.toLowerCase() ) );
2536
2615
  },
2537
2616
 
2538
2617
  hasProtocol: function( url ) {
@@ -2554,14 +2633,25 @@ if ( !$.support.boxShadow ) {
2554
2633
  },
2555
2634
 
2556
2635
  squash: function( url, resolutionUrl ) {
2557
- var href, cleanedUrl, search, stateIndex,
2636
+ var href, cleanedUrl, search, stateIndex, docUrl,
2558
2637
  isPath = this.isPath( url ),
2559
2638
  uri = this.parseUrl( url ),
2560
2639
  preservedHash = uri.hash,
2561
2640
  uiState = "";
2562
2641
 
2563
- // produce a url against which we can resole the provided path
2564
- resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl());
2642
+ // produce a url against which we can resolve the provided path
2643
+ if ( !resolutionUrl ) {
2644
+ if ( isPath ) {
2645
+ resolutionUrl = path.getLocation();
2646
+ } else {
2647
+ docUrl = path.getDocumentUrl( true );
2648
+ if ( path.isPath( docUrl.hash ) ) {
2649
+ resolutionUrl = path.squash( docUrl.href );
2650
+ } else {
2651
+ resolutionUrl = docUrl.href;
2652
+ }
2653
+ }
2654
+ }
2565
2655
 
2566
2656
  // If the url is anything but a simple string, remove any preceding hash
2567
2657
  // eg #foo/bar -> foo/bar
@@ -2608,7 +2698,8 @@ if ( !$.support.boxShadow ) {
2608
2698
 
2609
2699
  // reconstruct each of the pieces with the new search string and hash
2610
2700
  href = path.parseUrl( href );
2611
- href = href.protocol + "//" + href.host + href.pathname + search + preservedHash;
2701
+ href = href.protocol + href.doubleSlash + href.host + href.pathname + search +
2702
+ preservedHash;
2612
2703
  } else {
2613
2704
  href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState;
2614
2705
  }
@@ -2629,11 +2720,10 @@ if ( !$.support.boxShadow ) {
2629
2720
  return ( hasHash ? "#" : "" ) + hash.replace( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$1" );
2630
2721
  },
2631
2722
 
2632
- // return the substring of a filepath before the sub-page key, for making
2633
- // a server request
2723
+ // return the substring of a filepath before the dialogHashKey, for making a server
2724
+ // request
2634
2725
  getFilePath: function( path ) {
2635
- var splitkey = "&" + $.mobile.subPageUrlKey;
2636
- return path && path.split( splitkey )[0].split( dialogHashKey )[0];
2726
+ return path && path.split( dialogHashKey )[0];
2637
2727
  },
2638
2728
 
2639
2729
  // check if the specified url refers to the first page in the main
@@ -3155,7 +3245,6 @@ if ( !$.support.boxShadow ) {
3155
3245
 
3156
3246
  // All lower case if not a vendor prop
3157
3247
  if ( props[ test ][ "prefix" ] === "" ) {
3158
- props[ test ][ "duration" ] = props[ test ][ "duration" ].toLowerCase();
3159
3248
  props[ test ][ "event" ] = props[ test ][ "event" ].toLowerCase();
3160
3249
  }
3161
3250
  });
@@ -3171,6 +3260,12 @@ if ( !$.support.boxShadow ) {
3171
3260
  $.fn.animationComplete = function( callback, type, fallbackTime ) {
3172
3261
  var timer, duration,
3173
3262
  that = this,
3263
+ eventBinding = function() {
3264
+
3265
+ // Clear the timer so we don't call callback twice
3266
+ clearTimeout( timer );
3267
+ callback.apply( this, arguments );
3268
+ },
3174
3269
  animationType = ( !type || type === "animation" ) ? "animation" : "transition";
3175
3270
 
3176
3271
  // Make sure selected type is supported by browser
@@ -3191,24 +3286,19 @@ if ( !$.support.boxShadow ) {
3191
3286
  }
3192
3287
 
3193
3288
  // If we could not read a duration use the default
3194
- if ( duration === 0 || duration === undefined ) {
3195
- duration = $.fn.animationComplete.default;
3289
+ if ( duration === 0 || duration === undefined || isNaN( duration ) ) {
3290
+ duration = $.fn.animationComplete.defaultDuration;
3196
3291
  }
3197
3292
  }
3198
3293
 
3199
3294
  // Sets up the fallback if event never comes
3200
3295
  timer = setTimeout( function() {
3201
- $( that ).off( props[ animationType ].event );
3296
+ $( that ).off( props[ animationType ].event, eventBinding );
3202
3297
  callback.apply( that );
3203
3298
  }, duration );
3204
3299
 
3205
3300
  // Bind the event
3206
- return $( this ).one( props[ animationType ].event, function() {
3207
-
3208
- // Clear the timer so we dont call callback twice
3209
- clearTimeout( timer );
3210
- callback.call( this, arguments );
3211
- });
3301
+ return $( this ).one( props[ animationType ].event, eventBinding );
3212
3302
  } else {
3213
3303
 
3214
3304
  // CSS animation / transitions not supported
@@ -3219,7 +3309,7 @@ if ( !$.support.boxShadow ) {
3219
3309
  };
3220
3310
 
3221
3311
  // Allow default callback to be configured on mobileInit
3222
- $.fn.animationComplete.default = 1000;
3312
+ $.fn.animationComplete.defaultDuration = 1000;
3223
3313
  })( jQuery );
3224
3314
 
3225
3315
  // This plugin is an experiment for abstracting away the touch and mouse
@@ -3837,7 +3927,7 @@ if ( eventCaptureSupported ) {
3837
3927
  if ( !isTaphold && origTarget === event.target ) {
3838
3928
  triggerCustomEvent( thisObject, "tap", event );
3839
3929
  } else if ( isTaphold ) {
3840
- event.stopPropagation();
3930
+ event.preventDefault();
3841
3931
  }
3842
3932
  }
3843
3933
 
@@ -3970,7 +4060,7 @@ if ( eventCaptureSupported ) {
3970
4060
  emitted = false;
3971
4061
 
3972
4062
  context.move = function( event ) {
3973
- if ( !start ) {
4063
+ if ( !start || event.isDefaultPrevented() ) {
3974
4064
  return;
3975
4065
  }
3976
4066
 
@@ -4033,8 +4123,8 @@ if ( eventCaptureSupported ) {
4033
4123
  $.each({
4034
4124
  scrollstop: "scrollstart",
4035
4125
  taphold: "tap",
4036
- swipeleft: "swipe",
4037
- swiperight: "swipe"
4126
+ swipeleft: "swipe.left",
4127
+ swiperight: "swipe.right"
4038
4128
  }, function( event, sourceEvent ) {
4039
4129
 
4040
4130
  $.event.special[ event ] = {
@@ -4270,13 +4360,14 @@ if ( eventCaptureSupported ) {
4270
4360
  page.find( base.linkSelector ).each(function( i, link ) {
4271
4361
  var thisAttr = $( link ).is( "[href]" ) ? "href" :
4272
4362
  $( link ).is( "[src]" ) ? "src" : "action",
4363
+ theLocation = $.mobile.path.parseLocation(),
4273
4364
  thisUrl = $( link ).attr( thisAttr );
4274
4365
 
4275
4366
  // XXX_jblas: We need to fix this so that it removes the document
4276
4367
  // base URL, and then prepends with the new page URL.
4277
4368
  // if full path exists and is same, chop it - helps IE out
4278
- thisUrl = thisUrl.replace( location.protocol + "//" +
4279
- location.host + location.pathname, "" );
4369
+ thisUrl = thisUrl.replace( theLocation.protocol + theLocation.doubleSlash +
4370
+ theLocation.host + theLocation.pathname, "" );
4280
4371
 
4281
4372
  if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
4282
4373
  $( link ).attr( thisAttr, newPath + thisUrl );
@@ -4476,13 +4567,9 @@ $.widget( "mobile.page", {
4476
4567
  initSelector: false,
4477
4568
 
4478
4569
  _create: function() {
4570
+ this._trigger( "beforecreate" );
4479
4571
  this.setLastScrollEnabled = true;
4480
4572
 
4481
- // TODO consider moving the navigation handler OUT of widget into
4482
- // some other object as glue between the navigate event and the
4483
- // content widget load and change methods
4484
- this._on( this.window, { navigate: "_filterNavigateEvents" });
4485
-
4486
4573
  this._on( this.window, {
4487
4574
  // disable an scroll setting when a hashchange has been fired,
4488
4575
  // this only works because the recording of the scroll position
@@ -4495,6 +4582,11 @@ $.widget( "mobile.page", {
4495
4582
  scrollstop: "_delayedRecordScroll"
4496
4583
  });
4497
4584
 
4585
+ // TODO consider moving the navigation handler OUT of widget into
4586
+ // some other object as glue between the navigate event and the
4587
+ // content widget load and change methods
4588
+ this._on( this.window, { navigate: "_filterNavigateEvents" });
4589
+
4498
4590
  // TODO move from page* events to content* events
4499
4591
  this._on({ pagechange: "_afterContentChange" });
4500
4592
 
@@ -4615,9 +4707,8 @@ $.widget( "mobile.page", {
4615
4707
  return $.mobile.navigate.history;
4616
4708
  },
4617
4709
 
4618
- // TODO use _getHistory
4619
4710
  _getActiveHistory: function() {
4620
- return $.mobile.navigate.history.getActive();
4711
+ return this._getHistory().getActive();
4621
4712
  },
4622
4713
 
4623
4714
  // TODO the document base should be determined at creation
@@ -4677,28 +4768,26 @@ $.widget( "mobile.page", {
4677
4768
  //
4678
4769
  // TODO move check to history object or path object?
4679
4770
  to = !$.mobile.path.isPath( to ) ? ( $.mobile.path.makeUrlAbsolute( "#" + to, this._getDocumentBase() ) ) : to;
4680
-
4681
- // If we're about to go to an initial URL that contains a
4682
- // reference to a non-existent internal page, go to the first
4683
- // page instead. We know that the initial hash refers to a
4684
- // non-existent page, because the initial hash did not end
4685
- // up in the initial history entry
4686
- // TODO move check to history object?
4687
- if ( to === $.mobile.path.makeUrlAbsolute( "#" + history.initialDst, this._getDocumentBase() ) &&
4688
- history.stack.length &&
4689
- history.stack[0].url !== history.initialDst.replace( $.mobile.dialogHashKey, "" ) ) {
4690
- to = this._getInitialContent();
4691
- }
4692
4771
  }
4693
4772
  return to || this._getInitialContent();
4694
4773
  },
4695
4774
 
4775
+ _transitionFromHistory: function( direction, defaultTransition ) {
4776
+ var history = this._getHistory(),
4777
+ entry = ( direction === "back" ? history.getLast() : history.getActive() );
4778
+
4779
+ return ( entry && entry.transition ) || defaultTransition;
4780
+ },
4781
+
4696
4782
  _handleDialog: function( changePageOptions, data ) {
4697
4783
  var to, active, activeContent = this.getActivePage();
4698
4784
 
4699
4785
  // If current active page is not a dialog skip the dialog and continue
4700
4786
  // in the same direction
4701
- if ( activeContent && !activeContent.hasClass( "ui-dialog" ) ) {
4787
+ // Note: The dialog widget is deprecated as of 1.4.0 and will be removed in 1.5.0.
4788
+ // Thus, as of 1.5.0 activeContent.data( "mobile-dialog" ) will always evaluate to
4789
+ // falsy, so the second condition in the if-statement below can be removed altogether.
4790
+ if ( activeContent && !activeContent.data( "mobile-dialog" ) ) {
4702
4791
  // determine if we're heading forward or backward and continue
4703
4792
  // accordingly past the current dialog
4704
4793
  if ( data.direction === "back" ) {
@@ -4719,7 +4808,9 @@ $.widget( "mobile.page", {
4719
4808
  // as most of this is lost by the domCache cleaning
4720
4809
  $.extend( changePageOptions, {
4721
4810
  role: active.role,
4722
- transition: active.transition,
4811
+ transition: this._transitionFromHistory(
4812
+ data.direction,
4813
+ changePageOptions.transition ),
4723
4814
  reverse: data.direction === "back"
4724
4815
  });
4725
4816
  }
@@ -4734,7 +4825,8 @@ $.widget( "mobile.page", {
4734
4825
 
4735
4826
  // transition is false if it's the first page, undefined
4736
4827
  // otherwise (and may be overridden by default)
4737
- transition = history.stack.length === 0 ? "none" : undefined,
4828
+ transition = history.stack.length === 0 ? "none" :
4829
+ this._transitionFromHistory( data.direction ),
4738
4830
 
4739
4831
  // default options for the changPage calls made after examining
4740
4832
  // the current state of the page and the hash, NOTE that the
@@ -4746,7 +4838,7 @@ $.widget( "mobile.page", {
4746
4838
  };
4747
4839
 
4748
4840
  $.extend( changePageOptions, data, {
4749
- transition: ( history.getLast() || {} ).transition || transition
4841
+ transition: transition
4750
4842
  });
4751
4843
 
4752
4844
  // TODO move to _handleDestination ?
@@ -4754,8 +4846,7 @@ $.widget( "mobile.page", {
4754
4846
  // key, and the initial destination isn't equal to the current target
4755
4847
  // page, use the special dialog handling
4756
4848
  if ( history.activeIndex > 0 &&
4757
- to.indexOf( $.mobile.dialogHashKey ) > -1 &&
4758
- history.initialDst !== to ) {
4849
+ to.indexOf( $.mobile.dialogHashKey ) > -1 ) {
4759
4850
 
4760
4851
  to = this._handleDialog( changePageOptions, data );
4761
4852
 
@@ -4806,7 +4897,8 @@ $.widget( "mobile.page", {
4806
4897
  // NOTE do _not_ use the :jqmData pseudo selector because parenthesis
4807
4898
  // are a valid url char and it breaks on the first occurence
4808
4899
  page = this.element
4809
- .children( "[data-" + this._getNs() +"url='" + dataUrl + "']" );
4900
+ .children( "[data-" + this._getNs() +
4901
+ "url='" + $.mobile.path.hashToSelector( dataUrl ) + "']" );
4810
4902
 
4811
4903
  // If we failed to find the page, check to see if the url is a
4812
4904
  // reference to an embedded page. If so, it may have been dynamically
@@ -4891,7 +4983,7 @@ $.widget( "mobile.page", {
4891
4983
  // TODO tagging a page with external to make sure that embedded pages aren't
4892
4984
  // removed by the various page handling code is bad. Having page handling code
4893
4985
  // in many places is bad. Solutions post 1.0
4894
- page.attr( "data-" + this._getNs() + "url", $.mobile.path.convertUrlToDataUrl(fileUrl) )
4986
+ page.attr( "data-" + this._getNs() + "url", this._createDataUrl( fileUrl ) )
4895
4987
  .attr( "data-" + this._getNs() + "external-page", true );
4896
4988
 
4897
4989
  return page;
@@ -4928,7 +5020,7 @@ $.widget( "mobile.page", {
4928
5020
  ( page || this.element ).trigger( deprecatedEvent, data );
4929
5021
 
4930
5022
  // use the widget trigger method for the new content* event
4931
- this.element.trigger( newEvent, data );
5023
+ this._trigger( name, newEvent, data );
4932
5024
 
4933
5025
  return {
4934
5026
  deprecatedEvent: deprecatedEvent,
@@ -4940,8 +5032,7 @@ $.widget( "mobile.page", {
4940
5032
  // or require ordering such that other bits are sprinkled in between parts that
4941
5033
  // could be abstracted out as a group
4942
5034
  _loadSuccess: function( absUrl, triggerData, settings, deferred ) {
4943
- var fileUrl = this._createFileUrl( absUrl ),
4944
- dataUrl = this._createDataUrl( absUrl );
5035
+ var fileUrl = this._createFileUrl( absUrl );
4945
5036
 
4946
5037
  return $.proxy(function( html, textStatus, xhr ) {
4947
5038
  //pre-parse html to check for a data-url,
@@ -4961,6 +5052,11 @@ $.widget( "mobile.page", {
4961
5052
  dataUrlRegex.test( RegExp.$1 ) &&
4962
5053
  RegExp.$1 ) {
4963
5054
  fileUrl = $.mobile.path.getFilePath( $("<div>" + RegExp.$1 + "</div>").text() );
5055
+
5056
+ // We specify that, if a data-url attribute is given on the page div, its value
5057
+ // must be given non-URL-encoded. However, in this part of the code, fileUrl is
5058
+ // assumed to be URL-encoded, so we URL-encode the retrieved value here
5059
+ fileUrl = this.window[ 0 ].encodeURIComponent( fileUrl );
4964
5060
  }
4965
5061
 
4966
5062
  //dont update the base tag if we are prefetching
@@ -4981,11 +5077,13 @@ $.widget( "mobile.page", {
4981
5077
 
4982
5078
  triggerData.content = content;
4983
5079
 
5080
+ triggerData.toPage = content;
5081
+
4984
5082
  // If the default behavior is prevented, stop here!
4985
5083
  // Note that it is the responsibility of the listener/handler
4986
5084
  // that called preventDefault(), to resolve/reject the
4987
5085
  // deferred object within the triggerData.
4988
- if ( !this._trigger( "load", undefined, triggerData ) ) {
5086
+ if ( this._triggerWithDeprecated( "load", triggerData ).event.isDefaultPrevented() ) {
4989
5087
  return;
4990
5088
  }
4991
5089
 
@@ -4996,23 +5094,11 @@ $.widget( "mobile.page", {
4996
5094
 
4997
5095
  this._include( content, settings );
4998
5096
 
4999
- // Enhancing the content may result in new dialogs/sub content being inserted
5000
- // into the DOM. If the original absUrl refers to a sub-content, that is the
5001
- // real content we are interested in.
5002
- if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
5003
- content = this.element.children( "[data-" + this._getNs() +"url='" + dataUrl + "']" );
5004
- }
5005
-
5006
5097
  // Remove loading message.
5007
5098
  if ( settings.showLoadMsg ) {
5008
5099
  this._hideLoading();
5009
5100
  }
5010
5101
 
5011
- // BEGIN DEPRECATED ---------------------------------------------------
5012
- // Let listeners know the content loaded successfully.
5013
- this.element.trigger( "pageload" );
5014
- // END DEPRECATED -----------------------------------------------------
5015
-
5016
5102
  deferred.resolve( absUrl, settings, content );
5017
5103
  }, this);
5018
5104
  },
@@ -5041,8 +5127,16 @@ $.widget( "mobile.page", {
5041
5127
  // know when the content is done loading, or if an error has occurred.
5042
5128
  var deferred = ( options && options.deferred ) || $.Deferred(),
5043
5129
 
5130
+ // Examining the option "reloadPage" passed by the user is deprecated as of 1.4.0
5131
+ // and will be removed in 1.5.0.
5132
+ // Copy option "reloadPage" to "reload", but only if option "reload" is not present
5133
+ reloadOptionExtension =
5134
+ ( ( options && options.reload === undefined &&
5135
+ options.reloadPage !== undefined ) ?
5136
+ { reload: options.reloadPage } : {} ),
5137
+
5044
5138
  // The default load options with overrides specified by the caller.
5045
- settings = $.extend( {}, this._loadDefaults, options ),
5139
+ settings = $.extend( {}, this._loadDefaults, options, reloadOptionExtension ),
5046
5140
 
5047
5141
  // The DOM element for the content after it has been loaded.
5048
5142
  content = null,
@@ -5052,9 +5146,6 @@ $.widget( "mobile.page", {
5052
5146
  absUrl = $.mobile.path.makeUrlAbsolute( url, this._findBaseWithDefault() ),
5053
5147
  fileUrl, dataUrl, pblEvent, triggerData;
5054
5148
 
5055
- // DEPRECATED reloadPage
5056
- settings.reload = settings.reloadPage;
5057
-
5058
5149
  // If the caller provided data, and we're using "get" request,
5059
5150
  // append the data to the URL.
5060
5151
  if ( settings.data && settings.type === "get" ) {
@@ -5086,7 +5177,7 @@ $.widget( "mobile.page", {
5086
5177
  $.mobile.path.isEmbeddedPage(fileUrl) &&
5087
5178
  !$.mobile.path.isFirstPageUrl(fileUrl) ) {
5088
5179
  deferred.reject( absUrl, settings );
5089
- return;
5180
+ return deferred.promise();
5090
5181
  }
5091
5182
 
5092
5183
  // Reset base to the default document base
@@ -5107,12 +5198,14 @@ $.widget( "mobile.page", {
5107
5198
  this._getBase().set(url);
5108
5199
  }
5109
5200
 
5110
- return;
5201
+ return deferred.promise();
5111
5202
  }
5112
5203
 
5113
5204
  triggerData = {
5114
5205
  url: url,
5115
5206
  absUrl: absUrl,
5207
+ toPage: url,
5208
+ prevPage: options ? options.fromPage : undefined,
5116
5209
  dataUrl: dataUrl,
5117
5210
  deferred: deferred,
5118
5211
  options: settings
@@ -5124,7 +5217,7 @@ $.widget( "mobile.page", {
5124
5217
  // If the default behavior is prevented, stop here!
5125
5218
  if ( pblEvent.deprecatedEvent.isDefaultPrevented() ||
5126
5219
  pblEvent.event.isDefaultPrevented() ) {
5127
- return;
5220
+ return deferred.promise();
5128
5221
  }
5129
5222
 
5130
5223
  if ( settings.showLoadMsg ) {
@@ -5140,7 +5233,7 @@ $.widget( "mobile.page", {
5140
5233
  if ( !( $.mobile.allowCrossDomainPages ||
5141
5234
  $.mobile.path.isSameDomain($.mobile.path.documentUrl, absUrl ) ) ) {
5142
5235
  deferred.reject( absUrl, settings );
5143
- return;
5236
+ return deferred.promise();
5144
5237
  }
5145
5238
 
5146
5239
  // Load the new content.
@@ -5153,6 +5246,8 @@ $.widget( "mobile.page", {
5153
5246
  success: this._loadSuccess( absUrl, triggerData, settings, deferred ),
5154
5247
  error: this._loadError( absUrl, triggerData, settings, deferred )
5155
5248
  });
5249
+
5250
+ return deferred.promise();
5156
5251
  },
5157
5252
 
5158
5253
  _loadError: function( absUrl, triggerData, settings, deferred ) {
@@ -5211,11 +5306,21 @@ $.widget( "mobile.page", {
5211
5306
 
5212
5307
  //trigger before show/hide events
5213
5308
  // TODO deprecate nextPage in favor of next
5214
- this._triggerWithDeprecated( prefix + "hide", { nextPage: to, samePage: samePage }, from );
5309
+ this._triggerWithDeprecated( prefix + "hide", {
5310
+
5311
+ // Deprecated in 1.4 remove in 1.5
5312
+ nextPage: to,
5313
+ toPage: to,
5314
+ prevPage: from,
5315
+ samePage: samePage
5316
+ }, from );
5215
5317
  }
5216
5318
 
5217
5319
  // TODO deprecate prevPage in favor of previous
5218
- this._triggerWithDeprecated( prefix + "show", { prevPage: from || $( "" ) }, to );
5320
+ this._triggerWithDeprecated( prefix + "show", {
5321
+ prevPage: from || $( "" ),
5322
+ toPage: to
5323
+ }, to );
5219
5324
  },
5220
5325
 
5221
5326
  // TODO make private once change has been defined in the widget
@@ -5235,14 +5340,14 @@ $.widget( "mobile.page", {
5235
5340
 
5236
5341
  promise = ( new TransitionHandler( transition, reverse, to, from ) ).transition();
5237
5342
 
5343
+ promise.done( $.proxy( function() {
5344
+ this._triggerCssTransitionEvents( to, from );
5345
+ }, this ));
5346
+
5238
5347
  // TODO temporary accomodation of argument deferred
5239
5348
  promise.done(function() {
5240
5349
  deferred.resolve.apply( deferred, arguments );
5241
5350
  });
5242
-
5243
- promise.done($.proxy(function() {
5244
- this._triggerCssTransitionEvents( to, from );
5245
- }, this));
5246
5351
  },
5247
5352
 
5248
5353
  _releaseTransitionLock: function() {
@@ -5287,9 +5392,13 @@ $.widget( "mobile.page", {
5287
5392
  },
5288
5393
 
5289
5394
  _triggerPageBeforeChange: function( to, triggerData, settings ) {
5290
- var pbcEvent = new $.Event( "pagebeforechange" );
5395
+ var returnEvents;
5291
5396
 
5292
- $.extend(triggerData, { toPage: to, options: settings });
5397
+ triggerData.prevPage = this.activePage;
5398
+ $.extend( triggerData, {
5399
+ toPage: to,
5400
+ options: settings
5401
+ });
5293
5402
 
5294
5403
  // NOTE: preserve the original target as the dataUrl value will be
5295
5404
  // simplified eg, removing ui-state, and removing query params from
@@ -5306,10 +5415,11 @@ $.widget( "mobile.page", {
5306
5415
  }
5307
5416
 
5308
5417
  // Let listeners know we're about to change the current page.
5309
- this.element.trigger( pbcEvent, triggerData );
5418
+ returnEvents = this._triggerWithDeprecated( "beforechange", triggerData );
5310
5419
 
5311
5420
  // If the default behavior is prevented, stop here!
5312
- if ( pbcEvent.isDefaultPrevented() ) {
5421
+ if ( returnEvents.event.isDefaultPrevented() ||
5422
+ returnEvents.deprecatedEvent.isDefaultPrevented() ) {
5313
5423
  return false;
5314
5424
  }
5315
5425
 
@@ -5382,6 +5492,7 @@ $.widget( "mobile.page", {
5382
5492
  return;
5383
5493
  }
5384
5494
 
5495
+ triggerData.prevPage = settings.fromPage;
5385
5496
  // if the (content|page)beforetransition default is prevented return early
5386
5497
  // Note, we have to check for both the deprecated and new events
5387
5498
  beforeTransition = this._triggerWithDeprecated( "beforetransition", triggerData );
@@ -5437,7 +5548,7 @@ $.widget( "mobile.page", {
5437
5548
 
5438
5549
  isPageTransitioning = false;
5439
5550
  this._triggerWithDeprecated( "transition", triggerData );
5440
- this.element.trigger( "pagechange", triggerData );
5551
+ this._triggerWithDeprecated( "change", triggerData );
5441
5552
 
5442
5553
  // Even if there is no page change to be done, we should keep the
5443
5554
  // urlHistory in sync with the hash changes
@@ -5515,12 +5626,6 @@ $.widget( "mobile.page", {
5515
5626
  } else {
5516
5627
  url += "#" + $.mobile.dialogHashKey;
5517
5628
  }
5518
-
5519
- // tack on another dialogHashKey if this is the same as the initial hash
5520
- // this makes sure that a history entry is created for this dialog
5521
- if ( $.mobile.navigate.history.activeIndex === 0 && url === $.mobile.navigate.history.initialDst ) {
5522
- url += $.mobile.dialogHashKey;
5523
- }
5524
5629
  }
5525
5630
 
5526
5631
  // if title element wasn't found, try the page div data attr too
@@ -5563,7 +5668,7 @@ $.widget( "mobile.page", {
5563
5668
  };
5564
5669
 
5565
5670
  if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) {
5566
- $.mobile.navigate( url, params, true);
5671
+ $.mobile.navigate( this.window[ 0 ].encodeURI( url ), params, true);
5567
5672
  } else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) {
5568
5673
  $.mobile.navigate.history.add( url, params );
5569
5674
  }
@@ -5604,8 +5709,8 @@ $.widget( "mobile.page", {
5604
5709
  }
5605
5710
 
5606
5711
  this._releaseTransitionLock();
5607
- this.element.trigger( "pagechange", triggerData );
5608
5712
  this._triggerWithDeprecated( "transition", triggerData );
5713
+ this._triggerWithDeprecated( "change", triggerData );
5609
5714
  }, this));
5610
5715
  },
5611
5716
 
@@ -5634,6 +5739,18 @@ $.widget( "mobile.page", {
5634
5739
 
5635
5740
  // resolved on domready
5636
5741
  var domreadyDeferred = $.Deferred(),
5742
+
5743
+ // resolved and nulled on window.load()
5744
+ loadDeferred = $.Deferred(),
5745
+
5746
+ // function that resolves the above deferred
5747
+ pageIsFullyLoaded = function() {
5748
+
5749
+ // Resolve and null the deferred
5750
+ loadDeferred.resolve();
5751
+ loadDeferred = null;
5752
+ },
5753
+
5637
5754
  documentUrl = $.mobile.path.documentUrl,
5638
5755
 
5639
5756
  // used to track last vclicked element to make sure its value is added to form data
@@ -5964,9 +6081,10 @@ $.widget( "mobile.page", {
5964
6081
  // lists and select dialogs, just write a hash in the link they
5965
6082
  // create. This means the actual URL path is based on whatever
5966
6083
  // the current value of the base tag is at the time this code
5967
- // is called. For now we are just assuming that any url with a
5968
- // hash in it is an application page reference.
5969
- if ( href.search( "#" ) !== -1 ) {
6084
+ // is called.
6085
+ if ( href.search( "#" ) !== -1 &&
6086
+ !( $.mobile.path.isExternal( href ) && $.mobile.path.isAbsoluteUrl( href ) ) ) {
6087
+
5970
6088
  href = href.replace( /[^#]*#/, "" );
5971
6089
  if ( !href ) {
5972
6090
  //link was an empty hash meant purely
@@ -6035,13 +6153,29 @@ $.widget( "mobile.page", {
6035
6153
  $.mobile.pageContainer.pagecontainer();
6036
6154
 
6037
6155
  //set page min-heights to be device specific
6038
- $.mobile.document.bind( "pageshow", $.mobile.resetActivePageHeight );
6156
+ $.mobile.document.bind( "pageshow", function() {
6157
+
6158
+ // We need to wait for window.load to make sure that styles have already been rendered,
6159
+ // otherwise heights of external toolbars will have the wrong value
6160
+ if ( loadDeferred ) {
6161
+ loadDeferred.done( $.mobile.resetActivePageHeight );
6162
+ } else {
6163
+ $.mobile.resetActivePageHeight();
6164
+ }
6165
+ });
6039
6166
  $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );
6040
6167
 
6041
6168
  };//navreadyDeferred done callback
6042
6169
 
6043
6170
  $( function() { domreadyDeferred.resolve(); } );
6044
6171
 
6172
+ // Account for the possibility that the load event has already fired
6173
+ if ( document.readyState === "complete" ) {
6174
+ pageIsFullyLoaded();
6175
+ } else {
6176
+ $.mobile.window.load( pageIsFullyLoaded );
6177
+ }
6178
+
6045
6179
  $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } );
6046
6180
  })( jQuery );
6047
6181
 
@@ -6174,12 +6308,19 @@ $.widget( "mobile.page", {
6174
6308
  // better transitions with fewer bugs. Ie, it's not guaranteed that the
6175
6309
  // object will be created and transition will be run immediately after as
6176
6310
  // it is today. So we wait until transition is invoked to gather the following
6177
- var reverseClass = this.reverse ? " reverse" : "",
6311
+ var none,
6312
+ reverseClass = this.reverse ? " reverse" : "",
6178
6313
  screenHeight = $.mobile.getScreenHeight(),
6179
- maxTransitionOverride = $.mobile.maxTransitionWidth !== false && $.mobile.window.width() > $.mobile.maxTransitionWidth,
6180
- none = !$.support.cssTransitions || !$.support.cssAnimations || maxTransitionOverride || !this.name || this.name === "none" || Math.max( $.mobile.window.scrollTop(), this.toScroll ) > $.mobile.getMaxScrollForTransition();
6314
+ maxTransitionOverride = $.mobile.maxTransitionWidth !== false &&
6315
+ $.mobile.window.width() > $.mobile.maxTransitionWidth;
6181
6316
 
6182
6317
  this.toScroll = $.mobile.navigate.history.getActive().lastScroll || $.mobile.defaultHomeScroll;
6318
+
6319
+ none = !$.support.cssTransitions || !$.support.cssAnimations ||
6320
+ maxTransitionOverride || !this.name || this.name === "none" ||
6321
+ Math.max( $.mobile.window.scrollTop(), this.toScroll ) >
6322
+ $.mobile.getMaxScrollForTransition();
6323
+
6183
6324
  this.toggleViewportClass();
6184
6325
 
6185
6326
  if ( this.$from && !none ) {
@@ -6724,9 +6865,9 @@ $.widget( "mobile.collapsible", {
6724
6865
  this._renderedOptions = this._getOptions( this.options );
6725
6866
 
6726
6867
  if ( this.options.enhanced ) {
6727
- ui.heading = $( ".ui-collapsible-heading", this.element[ 0 ] );
6868
+ ui.heading = this.element.children( ".ui-collapsible-heading" );
6728
6869
  ui.content = ui.heading.next();
6729
- ui.anchor = $( "a", ui.heading[ 0 ] ).first();
6870
+ ui.anchor = ui.heading.children();
6730
6871
  ui.status = ui.anchor.children( ".ui-collapsible-heading-status" );
6731
6872
  } else {
6732
6873
  this._enhance( elem, ui );
@@ -6840,7 +6981,7 @@ $.widget( "mobile.collapsible", {
6840
6981
  },
6841
6982
 
6842
6983
  _applyOptions: function( options ) {
6843
- var isCollapsed, newTheme, oldTheme, hasCorners,
6984
+ var isCollapsed, newTheme, oldTheme, hasCorners, hasIcon,
6844
6985
  elem = this.element,
6845
6986
  currentOpts = this._renderedOptions,
6846
6987
  ui = this._ui,
@@ -6857,38 +6998,61 @@ $.widget( "mobile.collapsible", {
6857
6998
 
6858
6999
  isCollapsed = elem.hasClass( "ui-collapsible-collapsed" );
6859
7000
 
6860
- // Only options referring to the current state need to be applied right away
6861
- // It is enough to store options covering the alternate in this.options.
7001
+ // We only need to apply the cue text for the current state right away.
7002
+ // The cue text for the alternate state will be stored in the options
7003
+ // and applied the next time the collapsible's state is toggled
6862
7004
  if ( isCollapsed ) {
6863
7005
  if ( opts.expandCueText !== undefined ) {
6864
7006
  status.text( opts.expandCueText );
6865
7007
  }
6866
- if ( opts.collapsedIcon !== undefined ) {
6867
- if ( currentOpts.collapsedIcon ) {
6868
- anchor.removeClass( "ui-icon-" + currentOpts.collapsedIcon );
6869
- }
6870
- if ( opts.collapsedIcon ) {
6871
- anchor.addClass( "ui-icon-" + opts.collapsedIcon );
6872
- }
6873
- }
6874
7008
  } else {
6875
7009
  if ( opts.collapseCueText !== undefined ) {
6876
7010
  status.text( opts.collapseCueText );
6877
7011
  }
6878
- if ( opts.expandedIcon !== undefined ) {
6879
- if ( currentOpts.expandedIcon ) {
6880
- anchor.removeClass( "ui-icon-" + currentOpts.expandedIcon );
6881
- }
6882
- if ( opts.expandedIcon ) {
6883
- anchor.addClass( "ui-icon-" + opts.expandedIcon );
6884
- }
6885
- }
6886
7012
  }
6887
7013
 
6888
- if ( opts.iconpos !== undefined ) {
6889
- anchor
6890
- .removeClass( iconposClass( currentOpts.iconpos ) )
6891
- .addClass( iconposClass( opts.iconpos ) );
7014
+ // Update icon
7015
+
7016
+ // Is it supposed to have an icon?
7017
+ hasIcon =
7018
+
7019
+ // If the collapsedIcon is being set, consult that
7020
+ ( opts.collapsedIcon !== undefined ? opts.collapsedIcon !== false :
7021
+
7022
+ // Otherwise consult the existing option value
7023
+ currentOpts.collapsedIcon !== false );
7024
+
7025
+
7026
+ // If any icon-related options have changed, make sure the new icon
7027
+ // state is reflected by first removing all icon-related classes
7028
+ // reflecting the current state and then adding all icon-related
7029
+ // classes for the new state
7030
+ if ( !( opts.iconpos === undefined &&
7031
+ opts.collapsedIcon === undefined &&
7032
+ opts.expandedIcon === undefined ) ) {
7033
+
7034
+ // Remove all current icon-related classes
7035
+ anchor.removeClass( [ iconposClass( currentOpts.iconpos ) ]
7036
+ .concat( ( currentOpts.expandedIcon ?
7037
+ [ "ui-icon-" + currentOpts.expandedIcon ] : [] ) )
7038
+ .concat( ( currentOpts.collapsedIcon ?
7039
+ [ "ui-icon-" + currentOpts.collapsedIcon ] : [] ) )
7040
+ .join( " " ) );
7041
+
7042
+ // Add new classes if an icon is supposed to be present
7043
+ if ( hasIcon ) {
7044
+ anchor.addClass(
7045
+ [ iconposClass( opts.iconpos !== undefined ?
7046
+ opts.iconpos : currentOpts.iconpos ) ]
7047
+ .concat( isCollapsed ?
7048
+ [ "ui-icon-" + ( opts.collapsedIcon !== undefined ?
7049
+ opts.collapsedIcon :
7050
+ currentOpts.collapsedIcon ) ] :
7051
+ [ "ui-icon-" + ( opts.expandedIcon !== undefined ?
7052
+ opts.expandedIcon :
7053
+ currentOpts.expandedIcon ) ] )
7054
+ .join( " " ) );
7055
+ }
6892
7056
  }
6893
7057
 
6894
7058
  if ( opts.theme !== undefined ) {
@@ -7010,16 +7174,31 @@ $.mobile.collapsible.defaults = {
7010
7174
 
7011
7175
  (function( $, undefined ) {
7012
7176
 
7177
+ var uiScreenHiddenRegex = /\bui-screen-hidden\b/;
7178
+ function noHiddenClass( elements ) {
7179
+ var index,
7180
+ length = elements.length,
7181
+ result = [];
7182
+
7183
+ for ( index = 0; index < length; index++ ) {
7184
+ if ( !elements[ index ].className.match( uiScreenHiddenRegex ) ) {
7185
+ result.push( elements[ index ] );
7186
+ }
7187
+ }
7188
+
7189
+ return $( result );
7190
+ }
7191
+
7013
7192
  $.mobile.behaviors.addFirstLastClasses = {
7014
7193
  _getVisibles: function( $els, create ) {
7015
7194
  var visibles;
7016
7195
 
7017
7196
  if ( create ) {
7018
- visibles = $els.not( ".ui-screen-hidden" );
7197
+ visibles = noHiddenClass( $els );
7019
7198
  } else {
7020
7199
  visibles = $els.filter( ":visible" );
7021
7200
  if ( visibles.length === 0 ) {
7022
- visibles = $els.not( ".ui-screen-hidden" );
7201
+ visibles = noHiddenClass( $els );
7023
7202
  }
7024
7203
  }
7025
7204
 
@@ -7222,7 +7401,7 @@ $.widget( "mobile.navbar", {
7222
7401
  _create: function() {
7223
7402
 
7224
7403
  var $navbar = this.element,
7225
- $navbtns = $navbar.find( "a" ),
7404
+ $navbtns = $navbar.find( "a, button" ),
7226
7405
  iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? this.options.iconpos : undefined;
7227
7406
 
7228
7407
  $navbar.addClass( "ui-navbar" )
@@ -7413,11 +7592,15 @@ $.widget( "mobile.listview", $.extend( {
7413
7592
  .attr( "title", $.trim( last.getEncodedText() ) )
7414
7593
  .addClass( altButtonClass )
7415
7594
  .empty();
7595
+
7596
+ // Reduce to the first anchor, because only the first gets the buttonClass
7597
+ a = a.first();
7416
7598
  } else if ( icon ) {
7417
7599
  buttonClass += " ui-btn-icon-right ui-icon-" + icon;
7418
7600
  }
7419
7601
 
7420
- a.first().addClass( buttonClass );
7602
+ // Apply buttonClass to the (first) anchor
7603
+ a.addClass( buttonClass );
7421
7604
  } else if ( isDivider ) {
7422
7605
  dividerTheme = ( getAttr( item[ 0 ], "theme" ) || o.dividerTheme || o.theme );
7423
7606
 
@@ -7460,7 +7643,7 @@ $.widget( "mobile.listview", $.extend( {
7460
7643
  $( this ).closest( "li" ).addClass( "ui-li-has-count" );
7461
7644
  });
7462
7645
  if ( countThemeClass ) {
7463
- countBubbles.addClass( countThemeClass );
7646
+ countBubbles.not( "[class*='ui-body-']" ).addClass( countThemeClass );
7464
7647
  }
7465
7648
 
7466
7649
  // Deprecated in 1.4. From 1.5 you have to add class ui-li-has-thumb or ui-li-has-icon to the LI.
@@ -7596,6 +7779,8 @@ $.mobile.behaviors.formReset = {
7596
7779
 
7597
7780
  (function( $, undefined ) {
7598
7781
 
7782
+ var escapeId = $.mobile.path.hashToSelector;
7783
+
7599
7784
  $.widget( "mobile.checkboxradio", $.extend( {
7600
7785
 
7601
7786
  initSelector: "input:not( :jqmData(role='flipswitch' ) )[type='checkbox'],input[type='radio']:not( :jqmData(role='flipswitch' ))",
@@ -7615,15 +7800,12 @@ $.widget( "mobile.checkboxradio", $.extend( {
7615
7800
  return input.jqmData( dataAttr ) ||
7616
7801
  input.closest( "form, fieldset" ).jqmData( dataAttr );
7617
7802
  },
7618
- // NOTE: Windows Phone could not find the label through a selector
7619
- // filter works though.
7620
- parentLabel = input.closest( "label" ),
7621
- label = parentLabel.length ? parentLabel :
7622
- input
7623
- .closest( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" )
7624
- .find( "label" )
7625
- .filter( "[for='" + $.mobile.path.hashToSelector( input[0].id ) + "']" )
7626
- .first(),
7803
+ label = this.options.enhanced ?
7804
+ {
7805
+ element: this.element.siblings( "label" ),
7806
+ isParent: false
7807
+ } :
7808
+ this._findLabel(),
7627
7809
  inputtype = input[0].type,
7628
7810
  checkedClass = "ui-" + inputtype + "-on",
7629
7811
  uncheckedClass = "ui-" + inputtype + "-off";
@@ -7636,7 +7818,8 @@ $.widget( "mobile.checkboxradio", $.extend( {
7636
7818
  this.options.disabled = true;
7637
7819
  }
7638
7820
 
7639
- o.iconpos = inheritAttr( input, "iconpos" ) || label.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
7821
+ o.iconpos = inheritAttr( input, "iconpos" ) ||
7822
+ label.element.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
7640
7823
 
7641
7824
  // Establish options
7642
7825
  o.mini = inheritAttr( input, "mini" ) || o.mini;
@@ -7644,8 +7827,8 @@ $.widget( "mobile.checkboxradio", $.extend( {
7644
7827
  // Expose for other methods
7645
7828
  $.extend( this, {
7646
7829
  input: input,
7647
- label: label,
7648
- parentLabel: parentLabel,
7830
+ label: label.element,
7831
+ labelIsParent: label.isParent,
7649
7832
  inputtype: inputtype,
7650
7833
  checkedClass: checkedClass,
7651
7834
  uncheckedClass: uncheckedClass
@@ -7655,7 +7838,7 @@ $.widget( "mobile.checkboxradio", $.extend( {
7655
7838
  this._enhance();
7656
7839
  }
7657
7840
 
7658
- this._on( label, {
7841
+ this._on( label.element, {
7659
7842
  vmouseover: "_handleLabelVMouseOver",
7660
7843
  vclick: "_handleLabelVClick"
7661
7844
  });
@@ -7671,10 +7854,36 @@ $.widget( "mobile.checkboxradio", $.extend( {
7671
7854
  this.refresh();
7672
7855
  },
7673
7856
 
7857
+ _findLabel: function() {
7858
+ var parentLabel, label, isParent,
7859
+ input = this.element,
7860
+ labelsList = input[ 0 ].labels;
7861
+
7862
+ if( labelsList && labelsList.length > 0 ) {
7863
+ label = $( labelsList[ 0 ] );
7864
+ isParent = $.contains( label[ 0 ], input[ 0 ] );
7865
+ } else {
7866
+ parentLabel = input.closest( "label" );
7867
+ isParent = ( parentLabel.length > 0 );
7868
+
7869
+ // NOTE: Windows Phone could not find the label through a selector
7870
+ // filter works though.
7871
+ label = isParent ? parentLabel :
7872
+ $( this.document[ 0 ].getElementsByTagName( "label" ) )
7873
+ .filter( "[for='" + escapeId( input[ 0 ].id ) + "']" )
7874
+ .first();
7875
+ }
7876
+
7877
+ return {
7878
+ element: label,
7879
+ isParent: isParent
7880
+ };
7881
+ },
7882
+
7674
7883
  _enhance: function() {
7675
7884
  this.label.addClass( "ui-btn ui-corner-all");
7676
7885
 
7677
- if ( this.parentLabel.length > 0 ) {
7886
+ if ( this.labelIsParent ) {
7678
7887
  this.input.add( this.label ).wrapAll( this._wrapper() );
7679
7888
  } else {
7680
7889
  //this.element.replaceWith( this.input.add( this.label ).wrapAll( this._wrapper() ) );
@@ -7708,18 +7917,10 @@ $.widget( "mobile.checkboxradio", $.extend( {
7708
7917
  },
7709
7918
 
7710
7919
  _handleInputVClick: function() {
7711
- var $this = this.element;
7712
-
7713
7920
  // Adds checked attribute to checked input when keyboard is used
7714
- if ( $this.is( ":checked" ) ) {
7715
-
7716
- $this.prop( "checked", true);
7717
- this._getInputSet().not( $this ).prop( "checked", false );
7718
- } else {
7719
- $this.prop( "checked", false );
7720
- }
7721
-
7722
- this._updateAll();
7921
+ this.element.prop( "checked", this.element.is( ":checked" ) );
7922
+ this._getInputSet().not( this.element ).prop( "checked", false );
7923
+ this._updateAll( true );
7723
7924
  },
7724
7925
 
7725
7926
  _handleLabelVMouseOver: function( event ) {
@@ -7762,23 +7963,60 @@ $.widget( "mobile.checkboxradio", $.extend( {
7762
7963
  });
7763
7964
  },
7764
7965
 
7765
- //returns either a set of radios with the same name attribute, or a single checkbox
7966
+ // Returns those radio buttons that are supposed to be in the same group as
7967
+ // this radio button. In the case of a checkbox or a radio lacking a name
7968
+ // attribute, it returns this.element.
7766
7969
  _getInputSet: function() {
7767
- if ( this.inputtype === "checkbox" ) {
7768
- return this.element;
7769
- }
7970
+ var selector, formId,
7971
+ radio = this.element[ 0 ],
7972
+ name = radio.name,
7973
+ form = radio.form,
7974
+ doc = this.element.parents().last().get( 0 ),
7975
+
7976
+ // A radio is always a member of its own group
7977
+ radios = this.element;
7978
+
7979
+ // Only start running selectors if this is an attached radio button with a name
7980
+ if ( name && this.inputtype === "radio" && doc ) {
7981
+ selector = "input[type='radio'][name='" + escapeId( name ) + "']";
7982
+
7983
+ // If we're inside a form
7984
+ if ( form ) {
7985
+ formId = form.getAttribute( "id" );
7986
+
7987
+ // If the form has an ID, collect radios scattered throught the document which
7988
+ // nevertheless are part of the form by way of the value of their form attribute
7989
+ if ( formId ) {
7990
+ radios = $( selector + "[form='" + escapeId( formId ) + "']", doc );
7991
+ }
7992
+
7993
+ // Also add to those the radios in the form itself
7994
+ radios = $( form ).find( selector ).filter( function() {
7770
7995
 
7771
- return this.element.closest( "form, :jqmData(role='page'), :jqmData(role='dialog')" )
7772
- .find( "input[name='" + this.element[ 0 ].name + "'][type='" + this.inputtype + "']" );
7996
+ // Some radios inside the form may belong to some other form by virtue of
7997
+ // having a form attribute defined on them, so we must filter them out here
7998
+ return ( this.form === form );
7999
+ }).add( radios );
8000
+
8001
+ // If we're outside a form
8002
+ } else {
8003
+
8004
+ // Collect all those radios which are also outside of a form and match our name
8005
+ radios = $( selector, doc ).filter( function() {
8006
+ return !this.form;
8007
+ });
8008
+ }
8009
+ }
8010
+ return radios;
7773
8011
  },
7774
8012
 
7775
- _updateAll: function() {
8013
+ _updateAll: function( changeTriggered ) {
7776
8014
  var self = this;
7777
8015
 
7778
8016
  this._getInputSet().each( function() {
7779
8017
  var $this = $( this );
7780
8018
 
7781
- if ( this.checked || self.inputtype === "checkbox" ) {
8019
+ if ( ( this.checked || self.inputtype === "checkbox" ) && !changeTriggered ) {
7782
8020
  $this.trigger( "change" );
7783
8021
  }
7784
8022
  })
@@ -7819,14 +8057,13 @@ $.widget( "mobile.checkboxradio", $.extend( {
7819
8057
  },
7820
8058
 
7821
8059
  refresh: function() {
7822
- var hasIcon = this._hasIcon(),
7823
- isChecked = this.element[ 0 ].checked,
8060
+ var isChecked = this.element[ 0 ].checked,
7824
8061
  active = $.mobile.activeBtnClass,
7825
8062
  iconposClass = "ui-btn-icon-" + this.options.iconpos,
7826
8063
  addClasses = [],
7827
8064
  removeClasses = [];
7828
8065
 
7829
- if ( hasIcon ) {
8066
+ if ( this._hasIcon() ) {
7830
8067
  removeClasses.push( active );
7831
8068
  addClasses.push( iconposClass );
7832
8069
  } else {
@@ -7842,6 +8079,8 @@ $.widget( "mobile.checkboxradio", $.extend( {
7842
8079
  removeClasses.push( this.checkedClass );
7843
8080
  }
7844
8081
 
8082
+ this.widget().toggleClass( "ui-state-disabled", this.element.prop( "disabled" ) );
8083
+
7845
8084
  this.label
7846
8085
  .addClass( addClasses.join( " " ) )
7847
8086
  .removeClass( removeClasses.join( " " ) );
@@ -7957,8 +8196,8 @@ $.widget( "mobile.button", {
7957
8196
  },
7958
8197
 
7959
8198
  _destroy: function() {
7960
- this.element.insertBefore( this.button );
7961
- this.button.remove();
8199
+ this.element.insertBefore( this.wrapper );
8200
+ this.wrapper.remove();
7962
8201
  },
7963
8202
 
7964
8203
  _getIconClasses: function( options ) {
@@ -8005,13 +8244,19 @@ $.widget( "mobile.button", {
8005
8244
  },
8006
8245
 
8007
8246
  refresh: function( create ) {
8247
+ var originalElement,
8248
+ isDisabled = this.element.prop( "disabled" );
8249
+
8008
8250
  if ( this.options.icon && this.options.iconpos === "notext" && this.element.attr( "title" ) ) {
8009
8251
  this.element.attr( "title", this.element.val() );
8010
8252
  }
8011
8253
  if ( !create ) {
8012
- var originalElement = this.element.detach();
8254
+ originalElement = this.element.detach();
8013
8255
  $( this.wrapper ).text( this.element.val() ).append( originalElement );
8014
8256
  }
8257
+ if ( this.options.disabled !== isDisabled ) {
8258
+ this._setOptions({ disabled: isDisabled });
8259
+ }
8015
8260
  }
8016
8261
  });
8017
8262
 
@@ -8749,7 +8994,7 @@ $.widget( "mobile.slider", $.extend( {
8749
8994
 
8750
8995
  // update control"s value
8751
8996
  if ( isInput ) {
8752
- valueChanged = control.val() !== newval;
8997
+ valueChanged = parseFloat( control.val() ) !== newval;
8753
8998
  control.val( newval );
8754
8999
  } else {
8755
9000
  valueChanged = control[ 0 ].selectedIndex !== newval;
@@ -8820,6 +9065,8 @@ $.widget( "mobile.slider", $.extend( {
8820
9065
  this.slider
8821
9066
  .toggleClass( "ui-state-disabled", value )
8822
9067
  .attr( "aria-disabled", value );
9068
+
9069
+ this.element.toggleClass( "ui-state-disabled", value );
8823
9070
  }
8824
9071
 
8825
9072
  }, $.mobile.behaviors.formReset ) );
@@ -8912,7 +9159,9 @@ $.widget( "mobile.slider", $.mobile.slider, {
8912
9159
  if ( o.popupEnabled && this._popup ) {
8913
9160
  this._positionPopup();
8914
9161
  this._popup.html( newValue );
8915
- } else if ( o.showValue && !this.options.mini ) {
9162
+ }
9163
+
9164
+ if ( o.showValue && !this.options.mini ) {
8916
9165
  this.handle.html( newValue );
8917
9166
  }
8918
9167
  },
@@ -9241,6 +9490,7 @@ $.widget( "mobile.flipswitch", $.extend({
9241
9490
  //if the first handle is dragged send the event to the first slider
9242
9491
  $.data( this._inputFirst.get(0), "mobile-slider" ).dragging = true;
9243
9492
  $.data( this._inputFirst.get(0), "mobile-slider" ).refresh( event );
9493
+ $.data( this._inputFirst.get(0), "mobile-slider" )._trigger( "start" );
9244
9494
  return false;
9245
9495
  },
9246
9496
 
@@ -9293,6 +9543,11 @@ $.widget( "mobile.flipswitch", $.extend({
9293
9543
  if ( options.highlight !== undefined ) {
9294
9544
  this._setHighlight( options.highlight );
9295
9545
  }
9546
+
9547
+ if ( options.disabled !== undefined ) {
9548
+ this._setDisabled( options.disabled );
9549
+ }
9550
+
9296
9551
  this._super( options );
9297
9552
  this.refresh();
9298
9553
  },
@@ -9399,6 +9654,11 @@ $.widget( "mobile.flipswitch", $.extend({
9399
9654
  this._inputLast.slider( "option", "highlight", value );
9400
9655
  },
9401
9656
 
9657
+ _setDisabled: function( value ) {
9658
+ this._inputFirst.prop( "disabled", value );
9659
+ this._inputLast.prop( "disabled", value );
9660
+ },
9661
+
9402
9662
  _destroy: function() {
9403
9663
  this._label.prependTo( this.element );
9404
9664
  this.element.removeClass( "ui-rangeslider ui-mini" );
@@ -9423,16 +9683,21 @@ $.widget( "mobile.flipswitch", $.extend({
9423
9683
  _create: function() {
9424
9684
  this._super();
9425
9685
 
9426
- if ( !!this.options.clearBtn || this.isSearch ) {
9686
+ if ( this.isSearch ) {
9687
+ this.options.clearBtn = true;
9688
+ }
9689
+
9690
+ if ( !!this.options.clearBtn && this.inputNeedsWrap ) {
9427
9691
  this._addClearBtn();
9428
9692
  }
9429
9693
  },
9430
9694
 
9431
9695
  clearButton: function() {
9432
-
9433
- return $( "<a href='#' class='ui-input-clear ui-btn ui-icon-delete ui-btn-icon-notext ui-corner-all" +
9434
- "' title='" + this.options.clearBtnText + "'>" + this.options.clearBtnText + "</a>" );
9435
-
9696
+ return $( "<a href='#' tabindex='-1' aria-hidden='true' " +
9697
+ "class='ui-input-clear ui-btn ui-icon-delete ui-btn-icon-notext ui-corner-all'>" +
9698
+ "</a>" )
9699
+ .attr( "title", this.options.clearBtnText )
9700
+ .text( this.options.clearBtnText );
9436
9701
  },
9437
9702
 
9438
9703
  _clearBtnClick: function( event ) {
@@ -9525,7 +9790,9 @@ $.widget( "mobile.flipswitch", $.extend({
9525
9790
 
9526
9791
  _destroy: function() {
9527
9792
  this._super();
9528
- this._destroyClear();
9793
+ if ( this.options.clearBtn ) {
9794
+ this._destroyClear();
9795
+ }
9529
9796
  }
9530
9797
 
9531
9798
  });
@@ -9555,7 +9822,7 @@ $.widget( "mobile.flipswitch", $.extend({
9555
9822
  "keyup": "_timeout",
9556
9823
  "change": "_timeout",
9557
9824
  "input": "_timeout",
9558
- "paste": "_timeout",
9825
+ "paste": "_timeout"
9559
9826
  });
9560
9827
 
9561
9828
  // Attach to the various you-have-become-visible notifications that the
@@ -9591,7 +9858,7 @@ $.widget( "mobile.flipswitch", $.extend({
9591
9858
  }, this ),
9592
9859
  "transition" );
9593
9860
  }
9594
- this._timeout();
9861
+ this._prepareHeightUpdate();
9595
9862
  }
9596
9863
  },
9597
9864
 
@@ -9822,7 +10089,9 @@ $.widget( "mobile.selectmenu", $.extend( {
9822
10089
  self.refresh();
9823
10090
 
9824
10091
  if ( !!options.nativeMenu ) {
9825
- this.blur();
10092
+ self._delay( function() {
10093
+ self.select.blur();
10094
+ });
9826
10095
  }
9827
10096
  });
9828
10097
 
@@ -10059,6 +10328,14 @@ $.widget( "mobile.popup", {
10059
10328
  history: !$.mobile.browser.oldIE
10060
10329
  },
10061
10330
 
10331
+ // When the user depresses the mouse/finger on an element inside the popup while the popup is
10332
+ // open, we ignore resize events for a short while. This prevents #6961.
10333
+ _handleDocumentVmousedown: function( theEvent ) {
10334
+ if ( this._isOpen && $.contains( this._ui.container[ 0 ], theEvent.target ) ) {
10335
+ this._ignoreResizeEvents();
10336
+ }
10337
+ },
10338
+
10062
10339
  _create: function() {
10063
10340
  var theElement = this.element,
10064
10341
  myId = theElement.attr( "id" ),
@@ -10069,6 +10346,10 @@ $.widget( "mobile.popup", {
10069
10346
  // it is determined whether there shall be AJAX nav.
10070
10347
  currentOptions.history = currentOptions.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
10071
10348
 
10349
+ this._on( this.document, {
10350
+ "vmousedown": "_handleDocumentVmousedown"
10351
+ });
10352
+
10072
10353
  // Define instance variables
10073
10354
  $.extend( this, {
10074
10355
  _scrollTop: 0,
@@ -10270,10 +10551,10 @@ $.widget( "mobile.popup", {
10270
10551
 
10271
10552
  if ( targetElement !== ui.container[ 0 ] ) {
10272
10553
  target = $( targetElement );
10273
- if ( 0 === target.parents().filter( ui.container[ 0 ] ).length ) {
10274
- $( this.document[ 0 ].activeElement ).one( "focus", function(/* theEvent */) {
10275
- target.blur();
10276
- });
10554
+ if ( !$.contains( ui.container[ 0 ], targetElement ) ) {
10555
+ $( this.document[ 0 ].activeElement ).one( "focus", $.proxy( function() {
10556
+ this._safelyBlur( targetElement );
10557
+ }, this ) );
10277
10558
  ui.focusElement.focus();
10278
10559
  theEvent.preventDefault();
10279
10560
  theEvent.stopImmediatePropagation();
@@ -10604,13 +10885,28 @@ $.widget( "mobile.popup", {
10604
10885
  }
10605
10886
  },
10606
10887
 
10888
+ _safelyBlur: function( currentElement ){
10889
+ if ( currentElement !== this.window[ 0 ] &&
10890
+ currentElement.nodeName.toLowerCase() !== "body" ) {
10891
+ $( currentElement ).blur();
10892
+ }
10893
+ },
10894
+
10607
10895
  _openPrerequisitesComplete: function() {
10608
- var id = this.element.attr( "id" );
10896
+ var id = this.element.attr( "id" ),
10897
+ firstFocus = this._ui.container.find( ":focusable" ).first();
10609
10898
 
10610
10899
  this._ui.container.addClass( "ui-popup-active" );
10611
10900
  this._isOpen = true;
10612
10901
  this._resizeScreen();
10613
- this._ui.container.attr( "tabindex", "0" ).focus();
10902
+
10903
+ // Check to see if currElement is not a child of the container. If it's not, blur
10904
+ if ( !$.contains( this._ui.container[ 0 ], this.document[ 0 ].activeElement ) ) {
10905
+ this._safelyBlur( this.document[ 0 ].activeElement );
10906
+ }
10907
+ if ( firstFocus.length > 0 ) {
10908
+ this._ui.focusElement = firstFocus;
10909
+ }
10614
10910
  this._ignoreResizeEvents();
10615
10911
  if ( id ) {
10616
10912
  this.document.find( "[aria-haspopup='true'][aria-owns='" + id + "']" ).attr( "aria-expanded", true );
@@ -10695,8 +10991,6 @@ $.widget( "mobile.popup", {
10695
10991
  var container = this._ui.container,
10696
10992
  id = this.element.attr( "id" );
10697
10993
 
10698
- container.removeAttr( "tabindex" );
10699
-
10700
10994
  // remove the global mutex for popups
10701
10995
  $.mobile.popup.active = undefined;
10702
10996
 
@@ -10876,11 +11170,6 @@ $.widget( "mobile.popup", {
10876
11170
  url = $.mobile.path.parseLocation().hash + hashkey;
10877
11171
  }
10878
11172
 
10879
- // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
10880
- if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
10881
- url += hashkey;
10882
- }
10883
-
10884
11173
  // swallow the the initial navigation event, and bind for the next
10885
11174
  this.window.one( "beforenavigate", function( theEvent ) {
10886
11175
  theEvent.preventDefault();
@@ -10993,7 +11282,7 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
10993
11282
  },
10994
11283
 
10995
11284
  _handleButtonVclickKeydown: function( event ) {
10996
- if ( this.options.disabled || this.isOpen ) {
11285
+ if ( this.options.disabled || this.isOpen || this.options.nativeMenu ) {
10997
11286
  return;
10998
11287
  }
10999
11288
 
@@ -11044,6 +11333,10 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11044
11333
  },
11045
11334
 
11046
11335
  _handleMenuPageHide: function() {
11336
+
11337
+ // After the dialog's done, we may want to trigger change if the value has actually changed
11338
+ this._delayedTrigger();
11339
+
11047
11340
  // TODO centralize page removal binding / handling in the page plugin.
11048
11341
  // Suggestion from @jblas to do refcounting
11049
11342
  //
@@ -11066,18 +11359,55 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11066
11359
  }
11067
11360
  },
11068
11361
 
11362
+ _handleListItemClick: function( event ) {
11363
+ var listItem = $( event.target ).closest( "li" ),
11364
+
11365
+ // Index of option tag to be selected
11366
+ oldIndex = this.select[ 0 ].selectedIndex,
11367
+ newIndex = $.mobile.getAttribute( listItem, "option-index" ),
11368
+ option = this._selectOptions().eq( newIndex )[ 0 ];
11369
+
11370
+ // Toggle selected status on the tag for multi selects
11371
+ option.selected = this.isMultiple ? !option.selected : true;
11372
+
11373
+ // Toggle checkbox class for multiple selects
11374
+ if ( this.isMultiple ) {
11375
+ listItem.find( "a" )
11376
+ .toggleClass( "ui-checkbox-on", option.selected )
11377
+ .toggleClass( "ui-checkbox-off", !option.selected );
11378
+ }
11379
+
11380
+ // If it's not a multiple select, trigger change after it has finished closing
11381
+ if ( !this.isMultiple && oldIndex !== newIndex ) {
11382
+ this._triggerChange = true;
11383
+ }
11384
+
11385
+ // Trigger change if it's a multiple select
11386
+ // Hide custom select for single selects only - otherwise focus clicked item
11387
+ // We need to grab the clicked item the hard way, because the list may have been rebuilt
11388
+ if ( this.isMultiple ) {
11389
+ this.select.trigger( "change" );
11390
+ this.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
11391
+ .find( "a" ).first().focus();
11392
+ }
11393
+ else {
11394
+ this.close();
11395
+ }
11396
+
11397
+ event.preventDefault();
11398
+ },
11399
+
11069
11400
  build: function() {
11070
11401
  var selectId, popupId, dialogId, label, thisPage, isMultiple, menuId,
11071
11402
  themeAttr, overlayTheme, overlayThemeAttr, dividerThemeAttr,
11072
11403
  menuPage, listbox, list, header, headerTitle, menuPageContent,
11073
- menuPageClose, headerClose, self,
11404
+ menuPageClose, headerClose,
11074
11405
  o = this.options;
11075
11406
 
11076
11407
  if ( o.nativeMenu ) {
11077
11408
  return this._super();
11078
11409
  }
11079
11410
 
11080
- self = this;
11081
11411
  selectId = this.selectId;
11082
11412
  popupId = selectId + "-listbox";
11083
11413
  dialogId = selectId + "-dialog";
@@ -11092,11 +11422,14 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11092
11422
  dividerThemeAttr = ( o.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
11093
11423
  menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogId + "'" + themeAttr + overlayThemeAttr + ">" +
11094
11424
  "<div data-" + $.mobile.ns + "role='header'>" +
11095
- "<div class='ui-title'>" + label.getEncodedText() + "</div>"+
11425
+ "<div class='ui-title'></div>"+
11096
11426
  "</div>"+
11097
11427
  "<div data-" + $.mobile.ns + "role='content'></div>"+
11098
11428
  "</div>" );
11099
- listbox = $( "<div id='" + popupId + "' class='ui-selectmenu'></div>" ).insertAfter( this.select ).popup({ theme: o.overlayTheme });
11429
+ listbox = $( "<div" + themeAttr + overlayThemeAttr + " id='" + popupId +
11430
+ "' class='ui-selectmenu'></div>" )
11431
+ .insertAfter( this.select )
11432
+ .popup();
11100
11433
  list = $( "<ul class='ui-selectmenu-list' id='" + menuId + "' role='listbox' aria-labelledby='" + this.buttonId + "'" + themeAttr + dividerThemeAttr + "></ul>" ).appendTo( listbox );
11101
11434
  header = $( "<div class='ui-header ui-bar-" + ( o.theme ? o.theme : "inherit" ) + "'></div>" ).prependTo( listbox );
11102
11435
  headerTitle = $( "<h1 class='ui-title'></h1>" ).appendTo( header );
@@ -11152,52 +11485,18 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11152
11485
  // Events for list items
11153
11486
  this.list.attr( "role", "listbox" );
11154
11487
  this._on( this.list, {
11155
- focusin : "_handleListFocus",
11156
- focusout : "_handleListFocus",
11157
- keydown: "_handleListKeydown"
11488
+ "focusin": "_handleListFocus",
11489
+ "focusout": "_handleListFocus",
11490
+ "keydown": "_handleListKeydown",
11491
+ "click li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)": "_handleListItemClick"
11158
11492
  });
11159
- this.list
11160
- .delegate( "li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)", "click", function( event ) {
11161
-
11162
- // index of option tag to be selected
11163
- var oldIndex = self.select[ 0 ].selectedIndex,
11164
- newIndex = $.mobile.getAttribute( this, "option-index" ),
11165
- option = self._selectOptions().eq( newIndex )[ 0 ];
11166
-
11167
- // toggle selected status on the tag for multi selects
11168
- option.selected = self.isMultiple ? !option.selected : true;
11169
-
11170
- // toggle checkbox class for multiple selects
11171
- if ( self.isMultiple ) {
11172
- $( this ).find( "a" )
11173
- .toggleClass( "ui-checkbox-on", option.selected )
11174
- .toggleClass( "ui-checkbox-off", !option.selected );
11175
- }
11176
-
11177
- // trigger change if value changed
11178
- if ( self.isMultiple || oldIndex !== newIndex ) {
11179
- self.select.trigger( "change" );
11180
- }
11181
-
11182
- // hide custom select for single selects only - otherwise focus clicked item
11183
- // We need to grab the clicked item the hard way, because the list may have been rebuilt
11184
- if ( self.isMultiple ) {
11185
- self.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
11186
- .find( "a" ).first().focus();
11187
- }
11188
- else {
11189
- self.close();
11190
- }
11191
-
11192
- event.preventDefault();
11193
- });
11194
11493
 
11195
11494
  // button refocus ensures proper height calculation
11196
11495
  // by removing the inline style and ensuring page inclusion
11197
11496
  this._on( this.menuPage, { pagehide: "_handleMenuPageHide" } );
11198
11497
 
11199
11498
  // Events on the popup
11200
- this._on( this.listbox, { popupafterclose: "close" } );
11499
+ this._on( this.listbox, { popupafterclose: "_popupClosed" } );
11201
11500
 
11202
11501
  // Close button on small overlays
11203
11502
  if ( this.isMultiple ) {
@@ -11207,6 +11506,18 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11207
11506
  return this;
11208
11507
  },
11209
11508
 
11509
+ _popupClosed: function() {
11510
+ this.close();
11511
+ this._delayedTrigger();
11512
+ },
11513
+
11514
+ _delayedTrigger: function() {
11515
+ if ( this._triggerChange ) {
11516
+ this.element.trigger( "change" );
11517
+ }
11518
+ this._triggerChange = false;
11519
+ },
11520
+
11210
11521
  _isRebuildRequired: function() {
11211
11522
  var list = this.list.find( "li" ),
11212
11523
  options = this._selectOptions().not( ".ui-screen-hidden" );
@@ -11242,9 +11553,8 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11242
11553
  .find( "a" ).removeClass( $.mobile.activeBtnClass ).end()
11243
11554
  .attr( "aria-selected", false )
11244
11555
  .each(function( i ) {
11245
-
11556
+ var item = $( this );
11246
11557
  if ( $.inArray( i, indices ) > -1 ) {
11247
- var item = $( this );
11248
11558
 
11249
11559
  // Aria selected attr
11250
11560
  item.attr( "aria-selected", true );
@@ -11259,6 +11569,8 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11259
11569
  item.find( "a" ).addClass( $.mobile.activeBtnClass );
11260
11570
  }
11261
11571
  }
11572
+ } else if ( self.isMultiple ) {
11573
+ item.find( "a" ).removeClass( "ui-checkbox-on" ).addClass( "ui-checkbox-off" );
11262
11574
  }
11263
11575
  });
11264
11576
  },
@@ -11328,7 +11640,9 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11328
11640
 
11329
11641
  self.menuType = "page";
11330
11642
  self.menuPageContent.append( self.list );
11331
- self.menuPage.find( "div .ui-title" ).text( self.label.text() );
11643
+ self.menuPage
11644
+ .find( "div .ui-title" )
11645
+ .text( self.label.getEncodedText() || self.placeholder );
11332
11646
  } else {
11333
11647
  self.menuType = "overlay";
11334
11648
 
@@ -11370,10 +11684,15 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11370
11684
  }
11371
11685
 
11372
11686
  parent = option.parentNode;
11373
- text = $option.text();
11374
- anchor = document.createElement( "a" );
11375
11687
  classes = [];
11376
11688
 
11689
+ // Although using .text() here raises the risk that, when we later paste this into the
11690
+ // list item we end up pasting possibly malicious things like <script> tags, that risk
11691
+ // only arises if we do something like $( "<li><a href='#'>" + text + "</a></li>" ). We
11692
+ // don't do that. We do document.createTextNode( text ) instead, which guarantees that
11693
+ // whatever we paste in will end up as text, with characters like <, > and & escaped.
11694
+ text = $option.text();
11695
+ anchor = document.createElement( "a" );
11377
11696
  anchor.setAttribute( "href", "#" );
11378
11697
  anchor.appendChild( document.createTextNode( text ) );
11379
11698
 
@@ -11744,7 +12063,7 @@ $.fn.buttonMarkup.defaults = {
11744
12063
  };
11745
12064
 
11746
12065
  $.extend( $.fn.buttonMarkup, {
11747
- initSelector: "a:jqmData(role='button'), .ui-bar > a, .ui-bar > :jqmData(role='controlgroup') > a, button"
12066
+ initSelector: "a:jqmData(role='button'), .ui-bar > a, .ui-bar > :jqmData(role='controlgroup') > a, button:not(:jqmData(role='navbar') button)"
11748
12067
  });
11749
12068
 
11750
12069
  })( jQuery );
@@ -11765,16 +12084,22 @@ $.widget( "mobile.controlgroup", $.extend( {
11765
12084
 
11766
12085
  _create: function() {
11767
12086
  var elem = this.element,
11768
- opts = this.options;
12087
+ opts = this.options,
12088
+ keepNative = $.mobile.page.prototype.keepNativeSelector();
11769
12089
 
11770
12090
  // Run buttonmarkup
11771
12091
  if ( $.fn.buttonMarkup ) {
11772
- this.element.find( $.fn.buttonMarkup.initSelector ).buttonMarkup();
12092
+ this.element
12093
+ .find( $.fn.buttonMarkup.initSelector )
12094
+ .not( keepNative )
12095
+ .buttonMarkup();
11773
12096
  }
11774
12097
  // Enhance child widgets
11775
12098
  $.each( this._childWidgets, $.proxy( function( number, widgetName ) {
11776
12099
  if ( $.mobile[ widgetName ] ) {
11777
- this.element.find( $.mobile[ widgetName ].initSelector ).not( $.mobile.page.prototype.keepNativeSelector() )[ widgetName ]();
12100
+ this.element
12101
+ .find( $.mobile[ widgetName ].initSelector )
12102
+ .not( keepNative )[ widgetName ]();
11778
12103
  }
11779
12104
  }, this ));
11780
12105
 
@@ -11945,8 +12270,7 @@ $.widget( "mobile.controlgroup", $.extend( {
11945
12270
  role: role,
11946
12271
  page: page,
11947
12272
  leftbtn: leftbtn,
11948
- rightbtn: rightbtn,
11949
- backBtn: null
12273
+ rightbtn: rightbtn
11950
12274
  });
11951
12275
  this.element.attr( "role", role === "header" ? "banner" : "contentinfo" ).addClass( "ui-" + role );
11952
12276
  this.refresh();
@@ -11954,15 +12278,7 @@ $.widget( "mobile.controlgroup", $.extend( {
11954
12278
  },
11955
12279
  _setOptions: function( o ) {
11956
12280
  if ( o.addBackBtn !== undefined ) {
11957
- if ( this.options.addBackBtn &&
11958
- this.role === "header" &&
11959
- $( ".ui-page" ).length > 1 &&
11960
- this.page[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) !== $.mobile.path.stripHash( location.hash ) &&
11961
- !this.leftbtn ) {
11962
- this._addBackButton();
11963
- } else {
11964
- this.element.find( ".ui-toolbar-back-btn" ).remove();
11965
- }
12281
+ this._updateBackButton();
11966
12282
  }
11967
12283
  if ( o.backBtnTheme != null ) {
11968
12284
  this.element
@@ -11989,6 +12305,8 @@ $.widget( "mobile.controlgroup", $.extend( {
11989
12305
  this._setRelative();
11990
12306
  if ( this.role === "footer" ) {
11991
12307
  this.element.appendTo( "body" );
12308
+ } else if ( this.role === "header" ) {
12309
+ this._updateBackButton();
11992
12310
  }
11993
12311
  }
11994
12312
  this._addHeadingClasses();
@@ -12010,27 +12328,70 @@ $.widget( "mobile.controlgroup", $.extend( {
12010
12328
  },
12011
12329
  // Deprecated in 1.4. As from 1.5 ui-btn-left/right classes have to be present in the markup.
12012
12330
  _addHeaderButtonClasses: function() {
12013
- var $headeranchors = this.element.children( "a, button" );
12014
- this.leftbtn = $headeranchors.hasClass( "ui-btn-left" );
12015
- this.rightbtn = $headeranchors.hasClass( "ui-btn-right" );
12331
+ var headerAnchors = this.element.children( "a, button" );
12016
12332
 
12017
- this.leftbtn = this.leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
12333
+ // Do not mistake a back button for a left toolbar button
12334
+ this.leftbtn = headerAnchors.hasClass( "ui-btn-left" ) &&
12335
+ !headerAnchors.hasClass( "ui-toolbar-back-btn" );
12018
12336
 
12019
- this.rightbtn = this.rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
12337
+ this.rightbtn = headerAnchors.hasClass( "ui-btn-right" );
12020
12338
 
12021
- },
12022
- _addBackButton: function() {
12023
- var theme,
12024
- options = this.options;
12339
+ // Filter out right buttons and back buttons
12340
+ this.leftbtn = this.leftbtn ||
12341
+ headerAnchors.eq( 0 )
12342
+ .not( ".ui-btn-right,.ui-toolbar-back-btn" )
12343
+ .addClass( "ui-btn-left" )
12344
+ .length;
12025
12345
 
12026
- if ( !this.backBtn ) {
12346
+ this.rightbtn = this.rightbtn || headerAnchors.eq( 1 ).addClass( "ui-btn-right" ).length;
12347
+ },
12348
+ _updateBackButton: function() {
12349
+ var backButton,
12350
+ options = this.options,
12027
12351
  theme = options.backBtnTheme || options.theme;
12028
- this.backBtn = $( "<a role='button' href='javascript:void(0);' " +
12029
- "class='ui-btn ui-corner-all ui-shadow ui-btn-left " +
12030
- ( theme ? "ui-btn-" + theme + " " : "" ) +
12031
- "ui-toolbar-back-btn ui-icon-carat-l ui-btn-icon-left' " +
12032
- "data-" + $.mobile.ns + "rel='back'>" + options.backBtnText + "</a>" )
12033
- .prependTo( this.element );
12352
+
12353
+ // Retrieve the back button or create a new, empty one
12354
+ backButton = this._backButton = ( this._backButton || {} );
12355
+
12356
+ // We add a back button only if the option to do so is on
12357
+ if ( this.options.addBackBtn &&
12358
+
12359
+ // This must also be a header toolbar
12360
+ this.role === "header" &&
12361
+
12362
+ // There must be multiple pages in the DOM
12363
+ $( ".ui-page" ).length > 1 &&
12364
+ ( this.page ?
12365
+
12366
+ // If the toolbar is internal the page's URL must differ from the hash
12367
+ ( this.page[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) !==
12368
+ $.mobile.path.stripHash( location.hash ) ) :
12369
+
12370
+ // Otherwise, if the toolbar is external there must be at least one
12371
+ // history item to which one can go back
12372
+ ( $.mobile.navigate && $.mobile.navigate.history &&
12373
+ $.mobile.navigate.history.activeIndex > 0 ) ) &&
12374
+
12375
+ // The toolbar does not have a left button
12376
+ !this.leftbtn ) {
12377
+
12378
+ // Skip back button creation if one is already present
12379
+ if ( !backButton.attached ) {
12380
+ this.backButton = backButton.element = ( backButton.element ||
12381
+ $( "<a role='button' href='javascript:void(0);' " +
12382
+ "class='ui-btn ui-corner-all ui-shadow ui-btn-left " +
12383
+ ( theme ? "ui-btn-" + theme + " " : "" ) +
12384
+ "ui-toolbar-back-btn ui-icon-carat-l ui-btn-icon-left' " +
12385
+ "data-" + $.mobile.ns + "rel='back'>" + options.backBtnText +
12386
+ "</a>" ) )
12387
+ .prependTo( this.element );
12388
+ backButton.attached = true;
12389
+ }
12390
+
12391
+ // If we are not adding a back button, then remove the one present, if any
12392
+ } else if ( backButton.element ) {
12393
+ backButton.element.detach();
12394
+ backButton.attached = false;
12034
12395
  }
12035
12396
  },
12036
12397
  _addHeadingClasses: function() {
@@ -12041,6 +12402,27 @@ $.widget( "mobile.controlgroup", $.extend( {
12041
12402
  "role": "heading",
12042
12403
  "aria-level": "1"
12043
12404
  });
12405
+ },
12406
+ _destroy: function() {
12407
+ var currentTheme;
12408
+
12409
+ this.element.children( "h1, h2, h3, h4, h5, h6" )
12410
+ .removeClass( "ui-title" )
12411
+ .removeAttr( "role" )
12412
+ .removeAttr( "aria-level" );
12413
+
12414
+ if ( this.role === "header" ) {
12415
+ this.element.children( "a, button" )
12416
+ .removeClass( "ui-btn-left ui-btn-right ui-btn ui-shadow ui-corner-all" );
12417
+ if ( this.backButton) {
12418
+ this.backButton.remove();
12419
+ }
12420
+ }
12421
+
12422
+ currentTheme = this.options.theme ? this.options.theme : "inherit";
12423
+ this.element.removeClass( "ui-bar-" + currentTheme );
12424
+
12425
+ this.element.removeClass( "ui-" + this.role ).removeAttr( "role" );
12044
12426
  }
12045
12427
  });
12046
12428
 
@@ -12074,6 +12456,7 @@ $.widget( "mobile.controlgroup", $.extend( {
12074
12456
 
12075
12457
  _create: function() {
12076
12458
  this._super();
12459
+ this.pagecontainer = $( ":mobile-pagecontainer" );
12077
12460
  if ( this.options.position === "fixed" && !this.options.supportBlacklist() ) {
12078
12461
  this._makeFixed();
12079
12462
  }
@@ -12085,7 +12468,6 @@ $.widget( "mobile.controlgroup", $.extend( {
12085
12468
  this._addTransitionClass();
12086
12469
  this._bindPageEvents();
12087
12470
  this._bindToggleHandlers();
12088
- this._setOptions( this.options );
12089
12471
  },
12090
12472
 
12091
12473
  _setOptions: function( o ) {
@@ -12317,12 +12699,30 @@ $.widget( "mobile.controlgroup", $.extend( {
12317
12699
  },
12318
12700
 
12319
12701
  _destroy: function() {
12320
- var $el = this.element,
12321
- header = $el.hasClass( "ui-header" );
12702
+ var pageClasses, toolbarClasses, hasFixed, header, hasFullscreen,
12703
+ page = this.pagecontainer.pagecontainer( "getActivePage" );
12322
12704
 
12323
- $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), "" );
12324
- $el.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" );
12325
- $el.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
12705
+ this._super();
12706
+ if ( this.options.position === "fixed" ) {
12707
+ hasFixed = $( "body>.ui-" + this.role + "-fixed" )
12708
+ .add( page.find( ".ui-" + this.options.role + "-fixed" ) )
12709
+ .not( this.element ).length > 0;
12710
+ hasFullscreen = $( "body>.ui-" + this.role + "-fixed" )
12711
+ .add( page.find( ".ui-" + this.options.role + "-fullscreen" ) )
12712
+ .not( this.element ).length > 0;
12713
+ toolbarClasses = "ui-header-fixed ui-footer-fixed ui-header-fullscreen in out" +
12714
+ " ui-footer-fullscreen fade slidedown slideup ui-fixed-hidden";
12715
+ this.element.removeClass( toolbarClasses );
12716
+ if ( !hasFullscreen ) {
12717
+ pageClasses = "ui-page-" + this.role + "-fullscreen";
12718
+ }
12719
+ if ( !hasFixed ) {
12720
+ header = this.role === "header";
12721
+ pageClasses += " ui-page-" + this.role + "-fixed";
12722
+ page.css( "padding-" + ( header ? "top" : "bottom" ), "" );
12723
+ }
12724
+ page.removeClass( pageClasses );
12725
+ }
12326
12726
  }
12327
12727
 
12328
12728
  });
@@ -12680,7 +13080,6 @@ $.widget( "mobile.panel", {
12680
13080
  positionFixed: false
12681
13081
  },
12682
13082
 
12683
- _panelID: null,
12684
13083
  _closeLink: null,
12685
13084
  _parentPage: null,
12686
13085
  _page: null,
@@ -12691,19 +13090,20 @@ $.widget( "mobile.panel", {
12691
13090
 
12692
13091
  _create: function() {
12693
13092
  var el = this.element,
12694
- parentPage = el.closest( ":jqmData(role='page')" );
13093
+ parentPage = el.closest( ".ui-page, :jqmData(role='page')" );
12695
13094
 
12696
13095
  // expose some private props to other methods
12697
13096
  $.extend( this, {
12698
- _panelID: el.attr( "id" ),
12699
13097
  _closeLink: el.find( ":jqmData(rel='close')" ),
12700
13098
  _parentPage: ( parentPage.length > 0 ) ? parentPage : false,
13099
+ _openedPage: null,
12701
13100
  _page: this._getPage,
12702
13101
  _panelInner: this._getPanelInner(),
12703
- _wrapper: this._getWrapper,
12704
13102
  _fixedToolbars: this._getFixedToolbars
12705
13103
  });
12706
-
13104
+ if ( this.options.display !== "overlay" ){
13105
+ this._getWrapper();
13106
+ }
12707
13107
  this._addPanelClasses();
12708
13108
 
12709
13109
  // if animating, add the class to do so
@@ -12737,7 +13137,7 @@ $.widget( "mobile.panel", {
12737
13137
  var self = this,
12738
13138
  target = self._parentPage ? self._parentPage.parent() : self.element.parent();
12739
13139
 
12740
- self._modal = $( "<div class='" + self.options.classes.modal + "' data-panelid='" + self._panelID + "'></div>" )
13140
+ self._modal = $( "<div class='" + self.options.classes.modal + "'></div>" )
12741
13141
  .on( "mousedown", function() {
12742
13142
  self.close();
12743
13143
  })
@@ -12745,21 +13145,20 @@ $.widget( "mobile.panel", {
12745
13145
  },
12746
13146
 
12747
13147
  _getPage: function() {
12748
- var page = this._parentPage ? this._parentPage : $( "." + $.mobile.activePageClass );
13148
+ var page = this._openedPage || this._parentPage || $( "." + $.mobile.activePageClass );
12749
13149
 
12750
13150
  return page;
12751
13151
  },
12752
13152
 
12753
13153
  _getWrapper: function() {
12754
13154
  var wrapper = this._page().find( "." + this.options.classes.pageWrapper );
12755
-
12756
13155
  if ( wrapper.length === 0 ) {
12757
13156
  wrapper = this._page().children( ".ui-header:not(.ui-header-fixed), .ui-content:not(.ui-popup), .ui-footer:not(.ui-footer-fixed)" )
12758
13157
  .wrapAll( "<div class='" + this.options.classes.pageWrapper + "'></div>" )
12759
13158
  .parent();
12760
13159
  }
12761
13160
 
12762
- return wrapper;
13161
+ this._wrapper = wrapper;
12763
13162
  },
12764
13163
 
12765
13164
  _getFixedToolbars: function() {
@@ -12791,14 +13190,6 @@ $.widget( "mobile.panel", {
12791
13190
  this.element.addClass( this._getPanelClasses() );
12792
13191
  },
12793
13192
 
12794
- _handleCloseClickAndEatEvent: function( event ) {
12795
- if ( !event.isDefaultPrevented() ) {
12796
- event.preventDefault();
12797
- this.close();
12798
- return false;
12799
- }
12800
- },
12801
-
12802
13193
  _handleCloseClick: function( event ) {
12803
13194
  if ( !event.isDefaultPrevented() ) {
12804
13195
  this.close();
@@ -12815,7 +13206,7 @@ $.widget( "mobile.panel", {
12815
13206
  });
12816
13207
  },
12817
13208
 
12818
- _positionPanel: function() {
13209
+ _positionPanel: function( scrollToTop ) {
12819
13210
  var self = this,
12820
13211
  panelInnerHeight = self._panelInner.outerHeight(),
12821
13212
  expand = panelInnerHeight > $.mobile.getScreenHeight();
@@ -12825,7 +13216,9 @@ $.widget( "mobile.panel", {
12825
13216
  self._unfixPanel();
12826
13217
  $.mobile.resetActivePageHeight( panelInnerHeight );
12827
13218
  }
12828
- window.scrollTo( 0, $.mobile.defaultHomeScroll );
13219
+ if ( scrollToTop ) {
13220
+ this.window[ 0 ].scrollTo( 0, $.mobile.defaultHomeScroll );
13221
+ }
12829
13222
  } else {
12830
13223
  self._fixPanel();
12831
13224
  }
@@ -12869,9 +13262,13 @@ $.widget( "mobile.panel", {
12869
13262
  },
12870
13263
 
12871
13264
  _handleClick: function( e ) {
12872
- if ( e.currentTarget.href.split( "#" )[ 1 ] === this._panelID && this._panelID !== undefined ) {
13265
+ var link,
13266
+ panelId = this.element.attr( "id" );
13267
+
13268
+ if ( e.currentTarget.href.split( "#" )[ 1 ] === panelId && panelId !== undefined ) {
13269
+
12873
13270
  e.preventDefault();
12874
- var link = $( e.target );
13271
+ link = $( e.target );
12875
13272
  if ( link.hasClass( "ui-btn" ) ) {
12876
13273
  link.addClass( $.mobile.activeBtnClass );
12877
13274
  this.element.one( "panelopen panelclose", function() {
@@ -12879,7 +13276,6 @@ $.widget( "mobile.panel", {
12879
13276
  });
12880
13277
  }
12881
13278
  this.toggle();
12882
- return false;
12883
13279
  }
12884
13280
  },
12885
13281
 
@@ -12917,7 +13313,14 @@ $.widget( "mobile.panel", {
12917
13313
  self.close();
12918
13314
  }
12919
13315
  });
12920
-
13316
+ if ( !this._parentPage && this.options.display !== "overlay" ) {
13317
+ this._on( this.document, {
13318
+ "pageshow": function() {
13319
+ this._openedPage = null;
13320
+ this._getWrapper();
13321
+ }
13322
+ });
13323
+ }
12921
13324
  // Clean up open panels after page hide
12922
13325
  if ( self._parentPage ) {
12923
13326
  this.document.on( "pagehide", ":jqmData(role='page')", function() {
@@ -12945,16 +13348,17 @@ $.widget( "mobile.panel", {
12945
13348
  o = self.options,
12946
13349
 
12947
13350
  _openPanel = function() {
12948
- self.document.off( "panelclose" );
13351
+ self._off( self.document , "panelclose" );
12949
13352
  self._page().jqmData( "panel", "open" );
12950
13353
 
12951
13354
  if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
12952
- self._wrapper().addClass( o.classes.animate );
13355
+ self._wrapper.addClass( o.classes.animate );
12953
13356
  self._fixedToolbars().addClass( o.classes.animate );
12954
13357
  }
12955
13358
 
12956
13359
  if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
12957
- self.element.animationComplete( complete, "transition" );
13360
+ ( self._wrapper || self.element )
13361
+ .animationComplete( complete, "transition" );
12958
13362
  } else {
12959
13363
  setTimeout( complete, 0 );
12960
13364
  }
@@ -12968,13 +13372,13 @@ $.widget( "mobile.panel", {
12968
13372
  .removeClass( o.classes.panelClosed )
12969
13373
  .addClass( o.classes.panelOpen );
12970
13374
 
12971
- self._positionPanel();
13375
+ self._positionPanel( true );
12972
13376
 
12973
13377
  self._pageContentOpenClasses = self._getPosDisplayClasses( o.classes.pageContentPrefix );
12974
13378
 
12975
13379
  if ( o.display !== "overlay" ) {
12976
13380
  self._page().parent().addClass( o.classes.pageContainer );
12977
- self._wrapper().addClass( self._pageContentOpenClasses );
13381
+ self._wrapper.addClass( self._pageContentOpenClasses );
12978
13382
  self._fixedToolbars().addClass( self._pageContentOpenClasses );
12979
13383
  }
12980
13384
 
@@ -12987,21 +13391,28 @@ $.widget( "mobile.panel", {
12987
13391
  },
12988
13392
  complete = function() {
12989
13393
 
13394
+ // Bail if the panel was closed before the opening animation has completed
13395
+ if ( !self._open ) {
13396
+ return;
13397
+ }
13398
+
12990
13399
  if ( o.display !== "overlay" ) {
12991
- self._wrapper().addClass( o.classes.pageContentPrefix + "-open" );
13400
+ self._wrapper.addClass( o.classes.pageContentPrefix + "-open" );
12992
13401
  self._fixedToolbars().addClass( o.classes.pageContentPrefix + "-open" );
12993
13402
  }
12994
13403
 
12995
13404
  self._bindFixListener();
12996
13405
 
12997
13406
  self._trigger( "open" );
13407
+
13408
+ self._openedPage = self._page();
12998
13409
  };
12999
13410
 
13000
13411
  self._trigger( "beforeopen" );
13001
13412
 
13002
13413
  if ( self._page().jqmData( "panel" ) === "open" ) {
13003
- self.document.on( "panelclose", function() {
13004
- _openPanel();
13414
+ self._on( self.document, {
13415
+ "panelclose": _openPanel
13005
13416
  });
13006
13417
  } else {
13007
13418
  _openPanel();
@@ -13021,18 +13432,21 @@ $.widget( "mobile.panel", {
13021
13432
  self.element.removeClass( o.classes.panelOpen );
13022
13433
 
13023
13434
  if ( o.display !== "overlay" ) {
13024
- self._wrapper().removeClass( self._pageContentOpenClasses );
13435
+ self._wrapper.removeClass( self._pageContentOpenClasses );
13025
13436
  self._fixedToolbars().removeClass( self._pageContentOpenClasses );
13026
13437
  }
13027
13438
 
13028
13439
  if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
13029
- self.element.animationComplete( complete, "transition" );
13440
+ ( self._wrapper || self.element )
13441
+ .animationComplete( complete, "transition" );
13030
13442
  } else {
13031
13443
  setTimeout( complete, 0 );
13032
13444
  }
13033
13445
 
13034
13446
  if ( self._modal ) {
13035
- self._modal.removeClass( self._modalOpenClasses );
13447
+ self._modal
13448
+ .removeClass( self._modalOpenClasses )
13449
+ .height( "" );
13036
13450
  }
13037
13451
  },
13038
13452
  complete = function() {
@@ -13044,12 +13458,12 @@ $.widget( "mobile.panel", {
13044
13458
 
13045
13459
  if ( o.display !== "overlay" ) {
13046
13460
  self._page().parent().removeClass( o.classes.pageContainer );
13047
- self._wrapper().removeClass( o.classes.pageContentPrefix + "-open" );
13461
+ self._wrapper.removeClass( o.classes.pageContentPrefix + "-open" );
13048
13462
  self._fixedToolbars().removeClass( o.classes.pageContentPrefix + "-open" );
13049
13463
  }
13050
13464
 
13051
13465
  if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
13052
- self._wrapper().removeClass( o.classes.animate );
13466
+ self._wrapper.removeClass( o.classes.animate );
13053
13467
  self._fixedToolbars().removeClass( o.classes.animate );
13054
13468
  }
13055
13469
 
@@ -13060,6 +13474,8 @@ $.widget( "mobile.panel", {
13060
13474
  self._page().jqmRemoveData( "panel" );
13061
13475
 
13062
13476
  self._trigger( "close" );
13477
+
13478
+ self._openedPage = null;
13063
13479
  };
13064
13480
 
13065
13481
  self._trigger( "beforeclose" );
@@ -13084,7 +13500,7 @@ $.widget( "mobile.panel", {
13084
13500
  // remove the wrapper if not in use by another panel
13085
13501
  otherPanels = $( "body > :mobile-panel" ).add( $.mobile.activePage.find( ":mobile-panel" ) );
13086
13502
  if ( otherPanels.not( ".ui-panel-display-overlay" ).not( this.element ).length === 0 ) {
13087
- this._wrapper().children().unwrap();
13503
+ this._wrapper.children().unwrap();
13088
13504
  }
13089
13505
 
13090
13506
  if ( this._open ) {
@@ -13285,14 +13701,17 @@ $.widget( "mobile.table", $.mobile.table, {
13285
13701
 
13286
13702
  // create the hide/show toggles
13287
13703
  this.headers.not( "td" ).each( function() {
13288
- var header = $( this ),
13289
- priority = $.mobile.getAttribute( this, "priority" ),
13290
- cells = header.add( header.jqmData( "cells" ) );
13704
+ var input, cells,
13705
+ header = $( this ),
13706
+ priority = $.mobile.getAttribute( this, "priority" );
13291
13707
 
13292
13708
  if ( priority ) {
13709
+ cells = header.add( header.jqmData( "cells" ) );
13293
13710
  cells.addClass( opts.classes.priorityPrefix + priority );
13294
13711
 
13295
- ( keep ? inputs.eq( checkboxIndex++ ) :
13712
+ // Make sure the (new?) checkbox is associated with its header via .jqmData() and
13713
+ // that, vice versa, the header is also associated with the checkbox
13714
+ input = ( keep ? inputs.eq( checkboxIndex++ ) :
13296
13715
  $("<label><input type='checkbox' checked />" +
13297
13716
  ( header.children( "abbr" ).first().attr( "title" ) ||
13298
13717
  header.text() ) +
@@ -13302,7 +13721,13 @@ $.widget( "mobile.table", $.mobile.table, {
13302
13721
  .checkboxradio( {
13303
13722
  theme: opts.columnPopupTheme
13304
13723
  }) )
13305
- .jqmData( "cells", cells );
13724
+
13725
+ // Associate the header with the checkbox
13726
+ .jqmData( "header", header )
13727
+ .jqmData( "cells", cells );
13728
+
13729
+ // Associate the checkbox with the header
13730
+ header.jqmData( "input", input );
13306
13731
  }
13307
13732
  });
13308
13733
 
@@ -13319,14 +13744,6 @@ $.widget( "mobile.table", $.mobile.table, {
13319
13744
  input.jqmData( "cells" )
13320
13745
  .toggleClass( "ui-table-cell-hidden", !checked )
13321
13746
  .toggleClass( "ui-table-cell-visible", checked );
13322
-
13323
- if ( input[ 0 ].getAttribute( "locked" ) ) {
13324
- input.removeAttr( "locked" );
13325
-
13326
- this._unlockCells( input.jqmData( "cells" ) );
13327
- } else {
13328
- input.attr( "locked", true );
13329
- }
13330
13747
  },
13331
13748
 
13332
13749
  _unlockCells: function( cells ) {
@@ -13376,17 +13793,48 @@ $.widget( "mobile.table", $.mobile.table, {
13376
13793
  },
13377
13794
 
13378
13795
  _refresh: function( create ) {
13796
+ var headers, hiddenColumns, index;
13797
+
13798
+ // Calling _super() here updates this.headers
13379
13799
  this._super( create );
13380
13800
 
13381
13801
  if ( !create && this.options.mode === "columntoggle" ) {
13802
+ headers = this.headers;
13803
+ hiddenColumns = [];
13804
+
13805
+ // Find the index of the column header associated with each old checkbox among the
13806
+ // post-refresh headers and, if the header is still there, make sure the corresponding
13807
+ // column will be hidden if the pre-refresh checkbox indicates that the column is
13808
+ // hidden by recording its index in the array of hidden columns.
13809
+ this._menu.find( "input" ).each( function() {
13810
+ var input = $( this ),
13811
+ header = input.jqmData( "header" ),
13812
+ index = headers.index( header[ 0 ] );
13813
+
13814
+ if ( index > -1 && !input.prop( "checked" ) ) {
13815
+
13816
+ // The column header associated with /this/ checkbox is still present in the
13817
+ // post-refresh table and the checkbox is not checked, so the column associated
13818
+ // with this column header is currently hidden. Let's record that.
13819
+ hiddenColumns.push( index );
13820
+ }
13821
+ });
13822
+
13382
13823
  // columns not being replaced must be cleared from input toggle-locks
13383
- this._unlockCells( this.allHeaders );
13824
+ this._unlockCells( this.element.find( ".ui-table-cell-hidden, " +
13825
+ ".ui-table-cell-visible" ) );
13384
13826
 
13385
13827
  // update columntoggles and cells
13386
13828
  this._addToggles( this._menu, create );
13387
13829
 
13388
- // check/uncheck
13389
- this._setToggleState();
13830
+ // At this point all columns are visible, so uncheck the checkboxes that correspond to
13831
+ // those columns we've found to be hidden
13832
+ for ( index = hiddenColumns.length - 1 ; index > -1 ; index-- ) {
13833
+ headers.eq( hiddenColumns[ index ] ).jqmData( "input" )
13834
+ .prop( "checked", false )
13835
+ .checkboxradio( "refresh" )
13836
+ .trigger( "change" );
13837
+ }
13390
13838
  }
13391
13839
  },
13392
13840
 
@@ -13456,10 +13904,10 @@ $.widget( "mobile.table", $.mobile.table, {
13456
13904
  var cells = $( this ).jqmData( "cells" ),
13457
13905
  colstart = $.mobile.getAttribute( this, "colstart" ),
13458
13906
  hierarchyClass = cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top",
13459
- text = $( this ).text(),
13907
+ contents = $( this ).clone().contents(),
13460
13908
  iteration, filter;
13461
13909
 
13462
- if ( text !== "" ) {
13910
+ if ( contents.length > 0 ) {
13463
13911
 
13464
13912
  if ( hierarchyClass ) {
13465
13913
  iteration = parseInt( this.getAttribute( "colspan" ), 10 );
@@ -13469,18 +13917,24 @@ $.widget( "mobile.table", $.mobile.table, {
13469
13917
  filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")";
13470
13918
  }
13471
13919
 
13472
- table._addLabels( cells.filter( filter ), opts.classes.cellLabels + hierarchyClass, text );
13920
+ table._addLabels( cells.filter( filter ),
13921
+ opts.classes.cellLabels + hierarchyClass, contents );
13473
13922
  } else {
13474
- table._addLabels( cells, opts.classes.cellLabels, text );
13923
+ table._addLabels( cells, opts.classes.cellLabels, contents );
13475
13924
  }
13476
13925
 
13477
13926
  }
13478
13927
  });
13479
13928
  },
13480
13929
 
13481
- _addLabels: function( cells, label, text ) {
13930
+ _addLabels: function( cells, label, contents ) {
13931
+ if ( contents.length === 1 && contents[ 0 ].nodeName.toLowerCase() === "abbr" ) {
13932
+ contents = contents.eq( 0 ).attr( "title" );
13933
+ }
13482
13934
  // .not fixes #6006
13483
- cells.not( ":has(b." + label + ")" ).prepend( "<b class='" + label + "'>" + text + "</b>" );
13935
+ cells
13936
+ .not( ":has(b." + label + ")" )
13937
+ .prepend( $( "<b class='" + label + "'></b>" ).append( contents ) );
13484
13938
  }
13485
13939
  });
13486
13940
 
@@ -13539,7 +13993,9 @@ $.widget( "mobile.filterable", {
13539
13993
  }
13540
13994
 
13541
13995
  this._timer = this._delay( function() {
13542
- this._trigger( "beforefilter", null, { input: search } );
13996
+ if ( this._trigger( "beforefilter", null, { input: search } ) === false ) {
13997
+ return false;
13998
+ }
13543
13999
 
13544
14000
  // Change val as lastval for next execution
13545
14001
  search[ 0 ].setAttribute( "data-" + $.mobile.ns + "lastval", val );
@@ -13587,7 +14043,8 @@ $.widget( "mobile.filterable", {
13587
14043
  // If nothing is hidden, then the decision whether to hide or show the items
13588
14044
  // is based on the "filterReveal" option.
13589
14045
  if ( hide.length === 0 ) {
13590
- filterItems[ opts.filterReveal ? "addClass" : "removeClass" ]( "ui-screen-hidden" );
14046
+ filterItems[ ( opts.filterReveal && val.length === 0 ) ?
14047
+ "addClass" : "removeClass" ]( "ui-screen-hidden" );
13591
14048
  } else {
13592
14049
  $( hide ).addClass( "ui-screen-hidden" );
13593
14050
  $( show ).removeClass( "ui-screen-hidden" );
@@ -13638,6 +14095,8 @@ $.widget( "mobile.filterable", {
13638
14095
  this.document.find( selector );
13639
14096
 
13640
14097
  this._on( search, {
14098
+ keydown: "_onKeyDown",
14099
+ keypress: "_onKeyPress",
13641
14100
  keyup: "_onKeyUp",
13642
14101
  change: "_onKeyUp",
13643
14102
  input: "_onKeyUp"
@@ -13647,6 +14106,21 @@ $.widget( "mobile.filterable", {
13647
14106
  this._search = search;
13648
14107
  },
13649
14108
 
14109
+ // Prevent form submission
14110
+ _onKeyDown: function( event ) {
14111
+ if ( event.keyCode === $.ui.keyCode.ENTER ) {
14112
+ event.preventDefault();
14113
+ this._preventKeyPress = true;
14114
+ }
14115
+ },
14116
+
14117
+ _onKeyPress: function( event ) {
14118
+ if ( this._preventKeyPress ) {
14119
+ event.preventDefault();
14120
+ this._preventKeyPress = false;
14121
+ }
14122
+ },
14123
+
13650
14124
  _setOptions: function( options ) {
13651
14125
  var refilter = !( ( options.filterReveal === undefined ) &&
13652
14126
  ( options.filterCallback === undefined ) &&
@@ -13750,7 +14224,9 @@ $.widget( "mobile.filterable", $.mobile.filterable, {
13750
14224
  // Also trigger listviewbeforefilter if this widget is also a listview
13751
14225
  this._widget._trigger( "beforefilter", event, data );
13752
14226
  }
13753
- this._super( type, event, data );
14227
+
14228
+ // Passing back the response enables calling preventDefault()
14229
+ return this._super( type, event, data );
13754
14230
  },
13755
14231
 
13756
14232
  _setWidget: function( widget ) {
@@ -13835,6 +14311,24 @@ $.widget( "mobile.filterable", $.mobile.filterable, {
13835
14311
  return ret;
13836
14312
  },
13837
14313
 
14314
+ // The listview implementation accompanying this filterable backcompat layer will call
14315
+ // filterable.refresh() after it's done refreshing the listview to make sure the filterable
14316
+ // filters out any new items added. However, when the listview refresh has been initiated by
14317
+ // the filterable itself, then such filtering has already taken place, and calling the
14318
+ // filterable's refresh() method will cause an infinite recursion. We stop this by setting a
14319
+ // flag that will cause the filterable's refresh() method to short-circuit.
14320
+ _refreshChildWidget: function() {
14321
+ this._refreshingChildWidget = true;
14322
+ this._superApply( arguments );
14323
+ this._refreshingChildWidget = false;
14324
+ },
14325
+
14326
+ refresh: function() {
14327
+ if ( !this._refreshingChildWidget ) {
14328
+ this._superApply( arguments );
14329
+ }
14330
+ },
14331
+
13838
14332
  _destroy: function() {
13839
14333
  if ( this._isSearchInternal() ) {
13840
14334
  this._search.remove();
@@ -13865,6 +14359,38 @@ $.widget( "mobile.filterable", $.mobile.filterable, {
13865
14359
  }
13866
14360
  });
13867
14361
 
14362
+ // Instantiate a filterable on a listview that has the data-filter="true" attribute
14363
+ // This is not necessary for static content, because the auto-enhance takes care of instantiating
14364
+ // the filterable upon encountering data-filter="true". However, because of 1.3.x it is expected
14365
+ // that a listview with data-filter="true" will be filterable even if you just instantiate a
14366
+ // listview on it. The extension below ensures that this continues to happen in 1.4.x.
14367
+ $.widget( "mobile.listview", $.mobile.listview, {
14368
+ options: {
14369
+ filter: false
14370
+ },
14371
+ _create: function() {
14372
+ if ( this.options.filter === true &&
14373
+ !this.element.data( "mobile-filterable" ) ) {
14374
+ this.element.filterable();
14375
+ }
14376
+ return this._super();
14377
+ },
14378
+
14379
+ refresh: function() {
14380
+ var filterable;
14381
+
14382
+ this._superApply( arguments );
14383
+
14384
+ if ( this.options.filter === true ) {
14385
+ filterable = this.element.data( "mobile-filterable" );
14386
+
14387
+ if ( filterable ) {
14388
+ filterable.refresh();
14389
+ }
14390
+ }
14391
+ }
14392
+ });
14393
+
13868
14394
  })( jQuery );
13869
14395
 
13870
14396
  /*!
@@ -14799,7 +15325,8 @@ $.widget( "ui.tabs", {
14799
15325
  var path = $.mobile.path,
14800
15326
  $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ),
14801
15327
  hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ),
14802
- hashPage = document.getElementById( hash );
15328
+ theLocation = $.mobile.path.parseLocation(),
15329
+ hashPage = hash ? document.getElementById( hash ) : undefined;
14803
15330
 
14804
15331
  // if no pages are found, create one with body's inner html
14805
15332
  if ( !$pages.length ) {
@@ -14812,7 +15339,8 @@ $.widget( "ui.tabs", {
14812
15339
 
14813
15340
  // unless the data url is already set set it to the pathname
14814
15341
  if ( !$this[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) ) {
14815
- $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search );
15342
+ $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) ||
15343
+ path.convertUrlToDataUrl( theLocation.pathname + theLocation.search ) );
14816
15344
  }
14817
15345
  });
14818
15346
 
@@ -14849,11 +15377,6 @@ $.widget( "ui.tabs", {
14849
15377
  $.mobile.path.isPath( hash ) ||
14850
15378
  hash === $.mobile.dialogHashKey ) ) ) {
14851
15379
 
14852
- // Store the initial destination
14853
- if ( $.mobile.path.isHashValid( location.hash ) ) {
14854
- $.mobile.navigate.history.initialDst = hash.replace( "#", "" );
14855
- }
14856
-
14857
15380
  // make sure to set initial popstate state if it exists
14858
15381
  // so that navigation back to the initial page works properly
14859
15382
  if ( $.event.special.navigate.isPushStateEnabled() ) {