aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tests/unit/tabs/tabs_deprecated.js10
-rw-r--r--tests/unit/widget/widget_core.js45
-rw-r--r--ui/jquery.ui.tabs.js256
-rw-r--r--ui/jquery.ui.widget.js74
4 files changed, 217 insertions, 168 deletions
diff --git a/tests/unit/tabs/tabs_deprecated.js b/tests/unit/tabs/tabs_deprecated.js
index 1323c774a..d0d13fa84 100644
--- a/tests/unit/tabs/tabs_deprecated.js
+++ b/tests/unit/tabs/tabs_deprecated.js
@@ -281,7 +281,7 @@ test( "enable", function() {
var element = $( "#tabs1" ).tabs({
disabled: [ 0, 1 ],
- enable: function ( event, ui ) {
+ enable: function( event, ui ) {
equals( ui.tab, element.find( ".ui-tabs-nav a" )[ 1 ], "ui.tab" );
equals( ui.panel, element.find( ".ui-tabs-panel" )[ 1 ], "ui.panel" );
equals( ui.index, 1, "ui.index" );
@@ -296,10 +296,10 @@ test( "disable", function() {
expect( 3 );
var element = $( "#tabs1" ).tabs({
- disable: function ( event, ui ) {
- equals( ui.tab, element.find( ".ui-tabs-nav a" )[ 1 ], "ui.tab" );
- equals( ui.panel, element.find( ".ui-tabs-panel" )[ 1 ], "ui.panel" );
- equals( ui.index, 1, "ui.index" );
+ disable: function( event, ui ) {
+ equals( ui.tab, element.find( ".ui-tabs-nav a" )[ 1 ], "ui.tab" );
+ equals( ui.panel, element.find( ".ui-tabs-panel" )[ 1 ], "ui.panel" );
+ equals( ui.index, 1, "ui.index" );
}
});
element.tabs( "disable", 1 );
diff --git a/tests/unit/widget/widget_core.js b/tests/unit/widget/widget_core.js
index df83abe91..86fa658aa 100644
--- a/tests/unit/widget/widget_core.js
+++ b/tests/unit/widget/widget_core.js
@@ -1050,11 +1050,54 @@ test( "redefine", function() {
}
});
- var instance = new $.ui.testWidget();
+ var instance = new $.ui.testWidget({});
instance.method( "foo" );
equal( $.ui.testWidget.foo, "bar", "static properties remain" );
});
+test( "redefine deep prototype chain", function() {
+ expect( 8 );
+ $.widget( "ui.testWidget", {
+ method: function( str ) {
+ strictEqual( this, instance, "original invoked with correct this" );
+ equal( str, "level 4", "original invoked with correct parameter" );
+ }
+ });
+ $.widget( "ui.testWidget2", $.ui.testWidget, {
+ method: function( str ) {
+ strictEqual( this, instance, "testWidget2 invoked with correct this" );
+ equal( str, "level 2", "testWidget2 invoked with correct parameter" );
+ this._super( "level 3" );
+ }
+ });
+ $.widget( "ui.testWidget3", $.ui.testWidget2, {
+ method: function( str ) {
+ strictEqual( this, instance, "testWidget3 invoked with correct this" );
+ equal( str, "level 1", "testWidget3 invoked with correct parameter" );
+ this._super( "level 2" );
+ }
+ });
+ // redefine testWidget after other widgets have inherited from it
+ // this tests whether the inheriting widgets get updated prototype chains
+ $.widget( "ui.testWidget", $.ui.testWidget, {
+ method: function( str ) {
+ strictEqual( this, instance, "new invoked with correct this" );
+ equal( str, "level 3", "new invoked with correct parameter" );
+ this._super( "level 4" );
+ }
+ });
+ // redefine testWidget3 after it has been automatically redefined
+ // this tests whether we properly handle _super() when the topmost prototype
+ // doesn't have the method defined
+ $.widget( "ui.testWidget3", $.ui.testWidget3, {} );
+
+ var instance = new $.ui.testWidget3({});
+ instance.method( "level 1" );
+
+ delete $.ui.testWidget3;
+ delete $.ui.testWidget2;
+});
+
asyncTest( "_delay", function() {
expect( 6 );
var order = 0,
diff --git a/ui/jquery.ui.tabs.js b/ui/jquery.ui.tabs.js
index 7a701405c..dc52591b9 100644
--- a/ui/jquery.ui.tabs.js
+++ b/ui/jquery.ui.tabs.js
@@ -596,86 +596,79 @@ if ( $.uiBackCompat !== false ) {
};
// url method
- (function( $, prototype ) {
- prototype.url = function( index, url ) {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ url: function( index, url ) {
this.anchors.eq( index ).attr( "href", url );
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// ajaxOptions and cache options
- (function( $, prototype ) {
- $.extend( prototype.options, {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
ajaxOptions: null,
cache: false
- });
-
- var _create = prototype._create,
- _setOption = prototype._setOption,
- _destroy = prototype._destroy,
- oldurl = prototype.url || $.noop;
-
- $.extend( prototype, {
- _create: function() {
- _create.call( this );
+ },
- var self = this;
+ _create: function() {
+ this._super();
- this.element.bind( "tabsbeforeload.tabs", function( event, ui ) {
- // tab is already cached
- if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) {
- event.preventDefault();
- return;
- }
+ var self = this;
- $.extend( ui.ajaxSettings, self.options.ajaxOptions, {
- error: function( xhr, s, e ) {
- try {
- // Passing index avoid a race condition when this method is
- // called after the user has selected another tab.
- // Pass the anchor that initiated this request allows
- // loadError to manipulate the tab content panel via $(a.hash)
- self.options.ajaxOptions.error( xhr, s, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] );
- }
- catch ( e ) {}
- }
- });
+ this.element.bind( "tabsbeforeload.tabs", function( event, ui ) {
+ // tab is already cached
+ if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) {
+ event.preventDefault();
+ return;
+ }
- ui.jqXHR.success(function() {
- if ( self.options.cache ) {
- $.data( ui.tab[ 0 ], "cache.tabs", true );
+ $.extend( ui.ajaxSettings, self.options.ajaxOptions, {
+ error: function( xhr, s, e ) {
+ try {
+ // Passing index avoid a race condition when this method is
+ // called after the user has selected another tab.
+ // Pass the anchor that initiated this request allows
+ // loadError to manipulate the tab content panel via $(a.hash)
+ self.options.ajaxOptions.error( xhr, s, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] );
}
- });
+ catch ( e ) {}
+ }
});
- },
- _setOption: function( key, value ) {
- // reset cache if switching from cached to not cached
- if ( key === "cache" && value === false ) {
- this.anchors.removeData( "cache.tabs" );
- }
- _setOption.apply( this, arguments );
- },
+ ui.jqXHR.success(function() {
+ if ( self.options.cache ) {
+ $.data( ui.tab[ 0 ], "cache.tabs", true );
+ }
+ });
+ });
+ },
- _destroy: function() {
+ _setOption: function( key, value ) {
+ // reset cache if switching from cached to not cached
+ if ( key === "cache" && value === false ) {
this.anchors.removeData( "cache.tabs" );
- _destroy.call( this );
- },
-
- url: function( index, url ){
- this.anchors.eq( index ).removeData( "cache.tabs" );
- oldurl.apply( this, arguments );
}
- });
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ this._super( key, value );
+ },
+
+ _destroy: function() {
+ this.anchors.removeData( "cache.tabs" );
+ this._super();
+ },
+
+ url: function( index, url ){
+ this.anchors.eq( index ).removeData( "cache.tabs" );
+ this._superApply( arguments );
+ }
+ });
// abort method
- (function( $, prototype ) {
- prototype.abort = function() {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ abort: function() {
if ( this.xhr ) {
this.xhr.abort();
}
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// spinner
$.widget( "ui.tabs", $.ui.tabs, {
@@ -702,16 +695,13 @@ if ( $.uiBackCompat !== false ) {
});
// enable/disable events
- (function( $, prototype ) {
- $.extend( prototype.options, {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
enable: null,
disable: null
- });
-
- var enable = prototype.enable,
- disable = prototype.disable;
+ },
- prototype.enable = function( index ) {
+ enable: function( index ) {
var options = this.options,
trigger;
@@ -720,14 +710,14 @@ if ( $.uiBackCompat !== false ) {
trigger = true;
}
- enable.apply( this, arguments );
+ this._superApply( arguments );
if ( trigger ) {
this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
}
- };
+ },
- prototype.disable = function( index ) {
+ disable: function( index ) {
var options = this.options,
trigger;
@@ -736,23 +726,23 @@ if ( $.uiBackCompat !== false ) {
trigger = true;
}
- disable.apply( this, arguments );
+ this._superApply( arguments );
if ( trigger ) {
this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
}
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// add/remove methods and events
- (function( $, prototype ) {
- $.extend( prototype.options, {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
add: null,
remove: null,
tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
- });
+ },
- prototype.add = function( url, label, index ) {
+ add: function( url, label, index ) {
if ( index === undefined ) {
index = this.anchors.length;
}
@@ -803,9 +793,9 @@ if ( $.uiBackCompat !== false ) {
this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
return this;
- };
+ },
- prototype.remove = function( index ) {
+ remove: function( index ) {
index = this._getIndex( index );
var options = this.options,
tab = this.lis.eq( index ).remove(),
@@ -832,125 +822,117 @@ if ( $.uiBackCompat !== false ) {
this._trigger( "remove", null, this._ui( tab.find( "a" )[ 0 ], panel[ 0 ] ) );
return this;
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// length method
- (function( $, prototype ) {
- prototype.length = function() {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ length: function() {
return this.anchors.length;
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// panel ids (idPrefix option + title attribute)
- (function( $, prototype ) {
- $.extend( prototype.options, {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
idPrefix: "ui-tabs-"
- });
+ },
- var _tabId = prototype._tabId;
- prototype._tabId = function( a ) {
+ _tabId: function( a ) {
return $( a ).attr( "aria-controls" ) ||
a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF-]/g, "" ) ||
this.options.idPrefix + getNextTabId();
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// _createPanel method
- (function( $, prototype ) {
- $.extend( prototype.options, {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
panelTemplate: "<div></div>"
- });
+ },
- var _createPanel = prototype._createPanel;
- prototype._createPanel = function( id ) {
+ _createPanel: function( id ) {
return $( this.options.panelTemplate )
- .attr( "id", id )
- .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
- .data( "destroy.tabs", true );
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ .attr( "id", id )
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .data( "destroy.tabs", true );
+ }
+ });
// selected option
- (function( $, prototype ) {
- var _create = prototype._create,
- _setOption = prototype._setOption,
- _eventHandler = prototype._eventHandler;
-
- prototype._create = function() {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ _create: function() {
var options = this.options;
if ( options.active === null && options.selected !== undefined ) {
options.active = options.selected === -1 ? false : options.selected;
}
- _create.call( this );
+ this._super();
options.selected = options.active;
if ( options.selected === false ) {
options.selected = -1;
}
- };
+ },
- prototype._setOption = function( key, value ) {
+ _setOption: function( key, value ) {
if ( key !== "selected" ) {
- return _setOption.apply( this, arguments );
+ return this._super( key, value );
}
var options = this.options;
- _setOption.call( this, "active", value === -1 ? false : value );
+ this._super( "active", value === -1 ? false : value );
options.selected = options.active;
if ( options.selected === false ) {
options.selected = -1;
}
- };
+ },
- prototype._eventHandler = function( event ) {
- _eventHandler.apply( this, arguments );
+ _eventHandler: function( event ) {
+ this._superApply( arguments );
this.options.selected = this.options.active;
if ( this.options.selected === false ) {
this.options.selected = -1;
}
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// show and select event
- (function( $, prototype ) {
- $.extend( prototype.options, {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
show: null,
select: null
- });
- var _create = prototype._create,
- _trigger = prototype._trigger;
-
- prototype._create = function() {
- _create.call( this );
+ },
+ _create: function() {
+ this._super();
if ( this.options.active !== false ) {
this._trigger( "show", null, this._ui(
this.active[ 0 ], this._getPanelForTab( this.active )[ 0 ] ) );
}
- };
- prototype._trigger = function( type, event, data ) {
- var ret = _trigger.apply( this, arguments );
+ },
+ _trigger: function( type, event, data ) {
+ var ret = this._superApply( arguments );
if ( !ret ) {
return false;
}
if ( type === "beforeActivate" && data.newTab.length ) {
- ret = _trigger.call( this, "select", event, {
+ ret = this._super( "select", event, {
tab: data.newTab[ 0],
panel: data.newPanel[ 0 ],
index: data.newTab.closest( "li" ).index()
});
} else if ( type === "activate" && data.newTab.length ) {
- ret = _trigger.call( this, "show", event, {
+ ret = this._super( "show", event, {
tab: data.newTab[ 0 ],
panel: data.newPanel[ 0 ],
index: data.newTab.closest( "li" ).index()
});
}
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ return ret;
+ }
+ });
// select method
- (function( $, prototype ) {
- prototype.select = function( index ) {
+ $.widget( "ui.tabs", $.ui.tabs, {
+ select: function( index ) {
index = this._getIndex( index );
if ( index === -1 ) {
if ( this.options.collapsible && this.options.selected !== -1 ) {
@@ -960,8 +942,8 @@ if ( $.uiBackCompat !== false ) {
}
}
this.anchors.eq( index ).trigger( this.options.event + ".tabs" );
- };
- }( jQuery, jQuery.ui.tabs.prototype ) );
+ }
+ });
// cookie option
var listId = 0;
diff --git a/ui/jquery.ui.widget.js b/ui/jquery.ui.widget.js
index ad03e6f44..bf26373e1 100644
--- a/ui/jquery.ui.widget.js
+++ b/ui/jquery.ui.widget.js
@@ -23,8 +23,9 @@ $.cleanData = function( elems ) {
};
$.widget = function( name, base, prototype ) {
- var namespace = name.split( "." )[ 0 ],
- fullName;
+ var fullName, existingConstructor, constructor, basePrototype,
+ namespace = name.split( "." )[ 0 ];
+
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
@@ -39,12 +40,11 @@ $.widget = function( name, base, prototype ) {
};
$[ namespace ] = $[ namespace ] || {};
- // create the constructor using $.extend() so we can carry over any
- // static properties stored on the existing constructor (if there is one)
- $[ namespace ][ name ] = $.extend( function( options, element ) {
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
// allow instantiation without "new" keyword
if ( !this._createWidget ) {
- return new $[ namespace ][ name ]( options, element );
+ return new constructor( options, element );
}
// allow instantiation without initializing for simple inheritance
@@ -52,9 +52,19 @@ $.widget = function( name, base, prototype ) {
if ( arguments.length ) {
this._createWidget( options, element );
}
- }, $[ namespace ][ name ], { version: prototype.version } );
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
- var basePrototype = new base();
+ basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
@@ -83,17 +93,41 @@ $.widget = function( name, base, prototype ) {
return returnValue;
};
- }());
+ })();
}
});
- $[ namespace ][ name ].prototype = $.widget.extend( basePrototype, {
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: name
+ }, prototype, {
+ constructor: constructor,
namespace: namespace,
widgetName: name,
- widgetEventPrefix: name,
widgetBaseClass: fullName
- }, prototype );
+ });
- $.widget.bridge( name, $[ namespace ][ name ] );
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
};
$.widget.extend = function( target ) {
@@ -157,18 +191,8 @@ $.widget.bridge = function( name, object ) {
};
};
-$.Widget = function( options, element ) {
- // allow instantiation without "new" keyword
- if ( !this._createWidget ) {
- return new $[ namespace ][ name ]( options, element );
- }
-
- // allow instantiation without initializing for simple inheritance
- // must use "new" keyword (the code above always passes args)
- if ( arguments.length ) {
- this._createWidget( options, element );
- }
-};
+$.Widget = function( options, element ) {};
+$.Widget._childConstructors = [];
$.Widget.prototype = {
widgetName: "widget",