]> git.wh0rd.org Git - tt-rss.git/blob - lib/dojo/data/ItemFileReadStore.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / lib / dojo / data / ItemFileReadStore.js.uncompressed.js
1 define("dojo/data/ItemFileReadStore", ["../_base/kernel", "../_base/lang", "../_base/declare", "../_base/array", "../_base/xhr",
2         "../Evented", "./util/filter", "./util/simpleFetch", "../date/stamp"
3 ], function(kernel, lang, declare, array, xhr, Evented, filterUtil, simpleFetch, dateStamp){
4
5 // module:
6 //              dojo/data/ItemFileReadStore
7
8 var ItemFileReadStore = declare("dojo.data.ItemFileReadStore", [Evented],{
9         // summary:
10         //              The ItemFileReadStore implements the dojo/data/api/Read API and reads
11         //              data from JSON files that have contents in this format --
12         // |    { items: [
13         // |            { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]},
14         // |            { name:'Fozzie Bear', wears:['hat', 'tie']},
15         // |            { name:'Miss Piggy', pets:'Foo-Foo'}
16         // |    ]}
17         //              Note that it can also contain an 'identifier' property that specified which attribute on the items
18         //              in the array of items that acts as the unique identifier for that item.
19
20         constructor: function(/* Object */ keywordParameters){
21                 // summary:
22                 //              constructor
23                 // keywordParameters:
24                 //              {url: String} {data: jsonObject} {typeMap: object}
25                 //              The structure of the typeMap object is as follows:
26                 // |    {
27                 // |            type0: function || object,
28                 // |            type1: function || object,
29                 // |            ...
30                 // |            typeN: function || object
31                 // |    }
32                 //              Where if it is a function, it is assumed to be an object constructor that takes the
33                 //              value of _value as the initialization parameters.  If it is an object, then it is assumed
34                 //              to be an object of general form:
35                 // |    {
36                 // |            type: function, //constructor.
37                 // |            deserialize:    function(value) //The function that parses the value and constructs the object defined by type appropriately.
38                 // |    }
39
40                 this._arrayOfAllItems = [];
41                 this._arrayOfTopLevelItems = [];
42                 this._loadFinished = false;
43                 this._jsonFileUrl = keywordParameters.url;
44                 this._ccUrl = keywordParameters.url;
45                 this.url = keywordParameters.url;
46                 this._jsonData = keywordParameters.data;
47                 this.data = null;
48                 this._datatypeMap = keywordParameters.typeMap || {};
49                 if(!this._datatypeMap['Date']){
50                         //If no default mapping for dates, then set this as default.
51                         //We use the dojo/date/stamp here because the ISO format is the 'dojo way'
52                         //of generically representing dates.
53                         this._datatypeMap['Date'] = {
54                                 type: Date,
55                                 deserialize: function(value){
56                                         return dateStamp.fromISOString(value);
57                                 }
58                         };
59                 }
60                 this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true};
61                 this._itemsByIdentity = null;
62                 this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item.
63                 this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item.
64                 this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item.
65                 this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity
66                 this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset.
67                 this._queuedFetches = [];
68                 if(keywordParameters.urlPreventCache !== undefined){
69                         this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
70                 }
71                 if(keywordParameters.hierarchical !== undefined){
72                         this.hierarchical = keywordParameters.hierarchical?true:false;
73                 }
74                 if(keywordParameters.clearOnClose){
75                         this.clearOnClose = true;
76                 }
77                 if("failOk" in keywordParameters){
78                         this.failOk = keywordParameters.failOk?true:false;
79                 }
80         },
81
82         url: "",        // use "" rather than undefined for the benefit of the parser (#3539)
83
84         //Internal var, crossCheckUrl.  Used so that setting either url or _jsonFileUrl, can still trigger a reload
85         //when clearOnClose and close is used.
86         _ccUrl: "",
87
88         data: null,     // define this so that the parser can populate it
89
90         typeMap: null, //Define so parser can populate.
91
92         // clearOnClose: Boolean
93         //              Parameter to allow users to specify if a close call should force a reload or not.
94         //              By default, it retains the old behavior of not clearing if close is called.  But
95         //              if set true, the store will be reset to default state.  Note that by doing this,
96         //              all item handles will become invalid and a new fetch must be issued.
97         clearOnClose: false,
98
99         // urlPreventCache: Boolean
100         //              Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
101         //              Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
102         //              Added for tracker: #6072
103         urlPreventCache: false,
104
105         // failOk: Boolean
106         //              Parameter for specifying that it is OK for the xhrGet call to fail silently.
107         failOk: false,
108
109         // hierarchical: Boolean
110         //              Parameter to indicate to process data from the url as hierarchical
111         //              (data items can contain other data items in js form).  Default is true
112         //              for backwards compatibility.  False means only root items are processed
113         //              as items, all child objects outside of type-mapped objects and those in
114         //              specific reference format, are left straight JS data objects.
115         hierarchical: true,
116
117         _assertIsItem: function(/* dojo/data/api/Item */ item){
118                 // summary:
119                 //              This function tests whether the item passed in is indeed an item in the store.
120                 // item:
121                 //              The item to test for being contained by the store.
122                 if(!this.isItem(item)){
123                         throw new Error(this.declaredClass + ": Invalid item argument.");
124                 }
125         },
126
127         _assertIsAttribute: function(/* attribute-name-string */ attribute){
128                 // summary:
129                 //              This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
130                 // attribute:
131                 //              The attribute to test for being contained by the store.
132                 if(typeof attribute !== "string"){
133                         throw new Error(this.declaredClass + ": Invalid attribute argument.");
134                 }
135         },
136
137         getValue: function(     /* dojo/data/api/Item */ item,
138                                                    /* attribute-name-string */ attribute,
139                                                    /* value? */ defaultValue){
140                 // summary:
141                 //              See dojo/data/api/Read.getValue()
142                 var values = this.getValues(item, attribute);
143                 return (values.length > 0)?values[0]:defaultValue; // mixed
144         },
145
146         getValues: function(/* dojo/data/api/Item */ item,
147                                                 /* attribute-name-string */ attribute){
148                 // summary:
149                 //              See dojo/data/api/Read.getValues()
150
151                 this._assertIsItem(item);
152                 this._assertIsAttribute(attribute);
153                 // Clone it before returning.  refs: #10474
154                 return (item[attribute] || []).slice(0); // Array
155         },
156
157         getAttributes: function(/* dojo/data/api/Item */ item){
158                 // summary:
159                 //              See dojo/data/api/Read.getAttributes()
160                 this._assertIsItem(item);
161                 var attributes = [];
162                 for(var key in item){
163                         // Save off only the real item attributes, not the special id marks for O(1) isItem.
164                         if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){
165                                 attributes.push(key);
166                         }
167                 }
168                 return attributes; // Array
169         },
170
171         hasAttribute: function( /* dojo/data/api/Item */ item,
172                                                            /* attribute-name-string */ attribute){
173                 // summary:
174                 //              See dojo/data/api/Read.hasAttribute()
175                 this._assertIsItem(item);
176                 this._assertIsAttribute(attribute);
177                 return (attribute in item);
178         },
179
180         containsValue: function(/* dojo/data/api/Item */ item,
181                                                         /* attribute-name-string */ attribute,
182                                                         /* anything */ value){
183                 // summary:
184                 //              See dojo/data/api/Read.containsValue()
185                 var regexp = undefined;
186                 if(typeof value === "string"){
187                         regexp = filterUtil.patternToRegExp(value, false);
188                 }
189                 return this._containsValue(item, attribute, value, regexp); //boolean.
190         },
191
192         _containsValue: function(       /* dojo/data/api/Item */ item,
193                                                                  /* attribute-name-string */ attribute,
194                                                                  /* anything */ value,
195                                                                  /* RegExp?*/ regexp){
196                 // summary:
197                 //              Internal function for looking at the values contained by the item.
198                 // description:
199                 //              Internal function for looking at the values contained by the item.  This
200                 //              function allows for denoting if the comparison should be case sensitive for
201                 //              strings or not (for handling filtering cases where string case should not matter)
202                 // item:
203                 //              The data item to examine for attribute values.
204                 // attribute:
205                 //              The attribute to inspect.
206                 // value:
207                 //              The value to match.
208                 // regexp:
209                 //              Optional regular expression generated off value if value was of string type to handle wildcarding.
210                 //              If present and attribute values are string, then it can be used for comparison instead of 'value'
211                 return array.some(this.getValues(item, attribute), function(possibleValue){
212                         if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){
213                                 if(possibleValue.toString().match(regexp)){
214                                         return true; // Boolean
215                                 }
216                         }else if(value === possibleValue){
217                                 return true; // Boolean
218                         }
219                 });
220         },
221
222         isItem: function(/* anything */ something){
223                 // summary:
224                 //              See dojo/data/api/Read.isItem()
225                 if(something && something[this._storeRefPropName] === this){
226                         if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){
227                                 return true;
228                         }
229                 }
230                 return false; // Boolean
231         },
232
233         isItemLoaded: function(/* anything */ something){
234                 // summary:
235                 //              See dojo/data/api/Read.isItemLoaded()
236                 return this.isItem(something); //boolean
237         },
238
239         loadItem: function(/* object */ keywordArgs){
240                 // summary:
241                 //              See dojo/data/api/Read.loadItem()
242                 this._assertIsItem(keywordArgs.item);
243         },
244
245         getFeatures: function(){
246                 // summary:
247                 //              See dojo/data/api/Read.getFeatures()
248                 return this._features; //Object
249         },
250
251         getLabel: function(/* dojo/data/api/Item */ item){
252                 // summary:
253                 //              See dojo/data/api/Read.getLabel()
254                 if(this._labelAttr && this.isItem(item)){
255                         return this.getValue(item,this._labelAttr); //String
256                 }
257                 return undefined; //undefined
258         },
259
260         getLabelAttributes: function(/* dojo/data/api/Item */ item){
261                 // summary:
262                 //              See dojo/data/api/Read.getLabelAttributes()
263                 if(this._labelAttr){
264                         return [this._labelAttr]; //array
265                 }
266                 return null; //null
267         },
268
269         filter: function(/* Object */ requestArgs, /* item[] */ arrayOfItems, /* Function */ findCallback){
270                 // summary:
271                 //              This method handles the basic filtering needs for ItemFile* based stores.
272                 var items = [],
273                         i, key;
274
275                 if(requestArgs.query){
276                         var value,
277                                 ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
278
279                         //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
280                         //same value for each item examined.  Much more efficient.
281                         var regexpList = {};
282                         for(key in requestArgs.query){
283                                 value = requestArgs.query[key];
284                                 if(typeof value === "string"){
285                                         regexpList[key] = filterUtil.patternToRegExp(value, ignoreCase);
286                                 }else if(value instanceof RegExp){
287                                         regexpList[key] = value;
288                                 }
289                         }
290                         for(i = 0; i < arrayOfItems.length; ++i){
291                                 var match = true;
292                                 var candidateItem = arrayOfItems[i];
293                                 if(candidateItem === null){
294                                         match = false;
295                                 }else{
296                                         for(key in requestArgs.query){
297                                                 value = requestArgs.query[key];
298                                                 if(!this._containsValue(candidateItem, key, value, regexpList[key])){
299                                                         match = false;
300                                                 }
301                                         }
302                                 }
303                                 if(match){
304                                         items.push(candidateItem);
305                                 }
306                         }
307                         findCallback(items, requestArgs);
308                 }else{
309                         // We want a copy to pass back in case the parent wishes to sort the array.
310                         // We shouldn't allow resort of the internal list, so that multiple callers
311                         // can get lists and sort without affecting each other.  We also need to
312                         // filter out any null values that have been left as a result of deleteItem()
313                         // calls in ItemFileWriteStore.
314                         for(i = 0; i < arrayOfItems.length; ++i){
315                                 var item = arrayOfItems[i];
316                                 if(item !== null){
317                                         items.push(item);
318                                 }
319                         }
320                         findCallback(items, requestArgs);
321                 }
322         },
323
324         _fetchItems: function(  /* Object */ keywordArgs,
325                                                           /* Function */ findCallback,
326                                                           /* Function */ errorCallback){
327                 // summary:
328                 //              See dojo/data/util.simpleFetch.fetch()
329                 var self = this;
330
331                 if(this._loadFinished){
332                         this.filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions), findCallback);
333                 }else{
334                         //Do a check on the JsonFileUrl and crosscheck it.
335                         //If it doesn't match the cross-check, it needs to be updated
336                         //This allows for either url or _jsonFileUrl to he changed to
337                         //reset the store load location.  Done this way for backwards
338                         //compatibility.  People use _jsonFileUrl (even though officially
339                         //private.
340                         if(this._jsonFileUrl !== this._ccUrl){
341                                 kernel.deprecated(this.declaredClass + ": ",
342                                         "To change the url, set the url property of the store," +
343                                                 " not _jsonFileUrl.  _jsonFileUrl support will be removed in 2.0");
344                                 this._ccUrl = this._jsonFileUrl;
345                                 this.url = this._jsonFileUrl;
346                         }else if(this.url !== this._ccUrl){
347                                 this._jsonFileUrl = this.url;
348                                 this._ccUrl = this.url;
349                         }
350
351                         //See if there was any forced reset of data.
352                         if(this.data != null){
353                                 this._jsonData = this.data;
354                                 this.data = null;
355                         }
356
357                         if(this._jsonFileUrl){
358                                 //If fetches come in before the loading has finished, but while
359                                 //a load is in progress, we have to defer the fetching to be
360                                 //invoked in the callback.
361                                 if(this._loadInProgress){
362                                         this._queuedFetches.push({args: keywordArgs, filter: lang.hitch(self, "filter"), findCallback: lang.hitch(self, findCallback)});
363                                 }else{
364                                         this._loadInProgress = true;
365                                         var getArgs = {
366                                                 url: self._jsonFileUrl,
367                                                 handleAs: "json-comment-optional",
368                                                 preventCache: this.urlPreventCache,
369                                                 failOk: this.failOk
370                                         };
371                                         var getHandler = xhr.get(getArgs);
372                                         getHandler.addCallback(function(data){
373                                                 try{
374                                                         self._getItemsFromLoadedData(data);
375                                                         self._loadFinished = true;
376                                                         self._loadInProgress = false;
377
378                                                         self.filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions), findCallback);
379                                                         self._handleQueuedFetches();
380                                                 }catch(e){
381                                                         self._loadFinished = true;
382                                                         self._loadInProgress = false;
383                                                         errorCallback(e, keywordArgs);
384                                                 }
385                                         });
386                                         getHandler.addErrback(function(error){
387                                                 self._loadInProgress = false;
388                                                 errorCallback(error, keywordArgs);
389                                         });
390
391                                         //Wire up the cancel to abort of the request
392                                         //This call cancel on the deferred if it hasn't been called
393                                         //yet and then will chain to the simple abort of the
394                                         //simpleFetch keywordArgs
395                                         var oldAbort = null;
396                                         if(keywordArgs.abort){
397                                                 oldAbort = keywordArgs.abort;
398                                         }
399                                         keywordArgs.abort = function(){
400                                                 var df = getHandler;
401                                                 if(df && df.fired === -1){
402                                                         df.cancel();
403                                                         df = null;
404                                                 }
405                                                 if(oldAbort){
406                                                         oldAbort.call(keywordArgs);
407                                                 }
408                                         };
409                                 }
410                         }else if(this._jsonData){
411                                 try{
412                                         this._loadFinished = true;
413                                         this._getItemsFromLoadedData(this._jsonData);
414                                         this._jsonData = null;
415                                         self.filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions), findCallback);
416                                 }catch(e){
417                                         errorCallback(e, keywordArgs);
418                                 }
419                         }else{
420                                 errorCallback(new Error(this.declaredClass + ": No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs);
421                         }
422                 }
423         },
424
425         _handleQueuedFetches: function(){
426                 // summary:
427                 //              Internal function to execute delayed request in the store.
428                 
429                 //Execute any deferred fetches now.
430                 if(this._queuedFetches.length > 0){
431                         for(var i = 0; i < this._queuedFetches.length; i++){
432                                 var fData = this._queuedFetches[i],
433                                         delayedQuery = fData.args,
434                                         delayedFilter = fData.filter,
435                                         delayedFindCallback = fData.findCallback;
436                                 if(delayedFilter){
437                                         delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions), delayedFindCallback);
438                                 }else{
439                                         this.fetchItemByIdentity(delayedQuery);
440                                 }
441                         }
442                         this._queuedFetches = [];
443                 }
444         },
445
446         _getItemsArray: function(/*object?*/queryOptions){
447                 // summary:
448                 //              Internal function to determine which list of items to search over.
449                 // queryOptions: The query options parameter, if any.
450                 if(queryOptions && queryOptions.deep){
451                         return this._arrayOfAllItems;
452                 }
453                 return this._arrayOfTopLevelItems;
454         },
455
456         close: function(/*dojo/data/api/Request|Object?*/ request){
457                 // summary:
458                 //              See dojo/data/api/Read.close()
459                 if(this.clearOnClose &&
460                         this._loadFinished &&
461                         !this._loadInProgress){
462                         //Reset all internalsback to default state.  This will force a reload
463                         //on next fetch.  This also checks that the data or url param was set
464                         //so that the store knows it can get data.  Without one of those being set,
465                         //the next fetch will trigger an error.
466
467                         if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) &&
468                                 (this.url == "" || this.url == null)
469                                 ) && this.data == null){
470                                 console.debug(this.declaredClass + ": WARNING!  Data reload " +
471                                         " information has not been provided." +
472                                         "  Please set 'url' or 'data' to the appropriate value before" +
473                                         " the next fetch");
474                         }
475                         this._arrayOfAllItems = [];
476                         this._arrayOfTopLevelItems = [];
477                         this._loadFinished = false;
478                         this._itemsByIdentity = null;
479                         this._loadInProgress = false;
480                         this._queuedFetches = [];
481                 }
482         },
483
484         _getItemsFromLoadedData: function(/* Object */ dataObject){
485                 // summary:
486                 //              Function to parse the loaded data into item format and build the internal items array.
487                 // description:
488                 //              Function to parse the loaded data into item format and build the internal items array.
489                 // dataObject:
490                 //              The JS data object containing the raw data to convery into item format.
491                 // returns: Array
492                 //              Array of items in store item format.
493
494                 // First, we define a couple little utility functions...
495                 var addingArrays = false,
496                         self = this;
497
498                 function valueIsAnItem(/* anything */ aValue){
499                         // summary:
500                         //              Given any sort of value that could be in the raw json data,
501                         //              return true if we should interpret the value as being an
502                         //              item itself, rather than a literal value or a reference.
503                         // example:
504                         //      |       false == valueIsAnItem("Kermit");
505                         //      |       false == valueIsAnItem(42);
506                         //      |       false == valueIsAnItem(new Date());
507                         //      |       false == valueIsAnItem({_type:'Date', _value:'1802-05-14'});
508                         //      |       false == valueIsAnItem({_reference:'Kermit'});
509                         //      |       true == valueIsAnItem({name:'Kermit', color:'green'});
510                         //      |       true == valueIsAnItem({iggy:'pop'});
511                         //      |       true == valueIsAnItem({foo:42});
512                         return (aValue !== null) &&
513                                 (typeof aValue === "object") &&
514                                 (!lang.isArray(aValue) || addingArrays) &&
515                                 (!lang.isFunction(aValue)) &&
516                                 (aValue.constructor == Object || lang.isArray(aValue)) &&
517                                 (typeof aValue._reference === "undefined") &&
518                                 (typeof aValue._type === "undefined") &&
519                                 (typeof aValue._value === "undefined") &&
520                                 self.hierarchical;
521                 }
522
523                 function addItemAndSubItemsToArrayOfAllItems(/* dojo/data/api/Item */ anItem){
524                         self._arrayOfAllItems.push(anItem);
525                         for(var attribute in anItem){
526                                 var valueForAttribute = anItem[attribute];
527                                 if(valueForAttribute){
528                                         if(lang.isArray(valueForAttribute)){
529                                                 var valueArray = valueForAttribute;
530                                                 for(var k = 0; k < valueArray.length; ++k){
531                                                         var singleValue = valueArray[k];
532                                                         if(valueIsAnItem(singleValue)){
533                                                                 addItemAndSubItemsToArrayOfAllItems(singleValue);
534                                                         }
535                                                 }
536                                         }else{
537                                                 if(valueIsAnItem(valueForAttribute)){
538                                                         addItemAndSubItemsToArrayOfAllItems(valueForAttribute);
539                                                 }
540                                         }
541                                 }
542                         }
543                 }
544
545                 this._labelAttr = dataObject.label;
546
547                 // We need to do some transformations to convert the data structure
548                 // that we read from the file into a format that will be convenient
549                 // to work with in memory.
550
551                 // Step 1: Walk through the object hierarchy and build a list of all items
552                 var i,
553                         item;
554                 this._arrayOfAllItems = [];
555                 this._arrayOfTopLevelItems = dataObject.items;
556
557                 for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){
558                         item = this._arrayOfTopLevelItems[i];
559                         if(lang.isArray(item)){
560                                 addingArrays = true;
561                         }
562                         addItemAndSubItemsToArrayOfAllItems(item);
563                         item[this._rootItemPropName]=true;
564                 }
565
566                 // Step 2: Walk through all the attribute values of all the items,
567                 // and replace single values with arrays.  For example, we change this:
568                 //              { name:'Miss Piggy', pets:'Foo-Foo'}
569                 // into this:
570                 //              { name:['Miss Piggy'], pets:['Foo-Foo']}
571                 //
572                 // We also store the attribute names so we can validate our store
573                 // reference and item id special properties for the O(1) isItem
574                 var allAttributeNames = {},
575                         key;
576
577                 for(i = 0; i < this._arrayOfAllItems.length; ++i){
578                         item = this._arrayOfAllItems[i];
579                         for(key in item){
580                                 if(key !== this._rootItemPropName){
581                                         var value = item[key];
582                                         if(value !== null){
583                                                 if(!lang.isArray(value)){
584                                                         item[key] = [value];
585                                                 }
586                                         }else{
587                                                 item[key] = [null];
588                                         }
589                                 }
590                                 allAttributeNames[key]=key;
591                         }
592                 }
593
594                 // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName
595                 // This should go really fast, it will generally never even run the loop.
596                 while(allAttributeNames[this._storeRefPropName]){
597                         this._storeRefPropName += "_";
598                 }
599                 while(allAttributeNames[this._itemNumPropName]){
600                         this._itemNumPropName += "_";
601                 }
602                 while(allAttributeNames[this._reverseRefMap]){
603                         this._reverseRefMap += "_";
604                 }
605
606                 // Step 4: Some data files specify an optional 'identifier', which is
607                 // the name of an attribute that holds the identity of each item.
608                 // If this data file specified an identifier attribute, then build a
609                 // hash table of items keyed by the identity of the items.
610                 var arrayOfValues;
611
612                 var identifier = dataObject.identifier;
613                 if(identifier){
614                         this._itemsByIdentity = {};
615                         this._features['dojo.data.api.Identity'] = identifier;
616                         for(i = 0; i < this._arrayOfAllItems.length; ++i){
617                                 item = this._arrayOfAllItems[i];
618                                 arrayOfValues = item[identifier];
619                                 var identity = arrayOfValues[0];
620                                 if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
621                                         this._itemsByIdentity[identity] = item;
622                                 }else{
623                                         if(this._jsonFileUrl){
624                                                 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 + "]");
625                                         }else if(this._jsonData){
626                                                 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 + "]");
627                                         }
628                                 }
629                         }
630                 }else{
631                         this._features['dojo.data.api.Identity'] = Number;
632                 }
633
634                 // Step 5: Walk through all the items, and set each item's properties
635                 // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true.
636                 for(i = 0; i < this._arrayOfAllItems.length; ++i){
637                         item = this._arrayOfAllItems[i];
638                         item[this._storeRefPropName] = this;
639                         item[this._itemNumPropName] = i;
640                 }
641
642                 // Step 6: We walk through all the attribute values of all the items,
643                 // looking for type/value literals and item-references.
644                 //
645                 // We replace item-references with pointers to items.  For example, we change:
646                 //              { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
647                 // into this:
648                 //              { name:['Kermit'], friends:[miss_piggy] }
649                 // (where miss_piggy is the object representing the 'Miss Piggy' item).
650                 //
651                 // We replace type/value pairs with typed-literals.  For example, we change:
652                 //              { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
653                 // into this:
654                 //              { name:['Kermit'], born:(new Date(1918, 6, 18)) }
655                 //
656                 // We also generate the associate map for all items for the O(1) isItem function.
657                 for(i = 0; i < this._arrayOfAllItems.length; ++i){
658                         item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
659                         for(key in item){
660                                 arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}]
661                                 for(var j = 0; j < arrayOfValues.length; ++j){
662                                         value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}}
663                                         if(value !== null && typeof value == "object"){
664                                                 if(("_type" in value) && ("_value" in value)){
665                                                         var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber'
666                                                         var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}}
667                                                         if(!mappingObj){
668                                                                 throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'");
669                                                         }else if(lang.isFunction(mappingObj)){
670                                                                 arrayOfValues[j] = new mappingObj(value._value);
671                                                         }else if(lang.isFunction(mappingObj.deserialize)){
672                                                                 arrayOfValues[j] = mappingObj.deserialize(value._value);
673                                                         }else{
674                                                                 throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");
675                                                         }
676                                                 }
677                                                 if(value._reference){
678                                                         var referenceDescription = value._reference; // example: {name:'Miss Piggy'}
679                                                         if(!lang.isObject(referenceDescription)){
680                                                                 // example: 'Miss Piggy'
681                                                                 // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]}
682                                                                 arrayOfValues[j] = this._getItemByIdentity(referenceDescription);
683                                                         }else{
684                                                                 // example: {name:'Miss Piggy'}
685                                                                 // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
686                                                                 for(var k = 0; k < this._arrayOfAllItems.length; ++k){
687                                                                         var candidateItem = this._arrayOfAllItems[k],
688                                                                                 found = true;
689                                                                         for(var refKey in referenceDescription){
690                                                                                 if(candidateItem[refKey] != referenceDescription[refKey]){
691                                                                                         found = false;
692                                                                                 }
693                                                                         }
694                                                                         if(found){
695                                                                                 arrayOfValues[j] = candidateItem;
696                                                                         }
697                                                                 }
698                                                         }
699                                                         if(this.referenceIntegrity){
700                                                                 var refItem = arrayOfValues[j];
701                                                                 if(this.isItem(refItem)){
702                                                                         this._addReferenceToMap(refItem, item, key);
703                                                                 }
704                                                         }
705                                                 }else if(this.isItem(value)){
706                                                         //It's a child item (not one referenced through _reference).
707                                                         //We need to treat this as a referenced item, so it can be cleaned up
708                                                         //in a write store easily.
709                                                         if(this.referenceIntegrity){
710                                                                 this._addReferenceToMap(value, item, key);
711                                                         }
712                                                 }
713                                         }
714                                 }
715                         }
716                 }
717         },
718
719         _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
720                 // summary:
721                 //              Method to add an reference map entry for an item and attribute.
722                 // description:
723                 //              Method to add an reference map entry for an item and attribute.
724                 // refItem:
725                 //              The item that is referenced.
726                 // parentItem:
727                 //              The item that holds the new reference to refItem.
728                 // attribute:
729                 //              The attribute on parentItem that contains the new reference.
730
731                 //Stub function, does nothing.  Real processing is in ItemFileWriteStore.
732         },
733
734         getIdentity: function(/* dojo/data/api/Item */ item){
735                 // summary:
736                 //              See dojo/data/api/Identity.getIdentity()
737                 var identifier = this._features['dojo.data.api.Identity'];
738                 if(identifier === Number){
739                         return item[this._itemNumPropName]; // Number
740                 }else{
741                         var arrayOfValues = item[identifier];
742                         if(arrayOfValues){
743                                 return arrayOfValues[0]; // Object|String
744                         }
745                 }
746                 return null; // null
747         },
748
749         fetchItemByIdentity: function(/* Object */ keywordArgs){
750                 // summary:
751                 //              See dojo/data/api/Identity.fetchItemByIdentity()
752
753                 // Hasn't loaded yet, we have to trigger the load.
754                 var item,
755                         scope;
756                 if(!this._loadFinished){
757                         var self = this;
758                         //Do a check on the JsonFileUrl and crosscheck it.
759                         //If it doesn't match the cross-check, it needs to be updated
760                         //This allows for either url or _jsonFileUrl to he changed to
761                         //reset the store load location.  Done this way for backwards
762                         //compatibility.  People use _jsonFileUrl (even though officially
763                         //private.
764                         if(this._jsonFileUrl !== this._ccUrl){
765                                 kernel.deprecated(this.declaredClass + ": ",
766                                         "To change the url, set the url property of the store," +
767                                                 " not _jsonFileUrl.  _jsonFileUrl support will be removed in 2.0");
768                                 this._ccUrl = this._jsonFileUrl;
769                                 this.url = this._jsonFileUrl;
770                         }else if(this.url !== this._ccUrl){
771                                 this._jsonFileUrl = this.url;
772                                 this._ccUrl = this.url;
773                         }
774
775                         //See if there was any forced reset of data.
776                         if(this.data != null && this._jsonData == null){
777                                 this._jsonData = this.data;
778                                 this.data = null;
779                         }
780
781                         if(this._jsonFileUrl){
782
783                                 if(this._loadInProgress){
784                                         this._queuedFetches.push({args: keywordArgs});
785                                 }else{
786                                         this._loadInProgress = true;
787                                         var getArgs = {
788                                                 url: self._jsonFileUrl,
789                                                 handleAs: "json-comment-optional",
790                                                 preventCache: this.urlPreventCache,
791                                                 failOk: this.failOk
792                                         };
793                                         var getHandler = xhr.get(getArgs);
794                                         getHandler.addCallback(function(data){
795                                                 var scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
796                                                 try{
797                                                         self._getItemsFromLoadedData(data);
798                                                         self._loadFinished = true;
799                                                         self._loadInProgress = false;
800                                                         item = self._getItemByIdentity(keywordArgs.identity);
801                                                         if(keywordArgs.onItem){
802                                                                 keywordArgs.onItem.call(scope, item);
803                                                         }
804                                                         self._handleQueuedFetches();
805                                                 }catch(error){
806                                                         self._loadInProgress = false;
807                                                         if(keywordArgs.onError){
808                                                                 keywordArgs.onError.call(scope, error);
809                                                         }
810                                                 }
811                                         });
812                                         getHandler.addErrback(function(error){
813                                                 self._loadInProgress = false;
814                                                 if(keywordArgs.onError){
815                                                         var scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
816                                                         keywordArgs.onError.call(scope, error);
817                                                 }
818                                         });
819                                 }
820
821                         }else if(this._jsonData){
822                                 // Passed in data, no need to xhr.
823                                 self._getItemsFromLoadedData(self._jsonData);
824                                 self._jsonData = null;
825                                 self._loadFinished = true;
826                                 item = self._getItemByIdentity(keywordArgs.identity);
827                                 if(keywordArgs.onItem){
828                                         scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
829                                         keywordArgs.onItem.call(scope, item);
830                                 }
831                         }
832                 }else{
833                         // Already loaded.  We can just look it up and call back.
834                         item = this._getItemByIdentity(keywordArgs.identity);
835                         if(keywordArgs.onItem){
836                                 scope = keywordArgs.scope?keywordArgs.scope:kernel.global;
837                                 keywordArgs.onItem.call(scope, item);
838                         }
839                 }
840         },
841
842         _getItemByIdentity: function(/* Object */ identity){
843                 // summary:
844                 //              Internal function to look an item up by its identity map.
845                 var item = null;
846                 if(this._itemsByIdentity){
847                         // If this map is defined, we need to just try to get it.  If it fails
848                         // the item does not exist.
849                         if(Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
850                                 item = this._itemsByIdentity[identity];
851                         }
852                 }else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){
853                         item = this._arrayOfAllItems[identity];
854                 }
855                 if(item === undefined){
856                         item = null;
857                 }
858                 return item; // Object
859         },
860
861         getIdentityAttributes: function(/* dojo/data/api/Item */ item){
862                 // summary:
863                 //              See dojo/data/api/Identity.getIdentityAttributes()
864
865                 var identifier = this._features['dojo.data.api.Identity'];
866                 if(identifier === Number){
867                         // If (identifier === Number) it means getIdentity() just returns
868                         // an integer item-number for each item.  The dojo/data/api/Identity
869                         // spec says we need to return null if the identity is not composed
870                         // of attributes
871                         return null; // null
872                 }else{
873                         return [identifier]; // Array
874                 }
875         },
876
877         _forceLoad: function(){
878                 // summary:
879                 //              Internal function to force a load of the store if it hasn't occurred yet.  This is required
880                 //              for specific functions to work properly.
881                 var self = this;
882                 //Do a check on the JsonFileUrl and crosscheck it.
883                 //If it doesn't match the cross-check, it needs to be updated
884                 //This allows for either url or _jsonFileUrl to he changed to
885                 //reset the store load location.  Done this way for backwards
886                 //compatibility.  People use _jsonFileUrl (even though officially
887                 //private.
888                 if(this._jsonFileUrl !== this._ccUrl){
889                         kernel.deprecated(this.declaredClass + ": ",
890                                 "To change the url, set the url property of the store," +
891                                         " not _jsonFileUrl.  _jsonFileUrl support will be removed in 2.0");
892                         this._ccUrl = this._jsonFileUrl;
893                         this.url = this._jsonFileUrl;
894                 }else if(this.url !== this._ccUrl){
895                         this._jsonFileUrl = this.url;
896                         this._ccUrl = this.url;
897                 }
898
899                 //See if there was any forced reset of data.
900                 if(this.data != null){
901                         this._jsonData = this.data;
902                         this.data = null;
903                 }
904
905                 if(this._jsonFileUrl){
906                         var getArgs = {
907                                 url: this._jsonFileUrl,
908                                 handleAs: "json-comment-optional",
909                                 preventCache: this.urlPreventCache,
910                                 failOk: this.failOk,
911                                 sync: true
912                         };
913                         var getHandler = xhr.get(getArgs);
914                         getHandler.addCallback(function(data){
915                                 try{
916                                         //Check to be sure there wasn't another load going on concurrently
917                                         //So we don't clobber data that comes in on it.  If there is a load going on
918                                         //then do not save this data.  It will potentially clobber current data.
919                                         //We mainly wanted to sync/wait here.
920                                         //TODO:  Revisit the loading scheme of this store to improve multi-initial
921                                         //request handling.
922                                         if(self._loadInProgress !== true && !self._loadFinished){
923                                                 self._getItemsFromLoadedData(data);
924                                                 self._loadFinished = true;
925                                         }else if(self._loadInProgress){
926                                                 //Okay, we hit an error state we can't recover from.  A forced load occurred
927                                                 //while an async load was occurring.  Since we cannot block at this point, the best
928                                                 //that can be managed is to throw an error.
929                                                 throw new Error(this.declaredClass + ":  Unable to perform a synchronous load, an async load is in progress.");
930                                         }
931                                 }catch(e){
932                                         console.log(e);
933                                         throw e;
934                                 }
935                         });
936                         getHandler.addErrback(function(error){
937                                 throw error;
938                         });
939                 }else if(this._jsonData){
940                         self._getItemsFromLoadedData(self._jsonData);
941                         self._jsonData = null;
942                         self._loadFinished = true;
943                 }
944         }
945 });
946 //Mix in the simple fetch implementation to this class.
947 lang.extend(ItemFileReadStore,simpleFetch);
948
949 return ItemFileReadStore;
950
951 });