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.XmlStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2
dojo._hasResource["dojox.data.XmlStore"] = true;
3
dojo.provide("dojox.data.XmlStore");
4
dojo.provide("dojox.data.XmlItem");
5
 
6
dojo.require("dojo.data.util.simpleFetch");
7
dojo.require("dojo.data.util.filter");
8
dojo.require("dojox.data.dom");
9
 
10
dojo.declare("dojox.data.XmlStore", null, {
11
	//	summary:
12
	//		A data store for XML based services or documents
13
	//	description:
14
	//		A data store for XML based services or documents
15
 
16
	constructor: function(/* object */ args) {
17
		//	summary:
18
		//		Constructor for the XML store.
19
		//	args:
20
		//		An anonymous object to initialize properties.  It expects the following values:
21
		//		url:		The url to a service or an XML document that represents the store
22
		//		rootItem:	A tag name for root items
23
		//		keyAttribute:	An attribute name for a key or an indentify
24
		// 		attributeMap:   An anonymous object contains properties for attribute mapping,
25
		//						{"tag_name.item_attribute_name": "@xml_attribute_name", ...}
26
		//		sendQuery:		A boolean indicate to add a query string to the service URL
27
		console.log("XmlStore()");
28
		if(args){
29
			this._url = args.url;
30
			this._rootItem = (args.rootItem || args.rootitem);
31
			this._keyAttribute = (args.keyAttribute || args.keyattribute);
32
			this._attributeMap = (args.attributeMap || args.attributemap);
33
			this._labelAttr = args.label;
34
			this._sendQuery = (args.sendQuery || args.sendquery);
35
		}
36
		this._newItems = [];
37
		this._deletedItems = [];
38
		this._modifiedItems = [];
39
	},
40
 
41
/* dojo.data.api.Read */
42
 
43
	getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){
44
		//	summary:
45
		//		Return an attribute value
46
		//	description:
47
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
48
		//		If 'attribute' specifies "tagName", the tag name of the element is
49
		//		returned.
50
		//		If 'attribute' specifies "childNodes", the first element child is
51
		//		returned.
52
		//		If 'attribute' specifies "text()", the value of the first text
53
		//		child is returned.
54
		//		For generic attributes, if '_attributeMap' is specified,
55
		//		an actual attribute name is looked up with the tag name of
56
		//		the element and 'attribute' (concatenated with '.').
57
		//		Then, if 'attribute' starts with "@", the value of the XML
58
		//		attribute is returned.
59
		//		Otherwise, the first child element of the tag name specified with
60
		//		'attribute' is returned.
61
		//	item:
62
		//		An XML element that holds the attribute
63
		//	attribute:
64
		//		A tag name of a child element, An XML attribute name or one of
65
		// 		special names
66
		//	defaultValue:
67
		//		A default value
68
		//	returns:
69
		//		An attribute value found, otherwise 'defaultValue'
70
		var element = item.element;
71
		if(attribute === "tagName"){
72
			return element.nodeName;
73
		}else if (attribute === "childNodes"){
74
			for (var i = 0; i < element.childNodes.length; i++) {
75
				var node = element.childNodes[i];
76
				if (node.nodeType === 1 /*ELEMENT_NODE*/) {
77
					return this._getItem(node); //object
78
				}
79
			}
80
			return defaultValue;
81
		}else if(attribute === "text()"){
82
			for(var i = 0; i < element.childNodes.length; i++){
83
				var node = element.childNodes[i];
84
				if(node.nodeType === 3 /*TEXT_NODE*/ ||
85
					node.nodeType === 4 /*CDATA_SECTION_NODE*/){
86
					return node.nodeValue; //string
87
				}
88
			}
89
			return defaultValue;
90
		}else{
91
			attribute = this._getAttribute(element.nodeName, attribute);
92
			if(attribute.charAt(0) === '@'){
93
				var name = attribute.substring(1);
94
				var value = element.getAttribute(name);
95
				return (value !== undefined) ? value : defaultValue; //object
96
			}else{
97
				for(var i = 0; i < element.childNodes.length; i++){
98
					var node = element.childNodes[i];
99
					if(	node.nodeType === 1 /*ELEMENT_NODE*/ &&
100
						node.nodeName === attribute){
101
						return this._getItem(node); //object
102
					}
103
				}
104
				return defaultValue; //object
105
			}
106
		}
107
	},
108
 
109
	getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
110
		//	summary:
111
		//		Return an array of attribute values
112
		//	description:
113
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
114
		//		If 'attribute' specifies "tagName", the tag name of the element is
115
		//		returned.
116
		//		If 'attribute' specifies "childNodes", child elements are returned.
117
		//		If 'attribute' specifies "text()", the values of child text nodes
118
		//		are returned.
119
		//		For generic attributes, if '_attributeMap' is specified,
120
		//		an actual attribute name is looked up with the tag name of
121
		//		the element and 'attribute' (concatenated with '.').
122
		//		Then, if 'attribute' starts with "@", the value of the XML
123
		//		attribute is returned.
124
		//		Otherwise, child elements of the tag name specified with
125
		//		'attribute' are returned.
126
		//	item:
127
		//		An XML element that holds the attribute
128
		//	attribute:
129
		//		A tag name of child elements, An XML attribute name or one of
130
		//		special names
131
		//	returns:
132
		//		An array of attribute values found, otherwise an empty array
133
		var element = item.element;
134
		if(attribute === "tagName"){
135
			return [element.nodeName];
136
		}else if(attribute === "childNodes"){
137
			var values = [];
138
			for(var i = 0; i < element.childNodes.length; i++){
139
				var node = element.childNodes[i];
140
				if(node.nodeType === 1 /*ELEMENT_NODE*/){
141
					values.push(this._getItem(node));
142
				}
143
			}
144
			return values; //array
145
		}else if(attribute === "text()"){
146
			var values = [];
147
			for(var i = 0; i < element.childNodes.length; i++){
148
				var node = childNodes[i];
149
				if(node.nodeType === 3){
150
					values.push(node.nodeValue);
151
				}
152
			}
153
			return values; //array
154
		}else{
155
			attribute = this._getAttribute(element.nodeName, attribute);
156
			if(attribute.charAt(0) === '@'){
157
				var name = attribute.substring(1);
158
				var value = element.getAttribute(name);
159
				return (value !== undefined) ? [value] : []; //array
160
			}else{
161
				var values = [];
162
				for(var i = 0; i < element.childNodes.length; i++){
163
					var node = element.childNodes[i];
164
					if(	node.nodeType === 1 /*ELEMENT_NODE*/ &&
165
						node.nodeName === attribute){
166
						values.push(this._getItem(node));
167
					}
168
				}
169
				return values; //array
170
			}
171
		}
172
	},
173
 
174
	getAttributes: function(/* item */ item) {
175
		//	summary:
176
		//		Return an array of attribute names
177
		// 	description:
178
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
179
		//		tag names of child elements and XML attribute names of attributes
180
		//		specified to the element are returned along with special attribute
181
		//		names applicable to the element including "tagName", "childNodes"
182
		//		if the element has child elements, "text()" if the element has
183
		//		child text nodes, and attribute names in '_attributeMap' that match
184
		//		the tag name of the element.
185
		//	item:
186
		//		An XML element
187
		//	returns:
188
		//		An array of attributes found
189
		var element = item.element;
190
		var attributes = [];
191
		attributes.push("tagName");
192
		if(element.childNodes.length > 0){
193
			var names = {};
194
			var childNodes = true;
195
			var text = false;
196
			for(var i = 0; i < element.childNodes.length; i++){
197
				var node = element.childNodes[i];
198
				if (node.nodeType === 1 /*ELEMENT_NODE*/) {
199
					var name = node.nodeName;
200
					if(!names[name]){
201
						attributes.push(name);
202
						names[name] = name;
203
					}
204
					childNodes = true;
205
				}else if(node.nodeType === 3){
206
					text = true;
207
				}
208
			}
209
			if(childNodes){
210
				attributes.push("childNodes");
211
			}
212
			if(text){
213
				attributes.push("text()");
214
			}
215
		}
216
		for(var i = 0; i < element.attributes.length; i++){
217
			attributes.push("@" + element.attributes[i].nodeName);
218
		}
219
		if(this._attributeMap){
220
			for (var key in this._attributeMap){
221
				var i = key.indexOf('.');
222
				if(i > 0){
223
					var tagName = key.substring(0, i);
224
					if (tagName === element.nodeName){
225
						attributes.push(key.substring(i + 1));
226
					}
227
				}else{ // global attribute
228
					attributes.push(key);
229
				}
230
			}
231
		}
232
		return attributes; //array
233
	},
234
 
235
	hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
236
		//	summary:
237
		//		Check whether an element has the attribute
238
		//	item:
239
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
240
		//	attribute:
241
		//		A tag name of a child element, An XML attribute name or one of
242
		//		special names
243
		//	returns:
244
		//		True if the element has the attribute, otherwise false
245
		return (this.getValue(item, attribute) !== undefined); //boolean
246
	},
247
 
248
	containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){
249
		//	summary:
250
		//		Check whether the attribute values contain the value
251
		//	item:
252
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
253
		//	attribute:
254
		//		A tag name of a child element, An XML attribute name or one of
255
		//		special names
256
		//	returns:
257
		//		True if the attribute values contain the value, otherwise false
258
		var values = this.getValues(item, attribute);
259
		for(var i = 0; i < values.length; i++){
260
			if((typeof value === "string")){
261
				if(values[i].toString && values[i].toString() === value){
262
					return true;
263
				}
264
			}else if (values[i] === value){
265
				return true; //boolean
266
			}
267
		}
268
		return false;//boolean
269
	},
270
 
271
	isItem: function(/* anything */ something){
272
		//	summary:
273
		//		Check whether the object is an item (XML element)
274
		//	item:
275
		//		An object to check
276
		// 	returns:
277
		//		True if the object is an XML element, otherwise false
278
		if(something && something.element && something.store && something.store === this){
279
			return true; //boolean
280
		}
281
		return false; //boolran
282
	},
283
 
284
	isItemLoaded: function(/* anything */ something){
285
		//	summary:
286
		//		Check whether the object is an item (XML element) and loaded
287
		//	item:
288
		//		An object to check
289
		//	returns:
290
		//		True if the object is an XML element, otherwise false
291
		return this.isItem(something); //boolean
292
	},
293
 
294
	loadItem: function(/* object */ keywordArgs){
295
		//	summary:
296
		//		Load an item (XML element)
297
		//	keywordArgs:
298
		//		object containing the args for loadItem.  See dojo.data.api.Read.loadItem()
299
	},
300
 
301
	getFeatures: function() {
302
		//	summary:
303
		//		Return supported data APIs
304
		//	returns:
305
		//		"dojo.data.api.Read" and "dojo.data.api.Write"
306
		var features = {
307
			"dojo.data.api.Read": true,
308
			"dojo.data.api.Write": true
309
		};
310
		return features; //array
311
	},
312
 
313
	getLabel: function(/* item */ item){
314
		//	summary:
315
		//		See dojo.data.api.Read.getLabel()
316
		if(this._labelAttr && this.isItem(item)){
317
			var label = this.getValue(item,this._labelAttr);
318
			if(label){
319
				return label.toString();
320
			}
321
		}
322
		return undefined; //undefined
323
	},
324
 
325
	getLabelAttributes: function(/* item */ item){
326
		//	summary:
327
		//		See dojo.data.api.Read.getLabelAttributes()
328
		if(this._labelAttr){
329
			return [this._labelAttr]; //array
330
		}
331
		return null; //null
332
	},
333
 
334
	_fetchItems: function(request, fetchHandler, errorHandler) {
335
		//	summary:
336
		//		Fetch items (XML elements) that match to a query
337
		//	description:
338
		//		If '_sendQuery' is true, an XML document is loaded from
339
		//		'_url' with a query string.
340
		//		Otherwise, an XML document is loaded and list XML elements that
341
		//		match to a query (set of element names and their text attribute
342
		//		values that the items to contain).
343
		//		A wildcard, "*" can be used to query values to match all
344
		//		occurrences.
345
		//		If '_rootItem' is specified, it is used to fetch items.
346
		//	request:
347
		//		A request object
348
		//	fetchHandler:
349
		//		A function to call for fetched items
350
		//	errorHandler:
351
		//		A function to call on error
352
		var url = this._getFetchUrl(request);
353
		console.log("XmlStore._fetchItems(): url=" + url);
354
		if(!url){
355
			errorHandler(new Error("No URL specified."));
356
			return;
357
		}
358
		var localRequest = (!this._sendQuery ? request : null); // use request for _getItems()
359
 
360
		var self = this;
361
		var getArgs = {
362
				url: url,
363
				handleAs: "xml",
364
				preventCache: true
365
			};
366
		var getHandler = dojo.xhrGet(getArgs);
367
		getHandler.addCallback(function(data){
368
			var items = self._getItems(data, localRequest);
369
			console.log("XmlStore._fetchItems(): length=" + (items ? items.length : 0));
370
			if (items && items.length > 0) {
371
				fetchHandler(items, request);
372
			}
373
			else {
374
				fetchHandler([], request);
375
			}
376
		});
377
		getHandler.addErrback(function(data){
378
			errorHandler(data, request);
379
		});
380
	},
381
 
382
	_getFetchUrl: function(request){
383
		//	summary:
384
		//		Generate a URL for fetch
385
		//	description:
386
		//		This default implementation generates a query string in the form of
387
		//		"?name1=value1&name2=value2..." off properties of 'query' object
388
		//		specified in 'request' and appends it to '_url', if '_sendQuery'
389
		//		is set to false.
390
		//		Otherwise, '_url' is returned as is.
391
		//		Sub-classes may override this method for the custom URL generation.
392
		//	request:
393
		//		A request object
394
		//	returns:
395
		//		A fetch URL
396
		if(!this._sendQuery){
397
			return this._url;
398
		}
399
		var query = request.query;
400
		if(!query){
401
			return this._url;
402
		}
403
		if(dojo.isString(query)){
404
			return this._url + query;
405
		}
406
		var queryString = "";
407
		for(var name in query){
408
			var value = query[name];
409
			if(value){
410
				if(queryString){
411
					queryString += "&";
412
				}
413
				queryString += (name + "=" + value);
414
			}
415
		}
416
		if(!queryString){
417
			return this._url;
418
		}
419
		//Check to see if the URL already has query params or not.
420
		var fullUrl = this._url;
421
		if(fullUrl.indexOf("?") < 0){
422
			fullUrl += "?";
423
		}else{
424
			fullUrl += "&";
425
		}
426
		return fullUrl + queryString;
427
	},
428
 
429
	_getItems: function(document, request) {
430
		//	summary:
431
		//		Fetch items (XML elements) in an XML document based on a request
432
		//	description:
433
		//		This default implementation walks through child elements of
434
		//		the document element to see if all properties of 'query' object
435
		//		match corresponding attributes of the element (item).
436
		//		If 'request' is not specified, all child elements are returned.
437
		//		Sub-classes may override this method for the custom search in
438
		//		an XML document.
439
		//	document:
440
		//		An XML document
441
		//	request:
442
		//		A request object
443
		//	returns:
444
		//		An array of items
445
		var query = null;
446
		if(request){
447
			query = request.query;
448
		}
449
		var items = [];
450
		var nodes = null;
451
		if(this._rootItem){
452
			nodes = document.getElementsByTagName(this._rootItem);
453
		}
454
		else{
455
			nodes = document.documentElement.childNodes;
456
		}
457
		for(var i = 0; i < nodes.length; i++){
458
			var node = nodes[i];
459
			if(node.nodeType != 1 /*ELEMENT_NODE*/){
460
				continue;
461
			}
462
            var item = this._getItem(node);
463
			if(query){
464
				var found = true;
465
				var ignoreCase = request.queryOptions ? request.queryOptions.ignoreCase : false;
466
 
467
				//See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
468
				//same value for each item examined.  Much more efficient.
469
				var regexpList = {};
470
				for(var key in query){
471
					var value = query[key];
472
					if(typeof value === "string"){
473
						regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase);
474
					}
475
				}
476
 
477
				for(var attribute in query){
478
					var value = this.getValue(item, attribute);
479
					if(value){
480
						var queryValue = query[attribute];
481
						if ((typeof value) === "string" &&
482
							(regexpList[attribute])){
483
							if((value.match(regexpList[attribute])) !== null){
484
								continue;
485
							}
486
						}else if((typeof value) === "object"){
487
							if(	value.toString &&
488
								(regexpList[attribute])){
489
								var stringValue = value.toString();
490
								if((stringValue.match(regexpList[attribute])) !== null){
491
									continue;
492
								}
493
							}else{
494
								if(queryValue === "*" || queryValue === value){
495
									continue;
496
								}
497
							}
498
						}
499
					}
500
					found = false;
501
					break;
502
				}
503
				if(!found){
504
					continue;
505
				}
506
			}
507
			items.push(item);
508
		}
509
		dojo.forEach(items,function(item){
510
			item.element.parentNode.removeChild(item.element); // make it root
511
		},this);
512
		return items;
513
	},
514
 
515
	close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
516
		 //	summary:
517
		 //		See dojo.data.api.Read.close()
518
	},
519
 
520
/* dojo.data.api.Write */
521
 
522
	newItem: function(/* object? */ keywordArgs){
523
		//	summary:
524
		//		Return a new dojox.data.XmlItem
525
		//	description:
526
		//		At least, 'keywordArgs' must contain "tagName" to be used for
527
		//		the new	element.
528
		//		Other attributes in 'keywordArgs' are set to the new element,
529
		//		including "text()", but excluding "childNodes".
530
		// 	keywordArgs:
531
		//		An object containing initial attributes
532
		//	returns:
533
		//		An XML element
534
		console.log("XmlStore.newItem()");
535
		keywordArgs = (keywordArgs || {});
536
		var tagName = keywordArgs.tagName;
537
		if(!tagName){
538
			tagName = this._rootItem;
539
			if(!tagName){
540
				return null;
541
			}
542
		}
543
 
544
		var document = this._getDocument();
545
		var element = document.createElement(tagName);
546
		for(var attribute in keywordArgs){
547
			if(attribute === "tagName"){
548
				continue;
549
			}else if(attribute === "text()"){
550
				var text = document.createTextNode(keywordArgs[attribute]);
551
				element.appendChild(text);
552
			}else{
553
				attribute = this._getAttribute(tagName, attribute);
554
				if(attribute.charAt(0) === '@'){
555
					var name = attribute.substring(1);
556
					element.setAttribute(name, keywordArgs[attribute]);
557
				}else{
558
					var child = document.createElement(attribute);
559
					var text = document.createTextNode(keywordArgs[attribute]);
560
					child.appendChild(text);
561
					element.appendChild(child);
562
				}
563
			}
564
		}
565
 
566
		var item = this._getItem(element);
567
		this._newItems.push(item);
568
		return item; //object
569
	},
570
 
571
	deleteItem: function(/* item */ item){
572
		//	summary:
573
		//		Delete an dojox.data.XmlItem (wrapper to a XML element).
574
		//	item:
575
		//		An XML element to delete
576
		//	returns:
577
		//		True
578
		console.log("XmlStore.deleteItem()");
579
		var element = item.element;
580
		if(element.parentNode){
581
			this._backupItem(item);
582
			element.parentNode.removeChild(element);
583
			return true;
584
		}
585
		this._forgetItem(item);
586
		this._deletedItems.push(item);
587
		return true; //boolean
588
	},
589
 
590
	setValue: function(/* item */ item, /* attribute || string */ attribute, /* almost anything */ value){
591
		//	summary:
592
		//		Set an attribute value
593
		//	description:
594
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
595
		//		If 'attribute' specifies "tagName", nothing is set and false is
596
		//		returned.
597
		//		If 'attribute' specifies "childNodes", the value (XML element) is
598
		//		added to the element.
599
		//		If 'attribute' specifies "text()", a text node is created with
600
		//		the value and set it to the element as a child.
601
		//		For generic attributes, if '_attributeMap' is specified,
602
		//		an actual attribute name is looked up with the tag name of
603
		//		the element and 'attribute' (concatenated with '.').
604
		//		Then, if 'attribute' starts with "@", the value is set to the XML
605
		//		attribute.
606
		//		Otherwise, a text node is created with the value and set it to
607
		//		the first child element of the tag name specified with 'attribute'.
608
		//		If the child element does not exist, it is created.
609
		//	item:
610
		//		An XML element that holds the attribute
611
		//	attribute:
612
		//		A tag name of a child element, An XML attribute name or one of
613
		//		special names
614
		//	value:
615
		//		A attribute value to set
616
		//	returns:
617
		//		False for "tagName", otherwise true
618
		if(attribute === "tagName"){
619
			return false; //boolean
620
		}
621
 
622
		this._backupItem(item);
623
 
624
		var element = item.element;
625
		if(attribute === "childNodes"){
626
			var child = value.element;
627
			element.appendChild(child);
628
		}else if(attribute === "text()"){
629
			while (element.firstChild){
630
				element.removeChild(element.firstChild);
631
			}
632
			var text = this._getDocument(element).createTextNode(value);
633
			element.appendChild(text);
634
		}else{
635
			attribute = this._getAttribute(element.nodeName, attribute);
636
			if(attribute.charAt(0) === '@'){
637
				var name = attribute.substring(1);
638
				element.setAttribute(name, value);
639
			}else{
640
				var child = null;
641
				for(var i = 0; i < element.childNodes.length; i++){
642
					var node = element.childNodes[i];
643
					if(	node.nodeType === 1 /*ELEMENT_NODE*/&&
644
						node.nodeName === attribute){
645
						child = node;
646
						break;
647
					}
648
				}
649
				var document = this._getDocument(element);
650
				if(child){
651
					while(child.firstChild){
652
						child.removeChild(child.firstChild);
653
					}
654
				}else{
655
					child = document.createElement(attribute);
656
					element.appendChild(child);
657
				}
658
				var text = document.createTextNode(value);
659
				child.appendChild(text);
660
			}
661
		}
662
		return true; //boolean
663
	},
664
 
665
	setValues: function(/* item */ item, /* attribute || string */ attribute, /* array */ values){
666
		//	summary:
667
		//		Set attribute values
668
		//	description:
669
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
670
		//		If 'attribute' specifies "tagName", nothing is set and false is
671
		//		returned.
672
		//		If 'attribute' specifies "childNodes", the value (array of XML
673
		//		elements) is set to the element's childNodes.
674
		//		If 'attribute' specifies "text()", a text node is created with
675
		//		the values and set it to the element as a child.
676
		//		For generic attributes, if '_attributeMap' is specified,
677
		//		an actual attribute name is looked up with the tag name of
678
		//		the element and 'attribute' (concatenated with '.').
679
		//		Then, if 'attribute' starts with "@", the first value is set to
680
		//		the XML attribute.
681
		//		Otherwise, child elements of the tag name specified with
682
		//		'attribute' are replaced with new child elements and their
683
		//		child text nodes of values.
684
		//	item:
685
		//		An XML element that holds the attribute
686
		//	attribute:
687
		//		A tag name of child elements, an XML attribute name or one of
688
		//		special names
689
		//	value:
690
		//		A attribute value to set
691
		//	returns:
692
		//		False for "tagName", otherwise true
693
		if(attribute === "tagName"){
694
			return false; //boolean
695
		}
696
 
697
		this._backupItem(item);
698
 
699
		var element = item.element;
700
		if(attribute === "childNodes"){
701
			while(element.firstChild){
702
				element.removeChild(element.firstChild);
703
			}
704
			for(var i = 0; i < values.length; i++){
705
				var child = values[i].element;
706
				element.appendChild(child);
707
			}
708
		}else if(attribute === "text()"){
709
			while (element.firstChild){
710
				element.removeChild(element.firstChild);
711
			}
712
			var value = "";
713
			for(var i = 0; i < values.length; i++){
714
				value += values[i];
715
			}
716
			var text = this._getDocument(element).createTextNode(value);
717
			element.appendChild(text);
718
		}else{
719
			attribute = this._getAttribute(element.nodeName, attribute);
720
			if(attribute.charAt(0) === '@'){
721
				var name = attribute.substring(1);
722
				element.setAttribute(name, values[0]);
723
			}else{
724
				for(var i = element.childNodes.length - 1; i >= 0; i--){
725
					var node = element.childNodes[i];
726
					if(	node.nodeType === 1 /*ELEMENT_NODE*/ &&
727
						node.nodeName === attribute){
728
						element.removeChild(node);
729
					}
730
				}
731
				var document = this._getDocument(element);
732
				for(var i = 0; i < values.length; i++){
733
					var child = document.createElement(attribute);
734
					var text = document.createTextNode(values[i]);
735
					child.appendChild(text);
736
					element.appendChild(child);
737
				}
738
			}
739
		}
740
		return true; //boolean
741
	},
742
 
743
	unsetAttribute: function(/* item */ item, /* attribute || string */ attribute){
744
		//	summary:
745
		//		Remove an attribute
746
		//	description:
747
		//		'item' must be an instance of a dojox.data.XmlItem from the store instance.
748
		//		'attribute' can be an XML attribute name of the element or one of
749
		//		special names described below.
750
		//		If 'attribute' specifies "tagName", nothing is removed and false is
751
		//		returned.
752
		//		If 'attribute' specifies "childNodes" or "text()", all child nodes
753
		//		are removed.
754
		//		For generic attributes, if '_attributeMap' is specified,
755
		//		an actual attribute name is looked up with the tag name of
756
		//		the element and 'attribute' (concatenated with '.').
757
		//		Then, if 'attribute' starts with "@", the XML attribute is removed.
758
		//		Otherwise, child elements of the tag name specified with
759
		//		'attribute' are removed.
760
		//	item:
761
		//		An XML element that holds the attribute
762
		//	attribute:
763
		//		A tag name of child elements, an XML attribute name or one of
764
		//		special names
765
		//	returns:
766
		//		False for "tagName", otherwise true
767
		if(attribute === "tagName"){
768
			return false; //boolean
769
		}
770
 
771
		this._backupItem(item);
772
 
773
		var element = item.element;
774
		if(attribute === "childNodes" || attribute === "text()"){
775
			while(element.firstChild){
776
				element.removeChild(element.firstChild);
777
			}
778
		}else{
779
			attribute = this._getAttribute(element.nodeName, attribute);
780
			if(attribute.charAt(0) === '@'){
781
				var name = attribute.substring(1);
782
				element.removeAttribute(name);
783
			}else{
784
				for(var i = element.childNodes.length - 1; i >= 0; i--){
785
					var node = element.childNodes[i];
786
					if(	node.nodeType === 1 /*ELEMENT_NODE*/ &&
787
						node.nodeName === attribute){
788
						element.removeChild(node);
789
					}
790
				}
791
			}
792
		}
793
		return true; //boolean
794
	},
795
 
796
	save: function(/* object */ keywordArgs){
797
		//	summary:
798
		//		Save new and/or modified items (XML elements)
799
		// 	description:
800
		//		'_url' is used to save XML documents for new, modified and/or
801
		//		deleted XML elements.
802
		// 	keywordArgs:
803
		//		An object for callbacks
804
		if(!keywordArgs){
805
			keywordArgs = {};
806
		}
807
		for(var i = 0; i < this._modifiedItems.length; i++){
808
			this._saveItem(this._modifiedItems[i], keywordArgs, "PUT");
809
		}
810
		for(var i = 0; i < this._newItems.length; i++){
811
			var item = this._newItems[i];
812
			if(item.element.parentNode){ // reparented
813
				this._newItems.splice(i, 1);
814
				i--;
815
				continue;
816
			}
817
			this._saveItem(this._newItems[i], keywordArgs, "POST");
818
		}
819
		for(var i = 0; i < this._deletedItems.length; i++){
820
			this._saveItem(this._deletedItems[i], keywordArgs, "DELETE");
821
		}
822
	},
823
 
824
	revert: function(){
825
		// summary:
826
		//	Invalidate changes (new and/or modified elements)
827
		// returns:
828
		//	True
829
		console.log("XmlStore.revert() _newItems=" + this._newItems.length);
830
		console.log("XmlStore.revert() _deletedItems=" + this._deletedItems.length);
831
		console.log("XmlStore.revert() _modifiedItems=" + this._modifiedItems.length);
832
		this._newItems = [];
833
		this._restoreItems(this._deletedItems);
834
		this._deletedItems = [];
835
		this._restoreItems(this._modifiedItems);
836
		this._modifiedItems = [];
837
		return true; //boolean
838
	},
839
 
840
	isDirty: function(/* item? */ item){
841
		//	summary:
842
		//		Check whether an item is new, modified or deleted
843
		//	description:
844
		//		If 'item' is specified, true is returned if the item is new,
845
		//		modified or deleted.
846
		//		Otherwise, true is returned if there are any new, modified
847
		//		or deleted items.
848
		//	item:
849
		//		An item (XML element) to check
850
		//	returns:
851
		//		True if an item or items are new, modified or deleted, otherwise
852
		//		false
853
		if (item) {
854
			var element = this._getRootElement(item.element);
855
			return (this._getItemIndex(this._newItems, element) >= 0 ||
856
				this._getItemIndex(this._deletedItems, element) >= 0 ||
857
				this._getItemIndex(this._modifiedItems, element) >= 0); //boolean
858
		}
859
		else {
860
			return (this._newItems.length > 0 ||
861
				this._deletedItems.length > 0 ||
862
				this._modifiedItems.length > 0); //boolean
863
		}
864
	},
865
 
866
	_saveItem: function(item, keywordArgs, method){
867
		if(method === "PUT"){
868
			url = this._getPutUrl(item);
869
		}else if(method === "DELETE"){
870
			url = this._getDeleteUrl(item);
871
		}else{ // POST
872
			url = this._getPostUrl(item);
873
		}
874
		if(!url){
875
			if(keywordArgs.onError){
876
				keywordArgs.onError.call(scope, new Error("No URL for saving content: " + postContent));
877
			}
878
			return;
879
		}
880
 
881
		var saveArgs = {
882
			url: url,
883
			method: (method || "POST"),
884
			contentType: "text/xml",
885
			handleAs: "xml"
886
		};
887
		var saveHander;
888
		if(method === "PUT"){
889
			saveArgs.putData = this._getPutContent(item);
890
			saveHandler = dojo.rawXhrPut(saveArgs);
891
		}else if(method === "DELETE"){
892
			saveHandler = dojo.xhrDelete(saveArgs);
893
		}else{ // POST
894
			saveArgs.postData = this._getPostContent(item);
895
			saveHandler = dojo.rawXhrPost(saveArgs);
896
		}
897
		var scope = (keywordArgs.scope || dojo.global);
898
		var self = this;
899
		saveHandler.addCallback(function(data){
900
			self._forgetItem(item);
901
			if(keywordArgs.onComplete){
902
				keywordArgs.onComplete.call(scope);
903
			}
904
		});
905
		saveHandler.addErrback(function(error){
906
			if(keywordArgs.onError){
907
				keywordArgs.onError.call(scope, error);
908
			}
909
		});
910
	},
911
 
912
	_getPostUrl: function(item){
913
		//	summary:
914
		//		Generate a URL for post
915
		//	description:
916
		//		This default implementation just returns '_url'.
917
		//		Sub-classes may override this method for the custom URL.
918
		//	item:
919
		//		An item to save
920
		//	returns:
921
		//		A post URL
922
		return this._url; //string
923
	},
924
 
925
	_getPutUrl: function(item){
926
		//	summary:
927
		//		Generate a URL for put
928
		//	description:
929
		//		This default implementation just returns '_url'.
930
		//		Sub-classes may override this method for the custom URL.
931
		//	item:
932
		//		An item to save
933
		//	returns:
934
		//		A put URL
935
		return this._url; //string
936
	},
937
 
938
	_getDeleteUrl: function(item){
939
		//	summary:
940
		//		Generate a URL for delete
941
		// 	description:
942
		//		This default implementation returns '_url' with '_keyAttribute'
943
		//		as a query string.
944
		//		Sub-classes may override this method for the custom URL based on
945
		//		changes (new, deleted, or modified).
946
		// 	item:
947
		//		An item to delete
948
		// 	returns:
949
		//		A delete URL
950
		if (!this._url) {
951
			return this._url; //string
952
		}
953
		var url = this._url;
954
		if (item && this._keyAttribute) {
955
			var value = this.getValue(item, this._keyAttribute);
956
			if (value) {
957
				url = url + '?' + this._keyAttribute + '=' + value;
958
			}
959
		}
960
		return url;	//string
961
	},
962
 
963
	_getPostContent: function(item){
964
		//	summary:
965
		//		Generate a content to post
966
		// 	description:
967
		//		This default implementation generates an XML document for one
968
		//		(the first only) new or modified element.
969
		//		Sub-classes may override this method for the custom post content
970
		//		generation.
971
		//	item:
972
		//		An item to save
973
		//	returns:
974
		//		A post content
975
		var element = item.element;
976
		var declaration = "<?xml version=\"1.0\"?>"; // FIXME: encoding?
977
		return declaration + dojox.data.dom.innerXML(element); //XML string
978
	},
979
 
980
	_getPutContent: function(item){
981
		//	summary:
982
		//		Generate a content to put
983
		// 	description:
984
		//		This default implementation generates an XML document for one
985
		//		(the first only) new or modified element.
986
		//		Sub-classes may override this method for the custom put content
987
		//		generation.
988
		//	item:
989
		//		An item to save
990
		//	returns:
991
		//		A post content
992
		var element = item.element;
993
		var declaration = "<?xml version=\"1.0\"?>"; // FIXME: encoding?
994
		return declaration + dojox.data.dom.innerXML(element); //XML string
995
	},
996
 
997
/* internal API */
998
 
999
	_getAttribute: function(tagName, attribute){
1000
		if(this._attributeMap){
1001
			var key = tagName + "." + attribute;
1002
			var value = this._attributeMap[key];
1003
			if(value){
1004
				attribute = value;
1005
			}else{ // look for global attribute
1006
				value = this._attributeMap[attribute];
1007
				if(value){
1008
					attribute = value;
1009
				}
1010
			}
1011
		}
1012
		return attribute; //object
1013
	},
1014
 
1015
	_getItem: function(element){
1016
		return new dojox.data.XmlItem(element, this); //object
1017
	},
1018
 
1019
	_getItemIndex: function(items, element){
1020
		for(var i = 0; i < items.length; i++){
1021
			if(items[i].element === element){
1022
				return i; //int
1023
			}
1024
		}
1025
		return -1; //int
1026
	},
1027
 
1028
	_backupItem: function(item){
1029
		var element = this._getRootElement(item.element);
1030
		if(	this._getItemIndex(this._newItems, element) >= 0 ||
1031
			this._getItemIndex(this._modifiedItems, element) >= 0){
1032
			return; // new or already modified
1033
		}
1034
		if(element != item.element){
1035
			item = this._getItem(element);
1036
		}
1037
		item._backup = element.cloneNode(true);
1038
		this._modifiedItems.push(item);
1039
	},
1040
 
1041
	_restoreItems: function(items){
1042
 
1043
		dojo.forEach(items,function(item){
1044
			if(item._backup){
1045
				item.element = item._backup;
1046
				item._backup = null;
1047
			}
1048
		},this);
1049
	},
1050
 
1051
	_forgetItem: function(item){
1052
		var element = item.element;
1053
		var index = this._getItemIndex(this._newItems, element);
1054
		if(index >= 0){
1055
			this._newItems.splice(index, 1);
1056
		}
1057
		index = this._getItemIndex(this._deletedItems, element);
1058
		if(index >= 0){
1059
			this._deletedItems.splice(index, 1);
1060
		}
1061
		index = this._getItemIndex(this._modifiedItems, element);
1062
		if(index >= 0){
1063
			this._modifiedItems.splice(index, 1);
1064
		}
1065
	},
1066
 
1067
	_getDocument: function(element){
1068
		if(element){
1069
			return element.ownerDocument;  //DOMDocument
1070
		}else if(!this._document){
1071
			return dojox.data.dom.createDocument(); // DOMDocument
1072
		}
1073
	},
1074
 
1075
	_getRootElement: function(element){
1076
		while(element.parentNode){
1077
			element = element.parentNode;
1078
		}
1079
		return element; //DOMElement
1080
	}
1081
 
1082
});
1083
 
1084
//FIXME: Is a full class here really needed for containment of the item or would
1085
//an anon object work fine?
1086
dojo.declare("dojox.data.XmlItem", null, {
1087
	constructor: function(element, store) {
1088
		//	summary:
1089
		//		Initialize with an XML element
1090
		//	element:
1091
		//		An XML element
1092
		//	store:
1093
		//		The containing store, if any.
1094
		this.element = element;
1095
		this.store = store;
1096
	},
1097
	//	summary:
1098
	//		A data item of 'XmlStore'
1099
	//	description:
1100
	//		This class represents an item of 'XmlStore' holding an XML element.
1101
	//		'element'
1102
	//	element:
1103
	//		An XML element
1104
 
1105
	toString: function() {
1106
		//	summary:
1107
		//		Return a value of the first text child of the element
1108
		// 	returns:
1109
		//		a value of the first text child of the element
1110
		var str = "";
1111
		if (this.element) {
1112
			for (var i = 0; i < this.element.childNodes.length; i++) {
1113
				var node = this.element.childNodes[i];
1114
				if (node.nodeType === 3) {
1115
					str = node.nodeValue;
1116
					break;
1117
				}
1118
			}
1119
		}
1120
		return str;	//String
1121
	}
1122
 
1123
});
1124
dojo.extend(dojox.data.XmlStore,dojo.data.util.simpleFetch);
1125
 
1126
}