source: Dev/branches/rest-dojo-ui/client/dojo/data/ItemFileReadStore.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 33.0 KB
Line 
1define(["../_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
10var 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.
942lang.extend(ItemFileReadStore,simpleFetch);
943
944return ItemFileReadStore;
945});
Note: See TracBrowser for help on using the repository browser.