jquery/src/data.js
2013-02-04 09:35:33 -05:00

326 lines
8.4 KiB
JavaScript

var data_user, data_priv,
rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
rmultiDash = /([A-Z])/g;
function Data() {
// Nodes|Objects
this.owners = [];
// Data objects
this.cache = [];
}
Data.index = function( array, node ) {
return array.indexOf( node );
};
Data.prototype = {
add: function( owner ) {
return (this.cache[ this.owners.push( owner ) - 1 ] = {});
},
set: function( owner, data, value ) {
var prop,
index = Data.index( this.owners, owner );
// If there is no entry for this "owner", create one inline
// and set the index as though an owner entry had always existed
if ( index === -1 ) {
this.add( owner );
index = this.owners.length - 1;
}
// Handle: [ owner, key, value ] args
if ( typeof data === "string" ) {
this.cache[ index ][ data ] = value;
// Handle: [ owner, { properties } ] args
} else {
// In the case where there was actually no "owner" entry and
// this.add( owner ) was called to create one, there will be
// a corresponding empty plain object in the cache.
if ( jQuery.isEmptyObject( this.cache[ index ] ) ) {
this.cache[ index ] = data;
// Otherwise, copy the properties one-by-one to the cache object
} else {
for ( prop in data ) {
this.cache[ index ][ prop ] = data[ prop ];
}
}
}
return this;
},
get: function( owner, key ) {
var cache,
index = Data.index( this.owners, owner );
// A valid cache is found, or needs to be created.
// New entries will be added and return the current
// empty data object to be used as a return reference
// return this.add( owner );
// This logic was required by expectations made of the
// old data system.
cache = index === -1 ?
this.add( owner ) : this.cache[ index ];
return key === undefined ?
cache : cache[ key ];
},
access: function( owner, key, value ) {
if ( value === undefined && (key && typeof key !== "object") ) {
// Assume this is a request to read the cached data
return this.get( owner, key );
} else {
// If only an owner was specified, return the entire
// cache object.
if ( key === undefined ) {
return this.get( owner );
}
// Allow setting or extending (existing objects) with an
// object of properties, or a key and val
this.set( owner, key, value );
return value !== undefined ? value : key;
}
// Otherwise, this is a read request.
return this.get( owner, key );
},
remove: function( owner, key ) {
var i, l, name,
camel = jQuery.camelCase,
index = Data.index( this.owners, owner ),
cache = this.cache[ index ];
if ( key === undefined ) {
cache = {};
} else {
if ( cache ) {
// Support array or space separated string of keys
if ( !Array.isArray( key ) ) {
// Try the string as a key before any manipulation
//
if ( key in cache ) {
name = [ key ];
} else {
// If a key with the spaces exists, use it.
// Otherwise, create an array by matching non-whitespace
name = camel( key );
name = name in cache ?
[ name ] : ( name.match( core_rnotwhite ) || [] );
}
} 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 = key.concat( key.map( camel ) );
}
i = 0;
l = name.length;
for ( ; i < l; i++ ) {
delete cache[ name[i] ];
}
}
}
this.cache[ index ] = cache;
},
hasData: function( owner ) {
var index = Data.index( this.owners, owner );
return index !== -1 && !jQuery.isEmptyObject( this.cache[ index ] );
},
discard: function( owner ) {
var index = Data.index( this.owners, owner );
if ( index !== -1 ) {
this.owners.splice( index, 1 );
this.cache.splice( index, 1 );
}
return this;
}
};
// 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 );
}
// These may be used throughout the jQuery core codebase
data_user = new Data();
data_priv = new Data();
jQuery.extend({
// 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;
},
// Unique for each copy of jQuery on the page
// Non-digits removed to match rinlinejQuery
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
hasData: function( elem ) {
return data_user.hasData( elem ) || data_priv.hasData( elem );
},
data: function( elem, name, data ) {
return data_user.access( elem, name, data );
},
removeData: function( elem, name ) {
return data_user.remove( elem, name );
},
// TODO: Replace all calls to _data and _removeData with direct
// calls to
//
// data_priv.access( elem, name, data );
//
// data_priv.remove( elem, name );
//
_data: function( elem, name, data ) {
return data_priv.access( elem, name, data );
},
_removeData: function( elem, name ) {
return data_priv.remove( elem, name );
}
});
jQuery.fn.extend({
data: function( key, value ) {
var attrs, name,
elem = this[0],
i = 0,
data = null;
// Gets all values
if ( key === undefined ) {
if ( this.length ) {
data = data_user.get( elem );
if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
attrs = elem.attributes;
for ( ; i < attrs.length; i++ ) {
name = attrs[i].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = jQuery.camelCase( name.substring(5) );
dataAttr( elem, name, data[ name ] );
}
}
data_priv.set( elem, "hasDataAttrs", true );
}
}
return data;
}
// Sets multiple values
if ( typeof key === "object" ) {
return this.each(function() {
data_user.set( this, key );
});
}
return jQuery.access( this, function( value ) {
var data,
camelKey = jQuery.camelCase( key );
// Get the Data...
if ( value === undefined ) {
// Attempt to get data from the cache
// with the key as-is
data = data_user.get( elem, key );
if ( data !== undefined ) {
return data;
}
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
data = dataAttr( elem, key, undefined );
if ( data !== undefined ) {
return data;
}
// As a last resort, attempt to find
// the data by checking AGAIN, but with
// a camelCased key.
data = data_user.get( elem, camelKey );
if ( data !== undefined ) {
return data;
}
// We tried really hard, but the data doesn't exist.
return undefined;
}
// Set the data...
this.each(function() {
// 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 );
}
});
}, null, value, arguments.length > 1, null, true );
},
removeData: function( key ) {
return this.each(function() {
data_user.remove( this, key );
});
}
});
function dataAttr( elem, key, data ) {
var name;
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) {
name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
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;
} catch( e ) {}
// Make sure we set the data so it isn't changed later
data_user.set( elem, key, data );
} else {
data = undefined;
}
}
return data;
}