/ext-4.0.7/src/data/proxy/WebStorage.js

https://bitbucket.org/srogerf/javascript · JavaScript · 416 lines · 232 code · 69 blank · 115 comment · 52 complexity · b165a3a224b3ea162bafc3414f79f3b6 MD5 · raw file

  1. /*
  2. This file is part of Ext JS 4
  3. Copyright (c) 2011 Sencha Inc
  4. Contact: http://www.sencha.com/contact
  5. GNU General Public License Usage
  6. This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
  7. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
  8. */
  9. /**
  10. * @author Ed Spencer
  11. *
  12. * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
  13. * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
  14. * save {@link Ext.data.Model model instances} for offline use.
  15. * @private
  16. */
  17. Ext.define('Ext.data.proxy.WebStorage', {
  18. extend: 'Ext.data.proxy.Client',
  19. alternateClassName: 'Ext.data.WebStorageProxy',
  20. /**
  21. * @cfg {String} id
  22. * The unique ID used as the key in which all record data are stored in the local storage object.
  23. */
  24. id: undefined,
  25. /**
  26. * Creates the proxy, throws an error if local storage is not supported in the current browser.
  27. * @param {Object} config (optional) Config object.
  28. */
  29. constructor: function(config) {
  30. this.callParent(arguments);
  31. /**
  32. * @property {Object} cache
  33. * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
  34. */
  35. this.cache = {};
  36. //<debug>
  37. if (this.getStorageObject() === undefined) {
  38. Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
  39. }
  40. //</debug>
  41. //if an id is not given, try to use the store's id instead
  42. this.id = this.id || (this.store ? this.store.storeId : undefined);
  43. //<debug>
  44. if (this.id === undefined) {
  45. Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
  46. }
  47. //</debug>
  48. this.initialize();
  49. },
  50. //inherit docs
  51. create: function(operation, callback, scope) {
  52. var records = operation.records,
  53. length = records.length,
  54. ids = this.getIds(),
  55. id, record, i;
  56. operation.setStarted();
  57. for (i = 0; i < length; i++) {
  58. record = records[i];
  59. if (record.phantom) {
  60. record.phantom = false;
  61. id = this.getNextId();
  62. } else {
  63. id = record.getId();
  64. }
  65. this.setRecord(record, id);
  66. ids.push(id);
  67. }
  68. this.setIds(ids);
  69. operation.setCompleted();
  70. operation.setSuccessful();
  71. if (typeof callback == 'function') {
  72. callback.call(scope || this, operation);
  73. }
  74. },
  75. //inherit docs
  76. read: function(operation, callback, scope) {
  77. //TODO: respect sorters, filters, start and limit options on the Operation
  78. var records = [],
  79. ids = this.getIds(),
  80. length = ids.length,
  81. i, recordData, record;
  82. //read a single record
  83. if (operation.id) {
  84. record = this.getRecord(operation.id);
  85. if (record) {
  86. records.push(record);
  87. operation.setSuccessful();
  88. }
  89. } else {
  90. for (i = 0; i < length; i++) {
  91. records.push(this.getRecord(ids[i]));
  92. }
  93. operation.setSuccessful();
  94. }
  95. operation.setCompleted();
  96. operation.resultSet = Ext.create('Ext.data.ResultSet', {
  97. records: records,
  98. total : records.length,
  99. loaded : true
  100. });
  101. if (typeof callback == 'function') {
  102. callback.call(scope || this, operation);
  103. }
  104. },
  105. //inherit docs
  106. update: function(operation, callback, scope) {
  107. var records = operation.records,
  108. length = records.length,
  109. ids = this.getIds(),
  110. record, id, i;
  111. operation.setStarted();
  112. for (i = 0; i < length; i++) {
  113. record = records[i];
  114. this.setRecord(record);
  115. //we need to update the set of ids here because it's possible that a non-phantom record was added
  116. //to this proxy - in which case the record's id would never have been added via the normal 'create' call
  117. id = record.getId();
  118. if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
  119. ids.push(id);
  120. }
  121. }
  122. this.setIds(ids);
  123. operation.setCompleted();
  124. operation.setSuccessful();
  125. if (typeof callback == 'function') {
  126. callback.call(scope || this, operation);
  127. }
  128. },
  129. //inherit
  130. destroy: function(operation, callback, scope) {
  131. var records = operation.records,
  132. length = records.length,
  133. ids = this.getIds(),
  134. //newIds is a copy of ids, from which we remove the destroyed records
  135. newIds = [].concat(ids),
  136. i;
  137. for (i = 0; i < length; i++) {
  138. Ext.Array.remove(newIds, records[i].getId());
  139. this.removeRecord(records[i], false);
  140. }
  141. this.setIds(newIds);
  142. operation.setCompleted();
  143. operation.setSuccessful();
  144. if (typeof callback == 'function') {
  145. callback.call(scope || this, operation);
  146. }
  147. },
  148. /**
  149. * @private
  150. * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
  151. * @param {String} id The record's unique ID
  152. * @return {Ext.data.Model} The model instance
  153. */
  154. getRecord: function(id) {
  155. if (this.cache[id] === undefined) {
  156. var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
  157. data = {},
  158. Model = this.model,
  159. fields = Model.prototype.fields.items,
  160. length = fields.length,
  161. i, field, name, record;
  162. for (i = 0; i < length; i++) {
  163. field = fields[i];
  164. name = field.name;
  165. if (typeof field.decode == 'function') {
  166. data[name] = field.decode(rawData[name]);
  167. } else {
  168. data[name] = rawData[name];
  169. }
  170. }
  171. record = new Model(data, id);
  172. record.phantom = false;
  173. this.cache[id] = record;
  174. }
  175. return this.cache[id];
  176. },
  177. /**
  178. * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
  179. * @param {Ext.data.Model} record The model instance
  180. * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
  181. */
  182. setRecord: function(record, id) {
  183. if (id) {
  184. record.setId(id);
  185. } else {
  186. id = record.getId();
  187. }
  188. var me = this,
  189. rawData = record.data,
  190. data = {},
  191. model = me.model,
  192. fields = model.prototype.fields.items,
  193. length = fields.length,
  194. i = 0,
  195. field, name, obj, key;
  196. for (; i < length; i++) {
  197. field = fields[i];
  198. name = field.name;
  199. if (typeof field.encode == 'function') {
  200. data[name] = field.encode(rawData[name], record);
  201. } else {
  202. data[name] = rawData[name];
  203. }
  204. }
  205. obj = me.getStorageObject();
  206. key = me.getRecordKey(id);
  207. //keep the cache up to date
  208. me.cache[id] = record;
  209. //iPad bug requires that we remove the item before setting it
  210. obj.removeItem(key);
  211. obj.setItem(key, Ext.encode(data));
  212. },
  213. /**
  214. * @private
  215. * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
  216. * use instead because it updates the list of currently-stored record ids
  217. * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
  218. */
  219. removeRecord: function(id, updateIds) {
  220. var me = this,
  221. ids;
  222. if (id.isModel) {
  223. id = id.getId();
  224. }
  225. if (updateIds !== false) {
  226. ids = me.getIds();
  227. Ext.Array.remove(ids, id);
  228. me.setIds(ids);
  229. }
  230. me.getStorageObject().removeItem(me.getRecordKey(id));
  231. },
  232. /**
  233. * @private
  234. * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
  235. * storing data in the local storage object and should prevent naming collisions.
  236. * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
  237. * @return {String} The unique key for this record
  238. */
  239. getRecordKey: function(id) {
  240. if (id.isModel) {
  241. id = id.getId();
  242. }
  243. return Ext.String.format("{0}-{1}", this.id, id);
  244. },
  245. /**
  246. * @private
  247. * Returns the unique key used to store the current record counter for this proxy. This is used internally when
  248. * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
  249. * @return {String} The counter key
  250. */
  251. getRecordCounterKey: function() {
  252. return Ext.String.format("{0}-counter", this.id);
  253. },
  254. /**
  255. * @private
  256. * Returns the array of record IDs stored in this Proxy
  257. * @return {Number[]} The record IDs. Each is cast as a Number
  258. */
  259. getIds: function() {
  260. var ids = (this.getStorageObject().getItem(this.id) || "").split(","),
  261. length = ids.length,
  262. i;
  263. if (length == 1 && ids[0] === "") {
  264. ids = [];
  265. } else {
  266. for (i = 0; i < length; i++) {
  267. ids[i] = parseInt(ids[i], 10);
  268. }
  269. }
  270. return ids;
  271. },
  272. /**
  273. * @private
  274. * Saves the array of ids representing the set of all records in the Proxy
  275. * @param {Number[]} ids The ids to set
  276. */
  277. setIds: function(ids) {
  278. var obj = this.getStorageObject(),
  279. str = ids.join(",");
  280. obj.removeItem(this.id);
  281. if (!Ext.isEmpty(str)) {
  282. obj.setItem(this.id, str);
  283. }
  284. },
  285. /**
  286. * @private
  287. * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
  288. * Increments the counter.
  289. * @return {Number} The id
  290. */
  291. getNextId: function() {
  292. var obj = this.getStorageObject(),
  293. key = this.getRecordCounterKey(),
  294. last = obj.getItem(key),
  295. ids, id;
  296. if (last === null) {
  297. ids = this.getIds();
  298. last = ids[ids.length - 1] || 0;
  299. }
  300. id = parseInt(last, 10) + 1;
  301. obj.setItem(key, id);
  302. return id;
  303. },
  304. /**
  305. * @private
  306. * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
  307. * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
  308. */
  309. initialize: function() {
  310. var storageObject = this.getStorageObject();
  311. storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
  312. },
  313. /**
  314. * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
  315. * storage object.
  316. */
  317. clear: function() {
  318. var obj = this.getStorageObject(),
  319. ids = this.getIds(),
  320. len = ids.length,
  321. i;
  322. //remove all the records
  323. for (i = 0; i < len; i++) {
  324. this.removeRecord(ids[i]);
  325. }
  326. //remove the supporting objects
  327. obj.removeItem(this.getRecordCounterKey());
  328. obj.removeItem(this.id);
  329. },
  330. /**
  331. * @private
  332. * Abstract function which should return the storage object that data will be saved to. This must be implemented
  333. * in each subclass.
  334. * @return {Object} The storage object
  335. */
  336. getStorageObject: function() {
  337. //<debug>
  338. Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
  339. //</debug>
  340. }
  341. });