}
return access( this, function( value ) {
- var data;
+ var data, camelKey;
// The calling jQuery object (element matches) is not empty
// (and therefore has an element appears at this[ 0 ]) and the
// will result in `undefined` for elem = this[ 0 ] which will
// throw an exception if an attempt to read a data cache is made.
if ( elem && value === undefined ) {
-
// Attempt to get data from the cache
- // The key will always be camelCased in Data
+ // with the key as-is
data = dataUser.get( elem, key );
if ( data !== undefined ) {
return data;
}
+ camelKey = jQuery.camelCase( key );
+ // Attempt to get data from the cache
+ // with the key camelized
+ data = dataUser.get( elem, camelKey );
+ if ( data !== undefined ) {
+ return data;
+ }
+
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
- data = dataAttr( elem, key );
+ data = dataAttr( elem, camelKey, undefined );
if ( data !== undefined ) {
return data;
}
}
// Set the data...
- this.each( function() {
-
- // We always store the camelCased key
- dataUser.set( this, key, value );
- } );
+ camelKey = jQuery.camelCase( key );
+ this.each(function() {
+ // First, attempt to store a copy or reference of any
+ // data that might've been store with a camelCased key.
+ var data = dataUser.get( this, camelKey );
+
+ // For HTML5 data-* attribute interop, we have to
+ // store property names with dashes in a camelCase form.
+ // This might not apply to all properties...*
+ dataUser.set( this, camelKey, value );
+
+ // *... In the case of properties that might _actually_
+ // have dashes, we need to also store a copy of that
+ // unchanged property.
+ if ( key.indexOf("-") > -1 && data !== undefined ) {
+ dataUser.set( this, key, value );
+ }
+ });
}, null, value, arguments.length > 1, null, true );
},
Data.prototype = {
- cache: function( owner ) {
+ register: function( owner, initial ) {
+ var value = initial || {};
+
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable, non-writable property
+ // configurability must be true to allow the property to be
+ // deleted with the delete operator
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ writable: true,
+ configurable: true
+ });
+ }
+ return owner[ this.expando ];
+ },
+ cache: function( owner, initial ) {
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return an empty object.
+ if ( !acceptData( owner ) ) {
+ return {};
+ }
// Check if the owner object already has a cache
var value = owner[ this.expando ];
cache = this.cache( owner );
// Handle: [ owner, key, value ] args
- // Always use camelCase key (gh-2257)
if ( typeof data === "string" ) {
- cache[ jQuery.camelCase( data ) ] = value;
+ cache[ data ] = value;
// Handle: [ owner, { properties } ] args
} else {
-
// Copy the properties one-by-one to the cache object
for ( prop in data ) {
- cache[ jQuery.camelCase( prop ) ] = data[ prop ];
+ cache[ prop ] = data[ prop ];
}
}
return cache;
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
-
- // Always use camelCase key (gh-2257)
- owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
+ owner[ this.expando ] && owner[ this.expando ][ key ]
},
access: function( owner, key, value ) {
-
+ var stored;
// In cases where either:
//
// 1. No key was specified
// 2. The data stored at the key
//
if ( key === undefined ||
- ( ( key && typeof key === "string" ) && value === undefined ) ) {
+ ((key && typeof key === "string") && value === undefined) ) {
+
+ stored = this.get( owner, key );
- return this.get( owner, key );
+ return stored !== undefined ?
+ stored : this.get( owner, jQuery.camelCase(key) );
}
// When the key is not a string, or both a key and value
return value !== undefined ? value : key;
},
remove: function( owner, key ) {
- var i,
+ var i, name, camel,
cache = owner[ this.expando ];
if ( cache === undefined ) {
return;
}
- if ( key !== undefined ) {
+ if ( key === undefined ) {
+ this.register( owner );
+ } else {
// Support array or space separated string of keys
if ( jQuery.isArray( key ) ) {
-
- // If key is an array of keys...
- // We always set camelCase keys, so remove that.
- key = key.map( jQuery.camelCase );
+ // 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 = key.concat( key.map( jQuery.camelCase ) );
} else {
- key = jQuery.camelCase( key );
-
- // If a key with the spaces exists, use it.
- // Otherwise, create an array by matching non-whitespace
- key = key in cache ?
- [ key ] :
- ( key.match( rnotwhite ) || [] );
+ camel = jQuery.camelCase( key );
+ // Try the string as a key before any manipulation
+ if ( key in cache ) {
+ name = [ key, camel ];
+ } else {
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ name = camel;
+ name = name in cache ?
+ [ name ] : ( name.match( rnotwhite ) || [] );
+ }
}
- i = key.length;
+ i = name.length;
while ( i-- ) {
- delete cache[ key[ i ] ];
+ delete cache[ name[ i ] ];
}
}
assert.deepEqual( b.data( "long-param" ), { a: 2 }, "data with property long-param was found, 2" );
} );
-QUnit.test( ".data always sets data with the camelCased key (gh-2257)", function( assert ) {
- assert.expect( 18 );
-
- 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();
- assert.equal( allData[ key ], undefined, ".data does not store with hyphenated keys" );
- assert.equal( allData[ jQuery.camelCase( key ) ], val, ".data stores the camelCased key" );
- } );
-} );
-
QUnit.test(".data supports interoperable hyphenated/camelCase get/set of properties with arbitrary non-null|NaN|undefined values", function( assert ) {
var div = jQuery( "<div/>", { id: "hyphened" } ).appendTo( "#qunit-fixture" ),
datas = {
} );
} );
-QUnit.test( ".removeData supports removal of hyphenated properties via array (#12786, gh-2257)", function( assert ) {
- assert.expect( 4 );
+QUnit.test( ".removeData supports removal of hyphenated properties via array (#12786)", function( assert ) {
+ expect( 4 );
var div, plain, compare;
div = jQuery( "<div>" ).appendTo( "#qunit-fixture" );
plain = jQuery( {} );
- // Properties should always be camelCased
+ // When data is batch assigned (via plain object), the properties
+ // are not camel cased as they are with (property, value) calls
compare = {
// From batch assignment .data({ "a-a": 1 })
- "aA": 1,
-
+ "a-a": 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
assert.deepEqual( div.data(), {}, "Data is empty. (div)" );
assert.deepEqual( plain.data(), {}, "Data is empty. (plain)" );
} );