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");
6 dojo.require("dojo.data.util.simpleFetch");
7 dojo.require("dojo.data.util.filter");
8 dojo.require("dojox.data.dom");
10 dojo.declare("dojox.data.XmlStore", null, {
12 // A data store for XML based services or documents
14 // A data store for XML based services or documents
16 constructor: function(/* object */ args) {
18 // Constructor for the XML store.
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()");
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);
37 this._deletedItems = [];
38 this._modifiedItems = [];
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.
54 /* dojo.data.api.Read */
56 getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){
58 // Return an attribute value
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
63 // If 'attribute' specifies "childNodes", the first element child is
65 // If 'attribute' specifies "text()", the value of the first text
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.
75 // An XML element that holds the attribute
77 // A tag name of a child element, An XML attribute name or one of
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
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
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
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
117 return defaultValue; //object
122 getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
124 // Return an array of attribute values
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
129 // If 'attribute' specifies "childNodes", child elements are returned.
130 // If 'attribute' specifies "text()", the values of child text nodes
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.
140 // An XML element that holds the attribute
142 // A tag name of child elements, An XML attribute name or one of
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"){
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));
157 return values; //array
158 }else if(attribute === "text()"){
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);
166 return values; //array
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
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));
182 return values; //array
187 getAttributes: function(/* item */ item) {
189 // Return an array of attribute names
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.
201 // An array of attributes found
202 var element = item.element;
204 attributes.push("tagName");
205 if(element.childNodes.length > 0){
207 var childNodes = true;
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;
214 attributes.push(name);
218 }else if(node.nodeType === 3){
223 attributes.push("childNodes");
226 attributes.push("text()");
229 for(var i = 0; i < element.attributes.length; i++){
230 attributes.push("@" + element.attributes[i].nodeName);
232 if(this._attributeMap){
233 for (var key in this._attributeMap){
234 var i = key.indexOf('.');
236 var tagName = key.substring(0, i);
237 if (tagName === element.nodeName){
238 attributes.push(key.substring(i + 1));
240 }else{ // global attribute
241 attributes.push(key);
245 return attributes; //array
248 hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
250 // Check whether an element has the attribute
252 // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
254 // A tag name of a child element, An XML attribute name or one of
257 // True if the element has the attribute, otherwise false
258 return (this.getValue(item, attribute) !== undefined); //boolean
261 containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){
263 // Check whether the attribute values contain the value
265 // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
267 // A tag name of a child element, An XML attribute name or one of
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){
277 }else if (values[i] === value){
278 return true; //boolean
281 return false;//boolean
284 isItem: function(/* anything */ something){
286 // Check whether the object is an item (XML element)
288 // An object to check
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
294 return false; //boolran
297 isItemLoaded: function(/* anything */ something){
299 // Check whether the object is an item (XML element) and loaded
301 // An object to check
303 // True if the object is an XML element, otherwise false
304 return this.isItem(something); //boolean
307 loadItem: function(/* object */ keywordArgs){
309 // Load an item (XML element)
311 // object containing the args for loadItem. See dojo.data.api.Read.loadItem()
314 getFeatures: function() {
316 // Return supported data APIs
318 // "dojo.data.api.Read" and "dojo.data.api.Write"
320 "dojo.data.api.Read": true,
321 "dojo.data.api.Write": true
323 return features; //array
326 getLabel: function(/* item */ item){
328 // See dojo.data.api.Read.getLabel()
329 if((this.label !== "") && this.isItem(item)){
330 var label = this.getValue(item,this.label);
332 return label.toString();
335 return undefined; //undefined
338 getLabelAttributes: function(/* item */ item){
340 // See dojo.data.api.Read.getLabelAttributes()
341 if(this.label !== ""){
342 return [this.label]; //array
347 _fetchItems: function(request, fetchHandler, errorHandler) {
349 // Fetch items (XML elements) that match to a query
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
358 // If 'rootItem' is specified, it is used to fetch items.
362 // A function to call for fetched items
364 // A function to call on error
365 var url = this._getFetchUrl(request);
366 console.log("XmlStore._fetchItems(): url=" + url);
368 errorHandler(new Error("No URL specified."));
371 var localRequest = (!this.sendQuery ? request : null); // use request for _getItems()
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);
387 fetchHandler([], request);
390 getHandler.addErrback(function(data){
391 errorHandler(data, request);
395 _getFetchUrl: function(request){
397 // Generate a URL for fetch
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'
403 // Otherwise, 'url' is returned as is.
404 // Sub-classes may override this method for the custom URL generation.
412 var query = request.query;
416 if(dojo.isString(query)){
417 return this.url + query;
419 var queryString = "";
420 for(var name in query){
421 var value = query[name];
426 queryString += (name + "=" + value);
432 //Check to see if the URL already has query params or not.
433 var fullUrl = this.url;
434 if(fullUrl.indexOf("?") < 0){
439 return fullUrl + queryString;
442 _getItems: function(document, request) {
444 // Fetch items (XML elements) in an XML document based on a request
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
460 query = request.query;
465 console.log("Looking up root item: " + this.rootItem);
466 if(this.rootItem !== ""){
468 nodes = document.getElementsByTagName(this.rootItem);
471 nodes = document.documentElement.childNodes;
473 for(var i = 0; i < nodes.length; i++){
475 if(node.nodeType != 1 /*ELEMENT_NODE*/){
478 var item = this._getItem(node);
481 var ignoreCase = request.queryOptions ? request.queryOptions.ignoreCase : false;
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.
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);
493 for(var attribute in query){
494 var value = this.getValue(item, attribute);
496 var queryValue = query[attribute];
497 if ((typeof value) === "string" &&
498 (regexpList[attribute])){
499 if((value.match(regexpList[attribute])) !== null){
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){
510 if(queryValue === "*" || queryValue === value){
525 dojo.forEach(items,function(item){
526 item.element.parentNode.removeChild(item.element); // make it root
531 close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
533 // See dojo.data.api.Read.close()
536 /* dojo.data.api.Write */
538 newItem: function(/* object? */ keywordArgs){
540 // Return a new dojox.data.XmlItem
542 // At least, 'keywordArgs' must contain "tagName" to be used for
544 // Other attributes in 'keywordArgs' are set to the new element,
545 // including "text()", but excluding "childNodes".
547 // An object containing initial attributes
550 console.log("XmlStore.newItem()");
551 keywordArgs = (keywordArgs || {});
552 var tagName = keywordArgs.tagName;
554 tagName = this.rootItem;
560 var document = this._getDocument();
561 var element = document.createElement(tagName);
562 for(var attribute in keywordArgs){
563 if(attribute === "tagName"){
565 }else if(attribute === "text()"){
566 var text = document.createTextNode(keywordArgs[attribute]);
567 element.appendChild(text);
569 attribute = this._getAttribute(tagName, attribute);
570 if(attribute.charAt(0) === '@'){
571 var name = attribute.substring(1);
572 element.setAttribute(name, keywordArgs[attribute]);
574 var child = document.createElement(attribute);
575 var text = document.createTextNode(keywordArgs[attribute]);
576 child.appendChild(text);
577 element.appendChild(child);
582 var item = this._getItem(element);
583 this._newItems.push(item);
584 return item; //object
587 deleteItem: function(/* item */ item){
589 // Delete an dojox.data.XmlItem (wrapper to a XML element).
591 // An XML element to delete
594 console.log("XmlStore.deleteItem()");
595 var element = item.element;
596 if(element.parentNode){
597 this._backupItem(item);
598 element.parentNode.removeChild(element);
601 this._forgetItem(item);
602 this._deletedItems.push(item);
603 return true; //boolean
606 setValue: function(/* item */ item, /* attribute || string */ attribute, /* almost anything */ value){
608 // Set an attribute value
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
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
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.
626 // An XML element that holds the attribute
628 // A tag name of a child element, An XML attribute name or one of
631 // A attribute value to set
633 // False for "tagName", otherwise true
634 if(attribute === "tagName"){
635 return false; //boolean
638 this._backupItem(item);
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);
648 var text = this._getDocument(element).createTextNode(value);
649 element.appendChild(text);
651 attribute = this._getAttribute(element.nodeName, attribute);
652 if(attribute.charAt(0) === '@'){
653 var name = attribute.substring(1);
654 element.setAttribute(name, value);
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){
665 var document = this._getDocument(element);
667 while(child.firstChild){
668 child.removeChild(child.firstChild);
671 child = document.createElement(attribute);
672 element.appendChild(child);
674 var text = document.createTextNode(value);
675 child.appendChild(text);
678 return true; //boolean
681 setValues: function(/* item */ item, /* attribute || string */ attribute, /* array */ values){
683 // Set attribute values
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
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.
701 // An XML element that holds the attribute
703 // A tag name of child elements, an XML attribute name or one of
706 // A attribute value to set
708 // False for "tagName", otherwise true
709 if(attribute === "tagName"){
710 return false; //boolean
713 this._backupItem(item);
715 var element = item.element;
716 if(attribute === "childNodes"){
717 while(element.firstChild){
718 element.removeChild(element.firstChild);
720 for(var i = 0; i < values.length; i++){
721 var child = values[i].element;
722 element.appendChild(child);
724 }else if(attribute === "text()"){
725 while (element.firstChild){
726 element.removeChild(element.firstChild);
729 for(var i = 0; i < values.length; i++){
732 var text = this._getDocument(element).createTextNode(value);
733 element.appendChild(text);
735 attribute = this._getAttribute(element.nodeName, attribute);
736 if(attribute.charAt(0) === '@'){
737 var name = attribute.substring(1);
738 element.setAttribute(name, values[0]);
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);
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);
756 return true; //boolean
759 unsetAttribute: function(/* item */ item, /* attribute || string */ attribute){
761 // Remove an attribute
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
768 // If 'attribute' specifies "childNodes" or "text()", all child nodes
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.
777 // An XML element that holds the attribute
779 // A tag name of child elements, an XML attribute name or one of
782 // False for "tagName", otherwise true
783 if(attribute === "tagName"){
784 return false; //boolean
787 this._backupItem(item);
789 var element = item.element;
790 if(attribute === "childNodes" || attribute === "text()"){
791 while(element.firstChild){
792 element.removeChild(element.firstChild);
795 attribute = this._getAttribute(element.nodeName, attribute);
796 if(attribute.charAt(0) === '@'){
797 var name = attribute.substring(1);
798 element.removeAttribute(name);
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);
809 return true; //boolean
812 save: function(/* object */ keywordArgs){
814 // Save new and/or modified items (XML elements)
816 // 'url' is used to save XML documents for new, modified and/or
817 // deleted XML elements.
819 // An object for callbacks
823 for(var i = 0; i < this._modifiedItems.length; i++){
824 this._saveItem(this._modifiedItems[i], keywordArgs, "PUT");
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);
833 this._saveItem(this._newItems[i], keywordArgs, "POST");
835 for(var i = 0; i < this._deletedItems.length; i++){
836 this._saveItem(this._deletedItems[i], keywordArgs, "DELETE");
842 // Invalidate changes (new and/or modified elements)
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);
849 this._restoreItems(this._deletedItems);
850 this._deletedItems = [];
851 this._restoreItems(this._modifiedItems);
852 this._modifiedItems = [];
853 return true; //boolean
856 isDirty: function(/* item? */ item){
858 // Check whether an item is new, modified or deleted
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
865 // An item (XML element) to check
867 // True if an item or items are new, modified or deleted, otherwise
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
876 return (this._newItems.length > 0 ||
877 this._deletedItems.length > 0 ||
878 this._modifiedItems.length > 0); //boolean
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);
888 url = this._getPostUrl(item);
891 if(keywordArgs.onError){
892 keywordArgs.onError.call(scope, new Error("No URL for saving content: " + postContent));
899 method: (method || "POST"),
900 contentType: "text/xml",
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);
910 saveArgs.postData = this._getPostContent(item);
911 saveHandler = dojo.rawXhrPost(saveArgs);
913 var scope = (keywordArgs.scope || dojo.global);
915 saveHandler.addCallback(function(data){
916 self._forgetItem(item);
917 if(keywordArgs.onComplete){
918 keywordArgs.onComplete.call(scope);
921 saveHandler.addErrback(function(error){
922 if(keywordArgs.onError){
923 keywordArgs.onError.call(scope, error);
928 _getPostUrl: function(item){
930 // Generate a URL for post
932 // This default implementation just returns 'url'.
933 // Sub-classes may override this method for the custom URL.
938 return this.url; //string
941 _getPutUrl: function(item){
943 // Generate a URL for put
945 // This default implementation just returns 'url'.
946 // Sub-classes may override this method for the custom URL.
951 return this.url; //string
954 _getDeleteUrl: function(item){
956 // Generate a URL for delete
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).
967 if (item && this.keyAttribute !== "") {
968 var value = this.getValue(item, this.keyAttribute);
970 var key = this.keyAttribute.charAt(0) ==='@' ? this.keyAttribute.substring(1): this.keyAttribute;
971 url += url.indexOf('?') < 0 ? '?' : '&';
972 url += key + '=' + value;
978 _getPostContent: function(item){
980 // Generate a content to post
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
990 var element = item.element;
991 var declaration = "<?xml version=\"1.0\"?>"; // FIXME: encoding?
992 return declaration + dojox.data.dom.innerXML(element); //XML string
995 _getPutContent: function(item){
997 // Generate a content to put
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
1007 var element = item.element;
1008 var declaration = "<?xml version=\"1.0\"?>"; // FIXME: encoding?
1009 return declaration + dojox.data.dom.innerXML(element); //XML string
1014 _getAttribute: function(tagName, attribute){
1015 if(this._attributeMap){
1016 var key = tagName + "." + attribute;
1017 var value = this._attributeMap[key];
1020 }else{ // look for global attribute
1021 value = this._attributeMap[attribute];
1027 return attribute; //object
1030 _getItem: function(element){
1031 return new dojox.data.XmlItem(element, this); //object
1034 _getItemIndex: function(items, element){
1035 for(var i = 0; i < items.length; i++){
1036 if(items[i].element === element){
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
1049 if(element != item.element){
1050 item = this._getItem(element);
1052 item._backup = element.cloneNode(true);
1053 this._modifiedItems.push(item);
1056 _restoreItems: function(items){
1058 dojo.forEach(items,function(item){
1060 item.element = item._backup;
1061 item._backup = null;
1066 _forgetItem: function(item){
1067 var element = item.element;
1068 var index = this._getItemIndex(this._newItems, element);
1070 this._newItems.splice(index, 1);
1072 index = this._getItemIndex(this._deletedItems, element);
1074 this._deletedItems.splice(index, 1);
1076 index = this._getItemIndex(this._modifiedItems, element);
1078 this._modifiedItems.splice(index, 1);
1082 _getDocument: function(element){
1084 return element.ownerDocument; //DOMDocument
1085 }else if(!this._document){
1086 return dojox.data.dom.createDocument(); // DOMDocument
1090 _getRootElement: function(element){
1091 while(element.parentNode){
1092 element = element.parentNode;
1094 return element; //DOMElement
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) {
1104 // Initialize with an XML element
1108 // The containing store, if any.
1109 this.element = element;
1113 // A data item of 'XmlStore'
1115 // This class represents an item of 'XmlStore' holding an XML element.
1120 toString: function() {
1122 // Return a value of the first text child of the element
1124 // a value of the first text child of the 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;
1135 return str; //String
1139 dojo.extend(dojox.data.XmlStore,dojo.data.util.simpleFetch);