// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
- var name;
- for ( name in obj ) {
+ var key;
+ for ( key in obj ) {
// if the public data object is empty, the private is still empty
- if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ if ( key === "data" && jQuery.isEmptyObject( obj[ key ] ) ) {
continue;
}
- if ( name !== "toJSON" ) {
+ if ( key !== "toJSON" ) {
return false;
}
}
return true;
}
-function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
+function internalData( elem, key, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
- var ret, thisCache,
+ var thisCache, prop,
internalKey = jQuery.expando,
// We have to handle DOM nodes and JS objects differently because IE6-7
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
if ( (!id || !cache[id] || (!pvt && !cache[id].data)) &&
- data === undefined && typeof name === "string" ) {
+ data === undefined && typeof key === "string" ) {
return;
}
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}
- // An object can be passed to jQuery.data instead of a key/value pair; this gets
- // shallow copied over onto the existing cache
- if ( typeof name === "object" || typeof name === "function" ) {
- if ( pvt ) {
- cache[ id ] = jQuery.extend( cache[ id ], name );
- } else {
- cache[ id ].data = jQuery.extend( cache[ id ].data, name );
- }
- }
-
thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data
thisCache = thisCache.data;
}
- if ( data !== undefined ) {
- thisCache[ jQuery.camelCase( name ) ] = data;
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof key === "object" || typeof key === "function" ) {
+ for ( prop in key ) {
+ thisCache[ jQuery.camelCase( prop ) ] = key[ prop ];
+ }
+ // Stop here, ignore other arguments
+ return thisCache;
}
- // Check for both converted-to-camel and non-converted data property names
- // If a data property was specified
- if ( typeof name === "string" ) {
-
- // First Try to find as-is property data
- ret = thisCache[ name ];
-
- // Test for null|undefined property data
- if ( ret == null ) {
-
- // Try to find the camelCased property
- ret = thisCache[ jQuery.camelCase( name ) ];
- }
- } else {
- ret = thisCache;
+ if ( data !== undefined ) {
+ return thisCache[ jQuery.camelCase( key ) ] = data;
}
- return ret;
+ // We always set camelCased properties (gh-2257)
+ return typeof key === "string" ?
+ thisCache[ jQuery.camelCase( key ) ] :
+ // Return the whole cache if no key was specified
+ thisCache;
}
-function internalRemoveData( elem, name, pvt ) {
+function internalRemoveData( elem, key, pvt ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
return;
}
- if ( name ) {
+ if ( key ) {
thisCache = pvt ? cache[ id ] : cache[ id ].data;
if ( thisCache ) {
- // Support array or space separated string names for data keys
- if ( !jQuery.isArray( name ) ) {
-
- // try the string as a key before any manipulation
- if ( name in thisCache ) {
- name = [ name ];
- } else {
+ // Support array or space separated string keys for data keys
+ if ( jQuery.isArray( key ) ) {
- // split the camel cased version by spaces unless a key with the spaces exists
- name = jQuery.camelCase( name );
- if ( name in thisCache ) {
- name = [ name ];
- } else {
- name = name.split(" ");
- }
- }
+ // If "key" is an array of keys...
+ // We always use camelCased keys (gh-2257)
+ key = jQuery.map( key, jQuery.camelCase );
} else {
- // If "name" is an array of keys...
- // When data is initially created, via ("key", "val") signature,
- // keys will be converted to camelCase.
- // Since there is no way to tell _how_ a key was added, remove
- // both plain key and camelCase key. #12786
- // This will only penalize the array argument path.
- name = name.concat( jQuery.map( name, jQuery.camelCase ) );
+
+ // split the camel cased version by spaces
+ // unless a key with the spaces exists
+ key = jQuery.camelCase( key );
+ key = key in thisCache ? [ key ] : key.split( " " );
}
- i = name.length;
+ i = key.length;
while ( i-- ) {
- delete thisCache[ name[i] ];
+ delete thisCache[ key[ i ] ];
}
// If there is no data left in the cache, we want to continue
});
});
-test("jQuery.data supports interoperable hyphenated/camelCase get/set of properties with arbitrary non-null|NaN|undefined values", function() {
+test(".data should not miss attr() set data-* with hyphenated property names", function() {
+ expect(2);
+
+ var a, b;
+
+ a = jQuery("<div/>").appendTo("#qunit-fixture");
+
+ a.attr( "data-long-param", "test" );
+ a.data( "long-param", { a: 2 });
+
+ deepEqual( a.data("long-param"), { a: 2 }, "data with property long-param was found, 1" );
+
+ b = jQuery("<div/>").appendTo("#qunit-fixture");
+
+ b.attr( "data-long-param", "test" );
+ b.data( "long-param" );
+ b.data( "long-param", { a: 2 });
+
+ deepEqual( b.data("long-param"), { a: 2 }, "data with property long-param was found, 2" );
+});
+
+test(".data always sets data with the camelCased key (gh-2257)", function() {
+ expect( 36 );
+
+ var div = jQuery("<div>").appendTo("#qunit-fixture"),
+ datas = {
+ "non-empty": "a string",
+ "empty-string": "",
+ "one-value": 1,
+ "zero-value": 0,
+ "an-array": [],
+ "an-object": {},
+ "bool-true": true,
+ "bool-false": false,
+
+ // JSHint enforces double quotes,
+ // but JSON strings need double quotes to parse
+ // so we need escaped double quotes here
+ "some-json": "{ \"foo\": \"bar\" }"
+ };
+
+ jQuery.each( datas, function( key, val ) {
+ div.data( key, val );
+ var allData = div.data();
+ equal( allData[ key ], undefined, ".data(key, val) does not store with hyphenated keys" );
+ equal( allData[ jQuery.camelCase( key ) ], val, ".data(key, val) stores the camelCased key" );
+ });
+ div.removeData();
+
+ div.data( datas );
+ jQuery.each( datas, function( key, val ) {
+ var allData = div.data();
+ equal( allData[ key ], undefined, ".data(object) does not store with hyphenated keys" );
+ equal( allData[ jQuery.camelCase( key ) ], val, ".data(object) stores the camelCased key" );
+ });
+});
+
+test(".data supports interoperable hyphenated/camelCase get/set of properties with arbitrary non-null|NaN|undefined values", function() {
var div = jQuery("<div/>", { id: "hyphened" }).appendTo("#qunit-fixture"),
datas = {
"non-empty": "a string",
});
});
-test( ".removeData supports removal of hyphenated properties via array (#12786)", function() {
+test(".data supports interoperable removal of properties SET TWICE #13850", function() {
+ var div = jQuery("<div>").appendTo("#qunit-fixture"),
+ datas = {
+ "non-empty": "a string",
+ "empty-string": "",
+ "one-value": 1,
+ "zero-value": 0,
+ "an-array": [],
+ "an-object": {},
+ "bool-true": true,
+ "bool-false": false,
+ // JSHint enforces double quotes,
+ // but JSON strings need double quotes to parse
+ // so we need escaped double quotes here
+ "some-json": "{ \"foo\": \"bar\" }"
+ };
+
+ expect( 9 );
+
+ jQuery.each( datas, function( key, val ) {
+ div.data( key, val );
+ div.data( key, val );
+
+ div.removeData( key );
+
+ equal( div.data( key ), undefined, "removal: " + key );
+ });
+});
+
+test( ".removeData supports removal of hyphenated properties via array (#12786, gh-2257)", function() {
expect( 4 );
var div, plain, compare;
div = jQuery("<div>").appendTo("#qunit-fixture");
plain = jQuery({});
- // When data is batch assigned (via plain object), the properties
- // are not camel cased as they are with (property, value) calls
+ // Properties should always be camelCased
compare = {
// From batch assignment .data({ "a-a": 1 })
- "a-a": 1,
+ "aA": 1,
// From property, value assignment .data( "b-b", 1 )
"bB": 1
};
div.removeData([ "a-a", "b-b" ]);
plain.removeData([ "a-a", "b-b" ]);
- // NOTE: Timo's proposal for "propEqual" (or similar) would be nice here
deepEqual( div.data(), {}, "Data is empty. (div)" );
deepEqual( plain.data(), {}, "Data is empty. (plain)" );
});