]> git.pond.sub.org Git - eow/blobdiff - static/dojo-release-1.1.1/dojox/data/jsonPathStore.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojox / data / jsonPathStore.js
diff --git a/static/dojo-release-1.1.1/dojox/data/jsonPathStore.js b/static/dojo-release-1.1.1/dojox/data/jsonPathStore.js
new file mode 100644 (file)
index 0000000..01e4e23
--- /dev/null
@@ -0,0 +1,1191 @@
+if(!dojo._hasResource["dojox.data.jsonPathStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dojox.data.jsonPathStore"] = true;
+dojo.provide("dojox.data.jsonPathStore");
+dojo.require("dojox.jsonPath");
+dojo.require("dojo.date");
+dojo.require("dojo.date.locale");
+dojo.require("dojo.date.stamp");
+
+dojox.data.ASYNC_MODE = 0;
+dojox.data.SYNC_MODE = 1;
+
+dojo.declare("dojox.data.jsonPathStore",
+       null,
+       {
+               mode: dojox.data.ASYNC_MODE,
+               metaLabel: "_meta",
+               hideMetaAttributes: false,
+               autoIdPrefix: "_auto_",
+               autoIdentity: true,
+               idAttribute: "_id",
+               indexOnLoad: true,
+               labelAttribute: "",
+               url: "",
+               _replaceRegex: /\'\]/gi,
+               
+               constructor: function(options){
+                       //summary:
+                       //      jsonPathStore constructor, instantiate a new jsonPathStore 
+                       //
+                       //      Takes a single optional parameter in the form of a Javascript object
+                       //      containing one or more of the following properties. 
+                       //
+                       //      data: /*JSON String*/ || /* Javascript Object */, 
+                       //              JSON String or Javascript object this store will control
+                       //              JSON is converted into an object, and an object passed to
+                       //              the store will be used directly.  If no data and no url
+                       //              is provide, an empty object, {}, will be used as the initial
+                       //              store.
+                       //
+                       //      url: /* string url */   
+                       //              Load data from this url in JSON format and use the Object
+                       //              created from the data as the data source.
+                       //
+                       //      indexOnLoad: /* boolean */ 
+                       //              Defaults to true, but this may change in the near future.
+                       //              Parse the data object and set individual objects up as
+                       //              appropriate.  This will add meta data and assign
+                       //              id's to objects that dont' have them as defined by the
+                       //              idAttribute option.  Disabling this option will keep this 
+                       //              parsing from happening until a query is performed at which
+                       //              time only the top level of an item has meta info stored.
+                       //              This might work in some situations, but you will almost
+                       //              always want to indexOnLoad or use another option which
+                       //              will create an index.  In the future we will support a 
+                       //              generated index that maps by jsonPath allowing the
+                       //              server to take some of this load for larger data sets. 
+                       //
+                       //      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. There
+                       //              are utility routines for exporting data from the store
+                       //              that can clean any generated IDs before exporting and leave
+                       //              preexisting id's in tact.
+                       //
+                       //      metaLabel: /* string */
+                       //              Defaults to '_meta' overrides the attribute name that is used by the store
+                       //              for attaching meta information to an object while
+                       //              in the store's control.  Defaults to '_meta'. 
+                       //      
+                       //      hideMetaAttributes: /* boolean */
+                       //              Defaults to False.  When enabled, calls to getAttributes() will not 
+                       //              include the meta attribute.
+                       //
+                       //      autoIdPrefix: /*string*/
+                       //              Defaults to "_auto_".  This string is used as the prefix to any
+                       //              objects which have a generated id. A numeric index is appended
+                       //              to this string to complete the ID
+                       //
+                       //      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 (options){
+                               dojo.mixin(this,options);
+                       }
+
+                       this._dirtyItems=[];
+                       this._autoId=0;
+                       this._referenceId=0;
+                       this._references={};
+                       this._fetchQueue=[];
+                       this.index={};
+
+                       //regex to identify when we're travelling down metaObject (which we don't want to do) 
+                       var expr="("+this.metaLabel+"\'\])";
+                       this.metaRegex = new RegExp(expr);
+
+
+                       //no data or url, start with an empty object for a store
+                       if (!this.data && !this.url){
+                               this.setData({});
+                       }       
+
+                       //we have data, but no url, set the store as the data
+                       if (this.data && !this.url){
+                               this.setData(this.data);
+
+                               //remove the original refernce, we're now using _data from here on out
+                               delete this.data;
+                       }
+
+                       //given a url, load json data from as the store
+                       if (this.url){
+                               dojo.xhrGet({
+                                       url: options.url,
+                                       handleAs: "json",
+                                       load: dojo.hitch(this, "setData"),
+                                       sync: this.mode
+                               });
+                       }
+               },
+
+               _loadData: function(data){
+                       // summary:
+                       //      load data into the store. Index it if appropriate.
+                       if (this._data){
+                               delete this._data;
+                       }
+
+                       if (dojo.isString(data)){
+                               this._data = dojo.fromJson(data);
+                       }else{
+                               this._data = data;
+                       }
+                       
+                       if (this.indexOnLoad){
+                               this.buildIndex();              
+                       }       
+
+                       this._updateMeta(this._data, {path: "$"});
+
+                       this.onLoadData(this._data);
+               },
+
+               onLoadData: function(data){
+                       // summary
+                       //      Called after data has been loaded in the store.  
+                       //      If any requests happened while the startup is happening
+                       //      then process them now.
+
+                       while (this._fetchQueue.length>0){
+                               var req = this._fetchQueue.shift();
+                               this.fetch(req);
+                       }       
+
+               },
+
+               setData: function(data){
+                       // summary:
+                       //      set the stores' data to the supplied object and then 
+                       //      load and/or setup that data with the required meta info         
+                       this._loadData(data);
+               },
+
+               buildIndex: function(path, item){
+                       //summary: 
+                       //      parse the object structure, and turn any objects into
+                       //      jsonPathStore items. Basically this just does a recursive
+                       //      series of fetches which itself already examines any items
+                       //      as they are retrieved and setups up the required meta information. 
+                       //
+                       //      path: /* string */
+                       //              jsonPath Query for the starting point of this index construction.
+
+                       if (!this.idAttribute){
+                               throw new Error("buildIndex requires idAttribute for the store");
+                       }
+
+                       item = item || this._data;
+                       var origPath = path;
+                       path = path||"$";
+                       path += "[*]";
+                       var data = this.fetch({query: path,mode: dojox.data.SYNC_MODE});
+                       for(var i=0; i<data.length;i++){
+                               if(dojo.isObject(data[i])){
+                                       var newPath = data[i][this.metaLabel]["path"];
+                                       if (origPath){
+                                               //console.log("newPath: ", newPath);
+                                               //console.log("origPath: ", origPath);
+                                               //console.log("path: ", path);
+                                               //console.log("data[i]: ", data[i]);
+                                               var parts = origPath.split("\[\'");
+                                               var attribute = parts[parts.length-1].replace(this._replaceRegex,'');
+                                               //console.log("attribute: ", attribute);
+                                               //console.log("ParentItem: ", item, attribute);
+                                               if (!dojo.isArray(data[i])){
+                                                       this._addReference(data[i], {parent: item, attribute:attribute});
+                                                       this.buildIndex(newPath, data[i]);
+                                               }else{
+                                                       this.buildIndex(newPath,item);
+                                               }
+                                       }else{
+                                               var parts = newPath.split("\[\'");
+                                               var attribute = parts[parts.length-1].replace(this._replaceRegex,'');
+                                               this._addReference(data[i], {parent: this._data, attribute:attribute});
+                                               this.buildIndex(newPath, data[i]);
+                                       }
+                               }
+                       }
+               },
+
+               _correctReference: function(item){
+                       // summary:
+                       //      make sure we have an reference to the item in the store
+                       //      and not a clone. Takes an item, matches it to the corresponding
+                       //      item in the store and if it is the same, returns itself, otherwise
+                       //      it returns the item from the store.
+               
+                       if (this.index[item[this.idAttribute]][this.metaLabel]===item[this.metaLabel]){
+                               return this.index[item[this.idAttribute]];
+                       }
+                       return item;    
+               },
+
+               getValue: function(item, property){
+                       // summary:
+                       //      Gets the value of an item's 'property'
+                       //
+                       //      item: /* object */
+                       //      property: /* string */
+                       //              property to look up value for   
+                       item = this._correctReference(item);
+                       return item[property];
+               },
+
+               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   
+       
+                       item = this._correctReference(item);
+                       return dojo.isArray(item[property]) ? item[property] : [item[property]];
+               },
+
+               getAttributes: function(item){
+                       // summary:
+                       //      Gets the available attributes of an item's 'property' and returns
+                       //      it as an array. If the store has 'hideMetaAttributes' set to true
+                       //      the attributed identified by 'metaLabel' will not be included.
+                       //
+                       //      item: /* object */
+
+                       item = this._correctReference(item);
+                       var res = [];
+                       for (var i in item){
+                               if (this.hideMetaAttributes && (i==this.metaLabel)){continue;}
+                               res.push(i);
+                       }
+                       return res;
+               },
+
+               hasAttribute: function(item,attribute){
+                       // summary:
+                       //      Checks to see if item has attribute
+                       //
+                       //      item: /* object */
+                       //      attribute: /* string */
+               
+                       item = this._correctReference(item);
+                       if (attribute in item){return true;}
+                       return false;   
+               },
+
+               containsValue: function(item, attribute, value){
+                       // summary:
+                       //      Checks to see if 'item' has 'value' at 'attribute'
+                       //
+                       //      item: /* object */
+                       //      attribute: /* string */
+                       //      value: /* anything */
+                       item = this._correctReference(item);
+
+                       if (item[attribute] && item[attribute]==value){return true}
+                       if (dojo.isObject(item[attribute]) || dojo.isObject(value)){
+                               if (this._shallowCompare(item[attribute],value)){return true}
+                       }
+                       return false;   
+               },
+
+               _shallowCompare: function(a, b){
+                       //summary does a simple/shallow compare of properties on an object
+                       //to the same named properties on the given item. Returns
+                       //true if all props match. It will not descend into child objects
+                       //but it will compare child date objects
+
+                       if ((dojo.isObject(a) && !dojo.isObject(b))|| (dojo.isObject(b) && !dojo.isObject(a))) {
+                               return false;
+                       }
+
+                       if ( a["getFullYear"] || b["getFullYear"] ){
+                               //confirm that both are dates
+                               if ( (a["getFullYear"] && !b["getFullYear"]) || (b["getFullYear"] && !a["getFullYear"]) ){
+                                       return false;
+                               }else{
+                                       if (!dojo.date.compare(a,b)){
+                                               return true;
+                                       }
+                                       return false;
+                                       }
+                       }
+
+                       for (var i in b){       
+                               if (dojo.isObject(b[i])){
+                                       if (!a[i] || !dojo.isObject(a[i])){return false}
+
+                                       if (b[i]["getFullYear"]){
+                                               if(!a[i]["getFullYear"]){return false}
+                                               if (dojo.date.compare(a,b)){return false}       
+                                       }else{
+                                               if (!this._shallowCompare(a[i],b[i])){return false}
+                                       }
+                               }else{  
+                                       if (!b[i] || (a[i]!=b[i])){return false}
+                               }
+                       }
+
+                       //make sure there werent props on a that aren't on b, if there aren't, then
+                       //the previous section will have already evaluated things.
+
+                       for (var i in a){
+                               if (!b[i]){return false}
+                       }
+                       
+                       return true;
+               },
+
+               isItem: function(item){
+                       // summary:
+                       //      Checks to see if a passed 'item'
+                       //      is really a jsonPathStore item.  Currently
+                       //      it only verifies structure.  It does not verify
+                       //      that it belongs to this store at this time.
+                       //
+                       //      item: /* object */
+                       //      attribute: /* string */
+               
+                       if (!dojo.isObject(item) || !item[this.metaLabel]){return false}
+                       if (this.requireId && this._hasId && !item[this._id]){return false}
+                       return true;
+               },
+
+               isItemLoaded: function(item){
+                       // summary:
+                       //      returns isItem() :)
+                       //
+                       //      item: /* object */
+
+                       item = this._correctReference(item);
+                       return this.isItem(item);
+               },
+
+               loadItem: function(item){
+                       // summary:
+                       //      returns true. Future implementatins might alter this 
+                       return true;
+               },
+
+               _updateMeta: function(item, props){
+                       // summary:
+                       //      verifies that 'item' has a meta object attached
+                       //      and if not it creates it by setting it to 'props'
+                       //      if the meta attribute already exists, mix 'props'
+                       //      into it.
+
+                       if (item && item[this.metaLabel]){
+                               dojo.mixin(item[this.metaLabel], props);
+                               return;
+                       }
+
+                       item[this.metaLabel]=props;
+               },
+
+               cleanMeta: function(data, options){
+                       // summary
+                       //      Recurses through 'data' and removes an
+                       //      meta information that has been attached. This
+                       //      function will also removes any id's that were autogenerated
+                       //      from objects.  It will not touch id's that were not generated
+
+                       data = data || this._data;
+
+                       if (data[this.metaLabel]){
+                               if(data[this.metaLabel]["autoId"]){
+                                       delete data[this.idAttribute];
+                               }
+                               delete data[this.metaLabel];
+                       }
+
+                       if (dojo.isArray(data)){
+                               for(var i=0; i<data.length;i++){
+                                       if(dojo.isObject(data[i]) || dojo.isArray(data[i]) ){
+                                               this.cleanMeta(data[i]);
+                                       }
+                               }
+                       } else if (dojo.isObject(data)){
+                               for (var i in data){
+                                       this.cleanMeta(data[i]);
+                               }
+                       }
+               }, 
+
+               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 "$..*". jsonPath query to be performed 
+                       //              on data store. **note that since some widgets
+                       //              expect this to be an object, an object in the form
+                       //              of {query: '$[*'], queryOptions: "someOptions"} is
+                       //              acceptable      
+                       //
+                       //      mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE
+                       //              Override the stores default mode.
+                       //
+                       //      queryOptions: /* object */
+                       //              Options passed on to the underlying jsonPath query
+                       //              system.
+                       //
+                       //      start: /* int */
+                       //              Starting item in result set
+                       //
+                       //      count: /* int */
+                       //              Maximum number of items to return
+                       //
+                       //      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
+
+                       // we're not started yet, add this request to a queue and wait till we do       
+                       if (!this._data){
+                               this._fetchQueue.push(args);
+                               return args;
+                       }       
+                       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.mode) {args.mode = this.mode;}
+                       if (!args.queryOptions) {args.queryOptions={};}
+
+                       args.queryOptions.resultType='BOTH';
+                       var results = dojox.jsonPath.query(this._data, query, args.queryOptions);
+                       var tmp=[];
+                       var count=0;
+                       for (var i=0; i<results.length; i++){
+                               if(args.start && i<args.start){continue;}
+                               if (args.count && (count >= args.count)) { continue; }
+
+                               var item = results[i]["value"];
+                               var path = results[i]["path"];
+                               if (!dojo.isObject(item)){continue;}
+                               if(this.metaRegex.exec(path)){continue;}
+
+                               //this automatically records the objects path
+                               this._updateMeta(item,{path: results[i].path});
+
+                               //if autoIdentity and no id, generate one and add it to the item
+                               if(this.autoIdentity && !item[this.idAttribute]){
+                                       var newId = this.autoIdPrefix + this._autoId++;
+                                       item[this.idAttribute]=newId;
+                                       item[this.metaLabel]["autoId"]=true;
+                               }
+
+                               //add item to the item index if appropriate
+                               if(item[this.idAttribute]){this.index[item[this.idAttribute]]=item}
+                               count++;
+                               tmp.push(item);
+                       }
+                       results = tmp;
+                       var scope = args.scope || dojo.global;
+
+                       if ("sort" in args){
+                               console.log("TODO::add support for sorting in the fetch");
+                       }       
+
+                       if (args.mode==dojox.data.SYNC_MODE){ 
+                               return results; 
+                       };
+
+                       if (args.onBegin){      
+                               args["onBegin"].call(scope, results.length, args);
+                       }
+
+                       if (args.onItem){
+                               for (var i=0; i<results.length;i++){    
+                                       args["onItem"].call(scope, results[i], args);
+                               }
+                       }
+                       if (args.onComplete){
+                               args["onComplete"].call(scope, results, args);
+                       }
+
+                       return args;
+               },
+
+               dump: function(options){
+                       // summary:
+                       //
+                       //      exports the store data set. Takes an options
+                       //      object with a number of parameters
+                       //
+                       //      data: /* object */
+                       //              Defaults to the root of the store.
+                       //              The data to be exported.
+                       //      
+                       //      clone: /* boolean */
+                       //              clone the data set before returning it 
+                       //              or modifying it for export
+                       //
+                       //      cleanMeta: /* boolean */
+                       //              clean the meta data off of the data. Note
+                       //              that this will happen to the actual
+                       //              store data if !clone. If you want
+                       //              to continue using the store after
+                       //              this operation, it is probably better to export
+                       //              it as a clone if you want it cleaned.
+                       //
+                       //      suppressExportMeta: /* boolean */
+                       //              By default, when data is exported from the store
+                       //              some information, such as as a timestamp, is
+                       //              added to the root of exported data.  This
+                       //              prevents that from happening.  It is mainly used
+                       //              for making tests easier.
+                       //
+                       //      type: "raw" || "json"
+                       //              Defaults to 'json'. 'json' will convert the data into 
+                       //              json before returning it. 'raw' will just return a
+                       //              reference to the object  
+
+                       var options = options || {};
+                       var d=options.data || this._data;
+       
+                       if (!options.suppressExportMeta && options.clone){
+                               data = dojo.clone(d);
+                               if (data[this.metaLabel]){
+                                       data[this.metaLabel]["clone"]=true;
+                               }
+                       }else{
+                               var data=d;
+                       }
+
+                       if (!options.suppressExportMeta &&  data[this.metaLabel]){
+                               data[this.metaLabel]["last_export"]=new Date().toString()
+                       }
+
+                       if(options.cleanMeta){
+                               this.cleanMeta(data);
+                       }
+
+                       //console.log("Exporting: ", options, dojo.toJson(data));       
+                       switch(options.type){
+                               case "raw":
+                                       return data;
+                               case "json":
+                               default:
+                                       return dojo.toJson(data);
+                       }
+               },      
+
+               getFeatures: function(){
+                       // summary:
+                       //      return the store feature set
+
+                       return { 
+                               "dojo.data.api.Read": true,
+                               "dojo.data.api.Identity": true,
+                               "dojo.data.api.Write": true,
+                               "dojo.data.api.Notification": true
+                       }
+               },
+
+               getLabel: function(item){
+                       // summary
+                       //      returns the label for an item. The label
+                       //      is created by setting the store's labelAttribute 
+                       //      property with either an attribute name  or an array
+                       //      of attribute names.  Developers can also
+                       //      provide the store with a createLabel function which
+                       //      will do the actaul work of creating the label.  If not
+                       //      the default will just concatenate any of the identified
+                       //      attributes together.
+                       item = this._correctReference(item);
+                       var label="";
+
+                       if (dojo.isFunction(this.createLabel)){
+                               return this.createLabel(item);
+                       }
+
+                       if (this.labelAttribute){
+                               if (dojo.isArray(this.labelAttribute))  {
+                                       for(var i=0; i<this.labelAttribute.length; i++){
+                                               if (i>0) { label+=" ";}
+                                               label += item[this.labelAttribute[i]];
+                                       }
+                                       return label;
+                               }else{
+                                       return item[this.labelAttribute];
+                               }
+                       }
+                       return item.toString();
+               },
+
+               getLabelAttributes: function(item){
+                       // summary:
+                       //      returns an array of attributes that are used to create the label of an item
+                       item = this._correctReference(item);
+                       return dojo.isArray(this.labelAttribute) ? this.labelAttribute : [this.labelAttribute];
+               },
+
+               sort: function(a,b){
+                       console.log("TODO::implement default sort algo");
+               },
+
+               //Identity API Support
+
+               getIdentity: function(item){
+                       // summary
+                       //      returns the identity of an item or throws
+                       //      a not found error.
+
+                       if (this.isItem(item)){
+                               return item[this.idAttribute];
+                       }
+                       throw new Error("Id not found for item");
+               },
+
+               getIdentityAttributes: function(item){
+                       // summary:
+                       //      returns the attributes which are used to make up the 
+                       //      identity of an item.  Basically returns this.idAttribute
+
+                       return [this.idAttribute];
+               },
+
+               fetchItemByIdentity: function(args){
+                       // summary: 
+                       //      fetch an item by its identity. This store also provides
+                       //      a much more finger friendly alias, 'byId' which does the
+                       //      same thing as this function.  If provided a string
+                       //      this call will be treated as a SYNC request and will 
+                       //      return the identified item immediatly.  Alternatively it
+                       //      takes a object as a set of keywordArgs:
+                       //      
+                       //      identity: /* string */
+                       //              the id of the item you want to retrieve
+                       //      
+                       //      mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE
+                       //              overrides the default store fetch mode
+                       //      
+                       //      onItem: /* function */
+                       //              Result call back.  Passed the fetched item.
+                       //
+                       //      onError: /* function */
+                       //              error callback. 
+                       var id; 
+                       if (dojo.isString(args)){
+                               id = args;
+                               args = {identity: id, mode: dojox.data.SYNC_MODE}
+                       }else{
+                               if (args){
+                                       id = args["identity"];          
+                               }
+                               if (!args.mode){args.mode = this.mode}  
+                       }
+
+                       if (this.index && (this.index[id] || this.index["identity"])){
+                               
+                               if (args.mode==dojox.data.SYNC_MODE){
+                                       return this.index[id];
+                               }
+
+                               if (args.onItem){
+                                       args["onItem"].call(args.scope || dojo.global, this.index[id], args);
+                               }
+
+                               return args;
+                       }else{
+                               if (args.mode==dojox.data.SYNC_MODE){
+                                       return false;
+                               }
+                       }
+
+
+                       if(args.onError){
+                               args["onItem"].call(args.scope || dojo.global, new Error("Item Not Found: " + id), args);
+                       }
+                       
+                       return args;
+               },
+
+               //Write API Support
+               newItem: function(data, options){
+                       // 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.  This could be a
+                       //              new javascript object, or it could be an item that 
+                       //              already exists in the store.  If it already exists in the 
+                       //              store, then this will be added as a reference.  
+                       //
+                       //      options: /* object */
+                       //
+                       //              item: /* item */
+                       //                      reference to an existing store item
+                       //
+                       //              attribute: /* string */
+                       //                      attribute to add the item at.  If this is
+                       //                      not provided, the item's id will be used as the
+                       //                      attribute name. If specified attribute is an
+                       //                      array, the new item will be push()d on to the
+                       //                      end of it.
+                       //              oldValue: /* old value of item[attribute]
+                       //              newValue: new value item[attribute]
+
+                       var meta={};
+
+                       //default parent to the store root;
+                       var pInfo ={item:this._data};
+
+                       if (options){
+                               if (options.parent){
+                                       options.item = options.parent;
+                               }
+
+                               dojo.mixin(pInfo, options);
+                       }
+
+                       if (this.idAttribute && !data[this.idAttribute]){
+                               if (this.requireId){throw new Error("requireId is enabled, new items must have an id defined to be added");}
+                               if (this.autoIdentity){
+                                       var newId = this.autoIdPrefix + this._autoId++;
+                                       data[this.idAttribute]=newId;
+                                       meta["autoId"]=true;
+                               }
+                       }       
+
+                       if (!pInfo && !pInfo.attribute && !this.idAttribute && !data[this.idAttribute]){
+                               throw new Error("Adding a new item requires, at a minumum, either the pInfo information, including the pInfo.attribute, or an id on the item in the field identified by idAttribute");
+                       }
+
+                       //pInfo.parent = this._correctReference(pInfo.parent);
+                       //if there is no parent info supplied, default to the store root
+                       //and add to the pInfo.attribute or if that doestn' exist create an
+                       //attribute with the same name as the new items ID 
+                       if(!pInfo.attribute){pInfo.attribute = data[this.idAttribute]}
+
+                       pInfo.oldValue = this._trimItem(pInfo.item[pInfo.attribute]);
+                       if (dojo.isArray(pInfo.item[pInfo.attribute])){
+                               this._setDirty(pInfo.item);
+                               pInfo.item[pInfo.attribute].push(data);
+                       }else{
+                               this._setDirty(pInfo.item);
+                               pInfo.item[pInfo.attribute]=data;
+                       }
+
+                       pInfo.newValue = pInfo.item[pInfo.attribute];
+
+                       //add this item to the index
+                       if(data[this.idAttribute]){this.index[data[this.idAttribute]]=data}
+
+                       this._updateMeta(data, meta)
+
+                       //keep track of all references in the store so we can delete them as necessary
+                       this._addReference(data, pInfo);
+
+                       //mark this new item as dirty
+                       this._setDirty(data);
+
+                       //Notification API
+                       this.onNew(data, pInfo);
+
+                       //returns the original item, now decorated with some meta info
+                       return data;
+               },
+
+               _addReference: function(item, pInfo){
+                       // summary
+                       //      adds meta information to an item containing a reference id
+                       //      so that references can be deleted as necessary, when passed
+                       //      only a string, the string for parent info, it will only
+                       //      it will be treated as a string reference
+
+                       //console.log("_addReference: ", item, pInfo);  
+                       var rid = '_ref_' + this._referenceId++;
+                       if (!item[this.metaLabel]["referenceIds"]){
+                               item[this.metaLabel]["referenceIds"]=[];
+                       }
+
+                       item[this.metaLabel]["referenceIds"].push(rid);
+                       this._references[rid] = pInfo;                          
+               },
+
+               deleteItem: function(item){     
+                       // summary
+                       //      deletes item and any references to that item from the store.
+                       //      If the desire is to delete only one reference, unsetAttribute or
+                       //      setValue is the way to go.
+
+                       item = this._correctReference(item);
+                       console.log("Item: ", item);
+                       if (this.isItem(item)){
+                               while(item[this.metaLabel]["referenceIds"].length>0){
+                                       console.log("refs map: " , this._references);
+                                       console.log("item to delete: ", item);
+                                       var rid = item[this.metaLabel]["referenceIds"].pop();
+                                       var pInfo = this._references[rid];
+
+                                       console.log("deleteItem(): ", pInfo, pInfo.parent);
+                                       parentItem = pInfo.parent;
+                                       var attribute = pInfo.attribute;        
+                                       if(parentItem && parentItem[attribute] && !dojo.isArray(parentItem[attribute])){
+                                               this._setDirty(parentItem);
+                                               this.unsetAttribute(parentItem, attribute);
+                                               delete parentItem[attribute];
+                                       }
+
+                                       if (dojo.isArray(parentItem[attribute])){
+                                               console.log("Parent is array");
+                                               var oldValue = this._trimItem(parentItem[attribute]);
+                                               var found=false;
+                                               for (var i=0; i<parentItem[attribute].length && !found;i++){
+                                                       if (parentItem[attribute][i][this.metaLabel]===item[this.metaLabel]){
+                                                               found=true;     
+                                                       }                       
+                                               }       
+
+                                               if (found){
+                                                       this._setDirty(parentItem);
+                                                       var del =  parentItem[attribute].splice(i-1,1);
+                                                       delete del;
+                                               }
+
+                                               var newValue = this._trimItem(parentItem[attribute]);
+                                               this.onSet(parentItem,attribute,oldValue,newValue);     
+                                       }
+                                       delete this._references[rid];
+
+                               }
+                               this.onDelete(item);            
+                               delete item;
+                       }
+               },
+
+               _setDirty: function(item){
+                       // 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.
+
+                       //if an item is already in the list of dirty items, don't add it again
+                       //or it will overwrite the premodification data set.
+                       for (var i=0; i<this._dirtyItems.length; i++){
+                               if (item[this.idAttribute]==this._dirtyItems[i][this.idAttribute]){
+                                       return; 
+                               }       
+                       }
+
+                       this._dirtyItems.push({item: item, old: this._trimItem(item)});
+                       this._updateMeta(item, {isDirty: true});
+               },
+
+               setValue: function(item, attribute, value){
+                       // summary:
+                       //      sets 'attribute' on 'item' to 'value'
+                       item = this._correctReference(item);
+
+                       this._setDirty(item);
+                       var old = item[attribute] | undefined;
+                       item[attribute]=value;
+                       this.onSet(item,attribute,old,value);
+
+               },
+
+               setValues: function(item, attribute, values){
+                       // summary:
+                       //      sets 'attribute' on 'item' to 'value' value
+                       //      must be an array.
+
+
+                       item = this._correctReference(item);
+                       if (!dojo.isArray(values)){throw new Error("setValues expects to be passed an Array object as its value");}
+                       this._setDirty(item);
+                       var old = item[attribute] || null;
+                       item[attribute]=values
+                       this.onSet(item,attribute,old,values);
+               },
+
+               unsetAttribute: function(item, attribute){
+                       // summary:
+                       //      unsets 'attribute' on 'item'
+
+                       item = this._correctReference(item);
+                       this._setDirty(item);
+                       var old = item[attribute];
+                       delete item[attribute];
+                       this.onSet(item,attribute,old,null);
+               },
+
+               save: function(kwArgs){
+                       // summary:
+                       //      Takes an optional set of keyword Args with
+                       //      some save options.  Currently only format with options
+                       //      being "raw" or "json".  This function goes through
+                       //      the dirty item lists, clones and trims the item down so that
+                       //      the items children are not part of the data (the children are replaced
+                       //      with reference objects). This data is compiled into a single array, the dirty objects
+                       //      are all marked as clean, and the new data is then passed on to the onSave handler.
+
+                       var data = [];
+               
+                       if (!kwArgs){kwArgs={}}
+                       while (this._dirtyItems.length > 0){
+                               var item = this._dirtyItems.pop()["item"];
+                               var t = this._trimItem(item);
+                               var d;  
+                               switch(kwArgs.format){  
+                                       case "json":
+                                               d = dojo.toJson(t);     
+                                               break;
+                                       case "raw":
+                                       default:
+                                               d = t;
+                               }
+                               data.push(d);
+                               this._markClean(item);
+                       }
+
+                       this.onSave(data);
+               },
+
+               _markClean: function(item){
+                       // summary
+                       //      remove this meta information marking an item as "dirty"
+
+                       if (item && item[this.metaLabel] && item[this.metaLabel]["isDirty"]){
+                               delete item[this.metaLabel]["isDirty"];
+                       }       
+               },
+
+               revert: function(){
+                       // summary
+                       //      returns any modified data to its original state prior to a save();
+
+                       while (this._dirtyItems.length>0){
+                               var d = this._dirtyItems.pop();
+                               this._mixin(d.item, d.old);
+                       }
+                       this.onRevert();
+               },
+
+               _mixin: function(target, data){
+                       // summary:
+                       //      specialized mixin that hooks up objects in the store where references are identified.
+
+                       if (dojo.isObject(data)){
+                               if (dojo.isArray(data)){
+                                       while(target.length>0){target.pop();}
+                                       for (var i=0; i<data.length;i++){
+                                               if (dojo.isObject(data[i])){
+                                                       if (dojo.isArray(data[i])){
+                                                               var mix=[];
+                                                       }else{
+                                                               var mix={};
+                                                               if (data[i][this.metaLabel] && data[i][this.metaLabel]["type"] && data[i][this.metaLabel]["type"]=='reference'){
+                                                                       target[i]=this.index[data[i][this.idAttribute]];
+                                                                       continue;
+                                                               }
+                                                       }
+
+                                                       this._mixin(mix, data[i]);
+                                                       target.push(mix);
+                                               }else{
+                                                       target.push(data[i]);
+                                               }
+                                       }       
+                               }else{
+                                       for (var i in target){
+                                               if (i in data){continue;}
+                                               delete target[i];
+                                       }
+
+                                       for (var i in data){
+                                               if (dojo.isObject(data[i])){
+                                                       if (dojo.isArray(data[i])){
+                                                               var mix=[];
+                                                       }else{
+                                                               if (data[i][this.metaLabel] && data[i][this.metaLabel]["type"] && data[i][this.metaLabel]["type"]=='reference'){
+                                                                       target[i]=this.index[data[i][this.idAttribute]];
+                                                                       continue;
+                                                               }
+
+                                                               var mix={};
+                                                       }
+                                                       this._mixin(mix, data[i]);
+                                                       target[i]=mix;
+                                               }else{
+                                                       target[i]=data[i];
+                                               }
+                                       }       
+
+                               }
+                       }
+               },
+
+               isDirty: function(item){
+                       // summary
+                       //      returns true if the item is marked as dirty.
+
+                       item = this._correctReference(item);
+                       return item && item[this.metaLabel] && item[this.metaLabel]["isDirty"];
+               },
+
+               _createReference: function(item){
+                       // summary
+                       //      Create a small reference object that can be used to replace
+                       //      child objects during a trim
+
+                       var obj={};
+                       obj[this.metaLabel]={
+                               type:'reference'
+                       };
+
+                       obj[this.idAttribute]=item[this.idAttribute];
+                       return obj;
+               },
+
+               _trimItem: function(item){
+                       //summary:
+                       //      copy an item recursively stoppying at other items that have id's
+                       //      and replace them with a refrence object;
+                       var copy;
+                       if (dojo.isArray(item)){
+                               copy = [];
+                               for (var i=0; i<item.length;i++){
+                                       if (dojo.isArray(item[i])){
+                                               copy.push(this._trimItem(item[i]))
+                                       }else if (dojo.isObject(item[i])){
+                                               if (item[i]["getFullYear"]){
+                                                       copy.push(dojo.date.stamp.toISOString(item[i]));
+                                               }else if (item[i][this.idAttribute]){
+                                                       copy.push(this._createReference(item[i]));
+                                               }else{
+                                                       copy.push(this._trimItem(item[i]));     
+                                               }
+                                       } else {
+                                               copy.push(item[i]);     
+                                       }
+                               }
+                               return copy;
+                       } 
+
+                       if (dojo.isObject(item)){
+                               copy = {};
+
+                               for (var attr in item){
+                                       if (!item[attr]){ copy[attr]=undefined;continue;}
+                                       if (dojo.isArray(item[attr])){
+                                               copy[attr] = this._trimItem(item[attr]);
+                                       }else if (dojo.isObject(item[attr])){
+                                               if (item[attr]["getFullYear"]){
+                                                       copy[attr] =  dojo.date.stamp.toISOString(item[attr]);
+                                               }else if(item[attr][this.idAttribute]){
+                                                       copy[attr]=this._createReference(item[attr]);
+                                               } else {
+                                                       copy[attr]=this._trimItem(item[attr]);
+                                               }
+                                       } else {
+                                               copy[attr]=item[attr];
+                                       }
+                               }
+                               return copy;
+                       }
+               },
+
+               //Notifcation Support
+
+               onSet: function(){
+               },
+
+               onNew: function(){
+
+               },
+
+               onDelete: function(){
+
+               },      
+       
+               onSave: function(items){
+                       // summary:
+                       //      notification of the save event..not part of the notification api, 
+                       //      but probably should be.
+                       //console.log("onSave() ", items);
+               },
+
+               onRevert: function(){
+                       // summary:
+                       //      notification of the revert event..not part of the notification api, 
+                       //      but probably should be.
+
+               }
+       }
+);
+
+//setup an alias to byId, is there a better way to do this?
+dojox.data.jsonPathStore.byId=dojox.data.jsonPathStore.fetchItemByIdentity;
+
+}