data.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. Implementation Summary
  3. 1. Enforce API surface and semantic compatibility with 1.9.x branch
  4. 2. Improve the module's maintainability by reducing the storage
  5. paths to a single mechanism.
  6. 3. Use the same single mechanism to support "private" and "user" data.
  7. 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
  8. 5. Avoid exposing implementation details on user objects (eg. expando properties)
  9. 6. Provide a clear path for implementation upgrade to WeakMap in 2014
  10. */
  11. var data_user, data_priv,
  12. rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
  13. rmultiDash = /([A-Z])/g;
  14. function Data() {
  15. // Support: Android < 4,
  16. // Old WebKit does not have Object.preventExtensions/freeze method,
  17. // return new empty object instead with no [[set]] accessor
  18. Object.defineProperty( this.cache = {}, 0, {
  19. get: function() {
  20. return {};
  21. }
  22. });
  23. this.expando = jQuery.expando + Math.random();
  24. }
  25. Data.uid = 1;
  26. Data.accepts = function( owner ) {
  27. // Accepts only:
  28. // - Node
  29. // - Node.ELEMENT_NODE
  30. // - Node.DOCUMENT_NODE
  31. // - Object
  32. // - Any
  33. return owner.nodeType ?
  34. owner.nodeType === 1 || owner.nodeType === 9 : true;
  35. };
  36. Data.prototype = {
  37. key: function( owner ) {
  38. // We can accept data for non-element nodes in modern browsers,
  39. // but we should not, see #8335.
  40. // Always return the key for a frozen object.
  41. if ( !Data.accepts( owner ) ) {
  42. return 0;
  43. }
  44. var descriptor = {},
  45. // Check if the owner object already has a cache key
  46. unlock = owner[ this.expando ];
  47. // If not, create one
  48. if ( !unlock ) {
  49. unlock = Data.uid++;
  50. // Secure it in a non-enumerable, non-writable property
  51. try {
  52. descriptor[ this.expando ] = { value: unlock };
  53. Object.defineProperties( owner, descriptor );
  54. // Support: Android < 4
  55. // Fallback to a less secure definition
  56. } catch ( e ) {
  57. descriptor[ this.expando ] = unlock;
  58. jQuery.extend( owner, descriptor );
  59. }
  60. }
  61. // Ensure the cache object
  62. if ( !this.cache[ unlock ] ) {
  63. this.cache[ unlock ] = {};
  64. }
  65. return unlock;
  66. },
  67. set: function( owner, data, value ) {
  68. var prop,
  69. // There may be an unlock assigned to this node,
  70. // if there is no entry for this "owner", create one inline
  71. // and set the unlock as though an owner entry had always existed
  72. unlock = this.key( owner ),
  73. cache = this.cache[ unlock ];
  74. // Handle: [ owner, key, value ] args
  75. if ( typeof data === "string" ) {
  76. cache[ data ] = value;
  77. // Handle: [ owner, { properties } ] args
  78. } else {
  79. // Fresh assignments by object are shallow copied
  80. if ( jQuery.isEmptyObject( cache ) ) {
  81. jQuery.extend( this.cache[ unlock ], data );
  82. // Otherwise, copy the properties one-by-one to the cache object
  83. } else {
  84. for ( prop in data ) {
  85. cache[ prop ] = data[ prop ];
  86. }
  87. }
  88. }
  89. return cache;
  90. },
  91. get: function( owner, key ) {
  92. // Either a valid cache is found, or will be created.
  93. // New caches will be created and the unlock returned,
  94. // allowing direct access to the newly created
  95. // empty data object. A valid owner object must be provided.
  96. var cache = this.cache[ this.key( owner ) ];
  97. return key === undefined ?
  98. cache : cache[ key ];
  99. },
  100. access: function( owner, key, value ) {
  101. var stored;
  102. // In cases where either:
  103. //
  104. // 1. No key was specified
  105. // 2. A string key was specified, but no value provided
  106. //
  107. // Take the "read" path and allow the get method to determine
  108. // which value to return, respectively either:
  109. //
  110. // 1. The entire cache object
  111. // 2. The data stored at the key
  112. //
  113. if ( key === undefined ||
  114. ((key && typeof key === "string") && value === undefined) ) {
  115. stored = this.get( owner, key );
  116. return stored !== undefined ?
  117. stored : this.get( owner, jQuery.camelCase(key) );
  118. }
  119. // [*]When the key is not a string, or both a key and value
  120. // are specified, set or extend (existing objects) with either:
  121. //
  122. // 1. An object of properties
  123. // 2. A key and value
  124. //
  125. this.set( owner, key, value );
  126. // Since the "set" path can have two possible entry points
  127. // return the expected data based on which path was taken[*]
  128. return value !== undefined ? value : key;
  129. },
  130. remove: function( owner, key ) {
  131. var i, name, camel,
  132. unlock = this.key( owner ),
  133. cache = this.cache[ unlock ];
  134. if ( key === undefined ) {
  135. this.cache[ unlock ] = {};
  136. } else {
  137. // Support array or space separated string of keys
  138. if ( jQuery.isArray( key ) ) {
  139. // If "name" is an array of keys...
  140. // When data is initially created, via ("key", "val") signature,
  141. // keys will be converted to camelCase.
  142. // Since there is no way to tell _how_ a key was added, remove
  143. // both plain key and camelCase key. #12786
  144. // This will only penalize the array argument path.
  145. name = key.concat( key.map( jQuery.camelCase ) );
  146. } else {
  147. camel = jQuery.camelCase( key );
  148. // Try the string as a key before any manipulation
  149. if ( key in cache ) {
  150. name = [ key, camel ];
  151. } else {
  152. // If a key with the spaces exists, use it.
  153. // Otherwise, create an array by matching non-whitespace
  154. name = camel;
  155. name = name in cache ?
  156. [ name ] : ( name.match( core_rnotwhite ) || [] );
  157. }
  158. }
  159. i = name.length;
  160. while ( i-- ) {
  161. delete cache[ name[ i ] ];
  162. }
  163. }
  164. },
  165. hasData: function( owner ) {
  166. return !jQuery.isEmptyObject(
  167. this.cache[ owner[ this.expando ] ] || {}
  168. );
  169. },
  170. discard: function( owner ) {
  171. if ( owner[ this.expando ] ) {
  172. delete this.cache[ owner[ this.expando ] ];
  173. }
  174. }
  175. };
  176. // These may be used throughout the jQuery core codebase
  177. data_user = new Data();
  178. data_priv = new Data();
  179. jQuery.extend({
  180. acceptData: Data.accepts,
  181. hasData: function( elem ) {
  182. return data_user.hasData( elem ) || data_priv.hasData( elem );
  183. },
  184. data: function( elem, name, data ) {
  185. return data_user.access( elem, name, data );
  186. },
  187. removeData: function( elem, name ) {
  188. data_user.remove( elem, name );
  189. },
  190. // TODO: Now that all calls to _data and _removeData have been replaced
  191. // with direct calls to data_priv methods, these can be deprecated.
  192. _data: function( elem, name, data ) {
  193. return data_priv.access( elem, name, data );
  194. },
  195. _removeData: function( elem, name ) {
  196. data_priv.remove( elem, name );
  197. }
  198. });
  199. jQuery.fn.extend({
  200. data: function( key, value ) {
  201. var attrs, name,
  202. elem = this[ 0 ],
  203. i = 0,
  204. data = null;
  205. // Gets all values
  206. if ( key === undefined ) {
  207. if ( this.length ) {
  208. data = data_user.get( elem );
  209. if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
  210. attrs = elem.attributes;
  211. for ( ; i < attrs.length; i++ ) {
  212. name = attrs[ i ].name;
  213. if ( name.indexOf( "data-" ) === 0 ) {
  214. name = jQuery.camelCase( name.slice(5) );
  215. dataAttr( elem, name, data[ name ] );
  216. }
  217. }
  218. data_priv.set( elem, "hasDataAttrs", true );
  219. }
  220. }
  221. return data;
  222. }
  223. // Sets multiple values
  224. if ( typeof key === "object" ) {
  225. return this.each(function() {
  226. data_user.set( this, key );
  227. });
  228. }
  229. return jQuery.access( this, function( value ) {
  230. var data,
  231. camelKey = jQuery.camelCase( key );
  232. // The calling jQuery object (element matches) is not empty
  233. // (and therefore has an element appears at this[ 0 ]) and the
  234. // `value` parameter was not undefined. An empty jQuery object
  235. // will result in `undefined` for elem = this[ 0 ] which will
  236. // throw an exception if an attempt to read a data cache is made.
  237. if ( elem && value === undefined ) {
  238. // Attempt to get data from the cache
  239. // with the key as-is
  240. data = data_user.get( elem, key );
  241. if ( data !== undefined ) {
  242. return data;
  243. }
  244. // Attempt to get data from the cache
  245. // with the key camelized
  246. data = data_user.get( elem, camelKey );
  247. if ( data !== undefined ) {
  248. return data;
  249. }
  250. // Attempt to "discover" the data in
  251. // HTML5 custom data-* attrs
  252. data = dataAttr( elem, camelKey, undefined );
  253. if ( data !== undefined ) {
  254. return data;
  255. }
  256. // We tried really hard, but the data doesn't exist.
  257. return;
  258. }
  259. // Set the data...
  260. this.each(function() {
  261. // First, attempt to store a copy or reference of any
  262. // data that might've been store with a camelCased key.
  263. var data = data_user.get( this, camelKey );
  264. // For HTML5 data-* attribute interop, we have to
  265. // store property names with dashes in a camelCase form.
  266. // This might not apply to all properties...*
  267. data_user.set( this, camelKey, value );
  268. // *... In the case of properties that might _actually_
  269. // have dashes, we need to also store a copy of that
  270. // unchanged property.
  271. if ( key.indexOf("-") !== -1 && data !== undefined ) {
  272. data_user.set( this, key, value );
  273. }
  274. });
  275. }, null, value, arguments.length > 1, null, true );
  276. },
  277. removeData: function( key ) {
  278. return this.each(function() {
  279. data_user.remove( this, key );
  280. });
  281. }
  282. });
  283. function dataAttr( elem, key, data ) {
  284. var name;
  285. // If nothing was found internally, try to fetch any
  286. // data from the HTML5 data-* attribute
  287. if ( data === undefined && elem.nodeType === 1 ) {
  288. name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
  289. data = elem.getAttribute( name );
  290. if ( typeof data === "string" ) {
  291. try {
  292. data = data === "true" ? true :
  293. data === "false" ? false :
  294. data === "null" ? null :
  295. // Only convert to a number if it doesn't change the string
  296. +data + "" === data ? +data :
  297. rbrace.test( data ) ? JSON.parse( data ) :
  298. data;
  299. } catch( e ) {}
  300. // Make sure we set the data so it isn't changed later
  301. data_user.set( elem, key, data );
  302. } else {
  303. data = undefined;
  304. }
  305. }
  306. return data;
  307. }