Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Widget: Added $.widget.extend() which does deep extending, but only o…

…n plain objects.
  • Loading branch information...
commit b9153258b0f0edbff49496ed16d2aa93bec07d95 1 parent 63ff777
Jörn Zaefferer authored May 11, 2011 scottgonzalez committed May 11, 2011
1  tests/unit/widget/widget.html
@@ -14,6 +14,7 @@
14 14
 	<script src="../testsuite.js"></script>
15 15
 
16 16
 	<script src="widget_core.js"></script>
  17
+	<script src="widget_extend.js"></script>
17 18
 
18 19
 	<script src="../swarminject.js"></script>
19 20
 </head>
104  tests/unit/widget/widget_extend.js
... ...
@@ -0,0 +1,104 @@
  1
+test( "$.widget.extend()", function() {
  2
+	expect( 26 );
  3
+
  4
+	var settings = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" },
  5
+		options = { xnumber2: 1, xstring2: "x", xxx: "newstring" },
  6
+		optionsCopy = { xnumber2: 1, xstring2: "x", xxx: "newstring" },
  7
+		merged = { xnumber1: 5, xnumber2: 1, xstring1: "peter", xstring2: "x", xxx: "newstring" },
  8
+		deep1 = { foo: { bar: true } },
  9
+		deep1copy = { foo: { bar: true } },
  10
+		deep2 = { foo: { baz: true }, foo2: document },
  11
+		deep2copy = { foo: { baz: true }, foo2: document },
  12
+		deepmerged = { foo: { bar: true, baz: true }, foo2: document },
  13
+		arr = [1, 2, 3],
  14
+		nestedarray = { arr: arr },
  15
+		ret;
  16
+
  17
+	$.widget.extend( settings, options );
  18
+	deepEqual( settings, merged, "Check if extended: settings must be extended" );
  19
+	deepEqual( options, optionsCopy, "Check if not modified: options must not be modified" );
  20
+
  21
+	$.widget.extend( deep1, deep2 );
  22
+	deepEqual( deep1.foo, deepmerged.foo, "Check if foo: settings must be extended" );
  23
+	deepEqual( deep2.foo, deep2copy.foo, "Check if not deep2: options must not be modified" );
  24
+	equal( deep1.foo2, document, "Make sure that a deep clone was not attempted on the document" );
  25
+
  26
+	strictEqual( $.widget.extend({}, nestedarray).arr, arr, "Don't clone arrays" );
  27
+	ok( $.isPlainObject( $.widget.extend({ arr: arr }, { arr: {} }).arr ), "Cloned object heve to be an plain object" );
  28
+
  29
+	var empty = {};
  30
+	var optionsWithLength = { foo: { length: -1 } };
  31
+	$.widget.extend( empty, optionsWithLength );
  32
+	deepEqual( empty.foo, optionsWithLength.foo, "The length property must copy correctly" );
  33
+
  34
+	empty = {};
  35
+	var optionsWithDate = { foo: { date: new Date } };
  36
+	$.widget.extend( empty, optionsWithDate );
  37
+	deepEqual( empty.foo, optionsWithDate.foo, "Dates copy correctly" );
  38
+
  39
+	var myKlass = function() {};
  40
+	var customObject = new myKlass();
  41
+	var optionsWithCustomObject = { foo: { date: customObject } };
  42
+	empty = {};
  43
+	$.widget.extend( empty, optionsWithCustomObject );
  44
+	strictEqual( empty.foo.date, customObject, "Custom objects copy correctly (no methods)" );
  45
+
  46
+	// Makes the class a little more realistic
  47
+	myKlass.prototype = { someMethod: function(){} };
  48
+	empty = {};
  49
+	$.widget.extend( empty, optionsWithCustomObject );
  50
+	strictEqual( empty.foo.date, customObject, "Custom objects copy correctly" );
  51
+
  52
+	ret = $.widget.extend({ foo: 4 }, { foo: new Number(5) } );
  53
+	equal( ret.foo, 5, "Wrapped numbers copy correctly" );
  54
+
  55
+	var nullUndef;
  56
+	nullUndef = $.widget.extend( {}, options, { xnumber2: null } );
  57
+	strictEqual( nullUndef.xnumber2, null, "Check to make sure null values are copied");
  58
+
  59
+	nullUndef = $.widget.extend( {}, options, { xnumber2: undefined } );
  60
+	strictEqual( nullUndef.xnumber2, options.xnumber2, "Check to make sure undefined values are not copied");
  61
+
  62
+	nullUndef = $.widget.extend( {}, options, { xnumber0: null } );
  63
+	strictEqual( nullUndef.xnumber0, null, "Check to make sure null values are inserted");
  64
+
  65
+	var target = {};
  66
+	var recursive = { foo:target, bar:5 };
  67
+	$.widget.extend( target, recursive );
  68
+	deepEqual( target, { foo: {}, bar: 5 }, "Check to make sure a recursive obj doesn't go never-ending loop by not copying it over" );
  69
+
  70
+	ret = $.widget.extend( { foo: [] }, { foo: [0] } ); // 1907
  71
+	equal( ret.foo.length, 1, "Check to make sure a value with coersion 'false' copies over when necessary to fix #1907" );
  72
+
  73
+	ret = $.widget.extend( { foo: "1,2,3" }, { foo: [1, 2, 3] } );
  74
+	strictEqual( typeof ret.foo, "object", "Check to make sure values equal with coersion (but not actually equal) overwrite correctly" );
  75
+
  76
+	ret = $.widget.extend( { foo:"bar" }, { foo:null } );
  77
+	strictEqual( typeof ret.foo, "object", "Make sure a null value doesn't crash with deep extend, for #1908" );
  78
+
  79
+	var obj = { foo:null };
  80
+	$.widget.extend( obj, { foo:"notnull" } );
  81
+	equal( obj.foo, "notnull", "Make sure a null value can be overwritten" );
  82
+
  83
+	var defaults = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" },
  84
+		defaultsCopy = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" },
  85
+		options1 = { xnumber2: 1, xstring2: "x" },
  86
+		options1Copy = { xnumber2: 1, xstring2: "x" },
  87
+		options2 = { xstring2: "xx", xxx: "newstringx" },
  88
+		options2Copy = { xstring2: "xx", xxx: "newstringx" },
  89
+		merged2 = { xnumber1: 5, xnumber2: 1, xstring1: "peter", xstring2: "xx", xxx: "newstringx" };
  90
+
  91
+	var settings = $.widget.extend( {}, defaults, options1, options2 );
  92
+	deepEqual( settings, merged2, "Check if extended: settings must be extended" );
  93
+	deepEqual( defaults, defaultsCopy, "Check if not modified: options1 must not be modified" );
  94
+	deepEqual( options1, options1Copy, "Check if not modified: options1 must not be modified" );
  95
+	deepEqual( options2, options2Copy, "Check if not modified: options2 must not be modified" );
  96
+	
  97
+	var input = {
  98
+		key: [ 1, 2, 3 ]
  99
+	}
  100
+	var output = $.widget.extend( {}, input );
  101
+	deepEqual( input, output, "don't clone arrays" );
  102
+	input.key[0] = 10;
  103
+	deepEqual( input, output, "don't clone arrays" );
  104
+});
29  ui/jquery.ui.widget.js
@@ -55,7 +55,7 @@ $.widget = function( name, base, prototype ) {
55 55
 	// we need to make the options hash a property directly on the new instance
56 56
 	// otherwise we'll modify the options hash on the prototype that we're
57 57
 	// inheriting from
58  
-	basePrototype.options = $.extend( true, {}, basePrototype.options );
  58
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
59 59
 	$.each( prototype, function( prop, value ) {
60 60
 		if ( $.isFunction( value ) ) {
61 61
 			prototype[ prop ] = (function() {
@@ -83,7 +83,7 @@ $.widget = function( name, base, prototype ) {
83 83
 			}());
84 84
 		}
85 85
 	});
86  
-	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
  86
+	$[ namespace ][ name ].prototype = $.widget.extend( basePrototype, {
87 87
 		namespace: namespace,
88 88
 		widgetName: name,
89 89
 		widgetEventPrefix: name,
@@ -93,6 +93,23 @@ $.widget = function( name, base, prototype ) {
93 93
 	$.widget.bridge( name, $[ namespace ][ name ] );
94 94
 };
95 95
 
  96
+$.widget.extend = function( target ) {
  97
+	var input = slice.call( arguments, 1 ),
  98
+		inputIndex = 0,
  99
+		inputLength = input.length,
  100
+		key,
  101
+		value;
  102
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
  103
+		for ( key in input[ inputIndex ] ) {
  104
+			value = input[ inputIndex ][ key ];
  105
+			if (input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
  106
+				target[ key ] = $.isPlainObject( value ) ? $.widget.extend( {}, target[ key ], value ) : value;
  107
+			}
  108
+		}
  109
+	}
  110
+	return target;
  111
+};
  112
+
96 113
 $.widget.bridge = function( name, object ) {
97 114
 	$.fn[ name ] = function( options ) {
98 115
 		var isMethodCall = typeof options === "string",
@@ -101,7 +118,7 @@ $.widget.bridge = function( name, object ) {
101 118
 
102 119
 		// allow multiple hashes to be passed on init
103 120
 		options = !isMethodCall && args.length ?
104  
-			$.extend.apply( null, [ true, options ].concat(args) ) :
  121
+			$.widget.extend.apply( null, [ options ].concat(args) ) :
105 122
 			options;
106 123
 
107 124
 		if ( isMethodCall ) {
@@ -163,7 +180,7 @@ $.Widget.prototype = {
163 180
 	_createWidget: function( options, element ) {
164 181
 		element = $( element || this.defaultElement || this )[ 0 ];
165 182
 		this.element = $( element );
166  
-		this.options = $.extend( true, {},
  183
+		this.options = $.widget.extend( {},
167 184
 			this.options,
168 185
 			this._getCreateOptions(),
169 186
 			options );
@@ -218,7 +235,7 @@ $.Widget.prototype = {
218 235
 
219 236
 		if ( arguments.length === 0 ) {
220 237
 			// don't return a reference to the internal hash
221  
-			return $.extend( {}, this.options );
  238
+			return $.widget.extend( {}, this.options );
222 239
 		}
223 240
 
224 241
 		if ( typeof key === "string" ) {
@@ -230,7 +247,7 @@ $.Widget.prototype = {
230 247
 			parts = key.split( "." );
231 248
 			key = parts.shift();
232 249
 			if ( parts.length ) {
233  
-				curOption = options[ key ] = $.extend( true, {}, this.options[ key ] );
  250
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
234 251
 				for ( i = 0; i < parts.length - 1; i++ ) {
235 252
 					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
236 253
 					curOption = curOption[ parts[ i ] ];

0 notes on commit b915325

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