source: Dev/trunk/src/client/dojo/data/ItemFileReadStore.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 33.3 KB
Line 
1define(["../_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
8var 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.
947lang.extend(ItemFileReadStore,simpleFetch);
948
949return ItemFileReadStore;
950
951});
Note: See TracBrowser for help on using the repository browser.