jquery-mobile-rails-assets 1.4.1 → 1.4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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() ) {