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