Update copyright year
[jquery.git] / src / data.js
blob670a3ef8e07d9451a60eae79d50c2c63d28ed6cc
1 var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
2         rmultiDash = /([A-Z])/g;
4 function internalData( elem, name, data, pvt /* Internal Use Only */ ){
5         if ( !jQuery.acceptData( elem ) ) {
6                 return;
7         }
9         var ret, thisCache,
10                 internalKey = jQuery.expando,
12                 // We have to handle DOM nodes and JS objects differently because IE6-7
13                 // can't GC object references properly across the DOM-JS boundary
14                 isNode = elem.nodeType,
16                 // Only DOM nodes need the global jQuery cache; JS object data is
17                 // attached directly to the object so GC can occur automatically
18                 cache = isNode ? jQuery.cache : elem,
20                 // Only defining an ID for JS objects if its cache already exists allows
21                 // the code to shortcut on the same path as a DOM node with no cache
22                 id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
24         // Avoid doing any more work than we need to when trying to get data on an
25         // object that has no data at all
26         if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
27                 return;
28         }
30         if ( !id ) {
31                 // Only DOM nodes need a new unique ID for each element since their data
32                 // ends up in the global cache
33                 if ( isNode ) {
34                         id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;
35                 } else {
36                         id = internalKey;
37                 }
38         }
40         if ( !cache[ id ] ) {
41                 // Avoid exposing jQuery metadata on plain JS objects when the object
42                 // is serialized using JSON.stringify
43                 cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
44         }
46         // An object can be passed to jQuery.data instead of a key/value pair; this gets
47         // shallow copied over onto the existing cache
48         if ( typeof name === "object" || typeof name === "function" ) {
49                 if ( pvt ) {
50                         cache[ id ] = jQuery.extend( cache[ id ], name );
51                 } else {
52                         cache[ id ].data = jQuery.extend( cache[ id ].data, name );
53                 }
54         }
56         thisCache = cache[ id ];
58         // jQuery data() is stored in a separate object inside the object's internal data
59         // cache in order to avoid key collisions between internal data and user-defined
60         // data.
61         if ( !pvt ) {
62                 if ( !thisCache.data ) {
63                         thisCache.data = {};
64                 }
66                 thisCache = thisCache.data;
67         }
69         if ( data !== undefined ) {
70                 thisCache[ jQuery.camelCase( name ) ] = data;
71         }
73         // Check for both converted-to-camel and non-converted data property names
74         // If a data property was specified
75         if ( typeof name === "string" ) {
77                 // First Try to find as-is property data
78                 ret = thisCache[ name ];
80                 // Test for null|undefined property data
81                 if ( ret == null ) {
83                         // Try to find the camelCased property
84                         ret = thisCache[ jQuery.camelCase( name ) ];
85                 }
86         } else {
87                 ret = thisCache;
88         }
90         return ret;
93 function internalRemoveData( elem, name, pvt ) {
94         if ( !jQuery.acceptData( elem ) ) {
95                 return;
96         }
98         var thisCache, i,
99                 isNode = elem.nodeType,
101                 // See jQuery.data for more information
102                 cache = isNode ? jQuery.cache : elem,
103                 id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
105         // If there is already no cache entry for this object, there is no
106         // purpose in continuing
107         if ( !cache[ id ] ) {
108                 return;
109         }
111         if ( name ) {
113                 thisCache = pvt ? cache[ id ] : cache[ id ].data;
115                 if ( thisCache ) {
117                         // Support array or space separated string names for data keys
118                         if ( !jQuery.isArray( name ) ) {
120                                 // try the string as a key before any manipulation
121                                 if ( name in thisCache ) {
122                                         name = [ name ];
123                                 } else {
125                                         // split the camel cased version by spaces unless a key with the spaces exists
126                                         name = jQuery.camelCase( name );
127                                         if ( name in thisCache ) {
128                                                 name = [ name ];
129                                         } else {
130                                                 name = name.split(" ");
131                                         }
132                                 }
133                         } else {
134                                 // If "name" is an array of keys...
135                                 // When data is initially created, via ("key", "val") signature,
136                                 // keys will be converted to camelCase.
137                                 // Since there is no way to tell _how_ a key was added, remove
138                                 // both plain key and camelCase key. #12786
139                                 // This will only penalize the array argument path.
140                                 name = name.concat( jQuery.map( name, jQuery.camelCase ) );
141                         }
143                         i = name.length;
144                         while ( i-- ) {
145                                 delete thisCache[ name[i] ];
146                         }
148                         // If there is no data left in the cache, we want to continue
149                         // and let the cache object itself get destroyed
150                         if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
151                                 return;
152                         }
153                 }
154         }
156         // See jQuery.data for more information
157         if ( !pvt ) {
158                 delete cache[ id ].data;
160                 // Don't destroy the parent cache unless the internal data object
161                 // had been the only thing left in it
162                 if ( !isEmptyDataObject( cache[ id ] ) ) {
163                         return;
164                 }
165         }
167         // Destroy the cache
168         if ( isNode ) {
169                 jQuery.cleanData( [ elem ], true );
171         // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
172         } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
173                 delete cache[ id ];
175         // When all else fails, null
176         } else {
177                 cache[ id ] = null;
178         }
181 jQuery.extend({
182         cache: {},
184         // The following elements throw uncatchable exceptions if you
185         // attempt to add expando properties to them.
186         noData: {
187                 "applet": true,
188                 "embed": true,
189                 // Ban all objects except for Flash (which handle expandos)
190                 "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
191         },
193         hasData: function( elem ) {
194                 elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
195                 return !!elem && !isEmptyDataObject( elem );
196         },
198         data: function( elem, name, data ) {
199                 return internalData( elem, name, data );
200         },
202         removeData: function( elem, name ) {
203                 return internalRemoveData( elem, name );
204         },
206         // For internal use only.
207         _data: function( elem, name, data ) {
208                 return internalData( elem, name, data, true );
209         },
211         _removeData: function( elem, name ) {
212                 return internalRemoveData( elem, name, true );
213         },
215         // A method for determining if a DOM node can handle the data expando
216         acceptData: function( elem ) {
217                 // Do not set data on non-element because it will not be cleared (#8335).
218                 if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
219                         return false;
220                 }
222                 var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
224                 // nodes accept data unless otherwise specified; rejection can be conditional
225                 return !noData || noData !== true && elem.getAttribute("classid") === noData;
226         }
229 jQuery.fn.extend({
230         data: function( key, value ) {
231                 var attrs, name,
232                         data = null,
233                         i = 0,
234                         elem = this[0];
236                 // Special expections of .data basically thwart jQuery.access,
237                 // so implement the relevant behavior ourselves
239                 // Gets all values
240                 if ( key === undefined ) {
241                         if ( this.length ) {
242                                 data = jQuery.data( elem );
244                                 if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
245                                         attrs = elem.attributes;
246                                         for ( ; i < attrs.length; i++ ) {
247                                                 name = attrs[i].name;
249                                                 if ( name.indexOf("data-") === 0 ) {
250                                                         name = jQuery.camelCase( name.slice(5) );
252                                                         dataAttr( elem, name, data[ name ] );
253                                                 }
254                                         }
255                                         jQuery._data( elem, "parsedAttrs", true );
256                                 }
257                         }
259                         return data;
260                 }
262                 // Sets multiple values
263                 if ( typeof key === "object" ) {
264                         return this.each(function() {
265                                 jQuery.data( this, key );
266                         });
267                 }
269                 return arguments.length > 1 ?
271                         // Sets one value
272                         this.each(function() {
273                                 jQuery.data( this, key, value );
274                         }) :
276                         // Gets one value
277                         // Try to fetch any internally stored data first
278                         elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
279         },
281         removeData: function( key ) {
282                 return this.each(function() {
283                         jQuery.removeData( this, key );
284                 });
285         }
288 function dataAttr( elem, key, data ) {
289         // If nothing was found internally, try to fetch any
290         // data from the HTML5 data-* attribute
291         if ( data === undefined && elem.nodeType === 1 ) {
293                 var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
295                 data = elem.getAttribute( name );
297                 if ( typeof data === "string" ) {
298                         try {
299                                 data = data === "true" ? true :
300                                         data === "false" ? false :
301                                         data === "null" ? null :
302                                         // Only convert to a number if it doesn't change the string
303                                         +data + "" === data ? +data :
304                                         rbrace.test( data ) ? jQuery.parseJSON( data ) :
305                                                 data;
306                         } catch( e ) {}
308                         // Make sure we set the data so it isn't changed later
309                         jQuery.data( elem, key, data );
311                 } else {
312                         data = undefined;
313                 }
314         }
316         return data;
319 // checks a cache object for emptiness
320 function isEmptyDataObject( obj ) {
321         var name;
322         for ( name in obj ) {
324                 // if the public data object is empty, the private is still empty
325                 if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
326                         continue;
327                 }
328                 if ( name !== "toJSON" ) {
329                         return false;
330                 }
331         }
333         return true;