WebStorage.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-data-proxy-WebStorage'>/**
  19. </span> * @author Ed Spencer
  20. *
  21. * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
  22. * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
  23. * save {@link Ext.data.Model model instances} for offline use.
  24. * @private
  25. */
  26. Ext.define('Ext.data.proxy.WebStorage', {
  27. extend: 'Ext.data.proxy.Client',
  28. alternateClassName: 'Ext.data.WebStorageProxy',
  29. requires: [
  30. 'Ext.data.SequentialIdGenerator'
  31. ],
  32. <span id='Ext-data-proxy-WebStorage-cfg-id'> /**
  33. </span> * @cfg {String} id
  34. * The unique ID used as the key in which all record data are stored in the local storage object.
  35. */
  36. id: undefined,
  37. <span id='Ext-data-proxy-WebStorage-method-constructor'> /**
  38. </span> * Creates the proxy, throws an error if local storage is not supported in the current browser.
  39. * @param {Object} config (optional) Config object.
  40. */
  41. constructor: function(config) {
  42. this.callParent(arguments);
  43. <span id='Ext-data-proxy-WebStorage-property-cache'> /**
  44. </span> * @property {Object} cache
  45. * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
  46. */
  47. this.cache = {};
  48. //&lt;debug&gt;
  49. if (this.getStorageObject() === undefined) {
  50. Ext.Error.raise(&quot;Local Storage is not supported in this browser, please use another type of data proxy&quot;);
  51. }
  52. //&lt;/debug&gt;
  53. //if an id is not given, try to use the store's id instead
  54. this.id = this.id || (this.store ? this.store.storeId : undefined);
  55. //&lt;debug&gt;
  56. if (this.id === undefined) {
  57. Ext.Error.raise(&quot;No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details&quot;);
  58. }
  59. //&lt;/debug&gt;
  60. this.initialize();
  61. },
  62. //inherit docs
  63. create: function(operation, callback, scope) {
  64. var me = this,
  65. records = operation.records,
  66. length = records.length,
  67. ids = me.getIds(),
  68. id, record, i;
  69. operation.setStarted();
  70. if(me.isHierarchical === undefined) {
  71. // if the storage object does not yet contain any data, this is the first point at which we can determine whether or not this proxy deals with hierarchical data.
  72. // it cannot be determined during initialization because the Model is not decorated with NodeInterface until it is used in a TreeStore
  73. me.isHierarchical = !!records[0].isNode;
  74. if(me.isHierarchical) {
  75. me.getStorageObject().setItem(me.getTreeKey(), true);
  76. }
  77. }
  78. for (i = 0; i &lt; length; i++) {
  79. record = records[i];
  80. if (record.phantom) {
  81. record.phantom = false;
  82. id = me.getNextId();
  83. } else {
  84. id = record.getId();
  85. }
  86. me.setRecord(record, id);
  87. record.commit();
  88. ids.push(id);
  89. }
  90. me.setIds(ids);
  91. operation.setCompleted();
  92. operation.setSuccessful();
  93. if (typeof callback == 'function') {
  94. callback.call(scope || me, operation);
  95. }
  96. },
  97. //inherit docs
  98. read: function(operation, callback, scope) {
  99. //TODO: respect sorters, filters, start and limit options on the Operation
  100. var me = this,
  101. records = [],
  102. i = 0,
  103. success = true,
  104. Model = me.model,
  105. ids, length, record, data, id;
  106. operation.setStarted();
  107. if(me.isHierarchical) {
  108. records = me.getTreeData();
  109. } else {
  110. ids = me.getIds();
  111. length = ids.length;
  112. id = operation.id;
  113. //read a single record
  114. if (id) {
  115. data = me.getRecord(id);
  116. if (data !== null) {
  117. record = new Model(data, id, data);
  118. }
  119. if (record) {
  120. records.push(record);
  121. } else {
  122. success = false;
  123. }
  124. } else {
  125. for (; i &lt; length; i++) {
  126. id = ids[i];
  127. data = me.getRecord(id);
  128. records.push(new Model(data, id, data));
  129. }
  130. }
  131. }
  132. if(success) {
  133. operation.setSuccessful();
  134. }
  135. operation.setCompleted();
  136. operation.resultSet = Ext.create('Ext.data.ResultSet', {
  137. records: records,
  138. total : records.length,
  139. loaded : true
  140. });
  141. if (typeof callback == 'function') {
  142. callback.call(scope || me, operation);
  143. }
  144. },
  145. //inherit docs
  146. update: function(operation, callback, scope) {
  147. var records = operation.records,
  148. length = records.length,
  149. ids = this.getIds(),
  150. record, id, i;
  151. operation.setStarted();
  152. for (i = 0; i &lt; length; i++) {
  153. record = records[i];
  154. this.setRecord(record);
  155. record.commit();
  156. //we need to update the set of ids here because it's possible that a non-phantom record was added
  157. //to this proxy - in which case the record's id would never have been added via the normal 'create' call
  158. id = record.getId();
  159. if (id !== undefined &amp;&amp; Ext.Array.indexOf(ids, id) == -1) {
  160. ids.push(id);
  161. }
  162. }
  163. this.setIds(ids);
  164. operation.setCompleted();
  165. operation.setSuccessful();
  166. if (typeof callback == 'function') {
  167. callback.call(scope || this, operation);
  168. }
  169. },
  170. //inherit
  171. destroy: function(operation, callback, scope) {
  172. var me = this,
  173. records = operation.records,
  174. ids = me.getIds(),
  175. idLength = ids.length,
  176. newIds = [],
  177. removedHash = {},
  178. i = records.length,
  179. id;
  180. operation.setStarted();
  181. for (; i--;) {
  182. Ext.apply(removedHash, me.removeRecord(records[i]));
  183. }
  184. for(i = 0; i &lt; idLength; i++) {
  185. id = ids[i];
  186. if(!removedHash[id]) {
  187. newIds.push(id);
  188. }
  189. }
  190. me.setIds(newIds);
  191. operation.setCompleted();
  192. operation.setSuccessful();
  193. if (typeof callback == 'function') {
  194. callback.call(scope || me, operation);
  195. }
  196. },
  197. <span id='Ext-data-proxy-WebStorage-method-getRecord'> /**
  198. </span> * @private
  199. * Fetches record data from the Proxy by ID.
  200. * @param {String} id The record's unique ID
  201. * @return {Object} The record data
  202. */
  203. getRecord: function(id) {
  204. var me = this,
  205. cache = me.cache,
  206. data = !cache[id] ? Ext.decode(me.getStorageObject().getItem(me.getRecordKey(id))) : cache[id];
  207. if(!data) {
  208. return null;
  209. }
  210. cache[id] = data;
  211. data[me.model.prototype.idProperty] = id;
  212. return data;
  213. },
  214. <span id='Ext-data-proxy-WebStorage-method-setRecord'> /**
  215. </span> * Saves the given record in the Proxy.
  216. * @param {Ext.data.Model} record The model instance
  217. * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
  218. */
  219. setRecord: function(record, id) {
  220. if (id) {
  221. record.setId(id);
  222. } else {
  223. id = record.getId();
  224. }
  225. var me = this,
  226. rawData = record.data,
  227. data = {},
  228. model = me.model,
  229. fields = model.prototype.fields.items,
  230. length = fields.length,
  231. i = 0,
  232. field, name, obj, key;
  233. for (; i &lt; length; i++) {
  234. field = fields[i];
  235. name = field.name;
  236. if(field.persist) {
  237. data[name] = rawData[name];
  238. }
  239. }
  240. // no need to store the id in the data, since it is already stored in the record key
  241. delete data[me.model.prototype.idProperty];
  242. // if the record is a tree node and it's a direct child of the root node, do not store the parentId
  243. if(record.isNode &amp;&amp; record.get('depth') === 1) {
  244. delete data.parentId;
  245. }
  246. obj = me.getStorageObject();
  247. key = me.getRecordKey(id);
  248. //keep the cache up to date
  249. me.cache[id] = data;
  250. //iPad bug requires that we remove the item before setting it
  251. obj.removeItem(key);
  252. obj.setItem(key, Ext.encode(data));
  253. },
  254. <span id='Ext-data-proxy-WebStorage-method-removeRecord'> /**
  255. </span> * @private
  256. * Physically removes a given record from the local storage and recursively removes children if the record is a tree node. Used internally by {@link #destroy}.
  257. * @param {Ext.data.Model} record The record to remove
  258. * @return {Object} a hash with the ids of the records that were removed as keys and the records that were removed as values
  259. */
  260. removeRecord: function(record) {
  261. var me = this,
  262. id = record.getId(),
  263. records = {},
  264. i, childNodes;
  265. records[id] = record;
  266. me.getStorageObject().removeItem(me.getRecordKey(id));
  267. delete me.cache[id];
  268. if(record.childNodes) {
  269. childNodes = record.childNodes;
  270. for(i = childNodes.length; i--;) {
  271. Ext.apply(records, me.removeRecord(childNodes[i]));
  272. }
  273. }
  274. return records;
  275. },
  276. <span id='Ext-data-proxy-WebStorage-method-getRecordKey'> /**
  277. </span> * @private
  278. * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
  279. * storing data in the local storage object and should prevent naming collisions.
  280. * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
  281. * @return {String} The unique key for this record
  282. */
  283. getRecordKey: function(id) {
  284. if (id.isModel) {
  285. id = id.getId();
  286. }
  287. return Ext.String.format(&quot;{0}-{1}&quot;, this.id, id);
  288. },
  289. <span id='Ext-data-proxy-WebStorage-method-getRecordCounterKey'> /**
  290. </span> * @private
  291. * Returns the unique key used to store the current record counter for this proxy. This is used internally when
  292. * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
  293. * @return {String} The counter key
  294. */
  295. getRecordCounterKey: function() {
  296. return Ext.String.format(&quot;{0}-counter&quot;, this.id);
  297. },
  298. <span id='Ext-data-proxy-WebStorage-method-getTreeKey'> /**
  299. </span> * @private
  300. * Returns the unique key used to store the tree indicator. This is used internally to determine if the stored data is hierarchical
  301. * @return {String} The counter key
  302. */
  303. getTreeKey: function() {
  304. return Ext.String.format(&quot;{0}-tree&quot;, this.id);
  305. },
  306. <span id='Ext-data-proxy-WebStorage-method-getIds'> /**
  307. </span> * @private
  308. * Returns the array of record IDs stored in this Proxy
  309. * @return {Number[]} The record IDs. Each is cast as a Number
  310. */
  311. getIds: function() {
  312. var me = this,
  313. ids = (me.getStorageObject().getItem(me.id) || &quot;&quot;).split(&quot;,&quot;),
  314. model = me.model,
  315. length = ids.length,
  316. isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',
  317. i;
  318. if (length == 1 &amp;&amp; ids[0] === &quot;&quot;) {
  319. ids = [];
  320. } else {
  321. for (i = 0; i &lt; length; i++) {
  322. ids[i] = isString ? ids[i] : +ids[i];
  323. }
  324. }
  325. return ids;
  326. },
  327. <span id='Ext-data-proxy-WebStorage-method-setIds'> /**
  328. </span> * @private
  329. * Saves the array of ids representing the set of all records in the Proxy
  330. * @param {Number[]} ids The ids to set
  331. */
  332. setIds: function(ids) {
  333. var obj = this.getStorageObject(),
  334. str = ids.join(&quot;,&quot;);
  335. obj.removeItem(this.id);
  336. if (!Ext.isEmpty(str)) {
  337. obj.setItem(this.id, str);
  338. }
  339. },
  340. <span id='Ext-data-proxy-WebStorage-method-getNextId'> /**
  341. </span> * @private
  342. * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
  343. * Increments the counter.
  344. * @return {Number} The id
  345. */
  346. getNextId: function() {
  347. var me = this,
  348. obj = me.getStorageObject(),
  349. key = me.getRecordCounterKey(),
  350. model = me.model,
  351. isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',
  352. id;
  353. id = me.idGenerator.generate();
  354. obj.setItem(key, id);
  355. if(!isString) {
  356. id = +id;
  357. }
  358. return id;
  359. },
  360. <span id='Ext-data-proxy-WebStorage-method-getTreeData'> /**
  361. </span> * Gets tree data and transforms it from key value pairs into a hierarchical structure.
  362. * @private
  363. * @return {Ext.data.NodeInterface[]}
  364. */
  365. getTreeData: function() {
  366. var me = this,
  367. ids = me.getIds(),
  368. length = ids.length,
  369. records = [],
  370. recordHash = {},
  371. root = [],
  372. i = 0,
  373. Model = me.model,
  374. idProperty = Model.prototype.idProperty,
  375. rootLength, record, parent, parentId, children, id;
  376. for(; i &lt; length; i++) {
  377. id = ids[i];
  378. // get the record for each id
  379. record = me.getRecord(id);
  380. // push the record into the records array
  381. records.push(record);
  382. // add the record to the record hash so it can be easily retrieved by id later
  383. recordHash[id] = record;
  384. if(!record.parentId) {
  385. // push records that are at the root level (those with no parent id) into the &quot;root&quot; array
  386. root.push(record);
  387. }
  388. }
  389. rootLength = root.length;
  390. // sort the records by parent id for greater efficiency, so that each parent record only has to be found once for all of its children
  391. Ext.Array.sort(records, me.sortByParentId);
  392. // append each record to its parent, starting after the root node(s), since root nodes do not need to be attached to a parent
  393. for(i = rootLength; i &lt; length; i++) {
  394. record = records[i];
  395. parentId = record.parentId;
  396. if(!parent || parent[idProperty] !== parentId) {
  397. // if this record has a different parent id from the previous record, we need to look up the parent by id.
  398. parent = recordHash[parentId];
  399. parent.children = children = [];
  400. }
  401. // push the record onto its parent's children array
  402. children.push(record);
  403. }
  404. for(i = length; i--;) {
  405. record = records[i];
  406. if(!record.children &amp;&amp; !record.leaf) {
  407. // set non-leaf nodes with no children to loaded so the proxy won't try to dynamically load their contents when they are expanded
  408. record.loaded = true;
  409. }
  410. }
  411. // Create model instances out of all the &quot;root-level&quot; nodes.
  412. for(i = rootLength; i--;) {
  413. record = root[i];
  414. root[i] = new Model(record, record[idProperty], record);
  415. }
  416. return root;
  417. },
  418. <span id='Ext-data-proxy-WebStorage-method-sortByParentId'> /**
  419. </span> * Sorter function for sorting records by parentId
  420. * @private
  421. * @param {Object} node1
  422. * @param {Object} node2
  423. * @return {Number}
  424. */
  425. sortByParentId: function(node1, node2) {
  426. return (node1.parentId || 0) - (node2.parentId || 0);
  427. },
  428. <span id='Ext-data-proxy-WebStorage-method-initialize'> /**
  429. </span> * @private
  430. * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
  431. * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
  432. */
  433. initialize: function() {
  434. var me = this,
  435. storageObject = me.getStorageObject(),
  436. lastId = +storageObject.getItem(me.getRecordCounterKey());
  437. storageObject.setItem(me.id, storageObject.getItem(me.id) || &quot;&quot;);
  438. if(storageObject.getItem(me.getTreeKey())) {
  439. me.isHierarchical = true;
  440. }
  441. me.idGenerator = new Ext.data.SequentialIdGenerator({
  442. seed: lastId ? lastId + 1 : 1
  443. });
  444. },
  445. <span id='Ext-data-proxy-WebStorage-method-clear'> /**
  446. </span> * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
  447. * storage object.
  448. */
  449. clear: function() {
  450. var me = this,
  451. obj = me.getStorageObject(),
  452. ids = me.getIds(),
  453. len = ids.length,
  454. i;
  455. //remove all the records
  456. for (i = 0; i &lt; len; i++) {
  457. obj.removeItem(me.getRecordKey(ids[i]));
  458. }
  459. //remove the supporting objects
  460. obj.removeItem(me.getRecordCounterKey());
  461. obj.removeItem(me.getTreeKey());
  462. obj.removeItem(me.id);
  463. // clear the cache
  464. me.cache = {};
  465. },
  466. <span id='Ext-data-proxy-WebStorage-method-getStorageObject'> /**
  467. </span> * @private
  468. * Abstract function which should return the storage object that data will be saved to. This must be implemented
  469. * in each subclass.
  470. * @return {Object} The storage object
  471. */
  472. getStorageObject: function() {
  473. //&lt;debug&gt;
  474. Ext.Error.raise(&quot;The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass&quot;);
  475. //&lt;/debug&gt;
  476. }
  477. });</pre>
  478. </body>
  479. </html>