Skip to content
This repository
Browse code

[widget] Took widget from jQuery UI 1.9m7 - Fixes: #3544

  • Loading branch information...
commit 63dc7b2b7ab9697a0b86f9aca6b7ca1cef3f269a 1 parent 29465d7
Gabriel "_|Nix|_" Schulhof authored April 16, 2012

Showing 1 changed file with 336 additions and 109 deletions. Show diff stats Hide diff stats

  1. 445  js/jquery.ui.widget.js
445  js/jquery.ui.widget.js
... ...
@@ -1,43 +1,31 @@
1 1
 /*!
2 2
  * jQuery UI Widget @VERSION
3 3
  *
4  
- * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
  4
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
5 5
  * Dual licensed under the MIT or GPL Version 2 licenses.
6 6
  * http://jquery.org/license
7 7
  *
8 8
  * http://docs.jquery.com/UI/Widget
9 9
  */
10  
-
11 10
 (function( $, undefined ) {
12 11
 
13  
-// jQuery 1.4+
14  
-if ( $.cleanData ) {
15  
-	var _cleanData = $.cleanData;
16  
-	$.cleanData = function( elems ) {
17  
-		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
  12
+var slice = Array.prototype.slice;
  13
+
  14
+var _cleanData = $.cleanData;
  15
+$.cleanData = function( elems ) {
  16
+	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
  17
+		try {
18 18
 			$( elem ).triggerHandler( "remove" );
19  
-		}
20  
-		_cleanData( elems );
21  
-	};
22  
-} else {
23  
-	var _remove = $.fn.remove;
24  
-	$.fn.remove = function( selector, keepData ) {
25  
-		return this.each(function() {
26  
-			if ( !keepData ) {
27  
-				if ( !selector || $.filter( selector, [ this ] ).length ) {
28  
-					$( "*", this ).add( [ this ] ).each(function() {
29  
-						$( this ).triggerHandler( "remove" );
30  
-					});
31  
-				}
32  
-			}
33  
-			return _remove.call( $(this), selector, keepData );
34  
-		});
35  
-	};
36  
-}
  19
+		// http://bugs.jquery.com/ticket/8235
  20
+		} catch( e ) {}
  21
+	}
  22
+	_cleanData( elems );
  23
+};
37 24
 
38 25
 $.widget = function( name, base, prototype ) {
39  
-	var namespace = name.split( "." )[ 0 ],
40  
-		fullName;
  26
+	var fullName, existingConstructor, constructor, basePrototype,
  27
+		namespace = name.split( "." )[ 0 ];
  28
+
41 29
 	name = name.split( "." )[ 1 ];
42 30
 	fullName = namespace + "-" + name;
43 31
 
@@ -48,76 +36,156 @@ $.widget = function( name, base, prototype ) {
48 36
 
49 37
 	// create selector for plugin
50 38
 	$.expr[ ":" ][ fullName ] = function( elem ) {
51  
-		return !!$.data( elem, name );
  39
+		return !!$.data( elem, fullName );
52 40
 	};
53 41
 
54 42
 	$[ namespace ] = $[ namespace ] || {};
55  
-	$[ namespace ][ name ] = function( options, element ) {
  43
+	existingConstructor = $[ namespace ][ name ];
  44
+	constructor = $[ namespace ][ name ] = function( options, element ) {
  45
+		// allow instantiation without "new" keyword
  46
+		if ( !this._createWidget ) {
  47
+			return new constructor( options, element );
  48
+		}
  49
+
56 50
 		// allow instantiation without initializing for simple inheritance
  51
+		// must use "new" keyword (the code above always passes args)
57 52
 		if ( arguments.length ) {
58 53
 			this._createWidget( options, element );
59 54
 		}
60 55
 	};
61  
-
62  
-	var basePrototype = new base();
  56
+	// extend with the existing constructor to carry over any static properties
  57
+	$.extend( constructor, existingConstructor, {
  58
+		version: prototype.version,
  59
+		// copy the object used to create the prototype in case we need to
  60
+		// redefine the widget later
  61
+		_proto: $.extend( {}, prototype ),
  62
+		// track widgets that inherit from this widget in case this widget is
  63
+		// redefined after a widget inherits from it
  64
+		_childConstructors: []
  65
+	});
  66
+
  67
+	basePrototype = new base();
63 68
 	// we need to make the options hash a property directly on the new instance
64 69
 	// otherwise we'll modify the options hash on the prototype that we're
65 70
 	// inheriting from
66  
-//	$.each( basePrototype, function( key, val ) {
67  
-//		if ( $.isPlainObject(val) ) {
68  
-//			basePrototype[ key ] = $.extend( {}, val );
69  
-//		}
70  
-//	});
71  
-	basePrototype.options = $.extend( true, {}, basePrototype.options );
72  
-	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
  71
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
  72
+	$.each( prototype, function( prop, value ) {
  73
+		if ( $.isFunction( value ) ) {
  74
+			prototype[ prop ] = (function() {
  75
+				var _super = function() {
  76
+					return base.prototype[ prop ].apply( this, arguments );
  77
+				};
  78
+				var _superApply = function( args ) {
  79
+					return base.prototype[ prop ].apply( this, args );
  80
+				};
  81
+				return function() {
  82
+					var __super = this._super,
  83
+						__superApply = this._superApply,
  84
+						returnValue;
  85
+
  86
+					this._super = _super;
  87
+					this._superApply = _superApply;
  88
+
  89
+					returnValue = value.apply( this, arguments );
  90
+
  91
+					this._super = __super;
  92
+					this._superApply = __superApply;
  93
+
  94
+					return returnValue;
  95
+				};
  96
+			})();
  97
+		}
  98
+	});
  99
+	constructor.prototype = $.widget.extend( basePrototype, {
  100
+		// TODO: remove support for widgetEventPrefix
  101
+		// always use the name + a colon as the prefix, e.g., draggable:start
  102
+		// don't prefix for widgets that aren't DOM-based
  103
+		widgetEventPrefix: name
  104
+	}, prototype, {
  105
+		constructor: constructor,
73 106
 		namespace: namespace,
74 107
 		widgetName: name,
75  
-		widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
76  
-		widgetBaseClass: fullName
77  
-	}, prototype );
  108
+		// TODO remove widgetBaseClass, see #8155
  109
+		widgetBaseClass: fullName,
  110
+		widgetFullName: fullName
  111
+	});
  112
+
  113
+	// If this widget is being redefined then we need to find all widgets that
  114
+	// are inheriting from it and redefine all of them so that they inherit from
  115
+	// the new version of this widget. We're essentially trying to replace one
  116
+	// level in the prototype chain.
  117
+	if ( existingConstructor ) {
  118
+		$.each( existingConstructor._childConstructors, function( i, child ) {
  119
+			var childPrototype = child.prototype;
  120
+
  121
+			// redefine the child widget using the same prototype that was
  122
+			// originally used, but inherit from the new version of the base
  123
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
  124
+		});
  125
+		// remove the list of existing child constructors from the old constructor
  126
+		// so the old child constructors can be garbage collected
  127
+		delete existingConstructor._childConstructors;
  128
+	} else {
  129
+		base._childConstructors.push( constructor );
  130
+	}
  131
+
  132
+	$.widget.bridge( name, constructor );
  133
+};
78 134
 
79  
-	$.widget.bridge( name, $[ namespace ][ name ] );
  135
+$.widget.extend = function( target ) {
  136
+	var input = slice.call( arguments, 1 ),
  137
+		inputIndex = 0,
  138
+		inputLength = input.length,
  139
+		key,
  140
+		value;
  141
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
  142
+		for ( key in input[ inputIndex ] ) {
  143
+			value = input[ inputIndex ][ key ];
  144
+			if (input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
  145
+				target[ key ] = $.isPlainObject( value ) ? $.widget.extend( {}, target[ key ], value ) : value;
  146
+			}
  147
+		}
  148
+	}
  149
+	return target;
80 150
 };
81 151
 
82 152
 $.widget.bridge = function( name, object ) {
  153
+	var fullName = object.prototype.widgetFullName;
83 154
 	$.fn[ name ] = function( options ) {
84 155
 		var isMethodCall = typeof options === "string",
85  
-			args = Array.prototype.slice.call( arguments, 1 ),
  156
+			args = slice.call( arguments, 1 ),
86 157
 			returnValue = this;
87 158
 
88 159
 		// allow multiple hashes to be passed on init
89 160
 		options = !isMethodCall && args.length ?
90  
-			$.extend.apply( null, [ true, options ].concat(args) ) :
  161
+			$.widget.extend.apply( null, [ options ].concat(args) ) :
91 162
 			options;
92 163
 
93  
-		// prevent calls to internal methods
94  
-		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
95  
-			return returnValue;
96  
-		}
97  
-
98 164
 		if ( isMethodCall ) {
99 165
 			this.each(function() {
100  
-				var instance = $.data( this, name );
  166
+				var instance = $.data( this, fullName );
101 167
 				if ( !instance ) {
102  
-					throw "cannot call methods on " + name + " prior to initialization; " +
103  
-						"attempted to call method '" + options + "'";
  168
+					return $.error( "cannot call methods on " + name + " prior to initialization; " +
  169
+						"attempted to call method '" + options + "'" );
104 170
 				}
105  
-				if ( !$.isFunction( instance[options] ) ) {
106  
-					throw "no such method '" + options + "' for " + name + " widget instance";
  171
+				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
  172
+					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
107 173
 				}
108 174
 				var methodValue = instance[ options ].apply( instance, args );
109 175
 				if ( methodValue !== instance && methodValue !== undefined ) {
110  
-					returnValue = methodValue;
  176
+					returnValue = methodValue && methodValue.jquery ?
  177
+						returnValue.pushStack( methodValue.get() ) :
  178
+						methodValue;
111 179
 					return false;
112 180
 				}
113 181
 			});
114 182
 		} else {
115 183
 			this.each(function() {
116  
-				var instance = $.data( this, name );
  184
+				var instance = $.data( this, fullName );
117 185
 				if ( instance ) {
118 186
 					instance.option( options || {} )._init();
119 187
 				} else {
120  
-					$.data( this, name, new object( options, this ) );
  188
+					new object( options, this );
121 189
 				}
122 190
 			});
123 191
 		}
@@ -126,78 +194,115 @@ $.widget.bridge = function( name, object ) {
126 194
 	};
127 195
 };
128 196
 
129  
-$.Widget = function( options, element ) {
130  
-	// allow instantiation without initializing for simple inheritance
131  
-	if ( arguments.length ) {
132  
-		this._createWidget( options, element );
133  
-	}
134  
-};
  197
+$.Widget = function( options, element ) {};
  198
+$.Widget._childConstructors = [];
135 199
 
136 200
 $.Widget.prototype = {
137 201
 	widgetName: "widget",
138 202
 	widgetEventPrefix: "",
  203
+	defaultElement: "<div>",
139 204
 	options: {
140  
-		disabled: false
  205
+		disabled: false,
  206
+
  207
+		// callbacks
  208
+		create: null
141 209
 	},
142 210
 	_createWidget: function( options, element ) {
143  
-		// $.widget.bridge stores the plugin instance, but we do it anyway
144  
-		// so that it's stored even before the _create function runs
145  
-		$.data( element, this.widgetName, this );
  211
+		element = $( element || this.defaultElement || this )[ 0 ];
146 212
 		this.element = $( element );
147  
-		this.options = $.extend( true, {},
  213
+		this.options = $.widget.extend( {},
148 214
 			this.options,
149 215
 			this._getCreateOptions(),
150 216
 			options );
151 217
 
152  
-		var self = this;
153  
-		this.element.bind( "remove." + this.widgetName, function() {
154  
-			self.destroy();
155  
-		});
  218
+		this.bindings = $();
  219
+		this.hoverable = $();
  220
+		this.focusable = $();
  221
+
  222
+		if ( element !== this ) {
  223
+			// 1.9 BC for #7810
  224
+			// TODO remove dual storage
  225
+			$.data( element, this.widgetName, this );
  226
+			$.data( element, this.widgetFullName, this );
  227
+			this._bind({ remove: "destroy" });
  228
+			this.document = $( element.style ?
  229
+				// element within the document
  230
+				element.ownerDocument :
  231
+				// element is window or document
  232
+				element.document || element );
  233
+			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
  234
+		}
156 235
 
157 236
 		this._create();
158  
-		this._trigger( "create" );
  237
+		this._trigger( "create", null, this._getCreateEventData() );
159 238
 		this._init();
160 239
 	},
161  
-	_getCreateOptions: function() {
162  
-		var options = {};
163  
-		if ( $.metadata ) {
164  
-			options = $.metadata.get( element )[ this.widgetName ];
165  
-		}
166  
-		return options;
167  
-	},
168  
-	_create: function() {},
169  
-	_init: function() {},
  240
+	_getCreateOptions: $.noop,
  241
+	_getCreateEventData: $.noop,
  242
+	_create: $.noop,
  243
+	_init: $.noop,
170 244
 
171 245
 	destroy: function() {
  246
+		this._destroy();
  247
+		// we can probably remove the unbind calls in 2.0
  248
+		// all event bindings should go through this._bind()
172 249
 		this.element
173 250
 			.unbind( "." + this.widgetName )
174  
-			.removeData( this.widgetName );
  251
+			// 1.9 BC for #7810
  252
+			// TODO remove dual storage
  253
+			.removeData( this.widgetName )
  254
+			.removeData( this.widgetFullName );
175 255
 		this.widget()
176 256
 			.unbind( "." + this.widgetName )
177 257
 			.removeAttr( "aria-disabled" )
178 258
 			.removeClass(
179  
-				this.widgetBaseClass + "-disabled " +
  259
+				this.widgetFullName + "-disabled " +
180 260
 				"ui-state-disabled" );
  261
+
  262
+		// clean up events and states
  263
+		this.bindings.unbind( "." + this.widgetName );
  264
+		this.hoverable.removeClass( "ui-state-hover" );
  265
+		this.focusable.removeClass( "ui-state-focus" );
181 266
 	},
  267
+	_destroy: $.noop,
182 268
 
183 269
 	widget: function() {
184 270
 		return this.element;
185 271
 	},
186 272
 
187 273
 	option: function( key, value ) {
188  
-		var options = key;
  274
+		var options = key,
  275
+			parts,
  276
+			curOption,
  277
+			i;
189 278
 
190 279
 		if ( arguments.length === 0 ) {
191 280
 			// don't return a reference to the internal hash
192  
-			return $.extend( {}, this.options );
  281
+			return $.widget.extend( {}, this.options );
193 282
 		}
194 283
 
195  
-		if  (typeof key === "string" ) {
196  
-			if ( value === undefined ) {
197  
-				return this.options[ key ];
198  
-			}
  284
+		if ( typeof key === "string" ) {
  285
+			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
199 286
 			options = {};
200  
-			options[ key ] = value;
  287
+			parts = key.split( "." );
  288
+			key = parts.shift();
  289
+			if ( parts.length ) {
  290
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
  291
+				for ( i = 0; i < parts.length - 1; i++ ) {
  292
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
  293
+					curOption = curOption[ parts[ i ] ];
  294
+				}
  295
+				key = parts.pop();
  296
+				if ( value === undefined ) {
  297
+					return curOption[ key ] === undefined ? null : curOption[ key ];
  298
+				}
  299
+				curOption[ key ] = value;
  300
+			} else {
  301
+				if ( value === undefined ) {
  302
+					return this.options[ key ] === undefined ? null : this.options[ key ];
  303
+				}
  304
+				options[ key ] = value;
  305
+			}
201 306
 		}
202 307
 
203 308
 		this._setOptions( options );
@@ -205,10 +310,11 @@ $.Widget.prototype = {
205 310
 		return this;
206 311
 	},
207 312
 	_setOptions: function( options ) {
208  
-		var self = this;
209  
-		$.each( options, function( key, value ) {
210  
-			self._setOption( key, value );
211  
-		});
  313
+		var key;
  314
+
  315
+		for ( key in options ) {
  316
+			this._setOption( key, options[ key ] );
  317
+		}
212 318
 
213 319
 		return this;
214 320
 	},
@@ -217,10 +323,10 @@ $.Widget.prototype = {
217 323
 
218 324
 		if ( key === "disabled" ) {
219 325
 			this.widget()
220  
-				[ value ? "addClass" : "removeClass"](
221  
-					this.widgetBaseClass + "-disabled" + " " +
222  
-					"ui-state-disabled" )
  326
+				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
223 327
 				.attr( "aria-disabled", value );
  328
+			this.hoverable.removeClass( "ui-state-hover" );
  329
+			this.focusable.removeClass( "ui-state-focus" );
224 330
 		}
225 331
 
226 332
 		return this;
@@ -233,31 +339,152 @@ $.Widget.prototype = {
233 339
 		return this._setOption( "disabled", true );
234 340
 	},
235 341
 
  342
+	_bind: function( element, handlers ) {
  343
+		// no element argument, shuffle and use this.element
  344
+		if ( !handlers ) {
  345
+			handlers = element;
  346
+			element = this.element;
  347
+		} else {
  348
+			// accept selectors, DOM elements
  349
+			element = $( element );
  350
+			this.bindings = this.bindings.add( element );
  351
+		}
  352
+
  353
+		var instance = this;
  354
+		$.each( handlers, function( event, handler ) {
  355
+			function handlerProxy() {
  356
+				// allow widgets to customize the disabled handling
  357
+				// - disabled as an array instead of boolean
  358
+				// - disabled class as method for disabling individual parts
  359
+				if ( instance.options.disabled === true ||
  360
+						$( this ).hasClass( "ui-state-disabled" ) ) {
  361
+					return;
  362
+				}
  363
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
  364
+					.apply( instance, arguments );
  365
+			}
  366
+
  367
+			// copy the guid so direct unbinding works
  368
+			if ( typeof handler !== "string" ) {
  369
+				handlerProxy.guid = handler.guid =
  370
+					handler.guid || handlerProxy.guid || jQuery.guid++;
  371
+			}
  372
+
  373
+			var match = event.match( /^(\w+)\s*(.*)$/ ),
  374
+				eventName = match[1] + "." + instance.widgetName,
  375
+				selector = match[2];
  376
+			if ( selector ) {
  377
+				instance.widget().delegate( selector, eventName, handlerProxy );
  378
+			} else {
  379
+				element.bind( eventName, handlerProxy );
  380
+			}
  381
+		});
  382
+	},
  383
+
  384
+	_delay: function( handler, delay ) {
  385
+		function handlerProxy() {
  386
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
  387
+				.apply( instance, arguments );
  388
+		}
  389
+		var instance = this;
  390
+		return setTimeout( handlerProxy, delay || 0 );
  391
+	},
  392
+
  393
+	_hoverable: function( element ) {
  394
+		this.hoverable = this.hoverable.add( element );
  395
+		this._bind( element, {
  396
+			mouseenter: function( event ) {
  397
+				$( event.currentTarget ).addClass( "ui-state-hover" );
  398
+			},
  399
+			mouseleave: function( event ) {
  400
+				$( event.currentTarget ).removeClass( "ui-state-hover" );
  401
+			}
  402
+		});
  403
+	},
  404
+
  405
+	_focusable: function( element ) {
  406
+		this.focusable = this.focusable.add( element );
  407
+		this._bind( element, {
  408
+			focusin: function( event ) {
  409
+				$( event.currentTarget ).addClass( "ui-state-focus" );
  410
+			},
  411
+			focusout: function( event ) {
  412
+				$( event.currentTarget ).removeClass( "ui-state-focus" );
  413
+			}
  414
+		});
  415
+	},
  416
+
236 417
 	_trigger: function( type, event, data ) {
237  
-		var callback = this.options[ type ];
  418
+		var prop, orig,
  419
+			callback = this.options[ type ];
238 420
 
  421
+		data = data || {};
239 422
 		event = $.Event( event );
240 423
 		event.type = ( type === this.widgetEventPrefix ?
241 424
 			type :
242 425
 			this.widgetEventPrefix + type ).toLowerCase();
243  
-		data = data || {};
  426
+		// the original event may come from any element
  427
+		// so we need to reset the target on the new event
  428
+		event.target = this.element[ 0 ];
244 429
 
245 430
 		// copy original event properties over to the new event
246  
-		// this would happen if we could call $.event.fix instead of $.Event
247  
-		// but we don't have a way to force an event to be fixed multiple times
248  
-		if ( event.originalEvent ) {
249  
-			for ( var i = $.event.props.length, prop; i; ) {
250  
-				prop = $.event.props[ --i ];
251  
-				event[ prop ] = event.originalEvent[ prop ];
  431
+		orig = event.originalEvent;
  432
+		if ( orig ) {
  433
+			for ( prop in orig ) {
  434
+				if ( !( prop in event ) ) {
  435
+					event[ prop ] = orig[ prop ];
  436
+				}
252 437
 			}
253 438
 		}
254 439
 
255 440
 		this.element.trigger( event, data );
256  
-
257  
-		return !( $.isFunction(callback) &&
258  
-			callback.call( this.element[0], event, data ) === false ||
  441
+		return !( $.isFunction( callback ) &&
  442
+			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
259 443
 			event.isDefaultPrevented() );
260 444
 	}
261 445
 };
262 446
 
  447
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
  448
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
  449
+		if ( typeof options === "string" ) {
  450
+			options = { effect: options };
  451
+		}
  452
+		var hasOptions,
  453
+			effectName = !options ?
  454
+				method :
  455
+				options === true || typeof options === "number" ?
  456
+					defaultEffect :
  457
+					options.effect || defaultEffect;
  458
+		options = options || {};
  459
+		if ( typeof options === "number" ) {
  460
+			options = { duration: options };
  461
+		}
  462
+		hasOptions = !$.isEmptyObject( options );
  463
+		options.complete = callback;
  464
+		if ( options.delay ) {
  465
+			element.delay( options.delay );
  466
+		}
  467
+		if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
  468
+			element[ method ]( options );
  469
+		} else if ( effectName !== method && element[ effectName ] ) {
  470
+			element[ effectName ]( options.duration, options.easing, callback );
  471
+		} else {
  472
+			element.queue(function( next ) {
  473
+				$( this )[ method ]();
  474
+				if ( callback ) {
  475
+					callback.call( element[ 0 ] );
  476
+				}
  477
+				next();
  478
+			});
  479
+		}
  480
+	};
  481
+});
  482
+
  483
+// DEPRECATED
  484
+if ( $.uiBackCompat !== false ) {
  485
+	$.Widget.prototype._getCreateOptions = function() {
  486
+		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
  487
+	};
  488
+}
  489
+
263 490
 })( jQuery );

0 notes on commit 63dc7b2

Please sign in to comment.
Something went wrong with that request. Please try again.