Skip to content

Commit

Permalink
Using data() on JavaScript objects sets fields directly on the object…
Browse files Browse the repository at this point in the history
…. 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.
  • Loading branch information
Dave Reed authored and jeresig committed Sep 29, 2010
1 parent ec7ea3f commit cb811c0
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 61 deletions.
50 changes: 20 additions & 30 deletions src/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand All @@ -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 ) {
Expand All @@ -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 ) {
Expand All @@ -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 ];
}
}
}
},
Expand Down
43 changes: 40 additions & 3 deletions src/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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 );
}
}
Expand Down Expand Up @@ -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 );
}
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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;
Expand Down
60 changes: 34 additions & 26 deletions test/unit/data.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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" );
Expand All @@ -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() {
Expand All @@ -95,7 +88,7 @@ test(".data()", function() {
})

test(".data(String) and .data(String, Object)", function() {
expect(27);
expect(29);
var parent = jQuery("<div><div></div></div>"),
div = parent.children();

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -268,21 +263,34 @@ test("data-* attributes", function() {
});

test(".data(Object)", function() {
expect(2);
expect(4);

var div = jQuery("<div/>");

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() {
Expand Down
8 changes: 6 additions & 2 deletions test/unit/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand All @@ -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");
Expand Down

0 comments on commit cb811c0

Please sign in to comment.