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; });