source: Dev/trunk/src/client/dojo/data/ObjectStore.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 15.5 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
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});
Note: See TracBrowser for help on using the repository browser.