From 0204c3089e7beee0306594605cc64d1e050ecd07 Mon Sep 17 00:00:00 2001 From: Timmy Willison Date: Mon, 4 May 2015 09:36:58 -0400 Subject: [PATCH] Data: always camelCase keys in .data() - This effectively implements our "Embrace HTML5" option - Related: http://goo.gl/GcQAtn Fixes gh-2257 --- src/data.js | 97 ++++++++++++++++++----------------------------- test/unit/data.js | 96 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 67 deletions(-) diff --git a/src/data.js b/src/data.js index da2b01b5d..30acce094 100644 --- a/src/data.js +++ b/src/data.js @@ -41,14 +41,14 @@ function dataAttr( elem, key, data ) { // 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; } } @@ -56,12 +56,12 @@ function isEmptyDataObject( obj ) { 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 @@ -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 // 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; } @@ -99,16 +99,6 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) { 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 @@ -122,31 +112,28 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ) { 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; } @@ -164,41 +151,29 @@ function internalRemoveData( elem, name, pvt ) { 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 diff --git a/test/unit/data.js b/test/unit/data.js index 98d714eb6..b691bd1ba 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -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("
").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("
").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("
").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("
", { id: "hyphened" }).appendTo("#qunit-fixture"), datas = { "non-empty": "a string", @@ -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("
").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; @@ -605,11 +691,10 @@ test( ".removeData supports removal of hyphenated properties via array (#12786)" div = jQuery("
").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 }; @@ -624,7 +709,6 @@ test( ".removeData supports removal of hyphenated properties via array (#12786)" 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)" ); }); -- 2.39.5