04-data.js | |
---|---|
jQuery Annotated Source. | |
Chapter 4: Data | var rbrace = /^(?:\{.*\}|\[.*\])$/,
rmultiDash = /([a-z])([A-Z])/g;
jQuery.extend({
cache: {}, |
Please use with caution | uuid: 0, |
Unique for each copy of jQuery on the page Non-digits removed to match rinlinejQuery | expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), |
The following elements throw uncatchable exceptions if you attempt to add expando properties to them. | noData: {
"embed": true, |
Ban all objects except for Flash (which handle expandos) | "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
"applet": true
},
hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !isEmptyDataObject( elem );
},
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, |
We have to handle DOM nodes and JS objects differently because IE6-7 can't GC object references properly across the DOM-JS boundary | isNode = elem.nodeType, |
Only DOM nodes need the global jQuery cache; JS object data is attached directly to the object so GC can occur automatically | cache = isNode ? jQuery.cache : elem, |
Only defining an ID for JS objects if its cache already exists allows the code to shortcut on the same path as a DOM node with no cache | id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; |
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 || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
return;
}
if ( !id ) { |
Only DOM nodes need a new unique ID for each element since their data ends up in the global cache | if ( isNode ) {
elem[ jQuery.expando ] = id = ++jQuery.uuid;
} else {
id = jQuery.expando;
}
}
if ( !cache[ id ] ) {
cache[ id ] = {}; |
TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery metadata on plain JS objects when the object is serialized using JSON.stringify | if ( !isNode ) {
cache[ id ].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 ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
} else {
cache[ id ] = jQuery.extend(cache[ id ], name);
}
}
thisCache = cache[ id ]; |
Internal jQuery data is stored in a separate object inside the object's data cache in order to avoid key collisions between internal data and user-defined data | if ( pvt ) {
if ( !thisCache[ internalKey ] ) {
thisCache[ internalKey ] = {};
}
thisCache = thisCache[ internalKey ];
}
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
} |
TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should not attempt to inspect the internal events object using jQuery.data, as this internal data object is undocumented and subject to change. | if ( name === "events" && !thisCache[name] ) {
return thisCache[ internalKey ] && thisCache[ internalKey ].events;
}
return getByName ? |
Check for both converted-to-camel and non-converted data property names | thisCache[ jQuery.camelCase( name ) ] || thisCache[ name ] :
thisCache;
},
removeData: function( elem, name, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var internalKey = jQuery.expando, isNode = elem.nodeType, |
See jQuery.data for more information | cache = isNode ? jQuery.cache : elem, |
See jQuery.data for more information | id = isNode ? elem[ jQuery.expando ] : jQuery.expando; |
If there is already no cache entry for this object, there is no purpose in continuing | if ( !cache[ id ] ) {
return;
}
if ( name ) {
var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
if ( thisCache ) {
delete thisCache[ name ]; |
If there is no data left in the cache, we want to continue and let the cache object itself get destroyed | if ( !isEmptyDataObject(thisCache) ) {
return;
}
}
} |
See jQuery.data for more information | if ( pvt ) {
delete cache[ id ][ internalKey ]; |
Don't destroy the parent cache unless the internal data object had been the only thing left in it | if ( !isEmptyDataObject(cache[ id ]) ) {
return;
}
}
var internalCache = cache[ id ][ internalKey ]; |
Browsers that fail expando deletion also refuse to delete expandos on the window, but it will allow it on all other JS objects; other browsers don't care | if ( jQuery.support.deleteExpando || cache != window ) {
delete cache[ id ];
} else {
cache[ id ] = null;
} |
We destroyed the entire user cache at once because it's faster than iterating through each key, but we need to continue to persist internal data if it existed | if ( internalCache ) {
cache[ id ] = {}; |
TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery metadata on plain JS objects when the object is serialized using JSON.stringify | if ( !isNode ) {
cache[ id ].toJSON = jQuery.noop;
}
cache[ id ][ internalKey ] = internalCache; |
Otherwise, we need to eliminate the expando on the node to avoid false lookups in the cache for entries that no longer exist | } else if ( isNode ) { |
IE does not allow us to delete expando properties from nodes, nor does it have a removeAttribute function on Document nodes; we must handle all of these cases | if ( jQuery.support.deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando );
} else {
elem[ jQuery.expando ] = null;
}
}
}, |
For internal use only. | _data: function( elem, name, data ) {
return jQuery.data( elem, name, data, true );
}, |
A method for determining if a DOM node can handle the data expando | acceptData: function( elem ) {
if ( elem.nodeName ) {
var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
if ( match ) {
return !(match === true || elem.getAttribute("classid") !== match);
}
}
return true;
}
});
jQuery.fn.extend({
data: function( key, value ) {
var data = null;
if ( typeof key === "undefined" ) {
if ( this.length ) {
data = jQuery.data( this[0] );
if ( this[0].nodeType === 1 ) {
var attr = this[0].attributes, name;
for ( var i = 0, l = attr.length; i < l; i++ ) {
name = attr[i].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = jQuery.camelCase( name.substring(5) );
dataAttr( this[0], name, data[ name ] );
}
}
}
}
return data;
} else if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
}
var parts = key.split(".");
parts[1] = parts[1] ? "." + parts[1] : "";
if ( value === undefined ) {
data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); |
Try to fetch any internally stored data first | if ( data === undefined && this.length ) {
data = jQuery.data( this[0], key );
data = dataAttr( this[0], key, data );
}
return data === undefined && parts[1] ?
this.data( parts[0] ) :
data;
} else {
return this.each(function() {
var $this = jQuery( this ),
args = [ parts[0], value ];
$this.triggerHandler( "setData" + parts[1] + "!", args );
jQuery.data( this, key, value );
$this.triggerHandler( "changeData" + parts[1] + "!", args );
});
}
},
removeData: function( key ) {
return this.each(function() {
jQuery.removeData( this, key );
});
}
});
function dataAttr( elem, key, data ) { |
If nothing was found internally, try to fetch any data from the HTML5 data-* attribute | if ( data === undefined && elem.nodeType === 1 ) {
var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase();
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
!jQuery.isNaN( data ) ? parseFloat( data ) :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {} |
Make sure we set the data so it isn't changed later | jQuery.data( elem, key, data );
} else {
data = undefined;
}
}
return data;
} |
TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON property to be considered empty objects; this property always exists in order to make sure JSON.stringify does not expose internal metadata | function isEmptyDataObject( obj ) {
for ( var name in obj ) {
if ( name !== "toJSON" ) {
return false;
}
}
return true;
}
|