jquery_mobile_rails 1.4.2 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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