From cb811c04b035eb2d652b0831e20ea1d3a4d9c448 Mon Sep 17 00:00:00 2001 From: Dave Reed Date: Wed, 29 Sep 2010 06:46:25 -0700 Subject: [PATCH] Using data() on JavaScript objects sets fields directly on the object. Note that events are now a property of a function (to avoid JSON serialization - and only in the case of JavaScript objects, not DOM nodes). Fixes #6807. --- src/data.js | 50 ++++++++++++++++++------------------------- src/event.js | 43 ++++++++++++++++++++++++++++++++++--- test/unit/data.js | 60 +++++++++++++++++++++++++++++----------------------- test/unit/event.js | 8 +++++-- 4 files changed, 100 insertions(+), 61 deletions(-) diff --git a/src/data.js b/src/data.js index 43ab595..66d24c4 100644 --- a/src/data.js +++ b/src/data.js @@ -30,18 +30,17 @@ jQuery.extend({ windowData : elem; - var id = elem[ jQuery.expando ], cache = jQuery.cache, thisCache, - isNode = elem.nodeType, - store; + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : null, + cache = jQuery.cache, thisCache; - if ( !id && typeof name === "string" && data === undefined ) { + if ( isNode && !id && typeof name === "string" && data === undefined ) { return; } // Get the data from the object directly if ( !isNode ) { cache = elem; - id = jQuery.expando; // Compute a unique ID for the element } else if ( !id ) { @@ -55,26 +54,14 @@ jQuery.extend({ cache[ id ] = jQuery.extend(cache[ id ], name); } else { - store = jQuery.extend(cache[ id ], name); - cache[ id ] = function() { - return store; - }; + jQuery.extend( cache, name ); } - } else if ( !cache[ id ] ) { - if ( isNode ) { - cache[ id ] = {}; - - } else { - store = {}; - cache[ id ] = function() { - return store; - }; - } - + } else if ( isNode && !cache[ id ] ) { + cache[ id ] = {}; } - thisCache = isNode ? cache[ id ] : cache[ id ](); + thisCache = isNode ? cache[ id ] : cache; // Prevent overriding the named cache with undefined values if ( data !== undefined ) { @@ -94,11 +81,9 @@ jQuery.extend({ elem; var isNode = elem.nodeType, - id = elem[ jQuery.expando ], cache = jQuery.cache; - if ( id && !isNode ) { - id = id(); - } - var thisCache = cache[ id ]; + id = isNode ? elem[ jQuery.expando ] : elem, + cache = jQuery.cache, + thisCache = isNode ? cache[ id ] : id; // If we want to remove a specific section of the element's data if ( name ) { @@ -107,23 +92,28 @@ jQuery.extend({ delete thisCache[ name ]; // If we've removed all the data, remove the element's cache - if ( jQuery.isEmptyObject(thisCache) ) { + if ( isNode && jQuery.isEmptyObject(thisCache) ) { jQuery.removeData( elem ); } } // Otherwise, we want to remove all of the element's data } else { - if ( jQuery.support.deleteExpando || !isNode ) { + if ( isNode && jQuery.support.deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); - } // Completely remove the data cache - if ( isNode ) { + } else if ( isNode ) { delete cache[ id ]; + + // Remove all fields from the object + } else { + for ( var n in elem ) { + delete elem[ n ]; + } } } }, diff --git a/src/event.js b/src/event.js index dcb872a..5829948 100644 --- a/src/event.js +++ b/src/event.js @@ -54,8 +54,25 @@ jQuery.event = { return; } - var events = elemData.events = elemData.events || {}, + var events = elemData.events, eventHandle = elemData.handle; + + if ( typeof events === "function" ) { + // On plain objects events is a fn that holds the the data + // which prevents this data from being JSON serialized + // the function does not need to be called, it just contains the data + eventHandle = events.handle; + events = events.events; + + } else if ( !events ) { + if ( !elem.nodeType ) { + // On plain objects, create a fn that acts as the holder + // of the values to avoid JSON serialization of event data + elemData.events = elemData = function(){}; + } + + elemData.events = events = {}; + } if ( !eventHandle ) { elemData.handle = eventHandle = function() { @@ -159,6 +176,11 @@ jQuery.event = { if ( !elemData || !events ) { return; } + + if ( typeof events === "function" ) { + elemData = events; + events = events.events; + } // types is actually an event object here if ( types && types.type ) { @@ -259,7 +281,10 @@ jQuery.event = { delete elemData.events; delete elemData.handle; - if ( jQuery.isEmptyObject( elemData ) ) { + if ( typeof elemData === "function" ) { + delete elem.events; + + } else if ( jQuery.isEmptyObject( elemData ) ) { jQuery.removeData( elem ); } } @@ -319,7 +344,10 @@ jQuery.event = { event.currentTarget = elem; // Trigger the event, it is assumed that "handle" is a function - var handle = jQuery.data( elem, "handle" ); + var handle = elem.nodeType ? + jQuery.data( elem, "handle" ) : + elem.events && elem.events.handle; + if ( handle ) { handle.apply( elem, data ); } @@ -393,6 +421,11 @@ jQuery.event = { event.namespace = event.namespace || namespace_sort.join("."); events = jQuery.data(this, "events"); + + if ( typeof events === "function" ) { + events = events.events; + } + handlers = (events || {})[ event.type ]; if ( events && handlers ) { @@ -1007,6 +1040,10 @@ function liveHandler( event ) { related, match, handleObj, elem, j, i, l, data, close, namespace, ret, events = jQuery.data( this, "events" ); + if ( typeof events === "function" ) { + events = events.events; + } + // Make sure we avoid non-left-click bubbling in Firefox (#3861) if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { return; diff --git a/test/unit/data.js b/test/unit/data.js index 77ee099..2efa984 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -1,27 +1,22 @@ module("data"); test("expando", function(){ - expect(7); + expect(6); equals("expando" in jQuery, true, "jQuery is exposing the expando"); var obj = {}; - jQuery.data(obj); - equals( jQuery.expando in obj, true, "jQuery.data adds an expando to the object" ); - equals( typeof obj[jQuery.expando], "function", "jQuery.data adds an expando to the object as a function" ); + equals( jQuery.data(obj), obj, "jQuery.data(obj) returns the object"); + equals( jQuery.expando in obj, false, "jQuery.data(obj) did not add an expando to the object" ); obj = {}; jQuery.data(obj, 'test'); - equals( jQuery.expando in obj, false, "jQuery.data did not add an expando to the object" ); + equals( jQuery.expando in obj, false, "jQuery.data(obj,key) did not add an expando to the object" ); obj = {}; jQuery.data(obj, "foo", "bar"); - equals( jQuery.expando in obj, true, "jQuery.data added an expando to the object" ); - - var id = obj[jQuery.expando](); - equals( id in jQuery.cache, false, "jQuery.data did not add an entry to jQuery.cache" ); - - equals( id.foo, "bar", "jQuery.data worked correctly" ); + equals( jQuery.expando in obj, false, "jQuery.data(obj,key,value) did not add an expando to the object" ); + equals( obj.foo, "bar", "jQuery.data(obj,key,value) sets fields directly on the object." ); }); test("jQuery.acceptData", function() { @@ -43,7 +38,7 @@ test("jQuery.acceptData", function() { }); test("jQuery.data", function() { - expect(13); + expect(12); var div = document.createElement("div"); ok( jQuery.data(div, "test") === undefined, "Check for no data exists" ); @@ -67,17 +62,15 @@ test("jQuery.data", function() { jQuery.data(div, "test3", "orig"); jQuery.data(div, { "test": "in", "test2": "in2" }); - equals( jQuery.data(div, "test"), "in", "Verify setting an object in data." ); - equals( jQuery.data(div, "test2"), "in2", "Verify setting an object in data." ); - equals( jQuery.data(div, "test3"), "orig", "Verify original not overwritten." ); + equals( jQuery.data(div, "test"), "in", "Verify setting an object in data" ); + equals( jQuery.data(div, "test2"), "in2", "Verify setting an object in data" ); + equals( jQuery.data(div, "test3"), "orig", "Verify original not overwritten" ); var obj = {}; jQuery.data( obj, "prop", true ); - ok( obj[ jQuery.expando ], "Data is being stored on the object." ); - ok( obj[ jQuery.expando ]().prop, "Data is being stored on the object." ); - - equals( jQuery.data( obj, "prop" ), true, "Make sure the right value is retrieved." ); + ok( obj.prop, "Data is being stored on the object" ); + equals( jQuery.data( obj, "prop" ), true, "Make sure the right value is retrieved" ); }); test(".data()", function() { @@ -95,7 +88,7 @@ test(".data()", function() { }) test(".data(String) and .data(String, Object)", function() { - expect(27); + expect(29); var parent = jQuery("
"), div = parent.children(); @@ -173,14 +166,16 @@ test(".data(String) and .data(String, Object)", function() { equals( div.data("test.bar"), "testroot", "Check for unmatched namespace" ); // #3748 - var $elem = jQuery({}); + var $elem = jQuery({exists:true}); equals( $elem.data('nothing'), undefined, "Non-existent data returns undefined"); equals( $elem.data('null',null).data('null'), null, "null's are preserved"); equals( $elem.data('emptyString','').data('emptyString'), '', "Empty strings are preserved"); equals( $elem.data('false',false).data('false'), false, "false's are preserved"); - + equals( $elem.data('exists'), true, "Existing data is returned" ); + // Clean up $elem.removeData(); + ok( jQuery.isEmptyObject( $elem[0] ), "removeData clears the object" ); }); test("data-* attributes", function() { @@ -268,21 +263,34 @@ test("data-* attributes", function() { }); test(".data(Object)", function() { - expect(2); + expect(4); var div = jQuery("
"); div.data({ "test": "in", "test2": "in2" }); - equals( div.data("test"), "in", "Verify setting an object in data." ); - equals( div.data("test2"), "in2", "Verify setting an object in data." ); + equals( div.data("test"), "in", "Verify setting an object in data" ); + equals( div.data("test2"), "in2", "Verify setting an object in data" ); + + var obj = {test:"unset"}, + jqobj = jQuery(obj); + jqobj.data({ "test": "in", "test2": "in2" }); + equals( obj.test, "in", "Verify setting an object on an object extends the object" ); + equals( obj.test2, "in2", "Verify setting an object on an object extends the object" ); }); test("jQuery.removeData", function() { - expect(1); + expect(4); var div = jQuery("#foo")[0]; jQuery.data(div, "test", "testing"); jQuery.removeData(div, "test"); equals( jQuery.data(div, "test"), undefined, "Check removal of data" ); + + var obj = {}; + jQuery.data(obj, "test", "testing"); + equals( obj.test, "testing", "verify data on plain object"); + jQuery.removeData(obj, "test"); + equals( jQuery.data(obj, "test"), undefined, "Check removal of data on plain object" ); + equals( obj.test, undefined, "Check removal of data directly from plain object" ); }); test(".removeData()", function() { diff --git a/test/unit/event.js b/test/unit/event.js index 10751cc..b5e24c8 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -443,7 +443,7 @@ test("bind(name, false), unbind(name, false)", function() { }); test("bind()/trigger()/unbind() on plain object", function() { - expect( 2 ); + expect( 5 ); var obj = {}; @@ -457,7 +457,11 @@ test("bind()/trigger()/unbind() on plain object", function() { ok( true, "Custom event run." ); }); - ok( jQuery(obj).data("events"), "Object has events bound." ); + var events = jQuery(obj).data("events"); + ok( events, "Object has events bound." ); + equals( typeof events, "function", "'events' expando is a function on plain objects." ); + equals( obj.test, undefined, "Make sure that test event is not on the plain object." ); + equals( obj.handle, undefined, "Make sure that the event handler is not on the plain object." ); // Should trigger 1 jQuery(obj).trigger("test"); -- 1.7.10.4