diff options
Diffstat (limited to 'includes/js/dojox/data/XmlStore.js')
-rw-r--r-- | includes/js/dojox/data/XmlStore.js | 1141 |
1 files changed, 1141 insertions, 0 deletions
diff --git a/includes/js/dojox/data/XmlStore.js b/includes/js/dojox/data/XmlStore.js new file mode 100644 index 0000000..90b26a9 --- /dev/null +++ b/includes/js/dojox/data/XmlStore.js @@ -0,0 +1,1141 @@ +if(!dojo._hasResource["dojox.data.XmlStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.data.XmlStore"] = true; +dojo.provide("dojox.data.XmlStore"); +dojo.provide("dojox.data.XmlItem"); + +dojo.require("dojo.data.util.simpleFetch"); +dojo.require("dojo.data.util.filter"); +dojo.require("dojox.data.dom"); + +dojo.declare("dojox.data.XmlStore", null, { + // summary: + // A data store for XML based services or documents + // description: + // A data store for XML based services or documents + + constructor: function(/* object */ args) { + // summary: + // Constructor for the XML store. + // args: + // An anonymous object to initialize properties. It expects the following values: + // url: The url to a service or an XML document that represents the store + // rootItem: A tag name for root items + // keyAttribute: An attribute name for a key or an indentify + // attributeMap: An anonymous object contains properties for attribute mapping, + // {"tag_name.item_attribute_name": "@xml_attribute_name", ...} + // sendQuery: A boolean indicate to add a query string to the service URL + console.log("XmlStore()"); + if(args){ + this.url = args.url; + this.rootItem = (args.rootItem || args.rootitem || this.rootItem); + this.keyAttribute = (args.keyAttribute || args.keyattribute || this.keyAttribute); + this._attributeMap = (args.attributeMap || args.attributemap); + this.label = args.label || this.label; + this.sendQuery = (args.sendQuery || args.sendquery || this.sendQuery); + } + this._newItems = []; + this._deletedItems = []; + this._modifiedItems = []; + }, + + //Values that may be set by the parser. + //Ergo, have to be instantiated to something + //So the parser knows how to set them. + url: "", + + rootItem: "", + + keyAttribute: "", + + label: "", + + sendQuery: false, + +/* dojo.data.api.Read */ + + getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){ + // summary: + // Return an attribute value + // description: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // If 'attribute' specifies "tagName", the tag name of the element is + // returned. + // If 'attribute' specifies "childNodes", the first element child is + // returned. + // If 'attribute' specifies "text()", the value of the first text + // child is returned. + // For generic attributes, if '_attributeMap' is specified, + // an actual attribute name is looked up with the tag name of + // the element and 'attribute' (concatenated with '.'). + // Then, if 'attribute' starts with "@", the value of the XML + // attribute is returned. + // Otherwise, the first child element of the tag name specified with + // 'attribute' is returned. + // item: + // An XML element that holds the attribute + // attribute: + // A tag name of a child element, An XML attribute name or one of + // special names + // defaultValue: + // A default value + // returns: + // An attribute value found, otherwise 'defaultValue' + var element = item.element; + if(attribute === "tagName"){ + return element.nodeName; + }else if (attribute === "childNodes"){ + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType === 1 /*ELEMENT_NODE*/) { + return this._getItem(node); //object + } + } + return defaultValue; + }else if(attribute === "text()"){ + for(var i = 0; i < element.childNodes.length; i++){ + var node = element.childNodes[i]; + if(node.nodeType === 3 /*TEXT_NODE*/ || + node.nodeType === 4 /*CDATA_SECTION_NODE*/){ + return node.nodeValue; //string + } + } + return defaultValue; + }else{ + attribute = this._getAttribute(element.nodeName, attribute); + if(attribute.charAt(0) === '@'){ + var name = attribute.substring(1); + var value = element.getAttribute(name); + return (value !== undefined) ? value : defaultValue; //object + }else{ + for(var i = 0; i < element.childNodes.length; i++){ + var node = element.childNodes[i]; + if( node.nodeType === 1 /*ELEMENT_NODE*/ && + node.nodeName === attribute){ + return this._getItem(node); //object + } + } + return defaultValue; //object + } + } + }, + + getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){ + // summary: + // Return an array of attribute values + // description: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // If 'attribute' specifies "tagName", the tag name of the element is + // returned. + // If 'attribute' specifies "childNodes", child elements are returned. + // If 'attribute' specifies "text()", the values of child text nodes + // are returned. + // For generic attributes, if '_attributeMap' is specified, + // an actual attribute name is looked up with the tag name of + // the element and 'attribute' (concatenated with '.'). + // Then, if 'attribute' starts with "@", the value of the XML + // attribute is returned. + // Otherwise, child elements of the tag name specified with + // 'attribute' are returned. + // item: + // An XML element that holds the attribute + // attribute: + // A tag name of child elements, An XML attribute name or one of + // special names + // returns: + // An array of attribute values found, otherwise an empty array + var element = item.element; + if(attribute === "tagName"){ + return [element.nodeName]; + }else if(attribute === "childNodes"){ + var values = []; + for(var i = 0; i < element.childNodes.length; i++){ + var node = element.childNodes[i]; + if(node.nodeType === 1 /*ELEMENT_NODE*/){ + values.push(this._getItem(node)); + } + } + return values; //array + }else if(attribute === "text()"){ + var values = []; + for(var i = 0; i < element.childNodes.length; i++){ + var node = childNodes[i]; + if(node.nodeType === 3){ + values.push(node.nodeValue); + } + } + return values; //array + }else{ + attribute = this._getAttribute(element.nodeName, attribute); + if(attribute.charAt(0) === '@'){ + var name = attribute.substring(1); + var value = element.getAttribute(name); + return (value !== undefined) ? [value] : []; //array + }else{ + var values = []; + for(var i = 0; i < element.childNodes.length; i++){ + var node = element.childNodes[i]; + if( node.nodeType === 1 /*ELEMENT_NODE*/ && + node.nodeName === attribute){ + values.push(this._getItem(node)); + } + } + return values; //array + } + } + }, + + getAttributes: function(/* item */ item) { + // summary: + // Return an array of attribute names + // description: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // tag names of child elements and XML attribute names of attributes + // specified to the element are returned along with special attribute + // names applicable to the element including "tagName", "childNodes" + // if the element has child elements, "text()" if the element has + // child text nodes, and attribute names in '_attributeMap' that match + // the tag name of the element. + // item: + // An XML element + // returns: + // An array of attributes found + var element = item.element; + var attributes = []; + attributes.push("tagName"); + if(element.childNodes.length > 0){ + var names = {}; + var childNodes = true; + var text = false; + for(var i = 0; i < element.childNodes.length; i++){ + var node = element.childNodes[i]; + if (node.nodeType === 1 /*ELEMENT_NODE*/) { + var name = node.nodeName; + if(!names[name]){ + attributes.push(name); + names[name] = name; + } + childNodes = true; + }else if(node.nodeType === 3){ + text = true; + } + } + if(childNodes){ + attributes.push("childNodes"); + } + if(text){ + attributes.push("text()"); + } + } + for(var i = 0; i < element.attributes.length; i++){ + attributes.push("@" + element.attributes[i].nodeName); + } + if(this._attributeMap){ + for (var key in this._attributeMap){ + var i = key.indexOf('.'); + if(i > 0){ + var tagName = key.substring(0, i); + if (tagName === element.nodeName){ + attributes.push(key.substring(i + 1)); + } + }else{ // global attribute + attributes.push(key); + } + } + } + return attributes; //array + }, + + hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){ + // summary: + // Check whether an element has the attribute + // item: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // attribute: + // A tag name of a child element, An XML attribute name or one of + // special names + // returns: + // True if the element has the attribute, otherwise false + return (this.getValue(item, attribute) !== undefined); //boolean + }, + + containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){ + // summary: + // Check whether the attribute values contain the value + // item: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // attribute: + // A tag name of a child element, An XML attribute name or one of + // special names + // returns: + // True if the attribute values contain the value, otherwise false + var values = this.getValues(item, attribute); + for(var i = 0; i < values.length; i++){ + if((typeof value === "string")){ + if(values[i].toString && values[i].toString() === value){ + return true; + } + }else if (values[i] === value){ + return true; //boolean + } + } + return false;//boolean + }, + + isItem: function(/* anything */ something){ + // summary: + // Check whether the object is an item (XML element) + // item: + // An object to check + // returns: + // True if the object is an XML element, otherwise false + if(something && something.element && something.store && something.store === this){ + return true; //boolean + } + return false; //boolran + }, + + isItemLoaded: function(/* anything */ something){ + // summary: + // Check whether the object is an item (XML element) and loaded + // item: + // An object to check + // returns: + // True if the object is an XML element, otherwise false + return this.isItem(something); //boolean + }, + + loadItem: function(/* object */ keywordArgs){ + // summary: + // Load an item (XML element) + // keywordArgs: + // object containing the args for loadItem. See dojo.data.api.Read.loadItem() + }, + + getFeatures: function() { + // summary: + // Return supported data APIs + // returns: + // "dojo.data.api.Read" and "dojo.data.api.Write" + var features = { + "dojo.data.api.Read": true, + "dojo.data.api.Write": true + }; + return features; //array + }, + + getLabel: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabel() + if((this.label !== "") && this.isItem(item)){ + var label = this.getValue(item,this.label); + if(label){ + return label.toString(); + } + } + return undefined; //undefined + }, + + getLabelAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabelAttributes() + if(this.label !== ""){ + return [this.label]; //array + } + return null; //null + }, + + _fetchItems: function(request, fetchHandler, errorHandler) { + // summary: + // Fetch items (XML elements) that match to a query + // description: + // If 'sendQuery' is true, an XML document is loaded from + // 'url' with a query string. + // Otherwise, an XML document is loaded and list XML elements that + // match to a query (set of element names and their text attribute + // values that the items to contain). + // A wildcard, "*" can be used to query values to match all + // occurrences. + // If 'rootItem' is specified, it is used to fetch items. + // request: + // A request object + // fetchHandler: + // A function to call for fetched items + // errorHandler: + // A function to call on error + var url = this._getFetchUrl(request); + console.log("XmlStore._fetchItems(): url=" + url); + if(!url){ + errorHandler(new Error("No URL specified.")); + return; + } + var localRequest = (!this.sendQuery ? request : null); // use request for _getItems() + + var self = this; + var getArgs = { + url: url, + handleAs: "xml", + preventCache: true + }; + var getHandler = dojo.xhrGet(getArgs); + getHandler.addCallback(function(data){ + var items = self._getItems(data, localRequest); + console.log("XmlStore._fetchItems(): length=" + (items ? items.length : 0)); + if (items && items.length > 0) { + fetchHandler(items, request); + } + else { + fetchHandler([], request); + } + }); + getHandler.addErrback(function(data){ + errorHandler(data, request); + }); + }, + + _getFetchUrl: function(request){ + // summary: + // Generate a URL for fetch + // description: + // This default implementation generates a query string in the form of + // "?name1=value1&name2=value2..." off properties of 'query' object + // specified in 'request' and appends it to 'url', if 'sendQuery' + // is set to false. + // Otherwise, 'url' is returned as is. + // Sub-classes may override this method for the custom URL generation. + // request: + // A request object + // returns: + // A fetch URL + if(!this.sendQuery){ + return this.url; + } + var query = request.query; + if(!query){ + return this.url; + } + if(dojo.isString(query)){ + return this.url + query; + } + var queryString = ""; + for(var name in query){ + var value = query[name]; + if(value){ + if(queryString){ + queryString += "&"; + } + queryString += (name + "=" + value); + } + } + if(!queryString){ + return this.url; + } + //Check to see if the URL already has query params or not. + var fullUrl = this.url; + if(fullUrl.indexOf("?") < 0){ + fullUrl += "?"; + }else{ + fullUrl += "&"; + } + return fullUrl + queryString; + }, + + _getItems: function(document, request) { + // summary: + // Fetch items (XML elements) in an XML document based on a request + // description: + // This default implementation walks through child elements of + // the document element to see if all properties of 'query' object + // match corresponding attributes of the element (item). + // If 'request' is not specified, all child elements are returned. + // Sub-classes may override this method for the custom search in + // an XML document. + // document: + // An XML document + // request: + // A request object + // returns: + // An array of items + var query = null; + if(request){ + query = request.query; + } + var items = []; + var nodes = null; + + console.log("Looking up root item: " + this.rootItem); + if(this.rootItem !== ""){ + + nodes = document.getElementsByTagName(this.rootItem); + } + else{ + nodes = document.documentElement.childNodes; + } + for(var i = 0; i < nodes.length; i++){ + var node = nodes[i]; + if(node.nodeType != 1 /*ELEMENT_NODE*/){ + continue; + } + var item = this._getItem(node); + if(query){ + var found = true; + var ignoreCase = request.queryOptions ? request.queryOptions.ignoreCase : false; + + //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the + //same value for each item examined. Much more efficient. + var regexpList = {}; + for(var key in query){ + var value = query[key]; + if(typeof value === "string"){ + regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase); + } + } + + for(var attribute in query){ + var value = this.getValue(item, attribute); + if(value){ + var queryValue = query[attribute]; + if ((typeof value) === "string" && + (regexpList[attribute])){ + if((value.match(regexpList[attribute])) !== null){ + continue; + } + }else if((typeof value) === "object"){ + if( value.toString && + (regexpList[attribute])){ + var stringValue = value.toString(); + if((stringValue.match(regexpList[attribute])) !== null){ + continue; + } + }else{ + if(queryValue === "*" || queryValue === value){ + continue; + } + } + } + } + found = false; + break; + } + if(!found){ + continue; + } + } + items.push(item); + } + dojo.forEach(items,function(item){ + item.element.parentNode.removeChild(item.element); // make it root + },this); + return items; + }, + + close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ + // summary: + // See dojo.data.api.Read.close() + }, + +/* dojo.data.api.Write */ + + newItem: function(/* object? */ keywordArgs){ + // summary: + // Return a new dojox.data.XmlItem + // description: + // At least, 'keywordArgs' must contain "tagName" to be used for + // the new element. + // Other attributes in 'keywordArgs' are set to the new element, + // including "text()", but excluding "childNodes". + // keywordArgs: + // An object containing initial attributes + // returns: + // An XML element + console.log("XmlStore.newItem()"); + keywordArgs = (keywordArgs || {}); + var tagName = keywordArgs.tagName; + if(!tagName){ + tagName = this.rootItem; + if(tagName === ""){ + return null; + } + } + + var document = this._getDocument(); + var element = document.createElement(tagName); + for(var attribute in keywordArgs){ + if(attribute === "tagName"){ + continue; + }else if(attribute === "text()"){ + var text = document.createTextNode(keywordArgs[attribute]); + element.appendChild(text); + }else{ + attribute = this._getAttribute(tagName, attribute); + if(attribute.charAt(0) === '@'){ + var name = attribute.substring(1); + element.setAttribute(name, keywordArgs[attribute]); + }else{ + var child = document.createElement(attribute); + var text = document.createTextNode(keywordArgs[attribute]); + child.appendChild(text); + element.appendChild(child); + } + } + } + + var item = this._getItem(element); + this._newItems.push(item); + return item; //object + }, + + deleteItem: function(/* item */ item){ + // summary: + // Delete an dojox.data.XmlItem (wrapper to a XML element). + // item: + // An XML element to delete + // returns: + // True + console.log("XmlStore.deleteItem()"); + var element = item.element; + if(element.parentNode){ + this._backupItem(item); + element.parentNode.removeChild(element); + return true; + } + this._forgetItem(item); + this._deletedItems.push(item); + return true; //boolean + }, + + setValue: function(/* item */ item, /* attribute || string */ attribute, /* almost anything */ value){ + // summary: + // Set an attribute value + // description: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // If 'attribute' specifies "tagName", nothing is set and false is + // returned. + // If 'attribute' specifies "childNodes", the value (XML element) is + // added to the element. + // If 'attribute' specifies "text()", a text node is created with + // the value and set it to the element as a child. + // For generic attributes, if '_attributeMap' is specified, + // an actual attribute name is looked up with the tag name of + // the element and 'attribute' (concatenated with '.'). + // Then, if 'attribute' starts with "@", the value is set to the XML + // attribute. + // Otherwise, a text node is created with the value and set it to + // the first child element of the tag name specified with 'attribute'. + // If the child element does not exist, it is created. + // item: + // An XML element that holds the attribute + // attribute: + // A tag name of a child element, An XML attribute name or one of + // special names + // value: + // A attribute value to set + // returns: + // False for "tagName", otherwise true + if(attribute === "tagName"){ + return false; //boolean + } + + this._backupItem(item); + + var element = item.element; + if(attribute === "childNodes"){ + var child = value.element; + element.appendChild(child); + }else if(attribute === "text()"){ + while (element.firstChild){ + element.removeChild(element.firstChild); + } + var text = this._getDocument(element).createTextNode(value); + element.appendChild(text); + }else{ + attribute = this._getAttribute(element.nodeName, attribute); + if(attribute.charAt(0) === '@'){ + var name = attribute.substring(1); + element.setAttribute(name, value); + }else{ + var child = null; + for(var i = 0; i < element.childNodes.length; i++){ + var node = element.childNodes[i]; + if( node.nodeType === 1 /*ELEMENT_NODE*/&& + node.nodeName === attribute){ + child = node; + break; + } + } + var document = this._getDocument(element); + if(child){ + while(child.firstChild){ + child.removeChild(child.firstChild); + } + }else{ + child = document.createElement(attribute); + element.appendChild(child); + } + var text = document.createTextNode(value); + child.appendChild(text); + } + } + return true; //boolean + }, + + setValues: function(/* item */ item, /* attribute || string */ attribute, /* array */ values){ + // summary: + // Set attribute values + // description: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // If 'attribute' specifies "tagName", nothing is set and false is + // returned. + // If 'attribute' specifies "childNodes", the value (array of XML + // elements) is set to the element's childNodes. + // If 'attribute' specifies "text()", a text node is created with + // the values and set it to the element as a child. + // For generic attributes, if '_attributeMap' is specified, + // an actual attribute name is looked up with the tag name of + // the element and 'attribute' (concatenated with '.'). + // Then, if 'attribute' starts with "@", the first value is set to + // the XML attribute. + // Otherwise, child elements of the tag name specified with + // 'attribute' are replaced with new child elements and their + // child text nodes of values. + // item: + // An XML element that holds the attribute + // attribute: + // A tag name of child elements, an XML attribute name or one of + // special names + // value: + // A attribute value to set + // returns: + // False for "tagName", otherwise true + if(attribute === "tagName"){ + return false; //boolean + } + + this._backupItem(item); + + var element = item.element; + if(attribute === "childNodes"){ + while(element.firstChild){ + element.removeChild(element.firstChild); + } + for(var i = 0; i < values.length; i++){ + var child = values[i].element; + element.appendChild(child); + } + }else if(attribute === "text()"){ + while (element.firstChild){ + element.removeChild(element.firstChild); + } + var value = ""; + for(var i = 0; i < values.length; i++){ + value += values[i]; + } + var text = this._getDocument(element).createTextNode(value); + element.appendChild(text); + }else{ + attribute = this._getAttribute(element.nodeName, attribute); + if(attribute.charAt(0) === '@'){ + var name = attribute.substring(1); + element.setAttribute(name, values[0]); + }else{ + for(var i = element.childNodes.length - 1; i >= 0; i--){ + var node = element.childNodes[i]; + if( node.nodeType === 1 /*ELEMENT_NODE*/ && + node.nodeName === attribute){ + element.removeChild(node); + } + } + var document = this._getDocument(element); + for(var i = 0; i < values.length; i++){ + var child = document.createElement(attribute); + var text = document.createTextNode(values[i]); + child.appendChild(text); + element.appendChild(child); + } + } + } + return true; //boolean + }, + + unsetAttribute: function(/* item */ item, /* attribute || string */ attribute){ + // summary: + // Remove an attribute + // description: + // 'item' must be an instance of a dojox.data.XmlItem from the store instance. + // 'attribute' can be an XML attribute name of the element or one of + // special names described below. + // If 'attribute' specifies "tagName", nothing is removed and false is + // returned. + // If 'attribute' specifies "childNodes" or "text()", all child nodes + // are removed. + // For generic attributes, if '_attributeMap' is specified, + // an actual attribute name is looked up with the tag name of + // the element and 'attribute' (concatenated with '.'). + // Then, if 'attribute' starts with "@", the XML attribute is removed. + // Otherwise, child elements of the tag name specified with + // 'attribute' are removed. + // item: + // An XML element that holds the attribute + // attribute: + // A tag name of child elements, an XML attribute name or one of + // special names + // returns: + // False for "tagName", otherwise true + if(attribute === "tagName"){ + return false; //boolean + } + + this._backupItem(item); + + var element = item.element; + if(attribute === "childNodes" || attribute === "text()"){ + while(element.firstChild){ + element.removeChild(element.firstChild); + } + }else{ + attribute = this._getAttribute(element.nodeName, attribute); + if(attribute.charAt(0) === '@'){ + var name = attribute.substring(1); + element.removeAttribute(name); + }else{ + for(var i = element.childNodes.length - 1; i >= 0; i--){ + var node = element.childNodes[i]; + if( node.nodeType === 1 /*ELEMENT_NODE*/ && + node.nodeName === attribute){ + element.removeChild(node); + } + } + } + } + return true; //boolean + }, + + save: function(/* object */ keywordArgs){ + // summary: + // Save new and/or modified items (XML elements) + // description: + // 'url' is used to save XML documents for new, modified and/or + // deleted XML elements. + // keywordArgs: + // An object for callbacks + if(!keywordArgs){ + keywordArgs = {}; + } + for(var i = 0; i < this._modifiedItems.length; i++){ + this._saveItem(this._modifiedItems[i], keywordArgs, "PUT"); + } + for(var i = 0; i < this._newItems.length; i++){ + var item = this._newItems[i]; + if(item.element.parentNode){ // reparented + this._newItems.splice(i, 1); + i--; + continue; + } + this._saveItem(this._newItems[i], keywordArgs, "POST"); + } + for(var i = 0; i < this._deletedItems.length; i++){ + this._saveItem(this._deletedItems[i], keywordArgs, "DELETE"); + } + }, + + revert: function(){ + // summary: + // Invalidate changes (new and/or modified elements) + // returns: + // True + console.log("XmlStore.revert() _newItems=" + this._newItems.length); + console.log("XmlStore.revert() _deletedItems=" + this._deletedItems.length); + console.log("XmlStore.revert() _modifiedItems=" + this._modifiedItems.length); + this._newItems = []; + this._restoreItems(this._deletedItems); + this._deletedItems = []; + this._restoreItems(this._modifiedItems); + this._modifiedItems = []; + return true; //boolean + }, + + isDirty: function(/* item? */ item){ + // summary: + // Check whether an item is new, modified or deleted + // description: + // If 'item' is specified, true is returned if the item is new, + // modified or deleted. + // Otherwise, true is returned if there are any new, modified + // or deleted items. + // item: + // An item (XML element) to check + // returns: + // True if an item or items are new, modified or deleted, otherwise + // false + if (item) { + var element = this._getRootElement(item.element); + return (this._getItemIndex(this._newItems, element) >= 0 || + this._getItemIndex(this._deletedItems, element) >= 0 || + this._getItemIndex(this._modifiedItems, element) >= 0); //boolean + } + else { + return (this._newItems.length > 0 || + this._deletedItems.length > 0 || + this._modifiedItems.length > 0); //boolean + } + }, + + _saveItem: function(item, keywordArgs, method){ + if(method === "PUT"){ + url = this._getPutUrl(item); + }else if(method === "DELETE"){ + url = this._getDeleteUrl(item); + }else{ // POST + url = this._getPostUrl(item); + } + if(!url){ + if(keywordArgs.onError){ + keywordArgs.onError.call(scope, new Error("No URL for saving content: " + postContent)); + } + return; + } + + var saveArgs = { + url: url, + method: (method || "POST"), + contentType: "text/xml", + handleAs: "xml" + }; + var saveHander; + if(method === "PUT"){ + saveArgs.putData = this._getPutContent(item); + saveHandler = dojo.rawXhrPut(saveArgs); + }else if(method === "DELETE"){ + saveHandler = dojo.xhrDelete(saveArgs); + }else{ // POST + saveArgs.postData = this._getPostContent(item); + saveHandler = dojo.rawXhrPost(saveArgs); + } + var scope = (keywordArgs.scope || dojo.global); + var self = this; + saveHandler.addCallback(function(data){ + self._forgetItem(item); + if(keywordArgs.onComplete){ + keywordArgs.onComplete.call(scope); + } + }); + saveHandler.addErrback(function(error){ + if(keywordArgs.onError){ + keywordArgs.onError.call(scope, error); + } + }); + }, + + _getPostUrl: function(item){ + // summary: + // Generate a URL for post + // description: + // This default implementation just returns 'url'. + // Sub-classes may override this method for the custom URL. + // item: + // An item to save + // returns: + // A post URL + return this.url; //string + }, + + _getPutUrl: function(item){ + // summary: + // Generate a URL for put + // description: + // This default implementation just returns 'url'. + // Sub-classes may override this method for the custom URL. + // item: + // An item to save + // returns: + // A put URL + return this.url; //string + }, + + _getDeleteUrl: function(item){ + // summary: + // Generate a URL for delete + // description: + // This default implementation returns 'url' with 'keyAttribute' + // as a query string. + // Sub-classes may override this method for the custom URL based on + // changes (new, deleted, or modified). + // item: + // An item to delete + // returns: + // A delete URL + var url = this.url; + if (item && this.keyAttribute !== "") { + var value = this.getValue(item, this.keyAttribute); + if (value) { + var key = this.keyAttribute.charAt(0) ==='@' ? this.keyAttribute.substring(1): this.keyAttribute; + url += url.indexOf('?') < 0 ? '?' : '&'; + url += key + '=' + value; + } + } + return url; //string + }, + + _getPostContent: function(item){ + // summary: + // Generate a content to post + // description: + // This default implementation generates an XML document for one + // (the first only) new or modified element. + // Sub-classes may override this method for the custom post content + // generation. + // item: + // An item to save + // returns: + // A post content + var element = item.element; + var declaration = "<?xml version=\"1.0\"?>"; // FIXME: encoding? + return declaration + dojox.data.dom.innerXML(element); //XML string + }, + + _getPutContent: function(item){ + // summary: + // Generate a content to put + // description: + // This default implementation generates an XML document for one + // (the first only) new or modified element. + // Sub-classes may override this method for the custom put content + // generation. + // item: + // An item to save + // returns: + // A post content + var element = item.element; + var declaration = "<?xml version=\"1.0\"?>"; // FIXME: encoding? + return declaration + dojox.data.dom.innerXML(element); //XML string + }, + +/* internal API */ + + _getAttribute: function(tagName, attribute){ + if(this._attributeMap){ + var key = tagName + "." + attribute; + var value = this._attributeMap[key]; + if(value){ + attribute = value; + }else{ // look for global attribute + value = this._attributeMap[attribute]; + if(value){ + attribute = value; + } + } + } + return attribute; //object + }, + + _getItem: function(element){ + return new dojox.data.XmlItem(element, this); //object + }, + + _getItemIndex: function(items, element){ + for(var i = 0; i < items.length; i++){ + if(items[i].element === element){ + return i; //int + } + } + return -1; //int + }, + + _backupItem: function(item){ + var element = this._getRootElement(item.element); + if( this._getItemIndex(this._newItems, element) >= 0 || + this._getItemIndex(this._modifiedItems, element) >= 0){ + return; // new or already modified + } + if(element != item.element){ + item = this._getItem(element); + } + item._backup = element.cloneNode(true); + this._modifiedItems.push(item); + }, + + _restoreItems: function(items){ + + dojo.forEach(items,function(item){ + if(item._backup){ + item.element = item._backup; + item._backup = null; + } + },this); + }, + + _forgetItem: function(item){ + var element = item.element; + var index = this._getItemIndex(this._newItems, element); + if(index >= 0){ + this._newItems.splice(index, 1); + } + index = this._getItemIndex(this._deletedItems, element); + if(index >= 0){ + this._deletedItems.splice(index, 1); + } + index = this._getItemIndex(this._modifiedItems, element); + if(index >= 0){ + this._modifiedItems.splice(index, 1); + } + }, + + _getDocument: function(element){ + if(element){ + return element.ownerDocument; //DOMDocument + }else if(!this._document){ + return dojox.data.dom.createDocument(); // DOMDocument + } + }, + + _getRootElement: function(element){ + while(element.parentNode){ + element = element.parentNode; + } + return element; //DOMElement + } + +}); + +//FIXME: Is a full class here really needed for containment of the item or would +//an anon object work fine? +dojo.declare("dojox.data.XmlItem", null, { + constructor: function(element, store) { + // summary: + // Initialize with an XML element + // element: + // An XML element + // store: + // The containing store, if any. + this.element = element; + this.store = store; + }, + // summary: + // A data item of 'XmlStore' + // description: + // This class represents an item of 'XmlStore' holding an XML element. + // 'element' + // element: + // An XML element + + toString: function() { + // summary: + // Return a value of the first text child of the element + // returns: + // a value of the first text child of the element + var str = ""; + if (this.element) { + for (var i = 0; i < this.element.childNodes.length; i++) { + var node = this.element.childNodes[i]; + if (node.nodeType === 3) { + str = node.nodeValue; + break; + } + } + } + return str; //String + } + +}); +dojo.extend(dojox.data.XmlStore,dojo.data.util.simpleFetch); + +} |