diff options
Diffstat (limited to 'lib/dojo/data/ItemFileReadStore.js')
-rw-r--r-- | lib/dojo/data/ItemFileReadStore.js | 1483 |
1 files changed, 935 insertions, 548 deletions
diff --git a/lib/dojo/data/ItemFileReadStore.js b/lib/dojo/data/ItemFileReadStore.js index 01c15ee00..1faed3c7d 100644 --- a/lib/dojo/data/ItemFileReadStore.js +++ b/lib/dojo/data/ItemFileReadStore.js @@ -5,558 +5,945 @@ */ -if(!dojo._hasResource["dojo.data.ItemFileReadStore"]){ -dojo._hasResource["dojo.data.ItemFileReadStore"]=true; +if(!dojo._hasResource["dojo.data.ItemFileReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.ItemFileReadStore"] = true; dojo.provide("dojo.data.ItemFileReadStore"); + dojo.require("dojo.data.util.filter"); dojo.require("dojo.data.util.simpleFetch"); dojo.require("dojo.date.stamp"); -dojo.declare("dojo.data.ItemFileReadStore",null,{constructor:function(_1){ -this._arrayOfAllItems=[]; -this._arrayOfTopLevelItems=[]; -this._loadFinished=false; -this._jsonFileUrl=_1.url; -this._ccUrl=_1.url; -this.url=_1.url; -this._jsonData=_1.data; -this.data=null; -this._datatypeMap=_1.typeMap||{}; -if(!this._datatypeMap["Date"]){ -this._datatypeMap["Date"]={type:Date,deserialize:function(_2){ -return dojo.date.stamp.fromISOString(_2); -}}; -} -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(_1.urlPreventCache!==undefined){ -this.urlPreventCache=_1.urlPreventCache?true:false; -} -if(_1.hierarchical!==undefined){ -this.hierarchical=_1.hierarchical?true:false; -} -if(_1.clearOnClose){ -this.clearOnClose=true; -} -if("failOk" in _1){ -this.failOk=_1.failOk?true:false; -} -},url:"",_ccUrl:"",data:null,typeMap:null,clearOnClose:false,urlPreventCache:false,failOk:false,hierarchical:true,_assertIsItem:function(_3){ -if(!this.isItem(_3)){ -throw new Error("dojo.data.ItemFileReadStore: Invalid item argument."); -} -},_assertIsAttribute:function(_4){ -if(typeof _4!=="string"){ -throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument."); -} -},getValue:function(_5,_6,_7){ -var _8=this.getValues(_5,_6); -return (_8.length>0)?_8[0]:_7; -},getValues:function(_9,_a){ -this._assertIsItem(_9); -this._assertIsAttribute(_a); -return (_9[_a]||[]).slice(0); -},getAttributes:function(_b){ -this._assertIsItem(_b); -var _c=[]; -for(var _d in _b){ -if((_d!==this._storeRefPropName)&&(_d!==this._itemNumPropName)&&(_d!==this._rootItemPropName)&&(_d!==this._reverseRefMap)){ -_c.push(_d); -} -} -return _c; -},hasAttribute:function(_e,_f){ -this._assertIsItem(_e); -this._assertIsAttribute(_f); -return (_f in _e); -},containsValue:function(_10,_11,_12){ -var _13=undefined; -if(typeof _12==="string"){ -_13=dojo.data.util.filter.patternToRegExp(_12,false); -} -return this._containsValue(_10,_11,_12,_13); -},_containsValue:function(_14,_15,_16,_17){ -return dojo.some(this.getValues(_14,_15),function(_18){ -if(_18!==null&&!dojo.isObject(_18)&&_17){ -if(_18.toString().match(_17)){ -return true; -} -}else{ -if(_16===_18){ -return true; -} -} -}); -},isItem:function(_19){ -if(_19&&_19[this._storeRefPropName]===this){ -if(this._arrayOfAllItems[_19[this._itemNumPropName]]===_19){ -return true; -} -} -return false; -},isItemLoaded:function(_1a){ -return this.isItem(_1a); -},loadItem:function(_1b){ -this._assertIsItem(_1b.item); -},getFeatures:function(){ -return this._features; -},getLabel:function(_1c){ -if(this._labelAttr&&this.isItem(_1c)){ -return this.getValue(_1c,this._labelAttr); -} -return undefined; -},getLabelAttributes:function(_1d){ -if(this._labelAttr){ -return [this._labelAttr]; -} -return null; -},_fetchItems:function(_1e,_1f,_20){ -var _21=this,_22=function(_23,_24){ -var _25=[],i,key; -if(_23.query){ -var _26,_27=_23.queryOptions?_23.queryOptions.ignoreCase:false; -var _28={}; -for(key in _23.query){ -_26=_23.query[key]; -if(typeof _26==="string"){ -_28[key]=dojo.data.util.filter.patternToRegExp(_26,_27); -}else{ -if(_26 instanceof RegExp){ -_28[key]=_26; -} -} -} -for(i=0;i<_24.length;++i){ -var _29=true; -var _2a=_24[i]; -if(_2a===null){ -_29=false; -}else{ -for(key in _23.query){ -_26=_23.query[key]; -if(!_21._containsValue(_2a,key,_26,_28[key])){ -_29=false; -} -} -} -if(_29){ -_25.push(_2a); -} -} -_1f(_25,_23); -}else{ -for(i=0;i<_24.length;++i){ -var _2b=_24[i]; -if(_2b!==null){ -_25.push(_2b); -} -} -_1f(_25,_23); -} -}; -if(this._loadFinished){ -_22(_1e,this._getItemsArray(_1e.queryOptions)); -}else{ -if(this._jsonFileUrl!==this._ccUrl){ -dojo.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:_1e,filter:_22}); -}else{ -this._loadInProgress=true; -var _2c={url:_21._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk}; -var _2d=dojo.xhrGet(_2c); -_2d.addCallback(function(_2e){ -try{ -_21._getItemsFromLoadedData(_2e); -_21._loadFinished=true; -_21._loadInProgress=false; -_22(_1e,_21._getItemsArray(_1e.queryOptions)); -_21._handleQueuedFetches(); -} -catch(e){ -_21._loadFinished=true; -_21._loadInProgress=false; -_20(e,_1e); -} -}); -_2d.addErrback(function(_2f){ -_21._loadInProgress=false; -_20(_2f,_1e); -}); -var _30=null; -if(_1e.abort){ -_30=_1e.abort; -} -_1e.abort=function(){ -var df=_2d; -if(df&&df.fired===-1){ -df.cancel(); -df=null; -} -if(_30){ -_30.call(_1e); -} -}; -} -}else{ -if(this._jsonData){ -try{ -this._loadFinished=true; -this._getItemsFromLoadedData(this._jsonData); -this._jsonData=null; -_22(_1e,this._getItemsArray(_1e.queryOptions)); -} -catch(e){ -_20(e,_1e); -} -}else{ -_20(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."),_1e); -} -} -} -},_handleQueuedFetches:function(){ -if(this._queuedFetches.length>0){ -for(var i=0;i<this._queuedFetches.length;i++){ -var _31=this._queuedFetches[i],_32=_31.args,_33=_31.filter; -if(_33){ -_33(_32,this._getItemsArray(_32.queryOptions)); -}else{ -this.fetchItemByIdentity(_32); -} -} -this._queuedFetches=[]; -} -},_getItemsArray:function(_34){ -if(_34&&_34.deep){ -return this._arrayOfAllItems; -} -return this._arrayOfTopLevelItems; -},close:function(_35){ -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(_36){ -var _37=false,_38=this; -function _39(_3a){ -var _3b=((_3a!==null)&&(typeof _3a==="object")&&(!dojo.isArray(_3a)||_37)&&(!dojo.isFunction(_3a))&&(_3a.constructor==Object||dojo.isArray(_3a))&&(typeof _3a._reference==="undefined")&&(typeof _3a._type==="undefined")&&(typeof _3a._value==="undefined")&&_38.hierarchical); -return _3b; -}; -function _3c(_3d){ -_38._arrayOfAllItems.push(_3d); -for(var _3e in _3d){ -var _3f=_3d[_3e]; -if(_3f){ -if(dojo.isArray(_3f)){ -var _40=_3f; -for(var k=0;k<_40.length;++k){ -var _41=_40[k]; -if(_39(_41)){ -_3c(_41); -} -} -}else{ -if(_39(_3f)){ -_3c(_3f); -} -} -} -} -}; -this._labelAttr=_36.label; -var i,_42; -this._arrayOfAllItems=[]; -this._arrayOfTopLevelItems=_36.items; -for(i=0;i<this._arrayOfTopLevelItems.length;++i){ -_42=this._arrayOfTopLevelItems[i]; -if(dojo.isArray(_42)){ -_37=true; -} -_3c(_42); -_42[this._rootItemPropName]=true; -} -var _43={},key; -for(i=0;i<this._arrayOfAllItems.length;++i){ -_42=this._arrayOfAllItems[i]; -for(key in _42){ -if(key!==this._rootItemPropName){ -var _44=_42[key]; -if(_44!==null){ -if(!dojo.isArray(_44)){ -_42[key]=[_44]; -} -}else{ -_42[key]=[null]; -} -} -_43[key]=key; -} -} -while(_43[this._storeRefPropName]){ -this._storeRefPropName+="_"; -} -while(_43[this._itemNumPropName]){ -this._itemNumPropName+="_"; -} -while(_43[this._reverseRefMap]){ -this._reverseRefMap+="_"; -} -var _45; -var _46=_36.identifier; -if(_46){ -this._itemsByIdentity={}; -this._features["dojo.data.api.Identity"]=_46; -for(i=0;i<this._arrayOfAllItems.length;++i){ -_42=this._arrayOfAllItems[i]; -_45=_42[_46]; -var _47=_45[0]; -if(!this._itemsByIdentity[_47]){ -this._itemsByIdentity[_47]=_42; -}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: ["+_46+"]. Value collided: ["+_47+"]"); -}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: ["+_46+"]. Value collided: ["+_47+"]"); -} -} -} -} -}else{ -this._features["dojo.data.api.Identity"]=Number; -} -for(i=0;i<this._arrayOfAllItems.length;++i){ -_42=this._arrayOfAllItems[i]; -_42[this._storeRefPropName]=this; -_42[this._itemNumPropName]=i; -} -for(i=0;i<this._arrayOfAllItems.length;++i){ -_42=this._arrayOfAllItems[i]; -for(key in _42){ -_45=_42[key]; -for(var j=0;j<_45.length;++j){ -_44=_45[j]; -if(_44!==null&&typeof _44=="object"){ -if(("_type" in _44)&&("_value" in _44)){ -var _48=_44._type; -var _49=this._datatypeMap[_48]; -if(!_49){ -throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '"+_48+"'"); -}else{ -if(dojo.isFunction(_49)){ -_45[j]=new _49(_44._value); -}else{ -if(dojo.isFunction(_49.deserialize)){ -_45[j]=_49.deserialize(_44._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(_44._reference){ -var _4a=_44._reference; -if(!dojo.isObject(_4a)){ -_45[j]=this._getItemByIdentity(_4a); -}else{ -for(var k=0;k<this._arrayOfAllItems.length;++k){ -var _4b=this._arrayOfAllItems[k],_4c=true; -for(var _4d in _4a){ -if(_4b[_4d]!=_4a[_4d]){ -_4c=false; -} -} -if(_4c){ -_45[j]=_4b; -} -} -} -if(this.referenceIntegrity){ -var _4e=_45[j]; -if(this.isItem(_4e)){ -this._addReferenceToMap(_4e,_42,key); -} -} -}else{ -if(this.isItem(_44)){ -if(this.referenceIntegrity){ -this._addReferenceToMap(_44,_42,key); -} -} -} -} -} -} -} -},_addReferenceToMap:function(_4f,_50,_51){ -},getIdentity:function(_52){ -var _53=this._features["dojo.data.api.Identity"]; -if(_53===Number){ -return _52[this._itemNumPropName]; -}else{ -var _54=_52[_53]; -if(_54){ -return _54[0]; -} -} -return null; -},fetchItemByIdentity:function(_55){ -var _56,_57; -if(!this._loadFinished){ -var _58=this; -if(this._jsonFileUrl!==this._ccUrl){ -dojo.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:_55}); -}else{ -this._loadInProgress=true; -var _59={url:_58._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk}; -var _5a=dojo.xhrGet(_59); -_5a.addCallback(function(_5b){ -var _5c=_55.scope?_55.scope:dojo.global; -try{ -_58._getItemsFromLoadedData(_5b); -_58._loadFinished=true; -_58._loadInProgress=false; -_56=_58._getItemByIdentity(_55.identity); -if(_55.onItem){ -_55.onItem.call(_5c,_56); -} -_58._handleQueuedFetches(); -} -catch(error){ -_58._loadInProgress=false; -if(_55.onError){ -_55.onError.call(_5c,error); -} -} -}); -_5a.addErrback(function(_5d){ -_58._loadInProgress=false; -if(_55.onError){ -var _5e=_55.scope?_55.scope:dojo.global; -_55.onError.call(_5e,_5d); -} -}); -} -}else{ -if(this._jsonData){ -_58._getItemsFromLoadedData(_58._jsonData); -_58._jsonData=null; -_58._loadFinished=true; -_56=_58._getItemByIdentity(_55.identity); -if(_55.onItem){ -_57=_55.scope?_55.scope:dojo.global; -_55.onItem.call(_57,_56); -} -} -} -}else{ -_56=this._getItemByIdentity(_55.identity); -if(_55.onItem){ -_57=_55.scope?_55.scope:dojo.global; -_55.onItem.call(_57,_56); -} -} -},_getItemByIdentity:function(_5f){ -var _60=null; -if(this._itemsByIdentity){ -_60=this._itemsByIdentity[_5f]; -}else{ -_60=this._arrayOfAllItems[_5f]; -} -if(_60===undefined){ -_60=null; -} -return _60; -},getIdentityAttributes:function(_61){ -var _62=this._features["dojo.data.api.Identity"]; -if(_62===Number){ -return null; -}else{ -return [_62]; -} -},_forceLoad:function(){ -var _63=this; -if(this._jsonFileUrl!==this._ccUrl){ -dojo.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){ -var _64={url:this._jsonFileUrl,handleAs:"json-comment-optional",preventCache:this.urlPreventCache,failOk:this.failOk,sync:true}; -var _65=dojo.xhrGet(_64); -_65.addCallback(function(_66){ -try{ -if(_63._loadInProgress!==true&&!_63._loadFinished){ -_63._getItemsFromLoadedData(_66); -_63._loadFinished=true; -}else{ -if(_63._loadInProgress){ -throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress."); -} -} -} -catch(e){ -throw e; -} -}); -_65.addErrback(function(_67){ -throw _67; + +dojo.declare("dojo.data.ItemFileReadStore", null,{ + // 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 'identifer' 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} + // keywordParameters: {data: jsonObject} + // keywordParameters: {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 dojo.date.stamp.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. + + //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, + + //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, + + //Parameter for specifying that it is OK for the xhrGet call to fail silently. + failOk: false, + + //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(/* 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("dojo.data.ItemFileReadStore: 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("dojo.data.ItemFileReadStore: Invalid attribute argument."); + } + }, + + getValue: function( /* 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(/* 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(/* 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( /* 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(/* item */ item, + /* attribute-name-string */ attribute, + /* anything */ value){ + // summary: + // See dojo.data.api.Read.containsValue() + var regexp = undefined; + if(typeof value === "string"){ + regexp = dojo.data.util.filter.patternToRegExp(value, false); + } + return this._containsValue(item, attribute, value, regexp); //boolean. + }, + + _containsValue: function( /* 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 dojo.some(this.getValues(item, attribute), function(possibleValue){ + if(possibleValue !== null && !dojo.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(/* 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(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabelAttributes() + if(this._labelAttr){ + return [this._labelAttr]; //array + } + return null; //null + }, + + _fetchItems: function( /* Object */ keywordArgs, + /* Function */ findCallback, + /* Function */ errorCallback){ + // summary: + // See dojo.data.util.simpleFetch.fetch() + var self = this, + filter = function(requestArgs, arrayOfItems){ + 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] = dojo.data.util.filter.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(!self._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); + } + }; + + if(this._loadFinished){ + filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); + }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){ + dojo.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; + } + + //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 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: filter}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = dojo.xhrGet(getArgs); + getHandler.addCallback(function(data){ + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; + + filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions)); + 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; + filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); + }catch(e){ + errorCallback(e, keywordArgs); + } + }else{ + errorCallback(new Error("dojo.data.ItemFileReadStore: 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; + if(delayedFilter){ + delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions)); + }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 || keywordArgs || null */ 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("dojo.data.ItemFileReadStore: 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:'May 14, 1802'}); + // | false == valueIsAnItem({_reference:'Kermit'}); + // | true == valueIsAnItem({name:'Kermit', color:'green'}); + // | true == valueIsAnItem({iggy:'pop'}); + // | true == valueIsAnItem({foo:42}); + var isItem = ( + (aValue !== null) && + (typeof aValue === "object") && + (!dojo.isArray(aValue) || addingArrays) && + (!dojo.isFunction(aValue)) && + (aValue.constructor == Object || dojo.isArray(aValue)) && + (typeof aValue._reference === "undefined") && + (typeof aValue._type === "undefined") && + (typeof aValue._value === "undefined") && + self.hierarchical + ); + return isItem; + } + + function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ + self._arrayOfAllItems.push(anItem); + for(var attribute in anItem){ + var valueForAttribute = anItem[attribute]; + if(valueForAttribute){ + if(dojo.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(dojo.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(!dojo.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(!this._itemsByIdentity[identity]){ + this._itemsByIdentity[identity] = item; + }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: [" + identifier + "]. Value collided: [" + identity + "]"); + }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: [" + 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:'July 18, 1918'}] } + // into this: + // { name:['Kermit'], born:(new Date('July 18, 1918')) } + // + // 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(dojo.isFunction(mappingObj)){ + arrayOfValues[j] = new mappingObj(value._value); + }else if(dojo.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(!dojo.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(/* 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){ + dojo.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; + } + + //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 = dojo.xhrGet(getArgs); + getHandler.addCallback(function(data){ + var scope = keywordArgs.scope?keywordArgs.scope:dojo.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:dojo.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:dojo.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:dojo.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){ + item = this._itemsByIdentity[identity]; + }else{ + item = this._arrayOfAllItems[identity]; + } + if(item === undefined){ + item = null; + } + return item; // Object + }, + + getIdentityAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Identity.getIdentifierAttributes() + + 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){ + dojo.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; + } + + //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){ + var getArgs = { + url: this._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk, + sync: true + }; + var getHandler = dojo.xhrGet(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("dojo.data.ItemFileReadStore: 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; + } + } }); -}else{ -if(this._jsonData){ -_63._getItemsFromLoadedData(_63._jsonData); -_63._jsonData=null; -_63._loadFinished=true; -} -} -}}); +//Mix in the simple fetch implementation to this class. dojo.extend(dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch); + } |