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