source: Dev/trunk/src/client/dojox/data/ServiceStore.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.4 KB
Line 
1define(["dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array"], function(declare, lang, array) {
2return declare("dojox.data.ServiceStore",
3        // ClientFilter is intentionally not required, ServiceStore does not need it, and is more
4        // lightweight without it, but if it is provided, the ServiceStore will use it.
5        lang.getObject("dojox.data.ClientFilter", 0)||null,{
6                // summary:
7                //              note that dojox.rpc.Service is not required, you can create your own services
8                //              A ServiceStore is a readonly data store that provides a data.data interface to an RPC service.
9                //              |               var myServices = new dojox.rpc.Service(dojo.moduleUrl("dojox.rpc.tests.resources", "test.smd"));
10                //              |               var serviceStore = new dojox.data.ServiceStore({service:myServices.ServiceStore});
11                //
12                //              The ServiceStore also supports lazy loading. References can be made to objects that have not been loaded.
13                //              For example if a service returned:
14                //              |               {"name":"Example","lazyLoadedObject":{"$ref":"obj2"}}
15                //
16                //              And this object has accessed using the dojo.data API:
17                //              |               var obj = serviceStore.getValue(myObject,"lazyLoadedObject");
18                //              The object would automatically be requested from the server (with an object id of "obj2").
19       
20       
21                // service: Object
22                //      This is the service object that is used to retrieve lazy data and save results
23                //      The function should be directly callable with a single parameter of an object id to be loaded
24                service: null,
25               
26                constructor: function(options){
27                        // summary:
28                        //              ServiceStore constructor, instantiate a new ServiceStore
29                        //              A ServiceStore can be configured from a JSON Schema. Queries are just
30                        //              passed through to the underlying services
31                        // options:
32                        //              Keyword arguments
33                        //
34                        //              ####The *schema* parameter
35                        //
36                        //              This is a schema object for this store. This should be JSON Schema format.
37                        //
38                        //              ####The *service* parameter
39                        //
40                        //              This is the service object that is used to retrieve lazy data and save results
41                        //              The function should be directly callable with a single parameter of an object id to be loaded
42                        //
43                        //              ####The *idAttribute* parameter
44                        //
45                        //              Defaults to 'id'. The name of the attribute that holds an objects id.
46                        //              This can be a preexisting id provided by the server.
47                        //              If an ID isn't already provided when an object
48                        //              is fetched or added to the store, the autoIdentity system
49                        //              will generate an id for it and add it to the index.
50                        //
51                        //              ####The *estimateCountFactor* parameter
52                        //
53                        //              This parameter is used by the ServiceStore to estimate the total count. When
54                        //              paging is indicated in a fetch and the response includes the full number of items
55                        //              requested by the fetch's count parameter, then the total count will be estimated
56                        //              to be estimateCountFactor multiplied by the provided count. If this is 1, then it is assumed that the server
57                        //              does not support paging, and the response is the full set of items, where the
58                        //              total count is equal to the number of items returned. If the server does support
59                        //              paging, an estimateCountFactor of 2 is a good value for estimating the total count
60                        //              It is also possible to override _processResults if the server can provide an exact
61                        //              total count.
62                        //
63                        //              ####The *syncMode* parameter
64                        //
65                        //              Setting this to true will set the store to using synchronous calls by default.
66                        //              Sync calls return their data immediately from the calling function, so
67                        //              callbacks are unnecessary. This will only work with a synchronous capable service.
68                        //
69                        // description:
70                        //              ServiceStore can do client side caching and result set updating if
71                        //              dojox.data.ClientFilter is loaded. Do this add:
72                        //      |       dojo.require("dojox.data.ClientFilter")
73                        //              prior to loading the ServiceStore (ClientFilter must be loaded before ServiceStore).
74                        //              To utilize client side filtering with a subclass, you can break queries into
75                        //              client side and server side components by putting client side actions in
76                        //              clientFilter property in fetch calls. For example you could override fetch:
77                        //      |       fetch: function(args){
78                        //      |               // do the sorting and paging on the client side
79                        //      |               args.clientFilter = {start:args.start, count: args.count, sort: args.sort};
80                        //      |               // args.query will be passed to the service object for the server side handling
81                        //      |               return this.inherited(arguments);
82                        //      |       }
83                        //              When extending this class, if you would like to create lazy objects, you can follow
84                        //              the example from dojox.data.tests.stores.ServiceStore:
85                        // |    var lazyItem = {
86                        // |            _loadObject: function(callback){
87                        // |                    this.name="loaded";
88                        // |                    delete this._loadObject;
89                        // |                    callback(this);
90                        // |            }
91                        // |    };
92
93                        //setup a byId alias to the api call
94                        this.byId=this.fetchItemByIdentity;
95                        this._index = {};
96                        // if the advanced json parser is enabled, we can pass through object updates as onSet events
97                        if(options){
98                                lang.mixin(this,options);
99                        }
100                        // We supply a default idAttribute for parser driven construction, but if no id attribute
101                        //      is supplied, it should be null so that auto identification takes place properly
102                        this.idAttribute = (options && options.idAttribute) || (this.schema && this.schema._idAttr);
103                },
104               
105                // schema:
106                //              This is a schema object for this store. This should be JSON Schema format.
107                schema: null,
108               
109                // idAttribute: String
110                //              Defaults to 'id'. The name of the attribute that holds an objects id.
111                //              This can be a preexisting id provided by the server.
112                //              If an ID isn't already provided when an object
113                //              is fetched or added to the store, the autoIdentity system
114                //              will generate an id for it and add it to the index.
115
116                idAttribute: "id",
117                labelAttribute: "label",
118               
119                // syncMode: Boolean
120                //              Setting this to true will set the store to using synchronous calls by default.
121                //              Sync calls return their data immediately from the calling function, so
122                //              callbacks are unnecessary. This will only work with a synchronous capable service.
123                syncMode: false,
124               
125                // estimateCountFactor:
126                //              This parameter is used by the ServiceStore to estimate the total count. When
127                //              paging is indicated in a fetch and the response includes the full number of items
128                //              requested by the fetch's count parameter, then the total count will be estimated
129                //              to be estimateCountFactor multiplied by the provided count. If this is 1, then it is assumed that the server
130                //              does not support paging, and the response is the full set of items, where the
131                //              total count is equal to the numer of items returned. If the server does support
132                //              paging, an estimateCountFactor of 2 is a good value for estimating the total count
133                //              It is also possible to override _processResults if the server can provide an exact
134                //              total count.   
135                estimateCountFactor: 1,
136               
137                getSchema: function(){
138                        // summary:
139                        //              Returns a reference to the JSON Schema
140                        // returns: Object
141                        return this.schema;
142                },
143
144                loadLazyValues:true,
145
146                getValue: function(/*Object*/ item, /*String*/property, /*value?*/defaultValue){
147                        // summary:
148                        //              Gets the value of an item's 'property'
149                        // item:
150                        //              The item to get the value from
151                        // property:
152                        //              property to look up value for
153                        // defaultValue:
154                        //              the default value
155
156                        var value = item[property];
157                        return value || // return the plain value since it was found;
158                                                (property in item ? // a truthy value was not found, see if we actually have it
159                                                        value : // we do, so we can return it
160                                                        item._loadObject ? // property was not found, maybe because the item is not loaded, we will try to load it synchronously so we can get the property
161                                                                (dojox.rpc._sync = true) && arguments.callee.call(this,dojox.data.ServiceStore.prototype.loadItem({item:item}) || {}, property, defaultValue) : // load the item and run getValue again
162                                                                defaultValue);// not in item -> return default value
163                },
164                getValues: function(item, property){
165                        // summary:
166                        //              Gets the value of an item's 'property' and returns
167                        //              it.     If this value is an array it is just returned,
168                        //              if not, the value is added to an array and that is returned.
169                        // item: Object
170                        // property: String
171                        //              property to look up value for
172
173                        var val = this.getValue(item,property);
174                        return val instanceof Array ? val : val === undefined ? [] : [val];
175                },
176
177                getAttributes: function(item){
178                        // summary:
179                        //              Gets the available attributes of an item's 'property' and returns
180                        //              it as an array.
181                        // item: Object
182
183                        var res = [];
184                        for(var i in item){
185                                if(item.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){
186                                        res.push(i);
187                                }
188                        }
189                        return res;
190                },
191
192                hasAttribute: function(item,attribute){
193                        // summary:
194                        //              Checks to see if item has attribute
195                        // item: Object
196                        // attribute: String
197                        return attribute in item;
198                },
199
200                containsValue: function(item, attribute, value){
201                        // summary:
202                        //              Checks to see if 'item' has 'value' at 'attribute'
203                        // item: Object
204                        // attribute: String
205                        // value: Anything
206                        return array.indexOf(this.getValues(item,attribute),value) > -1;
207                },
208
209
210                isItem: function(item){
211                        // summary:
212                        //              Checks to see if the argument is an item
213                        // item: Object
214
215                        // we have no way of determining if it belongs, we just have object returned from
216                        // service queries
217                        return (typeof item == 'object') && item && !(item instanceof Date);
218                },
219
220                isItemLoaded: function(/* object */ item){
221                        // summary:
222                        //              Checks to see if the item is loaded.
223
224                        return item && !item._loadObject;
225                },
226
227                loadItem: function(args){
228                        // summary:
229                        //              Loads an item and calls the callback handler. Note, that this will call the callback
230                        //              handler even if the item is loaded. Consequently, you can use loadItem to ensure
231                        //              that an item is loaded is situations when the item may or may not be loaded yet.
232                        //              If you access a value directly through property access, you can use this to load
233                        //              a lazy value as well (doesn't need to be an item).
234                        // example:
235                        //      |       store.loadItem({
236                        //      |               item: item, // this item may or may not be loaded
237                        //      |               onItem: function(item){
238                        //      |                       // do something with the item
239                        //      |               }
240                        //      |       });
241
242                        var item;
243                        if(args.item._loadObject){
244                                args.item._loadObject(function(result){
245                                        item = result; // in synchronous mode this can allow loadItem to return the value
246                                        delete item._loadObject;
247                                        var func = result instanceof Error ? args.onError : args.onItem;
248                                        if(func){
249                                                func.call(args.scope, result);
250                                        }
251                                });
252                        }else if(args.onItem){
253                                // even if it is already loaded, we will use call the callback, this makes it easier to
254                                // use when it is not known if the item is loaded (you can always safely call loadItem).
255                                args.onItem.call(args.scope, args.item);
256                        }
257                        return item;
258                },
259                _currentId : 0,
260                _processResults : function(results, deferred){
261                        // this should return an object with the items as an array and the total count of
262                        // items (maybe more than currently in the result set).
263                        // for example:
264                        //      | {totalCount:10, items: [{id:1},{id:2}]}
265
266                        // index the results, assigning ids as necessary
267
268                        if(results && typeof results == 'object'){
269                                var id = results.__id;
270                                if(!id){// if it hasn't been assigned yet
271                                        if(this.idAttribute){
272                                                // use the defined id if available
273                                                id = results[this.idAttribute];
274                                        }else{
275                                                id = this._currentId++;
276                                        }
277                                        if(id !== undefined){
278                                                var existingObj = this._index[id];
279                                                if(existingObj){
280                                                        for(var j in existingObj){
281                                                                delete existingObj[j]; // clear it so we can mixin
282                                                        }
283                                                        results = lang.mixin(existingObj,results);
284                                                }
285                                                results.__id = id;
286                                                this._index[id] = results;
287                                        }
288                                }
289                                for(var i in results){
290                                        results[i] = this._processResults(results[i], deferred).items;
291                                }
292                                var count = results.length;
293                        }
294                        return {totalCount: deferred.request.count == count ? (deferred.request.start || 0) + count * this.estimateCountFactor : count, items: results};
295                },
296                close: function(request){
297                        return request && request.abort && request.abort();
298                },
299                fetch: function(args){
300                        // summary:
301                        //              See dojo/data/api/Read.fetch
302                        // args:
303                        //              ####The *queryOptions.cache* parameter
304                        //
305                        //              If true, indicates that the query result should be cached for future use. This is only available
306                        //              if dojox.data.ClientFilter has been loaded before the ServiceStore
307                        //
308                        //              ####The *syncMode* parameter
309                        //
310                        //              Indicates that the call should be fetch synchronously if possible (this is not always possible)
311                        //
312                        //              ####The *clientFetch* parameter
313                        //
314                        //              This is a fetch keyword argument for explicitly doing client side filtering, querying, and paging
315
316                        args = args || {};
317
318                        if("syncMode" in args ? args.syncMode : this.syncMode){
319                                dojox.rpc._sync = true;
320                        }
321                        var self = this;
322
323                        var scope = args.scope || self;
324                        var defResult = this.cachingFetch ? this.cachingFetch(args) : this._doQuery(args);
325                        defResult.request = args;
326                        defResult.addCallback(function(results){
327                                if(args.clientFetch){
328                                        results = self.clientSideFetch({query:args.clientFetch,sort:args.sort,start:args.start,count:args.count},results);
329                                }
330                                var resultSet = self._processResults(results, defResult);
331                                results = args.results = resultSet.items;
332                                if(args.onBegin){
333                                        args.onBegin.call(scope, resultSet.totalCount, args);
334                                }
335                                if(args.onItem){
336                                        for(var i=0; i<results.length;i++){
337                                                args.onItem.call(scope, results[i], args);
338                                        }
339                                }
340                                if(args.onComplete){
341                                        args.onComplete.call(scope, args.onItem ? null : results, args);
342                                }
343                                return results;
344                        });
345                        defResult.addErrback(args.onError && function(err){
346                                return args.onError.call(scope, err, args);
347                        });
348                        args.abort = function(){
349                                // abort the request
350                                defResult.cancel();
351                        };
352                        args.store = this;
353                        return args;
354                },
355                _doQuery: function(args){
356                        var query= typeof args.queryStr == 'string' ? args.queryStr : args.query;
357                        return this.service(query);
358                },
359                getFeatures: function(){
360                        // summary:
361                        //              return the store feature set
362
363                        return {
364                                "dojo.data.api.Read": true,
365                                "dojo.data.api.Identity": true,
366                                "dojo.data.api.Schema": this.schema
367                        };
368                },
369
370                getLabel: function(item){
371                        // summary:
372                        //              returns the label for an item. Just gets the "label" attribute.
373
374                        return this.getValue(item,this.labelAttribute);
375                },
376
377                getLabelAttributes: function(item){
378                        // summary:
379                        //              returns an array of attributes that are used to create the label of an item
380                        return [this.labelAttribute];
381                },
382
383                //Identity API Support
384
385
386                getIdentity: function(item){
387                        return item.__id;
388                },
389
390                getIdentityAttributes: function(item){
391                        // summary:
392                        //              returns the attributes which are used to make up the
393                        //              identity of an item.    Basically returns this.idAttribute
394
395                        return [this.idAttribute];
396                },
397
398                fetchItemByIdentity: function(args){
399                        // summary:
400                        //              fetch an item by its identity, by looking in our index of what we have loaded
401                        var item = this._index[(args._prefix || '') + args.identity];
402                        if(item){
403                                // the item exists in the index
404                                if(item._loadObject){
405                                        // we have a handle on the item, but it isn't loaded yet, so we need to load it
406                                        args.item = item;
407                                        return this.loadItem(args);
408                                }else if(args.onItem){
409                                        // it's already loaded, so we can immediately callback
410                                        args.onItem.call(args.scope, item);
411                                }
412                        }else{
413                                // convert the different spellings
414                                return this.fetch({
415                                                query: args.identity,
416                                                onComplete: args.onItem,
417                                                onError: args.onError,
418                                                scope: args.scope
419                                        }).results;
420                        }
421                        return item;
422                }
423
424        }
425);
426});
Note: See TracBrowser for help on using the repository browser.