Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 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
}