dojox/rpc/JsonRest.js

  • Provides:

    • dojox.rpc.JsonRest
  • dojox.rpc.JsonRest

    • type
      Object
    • summary
  • dojox.rpc.JsonRest.serviceClass

    • summary
  • dojox.rpc.JsonRest.conflictDateHeader

    • summary
  • dojox.rpc.JsonRest.commit

    • type
      Function
    • parameters:
      • kwArgs: (typeof )
    • source: [view]
         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;
    • summary
      Saves the dirty data using REST Ajax methods
  • dojox.rpc.JsonRest.sendToServer

    • type
      Function
    • parameters:
      • actions: (typeof )
      • kwArgs: (typeof )
    • source: [view]
         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;
    • chains:
      • kwArgs.onComplete: (call)
      • kwArgs.onError: (call)
    • summary
  • dojox.rpc.JsonRest.getDirtyObjects

    • type
      Function
    • source: [view]
         return dirtyObjects;
    • summary
  • dojox.rpc.JsonRest.revert

    • type
      Function
    • parameters:
      • service: (typeof )
    • source: [view]
         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);
          }
         }
    • summary
      Reverts all the changes made to JSON/REST data
  • dojox.rpc.JsonRest.changing

    • type
      Function
    • parameters:
      • object: (typeof )
      • _deleting: (typeof )
    • source: [view]
         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    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});
    • 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.
  • dojox.rpc.JsonRest.deleteObject

    • type
      Function
    • parameters:
      • object: (typeof object)
        to delete
    • source: [view]
         this.changing(object,true);
    • summary
      deletes an object
  • dojox.rpc.JsonRest.getConstructor

    • type
      Function
    • parameters:
      • service: (typeof Function|String)
      • schema: (typeof )
    • source: [view]
         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});
    • summary
      Creates or gets a constructor for objects from this service
    • chains:
      • schema.prototype.initialize: (call)
  • dojox.rpc.JsonRest.getConstructor.__id

    • summary
  • dojox.rpc.JsonRest.fetch

    • type
      Function
    • parameters:
      • absoluteId: (typeof )
    • source: [view]
         var serviceAndId = jr.getServiceAndId(absoluteId);
         return this.byId(serviceAndId.service,serviceAndId.id);
    • summary
      Fetches a resource by an absolute path/id and returns a dojo.Deferred.
  • dojox.rpc.JsonRest.getIdAttribute

    • type
      Function
    • parameters:
      • service: (typeof )
    • source: [view]
         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';
    • summary
      Return the ids attribute used by this service (based on it's schema).
      Defaults to &quot;id&quot;, if not other id is defined
  • dojox.rpc.JsonRest.getServiceAndId

    • type
      Function
    • parameters:
      • absoluteId: (typeof String)
        This is the absolute id of the object
    • source: [view]
         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]};
    • 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
  • dojox.rpc.JsonRest.services

    • summary
  • resolveJson

    • type
      Function
    • parameters:
      • service: (typeof )
      • deferred: (typeof )
      • value: (typeof )
      • defaultId: (typeof )
    • source: [view]
        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;
    • summary
  • self._loadObject

    • type
      Function
    • parameters:
      • callback: (typeof )
    • source: [view]
            callback(result);
    • summary
  • self

    • mixins:
      • data: (normal)
    • summary
  • service._constructor

    • mixins:
      • service._schema: (normal)
    • summary
  • service._constructor.load

    • summary
  • dirtyObjects

    • summary
  • Rest

    • summary
  • jr

    • summary
  • servicePath

    • summary
  • service._schema

    • summary
  • result

    • summary
  • deferred

    • summary
  • serviceAndId

    • summary
  • dojox.rpc

    • type
      Object
    • summary
  • dojox

    • type
      Object
    • summary