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