diff options
author | Richard Beales <[email protected]> | 2013-03-18 07:32:01 +0000 |
---|---|---|
committer | Richard Beales <[email protected]> | 2013-03-18 07:32:01 +0000 |
commit | 7c97d17aaf373339a8bcd917ad59ca6018148f0d (patch) | |
tree | 5a3c04f0f9529be392c1263d3feb75806eb43797 /lib/dojo/data | |
parent | 70db7424e7068701e60cc5bcdfe8f858be508179 (diff) | |
parent | c670a80ddd9b03bd4ea6d940a9ed682fd26248d7 (diff) |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'lib/dojo/data')
24 files changed, 3714 insertions, 22 deletions
diff --git a/lib/dojo/data/ItemFileReadStore.js b/lib/dojo/data/ItemFileReadStore.js index b57daa2de..d393bcfe9 100644 --- a/lib/dojo/data/ItemFileReadStore.js +++ b/lib/dojo/data/ItemFileReadStore.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/ItemFileReadStore",["../_base/kernel","../_base/lang","../_base/declare","../_base/array","../_base/xhr","../Evented","../_base/window","./util/filter","./util/simpleFetch","../date/stamp"],function(_1,_2,_3,_4,_5,_6,_7,_8,_9,_a){var _b=_3("dojo.data.ItemFileReadStore",[_6],{constructor:function(_c){this._arrayOfAllItems=[];this._arrayOfTopLevelItems=[];this._loadFinished=false;this._jsonFileUrl=_c.url;this._ccUrl=_c.url;this.url=_c.url;this._jsonData=_c.data;this.data=null;this._datatypeMap=_c.typeMap||{};if(!this._datatypeMap["Date"]){this._datatypeMap["Date"]={type:Date,deserialize:function(_d){return _a.fromISOString(_d);}};}this._features={"dojo.data.api.Read":true,"dojo.data.api.Identity":true};this._itemsByIdentity=null;this._storeRefPropName="_S";this._itemNumPropName="_0";this._rootItemPropName="_RI";this._reverseRefMap="_RRM";this._loadInProgress=false;this._queuedFetches=[];if(_c.urlPreventCache!==undefined){this.urlPreventCache=_c.urlPreventCache?true:false;}if(_c.hierarchical!==undefined){this.hierarchical=_c.hierarchical?true:false;}if(_c.clearOnClose){this.clearOnClose=true;}if("failOk" in _c){this.failOk=_c.failOk?true:false;}},url:"",_ccUrl:"",data:null,typeMap:null,clearOnClose:false,urlPreventCache:false,failOk:false,hierarchical:true,_assertIsItem:function(_e){if(!this.isItem(_e)){throw new Error("dojo.data.ItemFileReadStore: Invalid item argument.");}},_assertIsAttribute:function(_f){if(typeof _f!=="string"){throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument.");}},getValue:function(_10,_11,_12){var _13=this.getValues(_10,_11);return (_13.length>0)?_13[0]:_12;},getValues:function(_14,_15){this._assertIsItem(_14);this._assertIsAttribute(_15);return (_14[_15]||[]).slice(0);},getAttributes:function(_16){this._assertIsItem(_16);var _17=[];for(var key in _16){if((key!==this._storeRefPropName)&&(key!==this._itemNumPropName)&&(key!==this._rootItemPropName)&&(key!==this._reverseRefMap)){_17.push(key);}}return _17;},hasAttribute:function(_18,_19){this._assertIsItem(_18);this._assertIsAttribute(_19);return (_19 in _18);},containsValue:function(_1a,_1b,_1c){var _1d=undefined;if(typeof _1c==="string"){_1d=_8.patternToRegExp(_1c,false);}return this._containsValue(_1a,_1b,_1c,_1d);},_containsValue:function(_1e,_1f,_20,_21){return _4.some(this.getValues(_1e,_1f),function(_22){if(_22!==null&&!_2.isObject(_22)&&_21){if(_22.toString().match(_21)){return true;}}else{if(_20===_22){return true;}}});},isItem:function(_23){if(_23&&_23[this._storeRefPropName]===this){if(this._arrayOfAllItems[_23[this._itemNumPropName]]===_23){return true;}}return false;},isItemLoaded:function(_24){return this.isItem(_24);},loadItem:function(_25){this._assertIsItem(_25.item);},getFeatures:function(){return this._features;},getLabel:function(_26){if(this._labelAttr&&this.isItem(_26)){return this.getValue(_26,this._labelAttr);}return undefined;},getLabelAttributes:function(_27){if(this._labelAttr){return [this._labelAttr];}return null;},_fetchItems:function(_28,_29,_2a){var _2b=this,_2c=function(_2d,_2e){var _2f=[],i,key;if(_2d.query){var _30,_31=_2d.queryOptions?_2d.queryOptions.ignoreCase:false;var _32={};for(key in _2d.query){_30=_2d.query[key];if(typeof _30==="string"){_32[key]=_8.patternToRegExp(_30,_31);}else{if(_30 instanceof RegExp){_32[key]=_30;}}}for(i=0;i<_2e.length;++i){var _33=true;var _34=_2e[i];if(_34===null){_33=false;}else{for(key in _2d.query){_30=_2d.query[key];if(!_2b._containsValue(_34,key,_30,_32[key])){_33=false;}}}if(_33){_2f.push(_34);}}_29(_2f,_2d);}else{for(i=0;i<_2e.length;++i){var _35=_2e[i];if(_35!==null){_2f.push(_35);}}_29(_2f,_2d);}};if(this._loadFinished){_2c(_28,this._getItemsArray(_28.queryOptions));}else{if(this._jsonFileUrl!==this._ccUrl){_1.deprecated("dojo.data.ItemFileReadStore: ","To change the url, set the url property of the store,"+" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");this._ccUrl=this._jsonFileUrl;this.url=this._jsonFileUrl;}else{if(this.url!==this._ccUrl){this._jsonFileUrl=this.url;this._ccUrl=this.url;}}if(this.data!=null){this._jsonData=this.data;this.data=null;}if(this._jsonFileUrl){if(this._loadInProgress){this._queuedFetches.push({args:_28,filter:_2c});}else{this._loadInProgress=true;var _36={url:_2b._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk};var _37=_5.get(_36);_37.addCallback(function(_38){try{_2b._getItemsFromLoadedData(_38);_2b._loadFinished=true;_2b._loadInProgress=false;_2c(_28,_2b._getItemsArray(_28.queryOptions));_2b._handleQueuedFetches();}catch(e){_2b._loadFinished=true;_2b._loadInProgress=false;_2a(e,_28);}});_37.addErrback(function(_39){_2b._loadInProgress=false;_2a(_39,_28);});var _3a=null;if(_28.abort){_3a=_28.abort;}_28.abort=function(){var df=_37;if(df&&df.fired===-1){df.cancel();df=null;}if(_3a){_3a.call(_28);}};}}else{if(this._jsonData){try{this._loadFinished=true;this._getItemsFromLoadedData(this._jsonData);this._jsonData=null;_2c(_28,this._getItemsArray(_28.queryOptions));}catch(e){_2a(e,_28);}}else{_2a(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."),_28);}}}},_handleQueuedFetches:function(){if(this._queuedFetches.length>0){for(var i=0;i<this._queuedFetches.length;i++){var _3b=this._queuedFetches[i],_3c=_3b.args,_3d=_3b.filter;if(_3d){_3d(_3c,this._getItemsArray(_3c.queryOptions));}else{this.fetchItemByIdentity(_3c);}}this._queuedFetches=[];}},_getItemsArray:function(_3e){if(_3e&&_3e.deep){return this._arrayOfAllItems;}return this._arrayOfTopLevelItems;},close:function(_3f){if(this.clearOnClose&&this._loadFinished&&!this._loadInProgress){if(((this._jsonFileUrl==""||this._jsonFileUrl==null)&&(this.url==""||this.url==null))&&this.data==null){}this._arrayOfAllItems=[];this._arrayOfTopLevelItems=[];this._loadFinished=false;this._itemsByIdentity=null;this._loadInProgress=false;this._queuedFetches=[];}},_getItemsFromLoadedData:function(_40){var _41=false,_42=this;function _43(_44){return (_44!==null)&&(typeof _44==="object")&&(!_2.isArray(_44)||_41)&&(!_2.isFunction(_44))&&(_44.constructor==Object||_2.isArray(_44))&&(typeof _44._reference==="undefined")&&(typeof _44._type==="undefined")&&(typeof _44._value==="undefined")&&_42.hierarchical;};function _45(_46){_42._arrayOfAllItems.push(_46);for(var _47 in _46){var _48=_46[_47];if(_48){if(_2.isArray(_48)){var _49=_48;for(var k=0;k<_49.length;++k){var _4a=_49[k];if(_43(_4a)){_45(_4a);}}}else{if(_43(_48)){_45(_48);}}}}};this._labelAttr=_40.label;var i,_4b;this._arrayOfAllItems=[];this._arrayOfTopLevelItems=_40.items;for(i=0;i<this._arrayOfTopLevelItems.length;++i){_4b=this._arrayOfTopLevelItems[i];if(_2.isArray(_4b)){_41=true;}_45(_4b);_4b[this._rootItemPropName]=true;}var _4c={},key;for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];for(key in _4b){if(key!==this._rootItemPropName){var _4d=_4b[key];if(_4d!==null){if(!_2.isArray(_4d)){_4b[key]=[_4d];}}else{_4b[key]=[null];}}_4c[key]=key;}}while(_4c[this._storeRefPropName]){this._storeRefPropName+="_";}while(_4c[this._itemNumPropName]){this._itemNumPropName+="_";}while(_4c[this._reverseRefMap]){this._reverseRefMap+="_";}var _4e;var _4f=_40.identifier;if(_4f){this._itemsByIdentity={};this._features["dojo.data.api.Identity"]=_4f;for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];_4e=_4b[_4f];var _50=_4e[0];if(!Object.hasOwnProperty.call(this._itemsByIdentity,_50)){this._itemsByIdentity[_50]=_4b;}else{if(this._jsonFileUrl){throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: ["+this._jsonFileUrl+"] is malformed. Items within the list have identifier: ["+_4f+"]. Value collided: ["+_50+"]");}else{if(this._jsonData){throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: ["+_4f+"]. Value collided: ["+_50+"]");}}}}}else{this._features["dojo.data.api.Identity"]=Number;}for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];_4b[this._storeRefPropName]=this;_4b[this._itemNumPropName]=i;}for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];for(key in _4b){_4e=_4b[key];for(var j=0;j<_4e.length;++j){_4d=_4e[j];if(_4d!==null&&typeof _4d=="object"){if(("_type" in _4d)&&("_value" in _4d)){var _51=_4d._type;var _52=this._datatypeMap[_51];if(!_52){throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '"+_51+"'");}else{if(_2.isFunction(_52)){_4e[j]=new _52(_4d._value);}else{if(_2.isFunction(_52.deserialize)){_4e[j]=_52.deserialize(_4d._value);}else{throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");}}}}if(_4d._reference){var _53=_4d._reference;if(!_2.isObject(_53)){_4e[j]=this._getItemByIdentity(_53);}else{for(var k=0;k<this._arrayOfAllItems.length;++k){var _54=this._arrayOfAllItems[k],_55=true;for(var _56 in _53){if(_54[_56]!=_53[_56]){_55=false;}}if(_55){_4e[j]=_54;}}}if(this.referenceIntegrity){var _57=_4e[j];if(this.isItem(_57)){this._addReferenceToMap(_57,_4b,key);}}}else{if(this.isItem(_4d)){if(this.referenceIntegrity){this._addReferenceToMap(_4d,_4b,key);}}}}}}}},_addReferenceToMap:function(_58,_59,_5a){},getIdentity:function(_5b){var _5c=this._features["dojo.data.api.Identity"];if(_5c===Number){return _5b[this._itemNumPropName];}else{var _5d=_5b[_5c];if(_5d){return _5d[0];}}return null;},fetchItemByIdentity:function(_5e){var _5f,_60;if(!this._loadFinished){var _61=this;if(this._jsonFileUrl!==this._ccUrl){_1.deprecated("dojo.data.ItemFileReadStore: ","To change the url, set the url property of the store,"+" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");this._ccUrl=this._jsonFileUrl;this.url=this._jsonFileUrl;}else{if(this.url!==this._ccUrl){this._jsonFileUrl=this.url;this._ccUrl=this.url;}}if(this.data!=null&&this._jsonData==null){this._jsonData=this.data;this.data=null;}if(this._jsonFileUrl){if(this._loadInProgress){this._queuedFetches.push({args:_5e});}else{this._loadInProgress=true;var _62={url:_61._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk};var _63=_5.get(_62);_63.addCallback(function(_64){var _65=_5e.scope?_5e.scope:_7.global;try{_61._getItemsFromLoadedData(_64);_61._loadFinished=true;_61._loadInProgress=false;_5f=_61._getItemByIdentity(_5e.identity);if(_5e.onItem){_5e.onItem.call(_65,_5f);}_61._handleQueuedFetches();}catch(error){_61._loadInProgress=false;if(_5e.onError){_5e.onError.call(_65,error);}}});_63.addErrback(function(_66){_61._loadInProgress=false;if(_5e.onError){var _67=_5e.scope?_5e.scope:_7.global;_5e.onError.call(_67,_66);}});}}else{if(this._jsonData){_61._getItemsFromLoadedData(_61._jsonData);_61._jsonData=null;_61._loadFinished=true;_5f=_61._getItemByIdentity(_5e.identity);if(_5e.onItem){_60=_5e.scope?_5e.scope:_7.global;_5e.onItem.call(_60,_5f);}}}}else{_5f=this._getItemByIdentity(_5e.identity);if(_5e.onItem){_60=_5e.scope?_5e.scope:_7.global;_5e.onItem.call(_60,_5f);}}},_getItemByIdentity:function(_68){var _69=null;if(this._itemsByIdentity){if(Object.hasOwnProperty.call(this._itemsByIdentity,_68)){_69=this._itemsByIdentity[_68];}}else{if(Object.hasOwnProperty.call(this._arrayOfAllItems,_68)){_69=this._arrayOfAllItems[_68];}}if(_69===undefined){_69=null;}return _69;},getIdentityAttributes:function(_6a){var _6b=this._features["dojo.data.api.Identity"];if(_6b===Number){return null;}else{return [_6b];}},_forceLoad:function(){var _6c=this;if(this._jsonFileUrl!==this._ccUrl){_1.deprecated("dojo.data.ItemFileReadStore: ","To change the url, set the url property of the store,"+" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");this._ccUrl=this._jsonFileUrl;this.url=this._jsonFileUrl;}else{if(this.url!==this._ccUrl){this._jsonFileUrl=this.url;this._ccUrl=this.url;}}if(this.data!=null){this._jsonData=this.data;this.data=null;}if(this._jsonFileUrl){var _6d={url:this._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk,sync:true};var _6e=_5.get(_6d);_6e.addCallback(function(_6f){try{if(_6c._loadInProgress!==true&&!_6c._loadFinished){_6c._getItemsFromLoadedData(_6f);_6c._loadFinished=true;}else{if(_6c._loadInProgress){throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress.");}}}catch(e){throw e;}});_6e.addErrback(function(_70){throw _70;});}else{if(this._jsonData){_6c._getItemsFromLoadedData(_6c._jsonData);_6c._jsonData=null;_6c._loadFinished=true;}}}});_2.extend(_b,_9);return _b;});
\ No newline at end of file +define("dojo/data/ItemFileReadStore",["../_base/kernel","../_base/lang","../_base/declare","../_base/array","../_base/xhr","../Evented","./util/filter","./util/simpleFetch","../date/stamp"],function(_1,_2,_3,_4,_5,_6,_7,_8,_9){var _a=_3("dojo.data.ItemFileReadStore",[_6],{constructor:function(_b){this._arrayOfAllItems=[];this._arrayOfTopLevelItems=[];this._loadFinished=false;this._jsonFileUrl=_b.url;this._ccUrl=_b.url;this.url=_b.url;this._jsonData=_b.data;this.data=null;this._datatypeMap=_b.typeMap||{};if(!this._datatypeMap["Date"]){this._datatypeMap["Date"]={type:Date,deserialize:function(_c){return _9.fromISOString(_c);}};}this._features={"dojo.data.api.Read":true,"dojo.data.api.Identity":true};this._itemsByIdentity=null;this._storeRefPropName="_S";this._itemNumPropName="_0";this._rootItemPropName="_RI";this._reverseRefMap="_RRM";this._loadInProgress=false;this._queuedFetches=[];if(_b.urlPreventCache!==undefined){this.urlPreventCache=_b.urlPreventCache?true:false;}if(_b.hierarchical!==undefined){this.hierarchical=_b.hierarchical?true:false;}if(_b.clearOnClose){this.clearOnClose=true;}if("failOk" in _b){this.failOk=_b.failOk?true:false;}},url:"",_ccUrl:"",data:null,typeMap:null,clearOnClose:false,urlPreventCache:false,failOk:false,hierarchical:true,_assertIsItem:function(_d){if(!this.isItem(_d)){throw new Error(this.declaredClass+": Invalid item argument.");}},_assertIsAttribute:function(_e){if(typeof _e!=="string"){throw new Error(this.declaredClass+": Invalid attribute argument.");}},getValue:function(_f,_10,_11){var _12=this.getValues(_f,_10);return (_12.length>0)?_12[0]:_11;},getValues:function(_13,_14){this._assertIsItem(_13);this._assertIsAttribute(_14);return (_13[_14]||[]).slice(0);},getAttributes:function(_15){this._assertIsItem(_15);var _16=[];for(var key in _15){if((key!==this._storeRefPropName)&&(key!==this._itemNumPropName)&&(key!==this._rootItemPropName)&&(key!==this._reverseRefMap)){_16.push(key);}}return _16;},hasAttribute:function(_17,_18){this._assertIsItem(_17);this._assertIsAttribute(_18);return (_18 in _17);},containsValue:function(_19,_1a,_1b){var _1c=undefined;if(typeof _1b==="string"){_1c=_7.patternToRegExp(_1b,false);}return this._containsValue(_19,_1a,_1b,_1c);},_containsValue:function(_1d,_1e,_1f,_20){return _4.some(this.getValues(_1d,_1e),function(_21){if(_21!==null&&!_2.isObject(_21)&&_20){if(_21.toString().match(_20)){return true;}}else{if(_1f===_21){return true;}}});},isItem:function(_22){if(_22&&_22[this._storeRefPropName]===this){if(this._arrayOfAllItems[_22[this._itemNumPropName]]===_22){return true;}}return false;},isItemLoaded:function(_23){return this.isItem(_23);},loadItem:function(_24){this._assertIsItem(_24.item);},getFeatures:function(){return this._features;},getLabel:function(_25){if(this._labelAttr&&this.isItem(_25)){return this.getValue(_25,this._labelAttr);}return undefined;},getLabelAttributes:function(_26){if(this._labelAttr){return [this._labelAttr];}return null;},filter:function(_27,_28,_29){var _2a=[],i,key;if(_27.query){var _2b,_2c=_27.queryOptions?_27.queryOptions.ignoreCase:false;var _2d={};for(key in _27.query){_2b=_27.query[key];if(typeof _2b==="string"){_2d[key]=_7.patternToRegExp(_2b,_2c);}else{if(_2b instanceof RegExp){_2d[key]=_2b;}}}for(i=0;i<_28.length;++i){var _2e=true;var _2f=_28[i];if(_2f===null){_2e=false;}else{for(key in _27.query){_2b=_27.query[key];if(!this._containsValue(_2f,key,_2b,_2d[key])){_2e=false;}}}if(_2e){_2a.push(_2f);}}_29(_2a,_27);}else{for(i=0;i<_28.length;++i){var _30=_28[i];if(_30!==null){_2a.push(_30);}}_29(_2a,_27);}},_fetchItems:function(_31,_32,_33){var _34=this;if(this._loadFinished){this.filter(_31,this._getItemsArray(_31.queryOptions),_32);}else{if(this._jsonFileUrl!==this._ccUrl){_1.deprecated(this.declaredClass+": ","To change the url, set the url property of the store,"+" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");this._ccUrl=this._jsonFileUrl;this.url=this._jsonFileUrl;}else{if(this.url!==this._ccUrl){this._jsonFileUrl=this.url;this._ccUrl=this.url;}}if(this.data!=null){this._jsonData=this.data;this.data=null;}if(this._jsonFileUrl){if(this._loadInProgress){this._queuedFetches.push({args:_31,filter:_2.hitch(_34,"filter"),findCallback:_2.hitch(_34,_32)});}else{this._loadInProgress=true;var _35={url:_34._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk};var _36=_5.get(_35);_36.addCallback(function(_37){try{_34._getItemsFromLoadedData(_37);_34._loadFinished=true;_34._loadInProgress=false;_34.filter(_31,_34._getItemsArray(_31.queryOptions),_32);_34._handleQueuedFetches();}catch(e){_34._loadFinished=true;_34._loadInProgress=false;_33(e,_31);}});_36.addErrback(function(_38){_34._loadInProgress=false;_33(_38,_31);});var _39=null;if(_31.abort){_39=_31.abort;}_31.abort=function(){var df=_36;if(df&&df.fired===-1){df.cancel();df=null;}if(_39){_39.call(_31);}};}}else{if(this._jsonData){try{this._loadFinished=true;this._getItemsFromLoadedData(this._jsonData);this._jsonData=null;_34.filter(_31,this._getItemsArray(_31.queryOptions),_32);}catch(e){_33(e,_31);}}else{_33(new Error(this.declaredClass+": No JSON source data was provided as either URL or a nested Javascript object."),_31);}}}},_handleQueuedFetches:function(){if(this._queuedFetches.length>0){for(var i=0;i<this._queuedFetches.length;i++){var _3a=this._queuedFetches[i],_3b=_3a.args,_3c=_3a.filter,_3d=_3a.findCallback;if(_3c){_3c(_3b,this._getItemsArray(_3b.queryOptions),_3d);}else{this.fetchItemByIdentity(_3b);}}this._queuedFetches=[];}},_getItemsArray:function(_3e){if(_3e&&_3e.deep){return this._arrayOfAllItems;}return this._arrayOfTopLevelItems;},close:function(_3f){if(this.clearOnClose&&this._loadFinished&&!this._loadInProgress){if(((this._jsonFileUrl==""||this._jsonFileUrl==null)&&(this.url==""||this.url==null))&&this.data==null){}this._arrayOfAllItems=[];this._arrayOfTopLevelItems=[];this._loadFinished=false;this._itemsByIdentity=null;this._loadInProgress=false;this._queuedFetches=[];}},_getItemsFromLoadedData:function(_40){var _41=false,_42=this;function _43(_44){return (_44!==null)&&(typeof _44==="object")&&(!_2.isArray(_44)||_41)&&(!_2.isFunction(_44))&&(_44.constructor==Object||_2.isArray(_44))&&(typeof _44._reference==="undefined")&&(typeof _44._type==="undefined")&&(typeof _44._value==="undefined")&&_42.hierarchical;};function _45(_46){_42._arrayOfAllItems.push(_46);for(var _47 in _46){var _48=_46[_47];if(_48){if(_2.isArray(_48)){var _49=_48;for(var k=0;k<_49.length;++k){var _4a=_49[k];if(_43(_4a)){_45(_4a);}}}else{if(_43(_48)){_45(_48);}}}}};this._labelAttr=_40.label;var i,_4b;this._arrayOfAllItems=[];this._arrayOfTopLevelItems=_40.items;for(i=0;i<this._arrayOfTopLevelItems.length;++i){_4b=this._arrayOfTopLevelItems[i];if(_2.isArray(_4b)){_41=true;}_45(_4b);_4b[this._rootItemPropName]=true;}var _4c={},key;for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];for(key in _4b){if(key!==this._rootItemPropName){var _4d=_4b[key];if(_4d!==null){if(!_2.isArray(_4d)){_4b[key]=[_4d];}}else{_4b[key]=[null];}}_4c[key]=key;}}while(_4c[this._storeRefPropName]){this._storeRefPropName+="_";}while(_4c[this._itemNumPropName]){this._itemNumPropName+="_";}while(_4c[this._reverseRefMap]){this._reverseRefMap+="_";}var _4e;var _4f=_40.identifier;if(_4f){this._itemsByIdentity={};this._features["dojo.data.api.Identity"]=_4f;for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];_4e=_4b[_4f];var _50=_4e[0];if(!Object.hasOwnProperty.call(this._itemsByIdentity,_50)){this._itemsByIdentity[_50]=_4b;}else{if(this._jsonFileUrl){throw new Error(this.declaredClass+": The json data as specified by: ["+this._jsonFileUrl+"] is malformed. Items within the list have identifier: ["+_4f+"]. Value collided: ["+_50+"]");}else{if(this._jsonData){throw new Error(this.declaredClass+": The json data provided by the creation arguments is malformed. Items within the list have identifier: ["+_4f+"]. Value collided: ["+_50+"]");}}}}}else{this._features["dojo.data.api.Identity"]=Number;}for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];_4b[this._storeRefPropName]=this;_4b[this._itemNumPropName]=i;}for(i=0;i<this._arrayOfAllItems.length;++i){_4b=this._arrayOfAllItems[i];for(key in _4b){_4e=_4b[key];for(var j=0;j<_4e.length;++j){_4d=_4e[j];if(_4d!==null&&typeof _4d=="object"){if(("_type" in _4d)&&("_value" in _4d)){var _51=_4d._type;var _52=this._datatypeMap[_51];if(!_52){throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '"+_51+"'");}else{if(_2.isFunction(_52)){_4e[j]=new _52(_4d._value);}else{if(_2.isFunction(_52.deserialize)){_4e[j]=_52.deserialize(_4d._value);}else{throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");}}}}if(_4d._reference){var _53=_4d._reference;if(!_2.isObject(_53)){_4e[j]=this._getItemByIdentity(_53);}else{for(var k=0;k<this._arrayOfAllItems.length;++k){var _54=this._arrayOfAllItems[k],_55=true;for(var _56 in _53){if(_54[_56]!=_53[_56]){_55=false;}}if(_55){_4e[j]=_54;}}}if(this.referenceIntegrity){var _57=_4e[j];if(this.isItem(_57)){this._addReferenceToMap(_57,_4b,key);}}}else{if(this.isItem(_4d)){if(this.referenceIntegrity){this._addReferenceToMap(_4d,_4b,key);}}}}}}}},_addReferenceToMap:function(_58,_59,_5a){},getIdentity:function(_5b){var _5c=this._features["dojo.data.api.Identity"];if(_5c===Number){return _5b[this._itemNumPropName];}else{var _5d=_5b[_5c];if(_5d){return _5d[0];}}return null;},fetchItemByIdentity:function(_5e){var _5f,_60;if(!this._loadFinished){var _61=this;if(this._jsonFileUrl!==this._ccUrl){_1.deprecated(this.declaredClass+": ","To change the url, set the url property of the store,"+" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");this._ccUrl=this._jsonFileUrl;this.url=this._jsonFileUrl;}else{if(this.url!==this._ccUrl){this._jsonFileUrl=this.url;this._ccUrl=this.url;}}if(this.data!=null&&this._jsonData==null){this._jsonData=this.data;this.data=null;}if(this._jsonFileUrl){if(this._loadInProgress){this._queuedFetches.push({args:_5e});}else{this._loadInProgress=true;var _62={url:_61._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk};var _63=_5.get(_62);_63.addCallback(function(_64){var _65=_5e.scope?_5e.scope:_1.global;try{_61._getItemsFromLoadedData(_64);_61._loadFinished=true;_61._loadInProgress=false;_5f=_61._getItemByIdentity(_5e.identity);if(_5e.onItem){_5e.onItem.call(_65,_5f);}_61._handleQueuedFetches();}catch(error){_61._loadInProgress=false;if(_5e.onError){_5e.onError.call(_65,error);}}});_63.addErrback(function(_66){_61._loadInProgress=false;if(_5e.onError){var _67=_5e.scope?_5e.scope:_1.global;_5e.onError.call(_67,_66);}});}}else{if(this._jsonData){_61._getItemsFromLoadedData(_61._jsonData);_61._jsonData=null;_61._loadFinished=true;_5f=_61._getItemByIdentity(_5e.identity);if(_5e.onItem){_60=_5e.scope?_5e.scope:_1.global;_5e.onItem.call(_60,_5f);}}}}else{_5f=this._getItemByIdentity(_5e.identity);if(_5e.onItem){_60=_5e.scope?_5e.scope:_1.global;_5e.onItem.call(_60,_5f);}}},_getItemByIdentity:function(_68){var _69=null;if(this._itemsByIdentity){if(Object.hasOwnProperty.call(this._itemsByIdentity,_68)){_69=this._itemsByIdentity[_68];}}else{if(Object.hasOwnProperty.call(this._arrayOfAllItems,_68)){_69=this._arrayOfAllItems[_68];}}if(_69===undefined){_69=null;}return _69;},getIdentityAttributes:function(_6a){var _6b=this._features["dojo.data.api.Identity"];if(_6b===Number){return null;}else{return [_6b];}},_forceLoad:function(){var _6c=this;if(this._jsonFileUrl!==this._ccUrl){_1.deprecated(this.declaredClass+": ","To change the url, set the url property of the store,"+" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");this._ccUrl=this._jsonFileUrl;this.url=this._jsonFileUrl;}else{if(this.url!==this._ccUrl){this._jsonFileUrl=this.url;this._ccUrl=this.url;}}if(this.data!=null){this._jsonData=this.data;this.data=null;}if(this._jsonFileUrl){var _6d={url:this._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk,sync:true};var _6e=_5.get(_6d);_6e.addCallback(function(_6f){try{if(_6c._loadInProgress!==true&&!_6c._loadFinished){_6c._getItemsFromLoadedData(_6f);_6c._loadFinished=true;}else{if(_6c._loadInProgress){throw new Error(this.declaredClass+": Unable to perform a synchronous load, an async load is in progress.");}}}catch(e){throw e;}});_6e.addErrback(function(_70){throw _70;});}else{if(this._jsonData){_6c._getItemsFromLoadedData(_6c._jsonData);_6c._jsonData=null;_6c._loadFinished=true;}}}});_2.extend(_a,_8);return _a;});
\ No newline at end of file diff --git a/lib/dojo/data/ItemFileReadStore.js.uncompressed.js b/lib/dojo/data/ItemFileReadStore.js.uncompressed.js new file mode 100644 index 000000000..ccffaf7cc --- /dev/null +++ b/lib/dojo/data/ItemFileReadStore.js.uncompressed.js @@ -0,0 +1,951 @@ +define("dojo/data/ItemFileReadStore", ["../_base/kernel", "../_base/lang", "../_base/declare", "../_base/array", "../_base/xhr", + "../Evented", "./util/filter", "./util/simpleFetch", "../date/stamp" +], function(kernel, lang, declare, array, xhr, Evented, filterUtil, simpleFetch, dateStamp){ + +// module: +// dojo/data/ItemFileReadStore + +var ItemFileReadStore = declare("dojo.data.ItemFileReadStore", [Evented],{ + // summary: + // The ItemFileReadStore implements the dojo/data/api/Read API and reads + // data from JSON files that have contents in this format -- + // | { items: [ + // | { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, + // | { name:'Fozzie Bear', wears:['hat', 'tie']}, + // | { name:'Miss Piggy', pets:'Foo-Foo'} + // | ]} + // Note that it can also contain an 'identifier' property that specified which attribute on the items + // in the array of items that acts as the unique identifier for that item. + + constructor: function(/* Object */ keywordParameters){ + // summary: + // constructor + // keywordParameters: + // {url: String} {data: jsonObject} {typeMap: object} + // The structure of the typeMap object is as follows: + // | { + // | type0: function || object, + // | type1: function || object, + // | ... + // | typeN: function || object + // | } + // Where if it is a function, it is assumed to be an object constructor that takes the + // value of _value as the initialization parameters. If it is an object, then it is assumed + // to be an object of general form: + // | { + // | type: function, //constructor. + // | deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. + // | } + + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = []; + this._loadFinished = false; + this._jsonFileUrl = keywordParameters.url; + this._ccUrl = keywordParameters.url; + this.url = keywordParameters.url; + this._jsonData = keywordParameters.data; + this.data = null; + this._datatypeMap = keywordParameters.typeMap || {}; + if(!this._datatypeMap['Date']){ + //If no default mapping for dates, then set this as default. + //We use the dojo/date/stamp here because the ISO format is the 'dojo way' + //of generically representing dates. + this._datatypeMap['Date'] = { + type: Date, + deserialize: function(value){ + return dateStamp.fromISOString(value); + } + }; + } + this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}; + this._itemsByIdentity = null; + this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item. + this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item. + this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item. + this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity + this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset. + this._queuedFetches = []; + if(keywordParameters.urlPreventCache !== undefined){ + this.urlPreventCache = keywordParameters.urlPreventCache?true:false; + } + if(keywordParameters.hierarchical !== undefined){ + this.hierarchical = keywordParameters.hierarchical?true:false; + } + if(keywordParameters.clearOnClose){ + this.clearOnClose = true; + } + if("failOk" in keywordParameters){ + this.failOk = keywordParameters.failOk?true:false; + } + }, + + url: "", // use "" rather than undefined for the benefit of the parser (#3539) + + //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload + //when clearOnClose and close is used. + _ccUrl: "", + + data: null, // define this so that the parser can populate it + + typeMap: null, //Define so parser can populate. + + // clearOnClose: Boolean + // Parameter to allow users to specify if a close call should force a reload or not. + // By default, it retains the old behavior of not clearing if close is called. But + // if set true, the store will be reset to default state. Note that by doing this, + // all item handles will become invalid and a new fetch must be issued. + clearOnClose: false, + + // urlPreventCache: Boolean + // Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url. + // Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option. + // Added for tracker: #6072 + urlPreventCache: false, + + // failOk: Boolean + // Parameter for specifying that it is OK for the xhrGet call to fail silently. + failOk: false, + + // hierarchical: Boolean + // Parameter to indicate to process data from the url as hierarchical + // (data items can contain other data items in js form). Default is true + // for backwards compatibility. False means only root items are processed + // as items, all child objects outside of type-mapped objects and those in + // specific reference format, are left straight JS data objects. + hierarchical: true, + + _assertIsItem: function(/* dojo/data/api/Item */ item){ + // summary: + // This function tests whether the item passed in is indeed an item in the store. + // item: + // The item to test for being contained by the store. + if(!this.isItem(item)){ + throw new Error(this.declaredClass + ": Invalid item argument."); + } + }, + + _assertIsAttribute: function(/* attribute-name-string */ attribute){ + // summary: + // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. + // attribute: + // The attribute to test for being contained by the store. + if(typeof attribute !== "string"){ + throw new Error(this.declaredClass + ": Invalid attribute argument."); + } + }, + + getValue: function( /* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute, + /* value? */ defaultValue){ + // summary: + // See dojo/data/api/Read.getValue() + var values = this.getValues(item, attribute); + return (values.length > 0)?values[0]:defaultValue; // mixed + }, + + getValues: function(/* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute){ + // summary: + // See dojo/data/api/Read.getValues() + + this._assertIsItem(item); + this._assertIsAttribute(attribute); + // Clone it before returning. refs: #10474 + return (item[attribute] || []).slice(0); // Array + }, + + getAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Read.getAttributes() + this._assertIsItem(item); + var attributes = []; + for(var key in item){ + // Save off only the real item attributes, not the special id marks for O(1) isItem. + if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){ + attributes.push(key); + } + } + return attributes; // Array + }, + + hasAttribute: function( /* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute){ + // summary: + // See dojo/data/api/Read.hasAttribute() + this._assertIsItem(item); + this._assertIsAttribute(attribute); + return (attribute in item); + }, + + containsValue: function(/* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute, + /* anything */ value){ + // summary: + // See dojo/data/api/Read.containsValue() + var regexp = undefined; + if(typeof value === "string"){ + regexp = filterUtil.patternToRegExp(value, false); + } + return this._containsValue(item, attribute, value, regexp); //boolean. + }, + + _containsValue: function( /* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute, + /* anything */ value, + /* RegExp?*/ regexp){ + // summary: + // Internal function for looking at the values contained by the item. + // description: + // Internal function for looking at the values contained by the item. This + // function allows for denoting if the comparison should be case sensitive for + // strings or not (for handling filtering cases where string case should not matter) + // item: + // The data item to examine for attribute values. + // attribute: + // The attribute to inspect. + // value: + // The value to match. + // regexp: + // Optional regular expression generated off value if value was of string type to handle wildcarding. + // If present and attribute values are string, then it can be used for comparison instead of 'value' + return array.some(this.getValues(item, attribute), function(possibleValue){ + if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){ + if(possibleValue.toString().match(regexp)){ + return true; // Boolean + } + }else if(value === possibleValue){ + return true; // Boolean + } + }); + }, + + isItem: function(/* anything */ something){ + // summary: + // See dojo/data/api/Read.isItem() + if(something && something[this._storeRefPropName] === this){ + if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){ + return true; + } + } + return false; // Boolean + }, + + isItemLoaded: function(/* anything */ something){ + // summary: + // See dojo/data/api/Read.isItemLoaded() + return this.isItem(something); //boolean + }, + + loadItem: function(/* object */ keywordArgs){ + // summary: + // See dojo/data/api/Read.loadItem() + this._assertIsItem(keywordArgs.item); + }, + + getFeatures: function(){ + // summary: + // See dojo/data/api/Read.getFeatures() + return this._features; //Object + }, + + getLabel: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Read.getLabel() + if(this._labelAttr && this.isItem(item)){ + return this.getValue(item,this._labelAttr); //String + } + return undefined; //undefined + }, + + getLabelAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Read.getLabelAttributes() + if(this._labelAttr){ + return [this._labelAttr]; //array + } + return null; //null + }, + + filter: function(/* Object */ requestArgs, /* item[] */ arrayOfItems, /* Function */ findCallback){ + // summary: + // This method handles the basic filtering needs for ItemFile* based stores. + var items = [], + i, key; + + if(requestArgs.query){ + var value, + ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; + + //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the + //same value for each item examined. Much more efficient. + var regexpList = {}; + for(key in requestArgs.query){ + value = requestArgs.query[key]; + if(typeof value === "string"){ + regexpList[key] = filterUtil.patternToRegExp(value, ignoreCase); + }else if(value instanceof RegExp){ + regexpList[key] = value; + } + } + for(i = 0; i < arrayOfItems.length; ++i){ + var match = true; + var candidateItem = arrayOfItems[i]; + if(candidateItem === null){ + match = false; + }else{ + for(key in requestArgs.query){ + value = requestArgs.query[key]; + if(!this._containsValue(candidateItem, key, value, regexpList[key])){ + match = false; + } + } + } + if(match){ + items.push(candidateItem); + } + } + findCallback(items, requestArgs); + }else{ + // We want a copy to pass back in case the parent wishes to sort the array. + // We shouldn't allow resort of the internal list, so that multiple callers + // can get lists and sort without affecting each other. We also need to + // filter out any null values that have been left as a result of deleteItem() + // calls in ItemFileWriteStore. + for(i = 0; i < arrayOfItems.length; ++i){ + var item = arrayOfItems[i]; + if(item !== null){ + items.push(item); + } + } + findCallback(items, requestArgs); + } + }, + + _fetchItems: function( /* Object */ keywordArgs, + /* Function */ findCallback, + /* Function */ errorCallback){ + // summary: + // See dojo/data/util.simpleFetch.fetch() + var self = this; + + if(this._loadFinished){ + this.filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions), findCallback); + }else{ + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + kernel.deprecated(this.declaredClass + ": ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } + + //See if there was any forced reset of data. + if(this.data != null){ + this._jsonData = this.data; + this.data = null; + } + + if(this._jsonFileUrl){ + //If fetches come in before the loading has finished, but while + //a load is in progress, we have to defer the fetching to be + //invoked in the callback. + if(this._loadInProgress){ + this._queuedFetches.push({args: keywordArgs, filter: lang.hitch(self, "filter"), findCallback: lang.hitch(self, findCallback)}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = xhr.get(getArgs); + getHandler.addCallback(function(data){ + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; + + self.filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions), findCallback); + self._handleQueuedFetches(); + }catch(e){ + self._loadFinished = true; + self._loadInProgress = false; + errorCallback(e, keywordArgs); + } + }); + getHandler.addErrback(function(error){ + self._loadInProgress = false; + errorCallback(error, keywordArgs); + }); + + //Wire up the cancel to abort of the request + //This call cancel on the deferred if it hasn't been called + //yet and then will chain to the simple abort of the + //simpleFetch keywordArgs + var oldAbort = null; + if(keywordArgs.abort){ + oldAbort = keywordArgs.abort; + } + keywordArgs.abort = function(){ + var df = getHandler; + if(df && df.fired === -1){ + df.cancel(); + df = null; + } + if(oldAbort){ + oldAbort.call(keywordArgs); + } + }; + } + }else if(this._jsonData){ + try{ + this._loadFinished = true; + this._getItemsFromLoadedData(this._jsonData); + this._jsonData = null; + self.filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions), findCallback); + }catch(e){ + errorCallback(e, keywordArgs); + } + }else{ + errorCallback(new Error(this.declaredClass + ": No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs); + } + } + }, + + _handleQueuedFetches: function(){ + // summary: + // Internal function to execute delayed request in the store. + + //Execute any deferred fetches now. + if(this._queuedFetches.length > 0){ + for(var i = 0; i < this._queuedFetches.length; i++){ + var fData = this._queuedFetches[i], + delayedQuery = fData.args, + delayedFilter = fData.filter, + delayedFindCallback = fData.findCallback; + if(delayedFilter){ + delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions), delayedFindCallback); + }else{ + this.fetchItemByIdentity(delayedQuery); + } + } + this._queuedFetches = []; + } + }, + + _getItemsArray: function(/*object?*/queryOptions){ + // summary: + // Internal function to determine which list of items to search over. + // queryOptions: The query options parameter, if any. + if(queryOptions && queryOptions.deep){ + return this._arrayOfAllItems; + } + return this._arrayOfTopLevelItems; + }, + + close: function(/*dojo/data/api/Request|Object?*/ request){ + // summary: + // See dojo/data/api/Read.close() + if(this.clearOnClose && + this._loadFinished && + !this._loadInProgress){ + //Reset all internalsback to default state. This will force a reload + //on next fetch. This also checks that the data or url param was set + //so that the store knows it can get data. Without one of those being set, + //the next fetch will trigger an error. + + if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) && + (this.url == "" || this.url == null) + ) && this.data == null){ + console.debug(this.declaredClass + ": WARNING! Data reload " + + " information has not been provided." + + " Please set 'url' or 'data' to the appropriate value before" + + " the next fetch"); + } + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = []; + this._loadFinished = false; + this._itemsByIdentity = null; + this._loadInProgress = false; + this._queuedFetches = []; + } + }, + + _getItemsFromLoadedData: function(/* Object */ dataObject){ + // summary: + // Function to parse the loaded data into item format and build the internal items array. + // description: + // Function to parse the loaded data into item format and build the internal items array. + // dataObject: + // The JS data object containing the raw data to convery into item format. + // returns: Array + // Array of items in store item format. + + // First, we define a couple little utility functions... + var addingArrays = false, + self = this; + + function valueIsAnItem(/* anything */ aValue){ + // summary: + // Given any sort of value that could be in the raw json data, + // return true if we should interpret the value as being an + // item itself, rather than a literal value or a reference. + // example: + // | false == valueIsAnItem("Kermit"); + // | false == valueIsAnItem(42); + // | false == valueIsAnItem(new Date()); + // | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'}); + // | false == valueIsAnItem({_reference:'Kermit'}); + // | true == valueIsAnItem({name:'Kermit', color:'green'}); + // | true == valueIsAnItem({iggy:'pop'}); + // | true == valueIsAnItem({foo:42}); + return (aValue !== null) && + (typeof aValue === "object") && + (!lang.isArray(aValue) || addingArrays) && + (!lang.isFunction(aValue)) && + (aValue.constructor == Object || lang.isArray(aValue)) && + (typeof aValue._reference === "undefined") && + (typeof aValue._type === "undefined") && + (typeof aValue._value === "undefined") && + self.hierarchical; + } + + function addItemAndSubItemsToArrayOfAllItems(/* dojo/data/api/Item */ anItem){ + self._arrayOfAllItems.push(anItem); + for(var attribute in anItem){ + var valueForAttribute = anItem[attribute]; + if(valueForAttribute){ + if(lang.isArray(valueForAttribute)){ + var valueArray = valueForAttribute; + for(var k = 0; k < valueArray.length; ++k){ + var singleValue = valueArray[k]; + if(valueIsAnItem(singleValue)){ + addItemAndSubItemsToArrayOfAllItems(singleValue); + } + } + }else{ + if(valueIsAnItem(valueForAttribute)){ + addItemAndSubItemsToArrayOfAllItems(valueForAttribute); + } + } + } + } + } + + this._labelAttr = dataObject.label; + + // We need to do some transformations to convert the data structure + // that we read from the file into a format that will be convenient + // to work with in memory. + + // Step 1: Walk through the object hierarchy and build a list of all items + var i, + item; + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = dataObject.items; + + for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ + item = this._arrayOfTopLevelItems[i]; + if(lang.isArray(item)){ + addingArrays = true; + } + addItemAndSubItemsToArrayOfAllItems(item); + item[this._rootItemPropName]=true; + } + + // Step 2: Walk through all the attribute values of all the items, + // and replace single values with arrays. For example, we change this: + // { name:'Miss Piggy', pets:'Foo-Foo'} + // into this: + // { name:['Miss Piggy'], pets:['Foo-Foo']} + // + // We also store the attribute names so we can validate our store + // reference and item id special properties for the O(1) isItem + var allAttributeNames = {}, + key; + + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + for(key in item){ + if(key !== this._rootItemPropName){ + var value = item[key]; + if(value !== null){ + if(!lang.isArray(value)){ + item[key] = [value]; + } + }else{ + item[key] = [null]; + } + } + allAttributeNames[key]=key; + } + } + + // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName + // This should go really fast, it will generally never even run the loop. + while(allAttributeNames[this._storeRefPropName]){ + this._storeRefPropName += "_"; + } + while(allAttributeNames[this._itemNumPropName]){ + this._itemNumPropName += "_"; + } + while(allAttributeNames[this._reverseRefMap]){ + this._reverseRefMap += "_"; + } + + // Step 4: Some data files specify an optional 'identifier', which is + // the name of an attribute that holds the identity of each item. + // If this data file specified an identifier attribute, then build a + // hash table of items keyed by the identity of the items. + var arrayOfValues; + + var identifier = dataObject.identifier; + if(identifier){ + this._itemsByIdentity = {}; + this._features['dojo.data.api.Identity'] = identifier; + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + arrayOfValues = item[identifier]; + var identity = arrayOfValues[0]; + if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){ + this._itemsByIdentity[identity] = item; + }else{ + if(this._jsonFileUrl){ + throw new Error(this.declaredClass + ": The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); + }else if(this._jsonData){ + throw new Error(this.declaredClass + ": The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); + } + } + } + }else{ + this._features['dojo.data.api.Identity'] = Number; + } + + // Step 5: Walk through all the items, and set each item's properties + // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + item[this._storeRefPropName] = this; + item[this._itemNumPropName] = i; + } + + // Step 6: We walk through all the attribute values of all the items, + // looking for type/value literals and item-references. + // + // We replace item-references with pointers to items. For example, we change: + // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + // into this: + // { name:['Kermit'], friends:[miss_piggy] } + // (where miss_piggy is the object representing the 'Miss Piggy' item). + // + // We replace type/value pairs with typed-literals. For example, we change: + // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] } + // into this: + // { name:['Kermit'], born:(new Date(1918, 6, 18)) } + // + // We also generate the associate map for all items for the O(1) isItem function. + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + for(key in item){ + arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] + for(var j = 0; j < arrayOfValues.length; ++j){ + value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} + if(value !== null && typeof value == "object"){ + if(("_type" in value) && ("_value" in value)){ + var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' + var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} + if(!mappingObj){ + throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); + }else if(lang.isFunction(mappingObj)){ + arrayOfValues[j] = new mappingObj(value._value); + }else if(lang.isFunction(mappingObj.deserialize)){ + arrayOfValues[j] = mappingObj.deserialize(value._value); + }else{ + throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); + } + } + if(value._reference){ + var referenceDescription = value._reference; // example: {name:'Miss Piggy'} + if(!lang.isObject(referenceDescription)){ + // example: 'Miss Piggy' + // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} + arrayOfValues[j] = this._getItemByIdentity(referenceDescription); + }else{ + // example: {name:'Miss Piggy'} + // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + for(var k = 0; k < this._arrayOfAllItems.length; ++k){ + var candidateItem = this._arrayOfAllItems[k], + found = true; + for(var refKey in referenceDescription){ + if(candidateItem[refKey] != referenceDescription[refKey]){ + found = false; + } + } + if(found){ + arrayOfValues[j] = candidateItem; + } + } + } + if(this.referenceIntegrity){ + var refItem = arrayOfValues[j]; + if(this.isItem(refItem)){ + this._addReferenceToMap(refItem, item, key); + } + } + }else if(this.isItem(value)){ + //It's a child item (not one referenced through _reference). + //We need to treat this as a referenced item, so it can be cleaned up + //in a write store easily. + if(this.referenceIntegrity){ + this._addReferenceToMap(value, item, key); + } + } + } + } + } + } + }, + + _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){ + // summary: + // Method to add an reference map entry for an item and attribute. + // description: + // Method to add an reference map entry for an item and attribute. + // refItem: + // The item that is referenced. + // parentItem: + // The item that holds the new reference to refItem. + // attribute: + // The attribute on parentItem that contains the new reference. + + //Stub function, does nothing. Real processing is in ItemFileWriteStore. + }, + + getIdentity: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Identity.getIdentity() + var identifier = this._features['dojo.data.api.Identity']; + if(identifier === Number){ + return item[this._itemNumPropName]; // Number + }else{ + var arrayOfValues = item[identifier]; + if(arrayOfValues){ + return arrayOfValues[0]; // Object|String + } + } + return null; // null + }, + + fetchItemByIdentity: function(/* Object */ keywordArgs){ + // summary: + // See dojo/data/api/Identity.fetchItemByIdentity() + + // Hasn't loaded yet, we have to trigger the load. + var item, + scope; + if(!this._loadFinished){ + var self = this; + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + kernel.deprecated(this.declaredClass + ": ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } + + //See if there was any forced reset of data. + if(this.data != null && this._jsonData == null){ + this._jsonData = this.data; + this.data = null; + } + + if(this._jsonFileUrl){ + + if(this._loadInProgress){ + this._queuedFetches.push({args: keywordArgs}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = xhr.get(getArgs); + getHandler.addCallback(function(data){ + var scope = keywordArgs.scope?keywordArgs.scope:kernel.global; + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; + item = self._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + keywordArgs.onItem.call(scope, item); + } + self._handleQueuedFetches(); + }catch(error){ + self._loadInProgress = false; + if(keywordArgs.onError){ + keywordArgs.onError.call(scope, error); + } + } + }); + getHandler.addErrback(function(error){ + self._loadInProgress = false; + if(keywordArgs.onError){ + var scope = keywordArgs.scope?keywordArgs.scope:kernel.global; + keywordArgs.onError.call(scope, error); + } + }); + } + + }else if(this._jsonData){ + // Passed in data, no need to xhr. + self._getItemsFromLoadedData(self._jsonData); + self._jsonData = null; + self._loadFinished = true; + item = self._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + scope = keywordArgs.scope?keywordArgs.scope:kernel.global; + keywordArgs.onItem.call(scope, item); + } + } + }else{ + // Already loaded. We can just look it up and call back. + item = this._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + scope = keywordArgs.scope?keywordArgs.scope:kernel.global; + keywordArgs.onItem.call(scope, item); + } + } + }, + + _getItemByIdentity: function(/* Object */ identity){ + // summary: + // Internal function to look an item up by its identity map. + var item = null; + if(this._itemsByIdentity){ + // If this map is defined, we need to just try to get it. If it fails + // the item does not exist. + if(Object.hasOwnProperty.call(this._itemsByIdentity, identity)){ + item = this._itemsByIdentity[identity]; + } + }else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){ + item = this._arrayOfAllItems[identity]; + } + if(item === undefined){ + item = null; + } + return item; // Object + }, + + getIdentityAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Identity.getIdentityAttributes() + + var identifier = this._features['dojo.data.api.Identity']; + if(identifier === Number){ + // If (identifier === Number) it means getIdentity() just returns + // an integer item-number for each item. The dojo/data/api/Identity + // spec says we need to return null if the identity is not composed + // of attributes + return null; // null + }else{ + return [identifier]; // Array + } + }, + + _forceLoad: function(){ + // summary: + // Internal function to force a load of the store if it hasn't occurred yet. This is required + // for specific functions to work properly. + var self = this; + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + kernel.deprecated(this.declaredClass + ": ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } + + //See if there was any forced reset of data. + if(this.data != null){ + this._jsonData = this.data; + this.data = null; + } + + if(this._jsonFileUrl){ + var getArgs = { + url: this._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk, + sync: true + }; + var getHandler = xhr.get(getArgs); + getHandler.addCallback(function(data){ + try{ + //Check to be sure there wasn't another load going on concurrently + //So we don't clobber data that comes in on it. If there is a load going on + //then do not save this data. It will potentially clobber current data. + //We mainly wanted to sync/wait here. + //TODO: Revisit the loading scheme of this store to improve multi-initial + //request handling. + if(self._loadInProgress !== true && !self._loadFinished){ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + }else if(self._loadInProgress){ + //Okay, we hit an error state we can't recover from. A forced load occurred + //while an async load was occurring. Since we cannot block at this point, the best + //that can be managed is to throw an error. + throw new Error(this.declaredClass + ": Unable to perform a synchronous load, an async load is in progress."); + } + }catch(e){ + console.log(e); + throw e; + } + }); + getHandler.addErrback(function(error){ + throw error; + }); + }else if(this._jsonData){ + self._getItemsFromLoadedData(self._jsonData); + self._jsonData = null; + self._loadFinished = true; + } + } +}); +//Mix in the simple fetch implementation to this class. +lang.extend(ItemFileReadStore,simpleFetch); + +return ItemFileReadStore; + +}); diff --git a/lib/dojo/data/ItemFileWriteStore.js b/lib/dojo/data/ItemFileWriteStore.js index 25651cf92..81e07c1a4 100644 --- a/lib/dojo/data/ItemFileWriteStore.js +++ b/lib/dojo/data/ItemFileWriteStore.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/ItemFileWriteStore",["../_base/lang","../_base/declare","../_base/array","../_base/json","../_base/window","./ItemFileReadStore","../date/stamp"],function(_1,_2,_3,_4,_5,_6,_7){return _2("dojo.data.ItemFileWriteStore",_6,{constructor:function(_8){this._features["dojo.data.api.Write"]=true;this._features["dojo.data.api.Notification"]=true;this._pending={_newItems:{},_modifiedItems:{},_deletedItems:{}};if(!this._datatypeMap["Date"].serialize){this._datatypeMap["Date"].serialize=function(_9){return _7.toISOString(_9,{zulu:true});};}if(_8&&(_8.referenceIntegrity===false)){this.referenceIntegrity=false;}this._saveInProgress=false;},referenceIntegrity:true,_assert:function(_a){if(!_a){throw new Error("assertion failed in ItemFileWriteStore");}},_getIdentifierAttribute:function(){return this.getFeatures()["dojo.data.api.Identity"];},newItem:function(_b,_c){this._assert(!this._saveInProgress);if(!this._loadFinished){this._forceLoad();}if(typeof _b!="object"&&typeof _b!="undefined"){throw new Error("newItem() was passed something other than an object");}var _d=null;var _e=this._getIdentifierAttribute();if(_e===Number){_d=this._arrayOfAllItems.length;}else{_d=_b[_e];if(typeof _d==="undefined"){throw new Error("newItem() was not passed an identity for the new item");}if(_1.isArray(_d)){throw new Error("newItem() was not passed an single-valued identity");}}if(this._itemsByIdentity){this._assert(typeof this._itemsByIdentity[_d]==="undefined");}this._assert(typeof this._pending._newItems[_d]==="undefined");this._assert(typeof this._pending._deletedItems[_d]==="undefined");var _f={};_f[this._storeRefPropName]=this;_f[this._itemNumPropName]=this._arrayOfAllItems.length;if(this._itemsByIdentity){this._itemsByIdentity[_d]=_f;_f[_e]=[_d];}this._arrayOfAllItems.push(_f);var _10=null;if(_c&&_c.parent&&_c.attribute){_10={item:_c.parent,attribute:_c.attribute,oldValue:undefined};var _11=this.getValues(_c.parent,_c.attribute);if(_11&&_11.length>0){var _12=_11.slice(0,_11.length);if(_11.length===1){_10.oldValue=_11[0];}else{_10.oldValue=_11.slice(0,_11.length);}_12.push(_f);this._setValueOrValues(_c.parent,_c.attribute,_12,false);_10.newValue=this.getValues(_c.parent,_c.attribute);}else{this._setValueOrValues(_c.parent,_c.attribute,_f,false);_10.newValue=_f;}}else{_f[this._rootItemPropName]=true;this._arrayOfTopLevelItems.push(_f);}this._pending._newItems[_d]=_f;for(var key in _b){if(key===this._storeRefPropName||key===this._itemNumPropName){throw new Error("encountered bug in ItemFileWriteStore.newItem");}var _13=_b[key];if(!_1.isArray(_13)){_13=[_13];}_f[key]=_13;if(this.referenceIntegrity){for(var i=0;i<_13.length;i++){var val=_13[i];if(this.isItem(val)){this._addReferenceToMap(val,_f,key);}}}}this.onNew(_f,_10);return _f;},_removeArrayElement:function(_14,_15){var _16=_3.indexOf(_14,_15);if(_16!=-1){_14.splice(_16,1);return true;}return false;},deleteItem:function(_17){this._assert(!this._saveInProgress);this._assertIsItem(_17);var _18=_17[this._itemNumPropName];var _19=this.getIdentity(_17);if(this.referenceIntegrity){var _1a=this.getAttributes(_17);if(_17[this._reverseRefMap]){_17["backup_"+this._reverseRefMap]=_1.clone(_17[this._reverseRefMap]);}_3.forEach(_1a,function(_1b){_3.forEach(this.getValues(_17,_1b),function(_1c){if(this.isItem(_1c)){if(!_17["backupRefs_"+this._reverseRefMap]){_17["backupRefs_"+this._reverseRefMap]=[];}_17["backupRefs_"+this._reverseRefMap].push({id:this.getIdentity(_1c),attr:_1b});this._removeReferenceFromMap(_1c,_17,_1b);}},this);},this);var _1d=_17[this._reverseRefMap];if(_1d){for(var _1e in _1d){var _1f=null;if(this._itemsByIdentity){_1f=this._itemsByIdentity[_1e];}else{_1f=this._arrayOfAllItems[_1e];}if(_1f){for(var _20 in _1d[_1e]){var _21=this.getValues(_1f,_20)||[];var _22=_3.filter(_21,function(_23){return !(this.isItem(_23)&&this.getIdentity(_23)==_19);},this);this._removeReferenceFromMap(_17,_1f,_20);if(_22.length<_21.length){this._setValueOrValues(_1f,_20,_22,true);}}}}}}this._arrayOfAllItems[_18]=null;_17[this._storeRefPropName]=null;if(this._itemsByIdentity){delete this._itemsByIdentity[_19];}this._pending._deletedItems[_19]=_17;if(_17[this._rootItemPropName]){this._removeArrayElement(this._arrayOfTopLevelItems,_17);}this.onDelete(_17);return true;},setValue:function(_24,_25,_26){return this._setValueOrValues(_24,_25,_26,true);},setValues:function(_27,_28,_29){return this._setValueOrValues(_27,_28,_29,true);},unsetAttribute:function(_2a,_2b){return this._setValueOrValues(_2a,_2b,[],true);},_setValueOrValues:function(_2c,_2d,_2e,_2f){this._assert(!this._saveInProgress);this._assertIsItem(_2c);this._assert(_1.isString(_2d));this._assert(typeof _2e!=="undefined");var _30=this._getIdentifierAttribute();if(_2d==_30){throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");}var _31=this._getValueOrValues(_2c,_2d);var _32=this.getIdentity(_2c);if(!this._pending._modifiedItems[_32]){var _33={};for(var key in _2c){if((key===this._storeRefPropName)||(key===this._itemNumPropName)||(key===this._rootItemPropName)){_33[key]=_2c[key];}else{if(key===this._reverseRefMap){_33[key]=_1.clone(_2c[key]);}else{_33[key]=_2c[key].slice(0,_2c[key].length);}}}this._pending._modifiedItems[_32]=_33;}var _34=false;if(_1.isArray(_2e)&&_2e.length===0){_34=delete _2c[_2d];_2e=undefined;if(this.referenceIntegrity&&_31){var _35=_31;if(!_1.isArray(_35)){_35=[_35];}for(var i=0;i<_35.length;i++){var _36=_35[i];if(this.isItem(_36)){this._removeReferenceFromMap(_36,_2c,_2d);}}}}else{var _37;if(_1.isArray(_2e)){_37=_2e.slice(0,_2e.length);}else{_37=[_2e];}if(this.referenceIntegrity){if(_31){var _35=_31;if(!_1.isArray(_35)){_35=[_35];}var map={};_3.forEach(_35,function(_38){if(this.isItem(_38)){var id=this.getIdentity(_38);map[id.toString()]=true;}},this);_3.forEach(_37,function(_39){if(this.isItem(_39)){var id=this.getIdentity(_39);if(map[id.toString()]){delete map[id.toString()];}else{this._addReferenceToMap(_39,_2c,_2d);}}},this);for(var rId in map){var _3a;if(this._itemsByIdentity){_3a=this._itemsByIdentity[rId];}else{_3a=this._arrayOfAllItems[rId];}this._removeReferenceFromMap(_3a,_2c,_2d);}}else{for(var i=0;i<_37.length;i++){var _36=_37[i];if(this.isItem(_36)){this._addReferenceToMap(_36,_2c,_2d);}}}}_2c[_2d]=_37;_34=true;}if(_2f){this.onSet(_2c,_2d,_31,_2e);}return _34;},_addReferenceToMap:function(_3b,_3c,_3d){var _3e=this.getIdentity(_3c);var _3f=_3b[this._reverseRefMap];if(!_3f){_3f=_3b[this._reverseRefMap]={};}var _40=_3f[_3e];if(!_40){_40=_3f[_3e]={};}_40[_3d]=true;},_removeReferenceFromMap:function(_41,_42,_43){var _44=this.getIdentity(_42);var _45=_41[this._reverseRefMap];var _46;if(_45){for(_46 in _45){if(_46==_44){delete _45[_46][_43];if(this._isEmpty(_45[_46])){delete _45[_46];}}}if(this._isEmpty(_45)){delete _41[this._reverseRefMap];}}},_dumpReferenceMap:function(){var i;for(i=0;i<this._arrayOfAllItems.length;i++){var _47=this._arrayOfAllItems[i];if(_47&&_47[this._reverseRefMap]){}}},_getValueOrValues:function(_48,_49){var _4a=undefined;if(this.hasAttribute(_48,_49)){var _4b=this.getValues(_48,_49);if(_4b.length==1){_4a=_4b[0];}else{_4a=_4b;}}return _4a;},_flatten:function(_4c){if(this.isItem(_4c)){return {_reference:this.getIdentity(_4c)};}else{if(typeof _4c==="object"){for(var _4d in this._datatypeMap){var _4e=this._datatypeMap[_4d];if(_1.isObject(_4e)&&!_1.isFunction(_4e)){if(_4c instanceof _4e.type){if(!_4e.serialize){throw new Error("ItemFileWriteStore: No serializer defined for type mapping: ["+_4d+"]");}return {_type:_4d,_value:_4e.serialize(_4c)};}}else{if(_4c instanceof _4e){return {_type:_4d,_value:_4c.toString()};}}}}return _4c;}},_getNewFileContentString:function(){var _4f={};var _50=this._getIdentifierAttribute();if(_50!==Number){_4f.identifier=_50;}if(this._labelAttr){_4f.label=this._labelAttr;}_4f.items=[];for(var i=0;i<this._arrayOfAllItems.length;++i){var _51=this._arrayOfAllItems[i];if(_51!==null){var _52={};for(var key in _51){if(key!==this._storeRefPropName&&key!==this._itemNumPropName&&key!==this._reverseRefMap&&key!==this._rootItemPropName){var _53=this.getValues(_51,key);if(_53.length==1){_52[key]=this._flatten(_53[0]);}else{var _54=[];for(var j=0;j<_53.length;++j){_54.push(this._flatten(_53[j]));_52[key]=_54;}}}}_4f.items.push(_52);}}var _55=true;return _4.toJson(_4f,_55);},_isEmpty:function(_56){var _57=true;if(_1.isObject(_56)){var i;for(i in _56){_57=false;break;}}else{if(_1.isArray(_56)){if(_56.length>0){_57=false;}}}return _57;},save:function(_58){this._assert(!this._saveInProgress);this._saveInProgress=true;var _59=this;var _5a=function(){_59._pending={_newItems:{},_modifiedItems:{},_deletedItems:{}};_59._saveInProgress=false;if(_58&&_58.onComplete){var _5b=_58.scope||_5.global;_58.onComplete.call(_5b);}};var _5c=function(err){_59._saveInProgress=false;if(_58&&_58.onError){var _5d=_58.scope||_5.global;_58.onError.call(_5d,err);}};if(this._saveEverything){var _5e=this._getNewFileContentString();this._saveEverything(_5a,_5c,_5e);}if(this._saveCustom){this._saveCustom(_5a,_5c);}if(!this._saveEverything&&!this._saveCustom){_5a();}},revert:function(){this._assert(!this._saveInProgress);var _5f;for(_5f in this._pending._modifiedItems){var _60=this._pending._modifiedItems[_5f];var _61=null;if(this._itemsByIdentity){_61=this._itemsByIdentity[_5f];}else{_61=this._arrayOfAllItems[_5f];}_60[this._storeRefPropName]=this;for(var key in _61){delete _61[key];}_1.mixin(_61,_60);}var _62;for(_5f in this._pending._deletedItems){_62=this._pending._deletedItems[_5f];_62[this._storeRefPropName]=this;var _63=_62[this._itemNumPropName];if(_62["backup_"+this._reverseRefMap]){_62[this._reverseRefMap]=_62["backup_"+this._reverseRefMap];delete _62["backup_"+this._reverseRefMap];}this._arrayOfAllItems[_63]=_62;if(this._itemsByIdentity){this._itemsByIdentity[_5f]=_62;}if(_62[this._rootItemPropName]){this._arrayOfTopLevelItems.push(_62);}}for(_5f in this._pending._deletedItems){_62=this._pending._deletedItems[_5f];if(_62["backupRefs_"+this._reverseRefMap]){_3.forEach(_62["backupRefs_"+this._reverseRefMap],function(_64){var _65;if(this._itemsByIdentity){_65=this._itemsByIdentity[_64.id];}else{_65=this._arrayOfAllItems[_64.id];}this._addReferenceToMap(_65,_62,_64.attr);},this);delete _62["backupRefs_"+this._reverseRefMap];}}for(_5f in this._pending._newItems){var _66=this._pending._newItems[_5f];_66[this._storeRefPropName]=null;this._arrayOfAllItems[_66[this._itemNumPropName]]=null;if(_66[this._rootItemPropName]){this._removeArrayElement(this._arrayOfTopLevelItems,_66);}if(this._itemsByIdentity){delete this._itemsByIdentity[_5f];}}this._pending={_newItems:{},_modifiedItems:{},_deletedItems:{}};return true;},isDirty:function(_67){if(_67){var _68=this.getIdentity(_67);return new Boolean(this._pending._newItems[_68]||this._pending._modifiedItems[_68]||this._pending._deletedItems[_68]).valueOf();}else{return !this._isEmpty(this._pending._newItems)||!this._isEmpty(this._pending._modifiedItems)||!this._isEmpty(this._pending._deletedItems);}},onSet:function(_69,_6a,_6b,_6c){},onNew:function(_6d,_6e){},onDelete:function(_6f){},close:function(_70){if(this.clearOnClose){if(!this.isDirty()){this.inherited(arguments);}else{throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close.");}}}});});
\ No newline at end of file +define("dojo/data/ItemFileWriteStore",["../_base/lang","../_base/declare","../_base/array","../_base/json","../_base/kernel","./ItemFileReadStore","../date/stamp"],function(_1,_2,_3,_4,_5,_6,_7){return _2("dojo.data.ItemFileWriteStore",_6,{constructor:function(_8){this._features["dojo.data.api.Write"]=true;this._features["dojo.data.api.Notification"]=true;this._pending={_newItems:{},_modifiedItems:{},_deletedItems:{}};if(!this._datatypeMap["Date"].serialize){this._datatypeMap["Date"].serialize=function(_9){return _7.toISOString(_9,{zulu:true});};}if(_8&&(_8.referenceIntegrity===false)){this.referenceIntegrity=false;}this._saveInProgress=false;},referenceIntegrity:true,_assert:function(_a){if(!_a){throw new Error("assertion failed in ItemFileWriteStore");}},_getIdentifierAttribute:function(){return this.getFeatures()["dojo.data.api.Identity"];},newItem:function(_b,_c){this._assert(!this._saveInProgress);if(!this._loadFinished){this._forceLoad();}if(typeof _b!="object"&&typeof _b!="undefined"){throw new Error("newItem() was passed something other than an object");}var _d=null;var _e=this._getIdentifierAttribute();if(_e===Number){_d=this._arrayOfAllItems.length;}else{_d=_b[_e];if(typeof _d==="undefined"){throw new Error("newItem() was not passed an identity for the new item");}if(_1.isArray(_d)){throw new Error("newItem() was not passed an single-valued identity");}}if(this._itemsByIdentity){this._assert(typeof this._itemsByIdentity[_d]==="undefined");}this._assert(typeof this._pending._newItems[_d]==="undefined");this._assert(typeof this._pending._deletedItems[_d]==="undefined");var _f={};_f[this._storeRefPropName]=this;_f[this._itemNumPropName]=this._arrayOfAllItems.length;if(this._itemsByIdentity){this._itemsByIdentity[_d]=_f;_f[_e]=[_d];}this._arrayOfAllItems.push(_f);var _10=null;if(_c&&_c.parent&&_c.attribute){_10={item:_c.parent,attribute:_c.attribute,oldValue:undefined};var _11=this.getValues(_c.parent,_c.attribute);if(_11&&_11.length>0){var _12=_11.slice(0,_11.length);if(_11.length===1){_10.oldValue=_11[0];}else{_10.oldValue=_11.slice(0,_11.length);}_12.push(_f);this._setValueOrValues(_c.parent,_c.attribute,_12,false);_10.newValue=this.getValues(_c.parent,_c.attribute);}else{this._setValueOrValues(_c.parent,_c.attribute,_f,false);_10.newValue=_f;}}else{_f[this._rootItemPropName]=true;this._arrayOfTopLevelItems.push(_f);}this._pending._newItems[_d]=_f;for(var key in _b){if(key===this._storeRefPropName||key===this._itemNumPropName){throw new Error("encountered bug in ItemFileWriteStore.newItem");}var _13=_b[key];if(!_1.isArray(_13)){_13=[_13];}_f[key]=_13;if(this.referenceIntegrity){for(var i=0;i<_13.length;i++){var val=_13[i];if(this.isItem(val)){this._addReferenceToMap(val,_f,key);}}}}this.onNew(_f,_10);return _f;},_removeArrayElement:function(_14,_15){var _16=_3.indexOf(_14,_15);if(_16!=-1){_14.splice(_16,1);return true;}return false;},deleteItem:function(_17){this._assert(!this._saveInProgress);this._assertIsItem(_17);var _18=_17[this._itemNumPropName];var _19=this.getIdentity(_17);if(this.referenceIntegrity){var _1a=this.getAttributes(_17);if(_17[this._reverseRefMap]){_17["backup_"+this._reverseRefMap]=_1.clone(_17[this._reverseRefMap]);}_3.forEach(_1a,function(_1b){_3.forEach(this.getValues(_17,_1b),function(_1c){if(this.isItem(_1c)){if(!_17["backupRefs_"+this._reverseRefMap]){_17["backupRefs_"+this._reverseRefMap]=[];}_17["backupRefs_"+this._reverseRefMap].push({id:this.getIdentity(_1c),attr:_1b});this._removeReferenceFromMap(_1c,_17,_1b);}},this);},this);var _1d=_17[this._reverseRefMap];if(_1d){for(var _1e in _1d){var _1f=null;if(this._itemsByIdentity){_1f=this._itemsByIdentity[_1e];}else{_1f=this._arrayOfAllItems[_1e];}if(_1f){for(var _20 in _1d[_1e]){var _21=this.getValues(_1f,_20)||[];var _22=_3.filter(_21,function(_23){return !(this.isItem(_23)&&this.getIdentity(_23)==_19);},this);this._removeReferenceFromMap(_17,_1f,_20);if(_22.length<_21.length){this._setValueOrValues(_1f,_20,_22,true);}}}}}}this._arrayOfAllItems[_18]=null;_17[this._storeRefPropName]=null;if(this._itemsByIdentity){delete this._itemsByIdentity[_19];}this._pending._deletedItems[_19]=_17;if(_17[this._rootItemPropName]){this._removeArrayElement(this._arrayOfTopLevelItems,_17);}this.onDelete(_17);return true;},setValue:function(_24,_25,_26){return this._setValueOrValues(_24,_25,_26,true);},setValues:function(_27,_28,_29){return this._setValueOrValues(_27,_28,_29,true);},unsetAttribute:function(_2a,_2b){return this._setValueOrValues(_2a,_2b,[],true);},_setValueOrValues:function(_2c,_2d,_2e,_2f){this._assert(!this._saveInProgress);this._assertIsItem(_2c);this._assert(_1.isString(_2d));this._assert(typeof _2e!=="undefined");var _30=this._getIdentifierAttribute();if(_2d==_30){throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");}var _31=this._getValueOrValues(_2c,_2d);var _32=this.getIdentity(_2c);if(!this._pending._modifiedItems[_32]){var _33={};for(var key in _2c){if((key===this._storeRefPropName)||(key===this._itemNumPropName)||(key===this._rootItemPropName)){_33[key]=_2c[key];}else{if(key===this._reverseRefMap){_33[key]=_1.clone(_2c[key]);}else{_33[key]=_2c[key].slice(0,_2c[key].length);}}}this._pending._modifiedItems[_32]=_33;}var _34=false;if(_1.isArray(_2e)&&_2e.length===0){_34=delete _2c[_2d];_2e=undefined;if(this.referenceIntegrity&&_31){var _35=_31;if(!_1.isArray(_35)){_35=[_35];}for(var i=0;i<_35.length;i++){var _36=_35[i];if(this.isItem(_36)){this._removeReferenceFromMap(_36,_2c,_2d);}}}}else{var _37;if(_1.isArray(_2e)){_37=_2e.slice(0,_2e.length);}else{_37=[_2e];}if(this.referenceIntegrity){if(_31){var _35=_31;if(!_1.isArray(_35)){_35=[_35];}var map={};_3.forEach(_35,function(_38){if(this.isItem(_38)){var id=this.getIdentity(_38);map[id.toString()]=true;}},this);_3.forEach(_37,function(_39){if(this.isItem(_39)){var id=this.getIdentity(_39);if(map[id.toString()]){delete map[id.toString()];}else{this._addReferenceToMap(_39,_2c,_2d);}}},this);for(var rId in map){var _3a;if(this._itemsByIdentity){_3a=this._itemsByIdentity[rId];}else{_3a=this._arrayOfAllItems[rId];}this._removeReferenceFromMap(_3a,_2c,_2d);}}else{for(var i=0;i<_37.length;i++){var _36=_37[i];if(this.isItem(_36)){this._addReferenceToMap(_36,_2c,_2d);}}}}_2c[_2d]=_37;_34=true;}if(_2f){this.onSet(_2c,_2d,_31,_2e);}return _34;},_addReferenceToMap:function(_3b,_3c,_3d){var _3e=this.getIdentity(_3c);var _3f=_3b[this._reverseRefMap];if(!_3f){_3f=_3b[this._reverseRefMap]={};}var _40=_3f[_3e];if(!_40){_40=_3f[_3e]={};}_40[_3d]=true;},_removeReferenceFromMap:function(_41,_42,_43){var _44=this.getIdentity(_42);var _45=_41[this._reverseRefMap];var _46;if(_45){for(_46 in _45){if(_46==_44){delete _45[_46][_43];if(this._isEmpty(_45[_46])){delete _45[_46];}}}if(this._isEmpty(_45)){delete _41[this._reverseRefMap];}}},_dumpReferenceMap:function(){var i;for(i=0;i<this._arrayOfAllItems.length;i++){var _47=this._arrayOfAllItems[i];if(_47&&_47[this._reverseRefMap]){}}},_getValueOrValues:function(_48,_49){var _4a=undefined;if(this.hasAttribute(_48,_49)){var _4b=this.getValues(_48,_49);if(_4b.length==1){_4a=_4b[0];}else{_4a=_4b;}}return _4a;},_flatten:function(_4c){if(this.isItem(_4c)){return {_reference:this.getIdentity(_4c)};}else{if(typeof _4c==="object"){for(var _4d in this._datatypeMap){var _4e=this._datatypeMap[_4d];if(_1.isObject(_4e)&&!_1.isFunction(_4e)){if(_4c instanceof _4e.type){if(!_4e.serialize){throw new Error("ItemFileWriteStore: No serializer defined for type mapping: ["+_4d+"]");}return {_type:_4d,_value:_4e.serialize(_4c)};}}else{if(_4c instanceof _4e){return {_type:_4d,_value:_4c.toString()};}}}}return _4c;}},_getNewFileContentString:function(){var _4f={};var _50=this._getIdentifierAttribute();if(_50!==Number){_4f.identifier=_50;}if(this._labelAttr){_4f.label=this._labelAttr;}_4f.items=[];for(var i=0;i<this._arrayOfAllItems.length;++i){var _51=this._arrayOfAllItems[i];if(_51!==null){var _52={};for(var key in _51){if(key!==this._storeRefPropName&&key!==this._itemNumPropName&&key!==this._reverseRefMap&&key!==this._rootItemPropName){var _53=this.getValues(_51,key);if(_53.length==1){_52[key]=this._flatten(_53[0]);}else{var _54=[];for(var j=0;j<_53.length;++j){_54.push(this._flatten(_53[j]));_52[key]=_54;}}}}_4f.items.push(_52);}}var _55=true;return _4.toJson(_4f,_55);},_isEmpty:function(_56){var _57=true;if(_1.isObject(_56)){var i;for(i in _56){_57=false;break;}}else{if(_1.isArray(_56)){if(_56.length>0){_57=false;}}}return _57;},save:function(_58){this._assert(!this._saveInProgress);this._saveInProgress=true;var _59=this;var _5a=function(){_59._pending={_newItems:{},_modifiedItems:{},_deletedItems:{}};_59._saveInProgress=false;if(_58&&_58.onComplete){var _5b=_58.scope||_5.global;_58.onComplete.call(_5b);}};var _5c=function(err){_59._saveInProgress=false;if(_58&&_58.onError){var _5d=_58.scope||_5.global;_58.onError.call(_5d,err);}};if(this._saveEverything){var _5e=this._getNewFileContentString();this._saveEverything(_5a,_5c,_5e);}if(this._saveCustom){this._saveCustom(_5a,_5c);}if(!this._saveEverything&&!this._saveCustom){_5a();}},revert:function(){this._assert(!this._saveInProgress);var _5f;for(_5f in this._pending._modifiedItems){var _60=this._pending._modifiedItems[_5f];var _61=null;if(this._itemsByIdentity){_61=this._itemsByIdentity[_5f];}else{_61=this._arrayOfAllItems[_5f];}_60[this._storeRefPropName]=this;for(var key in _61){delete _61[key];}_1.mixin(_61,_60);}var _62;for(_5f in this._pending._deletedItems){_62=this._pending._deletedItems[_5f];_62[this._storeRefPropName]=this;var _63=_62[this._itemNumPropName];if(_62["backup_"+this._reverseRefMap]){_62[this._reverseRefMap]=_62["backup_"+this._reverseRefMap];delete _62["backup_"+this._reverseRefMap];}this._arrayOfAllItems[_63]=_62;if(this._itemsByIdentity){this._itemsByIdentity[_5f]=_62;}if(_62[this._rootItemPropName]){this._arrayOfTopLevelItems.push(_62);}}for(_5f in this._pending._deletedItems){_62=this._pending._deletedItems[_5f];if(_62["backupRefs_"+this._reverseRefMap]){_3.forEach(_62["backupRefs_"+this._reverseRefMap],function(_64){var _65;if(this._itemsByIdentity){_65=this._itemsByIdentity[_64.id];}else{_65=this._arrayOfAllItems[_64.id];}this._addReferenceToMap(_65,_62,_64.attr);},this);delete _62["backupRefs_"+this._reverseRefMap];}}for(_5f in this._pending._newItems){var _66=this._pending._newItems[_5f];_66[this._storeRefPropName]=null;this._arrayOfAllItems[_66[this._itemNumPropName]]=null;if(_66[this._rootItemPropName]){this._removeArrayElement(this._arrayOfTopLevelItems,_66);}if(this._itemsByIdentity){delete this._itemsByIdentity[_5f];}}this._pending={_newItems:{},_modifiedItems:{},_deletedItems:{}};return true;},isDirty:function(_67){if(_67){var _68=this.getIdentity(_67);return new Boolean(this._pending._newItems[_68]||this._pending._modifiedItems[_68]||this._pending._deletedItems[_68]).valueOf();}else{return !this._isEmpty(this._pending._newItems)||!this._isEmpty(this._pending._modifiedItems)||!this._isEmpty(this._pending._deletedItems);}},onSet:function(_69,_6a,_6b,_6c){},onNew:function(_6d,_6e){},onDelete:function(_6f){},close:function(_70){if(this.clearOnClose){if(!this.isDirty()){this.inherited(arguments);}else{throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close.");}}}});});
\ No newline at end of file diff --git a/lib/dojo/data/ItemFileWriteStore.js.uncompressed.js b/lib/dojo/data/ItemFileWriteStore.js.uncompressed.js new file mode 100644 index 000000000..ee568a496 --- /dev/null +++ b/lib/dojo/data/ItemFileWriteStore.js.uncompressed.js @@ -0,0 +1,816 @@ +define("dojo/data/ItemFileWriteStore", ["../_base/lang", "../_base/declare", "../_base/array", "../_base/json", "../_base/kernel", + "./ItemFileReadStore", "../date/stamp" +], function(lang, declare, arrayUtil, jsonUtil, kernel, ItemFileReadStore, dateStamp){ + +// module: +// dojo/data/ItemFileWriteStore + +return declare("dojo.data.ItemFileWriteStore", ItemFileReadStore, { + // summary: + // TODOC + + constructor: function(/* object */ keywordParameters){ + // keywordParameters: + // The structure of the typeMap object is as follows: + // | { + // | type0: function || object, + // | type1: function || object, + // | ... + // | typeN: function || object + // | } + // Where if it is a function, it is assumed to be an object constructor that takes the + // value of _value as the initialization parameters. It is serialized assuming object.toString() + // serialization. If it is an object, then it is assumed + // to be an object of general form: + // | { + // | type: function, //constructor. + // | deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. + // | serialize: function(object) //The function that converts the object back into the proper file format form. + // | } + + // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs + this._features['dojo.data.api.Write'] = true; + this._features['dojo.data.api.Notification'] = true; + + // For keeping track of changes so that we can implement isDirty and revert + this._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + + if(!this._datatypeMap['Date'].serialize){ + this._datatypeMap['Date'].serialize = function(obj){ + return dateStamp.toISOString(obj, {zulu:true}); + }; + } + //Disable only if explicitly set to false. + if(keywordParameters && (keywordParameters.referenceIntegrity === false)){ + this.referenceIntegrity = false; + } + + // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes + this._saveInProgress = false; + }, + + referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively. + + _assert: function(/* boolean */ condition){ + if(!condition){ + throw new Error("assertion failed in ItemFileWriteStore"); + } + }, + + _getIdentifierAttribute: function(){ + // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute))); + return this.getFeatures()['dojo.data.api.Identity']; + }, + + +/* dojo/data/api/Write */ + + newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){ + // summary: + // See dojo/data/api/Write.newItem() + + this._assert(!this._saveInProgress); + + if(!this._loadFinished){ + // We need to do this here so that we'll be able to find out what + // identifierAttribute was specified in the data file. + this._forceLoad(); + } + + if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){ + throw new Error("newItem() was passed something other than an object"); + } + var newIdentity = null; + var identifierAttribute = this._getIdentifierAttribute(); + if(identifierAttribute === Number){ + newIdentity = this._arrayOfAllItems.length; + }else{ + newIdentity = keywordArgs[identifierAttribute]; + if(typeof newIdentity === "undefined"){ + throw new Error("newItem() was not passed an identity for the new item"); + } + if(lang.isArray(newIdentity)){ + throw new Error("newItem() was not passed an single-valued identity"); + } + } + + // make sure this identity is not already in use by another item, if identifiers were + // defined in the file. Otherwise it would be the item count, + // which should always be unique in this case. + if(this._itemsByIdentity){ + this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined"); + } + this._assert(typeof this._pending._newItems[newIdentity] === "undefined"); + this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined"); + + var newItem = {}; + newItem[this._storeRefPropName] = this; + newItem[this._itemNumPropName] = this._arrayOfAllItems.length; + if(this._itemsByIdentity){ + this._itemsByIdentity[newIdentity] = newItem; + //We have to set the identifier now, otherwise we can't look it + //up at calls to setValueorValues in parentInfo handling. + newItem[identifierAttribute] = [newIdentity]; + } + this._arrayOfAllItems.push(newItem); + + //We need to construct some data for the onNew call too... + var pInfo = null; + + // Now we need to check to see where we want to assign this thingm if any. + if(parentInfo && parentInfo.parent && parentInfo.attribute){ + pInfo = { + item: parentInfo.parent, + attribute: parentInfo.attribute, + oldValue: undefined + }; + + //See if it is multi-valued or not and handle appropriately + //Generally, all attributes are multi-valued for this store + //So, we only need to append if there are already values present. + var values = this.getValues(parentInfo.parent, parentInfo.attribute); + if(values && values.length > 0){ + var tempValues = values.slice(0, values.length); + if(values.length === 1){ + pInfo.oldValue = values[0]; + }else{ + pInfo.oldValue = values.slice(0, values.length); + } + tempValues.push(newItem); + this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false); + pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute); + }else{ + this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false); + pInfo.newValue = newItem; + } + }else{ + //Toplevel item, add to both top list as well as all list. + newItem[this._rootItemPropName]=true; + this._arrayOfTopLevelItems.push(newItem); + } + + this._pending._newItems[newIdentity] = newItem; + + //Clone over the properties to the new item + for(var key in keywordArgs){ + if(key === this._storeRefPropName || key === this._itemNumPropName){ + // Bummer, the user is trying to do something like + // newItem({_S:"foo"}). Unfortunately, our superclass, + // ItemFileReadStore, is already using _S in each of our items + // to hold private info. To avoid a naming collision, we + // need to move all our private info to some other property + // of all the items/objects. So, we need to iterate over all + // the items and do something like: + // item.__S = item._S; + // item._S = undefined; + // But first we have to make sure the new "__S" variable is + // not in use, which means we have to iterate over all the + // items checking for that. + throw new Error("encountered bug in ItemFileWriteStore.newItem"); + } + var value = keywordArgs[key]; + if(!lang.isArray(value)){ + value = [value]; + } + newItem[key] = value; + if(this.referenceIntegrity){ + for(var i = 0; i < value.length; i++){ + var val = value[i]; + if(this.isItem(val)){ + this._addReferenceToMap(val, newItem, key); + } + } + } + } + this.onNew(newItem, pInfo); // dojo/data/api/Notification call + return newItem; // item + }, + + _removeArrayElement: function(/* Array */ array, /* anything */ element){ + var index = arrayUtil.indexOf(array, element); + if(index != -1){ + array.splice(index, 1); + return true; + } + return false; + }, + + deleteItem: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Write.deleteItem() + this._assert(!this._saveInProgress); + this._assertIsItem(item); + + // Remove this item from the _arrayOfAllItems, but leave a null value in place + // of the item, so as not to change the length of the array, so that in newItem() + // we can still safely do: newIdentity = this._arrayOfAllItems.length; + var indexInArrayOfAllItems = item[this._itemNumPropName]; + var identity = this.getIdentity(item); + + //If we have reference integrity on, we need to do reference cleanup for the deleted item + if(this.referenceIntegrity){ + //First scan all the attributes of this items for references and clean them up in the map + //As this item is going away, no need to track its references anymore. + + //Get the attributes list before we generate the backup so it + //doesn't pollute the attributes list. + var attributes = this.getAttributes(item); + + //Backup the map, we'll have to restore it potentially, in a revert. + if(item[this._reverseRefMap]){ + item["backup_" + this._reverseRefMap] = lang.clone(item[this._reverseRefMap]); + } + + //TODO: This causes a reversion problem. This list won't be restored on revert since it is + //attached to the 'value'. item, not ours. Need to back tese up somehow too. + //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored + //later. Or just record them and call _addReferenceToMap on them in revert. + arrayUtil.forEach(attributes, function(attribute){ + arrayUtil.forEach(this.getValues(item, attribute), function(value){ + if(this.isItem(value)){ + //We have to back up all the references we had to others so they can be restored on a revert. + if(!item["backupRefs_" + this._reverseRefMap]){ + item["backupRefs_" + this._reverseRefMap] = []; + } + item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute}); + this._removeReferenceFromMap(value, item, attribute); + } + }, this); + }, this); + + //Next, see if we have references to this item, if we do, we have to clean them up too. + var references = item[this._reverseRefMap]; + if(references){ + //Look through all the items noted as references to clean them up. + for(var itemId in references){ + var containingItem = null; + if(this._itemsByIdentity){ + containingItem = this._itemsByIdentity[itemId]; + }else{ + containingItem = this._arrayOfAllItems[itemId]; + } + //We have a reference to a containing item, now we have to process the + //attributes and clear all references to the item being deleted. + if(containingItem){ + for(var attribute in references[itemId]){ + var oldValues = this.getValues(containingItem, attribute) || []; + var newValues = arrayUtil.filter(oldValues, function(possibleItem){ + return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity); + }, this); + //Remove the note of the reference to the item and set the values on the modified attribute. + this._removeReferenceFromMap(item, containingItem, attribute); + if(newValues.length < oldValues.length){ + this._setValueOrValues(containingItem, attribute, newValues, true); + } + } + } + } + } + } + + this._arrayOfAllItems[indexInArrayOfAllItems] = null; + + item[this._storeRefPropName] = null; + if(this._itemsByIdentity){ + delete this._itemsByIdentity[identity]; + } + this._pending._deletedItems[identity] = item; + + //Remove from the toplevel items, if necessary... + if(item[this._rootItemPropName]){ + this._removeArrayElement(this._arrayOfTopLevelItems, item); + } + this.onDelete(item); // dojo/data/api/Notification call + return true; + }, + + setValue: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){ + // summary: + // See dojo/data/api/Write.set() + return this._setValueOrValues(item, attribute, value, true); // boolean + }, + + setValues: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute, /* array */ values){ + // summary: + // See dojo/data/api/Write.setValues() + return this._setValueOrValues(item, attribute, values, true); // boolean + }, + + unsetAttribute: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute){ + // summary: + // See dojo/data/api/Write.unsetAttribute() + return this._setValueOrValues(item, attribute, [], true); + }, + + _setValueOrValues: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){ + this._assert(!this._saveInProgress); + + // Check for valid arguments + this._assertIsItem(item); + this._assert(lang.isString(attribute)); + this._assert(typeof newValueOrValues !== "undefined"); + + // Make sure the user isn't trying to change the item's identity + var identifierAttribute = this._getIdentifierAttribute(); + if(attribute == identifierAttribute){ + throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier."); + } + + // To implement the Notification API, we need to make a note of what + // the old attribute value was, so that we can pass that info when + // we call the onSet method. + var oldValueOrValues = this._getValueOrValues(item, attribute); + + var identity = this.getIdentity(item); + if(!this._pending._modifiedItems[identity]){ + // Before we actually change the item, we make a copy of it to + // record the original state, so that we'll be able to revert if + // the revert method gets called. If the item has already been + // modified then there's no need to do this now, since we already + // have a record of the original state. + var copyOfItemState = {}; + for(var key in item){ + if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){ + copyOfItemState[key] = item[key]; + }else if(key === this._reverseRefMap){ + copyOfItemState[key] = lang.clone(item[key]); + }else{ + copyOfItemState[key] = item[key].slice(0, item[key].length); + } + } + // Now mark the item as dirty, and save the copy of the original state + this._pending._modifiedItems[identity] = copyOfItemState; + } + + // Okay, now we can actually change this attribute on the item + var success = false; + + if(lang.isArray(newValueOrValues) && newValueOrValues.length === 0){ + + // If we were passed an empty array as the value, that counts + // as "unsetting" the attribute, so we need to remove this + // attribute from the item. + success = delete item[attribute]; + newValueOrValues = undefined; // used in the onSet Notification call below + + if(this.referenceIntegrity && oldValueOrValues){ + var oldValues = oldValueOrValues; + if(!lang.isArray(oldValues)){ + oldValues = [oldValues]; + } + for(var i = 0; i < oldValues.length; i++){ + var value = oldValues[i]; + if(this.isItem(value)){ + this._removeReferenceFromMap(value, item, attribute); + } + } + } + }else{ + var newValueArray; + if(lang.isArray(newValueOrValues)){ + // Unfortunately, it's not safe to just do this: + // newValueArray = newValueOrValues; + // Instead, we need to copy the array, which slice() does very nicely. + // This is so that our internal data structure won't + // get corrupted if the user mucks with the values array *after* + // calling setValues(). + newValueArray = newValueOrValues.slice(0, newValueOrValues.length); + }else{ + newValueArray = [newValueOrValues]; + } + + //We need to handle reference integrity if this is on. + //In the case of set, we need to see if references were added or removed + //and update the reference tracking map accordingly. + if(this.referenceIntegrity){ + if(oldValueOrValues){ + var oldValues = oldValueOrValues; + if(!lang.isArray(oldValues)){ + oldValues = [oldValues]; + } + //Use an associative map to determine what was added/removed from the list. + //Should be O(n) performant. First look at all the old values and make a list of them + //Then for any item not in the old list, we add it. If it was already present, we remove it. + //Then we pass over the map and any references left it it need to be removed (IE, no match in + //the new values list). + var map = {}; + arrayUtil.forEach(oldValues, function(possibleItem){ + if(this.isItem(possibleItem)){ + var id = this.getIdentity(possibleItem); + map[id.toString()] = true; + } + }, this); + arrayUtil.forEach(newValueArray, function(possibleItem){ + if(this.isItem(possibleItem)){ + var id = this.getIdentity(possibleItem); + if(map[id.toString()]){ + delete map[id.toString()]; + }else{ + this._addReferenceToMap(possibleItem, item, attribute); + } + } + }, this); + for(var rId in map){ + var removedItem; + if(this._itemsByIdentity){ + removedItem = this._itemsByIdentity[rId]; + }else{ + removedItem = this._arrayOfAllItems[rId]; + } + this._removeReferenceFromMap(removedItem, item, attribute); + } + }else{ + //Everything is new (no old values) so we have to just + //insert all the references, if any. + for(var i = 0; i < newValueArray.length; i++){ + var value = newValueArray[i]; + if(this.isItem(value)){ + this._addReferenceToMap(value, item, attribute); + } + } + } + } + item[attribute] = newValueArray; + success = true; + } + + // Now we make the dojo/data/api/Notification call + if(callOnSet){ + this.onSet(item, attribute, oldValueOrValues, newValueOrValues); + } + return success; // boolean + }, + + _addReferenceToMap: function(/* dojo/data/api/Item */ refItem, /* dojo/data/api/Item */ parentItem, /* string */ attribute){ + // summary: + // Method to add an reference map entry for an item and attribute. + // description: + // Method to add an reference map entry for an item and attribute. + // refItem: + // The item that is referenced. + // parentItem: + // The item that holds the new reference to refItem. + // attribute: + // The attribute on parentItem that contains the new reference. + + var parentId = this.getIdentity(parentItem); + var references = refItem[this._reverseRefMap]; + + if(!references){ + references = refItem[this._reverseRefMap] = {}; + } + var itemRef = references[parentId]; + if(!itemRef){ + itemRef = references[parentId] = {}; + } + itemRef[attribute] = true; + }, + + _removeReferenceFromMap: function(/* dojo/data/api/Item */ refItem, /* dojo/data/api/Item */ parentItem, /* string */ attribute){ + // summary: + // Method to remove an reference map entry for an item and attribute. + // description: + // Method to remove an reference map entry for an item and attribute. This will + // also perform cleanup on the map such that if there are no more references at all to + // the item, its reference object and entry are removed. + // refItem: + // The item that is referenced. + // parentItem: + // The item holding a reference to refItem. + // attribute: + // The attribute on parentItem that contains the reference. + var identity = this.getIdentity(parentItem); + var references = refItem[this._reverseRefMap]; + var itemId; + if(references){ + for(itemId in references){ + if(itemId == identity){ + delete references[itemId][attribute]; + if(this._isEmpty(references[itemId])){ + delete references[itemId]; + } + } + } + if(this._isEmpty(references)){ + delete refItem[this._reverseRefMap]; + } + } + }, + + _dumpReferenceMap: function(){ + // summary: + // Function to dump the reverse reference map of all items in the store for debug purposes. + // description: + // Function to dump the reverse reference map of all items in the store for debug purposes. + var i; + for(i = 0; i < this._arrayOfAllItems.length; i++){ + var item = this._arrayOfAllItems[i]; + if(item && item[this._reverseRefMap]){ + console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + jsonUtil.toJson(item[this._reverseRefMap])); + } + } + }, + + _getValueOrValues: function(/* dojo/data/api/Item */ item, /* attribute-name-string */ attribute){ + var valueOrValues = undefined; + if(this.hasAttribute(item, attribute)){ + var valueArray = this.getValues(item, attribute); + if(valueArray.length == 1){ + valueOrValues = valueArray[0]; + }else{ + valueOrValues = valueArray; + } + } + return valueOrValues; + }, + + _flatten: function(/* anything */ value){ + if(this.isItem(value)){ + // Given an item, return an serializable object that provides a + // reference to the item. + // For example, given kermit: + // var kermit = store.newItem({id:2, name:"Kermit"}); + // we want to return + // {_reference:2} + return {_reference: this.getIdentity(value)}; + }else{ + if(typeof value === "object"){ + for(var type in this._datatypeMap){ + var typeMap = this._datatypeMap[type]; + if(lang.isObject(typeMap) && !lang.isFunction(typeMap)){ + if(value instanceof typeMap.type){ + if(!typeMap.serialize){ + throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]"); + } + return {_type: type, _value: typeMap.serialize(value)}; + } + }else if(value instanceof typeMap){ + //SImple mapping, therefore, return as a toString serialization. + return {_type: type, _value: value.toString()}; + } + } + } + return value; + } + }, + + _getNewFileContentString: function(){ + // summary: + // Generate a string that can be saved to a file. + // The result should look similar to: + // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json + var serializableStructure = {}; + + var identifierAttribute = this._getIdentifierAttribute(); + if(identifierAttribute !== Number){ + serializableStructure.identifier = identifierAttribute; + } + if(this._labelAttr){ + serializableStructure.label = this._labelAttr; + } + serializableStructure.items = []; + for(var i = 0; i < this._arrayOfAllItems.length; ++i){ + var item = this._arrayOfAllItems[i]; + if(item !== null){ + var serializableItem = {}; + for(var key in item){ + if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){ + var valueArray = this.getValues(item, key); + if(valueArray.length == 1){ + serializableItem[key] = this._flatten(valueArray[0]); + }else{ + var serializableArray = []; + for(var j = 0; j < valueArray.length; ++j){ + serializableArray.push(this._flatten(valueArray[j])); + serializableItem[key] = serializableArray; + } + } + } + } + serializableStructure.items.push(serializableItem); + } + } + var prettyPrint = true; + return jsonUtil.toJson(serializableStructure, prettyPrint); + }, + + _isEmpty: function(something){ + // summary: + // Function to determine if an array or object has no properties or values. + // something: + // The array or object to examine. + var empty = true; + if(lang.isObject(something)){ + var i; + for(i in something){ + empty = false; + break; + } + }else if(lang.isArray(something)){ + if(something.length > 0){ + empty = false; + } + } + return empty; //boolean + }, + + save: function(/* object */ keywordArgs){ + // summary: + // See dojo/data/api/Write.save() + this._assert(!this._saveInProgress); + + // this._saveInProgress is set to true, briefly, from when save is first called to when it completes + this._saveInProgress = true; + + var self = this; + var saveCompleteCallback = function(){ + self._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + + self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks + if(keywordArgs && keywordArgs.onComplete){ + var scope = keywordArgs.scope || kernel.global; + keywordArgs.onComplete.call(scope); + } + }; + var saveFailedCallback = function(err){ + self._saveInProgress = false; + if(keywordArgs && keywordArgs.onError){ + var scope = keywordArgs.scope || kernel.global; + keywordArgs.onError.call(scope, err); + } + }; + + if(this._saveEverything){ + var newFileContentString = this._getNewFileContentString(); + this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString); + } + if(this._saveCustom){ + this._saveCustom(saveCompleteCallback, saveFailedCallback); + } + if(!this._saveEverything && !this._saveCustom){ + // Looks like there is no user-defined save-handler function. + // That's fine, it just means the datastore is acting as a "mock-write" + // store -- changes get saved in memory but don't get saved to disk. + saveCompleteCallback(); + } + }, + + revert: function(){ + // summary: + // See dojo/data/api/Write.revert() + this._assert(!this._saveInProgress); + + var identity; + for(identity in this._pending._modifiedItems){ + // find the original item and the modified item that replaced it + var copyOfItemState = this._pending._modifiedItems[identity]; + var modifiedItem = null; + if(this._itemsByIdentity){ + modifiedItem = this._itemsByIdentity[identity]; + }else{ + modifiedItem = this._arrayOfAllItems[identity]; + } + + // Restore the original item into a full-fledged item again, we want to try to + // keep the same object instance as if we don't it, causes bugs like #9022. + copyOfItemState[this._storeRefPropName] = this; + for(var key in modifiedItem){ + delete modifiedItem[key]; + } + lang.mixin(modifiedItem, copyOfItemState); + } + var deletedItem; + for(identity in this._pending._deletedItems){ + deletedItem = this._pending._deletedItems[identity]; + deletedItem[this._storeRefPropName] = this; + var index = deletedItem[this._itemNumPropName]; + + //Restore the reverse refererence map, if any. + if(deletedItem["backup_" + this._reverseRefMap]){ + deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap]; + delete deletedItem["backup_" + this._reverseRefMap]; + } + this._arrayOfAllItems[index] = deletedItem; + if(this._itemsByIdentity){ + this._itemsByIdentity[identity] = deletedItem; + } + if(deletedItem[this._rootItemPropName]){ + this._arrayOfTopLevelItems.push(deletedItem); + } + } + //We have to pass through it again and restore the reference maps after all the + //undeletes have occurred. + for(identity in this._pending._deletedItems){ + deletedItem = this._pending._deletedItems[identity]; + if(deletedItem["backupRefs_" + this._reverseRefMap]){ + arrayUtil.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){ + var refItem; + if(this._itemsByIdentity){ + refItem = this._itemsByIdentity[reference.id]; + }else{ + refItem = this._arrayOfAllItems[reference.id]; + } + this._addReferenceToMap(refItem, deletedItem, reference.attr); + }, this); + delete deletedItem["backupRefs_" + this._reverseRefMap]; + } + } + + for(identity in this._pending._newItems){ + var newItem = this._pending._newItems[identity]; + newItem[this._storeRefPropName] = null; + // null out the new item, but don't change the array index so + // so we can keep using _arrayOfAllItems.length. + this._arrayOfAllItems[newItem[this._itemNumPropName]] = null; + if(newItem[this._rootItemPropName]){ + this._removeArrayElement(this._arrayOfTopLevelItems, newItem); + } + if(this._itemsByIdentity){ + delete this._itemsByIdentity[identity]; + } + } + + this._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + return true; // boolean + }, + + isDirty: function(/* item? */ item){ + // summary: + // See dojo/data/api/Write.isDirty() + if(item){ + // return true if the item is dirty + var identity = this.getIdentity(item); + return new Boolean(this._pending._newItems[identity] || + this._pending._modifiedItems[identity] || + this._pending._deletedItems[identity]).valueOf(); // boolean + }else{ + // return true if the store is dirty -- which means return true + // if there are any new items, dirty items, or modified items + return !this._isEmpty(this._pending._newItems) || + !this._isEmpty(this._pending._modifiedItems) || + !this._isEmpty(this._pending._deletedItems); // boolean + } + }, + +/* dojo/data/api/Notification */ + + onSet: function(/* dojo/data/api/Item */ item, + /*attribute-name-string*/ attribute, + /*object|array*/ oldValue, + /*object|array*/ newValue){ + // summary: + // See dojo/data/api/Notification.onSet() + + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, + + onNew: function(/* dojo/data/api/Item */ newItem, /*object?*/ parentInfo){ + // summary: + // See dojo/data/api/Notification.onNew() + + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, + + onDelete: function(/* dojo/data/api/Item */ deletedItem){ + // summary: + // See dojo/data/api/Notification.onDelete() + + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, + + close: function(/* object? */ request){ + // summary: + // Over-ride of base close function of ItemFileReadStore to add in check for store state. + // description: + // Over-ride of base close function of ItemFileReadStore to add in check for store state. + // If the store is still dirty (unsaved changes), then an error will be thrown instead of + // clearing the internal state for reload from the url. + + //Clear if not dirty ... or throw an error + if(this.clearOnClose){ + if(!this.isDirty()){ + this.inherited(arguments); + }else{ + //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved). + throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close."); + } + } + } +}); + +}); diff --git a/lib/dojo/data/ObjectStore.js b/lib/dojo/data/ObjectStore.js index f0c241379..a2acacb41 100644 --- a/lib/dojo/data/ObjectStore.js +++ b/lib/dojo/data/ObjectStore.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/ObjectStore",["../_base/lang","../Evented","../_base/declare","../_base/Deferred","../_base/array","../_base/connect","../regexp"],function(_1,_2,_3,_4,_5,_6,_7){return _3("dojo.data.ObjectStore",[_2],{objectStore:null,constructor:function(_8){_1.mixin(this,_8);},labelProperty:"label",getValue:function(_9,_a,_b){return typeof _9.get==="function"?_9.get(_a):_a in _9?_9[_a]:_b;},getValues:function(_c,_d){var _e=this.getValue(_c,_d);return _e instanceof Array?_e:_e===undefined?[]:[_e];},getAttributes:function(_f){var res=[];for(var i in _f){if(_f.hasOwnProperty(i)&&!(i.charAt(0)=="_"&&i.charAt(1)=="_")){res.push(i);}}return res;},hasAttribute:function(_10,_11){return _11 in _10;},containsValue:function(_12,_13,_14){return _5.indexOf(this.getValues(_12,_13),_14)>-1;},isItem:function(_15){return (typeof _15=="object")&&_15&&!(_15 instanceof Date);},isItemLoaded:function(_16){return _16&&typeof _16.load!=="function";},loadItem:function(_17){var _18;if(typeof _17.item.load==="function"){_4.when(_17.item.load(),function(_19){_18=_19;var _1a=_19 instanceof Error?_17.onError:_17.onItem;if(_1a){_1a.call(_17.scope,_19);}});}else{if(_17.onItem){_17.onItem.call(_17.scope,_17.item);}}return _18;},close:function(_1b){return _1b&&_1b.abort&&_1b.abort();},fetch:function(_1c){_1c=_1.delegate(_1c,_1c&&_1c.queryOptions);var _1d=this;var _1e=_1c.scope||_1d;var _1f=_1c.query;if(typeof _1f=="object"){_1f=_1.delegate(_1f);for(var i in _1f){var _20=_1f[i];if(typeof _20=="string"){_1f[i]=RegExp("^"+_7.escapeString(_20,"*?").replace(/\*/g,".*").replace(/\?/g,".")+"$",_1c.ignoreCase?"mi":"m");_1f[i].toString=(function(_21){return function(){return _21;};})(_20);}}}var _22=this.objectStore.query(_1f,_1c);_4.when(_22.total,function(_23){_4.when(_22,function(_24){if(_1c.onBegin){_1c.onBegin.call(_1e,_23||_24.length,_1c);}if(_1c.onItem){for(var i=0;i<_24.length;i++){_1c.onItem.call(_1e,_24[i],_1c);}}if(_1c.onComplete){_1c.onComplete.call(_1e,_1c.onItem?null:_24,_1c);}return _24;},_25);},_25);function _25(_26){if(_1c.onError){_1c.onError.call(_1e,_26,_1c);}};_1c.abort=function(){if(_22.cancel){_22.cancel();}};if(_22.observe){if(this.observing){this.observing.cancel();}this.observing=_22.observe(function(_27,_28,_29){if(_5.indexOf(_1d._dirtyObjects,_27)==-1){if(_28==-1){_1d.onNew(_27);}else{if(_29==-1){_1d.onDelete(_27);}else{for(var i in _27){if(i!=_1d.objectStore.idProperty){_1d.onSet(_27,i,null,_27[i]);}}}}}},true);}this.onFetch(_22);_1c.store=this;return _1c;},getFeatures:function(){return {"dojo.data.api.Read":!!this.objectStore.get,"dojo.data.api.Identity":true,"dojo.data.api.Write":!!this.objectStore.put,"dojo.data.api.Notification":true};},getLabel:function(_2a){if(this.isItem(_2a)){return this.getValue(_2a,this.labelProperty);}return undefined;},getLabelAttributes:function(_2b){return [this.labelProperty];},getIdentity:function(_2c){return this.objectStore.getIdentity?this.objectStore.getIdentity(_2c):_2c[this.objectStore.idProperty||"id"];},getIdentityAttributes:function(_2d){return [this.objectStore.idProperty];},fetchItemByIdentity:function(_2e){var _2f;_4.when(this.objectStore.get(_2e.identity),function(_30){_2f=_30;_2e.onItem.call(_2e.scope,_30);},function(_31){_2e.onError.call(_2e.scope,_31);});return _2f;},newItem:function(_32,_33){if(_33){var _34=this.getValue(_33.parent,_33.attribute,[]);_34=_34.concat([_32]);_32.__parent=_34;this.setValue(_33.parent,_33.attribute,_34);}this._dirtyObjects.push({object:_32,save:true});this.onNew(_32);return _32;},deleteItem:function(_35){this.changing(_35,true);this.onDelete(_35);},setValue:function(_36,_37,_38){var old=_36[_37];this.changing(_36);_36[_37]=_38;this.onSet(_36,_37,old,_38);},setValues:function(_39,_3a,_3b){if(!_1.isArray(_3b)){throw new Error("setValues expects to be passed an Array object as its value");}this.setValue(_39,_3a,_3b);},unsetAttribute:function(_3c,_3d){this.changing(_3c);var old=_3c[_3d];delete _3c[_3d];this.onSet(_3c,_3d,old,undefined);},_dirtyObjects:[],changing:function(_3e,_3f){_3e.__isDirty=true;for(var i=0;i<this._dirtyObjects.length;i++){var _40=this._dirtyObjects[i];if(_3e==_40.object){if(_3f){_40.object=false;if(!this._saveNotNeeded){_40.save=true;}}return;}}var old=_3e instanceof Array?[]:{};for(i in _3e){if(_3e.hasOwnProperty(i)){old[i]=_3e[i];}}this._dirtyObjects.push({object:!_3f&&_3e,old:old,save:!this._saveNotNeeded});},save:function(_41){_41=_41||{};var _42,_43=[];var _44=[];var _45=this;var _46=this._dirtyObjects;var _47=_46.length;try{_6.connect(_41,"onError",function(){if(_41.revertOnError!==false){var _48=_46;_46=_44;_45.revert();_45._dirtyObjects=_48;}else{_45._dirtyObjects=_46.concat(_44);}});if(this.objectStore.transaction){var _49=this.objectStore.transaction();}for(var i=0;i<_46.length;i++){var _4a=_46[i];var _4b=_4a.object;var old=_4a.old;delete _4b.__isDirty;if(_4b){_42=this.objectStore.put(_4b,{overwrite:!!old});}else{if(typeof old!="undefined"){_42=this.objectStore.remove(this.getIdentity(old));}}_44.push(_4a);_46.splice(i--,1);_4.when(_42,function(_4c){if(!(--_47)){if(_41.onComplete){_41.onComplete.call(_41.scope,_43);}}},function(_4d){_47=-1;_41.onError.call(_41.scope,_4d);});}if(_49){_49.commit();}}catch(e){_41.onError.call(_41.scope,value);}},revert:function(_4e){var _4f=this._dirtyObjects;for(var i=_4f.length;i>0;){i--;var _50=_4f[i];var _51=_50.object;var old=_50.old;if(_51&&old){for(var j in old){if(old.hasOwnProperty(j)&&_51[j]!==old[j]){this.onSet(_51,j,_51[j],old[j]);_51[j]=old[j];}}for(j in _51){if(!old.hasOwnProperty(j)){this.onSet(_51,j,_51[j]);delete _51[j];}}}else{if(!old){this.onDelete(_51);}else{this.onNew(old);}}delete (_51||old).__isDirty;_4f.splice(i,1);}},isDirty:function(_52){if(!_52){return !!this._dirtyObjects.length;}return _52.__isDirty;},onSet:function(){},onNew:function(){},onDelete:function(){},onFetch:function(_53){}});});
\ No newline at end of file +define("dojo/data/ObjectStore",["../_base/lang","../Evented","../_base/declare","../_base/Deferred","../_base/array","../_base/connect","../regexp"],function(_1,_2,_3,_4,_5,_6,_7){function _8(_9){return _9=="*"?".*":_9=="?"?".":_9;};return _3("dojo.data.ObjectStore",[_2],{objectStore:null,constructor:function(_a){this._dirtyObjects=[];if(_a.labelAttribute){_a.labelProperty=_a.labelAttribute;}_1.mixin(this,_a);},labelProperty:"label",getValue:function(_b,_c,_d){return typeof _b.get==="function"?_b.get(_c):_c in _b?_b[_c]:_d;},getValues:function(_e,_f){var val=this.getValue(_e,_f);return val instanceof Array?val:val===undefined?[]:[val];},getAttributes:function(_10){var res=[];for(var i in _10){if(_10.hasOwnProperty(i)&&!(i.charAt(0)=="_"&&i.charAt(1)=="_")){res.push(i);}}return res;},hasAttribute:function(_11,_12){return _12 in _11;},containsValue:function(_13,_14,_15){return _5.indexOf(this.getValues(_13,_14),_15)>-1;},isItem:function(_16){return (typeof _16=="object")&&_16&&!(_16 instanceof Date);},isItemLoaded:function(_17){return _17&&typeof _17.load!=="function";},loadItem:function(_18){var _19;if(typeof _18.item.load==="function"){_4.when(_18.item.load(),function(_1a){_19=_1a;var _1b=_1a instanceof Error?_18.onError:_18.onItem;if(_1b){_1b.call(_18.scope,_1a);}});}else{if(_18.onItem){_18.onItem.call(_18.scope,_18.item);}}return _19;},close:function(_1c){return _1c&&_1c.abort&&_1c.abort();},fetch:function(_1d){_1d=_1.delegate(_1d,_1d&&_1d.queryOptions);var _1e=this;var _1f=_1d.scope||_1e;var _20=_1d.query;if(typeof _20=="object"){_20=_1.delegate(_20);for(var i in _20){var _21=_20[i];if(typeof _21=="string"){_20[i]=RegExp("^"+_7.escapeString(_21,"*?\\").replace(/\\.|\*|\?/g,_8)+"$",_1d.ignoreCase?"mi":"m");_20[i].toString=(function(_22){return function(){return _22;};})(_21);}}}var _23=this.objectStore.query(_20,_1d);_4.when(_23.total,function(_24){_4.when(_23,function(_25){if(_1d.onBegin){_1d.onBegin.call(_1f,_24||_25.length,_1d);}if(_1d.onItem){for(var i=0;i<_25.length;i++){_1d.onItem.call(_1f,_25[i],_1d);}}if(_1d.onComplete){_1d.onComplete.call(_1f,_1d.onItem?null:_25,_1d);}return _25;},_26);},_26);function _26(_27){if(_1d.onError){_1d.onError.call(_1f,_27,_1d);}};_1d.abort=function(){if(_23.cancel){_23.cancel();}};if(_23.observe){if(this.observing){this.observing.cancel();}this.observing=_23.observe(function(_28,_29,_2a){if(_5.indexOf(_1e._dirtyObjects,_28)==-1){if(_29==-1){_1e.onNew(_28);}else{if(_2a==-1){_1e.onDelete(_28);}else{for(var i in _28){if(i!=_1e.objectStore.idProperty){_1e.onSet(_28,i,null,_28[i]);}}}}}},true);}this.onFetch(_23);_1d.store=this;return _1d;},getFeatures:function(){return {"dojo.data.api.Read":!!this.objectStore.get,"dojo.data.api.Identity":true,"dojo.data.api.Write":!!this.objectStore.put,"dojo.data.api.Notification":true};},getLabel:function(_2b){if(this.isItem(_2b)){return this.getValue(_2b,this.labelProperty);}return undefined;},getLabelAttributes:function(_2c){return [this.labelProperty];},getIdentity:function(_2d){return this.objectStore.getIdentity?this.objectStore.getIdentity(_2d):_2d[this.objectStore.idProperty||"id"];},getIdentityAttributes:function(_2e){return [this.objectStore.idProperty];},fetchItemByIdentity:function(_2f){var _30;_4.when(this.objectStore.get(_2f.identity),function(_31){_30=_31;_2f.onItem.call(_2f.scope,_31);},function(_32){_2f.onError.call(_2f.scope,_32);});return _30;},newItem:function(_33,_34){if(_34){var _35=this.getValue(_34.parent,_34.attribute,[]);_35=_35.concat([_33]);_33.__parent=_35;this.setValue(_34.parent,_34.attribute,_35);}this._dirtyObjects.push({object:_33,save:true});this.onNew(_33);return _33;},deleteItem:function(_36){this.changing(_36,true);this.onDelete(_36);},setValue:function(_37,_38,_39){var old=_37[_38];this.changing(_37);_37[_38]=_39;this.onSet(_37,_38,old,_39);},setValues:function(_3a,_3b,_3c){if(!_1.isArray(_3c)){throw new Error("setValues expects to be passed an Array object as its value");}this.setValue(_3a,_3b,_3c);},unsetAttribute:function(_3d,_3e){this.changing(_3d);var old=_3d[_3e];delete _3d[_3e];this.onSet(_3d,_3e,old,undefined);},changing:function(_3f,_40){_3f.__isDirty=true;for(var i=0;i<this._dirtyObjects.length;i++){var _41=this._dirtyObjects[i];if(_3f==_41.object){if(_40){_41.object=false;if(!this._saveNotNeeded){_41.save=true;}}return;}}var old=_3f instanceof Array?[]:{};for(i in _3f){if(_3f.hasOwnProperty(i)){old[i]=_3f[i];}}this._dirtyObjects.push({object:!_40&&_3f,old:old,save:!this._saveNotNeeded});},save:function(_42){_42=_42||{};var _43,_44=[];var _45=[];var _46=this;var _47=this._dirtyObjects;var _48=_47.length;try{_6.connect(_42,"onError",function(){if(_42.revertOnError!==false){var _49=_47;_47=_45;_46.revert();_46._dirtyObjects=_49;}else{_46._dirtyObjects=_47.concat(_45);}});if(this.objectStore.transaction){var _4a=this.objectStore.transaction();}for(var i=0;i<_47.length;i++){var _4b=_47[i];var _4c=_4b.object;var old=_4b.old;delete _4c.__isDirty;if(_4c){_43=this.objectStore.put(_4c,{overwrite:!!old});}else{if(typeof old!="undefined"){_43=this.objectStore.remove(this.getIdentity(old));}}_45.push(_4b);_47.splice(i--,1);_4.when(_43,function(_4d){if(!(--_48)){if(_42.onComplete){_42.onComplete.call(_42.scope,_44);}}},function(_4e){_48=-1;_42.onError.call(_42.scope,_4e);});}if(_4a){_4a.commit();}}catch(e){_42.onError.call(_42.scope,value);}},revert:function(){var _4f=this._dirtyObjects;for(var i=_4f.length;i>0;){i--;var _50=_4f[i];var _51=_50.object;var old=_50.old;if(_51&&old){for(var j in old){if(old.hasOwnProperty(j)&&_51[j]!==old[j]){this.onSet(_51,j,_51[j],old[j]);_51[j]=old[j];}}for(j in _51){if(!old.hasOwnProperty(j)){this.onSet(_51,j,_51[j]);delete _51[j];}}}else{if(!old){this.onDelete(_51);}else{this.onNew(old);}}delete (_51||old).__isDirty;_4f.splice(i,1);}},isDirty:function(_52){if(!_52){return !!this._dirtyObjects.length;}return _52.__isDirty;},onSet:function(){},onNew:function(){},onDelete:function(){},onFetch:function(_53){}});});
\ No newline at end of file diff --git a/lib/dojo/data/ObjectStore.js.uncompressed.js b/lib/dojo/data/ObjectStore.js.uncompressed.js new file mode 100644 index 000000000..ff0ac99c7 --- /dev/null +++ b/lib/dojo/data/ObjectStore.js.uncompressed.js @@ -0,0 +1,544 @@ +define("dojo/data/ObjectStore", ["../_base/lang", "../Evented", "../_base/declare", "../_base/Deferred", "../_base/array", + "../_base/connect", "../regexp" +], function(lang, Evented, declare, Deferred, array, connect, regexp){ + +// module: +// dojo/data/ObjectStore + +function convertRegex(character){ + return character == '*' ? '.*' : character == '?' ? '.' : character; +} +return declare("dojo.data.ObjectStore", [Evented],{ + // summary: + // A Dojo Data implementation that wraps Dojo object stores for backwards + // compatibility. + + objectStore: null, + constructor: function(options){ + // options: + // The configuration information to pass into the data store. + // + // - options.objectStore: + // + // The object store to use as the source provider for this data store + + this._dirtyObjects = []; + if(options.labelAttribute){ + // accept the old labelAttribute to make it easier to switch from old data stores + options.labelProperty = options.labelAttribute; + } + lang.mixin(this, options); + }, + labelProperty: "label", + + getValue: function(/*Object*/ item, /*String*/property, /*value?*/defaultValue){ + // summary: + // Gets the value of an item's 'property' + // item: + // The item to get the value from + // property: + // property to look up value for + // defaultValue: + // the default value + + return typeof item.get === "function" ? item.get(property) : + property in item ? + item[property] : defaultValue; + }, + getValues: function(item, property){ + // summary: + // Gets the value of an item's 'property' and returns + // it. If this value is an array it is just returned, + // if not, the value is added to an array and that is returned. + // item: Object + // property: String + // property to look up value for + + var val = this.getValue(item,property); + return val instanceof Array ? val : val === undefined ? [] : [val]; + }, + + getAttributes: function(item){ + // summary: + // Gets the available attributes of an item's 'property' and returns + // it as an array. + // item: Object + + var res = []; + for(var i in item){ + if(item.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){ + res.push(i); + } + } + return res; + }, + + hasAttribute: function(item,attribute){ + // summary: + // Checks to see if item has attribute + // item: Object + // The item to check + // attribute: String + // The attribute to check + return attribute in item; + }, + + containsValue: function(item, attribute, value){ + // summary: + // Checks to see if 'item' has 'value' at 'attribute' + // item: Object + // The item to check + // attribute: String + // The attribute to check + // value: Anything + // The value to look for + return array.indexOf(this.getValues(item,attribute),value) > -1; + }, + + + isItem: function(item){ + // summary: + // Checks to see if the argument is an item + // item: Object + // The item to check + + // we have no way of determining if it belongs, we just have object returned from + // service queries + return (typeof item == 'object') && item && !(item instanceof Date); + }, + + isItemLoaded: function(item){ + // summary: + // Checks to see if the item is loaded. + // item: Object + // The item to check + + return item && typeof item.load !== "function"; + }, + + loadItem: function(args){ + // summary: + // Loads an item and calls the callback handler. Note, that this will call the callback + // handler even if the item is loaded. Consequently, you can use loadItem to ensure + // that an item is loaded is situations when the item may or may not be loaded yet. + // If you access a value directly through property access, you can use this to load + // a lazy value as well (doesn't need to be an item). + // args: Object + // See dojo/data/api/Read.fetch() + // example: + // | store.loadItem({ + // | item: item, // this item may or may not be loaded + // | onItem: function(item){ + // | // do something with the item + // | } + // | }); + + var item; + if(typeof args.item.load === "function"){ + Deferred.when(args.item.load(), function(result){ + item = result; // in synchronous mode this can allow loadItem to return the value + var func = result instanceof Error ? args.onError : args.onItem; + if(func){ + func.call(args.scope, result); + } + }); + }else if(args.onItem){ + // even if it is already loaded, we will use call the callback, this makes it easier to + // use when it is not known if the item is loaded (you can always safely call loadItem). + args.onItem.call(args.scope, args.item); + } + return item; + }, + close: function(request){ + // summary: + // See dojo/data/api/Read.close() + return request && request.abort && request.abort(); + }, + fetch: function(args){ + // summary: + // See dojo/data/api/Read.fetch() + + args = lang.delegate(args, args && args.queryOptions); + var self = this; + var scope = args.scope || self; + var query = args.query; + if(typeof query == "object"){ // can be null, but that is ignore by for-in + query = lang.delegate(query); // don't modify the original + for(var i in query){ + // find any strings and convert them to regular expressions for wildcard support + var required = query[i]; + if(typeof required == "string"){ + query[i] = RegExp("^" + regexp.escapeString(required, "*?\\").replace(/\\.|\*|\?/g, convertRegex) + "$", args.ignoreCase ? "mi" : "m"); + query[i].toString = (function(original){ + return function(){ + return original; + }; + })(required); + } + } + } + + var results = this.objectStore.query(query, args); + Deferred.when(results.total, function(totalCount){ + Deferred.when(results, function(results){ + if(args.onBegin){ + args.onBegin.call(scope, totalCount || results.length, args); + } + if(args.onItem){ + for(var i=0; i<results.length;i++){ + args.onItem.call(scope, results[i], args); + } + } + if(args.onComplete){ + args.onComplete.call(scope, args.onItem ? null : results, args); + } + return results; + }, errorHandler); + }, errorHandler); + function errorHandler(error){ + if(args.onError){ + args.onError.call(scope, error, args); + } + } + args.abort = function(){ + // abort the request + if(results.cancel){ + results.cancel(); + } + }; + if(results.observe){ + if(this.observing){ + // if we were previously observing, cancel the last time to avoid multiple notifications. Just the best we can do for the impedance mismatch between APIs + this.observing.cancel(); + } + this.observing = results.observe(function(object, removedFrom, insertedInto){ + if(array.indexOf(self._dirtyObjects, object) == -1){ + if(removedFrom == -1){ + self.onNew(object); + } + else if(insertedInto == -1){ + self.onDelete(object); + } + else{ + for(var i in object){ + if(i != self.objectStore.idProperty){ + self.onSet(object, i, null, object[i]); + } + } + } + } + }, true); + } + this.onFetch(results); + args.store = this; + return args; + }, + getFeatures: function(){ + // summary: + // return the store feature set + + return { + "dojo.data.api.Read": !!this.objectStore.get, + "dojo.data.api.Identity": true, + "dojo.data.api.Write": !!this.objectStore.put, + "dojo.data.api.Notification": true + }; + }, + + getLabel: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Read.getLabel() + if(this.isItem(item)){ + return this.getValue(item,this.labelProperty); //String + } + return undefined; //undefined + }, + + getLabelAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // See dojo/data/api/Read.getLabelAttributes() + return [this.labelProperty]; //array + }, + + //Identity API Support + + + getIdentity: function(item){ + // summary: + // returns the identity of the given item + // See dojo/data/api/Read.getIdentity() + return this.objectStore.getIdentity ? this.objectStore.getIdentity(item) : item[this.objectStore.idProperty || "id"]; + }, + + getIdentityAttributes: function(item){ + // summary: + // returns the attributes which are used to make up the + // identity of an item. Basically returns this.objectStore.idProperty + // See dojo/data/api/Read.getIdentityAttributes() + + return [this.objectStore.idProperty]; + }, + + fetchItemByIdentity: function(args){ + // summary: + // fetch an item by its identity, by looking in our index of what we have loaded + var item; + Deferred.when(this.objectStore.get(args.identity), + function(result){ + item = result; + args.onItem.call(args.scope, result); + }, + function(error){ + args.onError.call(args.scope, error); + } + ); + return item; + }, + + newItem: function(data, parentInfo){ + // summary: + // adds a new item to the store at the specified point. + // Takes two parameters, data, and options. + // data: Object + // The data to be added in as an item. + // data: Object + // See dojo/data/api/Write.newItem() + + if(parentInfo){ + // get the previous value or any empty array + var values = this.getValue(parentInfo.parent,parentInfo.attribute,[]); + // set the new value + values = values.concat([data]); + data.__parent = values; + this.setValue(parentInfo.parent, parentInfo.attribute, values); + } + this._dirtyObjects.push({object:data, save: true}); + this.onNew(data); + return data; + }, + deleteItem: function(item){ + // summary: + // deletes item and any references to that item from the store. + // item: + // item to delete + + // If the desire is to delete only one reference, unsetAttribute or + // setValue is the way to go. + this.changing(item, true); + + this.onDelete(item); + }, + setValue: function(item, attribute, value){ + // summary: + // sets 'attribute' on 'item' to 'value' + // See dojo/data/api/Write.setValue() + + var old = item[attribute]; + this.changing(item); + item[attribute]=value; + this.onSet(item,attribute,old,value); + }, + setValues: function(item, attribute, values){ + // summary: + // sets 'attribute' on 'item' to 'value' value + // must be an array. + // See dojo/data/api/Write.setValues() + + if(!lang.isArray(values)){ + throw new Error("setValues expects to be passed an Array object as its value"); + } + this.setValue(item,attribute,values); + }, + + unsetAttribute: function(item, attribute){ + // summary: + // unsets 'attribute' on 'item' + // See dojo/data/api/Write.unsetAttribute() + + this.changing(item); + var old = item[attribute]; + delete item[attribute]; + this.onSet(item,attribute,old,undefined); + }, + + changing: function(object,_deleting){ + // summary: + // adds an object to the list of dirty objects. This object + // contains a reference to the object itself as well as a + // cloned and trimmed version of old object for use with + // revert. + // object: Object + // Indicates that the given object is changing and should be marked as + // dirty for the next save + // _deleting: [private] Boolean + + object.__isDirty = true; + //if an object is already in the list of dirty objects, don't add it again + //or it will overwrite the premodification data set. + for(var i=0; i<this._dirtyObjects.length; i++){ + var dirty = this._dirtyObjects[i]; + if(object==dirty.object){ + if(_deleting){ + // we are deleting, no object is an indicator of deletiong + dirty.object = false; + if(!this._saveNotNeeded){ + dirty.save = true; + } + } + return; + } + } + var old = object instanceof Array ? [] : {}; + for(i in object){ + if(object.hasOwnProperty(i)){ + old[i] = object[i]; + } + } + this._dirtyObjects.push({object: !_deleting && object, old: old, save: !this._saveNotNeeded}); + }, + + save: function(kwArgs){ + // summary: + // Saves the dirty data using object store provider. See dojo/data/api/Write for API. + // kwArgs: + // - kwArgs.global: + // This will cause the save to commit the dirty data for all + // ObjectStores as a single transaction. + // + // - kwArgs.revertOnError: + // This will cause the changes to be reverted if there is an + // error on the save. By default a revert is executed unless + // a value of false is provide for this parameter. + // + // - kwArgs.onError: + // Called when an error occurs in the commit + // + // - kwArgs.onComplete: + // Called when an the save/commit is completed + + kwArgs = kwArgs || {}; + var result, actions = []; + var savingObjects = []; + var self = this; + var dirtyObjects = this._dirtyObjects; + var left = dirtyObjects.length;// this is how many changes are remaining to be received from the server + try{ + connect.connect(kwArgs,"onError",function(){ + if(kwArgs.revertOnError !== false){ + var postCommitDirtyObjects = dirtyObjects; + dirtyObjects = savingObjects; + self.revert(); // revert if there was an error + self._dirtyObjects = postCommitDirtyObjects; + } + else{ + self._dirtyObjects = dirtyObjects.concat(savingObjects); + } + }); + if(this.objectStore.transaction){ + var transaction = this.objectStore.transaction(); + } + for(var i = 0; i < dirtyObjects.length; i++){ + var dirty = dirtyObjects[i]; + var object = dirty.object; + var old = dirty.old; + delete object.__isDirty; + if(object){ + result = this.objectStore.put(object, {overwrite: !!old}); + } + else if(typeof old != "undefined"){ + result = this.objectStore.remove(this.getIdentity(old)); + } + savingObjects.push(dirty); + dirtyObjects.splice(i--,1); + Deferred.when(result, function(value){ + if(!(--left)){ + if(kwArgs.onComplete){ + kwArgs.onComplete.call(kwArgs.scope, actions); + } + } + },function(value){ + + // on an error we want to revert, first we want to separate any changes that were made since the commit + left = -1; // first make sure that success isn't called + kwArgs.onError.call(kwArgs.scope, value); + }); + + } + if(transaction){ + transaction.commit(); + } + }catch(e){ + kwArgs.onError.call(kwArgs.scope, value); + } + }, + + revert: function(){ + // summary: + // returns any modified data to its original state prior to a save(); + + var dirtyObjects = this._dirtyObjects; + for(var i = dirtyObjects.length; i > 0;){ + i--; + var dirty = dirtyObjects[i]; + var object = dirty.object; + var old = dirty.old; + if(object && old){ + // changed + for(var j in old){ + if(old.hasOwnProperty(j) && object[j] !== old[j]){ + this.onSet(object, j, object[j], old[j]); + object[j] = old[j]; + } + } + for(j in object){ + if(!old.hasOwnProperty(j)){ + this.onSet(object, j, object[j]); + delete object[j]; + } + } + }else if(!old){ + // was an addition, remove it + this.onDelete(object); + }else{ + // was a deletion, we will add it back + this.onNew(old); + } + delete (object || old).__isDirty; + dirtyObjects.splice(i, 1); + } + + }, + isDirty: function(item){ + // summary: + // returns true if the item is marked as dirty or true if there are any dirty items + // item: Object + // The item to check + if(!item){ + return !!this._dirtyObjects.length; + } + return item.__isDirty; + }, + + // Notification Support + + onSet: function(){ + // summary: + // See dojo/data/api/Notification.onSet() + }, + onNew: function(){ + // summary: + // See dojo/data/api/Notification.onNew() + }, + onDelete: function(){ + // summary: + // See dojo/data/api/Notification.onDelete() + }, + // an extra to get result sets + onFetch: function(results){ + // summary: + // Called when a fetch occurs + } + + } +); +}); diff --git a/lib/dojo/data/api/Identity.js b/lib/dojo/data/api/Identity.js index 110113bc8..4b7cd6671 100644 --- a/lib/dojo/data/api/Identity.js +++ b/lib/dojo/data/api/Identity.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/api/Identity",["../..","./Read"],function(_1){_1.declare("dojo.data.api.Identity",_1.data.api.Read,{getFeatures:function(){return {"dojo.data.api.Read":true,"dojo.data.api.Identity":true};},getIdentity:function(_2){throw new Error("Unimplemented API: dojo.data.api.Identity.getIdentity");},getIdentityAttributes:function(_3){throw new Error("Unimplemented API: dojo.data.api.Identity.getIdentityAttributes");},fetchItemByIdentity:function(_4){if(!this.isItemLoaded(_4.item)){throw new Error("Unimplemented API: dojo.data.api.Identity.fetchItemByIdentity");}}});return _1.data.api.Identity;});
\ No newline at end of file +define("dojo/data/api/Identity",["../../_base/declare","./Read"],function(_1,_2){return _1("dojo.data.api.Identity",_2,{getFeatures:function(){return {"dojo.data.api.Read":true,"dojo.data.api.Identity":true};},getIdentity:function(_3){throw new Error("Unimplemented API: dojo.data.api.Identity.getIdentity");},getIdentityAttributes:function(_4){throw new Error("Unimplemented API: dojo.data.api.Identity.getIdentityAttributes");},fetchItemByIdentity:function(_5){if(!this.isItemLoaded(_5.item)){throw new Error("Unimplemented API: dojo.data.api.Identity.fetchItemByIdentity");}}});});
\ No newline at end of file diff --git a/lib/dojo/data/api/Identity.js.uncompressed.js b/lib/dojo/data/api/Identity.js.uncompressed.js new file mode 100644 index 000000000..edb45f75e --- /dev/null +++ b/lib/dojo/data/api/Identity.js.uncompressed.js @@ -0,0 +1,109 @@ +define("dojo/data/api/Identity", ["../../_base/declare", "./Read"], function(declare, Read){ + +// module: +// dojo/data/api/Identity + +return declare("dojo.data.api.Identity", Read, { + // summary: + // This is an abstract API that data provider implementations conform to. + // This file defines methods signatures and intentionally leaves all the + // methods unimplemented. + + getFeatures: function(){ + // summary: + // See dojo/data/api/Read.getFeatures() + return { + 'dojo.data.api.Read': true, + 'dojo.data.api.Identity': true + }; + }, + + getIdentity: function(/* dojo/data/api/Item */ item){ + // summary: + // Returns a unique identifier for an item. The return value will be + // either a string or something that has a toString() method (such as, + // for example, a dojox/uuid object). + // item: + // The item from the store from which to obtain its identifier. + // exceptions: + // Conforming implementations may throw an exception or return null if + // item is not an item. + // example: + // | var itemId = store.getIdentity(kermit); + // | assert(kermit === store.findByIdentity(store.getIdentity(kermit))); + throw new Error('Unimplemented API: dojo.data.api.Identity.getIdentity'); + }, + + getIdentityAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // Returns an array of attribute names that are used to generate the identity. + // For most stores, this is a single attribute, but for some complex stores + // such as RDB backed stores that use compound (multi-attribute) identifiers + // it can be more than one. If the identity is not composed of attributes + // on the item, it will return null. This function is intended to identify + // the attributes that comprise the identity so that so that during a render + // of all attributes, the UI can hide the the identity information if it + // chooses. + // item: + // The item from the store from which to obtain the array of public attributes that + // compose the identifier, if any. + // example: + // | var itemId = store.getIdentity(kermit); + // | var identifiers = store.getIdentityAttributes(itemId); + // | assert(typeof identifiers === "array" || identifiers === null); + throw new Error('Unimplemented API: dojo.data.api.Identity.getIdentityAttributes'); + }, + + + fetchItemByIdentity: function(/* object */ keywordArgs){ + // summary: + // Given the identity of an item, this method returns the item that has + // that identity through the onItem callback. Conforming implementations + // should return null if there is no item with the given identity. + // Implementations of fetchItemByIdentity() may sometimes return an item + // from a local cache and may sometimes fetch an item from a remote server, + // keywordArgs: + // An anonymous object that defines the item to locate and callbacks to invoke when the + // item has been located and load has completed. The format of the object is as follows: + // | { + // | identity: string|object, + // | onItem: Function, + // | onError: Function, + // | scope: object + // | } + // + // ####The *identity* parameter + // + // The identity parameter is the identity of the item you wish to locate and load + // This attribute is required. It should be a string or an object that toString() + // can be called on. + // + // ####The *onItem* parameter + // + // Function(item) + // The onItem parameter is the callback to invoke when the item has been loaded. It takes only one + // parameter, the item located, or null if none found. + // + // ####The *onError* parameter + // + // Function(error) + // The onError parameter is the callback to invoke when the item load encountered an error. It takes only one + // parameter, the error object + // + // ####The *scope* parameter + // + // If a scope object is provided, all of the callback functions (onItem, + // onError, etc) will be invoked in the context of the scope object. + // In the body of the callback function, the value of the "this" + // keyword will be the scope object. If no scope object is provided, + // the callback functions will be called in the context of dojo.global. + // For example, onItem.call(scope, item, request) vs. + // onItem.call(dojo.global, item, request) + + if(!this.isItemLoaded(keywordArgs.item)){ + throw new Error('Unimplemented API: dojo.data.api.Identity.fetchItemByIdentity'); + } + } +}); + +}); diff --git a/lib/dojo/data/api/Item.js b/lib/dojo/data/api/Item.js new file mode 100644 index 000000000..d7bd7cb5b --- /dev/null +++ b/lib/dojo/data/api/Item.js @@ -0,0 +1,8 @@ +/* + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + +//>>built +define("dojo/data/api/Item",["../../_base/declare"],function(_1){return _1(null,{});});
\ No newline at end of file diff --git a/lib/dojo/data/api/Item.js.uncompressed.js b/lib/dojo/data/api/Item.js.uncompressed.js new file mode 100644 index 000000000..4cb850f8e --- /dev/null +++ b/lib/dojo/data/api/Item.js.uncompressed.js @@ -0,0 +1,13 @@ +define("dojo/data/api/Item", ["../../_base/declare"], function(declare){ + + // module: + // dojo/data/api/Item + + return declare(null, { + // summary: + // An item in a dojo/data store + // Class for documentation purposes only. An item can take any form, so no + // properties or methods are defined here. + }); + +}); diff --git a/lib/dojo/data/api/Notification.js b/lib/dojo/data/api/Notification.js index d4da32e41..ac6a1da06 100644 --- a/lib/dojo/data/api/Notification.js +++ b/lib/dojo/data/api/Notification.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/api/Notification",["../..","./Read"],function(_1){_1.declare("dojo.data.api.Notification",_1.data.api.Read,{getFeatures:function(){return {"dojo.data.api.Read":true,"dojo.data.api.Notification":true};},onSet:function(_2,_3,_4,_5){throw new Error("Unimplemented API: dojo.data.api.Notification.onSet");},onNew:function(_6,_7){throw new Error("Unimplemented API: dojo.data.api.Notification.onNew");},onDelete:function(_8){throw new Error("Unimplemented API: dojo.data.api.Notification.onDelete");}});return _1.data.api.Notification;});
\ No newline at end of file +define("dojo/data/api/Notification",["../../_base/declare","./Read"],function(_1,_2){return _1("dojo.data.api.Notification",_2,{getFeatures:function(){return {"dojo.data.api.Read":true,"dojo.data.api.Notification":true};},onSet:function(_3,_4,_5,_6){throw new Error("Unimplemented API: dojo.data.api.Notification.onSet");},onNew:function(_7,_8){throw new Error("Unimplemented API: dojo.data.api.Notification.onNew");},onDelete:function(_9){throw new Error("Unimplemented API: dojo.data.api.Notification.onDelete");}});});
\ No newline at end of file diff --git a/lib/dojo/data/api/Notification.js.uncompressed.js b/lib/dojo/data/api/Notification.js.uncompressed.js new file mode 100644 index 000000000..4a21ac838 --- /dev/null +++ b/lib/dojo/data/api/Notification.js.uncompressed.js @@ -0,0 +1,110 @@ +define("dojo/data/api/Notification", ["../../_base/declare", "./Read"], function(declare, Read){ + +// module: +// dojo/data/api/Notification + +return declare("dojo.data.api.Notification", Read, { + // summary: + // This is an abstract API that data provider implementations conform to. + // This file defines functions signatures and intentionally leaves all the + // functions unimplemented. + // description: + // This API defines a set of APIs that all datastores that conform to the + // Notifications API must implement. In general, most stores will implement + // these APIs as no-op functions for users who wish to monitor them to be able + // to connect to then via dojo.connect(). For non-users of dojo.connect, + // they should be able to just replace the function on the store to obtain + // notifications. Both read-only and read-write stores may implement + // this feature. In the case of a read-only store, this feature makes sense if + // the store itself does internal polling to a back-end server and periodically updates + // its cache of items (deletes, adds, and updates). + // example: + // | function onSet(item, attribute, oldValue, newValue){ + // | //Do something with the information... + // | }; + // | var store = new some.newStore(); + // | dojo.connect(store, "onSet", onSet); + + getFeatures: function(){ + // summary: + // See dojo/data/api/Read.getFeatures() + return { + 'dojo.data.api.Read': true, + 'dojo.data.api.Notification': true + }; + }, + + onSet: function(/* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute, + /* object|array */ oldValue, + /* object|array */ newValue){ + // summary: + // This function is called any time an item is modified via setValue, setValues, unsetAttribute, etc. + // description: + // This function is called any time an item is modified via setValue, setValues, unsetAttribute, etc. + // Its purpose is to provide a hook point for those who wish to monitor actions on items in the store + // in a simple manner. The general expected usage is to dojo.connect() to the store's + // implementation and be called after the store function is called. + // item: + // The item being modified. + // attribute: + // The attribute being changed represented as a string name. + // oldValue: + // The old value of the attribute. In the case of single value calls, such as setValue, unsetAttribute, etc, + // this value will be generally be an atomic value of some sort (string, int, etc, object). In the case of + // multi-valued attributes, it will be an array. + // newValue: + // The new value of the attribute. In the case of single value calls, such as setValue, this value will be + // generally be an atomic value of some sort (string, int, etc, object). In the case of multi-valued attributes, + // it will be an array. In the case of unsetAttribute, the new value will be 'undefined'. + // returns: + // Nothing. + throw new Error('Unimplemented API: dojo.data.api.Notification.onSet'); + }, + + onNew: function(/* dojo/data/api/Item */ newItem, /*object?*/ parentInfo){ + // summary: + // This function is called any time a new item is created in the store. + // It is called immediately after the store newItem processing has completed. + // description: + // This function is called any time a new item is created in the store. + // It is called immediately after the store newItem processing has completed. + // newItem: + // The item created. + // parentInfo: + // An optional javascript object that is passed when the item created was placed in the store + // hierarchy as a value f another item's attribute, instead of a root level item. Note that if this + // function is invoked with a value for parentInfo, then onSet is not invoked stating the attribute of + // the parent item was modified. This is to avoid getting two notification events occurring when a new item + // with a parent is created. The structure passed in is as follows: + // | { + // | item: someItem, //The parent item + // | attribute: "attribute-name-string", //The attribute the new item was assigned to. + // | oldValue: something //Whatever was the previous value for the attribute. + // | //If it is a single-value attribute only, then this value will be a single value. + // | //If it was a multi-valued attribute, then this will be an array of all the values minus the new one. + // | newValue: something //The new value of the attribute. In the case of single value calls, such as setValue, this value will be + // | //generally be an atomic value of some sort (string, int, etc, object). In the case of multi-valued attributes, + // | //it will be an array. + // | } + // returns: + // Nothing. + throw new Error('Unimplemented API: dojo.data.api.Notification.onNew'); + }, + + onDelete: function(/* dojo/data/api/Item */ deletedItem){ + // summary: + // This function is called any time an item is deleted from the store. + // It is called immediately after the store deleteItem processing has completed. + // description: + // This function is called any time an item is deleted from the store. + // It is called immediately after the store deleteItem processing has completed. + // deletedItem: + // The item deleted. + // returns: + // Nothing. + throw new Error('Unimplemented API: dojo.data.api.Notification.onDelete'); + } +}); + +}); diff --git a/lib/dojo/data/api/Read.js b/lib/dojo/data/api/Read.js index 4682c6f51..b3fb2a1c2 100644 --- a/lib/dojo/data/api/Read.js +++ b/lib/dojo/data/api/Read.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/api/Read",["../..","./Request"],function(_1){_1.declare("dojo.data.api.Read",null,{getValue:function(_2,_3,_4){throw new Error("Unimplemented API: dojo.data.api.Read.getValue");},getValues:function(_5,_6){throw new Error("Unimplemented API: dojo.data.api.Read.getValues");},getAttributes:function(_7){throw new Error("Unimplemented API: dojo.data.api.Read.getAttributes");},hasAttribute:function(_8,_9){throw new Error("Unimplemented API: dojo.data.api.Read.hasAttribute");},containsValue:function(_a,_b,_c){throw new Error("Unimplemented API: dojo.data.api.Read.containsValue");},isItem:function(_d){throw new Error("Unimplemented API: dojo.data.api.Read.isItem");},isItemLoaded:function(_e){throw new Error("Unimplemented API: dojo.data.api.Read.isItemLoaded");},loadItem:function(_f){if(!this.isItemLoaded(_f.item)){throw new Error("Unimplemented API: dojo.data.api.Read.loadItem");}},fetch:function(_10){throw new Error("Unimplemented API: dojo.data.api.Read.fetch");},getFeatures:function(){return {"dojo.data.api.Read":true};},close:function(_11){throw new Error("Unimplemented API: dojo.data.api.Read.close");},getLabel:function(_12){throw new Error("Unimplemented API: dojo.data.api.Read.getLabel");},getLabelAttributes:function(_13){throw new Error("Unimplemented API: dojo.data.api.Read.getLabelAttributes");}});return _1.data.api.Read;});
\ No newline at end of file +define("dojo/data/api/Read",["../../_base/declare"],function(_1){return _1("dojo.data.api.Read",null,{getValue:function(_2,_3,_4){throw new Error("Unimplemented API: dojo.data.api.Read.getValue");},getValues:function(_5,_6){throw new Error("Unimplemented API: dojo.data.api.Read.getValues");},getAttributes:function(_7){throw new Error("Unimplemented API: dojo.data.api.Read.getAttributes");},hasAttribute:function(_8,_9){throw new Error("Unimplemented API: dojo.data.api.Read.hasAttribute");},containsValue:function(_a,_b,_c){throw new Error("Unimplemented API: dojo.data.api.Read.containsValue");},isItem:function(_d){throw new Error("Unimplemented API: dojo.data.api.Read.isItem");},isItemLoaded:function(_e){throw new Error("Unimplemented API: dojo.data.api.Read.isItemLoaded");},loadItem:function(_f){if(!this.isItemLoaded(_f.item)){throw new Error("Unimplemented API: dojo.data.api.Read.loadItem");}},fetch:function(_10){throw new Error("Unimplemented API: dojo.data.api.Read.fetch");},getFeatures:function(){return {"dojo.data.api.Read":true};},close:function(_11){throw new Error("Unimplemented API: dojo.data.api.Read.close");},getLabel:function(_12){throw new Error("Unimplemented API: dojo.data.api.Read.getLabel");},getLabelAttributes:function(_13){throw new Error("Unimplemented API: dojo.data.api.Read.getLabelAttributes");}});});
\ No newline at end of file diff --git a/lib/dojo/data/api/Read.js.uncompressed.js b/lib/dojo/data/api/Read.js.uncompressed.js new file mode 100644 index 000000000..69d46c756 --- /dev/null +++ b/lib/dojo/data/api/Read.js.uncompressed.js @@ -0,0 +1,486 @@ +define("dojo/data/api/Read", ["../../_base/declare"], function(declare){ + +// module: +// dojo/data/api/Read + +return declare("dojo.data.api.Read", null, { + // summary: + // This is an abstract API that data provider implementations conform to. + // This file defines methods signatures and intentionally leaves all the + // methods unimplemented. For more information on the dojo.data APIs, + // please visit: http://www.dojotoolkit.org/node/98 + + getValue: function( /* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute, + /* value? */ defaultValue){ + // summary: + // Returns a single attribute value. + // Returns defaultValue if and only if *item* does not have a value for *attribute*. + // Returns null if and only if null was explicitly set as the attribute value. + // Returns undefined if and only if the item does not have a value for the + // given attribute (which is the same as saying the item does not have the attribute). + // description: + // Saying that an "item x does not have a value for an attribute y" + // is identical to saying that an "item x does not have attribute y". + // It is an oxymoron to say "that attribute is present but has no values" + // or "the item has that attribute but does not have any attribute values". + // If store.hasAttribute(item, attribute) returns false, then + // store.getValue(item, attribute) will return undefined. + // item: + // The item to access values on. + // attribute: + // The attribute to access represented as a string. + // defaultValue: + // Optional. A default value to use for the getValue return in the attribute does not exist or has no value. + // returns: + // a literal, an item, null, or undefined (never an array) + // exceptions: + // Throws an exception if *item* is not an item, or *attribute* is not a string + // example: + // | var darthVader = store.getValue(lukeSkywalker, "father"); + throw new Error('Unimplemented API: dojo.data.api.Read.getValue'); + }, + + getValues: function(/* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute){ + // summary: + // This getValues() method works just like the getValue() method, but getValues() + // always returns an array rather than a single attribute value. The array + // may be empty, may contain a single attribute value, or may contain + // many attribute values. + // If the item does not have a value for the given attribute, then getValues() + // will return an empty array: []. (So, if store.hasAttribute(item, attribute) + // has a return of false, then store.getValues(item, attribute) will return [].) + // item: + // The item to access values on. + // attribute: + // The attribute to access represented as a string. + // returns: + // an array that may contain literals and items + // exceptions: + // Throws an exception if *item* is not an item, or *attribute* is not a string + // example: + // | var friendsOfLuke = store.getValues(lukeSkywalker, "friends"); + throw new Error('Unimplemented API: dojo.data.api.Read.getValues'); + }, + + getAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // Returns an array with all the attributes that this item has. This + // method will always return an array; if the item has no attributes + // at all, getAttributes() will return an empty array: []. + // item: + // The item to access attributes on. + // exceptions: + // Throws an exception if *item* is not an item, or *attribute* is not a string + // example: + // | var array = store.getAttributes(kermit); + throw new Error('Unimplemented API: dojo.data.api.Read.getAttributes'); + }, + + hasAttribute: function( /* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute){ + // summary: + // Returns true if the given *item* has a value for the given *attribute*. + // item: + // The item to access attributes on. + // attribute: + // The attribute to access represented as a string. + // exceptions: + // Throws an exception if *item* is not an item, or *attribute* is not a string + // example: + // | var trueOrFalse = store.hasAttribute(kermit, "color"); + throw new Error('Unimplemented API: dojo.data.api.Read.hasAttribute'); + }, + + containsValue: function(/* dojo/data/api/Item */ item, + /* attribute-name-string */ attribute, + /* anything */ value){ + // summary: + // Returns true if the given *value* is one of the values that getValues() + // would return. + // item: + // The item to access values on. + // attribute: + // The attribute to access represented as a string. + // value: + // The value to match as a value for the attribute. + // exceptions: + // Throws an exception if *item* is not an item, or *attribute* is not a string + // example: + // | var trueOrFalse = store.containsValue(kermit, "color", "green"); + throw new Error('Unimplemented API: dojo.data.api.Read.containsValue'); + }, + + isItem: function(/* anything */ something){ + // summary: + // Returns true if *something* is an item and came from the store instance. + // Returns false if *something* is a literal, an item from another store instance, + // or is any object other than an item. + // something: + // Can be anything. + // example: + // | var yes = store.isItem(store.newItem()); + // | var no = store.isItem("green"); + throw new Error('Unimplemented API: dojo.data.api.Read.isItem'); + }, + + isItemLoaded: function(/* anything */ something){ + // summary: + // Returns false if isItem(something) is false. Returns false if + // if isItem(something) is true but the the item is not yet loaded + // in local memory (for example, if the item has not yet been read + // from the server). + // something: + // Can be anything. + // example: + // | var yes = store.isItemLoaded(store.newItem()); + // | var no = store.isItemLoaded("green"); + throw new Error('Unimplemented API: dojo.data.api.Read.isItemLoaded'); + }, + + loadItem: function(/* Object */ keywordArgs){ + // summary: + // Given an item, this method loads the item so that a subsequent call + // to store.isItemLoaded(item) will return true. If a call to + // isItemLoaded() returns true before loadItem() is even called, + // then loadItem() need not do any work at all and will not even invoke + // the callback handlers. So, before invoking this method, check that + // the item has not already been loaded. + // keywordArgs: + // An anonymous object that defines the item to load and callbacks to invoke when the + // load has completed. The format of the object is as follows: + // | { + // | item: object, + // | onItem: Function, + // | onError: Function, + // | scope: object + // | } + // + // ####The *item* parameter + // + // The item parameter is an object that represents the item in question that should be + // contained by the store. This attribute is required. + // + // ####The *onItem* parameter + // + // Function(item) + // The onItem parameter is the callback to invoke when the item has been loaded. It takes only one + // parameter, the fully loaded item. + // + // ####The *onError* parameter + // + // Function(error) + // The onError parameter is the callback to invoke when the item load encountered an error. It takes only one + // parameter, the error object + // + // ####The *scope* parameter + // + // If a scope object is provided, all of the callback functions (onItem, + // onError, etc) will be invoked in the context of the scope object. + // In the body of the callback function, the value of the "this" + // keyword will be the scope object. If no scope object is provided, + // the callback functions will be called in the context of dojo.global(). + // For example, onItem.call(scope, item, request) vs. + // onItem.call(dojo.global(), item, request) + if(!this.isItemLoaded(keywordArgs.item)){ + throw new Error('Unimplemented API: dojo.data.api.Read.loadItem'); + } + }, + + fetch: function(/* Object */ keywordArgs){ + // summary: + // Given a query and set of defined options, such as a start and count of items to return, + // this method executes the query and makes the results available as data items. + // The format and expectations of stores is that they operate in a generally asynchronous + // manner, therefore callbacks are always used to return items located by the fetch parameters. + // description: + // A Request object will always be returned and is returned immediately. + // The basic request is nothing more than the keyword args passed to fetch and + // an additional function attached, abort(). The returned request object may then be used + // to cancel a fetch. All data items returns are passed through the callbacks defined in the + // fetch parameters and are not present on the 'request' object. + // + // This does not mean that custom stores can not add methods and properties to the request object + // returned, only that the API does not require it. For more info about the Request API, + // see dojo/data/api/Request + // keywordArgs: + // The keywordArgs parameter may either be an instance of + // conforming to dojo/data/api/Request or may be a simple anonymous object + // that may contain any of the following: + // | { + // | query: query-object or query-string, + // | queryOptions: object, + // | onBegin: Function, + // | onItem: Function, + // | onComplete: Function, + // | onError: Function, + // | scope: object, + // | start: int + // | count: int + // | sort: array + // | } + // All implementations should accept keywordArgs objects with any of + // the 9 standard properties: query, onBegin, onItem, onComplete, onError + // scope, sort, start, and count. Some implementations may accept additional + // properties in the keywordArgs object as valid parameters, such as + // {includeOutliers:true}. + // + // ####The *query* parameter + // + // The query may be optional in some data store implementations. + // The dojo/data/api/Read API does not specify the syntax or semantics + // of the query itself -- each different data store implementation + // may have its own notion of what a query should look like. + // However, as of dojo 0.9, 1.0, and 1.1, all the provided datastores in dojo.data + // and dojox.data support an object structure query, where the object is a set of + // name/value parameters such as { attrFoo: valueBar, attrFoo1: valueBar1}. Most of the + // dijit widgets, such as ComboBox assume this to be the case when working with a datastore + // when they dynamically update the query. Therefore, for maximum compatibility with dijit + // widgets the recommended query parameter is a key/value object. That does not mean that the + // the datastore may not take alternative query forms, such as a simple string, a Date, a number, + // or a mix of such. Ultimately, The dojo/data/api/Read API is agnostic about what the query + // format. + // + // Further note: In general for query objects that accept strings as attribute + // value matches, the store should also support basic filtering capability, such as * + // (match any character) and ? (match single character). An example query that is a query object + // would be like: { attrFoo: "value*"}. Which generally means match all items where they have + // an attribute named attrFoo, with a value that starts with 'value'. + // + // ####The *queryOptions* parameter + // + // The queryOptions parameter is an optional parameter used to specify options that may modify + // the query in some fashion, such as doing a case insensitive search, or doing a deep search + // where all items in a hierarchical representation of data are scanned instead of just the root + // items. It currently defines two options that all datastores should attempt to honor if possible: + // | { + // | ignoreCase: boolean, // Whether or not the query should match case sensitively or not. Default behaviour is false. + // | deep: boolean // Whether or not a fetch should do a deep search of items and all child + // | // items instead of just root-level items in a datastore. Default is false. + // | } + // + // ####The *onBegin* parameter. + // + // function(size, request); + // If an onBegin callback function is provided, the callback function + // will be called just once, before the first onItem callback is called. + // The onBegin callback function will be passed two arguments, the + // the total number of items identified and the Request object. If the total number is + // unknown, then size will be -1. Note that size is not necessarily the size of the + // collection of items returned from the query, as the request may have specified to return only a + // subset of the total set of items through the use of the start and count parameters. + // + // ####The *onItem* parameter. + // + // function(item, request); + // + // If an onItem callback function is provided, the callback function + // will be called as each item in the result is received. The callback + // function will be passed two arguments: the item itself, and the + // Request object. + // + // ####The *onComplete* parameter. + // + // function(items, request); + // + // If an onComplete callback function is provided, the callback function + // will be called just once, after the last onItem callback is called. + // Note that if the onItem callback is not present, then onComplete will be passed + // an array containing all items which matched the query and the request object. + // If the onItem callback is present, then onComplete is called as: + // onComplete(null, request). + // + // ####The *onError* parameter. + // + // function(errorData, request); + // + // If an onError callback function is provided, the callback function + // will be called if there is any sort of error while attempting to + // execute the query. + // The onError callback function will be passed two arguments: + // an Error object and the Request object. + // + // ####The *scope* parameter. + // + // If a scope object is provided, all of the callback functions (onItem, + // onComplete, onError, etc) will be invoked in the context of the scope + // object. In the body of the callback function, the value of the "this" + // keyword will be the scope object. If no scope object is provided, + // the callback functions will be called in the context of dojo.global(). + // For example, onItem.call(scope, item, request) vs. + // onItem.call(dojo.global(), item, request) + // + // ####The *start* parameter. + // + // If a start parameter is specified, this is a indication to the datastore to + // only start returning items once the start number of items have been located and + // skipped. When this parameter is paired with 'count', the store should be able + // to page across queries with millions of hits by only returning subsets of the + // hits for each query + // + // ####The *count* parameter. + // + // If a count parameter is specified, this is a indication to the datastore to + // only return up to that many items. This allows a fetch call that may have + // millions of item matches to be paired down to something reasonable. + // + // ####The *sort* parameter. + // + // If a sort parameter is specified, this is a indication to the datastore to + // sort the items in some manner before returning the items. The array is an array of + // javascript objects that must conform to the following format to be applied to the + // fetching of items: + // | { + // | attribute: attribute || attribute-name-string, + // | descending: true|false; // Optional. Default is false. + // | } + // Note that when comparing attributes, if an item contains no value for the attribute + // (undefined), then it the default ascending sort logic should push it to the bottom + // of the list. In the descending order case, it such items should appear at the top of the list. + // returns: + // The fetch() method will return a javascript object conforming to the API + // defined in dojo/data/api/Request. In general, it will be the keywordArgs + // object returned with the required functions in Request.js attached. + // Its general purpose is to provide a convenient way for a caller to abort an + // ongoing fetch. + // + // The Request object may also have additional properties when it is returned + // such as request.store property, which is a pointer to the datastore object that + // fetch() is a method of. + // exceptions: + // Throws an exception if the query is not valid, or if the query + // is required but was not supplied. + // example: + // Fetch all books identified by the query and call 'showBooks' when complete + // | var request = store.fetch({query:"all books", onComplete: showBooks}); + // example: + // Fetch all items in the story and call 'showEverything' when complete. + // | var request = store.fetch(onComplete: showEverything); + // example: + // Fetch only 10 books that match the query 'all books', starting at the fifth book found during the search. + // This demonstrates how paging can be done for specific queries. + // | var request = store.fetch({query:"all books", start: 4, count: 10, onComplete: showBooks}); + // example: + // Fetch all items that match the query, calling 'callback' each time an item is located. + // | var request = store.fetch({query:"foo/bar", onItem:callback}); + // example: + // Fetch the first 100 books by author King, call showKing when up to 100 items have been located. + // | var request = store.fetch({query:{author:"King"}, start: 0, count:100, onComplete: showKing}); + // example: + // Locate the books written by Author King, sort it on title and publisher, then return the first 100 items from the sorted items. + // | var request = store.fetch({query:{author:"King"}, sort: [{ attribute: "title", descending: true}, {attribute: "publisher"}], ,start: 0, count:100, onComplete: 'showKing'}); + // example: + // Fetch the first 100 books by authors starting with the name King, then call showKing when up to 100 items have been located. + // | var request = store.fetch({query:{author:"King*"}, start: 0, count:100, onComplete: showKing}); + // example: + // Fetch the first 100 books by authors ending with 'ing', but only have one character before it (King, Bing, Ling, Sing, etc.), then call showBooks when up to 100 items have been located. + // | var request = store.fetch({query:{author:"?ing"}, start: 0, count:100, onComplete: showBooks}); + // example: + // Fetch the first 100 books by author King, where the name may appear as King, king, KING, kInG, and so on, then call showKing when up to 100 items have been located. + // | var request = store.fetch({query:{author:"King"}, queryOptions:(ignoreCase: true}, start: 0, count:100, onComplete: showKing}); + // example: + // Paging + // | var store = new LargeRdbmsStore({url:"jdbc:odbc:foobar"}); + // | var fetchArgs = { + // | query: {type:"employees", name:"Hillary *"}, // string matching + // | sort: [{attribute:"department", descending:true}], + // | start: 0, + // | count: 20, + // | scope: displayer, + // | onBegin: showThrobber, + // | onItem: displayItem, + // | onComplete: stopThrobber, + // | onError: handleFetchError, + // | }; + // | store.fetch(fetchArgs); + // | ... + // and then when the user presses the "Next Page" button... + // | fetchArgs.start += 20; + // | store.fetch(fetchArgs); // get the next 20 items + throw new Error('Unimplemented API: dojo.data.api.Read.fetch'); + }, + + getFeatures: function(){ + // summary: + // The getFeatures() method returns an simple keyword values object + // that specifies what interface features the datastore implements. + // A simple CsvStore may be read-only, and the only feature it + // implements will be the 'dojo/data/api/Read' interface, so the + // getFeatures() method will return an object like this one: + // {'dojo.data.api.Read': true}. + // A more sophisticated datastore might implement a variety of + // interface features, like 'dojo.data.api.Read', 'dojo/data/api/Write', + // 'dojo.data.api.Identity', and 'dojo/data/api/Attribution'. + return { + 'dojo.data.api.Read': true + }; + }, + + close: function(/*dojo/data/api/Request|Object?*/ request){ + // summary: + // The close() method is intended for instructing the store to 'close' out + // any information associated with a particular request. + // description: + // The close() method is intended for instructing the store to 'close' out + // any information associated with a particular request. In general, this API + // expects to receive as a parameter a request object returned from a fetch. + // It will then close out anything associated with that request, such as + // clearing any internal datastore caches and closing any 'open' connections. + // For some store implementations, this call may be a no-op. + // request: + // An instance of a request for the store to use to identify what to close out. + // If no request is passed, then the store should clear all internal caches (if any) + // and close out all 'open' connections. It does not render the store unusable from + // there on, it merely cleans out any current data and resets the store to initial + // state. + // example: + // | var request = store.fetch({onComplete: doSomething}); + // | ... + // | store.close(request); + throw new Error('Unimplemented API: dojo.data.api.Read.close'); + }, + + getLabel: function(/* dojo/data/api/Item */ item){ + // summary: + // Method to inspect the item and return a user-readable 'label' for the item + // that provides a general/adequate description of what the item is. + // description: + // Method to inspect the item and return a user-readable 'label' for the item + // that provides a general/adequate description of what the item is. In general + // most labels will be a specific attribute value or collection of the attribute + // values that combine to label the item in some manner. For example for an item + // that represents a person it may return the label as: "firstname lastlame" where + // the firstname and lastname are attributes on the item. If the store is unable + // to determine an adequate human readable label, it should return undefined. Users that wish + // to customize how a store instance labels items should replace the getLabel() function on + // their instance of the store, or extend the store and replace the function in + // the extension class. + // item: + // The item to return the label for. + // returns: + // A user-readable string representing the item or undefined if no user-readable label can + // be generated. + throw new Error('Unimplemented API: dojo.data.api.Read.getLabel'); + }, + + getLabelAttributes: function(/* dojo/data/api/Item */ item){ + // summary: + // Method to inspect the item and return an array of what attributes of the item were used + // to generate its label, if any. + // description: + // Method to inspect the item and return an array of what attributes of the item were used + // to generate its label, if any. This function is to assist UI developers in knowing what + // attributes can be ignored out of the attributes an item has when displaying it, in cases + // where the UI is using the label as an overall identifer should they wish to hide + // redundant information. + // item: + // The item to return the list of label attributes for. + // returns: + // An array of attribute names that were used to generate the label, or null if public attributes + // were not used to generate the label. + throw new Error('Unimplemented API: dojo.data.api.Read.getLabelAttributes'); + } +}); + +}); diff --git a/lib/dojo/data/api/Request.js b/lib/dojo/data/api/Request.js index 83b3696aa..22e6129c1 100644 --- a/lib/dojo/data/api/Request.js +++ b/lib/dojo/data/api/Request.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/api/Request",["../.."],function(_1){_1.declare("dojo.data.api.Request",null,{abort:function(){throw new Error("Unimplemented API: dojo.data.api.Request.abort");}});return _1.data.api.Request;});
\ No newline at end of file +define("dojo/data/api/Request",["../../_base/declare"],function(_1){return _1("dojo.data.api.Request",null,{abort:function(){throw new Error("Unimplemented API: dojo.data.api.Request.abort");}});});
\ No newline at end of file diff --git a/lib/dojo/data/api/Request.js.uncompressed.js b/lib/dojo/data/api/Request.js.uncompressed.js new file mode 100644 index 000000000..eedaf824a --- /dev/null +++ b/lib/dojo/data/api/Request.js.uncompressed.js @@ -0,0 +1,33 @@ +define("dojo/data/api/Request", ["../../_base/declare"], function(declare){ + +// module: +// dojo/data/api/Request + +return declare("dojo.data.api.Request", null, { + // summary: + // This class defines out the semantics of what a 'Request' object looks like + // when returned from a fetch() method. In general, a request object is + // nothing more than the original keywordArgs from fetch with an abort function + // attached to it to allow users to abort a particular request if they so choose. + // No other functions are required on a general Request object return. That does not + // inhibit other store implementations from adding extensions to it, of course. + // + // This is an abstract API that data provider implementations conform to. + // This file defines methods signatures and intentionally leaves all the + // methods unimplemented. + // + // For more details on fetch, see dojo/data/api/Read.fetch(). + + abort: function(){ + // summary: + // This function is a hook point for stores to provide as a way for + // a fetch to be halted mid-processing. + // description: + // This function is a hook point for stores to provide as a way for + // a fetch to be halted mid-processing. For more details on the fetch() api, + // please see dojo/data/api/Read.fetch(). + throw new Error('Unimplemented API: dojo.data.api.Request.abort'); + } +}); + +}); diff --git a/lib/dojo/data/api/Write.js b/lib/dojo/data/api/Write.js index 33392dc42..4aba32e97 100644 --- a/lib/dojo/data/api/Write.js +++ b/lib/dojo/data/api/Write.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/api/Write",["../..","./Read"],function(_1){_1.declare("dojo.data.api.Write",_1.data.api.Read,{getFeatures:function(){return {"dojo.data.api.Read":true,"dojo.data.api.Write":true};},newItem:function(_2,_3){throw new Error("Unimplemented API: dojo.data.api.Write.newItem");},deleteItem:function(_4){throw new Error("Unimplemented API: dojo.data.api.Write.deleteItem");},setValue:function(_5,_6,_7){throw new Error("Unimplemented API: dojo.data.api.Write.setValue");},setValues:function(_8,_9,_a){throw new Error("Unimplemented API: dojo.data.api.Write.setValues");},unsetAttribute:function(_b,_c){throw new Error("Unimplemented API: dojo.data.api.Write.clear");},save:function(_d){throw new Error("Unimplemented API: dojo.data.api.Write.save");},revert:function(){throw new Error("Unimplemented API: dojo.data.api.Write.revert");},isDirty:function(_e){throw new Error("Unimplemented API: dojo.data.api.Write.isDirty");}});return _1.data.api.Write;});
\ No newline at end of file +define("dojo/data/api/Write",["../../_base/declare","./Read"],function(_1,_2){return _1("dojo.data.api.Write",_2,{getFeatures:function(){return {"dojo.data.api.Read":true,"dojo.data.api.Write":true};},newItem:function(_3,_4){throw new Error("Unimplemented API: dojo.data.api.Write.newItem");},deleteItem:function(_5){throw new Error("Unimplemented API: dojo.data.api.Write.deleteItem");},setValue:function(_6,_7,_8){throw new Error("Unimplemented API: dojo.data.api.Write.setValue");},setValues:function(_9,_a,_b){throw new Error("Unimplemented API: dojo.data.api.Write.setValues");},unsetAttribute:function(_c,_d){throw new Error("Unimplemented API: dojo.data.api.Write.clear");},save:function(_e){throw new Error("Unimplemented API: dojo.data.api.Write.save");},revert:function(){throw new Error("Unimplemented API: dojo.data.api.Write.revert");},isDirty:function(_f){throw new Error("Unimplemented API: dojo.data.api.Write.isDirty");}});});
\ No newline at end of file diff --git a/lib/dojo/data/api/Write.js.uncompressed.js b/lib/dojo/data/api/Write.js.uncompressed.js new file mode 100644 index 000000000..5e2721f3c --- /dev/null +++ b/lib/dojo/data/api/Write.js.uncompressed.js @@ -0,0 +1,206 @@ +define("dojo/data/api/Write", ["../../_base/declare", "./Read"], function(declare, Read){ + +// module: +// dojo/data/api/Write + +return declare("dojo.data.api.Write", Read, { + // summary: + // This is an abstract API that data provider implementations conform to. + // This file defines function signatures and intentionally leaves all the + // functions unimplemented. + + getFeatures: function(){ + // summary: + // See dojo/data/api/Read.getFeatures() + return { + 'dojo.data.api.Read': true, + 'dojo.data.api.Write': true + }; + }, + + newItem: function(/* Object? */ keywordArgs, /*Object?*/ parentInfo){ + // summary: + // Returns a newly created item. Sets the attributes of the new + // item based on the *keywordArgs* provided. In general, the attribute + // names in the keywords become the attributes in the new item and as for + // the attribute values in keywordArgs, they become the values of the attributes + // in the new item. In addition, for stores that support hierarchical item + // creation, an optional second parameter is accepted that defines what item is the parent + // of the new item and what attribute of that item should the new item be assigned to. + // In general, this will assume that the attribute targeted is multi-valued and a new item + // is appended onto the list of values for that attribute. + // keywordArgs: + // A javascript object defining the initial content of the item as a set of JavaScript 'property name: value' pairs. + // parentInfo: + // An optional javascript object defining what item is the parent of this item (in a hierarchical store. Not all stores do hierarchical items), + // and what attribute of that parent to assign the new item to. If this is present, and the attribute specified + // is a multi-valued attribute, it will append this item into the array of values for that attribute. The structure + // of the object is as follows: + // | { + // | parent: someItem, + // | attribute: "attribute-name-string" + // | } + // exceptions: + // Throws an exception if *keywordArgs* is a string or a number or + // anything other than a simple anonymous object. + // Throws an exception if the item in parentInfo is not an item from the store + // or if the attribute isn't an attribute name string. + // example: + // | var kermit = store.newItem({name: "Kermit", color:[blue, green]}); + + throw new Error('Unimplemented API: dojo.data.api.Write.newItem'); + }, + + deleteItem: function(/* dojo/data/api/Item */ item){ + // summary: + // Deletes an item from the store. + // item: + // The item to delete. + // exceptions: + // Throws an exception if the argument *item* is not an item + // (if store.isItem(item) returns false). + // example: + // | var success = store.deleteItem(kermit); + throw new Error('Unimplemented API: dojo.data.api.Write.deleteItem'); + }, + + setValue: function( /* dojo/data/api/Item */ item, + /* string */ attribute, + /* almost anything */ value){ + // summary: + // Sets the value of an attribute on an item. + // Replaces any previous value or values. + // item: + // The item to modify. + // attribute: + // The attribute of the item to change represented as a string name. + // value: + // The value to assign to the item. + // exceptions: + // Throws an exception if *item* is not an item, or if *attribute* + // is neither an attribute object or a string. + // Throws an exception if *value* is undefined. + // example: + // | var success = store.set(kermit, "color", "green"); + throw new Error('Unimplemented API: dojo.data.api.Write.setValue'); + }, + + setValues: function(/* dojo/data/api/Item */ item, + /* string */ attribute, + /* array */ values){ + // summary: + // Adds each value in the *values* array as a value of the given + // attribute on the given item. + // Replaces any previous value or values. + // Calling store.setValues(x, y, []) (with *values* as an empty array) has + // the same effect as calling store.unsetAttribute(x, y). + // item: + // The item to modify. + // attribute: + // The attribute of the item to change represented as a string name. + // values: + // An array of values to assign to the attribute.. + // exceptions: + // Throws an exception if *values* is not an array, if *item* is not an + // item, or if *attribute* is neither an attribute object or a string. + // example: + // | var success = store.setValues(kermit, "color", ["green", "aqua"]); + // | success = store.setValues(kermit, "color", []); + // | if (success){assert(!store.hasAttribute(kermit, "color"));} + throw new Error('Unimplemented API: dojo.data.api.Write.setValues'); + }, + + unsetAttribute: function( /* dojo/data/api/Item */ item, + /* string */ attribute){ + // summary: + // Deletes all the values of an attribute on an item. + // item: + // The item to modify. + // attribute: + // The attribute of the item to unset represented as a string. + // exceptions: + // Throws an exception if *item* is not an item, or if *attribute* + // is neither an attribute object or a string. + // example: + // | var success = store.unsetAttribute(kermit, "color"); + // | if (success){assert(!store.hasAttribute(kermit, "color"));} + throw new Error('Unimplemented API: dojo.data.api.Write.clear'); + }, + + save: function(/* object */ keywordArgs){ + // summary: + // Saves to the server all the changes that have been made locally. + // The save operation may take some time and is generally performed + // in an asynchronous fashion. The outcome of the save action is + // is passed into the set of supported callbacks for the save. + // keywordArgs: + // | { + // | onComplete: function + // | onError: function + // | scope: object + // | } + // + // ####The *onComplete* parameter. + // + // function(); + // + // If an onComplete callback function is provided, the callback function + // will be called just once, after the save has completed. No parameters + // are generally passed to the onComplete. + // + // ####The *onError* parameter. + // + // function(errorData); + // + // If an onError callback function is provided, the callback function + // will be called if there is any sort of error while attempting to + // execute the save. The onError function will be based one parameter, the + // error. + // + // ####The *scope* parameter. + // + // If a scope object is provided, all of the callback function ( + // onComplete, onError, etc) will be invoked in the context of the scope + // object. In the body of the callback function, the value of the "this" + // keyword will be the scope object. If no scope object is provided, + // the callback functions will be called in the context of dojo.global. + // For example, onComplete.call(scope) vs. + // onComplete.call(dojo.global) + // returns: + // Nothing. Since the saves are generally asynchronous, there is + // no need to return anything. All results are passed via callbacks. + // example: + // | store.save({onComplete: onSave}); + // | store.save({scope: fooObj, onComplete: onSave, onError: saveFailed}); + throw new Error('Unimplemented API: dojo.data.api.Write.save'); + }, + + revert: function(){ + // summary: + // Discards any unsaved changes. + // description: + // Discards any unsaved changes. + // example: + // | var success = store.revert(); + throw new Error('Unimplemented API: dojo.data.api.Write.revert'); + }, + + isDirty: function(/* item? */ item){ + // summary: + // Given an item, isDirty() returns true if the item has been modified + // since the last save(). If isDirty() is called with no *item* argument, + // then this function returns true if any item has been modified since + // the last save(). + // item: + // The item to check. + // exceptions: + // Throws an exception if isDirty() is passed an argument and the + // argument is not an item. + // example: + // | var trueOrFalse = store.isDirty(kermit); // true if kermit is dirty + // | var trueOrFalse = store.isDirty(); // true if any item is dirty + throw new Error('Unimplemented API: dojo.data.api.Write.isDirty'); + } +}); + +}); diff --git a/lib/dojo/data/util/filter.js b/lib/dojo/data/util/filter.js index 582b31131..d5d1f175e 100644 --- a/lib/dojo/data/util/filter.js +++ b/lib/dojo/data/util/filter.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/util/filter",["dojo/_base/lang"],function(_1){var _2=_1.getObject("dojo.data.util.filter",true);_2.patternToRegExp=function(_3,_4){var _5="^";var c=null;for(var i=0;i<_3.length;i++){c=_3.charAt(i);switch(c){case "\\":_5+=c;i++;_5+=_3.charAt(i);break;case "*":_5+=".*";break;case "?":_5+=".";break;case "$":case "^":case "/":case "+":case ".":case "|":case "(":case ")":case "{":case "}":case "[":case "]":_5+="\\";default:_5+=c;}}_5+="$";if(_4){return new RegExp(_5,"mi");}else{return new RegExp(_5,"m");}};return _2;});
\ No newline at end of file +define("dojo/data/util/filter",["../../_base/lang"],function(_1){var _2={};_1.setObject("dojo.data.util.filter",_2);_2.patternToRegExp=function(_3,_4){var _5="^";var c=null;for(var i=0;i<_3.length;i++){c=_3.charAt(i);switch(c){case "\\":_5+=c;i++;_5+=_3.charAt(i);break;case "*":_5+=".*";break;case "?":_5+=".";break;case "$":case "^":case "/":case "+":case ".":case "|":case "(":case ")":case "{":case "}":case "[":case "]":_5+="\\";default:_5+=c;}}_5+="$";if(_4){return new RegExp(_5,"mi");}else{return new RegExp(_5,"m");}};return _2;});
\ No newline at end of file diff --git a/lib/dojo/data/util/filter.js.uncompressed.js b/lib/dojo/data/util/filter.js.uncompressed.js new file mode 100644 index 000000000..ec3e99ff1 --- /dev/null +++ b/lib/dojo/data/util/filter.js.uncompressed.js @@ -0,0 +1,77 @@ +define("dojo/data/util/filter", ["../../_base/lang"], function(lang){ + // module: + // dojo/data/util/filter + // summary: + // TODOC + +var filter = {}; +lang.setObject("dojo.data.util.filter", filter); + +filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){ + // summary: + // Helper function to convert a simple pattern to a regular expression for matching. + // description: + // Returns a regular expression object that conforms to the defined conversion rules. + // For example: + // + // - ca* -> /^ca.*$/ + // - *ca* -> /^.*ca.*$/ + // - *c\*a* -> /^.*c\*a.*$/ + // - *c\*a?* -> /^.*c\*a..*$/ + // + // and so on. + // pattern: string + // A simple matching pattern to convert that follows basic rules: + // + // - * Means match anything, so ca* means match anything starting with ca + // - ? Means match single character. So, b?b will match to bob and bab, and so on. + // - \ is an escape character. So for example, \* means do not treat * as a match, but literal character *. + // + // To use a \ as a character in the string, it must be escaped. So in the pattern it should be + // represented by \\ to be treated as an ordinary \ character instead of an escape. + // ignoreCase: + // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing + // By default, it is assumed case sensitive. + + var rxp = "^"; + var c = null; + for(var i = 0; i < pattern.length; i++){ + c = pattern.charAt(i); + switch(c){ + case '\\': + rxp += c; + i++; + rxp += pattern.charAt(i); + break; + case '*': + rxp += ".*"; break; + case '?': + rxp += "."; break; + case '$': + case '^': + case '/': + case '+': + case '.': + case '|': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + rxp += "\\"; //fallthrough + default: + rxp += c; + } + } + rxp += "$"; + if(ignoreCase){ + return new RegExp(rxp,"mi"); //RegExp + }else{ + return new RegExp(rxp,"m"); //RegExp + } + +}; + +return filter; +}); diff --git a/lib/dojo/data/util/simpleFetch.js b/lib/dojo/data/util/simpleFetch.js index d339ecda6..b4271f428 100644 --- a/lib/dojo/data/util/simpleFetch.js +++ b/lib/dojo/data/util/simpleFetch.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/util/simpleFetch",["dojo/_base/lang","dojo/_base/window","./sorter"],function(_1,_2,_3){var _4=_1.getObject("dojo.data.util.simpleFetch",true);_4.fetch=function(_5){_5=_5||{};if(!_5.store){_5.store=this;}var _6=this;var _7=function(_8,_9){if(_9.onError){var _a=_9.scope||_2.global;_9.onError.call(_a,_8,_9);}};var _b=function(_c,_d){var _e=_d.abort||null;var _f=false;var _10=_d.start?_d.start:0;var _11=(_d.count&&(_d.count!==Infinity))?(_10+_d.count):_c.length;_d.abort=function(){_f=true;if(_e){_e.call(_d);}};var _12=_d.scope||_2.global;if(!_d.store){_d.store=_6;}if(_d.onBegin){_d.onBegin.call(_12,_c.length,_d);}if(_d.sort){_c.sort(_3.createSortFunction(_d.sort,_6));}if(_d.onItem){for(var i=_10;(i<_c.length)&&(i<_11);++i){var _13=_c[i];if(!_f){_d.onItem.call(_12,_13,_d);}}}if(_d.onComplete&&!_f){var _14=null;if(!_d.onItem){_14=_c.slice(_10,_11);}_d.onComplete.call(_12,_14,_d);}};this._fetchItems(_5,_b,_7);return _5;};return _4;});
\ No newline at end of file +define("dojo/data/util/simpleFetch",["../../_base/lang","../../_base/kernel","./sorter"],function(_1,_2,_3){var _4={};_1.setObject("dojo.data.util.simpleFetch",_4);_4.errorHandler=function(_5,_6){if(_6.onError){var _7=_6.scope||_2.global;_6.onError.call(_7,_5,_6);}};_4.fetchHandler=function(_8,_9){var _a=_9.abort||null,_b=false,_c=_9.start?_9.start:0,_d=(_9.count&&(_9.count!==Infinity))?(_c+_9.count):_8.length;_9.abort=function(){_b=true;if(_a){_a.call(_9);}};var _e=_9.scope||_2.global;if(!_9.store){_9.store=this;}if(_9.onBegin){_9.onBegin.call(_e,_8.length,_9);}if(_9.sort){_8.sort(_3.createSortFunction(_9.sort,this));}if(_9.onItem){for(var i=_c;(i<_8.length)&&(i<_d);++i){var _f=_8[i];if(!_b){_9.onItem.call(_e,_f,_9);}}}if(_9.onComplete&&!_b){var _10=null;if(!_9.onItem){_10=_8.slice(_c,_d);}_9.onComplete.call(_e,_10,_9);}};_4.fetch=function(_11){_11=_11||{};if(!_11.store){_11.store=this;}this._fetchItems(_11,_1.hitch(this,"fetchHandler"),_1.hitch(this,"errorHandler"));return _11;};return _4;});
\ No newline at end of file diff --git a/lib/dojo/data/util/simpleFetch.js.uncompressed.js b/lib/dojo/data/util/simpleFetch.js.uncompressed.js new file mode 100644 index 000000000..94eb9e7a5 --- /dev/null +++ b/lib/dojo/data/util/simpleFetch.js.uncompressed.js @@ -0,0 +1,240 @@ +define("dojo/data/util/simpleFetch", ["../../_base/lang", "../../_base/kernel", "./sorter"], + function(lang, kernel, sorter){ + // module: + // dojo/data/util/simpleFetch + // summary: + // The simpleFetch mixin is designed to serve as a set of function(s) that can + // be mixed into other datastore implementations to accelerate their development. + +var simpleFetch = {}; +lang.setObject("dojo.data.util.simpleFetch", simpleFetch); + +simpleFetch.errorHandler = function(/*Object*/ errorData, /*Object*/ requestObject){ + // summary: + // The error handler when there is an error fetching items. This function should not be called + // directly and is used by simpleFetch.fetch(). + if(requestObject.onError){ + var scope = requestObject.scope || kernel.global; + requestObject.onError.call(scope, errorData, requestObject); + } +}; + +simpleFetch.fetchHandler = function(/*Array*/ items, /*Object*/ requestObject){ + // summary: + // The handler when items are sucessfully fetched. This function should not be called directly + // and is used by simpleFetch.fetch(). + var oldAbortFunction = requestObject.abort || null, + aborted = false, + + startIndex = requestObject.start?requestObject.start: 0, + endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length; + + requestObject.abort = function(){ + aborted = true; + if(oldAbortFunction){ + oldAbortFunction.call(requestObject); + } + }; + + var scope = requestObject.scope || kernel.global; + if(!requestObject.store){ + requestObject.store = this; + } + if(requestObject.onBegin){ + requestObject.onBegin.call(scope, items.length, requestObject); + } + if(requestObject.sort){ + items.sort(sorter.createSortFunction(requestObject.sort, this)); + } + if(requestObject.onItem){ + for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){ + var item = items[i]; + if(!aborted){ + requestObject.onItem.call(scope, item, requestObject); + } + } + } + if(requestObject.onComplete && !aborted){ + var subset = null; + if(!requestObject.onItem){ + subset = items.slice(startIndex, endIndex); + } + requestObject.onComplete.call(scope, subset, requestObject); + } +}; + +simpleFetch.fetch = function(/* Object? */ request){ + // summary: + // The simpleFetch mixin is designed to serve as a set of function(s) that can + // be mixed into other datastore implementations to accelerate their development. + // description: + // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems() + // call by returning an array of all the found items that matched the query. The simpleFetch mixin + // is not designed to work for datastores that respond to a fetch() call by incrementally + // loading items, or sequentially loading partial batches of the result + // set. For datastores that mixin simpleFetch, simpleFetch + // implements a fetch method that automatically handles eight of the fetch() + // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope + // The class mixing in simpleFetch should not implement fetch(), + // but should instead implement a _fetchItems() method. The _fetchItems() + // method takes three arguments, the keywordArgs object that was passed + // to fetch(), a callback function to be called when the result array is + // available, and an error callback to be called if something goes wrong. + // The _fetchItems() method should ignore any keywordArgs parameters for + // start, count, onBegin, onItem, onComplete, onError, sort, and scope. + // The _fetchItems() method needs to correctly handle any other keywordArgs + // parameters, including the query parameter and any optional parameters + // (such as includeChildren). The _fetchItems() method should create an array of + // result items and pass it to the fetchHandler along with the original request object -- + // or, the _fetchItems() method may, if it wants to, create an new request object + // with other specifics about the request that are specific to the datastore and pass + // that as the request object to the handler. + // + // For more information on this specific function, see dojo/data/api/Read.fetch() + // + // request: + // The keywordArgs parameter may either be an instance of + // conforming to dojo/data/api/Request or may be a simple anonymous object + // that may contain any of the following: + // | { + // | query: query-object or query-string, + // | queryOptions: object, + // | onBegin: Function, + // | onItem: Function, + // | onComplete: Function, + // | onError: Function, + // | scope: object, + // | start: int + // | count: int + // | sort: array + // | } + // All implementations should accept keywordArgs objects with any of + // the 9 standard properties: query, onBegin, onItem, onComplete, onError + // scope, sort, start, and count. Some implementations may accept additional + // properties in the keywordArgs object as valid parameters, such as + // {includeOutliers:true}. + // + // ####The *query* parameter + // + // The query may be optional in some data store implementations. + // The dojo/data/api/Read API does not specify the syntax or semantics + // of the query itself -- each different data store implementation + // may have its own notion of what a query should look like. + // However, as of dojo 0.9, 1.0, and 1.1, all the provided datastores in dojo.data + // and dojox.data support an object structure query, where the object is a set of + // name/value parameters such as { attrFoo: valueBar, attrFoo1: valueBar1}. Most of the + // dijit widgets, such as ComboBox assume this to be the case when working with a datastore + // when they dynamically update the query. Therefore, for maximum compatibility with dijit + // widgets the recommended query parameter is a key/value object. That does not mean that the + // the datastore may not take alternative query forms, such as a simple string, a Date, a number, + // or a mix of such. Ultimately, The dojo/data/api/Read API is agnostic about what the query + // format. + // + // Further note: In general for query objects that accept strings as attribute + // value matches, the store should also support basic filtering capability, such as * + // (match any character) and ? (match single character). An example query that is a query object + // would be like: { attrFoo: "value*"}. Which generally means match all items where they have + // an attribute named attrFoo, with a value that starts with 'value'. + // + // ####The *queryOptions* parameter + // + // The queryOptions parameter is an optional parameter used to specify options that may modify + // the query in some fashion, such as doing a case insensitive search, or doing a deep search + // where all items in a hierarchical representation of data are scanned instead of just the root + // items. It currently defines two options that all datastores should attempt to honor if possible: + // | { + // | ignoreCase: boolean, // Whether or not the query should match case sensitively or not. Default behaviour is false. + // | deep: boolean // Whether or not a fetch should do a deep search of items and all child + // | // items instead of just root-level items in a datastore. Default is false. + // | } + // + // ####The *onBegin* parameter. + // + // function(size, request); + // If an onBegin callback function is provided, the callback function + // will be called just once, before the first onItem callback is called. + // The onBegin callback function will be passed two arguments, the + // the total number of items identified and the Request object. If the total number is + // unknown, then size will be -1. Note that size is not necessarily the size of the + // collection of items returned from the query, as the request may have specified to return only a + // subset of the total set of items through the use of the start and count parameters. + // + // ####The *onItem* parameter. + // + // function(item, request); + // + // If an onItem callback function is provided, the callback function + // will be called as each item in the result is received. The callback + // function will be passed two arguments: the item itself, and the + // Request object. + // + // ####The *onComplete* parameter. + // + // function(items, request); + // + // If an onComplete callback function is provided, the callback function + // will be called just once, after the last onItem callback is called. + // Note that if the onItem callback is not present, then onComplete will be passed + // an array containing all items which matched the query and the request object. + // If the onItem callback is present, then onComplete is called as: + // onComplete(null, request). + // + // ####The *onError* parameter. + // + // function(errorData, request); + // + // If an onError callback function is provided, the callback function + // will be called if there is any sort of error while attempting to + // execute the query. + // The onError callback function will be passed two arguments: + // an Error object and the Request object. + // + // ####The *scope* parameter. + // + // If a scope object is provided, all of the callback functions (onItem, + // onComplete, onError, etc) will be invoked in the context of the scope + // object. In the body of the callback function, the value of the "this" + // keyword will be the scope object. If no scope object is provided, + // the callback functions will be called in the context of dojo.global(). + // For example, onItem.call(scope, item, request) vs. + // onItem.call(dojo.global(), item, request) + // + // ####The *start* parameter. + // + // If a start parameter is specified, this is a indication to the datastore to + // only start returning items once the start number of items have been located and + // skipped. When this parameter is paired with 'count', the store should be able + // to page across queries with millions of hits by only returning subsets of the + // hits for each query + // + // ####The *count* parameter. + // + // If a count parameter is specified, this is a indication to the datastore to + // only return up to that many items. This allows a fetch call that may have + // millions of item matches to be paired down to something reasonable. + // + // ####The *sort* parameter. + // + // If a sort parameter is specified, this is a indication to the datastore to + // sort the items in some manner before returning the items. The array is an array of + // javascript objects that must conform to the following format to be applied to the + // fetching of items: + // | { + // | attribute: attribute || attribute-name-string, + // | descending: true|false; // Optional. Default is false. + // | } + // Note that when comparing attributes, if an item contains no value for the attribute + // (undefined), then it the default ascending sort logic should push it to the bottom + // of the list. In the descending order case, it such items should appear at the top of the list. + + request = request || {}; + if(!request.store){ + request.store = this; + } + + this._fetchItems(request, lang.hitch(this, "fetchHandler"), lang.hitch(this, "errorHandler")); + return request; // Object +}; + +return simpleFetch; +}); diff --git a/lib/dojo/data/util/sorter.js b/lib/dojo/data/util/sorter.js index 2591fe18e..9817434a3 100644 --- a/lib/dojo/data/util/sorter.js +++ b/lib/dojo/data/util/sorter.js @@ -1,8 +1,8 @@ /* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ //>>built -define("dojo/data/util/sorter",["dojo/_base/lang"],function(_1){var _2=_1.getObject("dojo.data.util.sorter",true);_2.basicComparator=function(a,b){var r=-1;if(a===null){a=undefined;}if(b===null){b=undefined;}if(a==b){r=0;}else{if(a>b||a==null){r=1;}}return r;};_2.createSortFunction=function(_3,_4){var _5=[];function _6(_7,_8,_9,s){return function(_a,_b){var a=s.getValue(_a,_7);var b=s.getValue(_b,_7);return _8*_9(a,b);};};var _c;var _d=_4.comparatorMap;var bc=_2.basicComparator;for(var i=0;i<_3.length;i++){_c=_3[i];var _e=_c.attribute;if(_e){var _f=(_c.descending)?-1:1;var _10=bc;if(_d){if(typeof _e!=="string"&&("toString" in _e)){_e=_e.toString();}_10=_d[_e]||bc;}_5.push(_6(_e,_f,_10,_4));}}return function(_11,_12){var i=0;while(i<_5.length){var ret=_5[i++](_11,_12);if(ret!==0){return ret;}}return 0;};};return _2;});
\ No newline at end of file +define("dojo/data/util/sorter",["../../_base/lang"],function(_1){var _2={};_1.setObject("dojo.data.util.sorter",_2);_2.basicComparator=function(a,b){var r=-1;if(a===null){a=undefined;}if(b===null){b=undefined;}if(a==b){r=0;}else{if(a>b||a==null){r=1;}}return r;};_2.createSortFunction=function(_3,_4){var _5=[];function _6(_7,_8,_9,s){return function(_a,_b){var a=s.getValue(_a,_7);var b=s.getValue(_b,_7);return _8*_9(a,b);};};var _c;var _d=_4.comparatorMap;var bc=_2.basicComparator;for(var i=0;i<_3.length;i++){_c=_3[i];var _e=_c.attribute;if(_e){var _f=(_c.descending)?-1:1;var _10=bc;if(_d){if(typeof _e!=="string"&&("toString" in _e)){_e=_e.toString();}_10=_d[_e]||bc;}_5.push(_6(_e,_f,_10,_4));}}return function(_11,_12){var i=0;while(i<_5.length){var ret=_5[i++](_11,_12);if(ret!==0){return ret;}}return 0;};};return _2;});
\ No newline at end of file diff --git a/lib/dojo/data/util/sorter.js.uncompressed.js b/lib/dojo/data/util/sorter.js.uncompressed.js new file mode 100644 index 000000000..268fa5847 --- /dev/null +++ b/lib/dojo/data/util/sorter.js.uncompressed.js @@ -0,0 +1,99 @@ +define("dojo/data/util/sorter", ["../../_base/lang"], function(lang){ + // module: + // dojo/data/util/sorter + // summary: + // TODOC + +var sorter = {}; +lang.setObject("dojo.data.util.sorter", sorter); + +sorter.basicComparator = function( /*anything*/ a, + /*anything*/ b){ + // summary: + // Basic comparison function that compares if an item is greater or less than another item + // description: + // returns 1 if a > b, -1 if a < b, 0 if equal. + // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list. + // And compared to each other, null is equivalent to undefined. + + //null is a problematic compare, so if null, we set to undefined. + //Makes the check logic simple, compact, and consistent + //And (null == undefined) === true, so the check later against null + //works for undefined and is less bytes. + var r = -1; + if(a === null){ + a = undefined; + } + if(b === null){ + b = undefined; + } + if(a == b){ + r = 0; + }else if(a > b || a == null){ + r = 1; + } + return r; //int {-1,0,1} +}; + +sorter.createSortFunction = function( /* attributes[] */sortSpec, /*dojo/data/api/Read*/ store){ + // summary: + // Helper function to generate the sorting function based off the list of sort attributes. + // description: + // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists + // it will look in the mapping for comparisons function for the attributes. If one is found, it will + // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates. + // Returns the sorting function for this particular list of attributes and sorting directions. + // sortSpec: + // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending. + // The objects should be formatted as follows: + // | { + // | attribute: "attributeName-string" || attribute, + // | descending: true|false; // Default is false. + // | } + // store: + // The datastore object to look up item values from. + + var sortFunctions=[]; + + function createSortFunction(attr, dir, comp, s){ + //Passing in comp and s (comparator and store), makes this + //function much faster. + return function(itemA, itemB){ + var a = s.getValue(itemA, attr); + var b = s.getValue(itemB, attr); + return dir * comp(a,b); //int + }; + } + var sortAttribute; + var map = store.comparatorMap; + var bc = sorter.basicComparator; + for(var i = 0; i < sortSpec.length; i++){ + sortAttribute = sortSpec[i]; + var attr = sortAttribute.attribute; + if(attr){ + var dir = (sortAttribute.descending) ? -1 : 1; + var comp = bc; + if(map){ + if(typeof attr !== "string" && ("toString" in attr)){ + attr = attr.toString(); + } + comp = map[attr] || bc; + } + sortFunctions.push(createSortFunction(attr, + dir, comp, store)); + } + } + return function(rowA, rowB){ + var i=0; + while(i < sortFunctions.length){ + var ret = sortFunctions[i++](rowA, rowB); + if(ret !== 0){ + return ret;//int + } + } + return 0; //int + }; // Function +}; + +return sorter; +}); |