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