UNPKG

dojo

Version:

Dojo core is a powerful, lightweight library that makes common tasks quicker and easier. Animate elements, manipulate the DOM, and query with easy CSS syntax, all without sacrificing performance.

952 lines (878 loc) 34.1 kB
define(["../_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; });