]> git.pond.sub.org Git - eow/blob - 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
1 if(!dojo._hasResource["dojox.data.jsonPathStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojox.data.jsonPathStore"] = true;
3 dojo.provide("dojox.data.jsonPathStore");
4 dojo.require("dojox.jsonPath");
5 dojo.require("dojo.date");
6 dojo.require("dojo.date.locale");
7 dojo.require("dojo.date.stamp");
8
9 dojox.data.ASYNC_MODE = 0;
10 dojox.data.SYNC_MODE = 1;
11
12 dojo.declare("dojox.data.jsonPathStore",
13         null,
14         {
15                 mode: dojox.data.ASYNC_MODE,
16                 metaLabel: "_meta",
17                 hideMetaAttributes: false,
18                 autoIdPrefix: "_auto_",
19                 autoIdentity: true,
20                 idAttribute: "_id",
21                 indexOnLoad: true,
22                 labelAttribute: "",
23                 url: "",
24                 _replaceRegex: /\'\]/gi,
25                 
26                 constructor: function(options){
27                         //summary:
28                         //      jsonPathStore constructor, instantiate a new jsonPathStore 
29                         //
30                         //      Takes a single optional parameter in the form of a Javascript object
31                         //      containing one or more of the following properties. 
32                         //
33                         //      data: /*JSON String*/ || /* Javascript Object */, 
34                         //              JSON String or Javascript object this store will control
35                         //              JSON is converted into an object, and an object passed to
36                         //              the store will be used directly.  If no data and no url
37                         //              is provide, an empty object, {}, will be used as the initial
38                         //              store.
39                         //
40                         //      url: /* string url */   
41                         //              Load data from this url in JSON format and use the Object
42                         //              created from the data as the data source.
43                         //
44                         //      indexOnLoad: /* boolean */ 
45                         //              Defaults to true, but this may change in the near future.
46                         //              Parse the data object and set individual objects up as
47                         //              appropriate.  This will add meta data and assign
48                         //              id's to objects that dont' have them as defined by the
49                         //              idAttribute option.  Disabling this option will keep this 
50                         //              parsing from happening until a query is performed at which
51                         //              time only the top level of an item has meta info stored.
52                         //              This might work in some situations, but you will almost
53                         //              always want to indexOnLoad or use another option which
54                         //              will create an index.  In the future we will support a 
55                         //              generated index that maps by jsonPath allowing the
56                         //              server to take some of this load for larger data sets. 
57                         //
58                         //      idAttribute: /* string */
59                         //              Defaults to '_id'. The name of the attribute that holds an objects id.
60                         //              This can be a preexisting id provided by the server.  
61                         //              If an ID isn't already provided when an object
62                         //              is fetched or added to the store, the autoIdentity system
63                         //              will generate an id for it and add it to the index. There
64                         //              are utility routines for exporting data from the store
65                         //              that can clean any generated IDs before exporting and leave
66                         //              preexisting id's in tact.
67                         //
68                         //      metaLabel: /* string */
69                         //              Defaults to '_meta' overrides the attribute name that is used by the store
70                         //              for attaching meta information to an object while
71                         //              in the store's control.  Defaults to '_meta'. 
72                         //      
73                         //      hideMetaAttributes: /* boolean */
74                         //              Defaults to False.  When enabled, calls to getAttributes() will not 
75                         //              include the meta attribute.
76                         //
77                         //      autoIdPrefix: /*string*/
78                         //              Defaults to "_auto_".  This string is used as the prefix to any
79                         //              objects which have a generated id. A numeric index is appended
80                         //              to this string to complete the ID
81                         //
82                         //      mode: dojox.data.ASYNC_MODE || dojox.data.SYNC_MODE
83                         //              Defaults to ASYNC_MODE.  This option sets the default mode for this store.
84                         //              Sync calls return their data immediately from the calling function
85                         //              instead of calling the callback functions.  Functions such as 
86                         //              fetchItemByIdentity() and fetch() both accept a string parameter in addtion
87                         //              to the normal keywordArgs parameter.  When passed this option, SYNC_MODE will
88                         //              automatically be used even when the default mode of the system is ASYNC_MODE.
89                         //              A normal request to fetch or fetchItemByIdentity (with kwArgs object) can also 
90                         //              include a mode property to override this setting for that one request.
91
92                         //setup a byId alias to the api call    
93                         this.byId=this.fetchItemByIdentity;
94
95                         if (options){
96                                 dojo.mixin(this,options);
97                         }
98
99                         this._dirtyItems=[];
100                         this._autoId=0;
101                         this._referenceId=0;
102                         this._references={};
103                         this._fetchQueue=[];
104                         this.index={};
105
106                         //regex to identify when we're travelling down metaObject (which we don't want to do) 
107                         var expr="("+this.metaLabel+"\'\])";
108                         this.metaRegex = new RegExp(expr);
109
110
111                         //no data or url, start with an empty object for a store
112                         if (!this.data && !this.url){
113                                 this.setData({});
114                         }       
115
116                         //we have data, but no url, set the store as the data
117                         if (this.data && !this.url){
118                                 this.setData(this.data);
119
120                                 //remove the original refernce, we're now using _data from here on out
121                                 delete this.data;
122                         }
123
124                         //given a url, load json data from as the store
125                         if (this.url){
126                                 dojo.xhrGet({
127                                         url: options.url,
128                                         handleAs: "json",
129                                         load: dojo.hitch(this, "setData"),
130                                         sync: this.mode
131                                 });
132                         }
133                 },
134
135                 _loadData: function(data){
136                         // summary:
137                         //      load data into the store. Index it if appropriate.
138                         if (this._data){
139                                 delete this._data;
140                         }
141
142                         if (dojo.isString(data)){
143                                 this._data = dojo.fromJson(data);
144                         }else{
145                                 this._data = data;
146                         }
147                         
148                         if (this.indexOnLoad){
149                                 this.buildIndex();              
150                         }       
151
152                         this._updateMeta(this._data, {path: "$"});
153
154                         this.onLoadData(this._data);
155                 },
156
157                 onLoadData: function(data){
158                         // summary
159                         //      Called after data has been loaded in the store.  
160                         //      If any requests happened while the startup is happening
161                         //      then process them now.
162
163                         while (this._fetchQueue.length>0){
164                                 var req = this._fetchQueue.shift();
165                                 this.fetch(req);
166                         }       
167
168                 },
169
170                 setData: function(data){
171                         // summary:
172                         //      set the stores' data to the supplied object and then 
173                         //      load and/or setup that data with the required meta info         
174                         this._loadData(data);
175                 },
176
177                 buildIndex: function(path, item){
178                         //summary: 
179                         //      parse the object structure, and turn any objects into
180                         //      jsonPathStore items. Basically this just does a recursive
181                         //      series of fetches which itself already examines any items
182                         //      as they are retrieved and setups up the required meta information. 
183                         //
184                         //      path: /* string */
185                         //              jsonPath Query for the starting point of this index construction.
186
187                         if (!this.idAttribute){
188                                 throw new Error("buildIndex requires idAttribute for the store");
189                         }
190
191                         item = item || this._data;
192                         var origPath = path;
193                         path = path||"$";
194                         path += "[*]";
195                         var data = this.fetch({query: path,mode: dojox.data.SYNC_MODE});
196                         for(var i=0; i<data.length;i++){
197                                 if(dojo.isObject(data[i])){
198                                         var newPath = data[i][this.metaLabel]["path"];
199                                         if (origPath){
200                                                 //console.log("newPath: ", newPath);
201                                                 //console.log("origPath: ", origPath);
202                                                 //console.log("path: ", path);
203                                                 //console.log("data[i]: ", data[i]);
204                                                 var parts = origPath.split("\[\'");
205                                                 var attribute = parts[parts.length-1].replace(this._replaceRegex,'');
206                                                 //console.log("attribute: ", attribute);
207                                                 //console.log("ParentItem: ", item, attribute);
208                                                 if (!dojo.isArray(data[i])){
209                                                         this._addReference(data[i], {parent: item, attribute:attribute});
210                                                         this.buildIndex(newPath, data[i]);
211                                                 }else{
212                                                         this.buildIndex(newPath,item);
213                                                 }
214                                         }else{
215                                                 var parts = newPath.split("\[\'");
216                                                 var attribute = parts[parts.length-1].replace(this._replaceRegex,'');
217                                                 this._addReference(data[i], {parent: this._data, attribute:attribute});
218                                                 this.buildIndex(newPath, data[i]);
219                                         }
220                                 }
221                         }
222                 },
223
224                 _correctReference: function(item){
225                         // summary:
226                         //      make sure we have an reference to the item in the store
227                         //      and not a clone. Takes an item, matches it to the corresponding
228                         //      item in the store and if it is the same, returns itself, otherwise
229                         //      it returns the item from the store.
230                 
231                         if (this.index[item[this.idAttribute]][this.metaLabel]===item[this.metaLabel]){
232                                 return this.index[item[this.idAttribute]];
233                         }
234                         return item;    
235                 },
236
237                 getValue: function(item, property){
238                         // summary:
239                         //      Gets the value of an item's 'property'
240                         //
241                         //      item: /* object */
242                         //      property: /* string */
243                         //              property to look up value for   
244                         item = this._correctReference(item);
245                         return item[property];
246                 },
247
248                 getValues: function(item, property){
249                         // summary:
250                         //      Gets the value of an item's 'property' and returns
251                         //      it.  If this value is an array it is just returned,
252                         //      if not, the value is added to an array and that is returned.
253                         //
254                         //      item: /* object */
255                         //      property: /* string */
256                         //              property to look up value for   
257         
258                         item = this._correctReference(item);
259                         return dojo.isArray(item[property]) ? item[property] : [item[property]];
260                 },
261
262                 getAttributes: function(item){
263                         // summary:
264                         //      Gets the available attributes of an item's 'property' and returns
265                         //      it as an array. If the store has 'hideMetaAttributes' set to true
266                         //      the attributed identified by 'metaLabel' will not be included.
267                         //
268                         //      item: /* object */
269
270                         item = this._correctReference(item);
271                         var res = [];
272                         for (var i in item){
273                                 if (this.hideMetaAttributes && (i==this.metaLabel)){continue;}
274                                 res.push(i);
275                         }
276                         return res;
277                 },
278
279                 hasAttribute: function(item,attribute){
280                         // summary:
281                         //      Checks to see if item has attribute
282                         //
283                         //      item: /* object */
284                         //      attribute: /* string */
285                 
286                         item = this._correctReference(item);
287                         if (attribute in item){return true;}
288                         return false;   
289                 },
290
291                 containsValue: function(item, attribute, value){
292                         // summary:
293                         //      Checks to see if 'item' has 'value' at 'attribute'
294                         //
295                         //      item: /* object */
296                         //      attribute: /* string */
297                         //      value: /* anything */
298                         item = this._correctReference(item);
299
300                         if (item[attribute] && item[attribute]==value){return true}
301                         if (dojo.isObject(item[attribute]) || dojo.isObject(value)){
302                                 if (this._shallowCompare(item[attribute],value)){return true}
303                         }
304                         return false;   
305                 },
306
307                 _shallowCompare: function(a, b){
308                         //summary does a simple/shallow compare of properties on an object
309                         //to the same named properties on the given item. Returns
310                         //true if all props match. It will not descend into child objects
311                         //but it will compare child date objects
312
313                         if ((dojo.isObject(a) && !dojo.isObject(b))|| (dojo.isObject(b) && !dojo.isObject(a))) {
314                                 return false;
315                         }
316
317                         if ( a["getFullYear"] || b["getFullYear"] ){
318                                 //confirm that both are dates
319                                 if ( (a["getFullYear"] && !b["getFullYear"]) || (b["getFullYear"] && !a["getFullYear"]) ){
320                                         return false;
321                                 }else{
322                                         if (!dojo.date.compare(a,b)){
323                                                 return true;
324                                         }
325                                         return false;
326                                 }
327                         }
328
329                         for (var i in b){       
330                                 if (dojo.isObject(b[i])){
331                                         if (!a[i] || !dojo.isObject(a[i])){return false}
332
333                                         if (b[i]["getFullYear"]){
334                                                 if(!a[i]["getFullYear"]){return false}
335                                                 if (dojo.date.compare(a,b)){return false}       
336                                         }else{
337                                                 if (!this._shallowCompare(a[i],b[i])){return false}
338                                         }
339                                 }else{  
340                                         if (!b[i] || (a[i]!=b[i])){return false}
341                                 }
342                         }
343
344                         //make sure there werent props on a that aren't on b, if there aren't, then
345                         //the previous section will have already evaluated things.
346
347                         for (var i in a){
348                                 if (!b[i]){return false}
349                         }
350                         
351                         return true;
352                 },
353
354                 isItem: function(item){
355                         // summary:
356                         //      Checks to see if a passed 'item'
357                         //      is really a jsonPathStore item.  Currently
358                         //      it only verifies structure.  It does not verify
359                         //      that it belongs to this store at this time.
360                         //
361                         //      item: /* object */
362                         //      attribute: /* string */
363                 
364                         if (!dojo.isObject(item) || !item[this.metaLabel]){return false}
365                         if (this.requireId && this._hasId && !item[this._id]){return false}
366                         return true;
367                 },
368
369                 isItemLoaded: function(item){
370                         // summary:
371                         //      returns isItem() :)
372                         //
373                         //      item: /* object */
374
375                         item = this._correctReference(item);
376                         return this.isItem(item);
377                 },
378
379                 loadItem: function(item){
380                         // summary:
381                         //      returns true. Future implementatins might alter this 
382                         return true;
383                 },
384
385                 _updateMeta: function(item, props){
386                         // summary:
387                         //      verifies that 'item' has a meta object attached
388                         //      and if not it creates it by setting it to 'props'
389                         //      if the meta attribute already exists, mix 'props'
390                         //      into it.
391
392                         if (item && item[this.metaLabel]){
393                                 dojo.mixin(item[this.metaLabel], props);
394                                 return;
395                         }
396
397                         item[this.metaLabel]=props;
398                 },
399
400                 cleanMeta: function(data, options){
401                         // summary
402                         //      Recurses through 'data' and removes an
403                         //      meta information that has been attached. This
404                         //      function will also removes any id's that were autogenerated
405                         //      from objects.  It will not touch id's that were not generated
406
407                         data = data || this._data;
408
409                         if (data[this.metaLabel]){
410                                 if(data[this.metaLabel]["autoId"]){
411                                         delete data[this.idAttribute];
412                                 }
413                                 delete data[this.metaLabel];
414                         }
415
416                         if (dojo.isArray(data)){
417                                 for(var i=0; i<data.length;i++){
418                                         if(dojo.isObject(data[i]) || dojo.isArray(data[i]) ){
419                                                 this.cleanMeta(data[i]);
420                                         }
421                                 }
422                         } else if (dojo.isObject(data)){
423                                 for (var i in data){
424                                         this.cleanMeta(data[i]);
425                                 }
426                         }
427                 }, 
428
429                 fetch: function(args){
430                         //console.log("fetch() ", args);
431                         // summary
432                         //      
433                         //      fetch takes either a string argument or a keywordArgs
434                         //      object containing the parameters for the search.
435                         //      If passed a string, fetch will interpret this string
436                         //      as the query to be performed and will do so in 
437                         //      SYNC_MODE returning the results immediately.
438                         //      If an object is supplied as 'args', its options will be 
439                         //      parsed and then contained query executed. 
440                         //
441                         //      query: /* string or object */
442                         //              Defaults to "$..*". jsonPath query to be performed 
443                         //              on data store. **note that since some widgets
444                         //              expect this to be an object, an object in the form
445                         //              of {query: '$[*'], queryOptions: "someOptions"} is
446                         //              acceptable      
447                         //
448                         //      mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE
449                         //              Override the stores default mode.
450                         //
451                         //      queryOptions: /* object */
452                         //              Options passed on to the underlying jsonPath query
453                         //              system.
454                         //
455                         //      start: /* int */
456                         //              Starting item in result set
457                         //
458                         //      count: /* int */
459                         //              Maximum number of items to return
460                         //
461                         //      sort: /* function */
462                         //              Not Implemented yet
463                         //
464                         //      The following only apply to ASYNC requests (the default)
465                         //
466                         //      onBegin: /* function */
467                         //              called before any results are returned. Parameters
468                         //              will be the count and the original fetch request
469                         //      
470                         //      onItem: /*function*/
471                         //              called for each returned item.  Parameters will be
472                         //              the item and the fetch request
473                         //
474                         //      onComplete: /* function */
475                         //              called on completion of the request.  Parameters will   
476                         //              be the complete result set and the request
477                         //
478                         //      onError: /* function */
479                         //              colled in the event of an error
480
481                         // we're not started yet, add this request to a queue and wait till we do       
482                         if (!this._data){
483                                 this._fetchQueue.push(args);
484                                 return args;
485                         }       
486                         if(dojo.isString(args)){
487                                         query = args;
488                                         args={query: query, mode: dojox.data.SYNC_MODE};
489                                         
490                         }
491
492                         var query;
493                         if (!args || !args.query){
494                                 if (!args){
495                                         var args={};    
496                                 }
497
498                                 if (!args.query){
499                                         args.query="$..*";
500                                         query=args.query;
501                                 }
502
503                         }
504
505                         if (dojo.isObject(args.query)){
506                                 if (args.query.query){
507                                         query = args.query.query;
508                                 }else{
509                                         query = args.query = "$..*";
510                                 }
511                                 if (args.query.queryOptions){
512                                         args.queryOptions=args.query.queryOptions
513                                 }
514                         }else{
515                                 query=args.query;
516                         }
517
518                         if (!args.mode) {args.mode = this.mode;}
519                         if (!args.queryOptions) {args.queryOptions={};}
520
521                         args.queryOptions.resultType='BOTH';
522                         var results = dojox.jsonPath.query(this._data, query, args.queryOptions);
523                         var tmp=[];
524                         var count=0;
525                         for (var i=0; i<results.length; i++){
526                                 if(args.start && i<args.start){continue;}
527                                 if (args.count && (count >= args.count)) { continue; }
528
529                                 var item = results[i]["value"];
530                                 var path = results[i]["path"];
531                                 if (!dojo.isObject(item)){continue;}
532                                 if(this.metaRegex.exec(path)){continue;}
533
534                                 //this automatically records the objects path
535                                 this._updateMeta(item,{path: results[i].path});
536
537                                 //if autoIdentity and no id, generate one and add it to the item
538                                 if(this.autoIdentity && !item[this.idAttribute]){
539                                         var newId = this.autoIdPrefix + this._autoId++;
540                                         item[this.idAttribute]=newId;
541                                         item[this.metaLabel]["autoId"]=true;
542                                 }
543
544                                 //add item to the item index if appropriate
545                                 if(item[this.idAttribute]){this.index[item[this.idAttribute]]=item}
546                                 count++;
547                                 tmp.push(item);
548                         }
549                         results = tmp;
550                         var scope = args.scope || dojo.global;
551
552                         if ("sort" in args){
553                                 console.log("TODO::add support for sorting in the fetch");
554                         }       
555
556                         if (args.mode==dojox.data.SYNC_MODE){ 
557                                 return results; 
558                         };
559
560                         if (args.onBegin){      
561                                 args["onBegin"].call(scope, results.length, args);
562                         }
563
564                         if (args.onItem){
565                                 for (var i=0; i<results.length;i++){    
566                                         args["onItem"].call(scope, results[i], args);
567                                 }
568                         }
569  
570                         if (args.onComplete){
571                                 args["onComplete"].call(scope, results, args);
572                         }
573
574                         return args;
575                 },
576
577                 dump: function(options){
578                         // summary:
579                         //
580                         //      exports the store data set. Takes an options
581                         //      object with a number of parameters
582                         //
583                         //      data: /* object */
584                         //              Defaults to the root of the store.
585                         //              The data to be exported.
586                         //      
587                         //      clone: /* boolean */
588                         //              clone the data set before returning it 
589                         //              or modifying it for export
590                         //
591                         //      cleanMeta: /* boolean */
592                         //              clean the meta data off of the data. Note
593                         //              that this will happen to the actual
594                         //              store data if !clone. If you want
595                         //              to continue using the store after
596                         //              this operation, it is probably better to export
597                         //              it as a clone if you want it cleaned.
598                         //
599                         //      suppressExportMeta: /* boolean */
600                         //              By default, when data is exported from the store
601                         //              some information, such as as a timestamp, is
602                         //              added to the root of exported data.  This
603                         //              prevents that from happening.  It is mainly used
604                         //              for making tests easier.
605                         //
606                         //      type: "raw" || "json"
607                         //              Defaults to 'json'. 'json' will convert the data into 
608                         //              json before returning it. 'raw' will just return a
609                         //              reference to the object  
610
611                         var options = options || {};
612                         var d=options.data || this._data;
613         
614                         if (!options.suppressExportMeta && options.clone){
615                                 data = dojo.clone(d);
616                                 if (data[this.metaLabel]){
617                                         data[this.metaLabel]["clone"]=true;
618                                 }
619                         }else{
620                                 var data=d;
621                         }
622
623                         if (!options.suppressExportMeta &&  data[this.metaLabel]){
624                                 data[this.metaLabel]["last_export"]=new Date().toString()
625                         }
626
627                         if(options.cleanMeta){
628                                 this.cleanMeta(data);
629                         }
630
631                         //console.log("Exporting: ", options, dojo.toJson(data));       
632                         switch(options.type){
633                                 case "raw":
634                                         return data;
635                                 case "json":
636                                 default:
637                                         return dojo.toJson(data);
638                         }
639                 },      
640
641                 getFeatures: function(){
642                         // summary:
643                         //      return the store feature set
644
645                         return { 
646                                 "dojo.data.api.Read": true,
647                                 "dojo.data.api.Identity": true,
648                                 "dojo.data.api.Write": true,
649                                 "dojo.data.api.Notification": true
650                         }
651                 },
652
653                 getLabel: function(item){
654                         // summary
655                         //      returns the label for an item. The label
656                         //      is created by setting the store's labelAttribute 
657                         //      property with either an attribute name  or an array
658                         //      of attribute names.  Developers can also
659                         //      provide the store with a createLabel function which
660                         //      will do the actaul work of creating the label.  If not
661                         //      the default will just concatenate any of the identified
662                         //      attributes together.
663                         item = this._correctReference(item);
664                         var label="";
665
666                         if (dojo.isFunction(this.createLabel)){
667                                 return this.createLabel(item);
668                         }
669
670                         if (this.labelAttribute){
671                                 if (dojo.isArray(this.labelAttribute))  {
672                                         for(var i=0; i<this.labelAttribute.length; i++){
673                                                 if (i>0) { label+=" ";}
674                                                 label += item[this.labelAttribute[i]];
675                                         }
676                                         return label;
677                                 }else{
678                                         return item[this.labelAttribute];
679                                 }
680                         }
681                         return item.toString();
682                 },
683
684                 getLabelAttributes: function(item){
685                         // summary:
686                         //      returns an array of attributes that are used to create the label of an item
687                         item = this._correctReference(item);
688                         return dojo.isArray(this.labelAttribute) ? this.labelAttribute : [this.labelAttribute];
689                 },
690
691                 sort: function(a,b){
692                         console.log("TODO::implement default sort algo");
693                 },
694
695                 //Identity API Support
696
697                 getIdentity: function(item){
698                         // summary
699                         //      returns the identity of an item or throws
700                         //      a not found error.
701
702                         if (this.isItem(item)){
703                                 return item[this.idAttribute];
704                         }
705                         throw new Error("Id not found for item");
706                 },
707
708                 getIdentityAttributes: function(item){
709                         // summary:
710                         //      returns the attributes which are used to make up the 
711                         //      identity of an item.  Basically returns this.idAttribute
712
713                         return [this.idAttribute];
714                 },
715
716                 fetchItemByIdentity: function(args){
717                         // summary: 
718                         //      fetch an item by its identity. This store also provides
719                         //      a much more finger friendly alias, 'byId' which does the
720                         //      same thing as this function.  If provided a string
721                         //      this call will be treated as a SYNC request and will 
722                         //      return the identified item immediatly.  Alternatively it
723                         //      takes a object as a set of keywordArgs:
724                         //      
725                         //      identity: /* string */
726                         //              the id of the item you want to retrieve
727                         //      
728                         //      mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE
729                         //              overrides the default store fetch mode
730                         //      
731                         //      onItem: /* function */
732                         //              Result call back.  Passed the fetched item.
733                         //
734                         //      onError: /* function */
735                         //              error callback. 
736                         var id; 
737                         if (dojo.isString(args)){
738                                 id = args;
739                                 args = {identity: id, mode: dojox.data.SYNC_MODE}
740                         }else{
741                                 if (args){
742                                         id = args["identity"];          
743                                 }
744                                 if (!args.mode){args.mode = this.mode}  
745                         }
746
747                         if (this.index && (this.index[id] || this.index["identity"])){
748                                 
749                                 if (args.mode==dojox.data.SYNC_MODE){
750                                         return this.index[id];
751                                 }
752
753                                 if (args.onItem){
754                                         args["onItem"].call(args.scope || dojo.global, this.index[id], args);
755                                 }
756
757                                 return args;
758                         }else{
759                                 if (args.mode==dojox.data.SYNC_MODE){
760                                         return false;
761                                 }
762                         }
763
764
765                         if(args.onError){
766                                 args["onItem"].call(args.scope || dojo.global, new Error("Item Not Found: " + id), args);
767                         }
768                         
769                         return args;
770                 },
771
772                 //Write API Support
773                 newItem: function(data, options){
774                         // summary:
775                         //      adds a new item to the store at the specified point.
776                         //      Takes two parameters, data, and options. 
777                         //
778                         //      data: /* object */
779                         //              The data to be added in as an item.  This could be a
780                         //              new javascript object, or it could be an item that 
781                         //              already exists in the store.  If it already exists in the 
782                         //              store, then this will be added as a reference.  
783                         //
784                         //      options: /* object */
785                         //
786                         //              item: /* item */
787                         //                      reference to an existing store item
788                         //
789                         //              attribute: /* string */
790                         //                      attribute to add the item at.  If this is
791                         //                      not provided, the item's id will be used as the
792                         //                      attribute name. If specified attribute is an
793                         //                      array, the new item will be push()d on to the
794                         //                      end of it.
795                         //              oldValue: /* old value of item[attribute]
796                         //              newValue: new value item[attribute]
797
798                         var meta={};
799
800                         //default parent to the store root;
801                         var pInfo ={item:this._data};
802
803                         if (options){
804                                 if (options.parent){
805                                         options.item = options.parent;
806                                 }
807
808                                 dojo.mixin(pInfo, options);
809                         }
810
811                         if (this.idAttribute && !data[this.idAttribute]){
812                                 if (this.requireId){throw new Error("requireId is enabled, new items must have an id defined to be added");}
813                                 if (this.autoIdentity){
814                                         var newId = this.autoIdPrefix + this._autoId++;
815                                         data[this.idAttribute]=newId;
816                                         meta["autoId"]=true;
817                                 }
818                         }       
819
820                         if (!pInfo && !pInfo.attribute && !this.idAttribute && !data[this.idAttribute]){
821                                 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");
822                         }
823
824                         //pInfo.parent = this._correctReference(pInfo.parent);
825                         //if there is no parent info supplied, default to the store root
826                         //and add to the pInfo.attribute or if that doestn' exist create an
827                         //attribute with the same name as the new items ID 
828                         if(!pInfo.attribute){pInfo.attribute = data[this.idAttribute]}
829
830                         pInfo.oldValue = this._trimItem(pInfo.item[pInfo.attribute]);
831                         if (dojo.isArray(pInfo.item[pInfo.attribute])){
832                                 this._setDirty(pInfo.item);
833                                 pInfo.item[pInfo.attribute].push(data);
834                         }else{
835                                 this._setDirty(pInfo.item);
836                                 pInfo.item[pInfo.attribute]=data;
837                         }
838
839                         pInfo.newValue = pInfo.item[pInfo.attribute];
840
841                         //add this item to the index
842                         if(data[this.idAttribute]){this.index[data[this.idAttribute]]=data}
843
844                         this._updateMeta(data, meta)
845
846                         //keep track of all references in the store so we can delete them as necessary
847                         this._addReference(data, pInfo);
848
849                         //mark this new item as dirty
850                         this._setDirty(data);
851
852                         //Notification API
853                         this.onNew(data, pInfo);
854
855                         //returns the original item, now decorated with some meta info
856                         return data;
857                 },
858
859                 _addReference: function(item, pInfo){
860                         // summary
861                         //      adds meta information to an item containing a reference id
862                         //      so that references can be deleted as necessary, when passed
863                         //      only a string, the string for parent info, it will only
864                         //      it will be treated as a string reference
865
866                         //console.log("_addReference: ", item, pInfo);  
867                         var rid = '_ref_' + this._referenceId++;
868                         if (!item[this.metaLabel]["referenceIds"]){
869                                 item[this.metaLabel]["referenceIds"]=[];
870                         }
871
872                         item[this.metaLabel]["referenceIds"].push(rid);
873                         this._references[rid] = pInfo;                          
874                 },
875
876                 deleteItem: function(item){     
877                         // summary
878                         //      deletes item and any references to that item from the store.
879                         //      If the desire is to delete only one reference, unsetAttribute or
880                         //      setValue is the way to go.
881
882                         item = this._correctReference(item);
883                         console.log("Item: ", item);
884                         if (this.isItem(item)){
885                                 while(item[this.metaLabel]["referenceIds"].length>0){
886                                         console.log("refs map: " , this._references);
887                                         console.log("item to delete: ", item);
888                                         var rid = item[this.metaLabel]["referenceIds"].pop();
889                                         var pInfo = this._references[rid];
890
891                                         console.log("deleteItem(): ", pInfo, pInfo.parent);
892                                         parentItem = pInfo.parent;
893                                         var attribute = pInfo.attribute;        
894                                         if(parentItem && parentItem[attribute] && !dojo.isArray(parentItem[attribute])){
895                                                 this._setDirty(parentItem);
896                                                 this.unsetAttribute(parentItem, attribute);
897                                                 delete parentItem[attribute];
898                                         }
899
900                                         if (dojo.isArray(parentItem[attribute])){
901                                                 console.log("Parent is array");
902                                                 var oldValue = this._trimItem(parentItem[attribute]);
903                                                 var found=false;
904                                                 for (var i=0; i<parentItem[attribute].length && !found;i++){
905                                                         if (parentItem[attribute][i][this.metaLabel]===item[this.metaLabel]){
906                                                                 found=true;     
907                                                         }                       
908                                                 }       
909
910                                                 if (found){
911                                                         this._setDirty(parentItem);
912                                                         var del =  parentItem[attribute].splice(i-1,1);
913                                                         delete del;
914                                                 }
915
916                                                 var newValue = this._trimItem(parentItem[attribute]);
917                                                 this.onSet(parentItem,attribute,oldValue,newValue);     
918                                         }
919                                         delete this._references[rid];
920
921                                 }
922                                 this.onDelete(item);            
923                                 delete item;
924                         }
925                 },
926
927                 _setDirty: function(item){
928                         // summary:
929                         //      adds an item to the list of dirty items.  This item
930                         //      contains a reference to the item itself as well as a
931                         //      cloned and trimmed version of old item for use with
932                         //      revert.
933
934                         //if an item is already in the list of dirty items, don't add it again
935                         //or it will overwrite the premodification data set.
936                         for (var i=0; i<this._dirtyItems.length; i++){
937                                 if (item[this.idAttribute]==this._dirtyItems[i][this.idAttribute]){
938                                         return; 
939                                 }       
940                         }
941
942                         this._dirtyItems.push({item: item, old: this._trimItem(item)});
943                         this._updateMeta(item, {isDirty: true});
944                 },
945
946                 setValue: function(item, attribute, value){
947                         // summary:
948                         //      sets 'attribute' on 'item' to 'value'
949                         item = this._correctReference(item);
950
951                         this._setDirty(item);
952                         var old = item[attribute] | undefined;
953                         item[attribute]=value;
954                         this.onSet(item,attribute,old,value);
955
956                 },
957
958                 setValues: function(item, attribute, values){
959                         // summary:
960                         //      sets 'attribute' on 'item' to 'value' value
961                         //      must be an array.
962
963
964                         item = this._correctReference(item);
965                         if (!dojo.isArray(values)){throw new Error("setValues expects to be passed an Array object as its value");}
966                         this._setDirty(item);
967                         var old = item[attribute] || null;
968                         item[attribute]=values
969                         this.onSet(item,attribute,old,values);
970                 },
971
972                 unsetAttribute: function(item, attribute){
973                         // summary:
974                         //      unsets 'attribute' on 'item'
975
976                         item = this._correctReference(item);
977                         this._setDirty(item);
978                         var old = item[attribute];
979                         delete item[attribute];
980                         this.onSet(item,attribute,old,null);
981                 },
982
983                 save: function(kwArgs){
984                         // summary:
985                         //      Takes an optional set of keyword Args with
986                         //      some save options.  Currently only format with options
987                         //      being "raw" or "json".  This function goes through
988                         //      the dirty item lists, clones and trims the item down so that
989                         //      the items children are not part of the data (the children are replaced
990                         //      with reference objects). This data is compiled into a single array, the dirty objects
991                         //      are all marked as clean, and the new data is then passed on to the onSave handler.
992
993                         var data = [];
994                 
995                         if (!kwArgs){kwArgs={}}
996                         while (this._dirtyItems.length > 0){
997                                 var item = this._dirtyItems.pop()["item"];
998                                 var t = this._trimItem(item);
999                                 var d;  
1000                                 switch(kwArgs.format){  
1001                                         case "json":
1002                                                 d = dojo.toJson(t);     
1003                                                 break;
1004                                         case "raw":
1005                                         default:
1006                                                 d = t;
1007                                 }
1008                                 data.push(d);
1009                                 this._markClean(item);
1010                         }
1011
1012                         this.onSave(data);
1013                 },
1014
1015                 _markClean: function(item){
1016                         // summary
1017                         //      remove this meta information marking an item as "dirty"
1018
1019                         if (item && item[this.metaLabel] && item[this.metaLabel]["isDirty"]){
1020                                 delete item[this.metaLabel]["isDirty"];
1021                         }       
1022                 },
1023
1024                 revert: function(){
1025                         // summary
1026                         //      returns any modified data to its original state prior to a save();
1027
1028                         while (this._dirtyItems.length>0){
1029                                 var d = this._dirtyItems.pop();
1030                                 this._mixin(d.item, d.old);
1031                         }
1032                         this.onRevert();
1033                 },
1034
1035                 _mixin: function(target, data){
1036                         // summary:
1037                         //      specialized mixin that hooks up objects in the store where references are identified.
1038
1039                         if (dojo.isObject(data)){
1040                                 if (dojo.isArray(data)){
1041                                         while(target.length>0){target.pop();}
1042                                         for (var i=0; i<data.length;i++){
1043                                                 if (dojo.isObject(data[i])){
1044                                                         if (dojo.isArray(data[i])){
1045                                                                 var mix=[];
1046                                                         }else{
1047                                                                 var mix={};
1048                                                                 if (data[i][this.metaLabel] && data[i][this.metaLabel]["type"] && data[i][this.metaLabel]["type"]=='reference'){
1049                                                                         target[i]=this.index[data[i][this.idAttribute]];
1050                                                                         continue;
1051                                                                 }
1052                                                         }
1053
1054                                                         this._mixin(mix, data[i]);
1055                                                         target.push(mix);
1056                                                 }else{
1057                                                         target.push(data[i]);
1058                                                 }
1059                                         }       
1060                                 }else{
1061                                         for (var i in target){
1062                                                 if (i in data){continue;}
1063                                                 delete target[i];
1064                                         }
1065
1066                                         for (var i in data){
1067                                                 if (dojo.isObject(data[i])){
1068                                                         if (dojo.isArray(data[i])){
1069                                                                 var mix=[];
1070                                                         }else{
1071                                                                 if (data[i][this.metaLabel] && data[i][this.metaLabel]["type"] && data[i][this.metaLabel]["type"]=='reference'){
1072                                                                         target[i]=this.index[data[i][this.idAttribute]];
1073                                                                         continue;
1074                                                                 }
1075
1076                                                                 var mix={};
1077                                                         }
1078                                                         this._mixin(mix, data[i]);
1079                                                         target[i]=mix;
1080                                                 }else{
1081                                                         target[i]=data[i];
1082                                                 }
1083                                         }       
1084
1085                                 }
1086                         }
1087                 },
1088
1089                 isDirty: function(item){
1090                         // summary
1091                         //      returns true if the item is marked as dirty.
1092
1093                         item = this._correctReference(item);
1094                         return item && item[this.metaLabel] && item[this.metaLabel]["isDirty"];
1095                 },
1096
1097                 _createReference: function(item){
1098                         // summary
1099                         //      Create a small reference object that can be used to replace
1100                         //      child objects during a trim
1101
1102                         var obj={};
1103                         obj[this.metaLabel]={
1104                                 type:'reference'
1105                         };
1106
1107                         obj[this.idAttribute]=item[this.idAttribute];
1108                         return obj;
1109                 },
1110
1111                 _trimItem: function(item){
1112                         //summary:
1113                         //      copy an item recursively stoppying at other items that have id's
1114                         //      and replace them with a refrence object;
1115                         var copy;
1116                         if (dojo.isArray(item)){
1117                                 copy = [];
1118                                 for (var i=0; i<item.length;i++){
1119                                         if (dojo.isArray(item[i])){
1120                                                 copy.push(this._trimItem(item[i]))
1121                                         }else if (dojo.isObject(item[i])){
1122                                                 if (item[i]["getFullYear"]){
1123                                                         copy.push(dojo.date.stamp.toISOString(item[i]));
1124                                                 }else if (item[i][this.idAttribute]){
1125                                                         copy.push(this._createReference(item[i]));
1126                                                 }else{
1127                                                         copy.push(this._trimItem(item[i]));     
1128                                                 }
1129                                         } else {
1130                                                 copy.push(item[i]);     
1131                                         }
1132                                 }
1133                                 return copy;
1134                         } 
1135
1136                         if (dojo.isObject(item)){
1137                                 copy = {};
1138
1139                                 for (var attr in item){
1140                                         if (!item[attr]){ copy[attr]=undefined;continue;}
1141                                         if (dojo.isArray(item[attr])){
1142                                                 copy[attr] = this._trimItem(item[attr]);
1143                                         }else if (dojo.isObject(item[attr])){
1144                                                 if (item[attr]["getFullYear"]){
1145                                                         copy[attr] =  dojo.date.stamp.toISOString(item[attr]);
1146                                                 }else if(item[attr][this.idAttribute]){
1147                                                         copy[attr]=this._createReference(item[attr]);
1148                                                 } else {
1149                                                         copy[attr]=this._trimItem(item[attr]);
1150                                                 }
1151                                         } else {
1152                                                 copy[attr]=item[attr];
1153                                         }
1154                                 }
1155                                 return copy;
1156                         }
1157                 },
1158
1159                 //Notifcation Support
1160
1161                 onSet: function(){
1162                 },
1163
1164                 onNew: function(){
1165
1166                 },
1167
1168                 onDelete: function(){
1169
1170                 },      
1171         
1172                 onSave: function(items){
1173                         // summary:
1174                         //      notification of the save event..not part of the notification api, 
1175                         //      but probably should be.
1176                         //console.log("onSave() ", items);
1177                 },
1178
1179                 onRevert: function(){
1180                         // summary:
1181                         //      notification of the revert event..not part of the notification api, 
1182                         //      but probably should be.
1183
1184                 }
1185         }
1186 );
1187
1188 //setup an alias to byId, is there a better way to do this?
1189 dojox.data.jsonPathStore.byId=dojox.data.jsonPathStore.fetchItemByIdentity;
1190
1191 }