2013-02-11 17:39:44 +00:00
|
|
|
/*
|
|
|
|
Implementation Summary
|
|
|
|
|
|
|
|
1. Enforce API surface and semantic compatibility with 1.9.x branch
|
|
|
|
2. Improve the module's maintainability by reducing the storage
|
|
|
|
paths to a single mechanism.
|
|
|
|
3. Use the same single mechanism to support "private" and "user" data.
|
|
|
|
4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
|
|
|
|
5. Avoid exposing implementation details on user objects (eg. expando properties)
|
|
|
|
6. Provide a clear path for implementation upgrade to WeakMap in 2014
|
|
|
|
*/
|
2013-02-03 20:27:55 +00:00
|
|
|
var data_user, data_priv,
|
|
|
|
rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
|
2011-09-07 14:13:22 +00:00
|
|
|
rmultiDash = /([A-Z])/g;
|
2013-01-26 01:44:55 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
function Data() {
|
2013-02-11 17:39:44 +00:00
|
|
|
this.cache = {};
|
2013-02-23 17:41:37 +00:00
|
|
|
this.expando = jQuery.expando + Math.random();
|
2013-02-03 20:27:55 +00:00
|
|
|
}
|
2013-01-04 01:39:15 +00:00
|
|
|
|
2013-02-11 17:39:44 +00:00
|
|
|
Data.uid = 1;
|
2013-01-04 01:39:15 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
Data.prototype = {
|
2013-02-23 17:41:37 +00:00
|
|
|
key: function( owner ) {
|
|
|
|
var descriptor = {},
|
|
|
|
// Check if the owner object already has a cache key
|
|
|
|
unlock = owner[ this.expando ];
|
|
|
|
|
|
|
|
// If not, create one
|
|
|
|
if ( !unlock ) {
|
|
|
|
unlock = Data.uid++;
|
|
|
|
descriptor[ this.expando ] = { value: unlock };
|
|
|
|
|
|
|
|
// Secure it in a non-enumerable, non-writable property
|
|
|
|
try {
|
|
|
|
Object.defineProperties( owner, descriptor );
|
|
|
|
|
|
|
|
// Support: Android<4
|
|
|
|
// Fallback to a less secure definition
|
|
|
|
} catch ( e ) {
|
|
|
|
descriptor[ this.expando ] = unlock;
|
|
|
|
jQuery.extend( owner, descriptor );
|
|
|
|
}
|
2013-02-11 17:39:44 +00:00
|
|
|
}
|
|
|
|
|
2013-02-23 17:41:37 +00:00
|
|
|
// Ensure the cache object
|
2013-02-11 17:39:44 +00:00
|
|
|
if ( !this.cache[ unlock ] ) {
|
|
|
|
this.cache[ unlock ] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return unlock;
|
2013-02-03 20:27:55 +00:00
|
|
|
},
|
|
|
|
set: function( owner, data, value ) {
|
2013-02-27 04:41:57 +00:00
|
|
|
var prop,
|
|
|
|
// There may be an unlock assigned to this node,
|
|
|
|
// if there is no entry for this "owner", create one inline
|
|
|
|
// and set the unlock as though an owner entry had always existed
|
|
|
|
unlock = this.key( owner ),
|
|
|
|
cache = this.cache[ unlock ];
|
2013-02-11 17:39:44 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// Handle: [ owner, key, value ] args
|
|
|
|
if ( typeof data === "string" ) {
|
2013-02-11 17:39:44 +00:00
|
|
|
cache[ data ] = value;
|
2013-01-04 01:39:15 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// Handle: [ owner, { properties } ] args
|
2012-12-31 20:09:45 +00:00
|
|
|
} else {
|
2013-02-27 04:41:57 +00:00
|
|
|
// Support an expectation from the old data system where plain
|
|
|
|
// objects used to initialize would be set to the cache by
|
|
|
|
// reference, instead of having properties and values copied.
|
|
|
|
// Note, this will kill the connection between
|
2013-02-11 17:39:44 +00:00
|
|
|
// "this.cache[ unlock ]" and "cache"
|
|
|
|
if ( jQuery.isEmptyObject( cache ) ) {
|
2013-02-27 04:41:57 +00:00
|
|
|
this.cache[ unlock ] = data;
|
2013-02-03 20:27:55 +00:00
|
|
|
// Otherwise, copy the properties one-by-one to the cache object
|
|
|
|
} else {
|
|
|
|
for ( prop in data ) {
|
2013-02-11 17:39:44 +00:00
|
|
|
cache[ prop ] = data[ prop ];
|
2013-02-03 20:27:55 +00:00
|
|
|
}
|
|
|
|
}
|
2011-01-09 21:52:33 +00:00
|
|
|
}
|
2013-02-11 17:39:44 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
get: function( owner, key ) {
|
2013-02-11 17:39:44 +00:00
|
|
|
// Either a valid cache is found, or will be created.
|
|
|
|
// New caches will be created and the unlock returned,
|
|
|
|
// allowing direct access to the newly created
|
|
|
|
// empty data object.
|
2013-02-23 17:41:37 +00:00
|
|
|
var cache = this.cache[ this.key( owner ) ];
|
2013-02-03 20:27:55 +00:00
|
|
|
|
|
|
|
return key === undefined ?
|
|
|
|
cache : cache[ key ];
|
|
|
|
},
|
|
|
|
access: function( owner, key, value ) {
|
2013-02-13 17:14:20 +00:00
|
|
|
// In cases where either:
|
|
|
|
//
|
|
|
|
// 1. No key was specified
|
|
|
|
// 2. A string key was specified, but no value provided
|
|
|
|
//
|
|
|
|
// Take the "read" path and allow the get method to determine
|
|
|
|
// which value to return, respectively either:
|
|
|
|
//
|
|
|
|
// 1. The entire cache object
|
|
|
|
// 2. The data stored at the key
|
|
|
|
//
|
|
|
|
if ( key === undefined ||
|
|
|
|
((key && typeof key === "string") && value === undefined) ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
return this.get( owner, key );
|
2013-01-04 01:39:15 +00:00
|
|
|
}
|
2013-02-13 17:14:20 +00:00
|
|
|
|
|
|
|
// [*]When the key is not a string, or both a key and value
|
|
|
|
// are specified, set or extend (existing objects) with either:
|
|
|
|
//
|
|
|
|
// 1. An object of properties
|
|
|
|
// 2. A key and value
|
|
|
|
//
|
|
|
|
this.set( owner, key, value );
|
|
|
|
|
|
|
|
// Since the "set" path can have two possible entry points
|
|
|
|
// return the expected data based on which path was taken[*]
|
|
|
|
return value !== undefined ? value : key;
|
2013-02-03 20:27:55 +00:00
|
|
|
},
|
|
|
|
remove: function( owner, key ) {
|
2013-02-27 04:41:57 +00:00
|
|
|
var i, name,
|
2013-02-23 17:41:37 +00:00
|
|
|
unlock = this.key( owner ),
|
2013-02-11 17:39:44 +00:00
|
|
|
cache = this.cache[ unlock ];
|
2013-01-04 01:39:15 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
if ( key === undefined ) {
|
2013-02-27 04:41:57 +00:00
|
|
|
this.cache[ unlock ] = {};
|
2013-02-03 20:27:55 +00:00
|
|
|
} else {
|
2013-02-27 04:41:57 +00:00
|
|
|
// Support array or space separated string of keys
|
|
|
|
if ( jQuery.isArray( key ) ) {
|
|
|
|
// 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 {
|
|
|
|
// Try the string as a key before any manipulation
|
|
|
|
if ( key in cache ) {
|
|
|
|
name = [ key ];
|
2013-02-03 20:27:55 +00:00
|
|
|
} else {
|
2013-02-27 04:41:57 +00:00
|
|
|
// If a key with the spaces exists, use it.
|
|
|
|
// Otherwise, create an array by matching non-whitespace
|
|
|
|
name = jQuery.camelCase( key );
|
|
|
|
name = name in cache ?
|
|
|
|
[ name ] : ( name.match( core_rnotwhite ) || [] );
|
2012-10-16 15:15:41 +00:00
|
|
|
}
|
2013-02-27 04:41:57 +00:00
|
|
|
}
|
2011-01-09 21:52:33 +00:00
|
|
|
|
2013-02-27 04:41:57 +00:00
|
|
|
i = name.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
delete cache[ name[i] ];
|
2013-01-04 01:39:15 +00:00
|
|
|
}
|
2011-01-09 21:52:33 +00:00
|
|
|
}
|
2013-02-03 20:27:55 +00:00
|
|
|
},
|
|
|
|
hasData: function( owner ) {
|
2013-02-11 17:39:44 +00:00
|
|
|
return !jQuery.isEmptyObject(
|
2013-02-23 17:41:37 +00:00
|
|
|
this.cache[ this.key( owner ) ]
|
2013-02-11 17:39:44 +00:00
|
|
|
);
|
2013-02-03 20:27:55 +00:00
|
|
|
},
|
|
|
|
discard: function( owner ) {
|
2013-02-23 17:41:37 +00:00
|
|
|
delete this.cache[ this.key( owner ) ];
|
2012-10-16 15:15:41 +00:00
|
|
|
}
|
2013-02-03 20:27:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// This will be used by remove()/cleanData() in manipulation to sever
|
|
|
|
// remaining references to node objects. One day we'll replace the dual
|
|
|
|
// arrays with a WeakMap and this won't be an issue.
|
|
|
|
// (Splices the data objects out of the internal cache arrays)
|
|
|
|
function data_discard( owner ) {
|
|
|
|
data_user.discard( owner );
|
|
|
|
data_priv.discard( owner );
|
|
|
|
}
|
2011-08-05 13:43:58 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// These may be used throughout the jQuery core codebase
|
|
|
|
data_user = new Data();
|
|
|
|
data_priv = new Data();
|
2011-11-06 22:24:44 +00:00
|
|
|
|
|
|
|
|
2012-10-16 15:15:41 +00:00
|
|
|
jQuery.extend({
|
2013-02-03 20:27:55 +00:00
|
|
|
// This is no longer relevant to jQuery core, but must remain
|
|
|
|
// supported for the sake of jQuery 1.9.x API surface compatibility.
|
|
|
|
acceptData: function() {
|
|
|
|
return true;
|
|
|
|
},
|
2011-01-09 21:52:33 +00:00
|
|
|
|
2012-10-16 15:15:41 +00:00
|
|
|
hasData: function( elem ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
return data_user.hasData( elem ) || data_priv.hasData( elem );
|
2012-10-16 15:15:41 +00:00
|
|
|
},
|
2012-07-06 13:22:44 +00:00
|
|
|
|
2012-10-16 15:15:41 +00:00
|
|
|
data: function( elem, name, data ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
return data_user.access( elem, name, data );
|
2012-10-16 15:15:41 +00:00
|
|
|
},
|
2012-07-06 13:22:44 +00:00
|
|
|
|
2012-10-16 15:15:41 +00:00
|
|
|
removeData: function( elem, name ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
return data_user.remove( elem, name );
|
2010-09-24 20:24:07 +00:00
|
|
|
},
|
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// TODO: Replace all calls to _data and _removeData with direct
|
|
|
|
// calls to
|
|
|
|
//
|
|
|
|
// data_priv.access( elem, name, data );
|
|
|
|
//
|
|
|
|
// data_priv.remove( elem, name );
|
|
|
|
//
|
2011-01-09 21:52:33 +00:00
|
|
|
_data: function( elem, name, data ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
return data_priv.access( elem, name, data );
|
2012-10-16 15:15:41 +00:00
|
|
|
},
|
2013-01-26 01:44:55 +00:00
|
|
|
|
2012-12-31 20:09:45 +00:00
|
|
|
_removeData: function( elem, name ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
return data_priv.remove( elem, name );
|
2009-11-25 17:09:53 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
jQuery.fn.extend({
|
2009-12-22 00:58:13 +00:00
|
|
|
data: function( key, value ) {
|
2012-10-31 17:55:51 +00:00
|
|
|
var attrs, name,
|
2011-12-06 20:25:38 +00:00
|
|
|
elem = this[0],
|
|
|
|
i = 0,
|
2011-09-19 20:13:57 +00:00
|
|
|
data = null;
|
2010-10-17 15:42:53 +00:00
|
|
|
|
2011-12-06 20:25:38 +00:00
|
|
|
// Gets all values
|
|
|
|
if ( key === undefined ) {
|
2010-10-17 15:42:53 +00:00
|
|
|
if ( this.length ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
data = data_user.get( elem );
|
2010-10-17 15:42:53 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
|
2012-10-31 17:55:51 +00:00
|
|
|
attrs = elem.attributes;
|
|
|
|
for ( ; i < attrs.length; i++ ) {
|
|
|
|
name = attrs[i].name;
|
2010-12-19 21:33:53 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
if ( name.indexOf( "data-" ) === 0 ) {
|
|
|
|
name = jQuery.camelCase( name.substring(5) );
|
2011-12-06 20:25:38 +00:00
|
|
|
dataAttr( elem, name, data[ name ] );
|
2010-12-03 07:19:39 +00:00
|
|
|
}
|
2010-10-17 15:42:53 +00:00
|
|
|
}
|
2013-02-03 20:27:55 +00:00
|
|
|
data_priv.set( elem, "hasDataAttrs", true );
|
2010-10-17 15:42:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
2011-12-06 20:25:38 +00:00
|
|
|
}
|
2009-12-09 21:16:18 +00:00
|
|
|
|
2011-12-06 20:25:38 +00:00
|
|
|
// Sets multiple values
|
|
|
|
if ( typeof key === "object" ) {
|
2009-12-09 21:16:18 +00:00
|
|
|
return this.each(function() {
|
2013-02-03 20:27:55 +00:00
|
|
|
data_user.set( this, key );
|
2009-12-09 21:16:18 +00:00
|
|
|
});
|
2009-11-25 17:09:53 +00:00
|
|
|
}
|
|
|
|
|
2011-12-06 20:25:38 +00:00
|
|
|
return jQuery.access( this, function( value ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
var data,
|
|
|
|
camelKey = jQuery.camelCase( key );
|
2011-12-06 20:25:38 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// Get the Data...
|
2011-12-06 20:25:38 +00:00
|
|
|
if ( value === undefined ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
|
|
|
|
// Attempt to get data from the cache
|
|
|
|
// with the key as-is
|
|
|
|
data = data_user.get( elem, key );
|
|
|
|
if ( data !== undefined ) {
|
|
|
|
return data;
|
|
|
|
}
|
2013-03-02 00:02:57 +00:00
|
|
|
|
|
|
|
// Attempt to get data from the cache
|
|
|
|
// with the key camelized
|
|
|
|
data = data_user.get( elem, camelKey );
|
|
|
|
if ( data !== undefined ) {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// Attempt to "discover" the data in
|
|
|
|
// HTML5 custom data-* attrs
|
|
|
|
data = dataAttr( elem, key, undefined );
|
|
|
|
if ( data !== undefined ) {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We tried really hard, but the data doesn't exist.
|
|
|
|
return undefined;
|
2009-11-27 19:28:42 +00:00
|
|
|
}
|
2010-07-27 17:45:32 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
// Set the data...
|
2011-12-06 20:25:38 +00:00
|
|
|
this.each(function() {
|
2013-02-03 20:27:55 +00:00
|
|
|
// First, attempt to store a copy or reference of any
|
|
|
|
// data that might've been store with a camelCased key.
|
|
|
|
var data = data_user.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...*
|
|
|
|
data_user.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 ( /-/.test( key ) && data !== undefined ) {
|
|
|
|
data_user.set( this, key, value );
|
|
|
|
}
|
2009-11-25 17:09:53 +00:00
|
|
|
});
|
2012-11-06 14:53:00 +00:00
|
|
|
}, null, value, arguments.length > 1, null, true );
|
2009-11-25 17:09:53 +00:00
|
|
|
},
|
|
|
|
|
2009-12-22 00:58:13 +00:00
|
|
|
removeData: function( key ) {
|
|
|
|
return this.each(function() {
|
2013-02-03 20:27:55 +00:00
|
|
|
data_user.remove( this, key );
|
2009-11-25 17:09:53 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2010-09-08 17:54:33 +00:00
|
|
|
|
2010-10-17 15:42:53 +00:00
|
|
|
function dataAttr( elem, key, data ) {
|
2013-02-03 20:27:55 +00:00
|
|
|
var name;
|
|
|
|
|
2010-10-17 15:42:53 +00:00
|
|
|
// If nothing was found internally, try to fetch any
|
|
|
|
// data from the HTML5 data-* attribute
|
|
|
|
if ( data === undefined && elem.nodeType === 1 ) {
|
2011-09-07 14:13:22 +00:00
|
|
|
|
2013-02-03 20:27:55 +00:00
|
|
|
name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
|
2011-04-10 19:17:00 +00:00
|
|
|
data = elem.getAttribute( name );
|
2010-10-17 15:42:53 +00:00
|
|
|
|
|
|
|
if ( typeof data === "string" ) {
|
|
|
|
try {
|
|
|
|
data = data === "true" ? true :
|
2013-02-03 20:27:55 +00:00
|
|
|
data === "false" ? false :
|
|
|
|
data === "null" ? null :
|
|
|
|
// Only convert to a number if it doesn't change the string
|
|
|
|
+data + "" === data ? +data :
|
|
|
|
rbrace.test( data ) ?
|
|
|
|
JSON.parse( data ) : data;
|
2010-10-17 15:42:53 +00:00
|
|
|
} catch( e ) {}
|
|
|
|
|
|
|
|
// Make sure we set the data so it isn't changed later
|
2013-02-03 20:27:55 +00:00
|
|
|
data_user.set( elem, key, data );
|
2010-10-17 15:42:53 +00:00
|
|
|
} else {
|
|
|
|
data = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|