jquery_mobile_rails 1.4.2 → 1.4.3

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.
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * jQuery Mobile 1.4.2
3
- * Git HEAD hash: 9d9a42a27d0c693e8b5569c3a10d771916af5045 <> Date: Fri Feb 28 2014 17:32:01 UTC
2
+ * jQuery Mobile 1.4.3
3
+ * Git HEAD hash: b9c6473e3d90af26570e6f14e5a0307897ab385c <> Date: Tue Jul 1 2014 15:37:36 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
  *
@@ -30,7 +30,7 @@
30
30
  $.extend( $.mobile, {
31
31
 
32
32
  // Version of the jQuery Mobile Framework
33
- version: "1.4.2",
33
+ version: "1.4.3",
34
34
 
35
35
  // Deprecated and no longer used in 1.4 remove in 1.5
36
36
  // Define the url parameter used for referencing widget-generated sub-pages.
@@ -80,7 +80,7 @@
80
80
  // Error response message - appears when an Ajax page request fails
81
81
  pageLoadErrorMessage: "Error Loading Page",
82
82
 
83
- // For error messages, which theme does the box uses?
83
+ // For error messages, which theme does the box use?
84
84
  pageLoadErrorMessageTheme: "a",
85
85
 
86
86
  // replace calls to window.history.back with phonegaps navigation helper
@@ -683,7 +683,13 @@ $.ui.plugin = {
683
683
  height = compensateToolbars( page,
684
684
  ( typeof height === "number" ) ? height : $.mobile.getScreenHeight() );
685
685
 
686
- page.css( "min-height", height - ( pageOuterHeight - pageHeight ) );
686
+ // Remove any previous min-height setting
687
+ page.css( "min-height", "" );
688
+
689
+ // Set the minimum height only if the height as determined by CSS is insufficient
690
+ if ( page.height() < height ) {
691
+ page.css( "min-height", height - ( pageOuterHeight - pageHeight ) );
692
+ }
687
693
  },
688
694
 
689
695
  loading: function() {
@@ -1886,7 +1892,7 @@ $.mobile.widget = $.Widget;
1886
1892
  iframe_doc.open();
1887
1893
 
1888
1894
  // Set document.domain for the Iframe document as well, if necessary.
1889
- domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
1895
+ domain && iframe_doc.write( '\x3cscript>document.domain="' + domain + '"\x3c/script>' );
1890
1896
 
1891
1897
  iframe_doc.close();
1892
1898
 
@@ -2357,19 +2363,34 @@ if ( !$.support.boxShadow ) {
2357
2363
  urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
2358
2364
 
2359
2365
  // Abstraction to address xss (Issue #4787) by removing the authority in
2360
- // browsers that auto decode it. All references to location.href should be
2366
+ // browsers that auto-decode it. All references to location.href should be
2361
2367
  // replaced with a call to this method so that it can be dealt with properly here
2362
2368
  getLocation: function( url ) {
2363
- var uri = url ? this.parseUrl( url ) : location,
2364
- hash = this.parseUrl( url || location.href ).hash;
2369
+ var parsedUrl = this.parseUrl( url || location.href ),
2370
+ uri = url ? parsedUrl : location,
2371
+
2372
+ // Make sure to parse the url or the location object for the hash because using
2373
+ // location.hash is autodecoded in firefox, the rest of the url should be from
2374
+ // the object (location unless we're testing) to avoid the inclusion of the
2375
+ // authority
2376
+ hash = parsedUrl.hash;
2365
2377
 
2366
2378
  // mimic the browser with an empty string when the hash is empty
2367
2379
  hash = hash === "#" ? "" : hash;
2368
2380
 
2369
- // Make sure to parse the url or the location object for the hash because using location.hash
2370
- // is autodecoded in firefox, the rest of the url should be from the object (location unless
2371
- // we're testing) to avoid the inclusion of the authority
2372
- return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
2381
+ return uri.protocol +
2382
+ parsedUrl.doubleSlash +
2383
+ uri.host +
2384
+
2385
+ // The pathname must start with a slash if there's a protocol, because you
2386
+ // can't have a protocol followed by a relative path. Also, it's impossible to
2387
+ // calculate absolute URLs from relative ones if the absolute one doesn't have
2388
+ // a leading "/".
2389
+ ( ( uri.protocol !== "" && uri.pathname.substring( 0, 1 ) !== "/" ) ?
2390
+ "/" : "" ) +
2391
+ uri.pathname +
2392
+ uri.search +
2393
+ hash;
2373
2394
  },
2374
2395
 
2375
2396
  //return the original document url
@@ -2639,7 +2660,8 @@ if ( !$.support.boxShadow ) {
2639
2660
 
2640
2661
  // reconstruct each of the pieces with the new search string and hash
2641
2662
  href = path.parseUrl( href );
2642
- href = href.protocol + "//" + href.host + href.pathname + search + preservedHash;
2663
+ href = href.protocol + href.doubleSlash + href.host + href.pathname + search +
2664
+ preservedHash;
2643
2665
  } else {
2644
2666
  href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState;
2645
2667
  }
@@ -3201,6 +3223,12 @@ if ( !$.support.boxShadow ) {
3201
3223
  $.fn.animationComplete = function( callback, type, fallbackTime ) {
3202
3224
  var timer, duration,
3203
3225
  that = this,
3226
+ eventBinding = function() {
3227
+
3228
+ // Clear the timer so we don't call callback twice
3229
+ clearTimeout( timer );
3230
+ callback.apply( this, arguments );
3231
+ },
3204
3232
  animationType = ( !type || type === "animation" ) ? "animation" : "transition";
3205
3233
 
3206
3234
  // Make sure selected type is supported by browser
@@ -3228,17 +3256,12 @@ if ( !$.support.boxShadow ) {
3228
3256
 
3229
3257
  // Sets up the fallback if event never comes
3230
3258
  timer = setTimeout( function() {
3231
- $( that ).off( props[ animationType ].event );
3259
+ $( that ).off( props[ animationType ].event, eventBinding );
3232
3260
  callback.apply( that );
3233
3261
  }, duration );
3234
3262
 
3235
3263
  // Bind the event
3236
- return $( this ).one( props[ animationType ].event, function() {
3237
-
3238
- // Clear the timer so we dont call callback twice
3239
- clearTimeout( timer );
3240
- callback.call( this, arguments );
3241
- });
3264
+ return $( this ).one( props[ animationType ].event, eventBinding );
3242
3265
  } else {
3243
3266
 
3244
3267
  // CSS animation / transitions not supported
@@ -3867,7 +3890,7 @@ if ( eventCaptureSupported ) {
3867
3890
  if ( !isTaphold && origTarget === event.target ) {
3868
3891
  triggerCustomEvent( thisObject, "tap", event );
3869
3892
  } else if ( isTaphold ) {
3870
- event.stopPropagation();
3893
+ event.preventDefault();
3871
3894
  }
3872
3895
  }
3873
3896
 
@@ -4063,8 +4086,8 @@ if ( eventCaptureSupported ) {
4063
4086
  $.each({
4064
4087
  scrollstop: "scrollstart",
4065
4088
  taphold: "tap",
4066
- swipeleft: "swipe",
4067
- swiperight: "swipe"
4089
+ swipeleft: "swipe.left",
4090
+ swiperight: "swipe.right"
4068
4091
  }, function( event, sourceEvent ) {
4069
4092
 
4070
4093
  $.event.special[ event ] = {
@@ -4300,13 +4323,14 @@ if ( eventCaptureSupported ) {
4300
4323
  page.find( base.linkSelector ).each(function( i, link ) {
4301
4324
  var thisAttr = $( link ).is( "[href]" ) ? "href" :
4302
4325
  $( link ).is( "[src]" ) ? "src" : "action",
4326
+ theLocation = $.mobile.path.parseLocation(),
4303
4327
  thisUrl = $( link ).attr( thisAttr );
4304
4328
 
4305
4329
  // XXX_jblas: We need to fix this so that it removes the document
4306
4330
  // base URL, and then prepends with the new page URL.
4307
4331
  // if full path exists and is same, chop it - helps IE out
4308
- thisUrl = thisUrl.replace( location.protocol + "//" +
4309
- location.host + location.pathname, "" );
4332
+ thisUrl = thisUrl.replace( theLocation.protocol + theLocation.doubleSlash +
4333
+ theLocation.host + theLocation.pathname, "" );
4310
4334
 
4311
4335
  if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
4312
4336
  $( link ).attr( thisAttr, newPath + thisUrl );
@@ -4506,6 +4530,7 @@ $.widget( "mobile.page", {
4506
4530
  initSelector: false,
4507
4531
 
4508
4532
  _create: function() {
4533
+ this._trigger( "beforecreate" );
4509
4534
  this.setLastScrollEnabled = true;
4510
4535
 
4511
4536
  this._on( this.window, {
@@ -4958,7 +4983,7 @@ $.widget( "mobile.page", {
4958
4983
  ( page || this.element ).trigger( deprecatedEvent, data );
4959
4984
 
4960
4985
  // use the widget trigger method for the new content* event
4961
- this.element.trigger( newEvent, data );
4986
+ this._trigger( name, newEvent, data );
4962
4987
 
4963
4988
  return {
4964
4989
  deprecatedEvent: deprecatedEvent,
@@ -5011,11 +5036,13 @@ $.widget( "mobile.page", {
5011
5036
 
5012
5037
  triggerData.content = content;
5013
5038
 
5039
+ triggerData.toPage = content;
5040
+
5014
5041
  // If the default behavior is prevented, stop here!
5015
5042
  // Note that it is the responsibility of the listener/handler
5016
5043
  // that called preventDefault(), to resolve/reject the
5017
5044
  // deferred object within the triggerData.
5018
- if ( !this._trigger( "load", undefined, triggerData ) ) {
5045
+ if ( this._triggerWithDeprecated( "load" ).event.isDefaultPrevented() ) {
5019
5046
  return;
5020
5047
  }
5021
5048
 
@@ -5038,11 +5065,6 @@ $.widget( "mobile.page", {
5038
5065
  this._hideLoading();
5039
5066
  }
5040
5067
 
5041
- // BEGIN DEPRECATED ---------------------------------------------------
5042
- // Let listeners know the content loaded successfully.
5043
- this.element.trigger( "pageload" );
5044
- // END DEPRECATED -----------------------------------------------------
5045
-
5046
5068
  deferred.resolve( absUrl, settings, content );
5047
5069
  }, this);
5048
5070
  },
@@ -5116,7 +5138,7 @@ $.widget( "mobile.page", {
5116
5138
  $.mobile.path.isEmbeddedPage(fileUrl) &&
5117
5139
  !$.mobile.path.isFirstPageUrl(fileUrl) ) {
5118
5140
  deferred.reject( absUrl, settings );
5119
- return;
5141
+ return deferred.promise();
5120
5142
  }
5121
5143
 
5122
5144
  // Reset base to the default document base
@@ -5137,12 +5159,14 @@ $.widget( "mobile.page", {
5137
5159
  this._getBase().set(url);
5138
5160
  }
5139
5161
 
5140
- return;
5162
+ return deferred.promise();
5141
5163
  }
5142
5164
 
5143
5165
  triggerData = {
5144
5166
  url: url,
5145
5167
  absUrl: absUrl,
5168
+ toPage: url,
5169
+ prevPage: options ? options.fromPage : undefined,
5146
5170
  dataUrl: dataUrl,
5147
5171
  deferred: deferred,
5148
5172
  options: settings
@@ -5154,7 +5178,7 @@ $.widget( "mobile.page", {
5154
5178
  // If the default behavior is prevented, stop here!
5155
5179
  if ( pblEvent.deprecatedEvent.isDefaultPrevented() ||
5156
5180
  pblEvent.event.isDefaultPrevented() ) {
5157
- return;
5181
+ return deferred.promise();
5158
5182
  }
5159
5183
 
5160
5184
  if ( settings.showLoadMsg ) {
@@ -5170,7 +5194,7 @@ $.widget( "mobile.page", {
5170
5194
  if ( !( $.mobile.allowCrossDomainPages ||
5171
5195
  $.mobile.path.isSameDomain($.mobile.path.documentUrl, absUrl ) ) ) {
5172
5196
  deferred.reject( absUrl, settings );
5173
- return;
5197
+ return deferred.promise();
5174
5198
  }
5175
5199
 
5176
5200
  // Load the new content.
@@ -5183,6 +5207,8 @@ $.widget( "mobile.page", {
5183
5207
  success: this._loadSuccess( absUrl, triggerData, settings, deferred ),
5184
5208
  error: this._loadError( absUrl, triggerData, settings, deferred )
5185
5209
  });
5210
+
5211
+ return deferred.promise();
5186
5212
  },
5187
5213
 
5188
5214
  _loadError: function( absUrl, triggerData, settings, deferred ) {
@@ -5241,11 +5267,21 @@ $.widget( "mobile.page", {
5241
5267
 
5242
5268
  //trigger before show/hide events
5243
5269
  // TODO deprecate nextPage in favor of next
5244
- this._triggerWithDeprecated( prefix + "hide", { nextPage: to, samePage: samePage }, from );
5270
+ this._triggerWithDeprecated( prefix + "hide", {
5271
+
5272
+ // Deprecated in 1.4 remove in 1.5
5273
+ nextPage: to,
5274
+ toPage: to,
5275
+ prevPage: from,
5276
+ samePage: samePage
5277
+ }, from );
5245
5278
  }
5246
5279
 
5247
5280
  // TODO deprecate prevPage in favor of previous
5248
- this._triggerWithDeprecated( prefix + "show", { prevPage: from || $( "" ) }, to );
5281
+ this._triggerWithDeprecated( prefix + "show", {
5282
+ prevPage: from || $( "" ),
5283
+ toPage: to
5284
+ }, to );
5249
5285
  },
5250
5286
 
5251
5287
  // TODO make private once change has been defined in the widget
@@ -5265,14 +5301,14 @@ $.widget( "mobile.page", {
5265
5301
 
5266
5302
  promise = ( new TransitionHandler( transition, reverse, to, from ) ).transition();
5267
5303
 
5304
+ promise.done( $.proxy( function() {
5305
+ this._triggerCssTransitionEvents( to, from );
5306
+ }, this ));
5307
+
5268
5308
  // TODO temporary accomodation of argument deferred
5269
5309
  promise.done(function() {
5270
5310
  deferred.resolve.apply( deferred, arguments );
5271
5311
  });
5272
-
5273
- promise.done($.proxy(function() {
5274
- this._triggerCssTransitionEvents( to, from );
5275
- }, this));
5276
5312
  },
5277
5313
 
5278
5314
  _releaseTransitionLock: function() {
@@ -5317,9 +5353,13 @@ $.widget( "mobile.page", {
5317
5353
  },
5318
5354
 
5319
5355
  _triggerPageBeforeChange: function( to, triggerData, settings ) {
5320
- var pbcEvent = new $.Event( "pagebeforechange" );
5356
+ var returnEvents;
5321
5357
 
5322
- $.extend(triggerData, { toPage: to, options: settings });
5358
+ triggerData.prevPage = this.activePage;
5359
+ $.extend( triggerData, {
5360
+ toPage: to,
5361
+ options: settings
5362
+ });
5323
5363
 
5324
5364
  // NOTE: preserve the original target as the dataUrl value will be
5325
5365
  // simplified eg, removing ui-state, and removing query params from
@@ -5336,10 +5376,11 @@ $.widget( "mobile.page", {
5336
5376
  }
5337
5377
 
5338
5378
  // Let listeners know we're about to change the current page.
5339
- this.element.trigger( pbcEvent, triggerData );
5379
+ returnEvents = this._triggerWithDeprecated( "beforechange", triggerData );
5340
5380
 
5341
5381
  // If the default behavior is prevented, stop here!
5342
- if ( pbcEvent.isDefaultPrevented() ) {
5382
+ if ( returnEvents.event.isDefaultPrevented() ||
5383
+ returnEvents.deprecatedEvent.isDefaultPrevented() ) {
5343
5384
  return false;
5344
5385
  }
5345
5386
 
@@ -5412,6 +5453,7 @@ $.widget( "mobile.page", {
5412
5453
  return;
5413
5454
  }
5414
5455
 
5456
+ triggerData.prevPage = settings.fromPage;
5415
5457
  // if the (content|page)beforetransition default is prevented return early
5416
5458
  // Note, we have to check for both the deprecated and new events
5417
5459
  beforeTransition = this._triggerWithDeprecated( "beforetransition", triggerData );
@@ -5467,7 +5509,7 @@ $.widget( "mobile.page", {
5467
5509
 
5468
5510
  isPageTransitioning = false;
5469
5511
  this._triggerWithDeprecated( "transition", triggerData );
5470
- this.element.trigger( "pagechange", triggerData );
5512
+ this._triggerWithDeprecated( "change", triggerData );
5471
5513
 
5472
5514
  // Even if there is no page change to be done, we should keep the
5473
5515
  // urlHistory in sync with the hash changes
@@ -5634,8 +5676,8 @@ $.widget( "mobile.page", {
5634
5676
  }
5635
5677
 
5636
5678
  this._releaseTransitionLock();
5637
- this.element.trigger( "pagechange", triggerData );
5638
5679
  this._triggerWithDeprecated( "transition", triggerData );
5680
+ this._triggerWithDeprecated( "change", triggerData );
5639
5681
  }, this));
5640
5682
  },
5641
5683
 
@@ -5667,6 +5709,15 @@ $.widget( "mobile.page", {
5667
5709
 
5668
5710
  // resolved and nulled on window.load()
5669
5711
  loadDeferred = $.Deferred(),
5712
+
5713
+ // function that resolves the above deferred
5714
+ pageIsFullyLoaded = function() {
5715
+
5716
+ // Resolve and null the deferred
5717
+ loadDeferred.resolve();
5718
+ loadDeferred = null;
5719
+ },
5720
+
5670
5721
  documentUrl = $.mobile.path.documentUrl,
5671
5722
 
5672
5723
  // used to track last vclicked element to make sure its value is added to form data
@@ -6084,12 +6135,12 @@ $.widget( "mobile.page", {
6084
6135
 
6085
6136
  $( function() { domreadyDeferred.resolve(); } );
6086
6137
 
6087
- $.mobile.window.load( function() {
6088
-
6089
- // Resolve and null the deferred
6090
- loadDeferred.resolve();
6091
- loadDeferred = null;
6092
- });
6138
+ // Account for the possibility that the load event has already fired
6139
+ if ( document.readyState === "complete" ) {
6140
+ pageIsFullyLoaded();
6141
+ } else {
6142
+ $.mobile.window.load( pageIsFullyLoaded );
6143
+ }
6093
6144
 
6094
6145
  $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } );
6095
6146
  })( jQuery );
@@ -7089,16 +7140,31 @@ $.mobile.collapsible.defaults = {
7089
7140
 
7090
7141
  (function( $, undefined ) {
7091
7142
 
7143
+ var uiScreenHiddenRegex = /\bui-screen-hidden\b/;
7144
+ function noHiddenClass( elements ) {
7145
+ var index,
7146
+ length = elements.length,
7147
+ result = [];
7148
+
7149
+ for ( index = 0; index < length; index++ ) {
7150
+ if ( !elements[ index ].className.match( uiScreenHiddenRegex ) ) {
7151
+ result.push( elements[ index ] );
7152
+ }
7153
+ }
7154
+
7155
+ return $( result );
7156
+ }
7157
+
7092
7158
  $.mobile.behaviors.addFirstLastClasses = {
7093
7159
  _getVisibles: function( $els, create ) {
7094
7160
  var visibles;
7095
7161
 
7096
7162
  if ( create ) {
7097
- visibles = $els.not( ".ui-screen-hidden" );
7163
+ visibles = noHiddenClass( $els );
7098
7164
  } else {
7099
7165
  visibles = $els.filter( ":visible" );
7100
7166
  if ( visibles.length === 0 ) {
7101
- visibles = $els.not( ".ui-screen-hidden" );
7167
+ visibles = noHiddenClass( $els );
7102
7168
  }
7103
7169
  }
7104
7170
 
@@ -7492,11 +7558,15 @@ $.widget( "mobile.listview", $.extend( {
7492
7558
  .attr( "title", $.trim( last.getEncodedText() ) )
7493
7559
  .addClass( altButtonClass )
7494
7560
  .empty();
7561
+
7562
+ // Reduce to the first anchor, because only the first gets the buttonClass
7563
+ a = a.first();
7495
7564
  } else if ( icon ) {
7496
7565
  buttonClass += " ui-btn-icon-right ui-icon-" + icon;
7497
7566
  }
7498
7567
 
7499
- a.first().addClass( buttonClass );
7568
+ // Apply buttonClass to the (first) anchor
7569
+ a.addClass( buttonClass );
7500
7570
  } else if ( isDivider ) {
7501
7571
  dividerTheme = ( getAttr( item[ 0 ], "theme" ) || o.dividerTheme || o.theme );
7502
7572
 
@@ -7539,7 +7609,7 @@ $.widget( "mobile.listview", $.extend( {
7539
7609
  $( this ).closest( "li" ).addClass( "ui-li-has-count" );
7540
7610
  });
7541
7611
  if ( countThemeClass ) {
7542
- countBubbles.addClass( countThemeClass );
7612
+ countBubbles.not( "[class*='ui-body-']" ).addClass( countThemeClass );
7543
7613
  }
7544
7614
 
7545
7615
  // Deprecated in 1.4. From 1.5 you have to add class ui-li-has-thumb or ui-li-has-icon to the LI.
@@ -7696,15 +7766,12 @@ $.widget( "mobile.checkboxradio", $.extend( {
7696
7766
  return input.jqmData( dataAttr ) ||
7697
7767
  input.closest( "form, fieldset" ).jqmData( dataAttr );
7698
7768
  },
7699
- // NOTE: Windows Phone could not find the label through a selector
7700
- // filter works though.
7701
- parentLabel = input.closest( "label" ),
7702
- label = parentLabel.length ? parentLabel :
7703
- input
7704
- .closest( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" )
7705
- .find( "label" )
7706
- .filter( "[for='" + escapeId( input[0].id ) + "']" )
7707
- .first(),
7769
+ label = this.options.enhanced ?
7770
+ {
7771
+ element: this.element.siblings( "label" ),
7772
+ isParent: false
7773
+ } :
7774
+ this._findLabel(),
7708
7775
  inputtype = input[0].type,
7709
7776
  checkedClass = "ui-" + inputtype + "-on",
7710
7777
  uncheckedClass = "ui-" + inputtype + "-off";
@@ -7717,7 +7784,8 @@ $.widget( "mobile.checkboxradio", $.extend( {
7717
7784
  this.options.disabled = true;
7718
7785
  }
7719
7786
 
7720
- o.iconpos = inheritAttr( input, "iconpos" ) || label.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
7787
+ o.iconpos = inheritAttr( input, "iconpos" ) ||
7788
+ label.element.attr( "data-" + $.mobile.ns + "iconpos" ) || o.iconpos,
7721
7789
 
7722
7790
  // Establish options
7723
7791
  o.mini = inheritAttr( input, "mini" ) || o.mini;
@@ -7725,8 +7793,8 @@ $.widget( "mobile.checkboxradio", $.extend( {
7725
7793
  // Expose for other methods
7726
7794
  $.extend( this, {
7727
7795
  input: input,
7728
- label: label,
7729
- parentLabel: parentLabel,
7796
+ label: label.element,
7797
+ labelIsParent: label.isParent,
7730
7798
  inputtype: inputtype,
7731
7799
  checkedClass: checkedClass,
7732
7800
  uncheckedClass: uncheckedClass
@@ -7736,7 +7804,7 @@ $.widget( "mobile.checkboxradio", $.extend( {
7736
7804
  this._enhance();
7737
7805
  }
7738
7806
 
7739
- this._on( label, {
7807
+ this._on( label.element, {
7740
7808
  vmouseover: "_handleLabelVMouseOver",
7741
7809
  vclick: "_handleLabelVClick"
7742
7810
  });
@@ -7752,10 +7820,36 @@ $.widget( "mobile.checkboxradio", $.extend( {
7752
7820
  this.refresh();
7753
7821
  },
7754
7822
 
7823
+ _findLabel: function() {
7824
+ var parentLabel, label, isParent,
7825
+ input = this.element,
7826
+ labelsList = input[ 0 ].labels;
7827
+
7828
+ if( labelsList && labelsList.length > 0 ) {
7829
+ label = $( labelsList[ 0 ] );
7830
+ isParent = $.contains( label[ 0 ], input[ 0 ] );
7831
+ } else {
7832
+ parentLabel = input.closest( "label" );
7833
+ isParent = ( parentLabel.length > 0 );
7834
+
7835
+ // NOTE: Windows Phone could not find the label through a selector
7836
+ // filter works though.
7837
+ label = isParent ? parentLabel :
7838
+ $( this.document[ 0 ].getElementsByTagName( "label" ) )
7839
+ .filter( "[for='" + escapeId( input[ 0 ].id ) + "']" )
7840
+ .first();
7841
+ }
7842
+
7843
+ return {
7844
+ element: label,
7845
+ isParent: isParent
7846
+ };
7847
+ },
7848
+
7755
7849
  _enhance: function() {
7756
7850
  this.label.addClass( "ui-btn ui-corner-all");
7757
7851
 
7758
- if ( this.parentLabel.length > 0 ) {
7852
+ if ( this.labelIsParent ) {
7759
7853
  this.input.add( this.label ).wrapAll( this._wrapper() );
7760
7854
  } else {
7761
7855
  //this.element.replaceWith( this.input.add( this.label ).wrapAll( this._wrapper() ) );
@@ -7792,7 +7886,7 @@ $.widget( "mobile.checkboxradio", $.extend( {
7792
7886
  // Adds checked attribute to checked input when keyboard is used
7793
7887
  this.element.prop( "checked", this.element.is( ":checked" ) );
7794
7888
  this._getInputSet().not( this.element ).prop( "checked", false );
7795
- this._updateAll();
7889
+ this._updateAll( true );
7796
7890
  },
7797
7891
 
7798
7892
  _handleLabelVMouseOver: function( event ) {
@@ -7854,7 +7948,7 @@ $.widget( "mobile.checkboxradio", $.extend( {
7854
7948
 
7855
7949
  // If we're inside a form
7856
7950
  if ( form ) {
7857
- formId = form.id;
7951
+ formId = form.getAttribute( "id" );
7858
7952
 
7859
7953
  // If the form has an ID, collect radios scattered throught the document which
7860
7954
  // nevertheless are part of the form by way of the value of their form attribute
@@ -7882,13 +7976,13 @@ $.widget( "mobile.checkboxradio", $.extend( {
7882
7976
  return radios;
7883
7977
  },
7884
7978
 
7885
- _updateAll: function() {
7979
+ _updateAll: function( changeTriggered ) {
7886
7980
  var self = this;
7887
7981
 
7888
7982
  this._getInputSet().each( function() {
7889
7983
  var $this = $( this );
7890
7984
 
7891
- if ( this.checked || self.inputtype === "checkbox" ) {
7985
+ if ( ( this.checked || self.inputtype === "checkbox" ) && !changeTriggered ) {
7892
7986
  $this.trigger( "change" );
7893
7987
  }
7894
7988
  })
@@ -9028,7 +9122,9 @@ $.widget( "mobile.slider", $.mobile.slider, {
9028
9122
  if ( o.popupEnabled && this._popup ) {
9029
9123
  this._positionPopup();
9030
9124
  this._popup.html( newValue );
9031
- } else if ( o.showValue && !this.options.mini ) {
9125
+ }
9126
+
9127
+ if ( o.showValue && !this.options.mini ) {
9032
9128
  this.handle.html( newValue );
9033
9129
  }
9034
9130
  },
@@ -10175,6 +10271,14 @@ $.widget( "mobile.popup", {
10175
10271
  history: !$.mobile.browser.oldIE
10176
10272
  },
10177
10273
 
10274
+ // When the user depresses the mouse/finger on an element inside the popup while the popup is
10275
+ // open, we ignore resize events for a short while. This prevents #6961.
10276
+ _handleDocumentVmousedown: function( theEvent ) {
10277
+ if ( this._isOpen && $.contains( this._ui.container[ 0 ], theEvent.target ) ) {
10278
+ this._ignoreResizeEvents();
10279
+ }
10280
+ },
10281
+
10178
10282
  _create: function() {
10179
10283
  var theElement = this.element,
10180
10284
  myId = theElement.attr( "id" ),
@@ -10185,6 +10289,10 @@ $.widget( "mobile.popup", {
10185
10289
  // it is determined whether there shall be AJAX nav.
10186
10290
  currentOptions.history = currentOptions.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
10187
10291
 
10292
+ this._on( this.document, {
10293
+ "vmousedown": "_handleDocumentVmousedown"
10294
+ });
10295
+
10188
10296
  // Define instance variables
10189
10297
  $.extend( this, {
10190
10298
  _scrollTop: 0,
@@ -11109,7 +11217,7 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11109
11217
  },
11110
11218
 
11111
11219
  _handleButtonVclickKeydown: function( event ) {
11112
- if ( this.options.disabled || this.isOpen ) {
11220
+ if ( this.options.disabled || this.isOpen || this.options.nativeMenu ) {
11113
11221
  return;
11114
11222
  }
11115
11223
 
@@ -11160,6 +11268,10 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11160
11268
  },
11161
11269
 
11162
11270
  _handleMenuPageHide: function() {
11271
+
11272
+ // After the dialog's done, we may want to trigger change if the value has actually changed
11273
+ this._delayedTrigger();
11274
+
11163
11275
  // TODO centralize page removal binding / handling in the page plugin.
11164
11276
  // Suggestion from @jblas to do refcounting
11165
11277
  //
@@ -11182,18 +11294,55 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11182
11294
  }
11183
11295
  },
11184
11296
 
11297
+ _handleListItemClick: function( event ) {
11298
+ var listItem = $( event.target ).closest( "li" ),
11299
+
11300
+ // Index of option tag to be selected
11301
+ oldIndex = this.select[ 0 ].selectedIndex,
11302
+ newIndex = $.mobile.getAttribute( listItem, "option-index" ),
11303
+ option = this._selectOptions().eq( newIndex )[ 0 ];
11304
+
11305
+ // Toggle selected status on the tag for multi selects
11306
+ option.selected = this.isMultiple ? !option.selected : true;
11307
+
11308
+ // Toggle checkbox class for multiple selects
11309
+ if ( this.isMultiple ) {
11310
+ listItem.find( "a" )
11311
+ .toggleClass( "ui-checkbox-on", option.selected )
11312
+ .toggleClass( "ui-checkbox-off", !option.selected );
11313
+ }
11314
+
11315
+ // If it's not a multiple select, trigger change after it has finished closing
11316
+ if ( !this.isMultiple && oldIndex !== newIndex ) {
11317
+ this._triggerChange = true;
11318
+ }
11319
+
11320
+ // Trigger change if it's a multiple select
11321
+ // Hide custom select for single selects only - otherwise focus clicked item
11322
+ // We need to grab the clicked item the hard way, because the list may have been rebuilt
11323
+ if ( this.isMultiple ) {
11324
+ this.select.trigger( "change" );
11325
+ this.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
11326
+ .find( "a" ).first().focus();
11327
+ }
11328
+ else {
11329
+ this.close();
11330
+ }
11331
+
11332
+ event.preventDefault();
11333
+ },
11334
+
11185
11335
  build: function() {
11186
11336
  var selectId, popupId, dialogId, label, thisPage, isMultiple, menuId,
11187
11337
  themeAttr, overlayTheme, overlayThemeAttr, dividerThemeAttr,
11188
11338
  menuPage, listbox, list, header, headerTitle, menuPageContent,
11189
- menuPageClose, headerClose, self,
11339
+ menuPageClose, headerClose,
11190
11340
  o = this.options;
11191
11341
 
11192
11342
  if ( o.nativeMenu ) {
11193
11343
  return this._super();
11194
11344
  }
11195
11345
 
11196
- self = this;
11197
11346
  selectId = this.selectId;
11198
11347
  popupId = selectId + "-listbox";
11199
11348
  dialogId = selectId + "-dialog";
@@ -11208,11 +11357,14 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11208
11357
  dividerThemeAttr = ( o.dividerTheme && isMultiple ) ? ( " data-" + $.mobile.ns + "divider-theme='" + o.dividerTheme + "'" ) : "";
11209
11358
  menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' class='ui-selectmenu' id='" + dialogId + "'" + themeAttr + overlayThemeAttr + ">" +
11210
11359
  "<div data-" + $.mobile.ns + "role='header'>" +
11211
- "<div class='ui-title'>" + label.getEncodedText() + "</div>"+
11360
+ "<div class='ui-title'></div>"+
11212
11361
  "</div>"+
11213
11362
  "<div data-" + $.mobile.ns + "role='content'></div>"+
11214
11363
  "</div>" );
11215
- listbox = $( "<div id='" + popupId + "' class='ui-selectmenu'></div>" ).insertAfter( this.select ).popup({ theme: o.overlayTheme });
11364
+ listbox = $( "<div" + themeAttr + overlayThemeAttr + " id='" + popupId +
11365
+ "' class='ui-selectmenu'></div>" )
11366
+ .insertAfter( this.select )
11367
+ .popup();
11216
11368
  list = $( "<ul class='ui-selectmenu-list' id='" + menuId + "' role='listbox' aria-labelledby='" + this.buttonId + "'" + themeAttr + dividerThemeAttr + "></ul>" ).appendTo( listbox );
11217
11369
  header = $( "<div class='ui-header ui-bar-" + ( o.theme ? o.theme : "inherit" ) + "'></div>" ).prependTo( listbox );
11218
11370
  headerTitle = $( "<h1 class='ui-title'></h1>" ).appendTo( header );
@@ -11268,52 +11420,18 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11268
11420
  // Events for list items
11269
11421
  this.list.attr( "role", "listbox" );
11270
11422
  this._on( this.list, {
11271
- focusin : "_handleListFocus",
11272
- focusout : "_handleListFocus",
11273
- keydown: "_handleListKeydown"
11423
+ "focusin": "_handleListFocus",
11424
+ "focusout": "_handleListFocus",
11425
+ "keydown": "_handleListKeydown",
11426
+ "click li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)": "_handleListItemClick"
11274
11427
  });
11275
- this.list
11276
- .delegate( "li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)", "click", function( event ) {
11277
-
11278
- // index of option tag to be selected
11279
- var oldIndex = self.select[ 0 ].selectedIndex,
11280
- newIndex = $.mobile.getAttribute( this, "option-index" ),
11281
- option = self._selectOptions().eq( newIndex )[ 0 ];
11282
-
11283
- // toggle selected status on the tag for multi selects
11284
- option.selected = self.isMultiple ? !option.selected : true;
11285
-
11286
- // toggle checkbox class for multiple selects
11287
- if ( self.isMultiple ) {
11288
- $( this ).find( "a" )
11289
- .toggleClass( "ui-checkbox-on", option.selected )
11290
- .toggleClass( "ui-checkbox-off", !option.selected );
11291
- }
11292
-
11293
- // trigger change if value changed
11294
- if ( self.isMultiple || oldIndex !== newIndex ) {
11295
- self.select.trigger( "change" );
11296
- }
11297
-
11298
- // hide custom select for single selects only - otherwise focus clicked item
11299
- // We need to grab the clicked item the hard way, because the list may have been rebuilt
11300
- if ( self.isMultiple ) {
11301
- self.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
11302
- .find( "a" ).first().focus();
11303
- }
11304
- else {
11305
- self.close();
11306
- }
11307
-
11308
- event.preventDefault();
11309
- });
11310
11428
 
11311
11429
  // button refocus ensures proper height calculation
11312
11430
  // by removing the inline style and ensuring page inclusion
11313
11431
  this._on( this.menuPage, { pagehide: "_handleMenuPageHide" } );
11314
11432
 
11315
11433
  // Events on the popup
11316
- this._on( this.listbox, { popupafterclose: "close" } );
11434
+ this._on( this.listbox, { popupafterclose: "_popupClosed" } );
11317
11435
 
11318
11436
  // Close button on small overlays
11319
11437
  if ( this.isMultiple ) {
@@ -11323,6 +11441,18 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11323
11441
  return this;
11324
11442
  },
11325
11443
 
11444
+ _popupClosed: function() {
11445
+ this.close();
11446
+ this._delayedTrigger();
11447
+ },
11448
+
11449
+ _delayedTrigger: function() {
11450
+ if ( this._triggerChange ) {
11451
+ this.element.trigger( "change" );
11452
+ }
11453
+ this._triggerChange = false;
11454
+ },
11455
+
11326
11456
  _isRebuildRequired: function() {
11327
11457
  var list = this.list.find( "li" ),
11328
11458
  options = this._selectOptions().not( ".ui-screen-hidden" );
@@ -11444,7 +11574,9 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11444
11574
 
11445
11575
  self.menuType = "page";
11446
11576
  self.menuPageContent.append( self.list );
11447
- self.menuPage.find( "div .ui-title" ).text( self.label.text() );
11577
+ self.menuPage
11578
+ .find( "div .ui-title" )
11579
+ .text( self.label.getEncodedText() || self.placeholder );
11448
11580
  } else {
11449
11581
  self.menuType = "overlay";
11450
11582
 
@@ -11486,7 +11618,7 @@ $.widget( "mobile.selectmenu", $.mobile.selectmenu, {
11486
11618
  }
11487
11619
 
11488
11620
  parent = option.parentNode;
11489
- text = $option.text();
11621
+ text = $option.getEncodedText();
11490
11622
  anchor = document.createElement( "a" );
11491
11623
  classes = [];
11492
11624
 
@@ -12069,15 +12201,7 @@ $.widget( "mobile.controlgroup", $.extend( {
12069
12201
  },
12070
12202
  _setOptions: function( o ) {
12071
12203
  if ( o.addBackBtn !== undefined ) {
12072
- if ( this.options.addBackBtn &&
12073
- this.role === "header" &&
12074
- $( ".ui-page" ).length > 1 &&
12075
- this.page[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) !== $.mobile.path.stripHash( location.hash ) &&
12076
- !this.leftbtn ) {
12077
- this._addBackButton();
12078
- } else {
12079
- this.element.find( ".ui-toolbar-back-btn" ).remove();
12080
- }
12204
+ this._updateBackButton();
12081
12205
  }
12082
12206
  if ( o.backBtnTheme != null ) {
12083
12207
  this.element
@@ -12104,6 +12228,8 @@ $.widget( "mobile.controlgroup", $.extend( {
12104
12228
  this._setRelative();
12105
12229
  if ( this.role === "footer" ) {
12106
12230
  this.element.appendTo( "body" );
12231
+ } else if ( this.role === "header" ) {
12232
+ this._updateBackButton();
12107
12233
  }
12108
12234
  }
12109
12235
  this._addHeadingClasses();
@@ -12125,25 +12251,71 @@ $.widget( "mobile.controlgroup", $.extend( {
12125
12251
  },
12126
12252
  // Deprecated in 1.4. As from 1.5 ui-btn-left/right classes have to be present in the markup.
12127
12253
  _addHeaderButtonClasses: function() {
12128
- var $headeranchors = this.element.children( "a, button" );
12129
- this.leftbtn = $headeranchors.hasClass( "ui-btn-left" );
12130
- this.rightbtn = $headeranchors.hasClass( "ui-btn-right" );
12254
+ var headerAnchors = this.element.children( "a, button" );
12255
+
12256
+ // Do not mistake a back button for a left toolbar button
12257
+ this.leftbtn = headerAnchors.hasClass( "ui-btn-left" ) &&
12258
+ !headerAnchors.hasClass( "ui-toolbar-back-btn" );
12131
12259
 
12132
- this.leftbtn = this.leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
12260
+ this.rightbtn = headerAnchors.hasClass( "ui-btn-right" );
12133
12261
 
12134
- this.rightbtn = this.rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
12262
+ // Filter out right buttons and back buttons
12263
+ this.leftbtn = this.leftbtn ||
12264
+ headerAnchors.eq( 0 )
12265
+ .not( ".ui-btn-right,.ui-toolbar-back-btn" )
12266
+ .addClass( "ui-btn-left" )
12267
+ .length;
12135
12268
 
12269
+ this.rightbtn = this.rightbtn || headerAnchors.eq( 1 ).addClass( "ui-btn-right" ).length;
12136
12270
  },
12137
- _addBackButton: function() {
12138
- var options = this.options,
12271
+ _updateBackButton: function() {
12272
+ var backButton,
12273
+ options = this.options,
12139
12274
  theme = options.backBtnTheme || options.theme;
12140
12275
 
12141
- $( "<a role='button' href='javascript:void(0);' " +
12142
- "class='ui-btn ui-corner-all ui-shadow ui-btn-left " +
12143
- ( theme ? "ui-btn-" + theme + " " : "" ) +
12144
- "ui-toolbar-back-btn ui-icon-carat-l ui-btn-icon-left' " +
12145
- "data-" + $.mobile.ns + "rel='back'>" + options.backBtnText + "</a>" )
12146
- .prependTo( this.element );
12276
+ // Retrieve the back button or create a new, empty one
12277
+ backButton = this._backButton = ( this._backButton || {} );
12278
+
12279
+ // We add a back button only if the option to do so is on
12280
+ if ( this.options.addBackBtn &&
12281
+
12282
+ // This must also be a header toolbar
12283
+ this.role === "header" &&
12284
+
12285
+ // There must be multiple pages in the DOM
12286
+ $( ".ui-page" ).length > 1 &&
12287
+ ( this.page ?
12288
+
12289
+ // If the toolbar is internal the page's URL must differ from the hash
12290
+ ( this.page[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) !==
12291
+ $.mobile.path.stripHash( location.hash ) ) :
12292
+
12293
+ // Otherwise, if the toolbar is external there must be at least one
12294
+ // history item to which one can go back
12295
+ ( $.mobile.navigate && $.mobile.navigate.history &&
12296
+ $.mobile.navigate.history.activeIndex > 0 ) ) &&
12297
+
12298
+ // The toolbar does not have a left button
12299
+ !this.leftbtn ) {
12300
+
12301
+ // Skip back button creation if one is already present
12302
+ if ( !backButton.attached ) {
12303
+ backButton.element = ( backButton.element ||
12304
+ $( "<a role='button' href='javascript:void(0);' " +
12305
+ "class='ui-btn ui-corner-all ui-shadow ui-btn-left " +
12306
+ ( theme ? "ui-btn-" + theme + " " : "" ) +
12307
+ "ui-toolbar-back-btn ui-icon-carat-l ui-btn-icon-left' " +
12308
+ "data-" + $.mobile.ns + "rel='back'>" + options.backBtnText +
12309
+ "</a>" ) )
12310
+ .prependTo( this.element );
12311
+ backButton.attached = true;
12312
+ }
12313
+
12314
+ // If we are not adding a back button, then remove the one present, if any
12315
+ } else if ( backButton.element ) {
12316
+ backButton.element.detach();
12317
+ backButton.attached = false;
12318
+ }
12147
12319
  },
12148
12320
  _addHeadingClasses: function() {
12149
12321
  this.element.children( "h1, h2, h3, h4, h5, h6" )
@@ -12901,14 +13073,6 @@ $.widget( "mobile.panel", {
12901
13073
  this.element.addClass( this._getPanelClasses() );
12902
13074
  },
12903
13075
 
12904
- _handleCloseClickAndEatEvent: function( event ) {
12905
- if ( !event.isDefaultPrevented() ) {
12906
- event.preventDefault();
12907
- this.close();
12908
- return false;
12909
- }
12910
- },
12911
-
12912
13076
  _handleCloseClick: function( event ) {
12913
13077
  if ( !event.isDefaultPrevented() ) {
12914
13078
  this.close();
@@ -12995,7 +13159,6 @@ $.widget( "mobile.panel", {
12995
13159
  });
12996
13160
  }
12997
13161
  this.toggle();
12998
- return false;
12999
13162
  }
13000
13163
  },
13001
13164
 
@@ -13065,7 +13228,7 @@ $.widget( "mobile.panel", {
13065
13228
  o = self.options,
13066
13229
 
13067
13230
  _openPanel = function() {
13068
- self.document.off( "panelclose" );
13231
+ self._off( self.document , "panelclose" );
13069
13232
  self._page().jqmData( "panel", "open" );
13070
13233
 
13071
13234
  if ( $.support.cssTransform3d && !!o.animate && o.display !== "overlay" ) {
@@ -13074,7 +13237,8 @@ $.widget( "mobile.panel", {
13074
13237
  }
13075
13238
 
13076
13239
  if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
13077
- self.element.animationComplete( complete, "transition" );
13240
+ ( self._wrapper || self.element )
13241
+ .animationComplete( complete, "transition" );
13078
13242
  } else {
13079
13243
  setTimeout( complete, 0 );
13080
13244
  }
@@ -13107,6 +13271,11 @@ $.widget( "mobile.panel", {
13107
13271
  },
13108
13272
  complete = function() {
13109
13273
 
13274
+ // Bail if the panel was closed before the opening animation has completed
13275
+ if ( !self._open ) {
13276
+ return;
13277
+ }
13278
+
13110
13279
  if ( o.display !== "overlay" ) {
13111
13280
  self._wrapper.addClass( o.classes.pageContentPrefix + "-open" );
13112
13281
  self._fixedToolbars().addClass( o.classes.pageContentPrefix + "-open" );
@@ -13122,8 +13291,8 @@ $.widget( "mobile.panel", {
13122
13291
  self._trigger( "beforeopen" );
13123
13292
 
13124
13293
  if ( self._page().jqmData( "panel" ) === "open" ) {
13125
- self.document.on( "panelclose", function() {
13126
- _openPanel();
13294
+ self._on( self.document, {
13295
+ "panelclose": _openPanel
13127
13296
  });
13128
13297
  } else {
13129
13298
  _openPanel();
@@ -13148,13 +13317,16 @@ $.widget( "mobile.panel", {
13148
13317
  }
13149
13318
 
13150
13319
  if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
13151
- self.element.animationComplete( complete, "transition" );
13320
+ ( self._wrapper || self.element )
13321
+ .animationComplete( complete, "transition" );
13152
13322
  } else {
13153
13323
  setTimeout( complete, 0 );
13154
13324
  }
13155
13325
 
13156
13326
  if ( self._modal ) {
13157
- self._modal.removeClass( self._modalOpenClasses );
13327
+ self._modal
13328
+ .removeClass( self._modalOpenClasses )
13329
+ .height( "" );
13158
13330
  }
13159
13331
  },
13160
13332
  complete = function() {
@@ -13409,14 +13581,17 @@ $.widget( "mobile.table", $.mobile.table, {
13409
13581
 
13410
13582
  // create the hide/show toggles
13411
13583
  this.headers.not( "td" ).each( function() {
13412
- var header = $( this ),
13413
- priority = $.mobile.getAttribute( this, "priority" ),
13414
- cells = header.add( header.jqmData( "cells" ) );
13584
+ var input, cells,
13585
+ header = $( this ),
13586
+ priority = $.mobile.getAttribute( this, "priority" );
13415
13587
 
13416
13588
  if ( priority ) {
13589
+ cells = header.add( header.jqmData( "cells" ) );
13417
13590
  cells.addClass( opts.classes.priorityPrefix + priority );
13418
13591
 
13419
- ( keep ? inputs.eq( checkboxIndex++ ) :
13592
+ // Make sure the (new?) checkbox is associated with its header via .jqmData() and
13593
+ // that, vice versa, the header is also associated with the checkbox
13594
+ input = ( keep ? inputs.eq( checkboxIndex++ ) :
13420
13595
  $("<label><input type='checkbox' checked />" +
13421
13596
  ( header.children( "abbr" ).first().attr( "title" ) ||
13422
13597
  header.text() ) +
@@ -13426,7 +13601,13 @@ $.widget( "mobile.table", $.mobile.table, {
13426
13601
  .checkboxradio( {
13427
13602
  theme: opts.columnPopupTheme
13428
13603
  }) )
13429
- .jqmData( "cells", cells );
13604
+
13605
+ // Associate the header with the checkbox
13606
+ .jqmData( "header", header )
13607
+ .jqmData( "cells", cells );
13608
+
13609
+ // Associate the checkbox with the header
13610
+ header.jqmData( "input", input );
13430
13611
  }
13431
13612
  });
13432
13613
 
@@ -13443,14 +13624,6 @@ $.widget( "mobile.table", $.mobile.table, {
13443
13624
  input.jqmData( "cells" )
13444
13625
  .toggleClass( "ui-table-cell-hidden", !checked )
13445
13626
  .toggleClass( "ui-table-cell-visible", checked );
13446
-
13447
- if ( input[ 0 ].getAttribute( "locked" ) ) {
13448
- input.removeAttr( "locked" );
13449
-
13450
- this._unlockCells( input.jqmData( "cells" ) );
13451
- } else {
13452
- input.attr( "locked", true );
13453
- }
13454
13627
  },
13455
13628
 
13456
13629
  _unlockCells: function( cells ) {
@@ -13500,9 +13673,33 @@ $.widget( "mobile.table", $.mobile.table, {
13500
13673
  },
13501
13674
 
13502
13675
  _refresh: function( create ) {
13676
+ var headers, hiddenColumns, index;
13677
+
13678
+ // Calling _super() here updates this.headers
13503
13679
  this._super( create );
13504
13680
 
13505
13681
  if ( !create && this.options.mode === "columntoggle" ) {
13682
+ headers = this.headers;
13683
+ hiddenColumns = [];
13684
+
13685
+ // Find the index of the column header associated with each old checkbox among the
13686
+ // post-refresh headers and, if the header is still there, make sure the corresponding
13687
+ // column will be hidden if the pre-refresh checkbox indicates that the column is
13688
+ // hidden by recording its index in the array of hidden columns.
13689
+ this._menu.find( "input" ).each( function() {
13690
+ var input = $( this ),
13691
+ header = input.jqmData( "header" ),
13692
+ index = headers.index( header[ 0 ] );
13693
+
13694
+ if ( index > -1 && !input.prop( "checked" ) ) {
13695
+
13696
+ // The column header associated with /this/ checkbox is still present in the
13697
+ // post-refresh table and the checkbox is not checked, so the column associated
13698
+ // with this column header is currently hidden. Let's record that.
13699
+ hiddenColumns.push( index );
13700
+ }
13701
+ });
13702
+
13506
13703
  // columns not being replaced must be cleared from input toggle-locks
13507
13704
  this._unlockCells( this.element.find( ".ui-table-cell-hidden, " +
13508
13705
  ".ui-table-cell-visible" ) );
@@ -13510,8 +13707,14 @@ $.widget( "mobile.table", $.mobile.table, {
13510
13707
  // update columntoggles and cells
13511
13708
  this._addToggles( this._menu, create );
13512
13709
 
13513
- // check/uncheck
13514
- this._setToggleState();
13710
+ // At this point all columns are visible, so uncheck the checkboxes that correspond to
13711
+ // those columns we've found to be hidden
13712
+ for ( index = hiddenColumns.length - 1 ; index > -1 ; index-- ) {
13713
+ headers.eq( hiddenColumns[ index ] ).jqmData( "input" )
13714
+ .prop( "checked", false )
13715
+ .checkboxradio( "refresh" )
13716
+ .trigger( "change" );
13717
+ }
13515
13718
  }
13516
13719
  },
13517
13720
 
@@ -13581,10 +13784,10 @@ $.widget( "mobile.table", $.mobile.table, {
13581
13784
  var cells = $( this ).jqmData( "cells" ),
13582
13785
  colstart = $.mobile.getAttribute( this, "colstart" ),
13583
13786
  hierarchyClass = cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top",
13584
- text = $( this ).text(),
13787
+ contents = $( this ).clone().contents(),
13585
13788
  iteration, filter;
13586
13789
 
13587
- if ( text !== "" ) {
13790
+ if ( contents.length > 0 ) {
13588
13791
 
13589
13792
  if ( hierarchyClass ) {
13590
13793
  iteration = parseInt( this.getAttribute( "colspan" ), 10 );
@@ -13594,18 +13797,21 @@ $.widget( "mobile.table", $.mobile.table, {
13594
13797
  filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")";
13595
13798
  }
13596
13799
 
13597
- table._addLabels( cells.filter( filter ), opts.classes.cellLabels + hierarchyClass, text );
13800
+ table._addLabels( cells.filter( filter ),
13801
+ opts.classes.cellLabels + hierarchyClass, contents );
13598
13802
  } else {
13599
- table._addLabels( cells, opts.classes.cellLabels, text );
13803
+ table._addLabels( cells, opts.classes.cellLabels, contents );
13600
13804
  }
13601
13805
 
13602
13806
  }
13603
13807
  });
13604
13808
  },
13605
13809
 
13606
- _addLabels: function( cells, label, text ) {
13810
+ _addLabels: function( cells, label, contents ) {
13607
13811
  // .not fixes #6006
13608
- cells.not( ":has(b." + label + ")" ).prepend( "<b class='" + label + "'>" + text + "</b>" );
13812
+ cells
13813
+ .not( ":has(b." + label + ")" )
13814
+ .prepend( $( "<b class='" + label + "'></b>" ).append( contents ) );
13609
13815
  }
13610
13816
  });
13611
13817
 
@@ -13664,7 +13870,9 @@ $.widget( "mobile.filterable", {
13664
13870
  }
13665
13871
 
13666
13872
  this._timer = this._delay( function() {
13667
- this._trigger( "beforefilter", null, { input: search } );
13873
+ if ( this._trigger( "beforefilter", null, { input: search } ) === false ) {
13874
+ return false;
13875
+ }
13668
13876
 
13669
13877
  // Change val as lastval for next execution
13670
13878
  search[ 0 ].setAttribute( "data-" + $.mobile.ns + "lastval", val );
@@ -13712,7 +13920,8 @@ $.widget( "mobile.filterable", {
13712
13920
  // If nothing is hidden, then the decision whether to hide or show the items
13713
13921
  // is based on the "filterReveal" option.
13714
13922
  if ( hide.length === 0 ) {
13715
- filterItems[ opts.filterReveal ? "addClass" : "removeClass" ]( "ui-screen-hidden" );
13923
+ filterItems[ ( opts.filterReveal && val.length === 0 ) ?
13924
+ "addClass" : "removeClass" ]( "ui-screen-hidden" );
13716
13925
  } else {
13717
13926
  $( hide ).addClass( "ui-screen-hidden" );
13718
13927
  $( show ).removeClass( "ui-screen-hidden" );
@@ -13763,6 +13972,8 @@ $.widget( "mobile.filterable", {
13763
13972
  this.document.find( selector );
13764
13973
 
13765
13974
  this._on( search, {
13975
+ keydown: "_onKeyDown",
13976
+ keypress: "_onKeyPress",
13766
13977
  keyup: "_onKeyUp",
13767
13978
  change: "_onKeyUp",
13768
13979
  input: "_onKeyUp"
@@ -13772,6 +13983,21 @@ $.widget( "mobile.filterable", {
13772
13983
  this._search = search;
13773
13984
  },
13774
13985
 
13986
+ // Prevent form submission
13987
+ _onKeyDown: function( event ) {
13988
+ if ( event.keyCode === $.ui.keyCode.ENTER ) {
13989
+ event.preventDefault();
13990
+ this._preventKeyPress = true;
13991
+ }
13992
+ },
13993
+
13994
+ _onKeyPress: function( event ) {
13995
+ if ( this._preventKeyPress ) {
13996
+ event.preventDefault();
13997
+ this._preventKeyPress = false;
13998
+ }
13999
+ },
14000
+
13775
14001
  _setOptions: function( options ) {
13776
14002
  var refilter = !( ( options.filterReveal === undefined ) &&
13777
14003
  ( options.filterCallback === undefined ) &&
@@ -13875,7 +14101,9 @@ $.widget( "mobile.filterable", $.mobile.filterable, {
13875
14101
  // Also trigger listviewbeforefilter if this widget is also a listview
13876
14102
  this._widget._trigger( "beforefilter", event, data );
13877
14103
  }
13878
- this._super( type, event, data );
14104
+
14105
+ // Passing back the response enables calling preventDefault()
14106
+ return this._super( type, event, data );
13879
14107
  },
13880
14108
 
13881
14109
  _setWidget: function( widget ) {
@@ -13990,6 +14218,49 @@ $.widget( "mobile.filterable", $.mobile.filterable, {
13990
14218
  }
13991
14219
  });
13992
14220
 
14221
+ // Instantiate a filterable on a listview that has the data-filter="true" attribute
14222
+ // This is not necessary for static content, because the auto-enhance takes care of instantiating
14223
+ // the filterable upon encountering data-filter="true". However, because of 1.3.x it is expected
14224
+ // that a listview with data-filter="true" will be filterable even if you just instantiate a
14225
+ // listview on it. The extension below ensures that this continues to happen in 1.4.x.
14226
+ $.widget( "mobile.listview", $.mobile.listview, {
14227
+ options: {
14228
+ filter: false
14229
+ },
14230
+ _create: function() {
14231
+ if ( this.options.filter === true &&
14232
+ !this.element.data( "mobile-filterable" ) ) {
14233
+ this.element.filterable();
14234
+ }
14235
+ return this._super();
14236
+ },
14237
+
14238
+ _afterListviewRefresh: function() {
14239
+ var filterable = this.element.data( "mobile-filterable" );
14240
+
14241
+ if ( this.options.filter === true && filterable ) {
14242
+ this._preventRefreshLoop = true;
14243
+ filterable.refresh();
14244
+ }
14245
+ },
14246
+
14247
+ // Eliminate infinite recursion caused by the fact that we call filterable.refresh() from
14248
+ // _afterListviewRefresh() above, which, in turn, calls _refreshChildWidget(), which, in
14249
+ // turn, calls listview refresh(), which would, in turn, calls _afterListviewRefresh()
14250
+ // above, if we wouldn't prevent that right here.
14251
+ refresh: function() {
14252
+ var returnValue;
14253
+
14254
+ if ( !this._preventRefreshLoop ) {
14255
+ returnValue = this._superApply( arguments );
14256
+ }
14257
+
14258
+ this._preventRefreshLoop = false;
14259
+
14260
+ return returnValue;
14261
+ }
14262
+ });
14263
+
13993
14264
  })( jQuery );
13994
14265
 
13995
14266
  /*!
@@ -14924,6 +15195,7 @@ $.widget( "ui.tabs", {
14924
15195
  var path = $.mobile.path,
14925
15196
  $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ),
14926
15197
  hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ),
15198
+ theLocation = $.mobile.path.parseLocation(),
14927
15199
  hashPage = document.getElementById( hash );
14928
15200
 
14929
15201
  // if no pages are found, create one with body's inner html
@@ -14937,7 +15209,8 @@ $.widget( "ui.tabs", {
14937
15209
 
14938
15210
  // unless the data url is already set set it to the pathname
14939
15211
  if ( !$this[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) ) {
14940
- $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search );
15212
+ $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) ||
15213
+ theLocation.pathname + theLocation.search );
14941
15214
  }
14942
15215
  });
14943
15216