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