e44a7e37b6
git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f
1141 lines
34 KiB
JavaScript
1141 lines
34 KiB
JavaScript
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);
|
|
|
|
}
|