Commit 0204c308 authored by Timmy Willison's avatar Timmy Willison

Data: always camelCase keys in .data()

- This effectively implements our "Embrace HTML5" option
- Related: http://goo.gl/GcQAtn

Fixes gh-2257
parent e185aa3f
...@@ -41,14 +41,14 @@ function dataAttr( elem, key, data ) { ...@@ -41,14 +41,14 @@ function dataAttr( elem, key, data ) {
// checks a cache object for emptiness // checks a cache object for emptiness
function isEmptyDataObject( obj ) { function isEmptyDataObject( obj ) {
var name; var key;
for ( name in obj ) { for ( key in obj ) {
// if the public data object is empty, the private is still empty // 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; continue;
} }
if ( name !== "toJSON" ) { if ( key !== "toJSON" ) {
return false; return false;
} }
} }
...@@ -56,12 +56,12 @@ function isEmptyDataObject( obj ) { ...@@ -56,12 +56,12 @@ function isEmptyDataObject( obj ) {
return true; return true;
} }
function internalData( elem, name, data, pvt /* Internal Use Only */ ) { function internalData( elem, key, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) { if ( !jQuery.acceptData( elem ) ) {
return; return;
} }
var ret, thisCache, var thisCache, prop,
internalKey = jQuery.expando, internalKey = jQuery.expando,
// We have to handle DOM nodes and JS objects differently because IE6-7 // We have to handle DOM nodes and JS objects differently because IE6-7
...@@ -79,7 +79,7 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) { ...@@ -79,7 +79,7 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
// Avoid doing any more work than we need to when trying to get data on an // Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all // object that has no data at all
if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && if ( (!id || !cache[id] || (!pvt && !cache[id].data)) &&
data === undefined && typeof name === "string" ) { data === undefined && typeof key === "string" ) {
return; return;
} }
...@@ -99,16 +99,6 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) { ...@@ -99,16 +99,6 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; 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 ]; thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data // jQuery data() is stored in a separate object inside the object's internal data
...@@ -122,31 +112,28 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) { ...@@ -122,31 +112,28 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
thisCache = thisCache.data; thisCache = thisCache.data;
} }
if ( data !== undefined ) { // An object can be passed to jQuery.data instead of a key/value pair; this gets
thisCache[ jQuery.camelCase( name ) ] = data; // 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
// Check for both converted-to-camel and non-converted data property names return thisCache;
// 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 ) ) { if ( !jQuery.acceptData( elem ) ) {
return; return;
} }
...@@ -164,41 +151,29 @@ function internalRemoveData( elem, name, pvt ) { ...@@ -164,41 +151,29 @@ function internalRemoveData( elem, name, pvt ) {
return; return;
} }
if ( name ) { if ( key ) {
thisCache = pvt ? cache[ id ] : cache[ id ].data; thisCache = pvt ? cache[ id ] : cache[ id ].data;
if ( thisCache ) { if ( thisCache ) {
// Support array or space separated string names for data keys // Support array or space separated string keys for data keys
if ( !jQuery.isArray( name ) ) { if ( jQuery.isArray( key ) ) {
// try the string as a key before any manipulation // If "key" is an array of keys...
if ( name in thisCache ) { // We always use camelCased keys (gh-2257)
name = [ name ]; key = jQuery.map( key, jQuery.camelCase );
} else { } else {
// split the camel cased version by spaces unless a key with the spaces exists // split the camel cased version by spaces
name = jQuery.camelCase( name ); // unless a key with the spaces exists
if ( name in thisCache ) { key = jQuery.camelCase( key );
name = [ name ]; key = key in thisCache ? [ key ] : key.split( " " );
} else {
name = name.split(" ");
}
}
} 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 ) );
} }
i = name.length; i = key.length;
while ( i-- ) { while ( i-- ) {
delete thisCache[ name[i] ]; delete thisCache[ key[ i ] ];
} }
// If there is no data left in the cache, we want to continue // If there is no data left in the cache, we want to continue
......
...@@ -534,8 +534,65 @@ test("jQuery.data should not miss data with preset hyphenated property names", f ...@@ -534,8 +534,65 @@ test("jQuery.data should not miss data with preset hyphenated property names", f
}); });
}); });
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"), var div = jQuery("<div/>", { id: "hyphened" }).appendTo("#qunit-fixture"),
datas = { datas = {
"non-empty": "a string", "non-empty": "a string",
...@@ -597,7 +654,36 @@ test("jQuery.data supports interoperable removal of hyphenated/camelCase propert ...@@ -597,7 +654,36 @@ test("jQuery.data supports interoperable removal of hyphenated/camelCase propert
}); });
}); });
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 ); expect( 4 );
var div, plain, compare; var div, plain, compare;
...@@ -605,11 +691,10 @@ test( ".removeData supports removal of hyphenated properties via array (#12786)" ...@@ -605,11 +691,10 @@ test( ".removeData supports removal of hyphenated properties via array (#12786)"
div = jQuery("<div>").appendTo("#qunit-fixture"); div = jQuery("<div>").appendTo("#qunit-fixture");
plain = jQuery({}); plain = jQuery({});
// When data is batch assigned (via plain object), the properties // Properties should always be camelCased
// are not camel cased as they are with (property, value) calls
compare = { compare = {
// From batch assignment .data({ "a-a": 1 }) // From batch assignment .data({ "a-a": 1 })
"a-a": 1, "aA": 1,
// From property, value assignment .data( "b-b", 1 ) // From property, value assignment .data( "b-b", 1 )
"bB": 1 "bB": 1
}; };
...@@ -624,7 +709,6 @@ test( ".removeData supports removal of hyphenated properties via array (#12786)" ...@@ -624,7 +709,6 @@ test( ".removeData supports removal of hyphenated properties via array (#12786)"
div.removeData([ "a-a", "b-b" ]); div.removeData([ "a-a", "b-b" ]);
plain.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( div.data(), {}, "Data is empty. (div)" );
deepEqual( plain.data(), {}, "Data is empty. (plain)" ); deepEqual( plain.data(), {}, "Data is empty. (plain)" );
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment