if(!dojo._hasResource["dojox.data.JsonRestStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dojox.data.JsonRestStore"] = true; dojo.provide("dojox.data.JsonRestStore"); dojo.require("dojox.rpc.Rest"); dojo.require("dojox.rpc.JsonReferencing"); // TODO: Make it work without this dependency // A JsonRestStore takes a REST service and uses it the remote communication for a // read/write dojo.data implementation. To use a JsonRestStore you should create a // service with a REST transport. This can be configured with an SMD: //{ // services: { // jsonRestStore: { // transport: "REST", // envelope: "URL", // target: "store.php", // contentType:"application/json", // parameters: [ // {name: "location", type: "string", optional: true} // ] // } // } //} // The SMD can then be used to create service, and the service can be passed to a JsonRestStore. For example: // var myServices = new dojox.rpc.Service(dojo.moduleUrl("dojox.rpc.tests.resources", "test.smd")); // var jsonStore = new dojox.data.JsonRestStore({service:myServices.jsonRestStore}); // // The JsonRestStore will then cause all saved modifications to be server using Rest commands (PUT, POST, or DELETE). // The JsonRestStore also supports lazy loading. References can be made to objects that have not been loaded. // For example if a service returned: // {"name":"Example","lazyLoadedObject":{"$ref":"obj2"}} // // And this object has accessed using the dojo.data API: // var obj = jsonStore.getValue(myObject,"lazyLoadedObject"); // The object would automatically be requested from the server (with an object id of "obj2"). // // When using a Rest store on a public network, it is important to implement proper security measures to // control access to resources dojox.data.ASYNC_MODE = 0; dojox.data.SYNC_MODE = 1; dojo.declare("dojox.data.JsonRestStore", null, { mode: dojox.data.ASYNC_MODE, constructor: function(options){ //summary: // JsonRestStore constructor, instantiate a new JsonRestStore // A JsonRestStore can be configured from a JSON Schema. Queries are just // passed through as URLs for XHR requests, // so there is nothing to configure, just plug n play. // Of course there are some options to fiddle with if you want: // // jsonSchema: /* object */ // // service: /* function */ // This is the service object that is used to retrieve lazy data and save results // The function should be directly callable with a single parameter of an object id to be loaded // The function should also have the following methods: // put(id,value) - puts the value at the given id // post(id,value) - posts (appends) the value at the given id // delete(id) - deletes the value corresponding to the given id // // idAttribute: /* string */ // Defaults to 'id'. The name of the attribute that holds an objects id. // This can be a preexisting id provided by the server. // If an ID isn't already provided when an object // is fetched or added to the store, the autoIdentity system // will generate an id for it and add it to the index. // mode: dojox.data.ASYNC_MODE || dojox.data.SYNC_MODE // Defaults to ASYNC_MODE. This option sets the default mode for this store. // Sync calls return their data immediately from the calling function // instead of calling the callback functions. Functions such as // fetchItemByIdentity() and fetch() both accept a string parameter in addtion // to the normal keywordArgs parameter. When passed this option, SYNC_MODE will // automatically be used even when the default mode of the system is ASYNC_MODE. // A normal request to fetch or fetchItemByIdentity (with kwArgs object) can also // include a mode property to override this setting for that one request. //setup a byId alias to the api call this.byId=this.fetchItemByIdentity; // if the advanced json parser is enabled, we can pass through object updates as onSet events dojo.connect(dojox.rpc,"onUpdate",this,function(obj,attrName,oldValue,newValue){ var prefix = this.service.serviceName + '/'; if (!obj._id){ console.log("no id on updated object ", obj); } else if (obj._id.substring(0,prefix.length) == prefix) this.onSet(obj,attrName,oldValue,newValue); }); if (options){ dojo.mixin(this,options); } if (!this.service) throw Error("A service is required for JsonRestStore"); if (!(this.service.contentType + '').match(/application\/json/)) throw Error("A service must use a contentType of 'application/json' in order to be used in a JsonRestStore"); this.idAttribute = (this.service._schema && this.service._schema._idAttr) || 'id'; var arrayModifyingMethodNames = ["splice","push","pop","unshift","shift","reverse","sort"]; this._arrayModifyingMethods = {}; var array = []; var _this = this; // setup array augmentation, for catching mods and setting arrays as dirty for (var i = 0; i < arrayModifyingMethodNames.length; i++){ (function(key){ // closure for the method to be bound correctly var method = array[key]; _this._arrayModifyingMethods[key] = function(){ _this._setDirty(this); // set the array as dirty before the native modifying operation return method.apply(this,arguments); } _this._arrayModifyingMethods[key]._augmented = 1; })(arrayModifyingMethodNames[i]); } this._deletedItems=[]; this._dirtyItems=[]; //given a url, load json data from as the store }, _loadById: function(id,callback){ var slashIndex = id.indexOf('/'); var serviceName = id.substring(0,slashIndex); var id = id.substring(slashIndex + 1); (this.service.serviceName == serviceName ? this.service : // use the current service if it is the right one dojox.rpc.services[serviceName])(id) // otherwise call by looking up the service .addCallback(callback); }, getValue: function(item, property,lazyCallback){ // summary: // Gets the value of an item's 'property' // // item: /* object */ // property: /* string */ // property to look up value for // lazyCallback: /* function*/ // not part of the API, but if you are using lazy loading properties, you may provide a callback to resume, in order to have asynchronous loading var value = item[property]; if (value && value.$ref){ dojox.rpc._sync = !lazyCallback; // tell the service to operate synchronously (I have some concerns about the "thread" safety with FF3, as I think it does event stacking on sync calls) this._loadById((value && value._id) || (item._id + '.' + property),lazyCallback); delete dojox.rpc._sync; // revert to normal async behavior } else if (lazyCallback){lazyCallback(value);} return value; }, getValues: function(item, property){ // summary: // Gets the value of an item's 'property' and returns // it. If this value is an array it is just returned, // if not, the value is added to an array and that is returned. // // item: /* object */ // property: /* string */ // property to look up value for var val = this.getValue(item,property); return dojo.isArray(val) ? val : [val]; }, getAttributes: function(item){ // summary: // Gets the available attributes of an item's 'property' and returns // it as an array. // // item: /* object */ var res = []; for (var i in item){ res.push(i); } return res; }, hasAttribute: function(item,attribute){ // summary: // Checks to see if item has attribute // // item: /* object */ // attribute: /* string */ return attribute in item; }, containsValue: function(item, attribute, value){ // summary: // Checks to see if 'item' has 'value' at 'attribute' // // item: /* object */ // attribute: /* string */ // value: /* anything */ return getValue(item,attribute)==value; }, isItem: function(item){ // summary: // Checks to see if a passed 'item' // is really a JsonRestStore item. // // item: /* object */ // attribute: /* string */ return !!(dojo.isObject(item) && item._id); }, isItemLoaded: function(item){ // summary: // returns isItem() :) // // item: /* object */ return !item.$ref; }, loadItem: function(item){ // summary: // Loads an item that has not been loaded yet. Lazy loading should happen through getValue, and if used properly, this should never need to be called // returns true. Note this does not work with lazy loaded primitives! if (item.$ref){ dojox.rpc._sync = true; // tell the service to operate synchronously this._loadById(item._id) delete dojox.rpc._sync; // revert to normal async behavior } return true; }, _walk : function(value,forEach){ // walk the graph, avoiding duplication var walked=[]; function walk(value){ if (value && typeof value == 'object' && !value.__walked){ value.__walked = true; walked.push(value); for (var i in value){ if (walk(value[i])){ forEach(value,i,value[i]); } } return true; } } walk(value); forEach({},null,value); for (var i = 0; i < walked.length;i++) delete walked[i].__walked; }, fetch: function(args){ //console.log("fetch() ", args); // summary // // fetch takes either a string argument or a keywordArgs // object containing the parameters for the search. // If passed a string, fetch will interpret this string // as the query to be performed and will do so in // SYNC_MODE returning the results immediately. // If an object is supplied as 'args', its options will be // parsed and then contained query executed. // // query: /* string or object */ // Defaults to "". This is basically passed to the XHR request as the URL to get the data // // start: /* int */ // Starting item in result set // // count: /* int */ // Maximum number of items to return // // cache: /* boolean */ // // sort: /* function */ // Not Implemented yet // // The following only apply to ASYNC requests (the default) // // onBegin: /* function */ // called before any results are returned. Parameters // will be the count and the original fetch request // // onItem: /*function*/ // called for each returned item. Parameters will be // the item and the fetch request // // onComplete: /* function */ // called on completion of the request. Parameters will // be the complete result set and the request // // onError: /* function */ // colled in the event of an error if(dojo.isString(args)){ query = args; args={query: query, mode: dojox.data.SYNC_MODE}; } var query; if (!args || !args.query){ if (!args){ var args={}; } if (!args.query){ args.query=""; query=args.query; } } if (dojo.isObject(args.query)){ if (args.query.query){ query = args.query.query; }else{ query = args.query = ""; } if (args.query.queryOptions){ args.queryOptions=args.query.queryOptions } }else{ query=args.query; } if (args.start || args.count){ query += '[' + (args.start ? args.start : '') + ':' + (args.count ? ((args.start || 0) + args.count) : '') + ']'; } var results = dojox.rpc._index[this.service.serviceName + '/' + query]; if (!args.mode){args.mode = this.mode;} var _this = this; var defResult; dojox.rpc._sync = this.mode; dojox._newId = query; if (results && !("cache" in args && !args.cache)){ // TODO: Add TTL maybe? defResult = new dojo.Deferred; defResult.callback(results); } else { defResult = this.service(query); } defResult.addCallback(function(results){ delete dojox._newId; // cleanup if (args.onBegin){ args["onBegin"].call(_this, results.length, args); } _this._walk(results,function(obj,i,value){ if (value instanceof Array){ for (var i in _this._arrayModifyingMethods){ if (!value[i]._augmented){ value[i] = _this._arrayModifyingMethods[i]; } } } }); if (args.onItem){ for (var i=0; i 0){ var dirty = this._dirtyItems.pop(); var item = dirty.item; var append = false; left++; var deferred; if (item instanceof Array && dirty.old instanceof Array){ // see if we can just append the item with a post append = true; for (var i = 0, l = dirty.old.length; i < l; i++){ if (item[i] != dirty.old[i]){ append = false; } } if (append){ // if we can, we will do posts to add from here for (;i 0){ left++; this.service['delete'](this.getIdentity(this._deletedItems.pop())).addCallback(finishOne); } }, revert: function(){ // summary // returns any modified data to its original state prior to a save(); while (this._dirtyItems.length>0){ var i; var d = this._dirtyItems.pop(); for (i in d.old){ d.item[i] = d.old[i]; } for (i in d.item){ if (!d.old.hasOwnProperty(i)) delete d.item[i] } } this.onRevert(); }, isDirty: function(item){ // summary // returns true if the item is marked as dirty. for (var i=0, l=this._dirtyItems.length; i