source: Dev/trunk/src/client/dojox/data/OpmlStore.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: 16.3 KB
Line 
1define(["dojo/_base/declare", "dojo/_base/lang", "dojo/_base/xhr", "dojo/data/util/simpleFetch", "dojo/data/util/filter",
2                "dojo/_base/kernel"],
3  function(declare, lang, xhr, simpleFetch, filterUtil, kernel) {
4
5var OpmlStore = declare("dojox.data.OpmlStore", null, {
6        // summary:
7        //              The OpmlStore implements the dojo/data/api/Read API.
8        // examples:
9        //      |       var opmlStore = new dojo.data.OpmlStore({url:"geography.xml"});
10        //      |       var opmlStore = new dojo.data.OpmlStore({url:"http://example.com/geography.xml"});
11
12        constructor: function(/* Object */ keywordParameters){
13                // summary:
14                //              constructor
15                // keywordParameters:
16                //              - {url: String, label: String}
17                //
18                //              Where label is optional and configures what should be used as the return from getLabel()
19                this._xmlData = null;
20                this._arrayOfTopLevelItems = [];
21                this._arrayOfAllItems = [];
22                this._metadataNodes = null;
23                this._loadFinished = false;
24                this.url = keywordParameters.url;
25                this._opmlData = keywordParameters.data; // XML DOM Document
26                if(keywordParameters.label){
27                        this.label = keywordParameters.label;
28                }
29                this._loadInProgress = false;   //Got to track the initial load to prevent duelling loads of the dataset.
30                this._queuedFetches = [];
31                this._identityMap = {};
32                this._identCount = 0;
33                this._idProp = "_I";
34                if(keywordParameters && "urlPreventCache" in keywordParameters){
35                        this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
36                }
37        },
38
39        // label: [public] string
40        //              The attribute of the Opml item to act as a label.
41        label: "text",
42
43        // url: [public] string
44        //              The location from which to fetch the Opml document.
45        url: "",
46
47        // urlPreventCache: [public] boolean
48        //              Flag to denote if the underlying xhrGet call should set preventCache.
49        urlPreventCache: false,
50
51        _assertIsItem: function(/* item */ item){
52                if(!this.isItem(item)){
53                        throw new Error("dojo.data.OpmlStore: a function was passed an item argument that was not an item");
54                }
55        },
56       
57        _assertIsAttribute: function(/*dojo/data/api/Item|String */ attribute){
58                // summary:
59                //              This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
60                // attribute:
61                //              The attribute to test for being contained by the store.
62                if(!lang.isString(attribute)){
63                        throw new Error("dojox.data.OpmlStore: a function was passed an attribute argument that was not an attribute object nor an attribute name string");
64                }
65        },
66       
67        _removeChildNodesThatAreNotElementNodes: function(/* node */ node, /* boolean */ recursive){
68                var childNodes = node.childNodes;
69                if(childNodes.length === 0){
70                        return;
71                }
72                var nodesToRemove = [];
73                var i, childNode;
74                for(i = 0; i < childNodes.length; ++i){
75                        childNode = childNodes[i];
76                        if(childNode.nodeType != 1){
77                                nodesToRemove.push(childNode);
78                        }
79                }
80                for(i = 0; i < nodesToRemove.length; ++i){
81                        childNode = nodesToRemove[i];
82                        node.removeChild(childNode);
83                }
84                if(recursive){
85                        for(i = 0; i < childNodes.length; ++i){
86                                childNode = childNodes[i];
87                                this._removeChildNodesThatAreNotElementNodes(childNode, recursive);
88                        }
89                }
90        },
91       
92        _processRawXmlTree: function(/* xmlDoc */ rawXmlTree){
93                this._loadFinished = true;
94                this._xmlData = rawXmlTree;
95                var headNodes = rawXmlTree.getElementsByTagName('head');
96                var headNode = headNodes[0];
97                if(headNode){
98                        this._removeChildNodesThatAreNotElementNodes(headNode);
99                        this._metadataNodes = headNode.childNodes;
100                }
101                var bodyNodes = rawXmlTree.getElementsByTagName('body');
102                var bodyNode = bodyNodes[0];
103                if(bodyNode){
104                        this._removeChildNodesThatAreNotElementNodes(bodyNode, true);
105                       
106                        var bodyChildNodes = bodyNodes[0].childNodes;
107                        for(var i = 0; i < bodyChildNodes.length; ++i){
108                                var node = bodyChildNodes[i];
109                                if(node.tagName == 'outline'){
110                                        this._identityMap[this._identCount] = node;
111                                        this._identCount++;
112                                        this._arrayOfTopLevelItems.push(node);
113                                        this._arrayOfAllItems.push(node);
114                                        this._checkChildNodes(node);
115                                }
116                        }
117                }
118        },
119
120        _checkChildNodes: function(node /*Node*/){
121                // summary:
122                //              Internal function to recurse over all child nodes from the store and add them
123                //              As non-toplevel items
124                // description:
125                //              Internal function to recurse over all child nodes from the store and add them
126                //              As non-toplevel items
127                // node:
128                //              The child node to walk.
129                if(node.firstChild){
130                        for(var i = 0; i < node.childNodes.length; i++){
131                                var child = node.childNodes[i];
132                                if(child.tagName == 'outline'){
133                                        this._identityMap[this._identCount] = child;
134                                        this._identCount++;
135                                        this._arrayOfAllItems.push(child);
136                                        this._checkChildNodes(child);
137                                }
138                        }
139                }
140        },
141
142        _getItemsArray: function(/*object?*/queryOptions){
143                // summary:
144                //              Internal function to determine which list of items to search over.
145                // queryOptions: The query options parameter, if any.
146                if(queryOptions && queryOptions.deep){
147                        return this._arrayOfAllItems;
148                }
149                return this._arrayOfTopLevelItems;
150        },
151
152/***************************************
153     dojo/data/api/Read API
154***************************************/
155        getValue: function( /* item */ item,
156                                                /* attribute|attribute-name-string */ attribute,
157                                                /* value? */ defaultValue){
158                // summary:
159                //              See dojo/data/api/Read.getValue()
160                this._assertIsItem(item);
161                this._assertIsAttribute(attribute);
162                if(attribute == 'children'){
163                        return (item.firstChild || defaultValue); //Object
164                }else{
165                        var value = item.getAttribute(attribute);
166                        return (value !== undefined) ? value : defaultValue; //Object
167                }
168        },
169       
170        getValues: function(/* item */ item,
171                                                /* attribute|attribute-name-string */ attribute){
172                // summary:
173                //              See dojo/data/api/Read.getValues()
174                this._assertIsItem(item);
175                this._assertIsAttribute(attribute);
176                var array = [];
177                if(attribute == 'children'){
178                        for(var i = 0; i < item.childNodes.length; ++i){
179                                array.push(item.childNodes[i]);
180                        }
181                } else if(item.getAttribute(attribute) !== null){
182                                array.push(item.getAttribute(attribute));
183                }
184                return array; // Array
185        },
186       
187        getAttributes: function(/* item */ item){
188                // summary:
189                //              See dojo/data/api/Read.getAttributes()
190                this._assertIsItem(item);
191                var attributes = [];
192                var xmlNode = item;
193                var xmlAttributes = xmlNode.attributes;
194                for(var i = 0; i < xmlAttributes.length; ++i){
195                        var xmlAttribute = xmlAttributes.item(i);
196                        attributes.push(xmlAttribute.nodeName);
197                }
198                if(xmlNode.childNodes.length > 0){
199                        attributes.push('children');
200                }
201                return attributes; //Array
202        },
203       
204        hasAttribute: function( /* item */ item,
205                                                        /* attribute|attribute-name-string */ attribute){
206                // summary:
207                //              See dojo/data/api/Read.hasAttribute()
208                return (this.getValues(item, attribute).length > 0); //Boolean
209        },
210       
211        containsValue: function(/* item */ item,
212                                                        /* attribute|attribute-name-string */ attribute,
213                                                        /* anything */ value){
214                // summary:
215                //              See dojo/data/api/Read.containsValue()
216                var regexp = undefined;
217                if(typeof value === "string"){
218                        regexp = filterUtil.patternToRegExp(value, false);
219                }
220                return this._containsValue(item, attribute, value, regexp); //boolean.
221        },
222
223        _containsValue: function(       /* item */ item,
224                                                                /* attribute|attribute-name-string */ attribute,
225                                                                /* anything */ value,
226                                                                /* RegExp?*/ regexp){
227                // summary:
228                //              Internal function for looking at the values contained by the item.
229                // description:
230                //              Internal function for looking at the values contained by the item.  This
231                //              function allows for denoting if the comparison should be case sensitive for
232                //              strings or not (for handling filtering cases where string case should not matter)
233                // item:
234                //              The data item to examine for attribute values.
235                // attribute:
236                //              The attribute to inspect.
237                // value:
238                //              The value to match.
239                // regexp:
240                //              Optional regular expression generated off value if value was of string type to handle wildcarding.
241                //              If present and attribute values are string, then it can be used for comparison instead of 'value'
242                var values = this.getValues(item, attribute);
243                for(var i = 0; i < values.length; ++i){
244                        var possibleValue = values[i];
245                        if(typeof possibleValue === "string" && regexp){
246                                return (possibleValue.match(regexp) !== null);
247                        }else{
248                                //Non-string matching.
249                                if(value === possibleValue){
250                                        return true; // Boolean
251                                }
252                        }
253                }
254                return false; // Boolean
255        },
256                       
257        isItem: function(/* anything */ something){
258                // summary:
259                //              See dojo/data/api/Read.isItem()
260                // description:
261                //              Four things are verified to ensure that "something" is an item:
262                //              something can not be null, the nodeType must be an XML Element,
263                //              the tagName must be "outline", and the node must be a member of
264                //              XML document for this datastore.
265                return (something &&
266                                something.nodeType == 1 &&
267                                something.tagName == 'outline' &&
268                                something.ownerDocument === this._xmlData); //Boolean
269        },
270       
271        isItemLoaded: function(/* anything */ something){
272                // summary:
273                //              See dojo/data/api/Read.isItemLoaded().
274                //              OpmlStore loads every item, so if it's an item, then it's loaded.
275                return this.isItem(something); //Boolean
276        },
277       
278        loadItem: function(/* item */ item){
279                // summary:
280                //              See dojo/data/api/Read.loadItem()
281                // description:
282                //              The OpmlStore always loads all items, so if it's an item, then it's loaded.
283                //
284                //              From the dojo/data/api/Read.loadItem docs:
285                //
286                //                      If a call to isItemLoaded() returns true before loadItem() is even called,
287                //                      then loadItem() need not do any work at all and will not even invoke the callback handlers.
288        },
289
290        getLabel: function(/* item */ item){
291                // summary:
292                //              See dojo/data/api/Read.getLabel()
293                if(this.isItem(item)){
294                        return this.getValue(item,this.label); //String
295                }
296                return undefined; //undefined
297        },
298
299        getLabelAttributes: function(/* item */ item){
300                // summary:
301                //              See dojo/data/api/Read.getLabelAttributes()
302                return [this.label]; //array
303        },
304
305        // The dojo/data/api/Read.fetch() function is implemented as
306        // a mixin from dojo.data.util.simpleFetch.
307        // That mixin requires us to define _fetchItems().
308        _fetchItems: function(  /* Object */ keywordArgs,
309                                                        /* Function */ findCallback,
310                                                        /* Function */ errorCallback){
311                // summary:
312                //              See dojo.data.util.simpleFetch.fetch()
313               
314                var self = this;
315                var filter = function(requestArgs, arrayOfItems){
316                        var items = null;
317                        if(requestArgs.query){
318                                items = [];
319                                var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
320
321                                //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
322                                //same value for each item examined.  Much more efficient.
323                                var regexpList = {};
324                                for(var key in requestArgs.query){
325                                        var value = requestArgs.query[key];
326                                        if(typeof value === "string"){
327                                                regexpList[key] = filterUtil.patternToRegExp(value, ignoreCase);
328                                        }
329                                }
330
331                                for(var i = 0; i < arrayOfItems.length; ++i){
332                                        var match = true;
333                                        var candidateItem = arrayOfItems[i];
334                                        for(var key in requestArgs.query){
335                                                var value = requestArgs.query[key];
336                                                if(!self._containsValue(candidateItem, key, value, regexpList[key])){
337                                                        match = false;
338                                                }
339                                        }
340                                        if(match){
341                                                items.push(candidateItem);
342                                        }
343                                }
344                        }else{
345                                // We want a copy to pass back in case the parent wishes to sort the array.  We shouldn't allow resort
346                                // of the internal list so that multiple callers can get lists and sort without affecting each other.
347                                if(arrayOfItems.length> 0){
348                                        items = arrayOfItems.slice(0,arrayOfItems.length);
349                                }
350                        }
351                        findCallback(items, requestArgs);
352                };
353
354                if(this._loadFinished){
355                        filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
356                }else{
357
358                        //If fetches come in before the loading has finished, but while
359                        //a load is in progress, we have to defer the fetching to be
360                        //invoked in the callback.
361                        if(this._loadInProgress){
362                                this._queuedFetches.push({args: keywordArgs, filter: filter});
363                        }else{
364                                if(this.url !== ""){
365                                        this._loadInProgress = true;
366                                        var getArgs = {
367                                                        url: self.url,
368                                                        handleAs: "xml",
369                                                        preventCache: self.urlPreventCache
370                                                };
371                                        var getHandler = xhr.get(getArgs);
372                                        getHandler.addCallback(function(data){
373                                                self._processRawXmlTree(data);
374                                                filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions));
375                                                self._handleQueuedFetches();
376                                        });
377                                        getHandler.addErrback(function(error){
378                                                throw error;
379                                        });
380                                }else if(this._opmlData){
381                                        this._processRawXmlTree(this._opmlData);
382                                        this._opmlData = null;
383                                        filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
384                                }else{
385                                        throw new Error("dojox.data.OpmlStore: No OPML source data was provided as either URL or XML data input.");
386                                }
387                        }
388                }
389        },
390       
391        getFeatures: function(){
392                // summary:
393                //              See dojo/data/api/Read.getFeatures()
394                var features = {
395                        'dojo.data.api.Read': true,
396                        'dojo.data.api.Identity': true
397                };
398                return features; //Object
399        },
400
401/***************************************
402     dojo/data/api/Identity API
403***************************************/
404        getIdentity: function(/* item */ item){
405                // summary:
406                //              See dojo/data/api/Identity.getIdentity()
407                if(this.isItem(item)){
408                        //No other way to do this other than O(n) without
409                        //complete rework of how the tree stores nodes.
410                        for(var i in this._identityMap){
411                                if(this._identityMap[i] === item){
412                                        return i;
413                                }
414                        }
415                }
416                return null; //null
417        },
418
419        fetchItemByIdentity: function(/* Object */ keywordArgs){
420                // summary:
421                //              See dojo/data/api/Identity.fetchItemByIdentity()
422
423                //Hasn't loaded yet, we have to trigger the load.
424                if(!this._loadFinished){
425                        var self = this;
426                        if(this.url !== ""){
427                                //If fetches come in before the loading has finished, but while
428                                //a load is in progress, we have to defer the fetching to be
429                                //invoked in the callback.
430                                if(this._loadInProgress){
431                                        this._queuedFetches.push({args: keywordArgs});
432                                }else{
433                                        this._loadInProgress = true;
434                                        var getArgs = {
435                                                        url: self.url,
436                                                        handleAs: "xml"
437                                                };
438                                        var getHandler = xhr.get(getArgs);
439                                        getHandler.addCallback(function(data){
440                                                var scope = keywordArgs.scope ? keywordArgs.scope : kernel.global;
441                                                try{
442                                                        self._processRawXmlTree(data);
443                                                        var item = self._identityMap[keywordArgs.identity];
444                                                        if(!self.isItem(item)){
445                                                                item = null;
446                                                        }
447                                                        if(keywordArgs.onItem){
448                                                                keywordArgs.onItem.call(scope, item);
449                                                        }
450                                                        self._handleQueuedFetches();
451                                                }catch(error){
452                                                        if(keywordArgs.onError){
453                                                                keywordArgs.onError.call(scope, error);
454                                                        }
455                                                }
456                                        });
457                                        getHandler.addErrback(function(error){
458                                                this._loadInProgress = false;
459                                                if(keywordArgs.onError){
460                                                        var scope = keywordArgs.scope ? keywordArgs.scope : kernel.global;
461                                                        keywordArgs.onError.call(scope, error);
462                                                }
463                                        });
464                                }
465                        }else if(this._opmlData){
466                                this._processRawXmlTree(this._opmlData);
467                                this._opmlData = null;
468                                var item = this._identityMap[keywordArgs.identity];
469                                if(!self.isItem(item)){
470                                        item = null;
471                                }
472                                if(keywordArgs.onItem){
473                                        var scope = keywordArgs.scope ? keywordArgs.scope : kernel.global;
474                                        keywordArgs.onItem.call(scope, item);
475                                }
476                        }
477                }else{
478                        //Already loaded.  We can just look it up and call back.
479                        var item = this._identityMap[keywordArgs.identity];
480                        if(!this.isItem(item)){
481                                item = null;
482                        }
483                        if(keywordArgs.onItem){
484                                var scope = keywordArgs.scope ? keywordArgs.scope : kernel.global;
485                                keywordArgs.onItem.call(scope, item);
486                        }
487                }
488        },
489
490        getIdentityAttributes: function(/* item */ item){
491                // summary:
492                //              See dojo/data/api/Identity.getIdentifierAttributes()
493
494                //Identity isn't a public attribute in the item, it's the node count.
495                //So, return null.
496                return null;
497        },
498
499        _handleQueuedFetches: function(){
500                // summary:
501                //              Internal function to execute delayed request in the store.
502               
503                //Execute any deferred fetches now.
504                if(this._queuedFetches.length > 0){
505                        for(var i = 0; i < this._queuedFetches.length; i++){
506                                var fData = this._queuedFetches[i];
507                                var delayedQuery = fData.args;
508                                var delayedFilter = fData.filter;
509                                if(delayedFilter){
510                                        delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions));
511                                }else{
512                                        this.fetchItemByIdentity(delayedQuery);
513                                }
514                        }
515                        this._queuedFetches = [];
516                }
517        },
518
519        close: function(/*dojo/data/api/Request|Object?*/ request){
520                // summary:
521                //              See dojo/data/api/Read.close()
522        }
523});
524//Mix in the simple fetch implementation to this class.
525lang.extend(OpmlStore, simpleFetch);
526
527return OpmlStore;
528});
529       
Note: See TracBrowser for help on using the repository browser.