ObjectStore.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojo.data.ObjectStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojo.data.ObjectStore"] = true;
  8. dojo.provide("dojo.data.ObjectStore");
  9. dojo.require("dojo.regexp");
  10. dojo.declare("dojo.data.ObjectStore", null,{
  11. objectStore: null,
  12. constructor: function(options){
  13. // summary:
  14. // A Dojo Data implementation that wraps Dojo object stores for backwards
  15. // compatibility.
  16. // options:
  17. // The configuration information to pass into the data store.
  18. // options.objectStore:
  19. // The object store to use as the source provider for this data store
  20. dojo.mixin(this, options);
  21. },
  22. labelProperty: "label",
  23. getValue: function(/*Object*/ item, /*String*/property, /*value?*/defaultValue){
  24. // summary:
  25. // Gets the value of an item's 'property'
  26. //
  27. // item:
  28. // The item to get the value from
  29. // property:
  30. // property to look up value for
  31. // defaultValue:
  32. // the default value
  33. return typeof item.get === "function" ? item.get(property) :
  34. property in item ?
  35. item[property] : defaultValue;
  36. },
  37. getValues: function(item, property){
  38. // summary:
  39. // Gets the value of an item's 'property' and returns
  40. // it. If this value is an array it is just returned,
  41. // if not, the value is added to an array and that is returned.
  42. //
  43. // item: /* object */
  44. // property: /* string */
  45. // property to look up value for
  46. var val = this.getValue(item,property);
  47. return val instanceof Array ? val : val === undefined ? [] : [val];
  48. },
  49. getAttributes: function(item){
  50. // summary:
  51. // Gets the available attributes of an item's 'property' and returns
  52. // it as an array.
  53. //
  54. // item: /* object */
  55. var res = [];
  56. for(var i in item){
  57. if(item.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){
  58. res.push(i);
  59. }
  60. }
  61. return res;
  62. },
  63. hasAttribute: function(item,attribute){
  64. // summary:
  65. // Checks to see if item has attribute
  66. //
  67. // item: /* object */
  68. // attribute: /* string */
  69. return attribute in item;
  70. },
  71. containsValue: function(item, attribute, value){
  72. // summary:
  73. // Checks to see if 'item' has 'value' at 'attribute'
  74. //
  75. // item: /* object */
  76. // attribute: /* string */
  77. // value: /* anything */
  78. return dojo.indexOf(this.getValues(item,attribute),value) > -1;
  79. },
  80. isItem: function(item){
  81. // summary:
  82. // Checks to see if the argument is an item
  83. //
  84. // item: /* object */
  85. // attribute: /* string */
  86. // we have no way of determining if it belongs, we just have object returned from
  87. // service queries
  88. return (typeof item == 'object') && item && !(item instanceof Date);
  89. },
  90. isItemLoaded: function(item){
  91. // summary:
  92. // Checks to see if the item is loaded.
  93. //
  94. // item: /* object */
  95. return item && typeof item.load !== "function";
  96. },
  97. loadItem: function(args){
  98. // summary:
  99. // Loads an item and calls the callback handler. Note, that this will call the callback
  100. // handler even if the item is loaded. Consequently, you can use loadItem to ensure
  101. // that an item is loaded is situations when the item may or may not be loaded yet.
  102. // If you access a value directly through property access, you can use this to load
  103. // a lazy value as well (doesn't need to be an item).
  104. //
  105. // example:
  106. // store.loadItem({
  107. // item: item, // this item may or may not be loaded
  108. // onItem: function(item){
  109. // // do something with the item
  110. // }
  111. // });
  112. var item;
  113. if(typeof args.item.load === "function"){
  114. dojo.when(args.item.load(), function(result){
  115. item = result; // in synchronous mode this can allow loadItem to return the value
  116. var func = result instanceof Error ? args.onError : args.onItem;
  117. if(func){
  118. func.call(args.scope, result);
  119. }
  120. });
  121. }else if(args.onItem){
  122. // even if it is already loaded, we will use call the callback, this makes it easier to
  123. // use when it is not known if the item is loaded (you can always safely call loadItem).
  124. args.onItem.call(args.scope, args.item);
  125. }
  126. return item;
  127. },
  128. close: function(request){
  129. return request && request.abort && request.abort();
  130. },
  131. fetch: function(args){
  132. // summary:
  133. // See dojo.data.api.Read.fetch
  134. //
  135. args = args || {};
  136. var self = this;
  137. var scope = args.scope || self;
  138. var query = args.query;
  139. if(typeof query == "object"){ // can be null, but that is ignore by for-in
  140. query = dojo.delegate(query); // don't modify the original
  141. for(var i in query){
  142. // find any strings and convert them to regular expressions for wildcard support
  143. var required = query[i];
  144. if(typeof required == "string"){
  145. query[i] = RegExp("^" + dojo.regexp.escapeString(required, "*?").replace(/\*/g, '.*').replace(/\?/g, '.') + "$", args.queryOptions && args.queryOptions.ignoreCase ? "mi" : "m");
  146. query[i].toString = (function(original){
  147. return function(){
  148. return original;
  149. }
  150. })(required);
  151. }
  152. }
  153. }
  154. var results = this.objectStore.query(query, args);
  155. dojo.when(results.total, function(totalCount){
  156. dojo.when(results, function(results){
  157. if(args.onBegin){
  158. args.onBegin.call(scope, totalCount || results.length, args);
  159. }
  160. if(args.onItem){
  161. for(var i=0; i<results.length;i++){
  162. args.onItem.call(scope, results[i], args);
  163. }
  164. }
  165. if(args.onComplete){
  166. args.onComplete.call(scope, args.onItem ? null : results, args);
  167. }
  168. return results;
  169. }, errorHandler);
  170. }, errorHandler);
  171. function errorHandler(error){
  172. if(args.onError){
  173. args.onError.call(scope, error, args);
  174. }
  175. }
  176. args.abort = function(){
  177. // abort the request
  178. if(results.cancel){
  179. results.cancel();
  180. }
  181. };
  182. args.store = this;
  183. return args;
  184. },
  185. getFeatures: function(){
  186. // summary:
  187. // return the store feature set
  188. return {
  189. "dojo.data.api.Read": !!this.objectStore.get,
  190. "dojo.data.api.Identity": true,
  191. "dojo.data.api.Write": !!this.objectStore.put,
  192. "dojo.data.api.Notification": true
  193. };
  194. },
  195. getLabel: function(/* item */ item){
  196. // summary:
  197. // See dojo.data.api.Read.getLabel()
  198. if(this.isItem(item)){
  199. return this.getValue(item,this.labelProperty); //String
  200. }
  201. return undefined; //undefined
  202. },
  203. getLabelAttributes: function(/* item */ item){
  204. // summary:
  205. // See dojo.data.api.Read.getLabelAttributes()
  206. return [this.labelProperty]; //array
  207. },
  208. //Identity API Support
  209. getIdentity: function(item){
  210. return item.getId ? item.getId() : item[this.objectStore.idProperty || "id"];
  211. },
  212. getIdentityAttributes: function(item){
  213. // summary:
  214. // returns the attributes which are used to make up the
  215. // identity of an item. Basically returns this.objectStore.idProperty
  216. return [this.objectStore.idProperty];
  217. },
  218. fetchItemByIdentity: function(args){
  219. // summary:
  220. // fetch an item by its identity, by looking in our index of what we have loaded
  221. var item;
  222. dojo.when(this.objectStore.get(args.identity),
  223. function(result){
  224. item = result;
  225. args.onItem.call(args.scope, result);
  226. },
  227. function(error){
  228. args.onError.call(args.scope, error);
  229. }
  230. );
  231. return item;
  232. },
  233. newItem: function(data, parentInfo){
  234. // summary:
  235. // adds a new item to the store at the specified point.
  236. // Takes two parameters, data, and options.
  237. //
  238. // data: /* object */
  239. // The data to be added in as an item.
  240. if(parentInfo){
  241. // get the previous value or any empty array
  242. var values = this.getValue(parentInfo.parent,parentInfo.attribute,[]);
  243. // set the new value
  244. values = values.concat([data]);
  245. data.__parent = values;
  246. this.setValue(parentInfo.parent, parentInfo.attribute, values);
  247. }
  248. this._dirtyObjects.push({object:data, save: true});
  249. this.onNew(data);
  250. return data;
  251. },
  252. deleteItem: function(item){
  253. // summary:
  254. // deletes item and any references to that item from the store.
  255. //
  256. // item:
  257. // item to delete
  258. //
  259. // If the desire is to delete only one reference, unsetAttribute or
  260. // setValue is the way to go.
  261. this.changing(item, true);
  262. this.onDelete(item);
  263. },
  264. setValue: function(item, attribute, value){
  265. // summary:
  266. // sets 'attribute' on 'item' to 'value'
  267. var old = item[attribute];
  268. this.changing(item);
  269. item[attribute]=value;
  270. this.onSet(item,attribute,old,value);
  271. },
  272. setValues: function(item, attribute, values){
  273. // summary:
  274. // sets 'attribute' on 'item' to 'value' value
  275. // must be an array.
  276. if(!dojo.isArray(values)){
  277. throw new Error("setValues expects to be passed an Array object as its value");
  278. }
  279. this.setValue(item,attribute,values);
  280. },
  281. unsetAttribute: function(item, attribute){
  282. // summary:
  283. // unsets 'attribute' on 'item'
  284. this.changing(item);
  285. var old = item[attribute];
  286. delete item[attribute];
  287. this.onSet(item,attribute,old,undefined);
  288. },
  289. _dirtyObjects: [],
  290. changing: function(object,_deleting){
  291. // summary:
  292. // adds an object to the list of dirty objects. This object
  293. // contains a reference to the object itself as well as a
  294. // cloned and trimmed version of old object for use with
  295. // revert.
  296. object.__isDirty = true;
  297. //if an object is already in the list of dirty objects, don't add it again
  298. //or it will overwrite the premodification data set.
  299. for(var i=0; i<this._dirtyObjects.length; i++){
  300. var dirty = this._dirtyObjects[i];
  301. if(object==dirty.object){
  302. if(_deleting){
  303. // we are deleting, no object is an indicator of deletiong
  304. dirty.object = false;
  305. if(!this._saveNotNeeded){
  306. dirty.save = true;
  307. }
  308. }
  309. return;
  310. }
  311. }
  312. var old = object instanceof Array ? [] : {};
  313. for(i in object){
  314. if(object.hasOwnProperty(i)){
  315. old[i] = object[i];
  316. }
  317. }
  318. this._dirtyObjects.push({object: !_deleting && object, old: old, save: !this._saveNotNeeded});
  319. },
  320. save: function(kwArgs){
  321. // summary:
  322. // Saves the dirty data using object store provider. See dojo.data.api.Write for API.
  323. //
  324. // kwArgs.global:
  325. // This will cause the save to commit the dirty data for all
  326. // ObjectStores as a single transaction.
  327. //
  328. // kwArgs.revertOnError
  329. // This will cause the changes to be reverted if there is an
  330. // error on the save. By default a revert is executed unless
  331. // a value of false is provide for this parameter.
  332. kwArgs = kwArgs || {};
  333. var result, actions = [];
  334. var alreadyRecorded = {};
  335. var savingObjects = [];
  336. var self;
  337. var dirtyObjects = this._dirtyObjects;
  338. var left = dirtyObjects.length;// this is how many changes are remaining to be received from the server
  339. try{
  340. dojo.connect(kwArgs,"onError",function(){
  341. if(kwArgs.revertOnError !== false){
  342. var postCommitDirtyObjects = dirtyObjects;
  343. dirtyObjects = savingObjects;
  344. var numDirty = 0; // make sure this does't do anything if it is called again
  345. jr.revert(); // revert if there was an error
  346. self._dirtyObjects = postCommitDirtyObjects;
  347. }
  348. else{
  349. self._dirtyObjects = dirtyObject.concat(savingObjects);
  350. }
  351. });
  352. if(this.objectStore.transaction){
  353. var transaction = this.objectStore.transaction();
  354. }
  355. for(var i = 0; i < dirtyObjects.length; i++){
  356. var dirty = dirtyObjects[i];
  357. var object = dirty.object;
  358. var old = dirty.old;
  359. delete object.__isDirty;
  360. if(object){
  361. result = this.objectStore.put(object, {overwrite: !!old});
  362. }
  363. else{
  364. result = this.objectStore.remove(this.getIdentity(old));
  365. }
  366. savingObjects.push(dirty);
  367. dirtyObjects.splice(i--,1);
  368. dojo.when(result, function(value){
  369. if(!(--left)){
  370. if(kwArgs.onComplete){
  371. kwArgs.onComplete.call(kwArgs.scope, actions);
  372. }
  373. }
  374. },function(value){
  375. // on an error we want to revert, first we want to separate any changes that were made since the commit
  376. left = -1; // first make sure that success isn't called
  377. kwArgs.onError.call(kwArgs.scope, value);
  378. });
  379. }
  380. if(transaction){
  381. transaction.commit();
  382. }
  383. }catch(e){
  384. kwArgs.onError.call(kwArgs.scope, value);
  385. }
  386. },
  387. revert: function(kwArgs){
  388. // summary
  389. // returns any modified data to its original state prior to a save();
  390. //
  391. var dirtyObjects = this._dirtyObjects;
  392. for(var i = dirtyObjects.length; i > 0;){
  393. i--;
  394. var dirty = dirtyObjects[i];
  395. var object = dirty.object;
  396. var old = dirty.old;
  397. if(object && old){
  398. // changed
  399. for(var j in old){
  400. if(old.hasOwnProperty(j) && object[j] !== old[j]){
  401. this.onSet(object, j, object[j], old[j]);
  402. object[j] = old[j];
  403. }
  404. }
  405. for(j in object){
  406. if(!old.hasOwnProperty(j)){
  407. this.onSet(object, j, object[j]);
  408. delete object[j];
  409. }
  410. }
  411. }else if(!old){
  412. // was an addition, remove it
  413. this.onDelete(object);
  414. }else{
  415. // was a deletion, we will add it back
  416. this.onNew(old);
  417. }
  418. delete (object || old).__isDirty;
  419. dirtyObjects.splice(i, 1);
  420. }
  421. },
  422. isDirty: function(item){
  423. // summary
  424. // returns true if the item is marked as dirty or true if there are any dirty items
  425. if(!item){
  426. return !!this._dirtyObjects.length;
  427. }
  428. return item.__isDirty;
  429. },
  430. //Notifcation Support
  431. onSet: function(){},
  432. onNew: function(){},
  433. onDelete: function(){}
  434. }
  435. );
  436. }