]> git.wh0rd.org - 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 });