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");
9 dojox.data.ASYNC_MODE = 0;
10 dojox.data.SYNC_MODE = 1;
12 dojo.declare("dojox.data.jsonPathStore",
15 mode: dojox.data.ASYNC_MODE,
17 hideMetaAttributes: false,
18 autoIdPrefix: "_auto_",
24 _replaceRegex: /\'\]/gi,
26 constructor: function(options){
28 // jsonPathStore constructor, instantiate a new jsonPathStore
30 // Takes a single optional parameter in the form of a Javascript object
31 // containing one or more of the following properties.
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
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.
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.
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.
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'.
73 // hideMetaAttributes: /* boolean */
74 // Defaults to False. When enabled, calls to getAttributes() will not
75 // include the meta attribute.
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
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.
92 //setup a byId alias to the api call
93 this.byId=this.fetchItemByIdentity;
96 dojo.mixin(this,options);
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);
111 //no data or url, start with an empty object for a store
112 if (!this.data && !this.url){
116 //we have data, but no url, set the store as the data
117 if (this.data && !this.url){
118 this.setData(this.data);
120 //remove the original refernce, we're now using _data from here on out
124 //given a url, load json data from as the store
129 load: dojo.hitch(this, "setData"),
135 _loadData: function(data){
137 // load data into the store. Index it if appropriate.
142 if (dojo.isString(data)){
143 this._data = dojo.fromJson(data);
148 if (this.indexOnLoad){
152 this._updateMeta(this._data, {path: "$"});
154 this.onLoadData(this._data);
157 onLoadData: function(data){
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.
163 while (this._fetchQueue.length>0){
164 var req = this._fetchQueue.shift();
170 setData: function(data){
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);
177 buildIndex: function(path, item){
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.
184 // path: /* string */
185 // jsonPath Query for the starting point of this index construction.
187 if (!this.idAttribute){
188 throw new Error("buildIndex requires idAttribute for the store");
191 item = item || this._data;
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"];
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]);
212 this.buildIndex(newPath,item);
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]);
224 _correctReference: function(item){
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.
231 if (this.index[item[this.idAttribute]][this.metaLabel]===item[this.metaLabel]){
232 return this.index[item[this.idAttribute]];
237 getValue: function(item, property){
239 // Gets the value of an item's 'property'
241 // item: /* object */
242 // property: /* string */
243 // property to look up value for
244 item = this._correctReference(item);
245 return item[property];
248 getValues: function(item, property){
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.
254 // item: /* object */
255 // property: /* string */
256 // property to look up value for
258 item = this._correctReference(item);
259 return dojo.isArray(item[property]) ? item[property] : [item[property]];
262 getAttributes: function(item){
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.
268 // item: /* object */
270 item = this._correctReference(item);
273 if (this.hideMetaAttributes && (i==this.metaLabel)){continue;}
279 hasAttribute: function(item,attribute){
281 // Checks to see if item has attribute
283 // item: /* object */
284 // attribute: /* string */
286 item = this._correctReference(item);
287 if (attribute in item){return true;}
291 containsValue: function(item, attribute, value){
293 // Checks to see if 'item' has 'value' at 'attribute'
295 // item: /* object */
296 // attribute: /* string */
297 // value: /* anything */
298 item = this._correctReference(item);
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}
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
313 if ((dojo.isObject(a) && !dojo.isObject(b))|| (dojo.isObject(b) && !dojo.isObject(a))) {
317 if ( a["getFullYear"] || b["getFullYear"] ){
318 //confirm that both are dates
319 if ( (a["getFullYear"] && !b["getFullYear"]) || (b["getFullYear"] && !a["getFullYear"]) ){
322 if (!dojo.date.compare(a,b)){
330 if (dojo.isObject(b[i])){
331 if (!a[i] || !dojo.isObject(a[i])){return false}
333 if (b[i]["getFullYear"]){
334 if(!a[i]["getFullYear"]){return false}
335 if (dojo.date.compare(a,b)){return false}
337 if (!this._shallowCompare(a[i],b[i])){return false}
340 if (!b[i] || (a[i]!=b[i])){return false}
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.
348 if (!b[i]){return false}
354 isItem: function(item){
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.
361 // item: /* object */
362 // attribute: /* string */
364 if (!dojo.isObject(item) || !item[this.metaLabel]){return false}
365 if (this.requireId && this._hasId && !item[this._id]){return false}
369 isItemLoaded: function(item){
371 // returns isItem() :)
373 // item: /* object */
375 item = this._correctReference(item);
376 return this.isItem(item);
379 loadItem: function(item){
381 // returns true. Future implementatins might alter this
385 _updateMeta: function(item, props){
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'
392 if (item && item[this.metaLabel]){
393 dojo.mixin(item[this.metaLabel], props);
397 item[this.metaLabel]=props;
400 cleanMeta: function(data, options){
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
407 data = data || this._data;
409 if (data[this.metaLabel]){
410 if(data[this.metaLabel]["autoId"]){
411 delete data[this.idAttribute];
413 delete data[this.metaLabel];
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]);
422 } else if (dojo.isObject(data)){
424 this.cleanMeta(data[i]);
429 fetch: function(args){
430 //console.log("fetch() ", args);
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.
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
448 // mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE
449 // Override the stores default mode.
451 // queryOptions: /* object */
452 // Options passed on to the underlying jsonPath query
456 // Starting item in result set
459 // Maximum number of items to return
461 // sort: /* function */
462 // Not Implemented yet
464 // The following only apply to ASYNC requests (the default)
466 // onBegin: /* function */
467 // called before any results are returned. Parameters
468 // will be the count and the original fetch request
470 // onItem: /*function*/
471 // called for each returned item. Parameters will be
472 // the item and the fetch request
474 // onComplete: /* function */
475 // called on completion of the request. Parameters will
476 // be the complete result set and the request
478 // onError: /* function */
479 // colled in the event of an error
481 // we're not started yet, add this request to a queue and wait till we do
483 this._fetchQueue.push(args);
486 if(dojo.isString(args)){
488 args={query: query, mode: dojox.data.SYNC_MODE};
493 if (!args || !args.query){
505 if (dojo.isObject(args.query)){
506 if (args.query.query){
507 query = args.query.query;
509 query = args.query = "$..*";
511 if (args.query.queryOptions){
512 args.queryOptions=args.query.queryOptions
518 if (!args.mode) {args.mode = this.mode;}
519 if (!args.queryOptions) {args.queryOptions={};}
521 args.queryOptions.resultType='BOTH';
522 var results = dojox.jsonPath.query(this._data, query, args.queryOptions);
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; }
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;}
534 //this automatically records the objects path
535 this._updateMeta(item,{path: results[i].path});
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;
544 //add item to the item index if appropriate
545 if(item[this.idAttribute]){this.index[item[this.idAttribute]]=item}
550 var scope = args.scope || dojo.global;
553 console.log("TODO::add support for sorting in the fetch");
556 if (args.mode==dojox.data.SYNC_MODE){
561 args["onBegin"].call(scope, results.length, args);
565 for (var i=0; i<results.length;i++){
566 args["onItem"].call(scope, results[i], args);
570 if (args.onComplete){
571 args["onComplete"].call(scope, results, args);
577 dump: function(options){
580 // exports the store data set. Takes an options
581 // object with a number of parameters
583 // data: /* object */
584 // Defaults to the root of the store.
585 // The data to be exported.
587 // clone: /* boolean */
588 // clone the data set before returning it
589 // or modifying it for export
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.
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.
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
611 var options = options || {};
612 var d=options.data || this._data;
614 if (!options.suppressExportMeta && options.clone){
615 data = dojo.clone(d);
616 if (data[this.metaLabel]){
617 data[this.metaLabel]["clone"]=true;
623 if (!options.suppressExportMeta && data[this.metaLabel]){
624 data[this.metaLabel]["last_export"]=new Date().toString()
627 if(options.cleanMeta){
628 this.cleanMeta(data);
631 //console.log("Exporting: ", options, dojo.toJson(data));
632 switch(options.type){
637 return dojo.toJson(data);
641 getFeatures: function(){
643 // return the store feature set
646 "dojo.data.api.Read": true,
647 "dojo.data.api.Identity": true,
648 "dojo.data.api.Write": true,
649 "dojo.data.api.Notification": true
653 getLabel: function(item){
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);
666 if (dojo.isFunction(this.createLabel)){
667 return this.createLabel(item);
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]];
678 return item[this.labelAttribute];
681 return item.toString();
684 getLabelAttributes: function(item){
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];
692 console.log("TODO::implement default sort algo");
695 //Identity API Support
697 getIdentity: function(item){
699 // returns the identity of an item or throws
700 // a not found error.
702 if (this.isItem(item)){
703 return item[this.idAttribute];
705 throw new Error("Id not found for item");
708 getIdentityAttributes: function(item){
710 // returns the attributes which are used to make up the
711 // identity of an item. Basically returns this.idAttribute
713 return [this.idAttribute];
716 fetchItemByIdentity: function(args){
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:
725 // identity: /* string */
726 // the id of the item you want to retrieve
728 // mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE
729 // overrides the default store fetch mode
731 // onItem: /* function */
732 // Result call back. Passed the fetched item.
734 // onError: /* function */
737 if (dojo.isString(args)){
739 args = {identity: id, mode: dojox.data.SYNC_MODE}
742 id = args["identity"];
744 if (!args.mode){args.mode = this.mode}
747 if (this.index && (this.index[id] || this.index["identity"])){
749 if (args.mode==dojox.data.SYNC_MODE){
750 return this.index[id];
754 args["onItem"].call(args.scope || dojo.global, this.index[id], args);
759 if (args.mode==dojox.data.SYNC_MODE){
766 args["onItem"].call(args.scope || dojo.global, new Error("Item Not Found: " + id), args);
773 newItem: function(data, options){
775 // adds a new item to the store at the specified point.
776 // Takes two parameters, data, and options.
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.
784 // options: /* object */
787 // reference to an existing store item
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
795 // oldValue: /* old value of item[attribute]
796 // newValue: new value item[attribute]
800 //default parent to the store root;
801 var pInfo ={item:this._data};
805 options.item = options.parent;
808 dojo.mixin(pInfo, options);
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;
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");
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]}
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);
835 this._setDirty(pInfo.item);
836 pInfo.item[pInfo.attribute]=data;
839 pInfo.newValue = pInfo.item[pInfo.attribute];
841 //add this item to the index
842 if(data[this.idAttribute]){this.index[data[this.idAttribute]]=data}
844 this._updateMeta(data, meta)
846 //keep track of all references in the store so we can delete them as necessary
847 this._addReference(data, pInfo);
849 //mark this new item as dirty
850 this._setDirty(data);
853 this.onNew(data, pInfo);
855 //returns the original item, now decorated with some meta info
859 _addReference: function(item, pInfo){
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
866 //console.log("_addReference: ", item, pInfo);
867 var rid = '_ref_' + this._referenceId++;
868 if (!item[this.metaLabel]["referenceIds"]){
869 item[this.metaLabel]["referenceIds"]=[];
872 item[this.metaLabel]["referenceIds"].push(rid);
873 this._references[rid] = pInfo;
876 deleteItem: function(item){
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.
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];
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];
900 if (dojo.isArray(parentItem[attribute])){
901 console.log("Parent is array");
902 var oldValue = this._trimItem(parentItem[attribute]);
904 for (var i=0; i<parentItem[attribute].length && !found;i++){
905 if (parentItem[attribute][i][this.metaLabel]===item[this.metaLabel]){
911 this._setDirty(parentItem);
912 var del = parentItem[attribute].splice(i-1,1);
916 var newValue = this._trimItem(parentItem[attribute]);
917 this.onSet(parentItem,attribute,oldValue,newValue);
919 delete this._references[rid];
927 _setDirty: function(item){
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
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]){
942 this._dirtyItems.push({item: item, old: this._trimItem(item)});
943 this._updateMeta(item, {isDirty: true});
946 setValue: function(item, attribute, value){
948 // sets 'attribute' on 'item' to 'value'
949 item = this._correctReference(item);
951 this._setDirty(item);
952 var old = item[attribute] | undefined;
953 item[attribute]=value;
954 this.onSet(item,attribute,old,value);
958 setValues: function(item, attribute, values){
960 // sets 'attribute' on 'item' to 'value' value
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);
972 unsetAttribute: function(item, attribute){
974 // unsets 'attribute' on 'item'
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);
983 save: function(kwArgs){
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.
995 if (!kwArgs){kwArgs={}}
996 while (this._dirtyItems.length > 0){
997 var item = this._dirtyItems.pop()["item"];
998 var t = this._trimItem(item);
1000 switch(kwArgs.format){
1009 this._markClean(item);
1015 _markClean: function(item){
1017 // remove this meta information marking an item as "dirty"
1019 if (item && item[this.metaLabel] && item[this.metaLabel]["isDirty"]){
1020 delete item[this.metaLabel]["isDirty"];
1026 // returns any modified data to its original state prior to a save();
1028 while (this._dirtyItems.length>0){
1029 var d = this._dirtyItems.pop();
1030 this._mixin(d.item, d.old);
1035 _mixin: function(target, data){
1037 // specialized mixin that hooks up objects in the store where references are identified.
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])){
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]];
1054 this._mixin(mix, data[i]);
1057 target.push(data[i]);
1061 for (var i in target){
1062 if (i in data){continue;}
1066 for (var i in data){
1067 if (dojo.isObject(data[i])){
1068 if (dojo.isArray(data[i])){
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]];
1078 this._mixin(mix, data[i]);
1089 isDirty: function(item){
1091 // returns true if the item is marked as dirty.
1093 item = this._correctReference(item);
1094 return item && item[this.metaLabel] && item[this.metaLabel]["isDirty"];
1097 _createReference: function(item){
1099 // Create a small reference object that can be used to replace
1100 // child objects during a trim
1103 obj[this.metaLabel]={
1107 obj[this.idAttribute]=item[this.idAttribute];
1111 _trimItem: function(item){
1113 // copy an item recursively stoppying at other items that have id's
1114 // and replace them with a refrence object;
1116 if (dojo.isArray(item)){
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]));
1127 copy.push(this._trimItem(item[i]));
1136 if (dojo.isObject(item)){
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]);
1149 copy[attr]=this._trimItem(item[attr]);
1152 copy[attr]=item[attr];
1159 //Notifcation Support
1168 onDelete: function(){
1172 onSave: function(items){
1174 // notification of the save event..not part of the notification api,
1175 // but probably should be.
1176 //console.log("onSave() ", items);
1179 onRevert: function(){
1181 // notification of the revert event..not part of the notification api,
1182 // but probably should be.
1188 //setup an alias to byId, is there a better way to do this?
1189 dojox.data.jsonPathStore.byId=dojox.data.jsonPathStore.fetchItemByIdentity;