//>>built define("dojox/data/JsonRestStore", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/connect", "dojox/rpc/Rest", "dojox/rpc/JsonRest", "dojox/json/schema", "dojox/data/ServiceStore"], function(lang, declare, connect, rpcRest, rpcJsonRest, jsonSchema, ServiceStore) { /*===== var ServiceStore = dojox.data.ServiceStore; =====*/ var JsonRestStore = declare("dojox.data.JsonRestStore", ServiceStore, { constructor: function(options){ //summary: // JsonRestStore is a Dojo Data store interface to JSON HTTP/REST web // storage services that support read and write through GET, PUT, POST, and DELETE. // options: // Keyword arguments // // The *schema* parameter // This is a schema object for this store. This should be JSON Schema format. // // The *service* parameter // 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 // Note that it is critical that the service parses responses as JSON. // If you are using dojox.rpc.Service, the easiest way to make sure this // happens is to make the responses have a content type of // application/json. If you are creating your own service, make sure you // use handleAs: "json" with your XHR requests. // // The *target* parameter // This is the target URL for this Service store. This may be used in place // of a service parameter to connect directly to RESTful URL without // using a dojox.rpc.Service object. // // The *idAttribute* parameter // 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. // // The *syncMode* parameter // Setting this to true will set the store to using synchronous calls by default. // Sync calls return their data immediately from the calling function, so // callbacks are unnecessary // // description: // The JsonRestStore will cause all saved modifications to be sent to the server using Rest commands (PUT, POST, or DELETE). // When using a Rest store on a public network, it is important to implement proper security measures to // control access to resources. // On the server side implementing a REST interface means providing GET, PUT, POST, and DELETE handlers. // GET - Retrieve an object or array/result set, this can be by id (like /table/1) or with a // query (like /table/?name=foo). // PUT - This should modify a object, the URL will correspond to the id (like /table/1), and the body will // provide the modified object // POST - This should create a new object. The URL will correspond to the target store (like /table/) // and the body should be the properties of the new object. The server's response should include a // Location header that indicates the id of the newly created object. This id will be used for subsequent // PUT and DELETE requests. JsonRestStore also includes a Content-Location header that indicates // the temporary randomly generated id used by client, and this location is used for subsequent // PUT/DELETEs if no Location header is provided by the server or if a modification is sent prior // to receiving a response from the server. // DELETE - This should delete an object by id. // These articles include more detailed information on using the JsonRestStore: // http://www.sitepen.com/blog/2008/06/13/restful-json-dojo-data/ // http://blog.medryx.org/2008/07/24/jsonreststore-overview/ // // example: // A JsonRestStore takes a REST service or a URL and uses it the remote communication for a // read/write dojo.data implementation. A JsonRestStore can be created with a simple URL like: // | new JsonRestStore({target:"/MyData/"}); // example: // To use a JsonRestStore with a service, 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}); // example: // 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"). // connect.connect(rpcRest._index,"onUpdate",this,function(obj,attrName,oldValue,newValue){ var prefix = this.service.servicePath; 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); } }); this.idAttribute = this.idAttribute || 'id';// no options about it, we have to have identity if(typeof options.target == 'string'){ options.target = options.target.match(/\/$/) || this.allowNoTrailingSlash ? options.target : (options.target + '/'); if(!this.service){ this.service = rpcJsonRest.services[options.target] || rpcRest(options.target, true); // create a default Rest service } } rpcJsonRest.registerService(this.service, options.target, this.schema); this.schema = this.service._schema = this.schema || this.service._schema || {}; // wrap the service with so it goes through JsonRest manager this.service._store = this; this.service.idAsRef = this.idAsRef; this.schema._idAttr = this.idAttribute; var constructor = rpcJsonRest.getConstructor(this.service); var self = this; this._constructor = function(data){ constructor.call(this, data); self.onNew(this); } this._constructor.prototype = constructor.prototype; this._index = rpcRest._index; }, // summary: // Will load any schemas referenced content-type header or in Link headers loadReferencedSchema: true, // summary: // Treat objects in queries as partially loaded objects idAsRef: false, referenceIntegrity: true, target:"", // summary: // Allow no trailing slash on target paths. This is generally discouraged since // it creates prevents simple scalar values from being used a relative URLs. // Disabled by default. allowNoTrailingSlash: false, //Write API Support newItem: function(data, parentInfo){ // summary: // adds a new item to the store at the specified point. // Takes two parameters, data, and options. // // data: /* object */ // The data to be added in as an item. data = new this._constructor(data); if(parentInfo){ // get the previous value or any empty array var values = this.getValue(parentInfo.parent,parentInfo.attribute,[]); // set the new value values = values.concat([data]); data.__parent = values; this.setValue(parentInfo.parent, parentInfo.attribute, values); } return data; }, deleteItem: function(item){ // summary: // deletes item and any references to that item from the store. // // item: // item to delete // // If the desire is to delete only one reference, unsetAttribute or // setValue is the way to go. var checked = []; var store = dataExtCfg._getStoreForItem(item) || this; if(this.referenceIntegrity){ // cleanup all references rpcJsonRest._saveNotNeeded = true; var index = rpcRest._index; var fixReferences = function(parent){ var toSplice; // keep track of the checked ones checked.push(parent); // mark it checked so we don't run into circular loops when encountering cycles parent.__checked = 1; for(var i in parent){ if(i.substring(0,2) != "__"){ var value = parent[i]; if(value == item){ if(parent != index){ // make sure we are just operating on real objects if(parent instanceof Array){ // mark it as needing to be spliced, don't do it now or it will mess up the index into the array (toSplice = toSplice || []).push(i); }else{ // property, just delete it. (dataExtCfg._getStoreForItem(parent) || store).unsetAttribute(parent, i); } } }else{ if((typeof value == 'object') && value){ if(!value.__checked){ // recursively search fixReferences(value); } if(typeof value.__checked == 'object' && parent != index){ // if it is a modified array, we will replace it (dataExtCfg._getStoreForItem(parent) || store).setValue(parent, i, value.__checked); } } } } } if(toSplice){ // we need to splice the deleted item out of these arrays i = toSplice.length; parent = parent.__checked = parent.concat(); // indicates that the array is modified while(i--){ parent.splice(toSplice[i], 1); } return parent; } return null; }; // start with the index fixReferences(index); rpcJsonRest._saveNotNeeded = false; var i = 0; while(checked[i]){ // remove the checked marker delete checked[i++].__checked; } } rpcJsonRest.deleteObject(item); store.onDelete(item); }, changing: function(item,_deleting){ // summary: // adds an item to the list of dirty items. This item // contains a reference to the item itself as well as a // cloned and trimmed version of old item for use with // revert. rpcJsonRest.changing(item,_deleting); }, cancelChanging : function(object){ // summary: // Removes an object from the list of dirty objects // This will prevent that object from being saved to the server on the next save // object: // The item to cancel changes on if(!object.__id){ return; } dirtyObjects = dirty=rpcJsonRest.getDirtyObjects(); for(var i=0; i]*)>;\s*rel="?definedby"?/); } schemaRef = schemaRef && schemaRef[1]; if(schemaRef){ var serviceAndId = rpcJsonRest.getServiceAndId((self.target + schemaRef).replace(/^(.*\/)?(\w+:\/\/)|[^\/\.]+\/\.\.\/|^.*\/(\/)/,"$2$3")); var schemaDeferred = rpcJsonRest.byId(serviceAndId.service, serviceAndId.id); schemaDeferred.addCallbacks(function(newSchema){ lang.mixin(self.schema, newSchema); return result; }, function(error){ console.error(error); // log it, but don't let it cause the main request to fail return result; }); return schemaDeferred; } return undefined;//don't change anything, and deal with the stupid post-commit lint complaints }); } return deferred; }, _processResults: function(results, deferred){ // index the results var count = results.length; // if we don't know the length, and it is partial result, we will guess that it is twice as big, that will work for most widgets return {totalCount:deferred.fullLength || (deferred.request.count == count ? (deferred.request.start || 0) + count * 2 : count), items: results}; }, getConstructor: function(){ // summary: // Gets the constructor for objects from this store return this._constructor; }, getIdentity: function(item){ var id = item.__clientId || item.__id; if(!id){ return id; } var prefix = this.service.servicePath.replace(/[^\/]*$/,''); // support for relative or absolute referencing with ids return id.substring(0,prefix.length) != prefix ? id : id.substring(prefix.length); // String }, fetchItemByIdentity: function(args){ var id = args.identity; var store = this; // if it is an absolute id, we want to find the right store to query if(id.toString().match(/^(\w*:)?\//)){ var serviceAndId = rpcJsonRest.getServiceAndId(id); store = serviceAndId.service._store; args.identity = serviceAndId.id; } args._prefix = store.service.servicePath.replace(/[^\/]*$/,''); return store.inherited(arguments); }, //Notifcation Support onSet: function(){}, onNew: function(){}, onDelete: function(){}, getFeatures: function(){ // summary: // return the store feature set var features = this.inherited(arguments); features["dojo.data.api.Write"] = true; features["dojo.data.api.Notification"] = true; return features; }, getParent: function(item){ // summary: // Returns the parent item (or query) for the given item // item: // The item to find the parent of return item && item.__parent; } } ); JsonRestStore.getStore = function(options, Class){ // summary: // Will retrieve or create a store using the given options (the same options // that are passed to JsonRestStore constructor. Returns a JsonRestStore instance // options: // See the JsonRestStore constructor // Class: // Constructor to use (for creating stores from JsonRestStore subclasses). // This is optional and defaults to JsonRestStore. if(typeof options.target == 'string'){ options.target = options.target.match(/\/$/) || options.allowNoTrailingSlash ? options.target : (options.target + '/'); var store = (rpcJsonRest.services[options.target] || {})._store; if(store){ return store; } } return new (Class || JsonRestStore)(options); }; var dataExtCfg = lang.getObject("dojox.data",true); dataExtCfg._getStoreForItem = function(item){ if(item.__id){ var serviceAndId = rpcJsonRest.getServiceAndId(item.__id); if(serviceAndId && serviceAndId.service._store){ return serviceAndId.service._store; }else{ var servicePath = item.__id.toString().match(/.*\//)[0]; return new JsonRestStore({target:servicePath}); } } return null; }; var jsonRefConfig = lang.getObject("dojox.json.ref", true); jsonRefConfig._useRefs = true; // Use referencing when identifiable objects are referenced return JsonRestStore; });