[483] | 1 | define(["dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array"], function(declare, lang, array) { |
---|
| 2 | return 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 | }); |
---|