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