diff options
| author | Tristan Zur <tzur@web.web.ccwn.org> | 2014-03-27 22:27:47 +0100 |
|---|---|---|
| committer | Tristan Zur <tzur@web.web.ccwn.org> | 2014-03-27 22:27:47 +0100 |
| commit | b62676ca5d3d6f6ba3f019ea3f99722e165a98d8 (patch) | |
| tree | 86722cb80f07d4569f90088eeaea2fc2f6e2ef94 /js/dojo-1.7.2/dojox/rpc/JsonRest.js | |
Diffstat (limited to 'js/dojo-1.7.2/dojox/rpc/JsonRest.js')
| -rw-r--r-- | js/dojo-1.7.2/dojox/rpc/JsonRest.js | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/js/dojo-1.7.2/dojox/rpc/JsonRest.js b/js/dojo-1.7.2/dojox/rpc/JsonRest.js new file mode 100644 index 0000000..f6bae55 --- /dev/null +++ b/js/dojo-1.7.2/dojox/rpc/JsonRest.js @@ -0,0 +1,458 @@ +//>>built +define("dojox/rpc/JsonRest", ["dojo", "dojox", "dojox/json/ref", "dojox/rpc/Rest"], function(dojo, dojox) { + var dirtyObjects = []; + var Rest = dojox.rpc.Rest; + var jr; + function resolveJson(service, deferred, value, defaultId){ + var timeStamp = deferred.ioArgs && deferred.ioArgs.xhr && deferred.ioArgs.xhr.getResponseHeader("Last-Modified"); + if(timeStamp && Rest._timeStamps){ + Rest._timeStamps[defaultId] = timeStamp; + } + var hrefProperty = service._schema && service._schema.hrefProperty; + if(hrefProperty){ + dojox.json.ref.refAttribute = hrefProperty; + } + value = value && dojox.json.ref.resolveJson(value, { + defaultId: defaultId, + index: Rest._index, + timeStamps: timeStamp && Rest._timeStamps, + time: timeStamp, + idPrefix: service.servicePath.replace(/[^\/]*$/,''), + idAttribute: jr.getIdAttribute(service), + schemas: jr.schemas, + loader: jr._loader, + idAsRef: service.idAsRef, + assignAbsoluteIds: true + }); + dojox.json.ref.refAttribute = "$ref"; + return value; + } + jr = dojox.rpc.JsonRest={ + serviceClass: dojox.rpc.Rest, + conflictDateHeader: "If-Unmodified-Since", + commit: function(kwArgs){ + // summary: + // Saves the dirty data using REST Ajax methods + + kwArgs = kwArgs || {}; + var actions = []; + var alreadyRecorded = {}; + var savingObjects = []; + for(var i = 0; i < dirtyObjects.length; i++){ + var dirty = dirtyObjects[i]; + var object = dirty.object; + var old = dirty.old; + var append = false; + if(!(kwArgs.service && (object || old) && + (object || old).__id.indexOf(kwArgs.service.servicePath)) && dirty.save){ + delete object.__isDirty; + if(object){ + if(old){ + // changed object + var pathParts; + if((pathParts = object.__id.match(/(.*)#.*/))){ // it is a path reference + // this means it is a sub object, we must go to the parent object and save it + object = Rest._index[pathParts[1]]; + } + if(!(object.__id in alreadyRecorded)){// if it has already been saved, we don't want to repeat it + // record that we are saving + alreadyRecorded[object.__id] = object; + if(kwArgs.incrementalUpdates + && !pathParts){ // I haven't figured out how we would do incremental updates on sub-objects yet + // make an incremental update using a POST + var incremental = (typeof kwArgs.incrementalUpdates == 'function' ? + kwArgs.incrementalUpdates : function(){ + incremental = {}; + for(var j in object){ + if(object.hasOwnProperty(j)){ + if(object[j] !== old[j]){ + incremental[j] = object[j]; + } + }else if(old.hasOwnProperty(j)){ + // we can't use incremental updates to remove properties + return null; + } + } + return incremental; + })(object, old); + } + + if(incremental){ + actions.push({method:"post",target:object, content: incremental}); + } + else{ + actions.push({method:"put",target:object,content:object}); + } + } + }else{ + // new object + var service = jr.getServiceAndId(object.__id).service; + var idAttribute = jr.getIdAttribute(service); + if((idAttribute in object) && !kwArgs.alwaysPostNewItems){ + // if the id attribute is specified, then we should know the location + actions.push({method:"put",target:object, content:object}); + }else{ + actions.push({method:"post",target:{__id:service.servicePath}, + content:object}); + } + } + }else if(old){ + // deleted object + actions.push({method:"delete",target:old}); + }//else{ this would happen if an object is created and then deleted, don't do anything + savingObjects.push(dirty); + dirtyObjects.splice(i--,1); + } + } + dojo.connect(kwArgs,"onError",function(){ + if(kwArgs.revertOnError !== false){ + var postCommitDirtyObjects = dirtyObjects; + dirtyObjects = savingObjects; + var numDirty = 0; // make sure this does't do anything if it is called again + jr.revert(); // revert if there was an error + dirtyObjects = postCommitDirtyObjects; + } + else{ + dirtyObjects = dirtyObject.concat(savingObjects); + } + }); + jr.sendToServer(actions, kwArgs); + return actions; + }, + sendToServer: function(actions, kwArgs){ + var xhrSendId; + var plainXhr = dojo.xhr; + var left = actions.length;// this is how many changes are remaining to be received from the server + var i, contentLocation; + var timeStamp; + var conflictDateHeader = this.conflictDateHeader; + // add headers for extra information + dojo.xhr = function(method,args){ + // keep the transaction open as we send requests + args.headers = args.headers || {}; + // the last one should commit the transaction + args.headers['Transaction'] = actions.length - 1 == i ? "commit" : "open"; + if(conflictDateHeader && timeStamp){ + args.headers[conflictDateHeader] = timeStamp; + } + if(contentLocation){ + args.headers['Content-ID'] = '<' + contentLocation + '>'; + } + return plainXhr.apply(dojo,arguments); + }; + for(i =0; i < actions.length;i++){ // iterate through the actions to execute + var action = actions[i]; + dojox.rpc.JsonRest._contentId = action.content && action.content.__id; // this is used by OfflineRest + var isPost = action.method == 'post'; + timeStamp = action.method == 'put' && Rest._timeStamps[action.content.__id]; + if(timeStamp){ + // update it now + Rest._timeStamps[action.content.__id] = (new Date()) + ''; + } + // send the content location to the server + contentLocation = isPost && dojox.rpc.JsonRest._contentId; + var serviceAndId = jr.getServiceAndId(action.target.__id); + var service = serviceAndId.service; + var dfd = action.deferred = service[action.method]( + serviceAndId.id.replace(/#/,''), // if we are using references, we need eliminate # + dojox.json.ref.toJson(action.content, false, service.servicePath, true) + ); + (function(object, dfd, service){ + dfd.addCallback(function(value){ + try{ + // Implements id assignment per the HTTP specification + var newId = dfd.ioArgs.xhr && dfd.ioArgs.xhr.getResponseHeader("Location"); + //TODO: match URLs if the servicePath is relative... + if(newId){ + // if the path starts in the middle of an absolute URL for Location, we will use the just the path part + var startIndex = newId.match(/(^\w+:\/\/)/) && newId.indexOf(service.servicePath); + newId = startIndex > 0 ? newId.substring(startIndex) : (service.servicePath + newId). + // now do simple relative URL resolution in case of a relative URL. + replace(/^(.*\/)?(\w+:\/\/)|[^\/\.]+\/\.\.\/|^.*\/(\/)/,'$2$3'); + object.__id = newId; + Rest._index[newId] = object; + } + value = resolveJson(service, dfd, value, object && object.__id); + }catch(e){} + if(!(--left)){ + if(kwArgs.onComplete){ + kwArgs.onComplete.call(kwArgs.scope, actions); + } + } + return value; + }); + })(action.content, dfd, service); + + dfd.addErrback(function(value){ + + // on an error we want to revert, first we want to separate any changes that were made since the commit + left = -1; // first make sure that success isn't called + kwArgs.onError.call(kwArgs.scope, value); + }); + } + // revert back to the normal XHR handler + dojo.xhr = plainXhr; + + }, + getDirtyObjects: function(){ + return dirtyObjects; + }, + revert: function(service){ + // summary: + // Reverts all the changes made to JSON/REST data + for(var i = dirtyObjects.length; i > 0;){ + i--; + var dirty = dirtyObjects[i]; + var object = dirty.object; + var old = dirty.old; + var store = dojox.data._getStoreForItem(object || old); + + if(!(service && (object || old) && + (object || old).__id.indexOf(service.servicePath))){ + // if we are in the specified store or if this is a global revert + if(object && old){ + // changed + for(var j in old){ + if(old.hasOwnProperty(j) && object[j] !== old[j]){ + if(store){ + store.onSet(object, j, object[j], old[j]); + } + object[j] = old[j]; + } + } + for(j in object){ + if(!old.hasOwnProperty(j)){ + if(store){ + store.onSet(object, j, object[j]); + } + delete object[j]; + } + } + }else if(!old){ + // was an addition, remove it + if(store){ + store.onDelete(object); + } + }else{ + // was a deletion, we will add it back + if(store){ + store.onNew(old); + } + } + delete (object || old).__isDirty; + dirtyObjects.splice(i, 1); + } + } + }, + changing: function(object,_deleting){ + // summary: + // adds an object to the list of dirty objects. This object + // contains a reference to the object itself as well as a + // cloned and trimmed version of old object for use with + // revert. + if(!object.__id){ + return; + } + object.__isDirty = true; + //if an object is already in the list of dirty objects, don't add it again + //or it will overwrite the premodification data set. + for(var i=0; i<dirtyObjects.length; i++){ + var dirty = dirtyObjects[i]; + if(object==dirty.object){ + if(_deleting){ + // we are deleting, no object is an indicator of deletiong + dirty.object = false; + if(!this._saveNotNeeded){ + dirty.save = true; + } + } + return; + } + } + var old = object instanceof Array ? [] : {}; + for(i in object){ + if(object.hasOwnProperty(i)){ + old[i] = object[i]; + } + } + dirtyObjects.push({object: !_deleting && object, old: old, save: !this._saveNotNeeded}); + }, + deleteObject: function(object){ + // summary: + // deletes an object + // object: + // object to delete + this.changing(object,true); + }, + getConstructor: function(/*Function|String*/service, schema){ + // summary: + // Creates or gets a constructor for objects from this service + if(typeof service == 'string'){ + var servicePath = service; + service = new dojox.rpc.Rest(service,true); + this.registerService(service, servicePath, schema); + } + if(service._constructor){ + return service._constructor; + } + service._constructor = function(data){ + // summary: + // creates a new object for this table + // + // data: + // object to mixed in + var self = this; + var args = arguments; + var properties; + var initializeCalled; + function addDefaults(schema){ + if(schema){ + addDefaults(schema['extends']); + properties = schema.properties; + for(var i in properties){ + var propDef = properties[i]; + if(propDef && (typeof propDef == 'object') && ("default" in propDef)){ + self[i] = propDef["default"]; + } + } + } + if(schema && schema.prototype && schema.prototype.initialize){ + initializeCalled = true; + schema.prototype.initialize.apply(self, args); + } + } + addDefaults(service._schema); + if(!initializeCalled && data && typeof data == 'object'){ + dojo.mixin(self,data); + } + var idAttribute = jr.getIdAttribute(service); + Rest._index[this.__id = this.__clientId = + service.servicePath + (this[idAttribute] || + Math.random().toString(16).substring(2,14) + '@' + ((dojox.rpc.Client && dojox.rpc.Client.clientId) || "client"))] = this; + if(dojox.json.schema && properties){ + dojox.json.schema.mustBeValid(dojox.json.schema.validate(this, service._schema)); + } + dirtyObjects.push({object:this, save: true}); + }; + return dojo.mixin(service._constructor, service._schema, {load:service}); + }, + fetch: function(absoluteId){ + // summary: + // Fetches a resource by an absolute path/id and returns a dojo.Deferred. + var serviceAndId = jr.getServiceAndId(absoluteId); + return this.byId(serviceAndId.service,serviceAndId.id); + }, + getIdAttribute: function(service){ + // summary: + // Return the ids attribute used by this service (based on it's schema). + // Defaults to "id", if not other id is defined + var schema = service._schema; + var idAttr; + if(schema){ + if(!(idAttr = schema._idAttr)){ + for(var i in schema.properties){ + if(schema.properties[i].identity || (schema.properties[i].link == "self")){ + schema._idAttr = idAttr = i; + } + } + } + } + return idAttr || 'id'; + }, + getServiceAndId: function(/*String*/absoluteId){ + // summary: + // Returns the REST service and the local id for the given absolute id. The result + // is returned as an object with a service property and an id property + // absoluteId: + // This is the absolute id of the object + var serviceName = ''; + + for(var service in jr.services){ + if((absoluteId.substring(0, service.length) == service) && (service.length >= serviceName.length)){ + serviceName = service; + } + } + if (serviceName){ + return {service: jr.services[serviceName], id:absoluteId.substring(serviceName.length)}; + } + var parts = absoluteId.match(/^(.*\/)([^\/]*)$/); + return {service: new jr.serviceClass(parts[1], true), id:parts[2]}; + }, + services:{}, + schemas:{}, + registerService: function(/*Function*/ service, /*String*/ servicePath, /*Object?*/ schema){ + // summary: + // Registers a service for as a JsonRest service, mapping it to a path and schema + // service: + // This is the service to register + // servicePath: + // This is the path that is used for all the ids for the objects returned by service + // schema: + // This is a JSON Schema object to associate with objects returned by this service + servicePath = service.servicePath = servicePath || service.servicePath; + service._schema = jr.schemas[servicePath] = schema || service._schema || {}; + jr.services[servicePath] = service; + }, + byId: function(service, id){ + // if caching is allowed, we look in the cache for the result + var deferred, result = Rest._index[(service.servicePath || '') + id]; + if(result && !result._loadObject){// cache hit + deferred = new dojo.Deferred(); + deferred.callback(result); + return deferred; + } + return this.query(service, id); + }, + query: function(service, id, args){ + var deferred = service(id, args); + + deferred.addCallback(function(result){ + if(result.nodeType && result.cloneNode){ + // return immediately if it is an XML document + return result; + } + return resolveJson(service, deferred, result, typeof id != 'string' || (args && (args.start || args.count)) ? undefined: id); + }); + return deferred; + }, + _loader: function(callback){ + // load a lazy object + var serviceAndId = jr.getServiceAndId(this.__id); + var self = this; + jr.query(serviceAndId.service, serviceAndId.id).addBoth(function(result){ + // if they are the same this means an object was loaded, otherwise it + // might be a primitive that was loaded or maybe an error + if(result == self){ + // we can clear the flag, so it is a loaded object + delete result.$ref; + delete result._loadObject; + }else{ + // it is probably a primitive value, we can't change the identity of an object to + // the loaded value, so we will keep it lazy, but define the lazy loader to always + // return the loaded value + self._loadObject = function(callback){ + callback(result); + }; + } + callback(result); + }); + }, + isDirty: function(item, store){ + // summary + // returns true if the item is marked as dirty or true if there are any dirty items + if(!item){ + if(store){ + return dojo.some(dirtyObjects, function(dirty){ + return dojox.data._getStoreForItem(dirty.object || dirty.old) == store; + }); + } + return !!dirtyObjects.length; + } + return item.__isDirty; + } + + }; + + return dojox.rpc.JsonRest; +}); + |
