source: Dev/branches/rest-dojo-ui/client/dojo/data/ObjectStore.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 14.1 KB
Line 
1define(["../_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
10return 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});
Note: See TracBrowser for help on using the repository browser.