source: Dev/trunk/src/client/dojox/data/KeyValueStore.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: 12.4 KB
Line 
1define(["dojo/_base/declare", "dojo/_base/lang", "dojo/_base/xhr", "dojo/_base/kernel",
2                "dojo/data/util/simpleFetch", "dojo/data/util/filter"],
3  function(declare, lang, xhr, kernel, simpleFetch, filterUtil) {
4
5var KeyValueStore = declare("dojox.data.KeyValueStore", null, {
6        // summary:
7        //              This is a dojo.data store implementation.  It can take in either a Javascript
8        //              array, JSON string, or URL as the data source.  Data is expected to be in the
9        //              following format:
10        // |    [
11        // |            { "key1": "value1" },
12        // |            { "key2": "value2" }
13        // |    ]
14        //              This is to mimic the Java Properties file format.  Each 'item' from this store
15        //              is a JS object representing a key-value pair.  If an item in the above array has
16        //              more than one key/value pair, only the first will be used/accessed.
17        constructor: function(/* Object */ keywordParameters){
18                // summary:
19                //              constructor
20                // keywordParameters:
21                //              - {url: String}
22                //              - {data: string}
23                //              - {dataVar: jsonObject}
24                if(keywordParameters.url){
25                        this.url = keywordParameters.url;
26                }
27                this._keyValueString = keywordParameters.data;
28                this._keyValueVar = keywordParameters.dataVar;
29                this._keyAttribute = "key";
30                this._valueAttribute = "value";
31                this._storeProp = "_keyValueStore";
32                this._features = {
33                        'dojo.data.api.Read': true,
34                        'dojo.data.api.Identity': true
35                };
36                this._loadInProgress = false;   //Got to track the initial load to prevent duelling loads of the dataset.
37                this._queuedFetches = [];
38                if(keywordParameters && "urlPreventCache" in keywordParameters){
39                        this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
40                }
41        },
42       
43        url: "",
44        data: "",
45
46        // urlPreventCache: boolean
47        //              Controls if urlPreventCache should be used with underlying xhrGet.
48        urlPreventCache: false,
49       
50        _assertIsItem: function(/* item */ item){
51                // summary:
52                //              This function tests whether the item passed in is indeed an item in the store.
53                // item:
54                //              The item to test for being contained by the store.
55                if(!this.isItem(item)){
56                        throw new Error("dojox.data.KeyValueStore: a function was passed an item argument that was not an item");
57                }
58        },
59       
60        _assertIsAttribute: function(/* item */ item, /* String */ attribute){
61                // summary:
62                //              This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
63                // attribute:
64                //              The attribute to test for being contained by the store.
65                if(!lang.isString(attribute)){
66                        throw new Error("dojox.data.KeyValueStore: a function was passed an attribute argument that was not an attribute object nor an attribute name string");
67                }
68        },
69
70/***************************************
71     dojo/data/api/Read API
72***************************************/
73        getValue: function(     /* item */ item,
74                                                /* attribute-name-string */ attribute,
75                                                /* value? */ defaultValue){
76                // summary:
77                //              See dojo/data/api/Read.getValue()
78                this._assertIsItem(item);
79                this._assertIsAttribute(item, attribute);
80                var value;
81                if(attribute == this._keyAttribute){ // Looking for key
82                        value = item[this._keyAttribute];
83                }else{
84                        value = item[this._valueAttribute]; // Otherwise, attribute == ('value' || the actual key )
85                }
86                if(value === undefined){
87                        value = defaultValue;
88                }
89                return value;
90        },
91
92        getValues: function(/* item */ item,
93                                                /* attribute-name-string */ attribute){
94                // summary:
95                //              See dojo/data/api/Read.getValues()
96                //              Key/Value syntax does not support multi-valued attributes, so this is just a
97                //              wrapper function for getValue().
98                var value = this.getValue(item, attribute);
99                return (value ? [value] : []); //Array
100        },
101
102        getAttributes: function(/* item */ item){
103                // summary:
104                //              See dojo/data/api/Read.getAttributes()
105                return [this._keyAttribute, this._valueAttribute, item[this._keyAttribute]];
106        },
107
108        hasAttribute: function( /* item */ item,
109                                                        /* attribute-name-string */ attribute){
110                // summary:
111                //              See dojo/data/api/Read.hasAttribute()
112                this._assertIsItem(item);
113                this._assertIsAttribute(item, attribute);
114                return (attribute == this._keyAttribute || attribute == this._valueAttribute || attribute == item[this._keyAttribute]);
115        },
116
117        containsValue: function(/* item */ item,
118                                                        /* attribute-name-string */ attribute,
119                                                        /* anything */ value){
120                // summary:
121                //              See dojo/data/api/Read.containsValue()
122                var regexp = undefined;
123                if(typeof value === "string"){
124                        regexp = filterUtil.patternToRegExp(value, false);
125                }
126                return this._containsValue(item, attribute, value, regexp); //boolean.
127        },
128
129        _containsValue: function(       /* item */ item,
130                                                                /* attribute|attribute-name-string */ attribute,
131                                                                /* anything */ value,
132                                                                /* RegExp?*/ regexp){
133                // summary:
134                //              Internal function for looking at the values contained by the item.
135                // description:
136                //              Internal function for looking at the values contained by the item.  This
137                //              function allows for denoting if the comparison should be case sensitive for
138                //              strings or not (for handling filtering cases where string case should not matter)
139                // item:
140                //              The data item to examine for attribute values.
141                // attribute:
142                //              The attribute to inspect.
143                // value:
144                //              The value to match.
145                // regexp:
146                //              Optional regular expression generated off value if value was of string type to handle wildcarding.
147                //              If present and attribute values are string, then it can be used for comparison instead of 'value'
148                var values = this.getValues(item, attribute);
149                for(var i = 0; i < values.length; ++i){
150                        var possibleValue = values[i];
151                        if(typeof possibleValue === "string" && regexp){
152                                return (possibleValue.match(regexp) !== null);
153                        }else{
154                                //Non-string matching.
155                                if(value === possibleValue){
156                                        return true; // Boolean
157                                }
158                        }
159                }
160                return false; // Boolean
161        },
162
163        isItem: function(/* anything */ something){
164                // summary:
165                //              See dojo/data/api/Read.isItem()
166                if(something && something[this._storeProp] === this){
167                        return true; //Boolean
168                }
169                return false; //Boolean
170        },
171
172        isItemLoaded: function(/* anything */ something){
173                // summary:
174                //              See dojo/data/api/Read.isItemLoaded()
175                //              The KeyValueStore always loads all items, so if it's an item, then it's loaded.
176                return this.isItem(something); //Boolean
177        },
178
179        loadItem: function(/* object */ keywordArgs){
180                // summary:
181                //              See dojo/data/api/Read.loadItem()
182                // description:
183                //              The KeyValueStore always loads all items, so if it's an item, then it's loaded.
184                //
185                //              From the dojo/data/api/Read.loadItem docs:
186                //
187                //                      If a call to isItemLoaded() returns true before loadItem() is even called,
188                //                      then loadItem() need not do any work at all and will not even invoke
189                //                      the callback handlers.
190        },
191
192        getFeatures: function(){
193                // summary:
194                //              See dojo/data/api/Read.getFeatures()
195                return this._features; //Object
196        },
197
198        close: function(/*dojo/data/api/Request|Object?*/ request){
199                // summary:
200                //              See dojo/data/api/Read.close()
201        },
202
203        getLabel: function(/* item */ item){
204                // summary:
205                //              See dojo/data/api/Read.getLabel()
206                return item[this._keyAttribute];
207        },
208
209        getLabelAttributes: function(/* item */ item){
210                // summary:
211                //              See dojo/data/api/Read.getLabelAttributes()
212                return [this._keyAttribute];
213        },
214       
215        // The dojo/data/api/Read.fetch() function is implemented as
216        // a mixin from dojo.data.util.simpleFetch.
217        // That mixin requires us to define _fetchItems().
218        _fetchItems: function(  /* Object */ keywordArgs,
219                                                        /* Function */ findCallback,
220                                                        /* Function */ errorCallback){
221                // summary:
222                //              See dojo.data.util.simpleFetch.fetch()
223               
224                var self = this;
225
226                var filter = function(requestArgs, arrayOfAllItems){
227                        var items = null;
228                        if(requestArgs.query){
229                                items = [];
230                                var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
231
232                                //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
233                                //same value for each item examined.  Much more efficient.
234                                var regexpList = {};
235                                for(var key in requestArgs.query){
236                                        var value = requestArgs.query[key];
237                                        if(typeof value === "string"){
238                                                regexpList[key] = filterUtil.patternToRegExp(value, ignoreCase);
239                                        }
240                                }
241
242                                for(var i = 0; i < arrayOfAllItems.length; ++i){
243                                        var match = true;
244                                        var candidateItem = arrayOfAllItems[i];
245                                        for(var key in requestArgs.query){
246                                                var value = requestArgs.query[key];
247                                                if(!self._containsValue(candidateItem, key, value, regexpList[key])){
248                                                        match = false;
249                                                }
250                                        }
251                                        if(match){
252                                                items.push(candidateItem);
253                                        }
254                                }
255                        }else if(requestArgs.identity){
256                                items = [];
257                                var item;
258                                for(var key in arrayOfAllItems){
259                                        item = arrayOfAllItems[key];
260                                        if(item[self._keyAttribute] == requestArgs.identity){
261                                                items.push(item);
262                                                break;
263                                        }
264                                }
265                        }else{
266                                // We want a copy to pass back in case the parent wishes to sort the array.  We shouldn't allow resort
267                                // of the internal list so that multiple callers can get lists and sort without affecting each other.
268                                if(arrayOfAllItems.length> 0){
269                                        items = arrayOfAllItems.slice(0,arrayOfAllItems.length);
270                                }
271                        }
272                        findCallback(items, requestArgs);
273                };
274
275                if(this._loadFinished){
276                        filter(keywordArgs, this._arrayOfAllItems);
277                }else{
278                        if(this.url !== ""){
279                                //If fetches come in before the loading has finished, but while
280                                //a load is in progress, we have to defer the fetching to be
281                                //invoked in the callback.
282                                if(this._loadInProgress){
283                                        this._queuedFetches.push({args: keywordArgs, filter: filter});
284                                }else{
285                                        this._loadInProgress = true;
286                                        var getArgs = {
287                                                        url: self.url,
288                                                        handleAs: "json-comment-filtered",
289                                                        preventCache: this.urlPreventCache
290                                                };
291                                        var getHandler = xhr.get(getArgs);
292                                        getHandler.addCallback(function(data){
293                                                self._processData(data);
294                                                filter(keywordArgs, self._arrayOfAllItems);
295                                                self._handleQueuedFetches();
296                                        });
297                                        getHandler.addErrback(function(error){
298                                                self._loadInProgress = false;
299                                                throw error;
300                                        });
301                                }
302                        }else if(this._keyValueString){
303                                this._processData(eval(this._keyValueString));
304                                this._keyValueString = null;
305                                filter(keywordArgs, this._arrayOfAllItems);
306                        }else if(this._keyValueVar){
307                                this._processData(this._keyValueVar);
308                                this._keyValueVar = null;
309                                filter(keywordArgs, this._arrayOfAllItems);
310                        }else{
311                                throw new Error("dojox.data.KeyValueStore: No source data was provided as either URL, String, or Javascript variable data input.");
312                        }
313                }
314               
315        },
316
317        _handleQueuedFetches: function(){
318                // summary:
319                //              Internal function to execute delayed request in the store.
320               
321                //Execute any deferred fetches now.
322                if(this._queuedFetches.length > 0){
323                        for(var i = 0; i < this._queuedFetches.length; i++){
324                                var fData = this._queuedFetches[i];
325                                var delayedFilter = fData.filter;
326                                var delayedQuery = fData.args;
327                                if(delayedFilter){
328                                        delayedFilter(delayedQuery, this._arrayOfAllItems);
329                                }else{
330                                        this.fetchItemByIdentity(fData.args);
331                                }
332                        }
333                        this._queuedFetches = [];
334                }
335        },
336       
337        _processData: function(/* Array */ data){
338                this._arrayOfAllItems = [];
339                for(var i=0; i<data.length; i++){
340                        this._arrayOfAllItems.push(this._createItem(data[i]));
341                }
342                this._loadFinished = true;
343                this._loadInProgress = false;
344        },
345       
346        _createItem: function(/* Object */ something){
347                var item = {};
348                item[this._storeProp] = this;
349                for(var i in something){
350                        item[this._keyAttribute] = i;
351                        item[this._valueAttribute] = something[i];
352                        break;
353                }
354                return item; //Object
355        },
356
357/***************************************
358     dojo/data/api/Identity API
359***************************************/
360        getIdentity: function(/* item */ item){
361                // summary:
362                //              See dojo/data/api/Identity.getIdentity()
363                if(this.isItem(item)){
364                        return item[this._keyAttribute]; //String
365                }
366                return null; //null
367        },
368
369        getIdentityAttributes: function(/* item */ item){
370                // summary:
371                //              See dojo/data/api/Identity.getIdentifierAttributes()
372                return [this._keyAttribute];
373        },
374
375        fetchItemByIdentity: function(/* object */ keywordArgs){
376                // summary:
377                //              See dojo/data/api/Identity.fetchItemByIdentity()
378                keywordArgs.oldOnItem = keywordArgs.onItem;
379                keywordArgs.onItem = null;
380                keywordArgs.onComplete = this._finishFetchItemByIdentity ;
381                this.fetch(keywordArgs);
382        },
383       
384        _finishFetchItemByIdentity: function(/* Array */ items, /* object */ request){
385                var scope = request.scope || kernel.global;
386                if(items.length){
387                        request.oldOnItem.call(scope, items[0]);
388                }else{
389                        request.oldOnItem.call(scope, null);
390                }
391        }
392});
393//Mix in the simple fetch implementation to this class.
394lang.extend(KeyValueStore,simpleFetch);
395return KeyValueStore;
396});
Note: See TracBrowser for help on using the repository browser.