XmlStore.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471
  1. define("dojox/data/XmlStore", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/xhr", "dojo/data/util/simpleFetch",
  2. "dojo/_base/query", "dojo/_base/array", "dojo/_base/window", "dojo/data/util/filter", "dojox/xml/parser",
  3. "dojox/data/XmlItem"],
  4. function(lang, declare, xhr, simpleFetch, domQuery, array, winUtil, filter, xmlParser, XmlItem) {
  5. var XmlStore = declare("dojox.data.XmlStore", null, {
  6. // summary:
  7. // A data store for XML based services or documents
  8. // description:
  9. // A data store for XML based services or documents
  10. constructor: function(/* object */ args){
  11. // summary:
  12. // Constructor for the XML store.
  13. // args:
  14. // An anonymous object to initialize properties. It expects the following values:
  15. // url: The url to a service or an XML document that represents the store
  16. // rootItem: A tag name for root items
  17. // keyAttribute: An attribute name for a key or an identity (unique identifier)
  18. // Required for serverside fetchByIdentity, etc. Not required for
  19. // client side fetchItemBIdentity, as it will use an XPath-like
  20. // structure if keyAttribute was not specified. Recommended to always
  21. // set this, though, for consistent identity behavior.
  22. // attributeMap: An anonymous object contains properties for attribute mapping,
  23. // {"tag_name.item_attribute_name": "@xml_attribute_name", ...}
  24. // sendQuery: A boolean indicate to add a query string to the service URL.
  25. // Default is false.
  26. // urlPreventCache: Parameter to indicate whether or not URL calls should apply
  27. // the preventCache option to the xhr request.
  28. if(args){
  29. this.url = args.url;
  30. this.rootItem = (args.rootItem || args.rootitem || this.rootItem);
  31. this.keyAttribute = (args.keyAttribute || args.keyattribute || this.keyAttribute);
  32. this._attributeMap = (args.attributeMap || args.attributemap);
  33. this.label = args.label || this.label;
  34. this.sendQuery = (args.sendQuery || args.sendquery || this.sendQuery);
  35. if("urlPreventCache" in args){
  36. this.urlPreventCache = args.urlPreventCache?true:false;
  37. }
  38. }
  39. this._newItems = [];
  40. this._deletedItems = [];
  41. this._modifiedItems = [];
  42. },
  43. //Values that may be set by the parser.
  44. //Ergo, have to be instantiated to something
  45. //So the parser knows how to set them.
  46. url: "",
  47. // A tag name for XML tags to be considered root items in the hierarchy
  48. rootItem: "",
  49. // An attribute name for a key or an identity (unique identifier)
  50. // Required for serverside fetchByIdentity, etc. Not required for
  51. // client side fetchItemBIdentity, as it will use an XPath-like
  52. // structure if keyAttribute was not specified. Recommended to always
  53. // set this, though, for consistent identity behavior.
  54. keyAttribute: "",
  55. // An attribute of the item to use as the label.
  56. label: "",
  57. // A boolean indicate to add a query string to the service URL.
  58. // Default is false.
  59. sendQuery: false,
  60. // An anonymous object that contains properties for attribute mapping,
  61. // for example {"tag_name.item_attribute_name": "@xml_attribute_name", ...}.
  62. // This is optional. This is done so that attributes which are actual
  63. // XML tag attributes (and not sub-tags of an XML tag), can be referenced.
  64. attributeMap: null,
  65. // Parameter to indicate whether or not URL calls should apply the preventCache option to the xhr request.
  66. urlPreventCache: true,
  67. /* dojo.data.api.Read */
  68. getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){
  69. // summary:
  70. // Return an attribute value
  71. // description:
  72. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  73. // If 'attribute' specifies "tagName", the tag name of the element is
  74. // returned.
  75. // If 'attribute' specifies "childNodes", the first element child is
  76. // returned.
  77. // If 'attribute' specifies "text()", the value of the first text
  78. // child is returned.
  79. // For generic attributes, if '_attributeMap' is specified,
  80. // an actual attribute name is looked up with the tag name of
  81. // the element and 'attribute' (concatenated with '.').
  82. // Then, if 'attribute' starts with "@", the value of the XML
  83. // attribute is returned.
  84. // Otherwise, the first child element of the tag name specified with
  85. // 'attribute' is returned.
  86. // item:
  87. // An XML element that holds the attribute
  88. // attribute:
  89. // A tag name of a child element, An XML attribute name or one of
  90. // special names
  91. // defaultValue:
  92. // A default value
  93. // returns:
  94. // An attribute value found, otherwise 'defaultValue'
  95. var element = item.element;
  96. var i;
  97. var node;
  98. if(attribute === "tagName"){
  99. return element.nodeName;
  100. }else if(attribute === "childNodes"){
  101. for(i = 0; i < element.childNodes.length; i++){
  102. node = element.childNodes[i];
  103. if(node.nodeType === 1 /*ELEMENT_NODE*/){
  104. return this._getItem(node); //object
  105. }
  106. }
  107. return defaultValue;
  108. }else if(attribute === "text()"){
  109. for(i = 0; i < element.childNodes.length; i++){
  110. node = element.childNodes[i];
  111. if(node.nodeType === 3 /*TEXT_NODE*/ ||
  112. node.nodeType === 4 /*CDATA_SECTION_NODE*/){
  113. return node.nodeValue; //string
  114. }
  115. }
  116. return defaultValue;
  117. }else{
  118. attribute = this._getAttribute(element.nodeName, attribute);
  119. if(attribute.charAt(0) === '@'){
  120. var name = attribute.substring(1);
  121. var value = element.getAttribute(name);
  122. //Note that getAttribute will return null or empty string for undefined/unset
  123. //attributes, therefore, we should just check the return was valid
  124. //non-empty string and not null.
  125. return (value) ? value : defaultValue; //object
  126. }else{
  127. for(i = 0; i < element.childNodes.length; i++){
  128. node = element.childNodes[i];
  129. if( node.nodeType === 1 /*ELEMENT_NODE*/ &&
  130. node.nodeName === attribute){
  131. return this._getItem(node); //object
  132. }
  133. }
  134. return defaultValue; //object
  135. }
  136. }
  137. },
  138. getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
  139. // summary:
  140. // Return an array of attribute values
  141. // description:
  142. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  143. // If 'attribute' specifies "tagName", the tag name of the element is
  144. // returned.
  145. // If 'attribute' specifies "childNodes", child elements are returned.
  146. // If 'attribute' specifies "text()", the values of child text nodes
  147. // are returned.
  148. // For generic attributes, if 'attributeMap' is specified,
  149. // an actual attribute name is looked up with the tag name of
  150. // the element and 'attribute' (concatenated with '.').
  151. // Then, if 'attribute' starts with "@", the value of the XML
  152. // attribute is returned.
  153. // Otherwise, child elements of the tag name specified with
  154. // 'attribute' are returned.
  155. // item:
  156. // An XML element that holds the attribute
  157. // attribute:
  158. // A tag name of child elements, An XML attribute name or one of
  159. // special names
  160. // returns:
  161. // An array of attribute values found, otherwise an empty array
  162. var element = item.element;
  163. var values = [];
  164. var i;
  165. var node;
  166. if(attribute === "tagName"){
  167. return [element.nodeName];
  168. }else if(attribute === "childNodes"){
  169. for(i = 0; i < element.childNodes.length; i++){
  170. node = element.childNodes[i];
  171. if(node.nodeType === 1 /*ELEMENT_NODE*/){
  172. values.push(this._getItem(node));
  173. }
  174. }
  175. return values; //array
  176. }else if(attribute === "text()"){
  177. var ec = element.childNodes;
  178. for(i = 0; i < ec.length; i++){
  179. node = ec[i];
  180. if(node.nodeType === 3 || node.nodeType === 4){
  181. values.push(node.nodeValue);
  182. }
  183. }
  184. return values; //array
  185. }else{
  186. attribute = this._getAttribute(element.nodeName, attribute);
  187. if(attribute.charAt(0) === '@'){
  188. var name = attribute.substring(1);
  189. var value = element.getAttribute(name);
  190. return (value !== undefined) ? [value] : []; //array
  191. }else{
  192. for(i = 0; i < element.childNodes.length; i++){
  193. node = element.childNodes[i];
  194. if( node.nodeType === 1 /*ELEMENT_NODE*/ &&
  195. node.nodeName === attribute){
  196. values.push(this._getItem(node));
  197. }
  198. }
  199. return values; //array
  200. }
  201. }
  202. },
  203. getAttributes: function(/* item */ item){
  204. // summary:
  205. // Return an array of attribute names
  206. // description:
  207. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  208. // tag names of child elements and XML attribute names of attributes
  209. // specified to the element are returned along with special attribute
  210. // names applicable to the element including "tagName", "childNodes"
  211. // if the element has child elements, "text()" if the element has
  212. // child text nodes, and attribute names in '_attributeMap' that match
  213. // the tag name of the element.
  214. // item:
  215. // An XML element
  216. // returns:
  217. // An array of attributes found
  218. var element = item.element;
  219. var attributes = [];
  220. var i;
  221. attributes.push("tagName");
  222. if(element.childNodes.length > 0){
  223. var names = {};
  224. var childNodes = true;
  225. var text = false;
  226. for(i = 0; i < element.childNodes.length; i++){
  227. var node = element.childNodes[i];
  228. if(node.nodeType === 1 /*ELEMENT_NODE*/){
  229. var name = node.nodeName;
  230. if(!names[name]){
  231. attributes.push(name);
  232. names[name] = name;
  233. }
  234. childNodes = true;
  235. }else if(node.nodeType === 3){
  236. text = true;
  237. }
  238. }
  239. if(childNodes){
  240. attributes.push("childNodes");
  241. }
  242. if(text){
  243. attributes.push("text()");
  244. }
  245. }
  246. for(i = 0; i < element.attributes.length; i++){
  247. attributes.push("@" + element.attributes[i].nodeName);
  248. }
  249. if(this._attributeMap){
  250. for(var key in this._attributeMap){
  251. i = key.indexOf('.');
  252. if(i > 0){
  253. var tagName = key.substring(0, i);
  254. if(tagName === element.nodeName){
  255. attributes.push(key.substring(i + 1));
  256. }
  257. }else{ // global attribute
  258. attributes.push(key);
  259. }
  260. }
  261. }
  262. return attributes; //array
  263. },
  264. hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
  265. // summary:
  266. // Check whether an element has the attribute
  267. // item:
  268. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  269. // attribute:
  270. // A tag name of a child element, An XML attribute name or one of
  271. // special names
  272. // returns:
  273. // True if the element has the attribute, otherwise false
  274. return (this.getValue(item, attribute) !== undefined); //boolean
  275. },
  276. containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){
  277. // summary:
  278. // Check whether the attribute values contain the value
  279. // item:
  280. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  281. // attribute:
  282. // A tag name of a child element, An XML attribute name or one of
  283. // special names
  284. // returns:
  285. // True if the attribute values contain the value, otherwise false
  286. var values = this.getValues(item, attribute);
  287. for(var i = 0; i < values.length; i++){
  288. if((typeof value === "string")){
  289. if(values[i].toString && values[i].toString() === value){
  290. return true;
  291. }
  292. }else if(values[i] === value){
  293. return true; //boolean
  294. }
  295. }
  296. return false;//boolean
  297. },
  298. isItem: function(/* anything */ something){
  299. // summary:
  300. // Check whether the object is an item (XML element)
  301. // item:
  302. // An object to check
  303. // returns:
  304. // True if the object is an XML element, otherwise false
  305. if(something && something.element && something.store && something.store === this){
  306. return true; //boolean
  307. }
  308. return false; //boolran
  309. },
  310. isItemLoaded: function(/* anything */ something){
  311. // summary:
  312. // Check whether the object is an item (XML element) and loaded
  313. // item:
  314. // An object to check
  315. // returns:
  316. // True if the object is an XML element, otherwise false
  317. return this.isItem(something); //boolean
  318. },
  319. loadItem: function(/* object */ keywordArgs){
  320. // summary:
  321. // Load an item (XML element)
  322. // keywordArgs:
  323. // object containing the args for loadItem. See dojo.data.api.Read.loadItem()
  324. },
  325. getFeatures: function(){
  326. // summary:
  327. // Return supported data APIs
  328. // returns:
  329. // "dojo.data.api.Read" and "dojo.data.api.Write"
  330. var features = {
  331. "dojo.data.api.Read": true,
  332. "dojo.data.api.Write": true
  333. };
  334. //Local XML parsing can implement Identity fairly simple via
  335. if(!this.sendQuery || this.keyAttribute !== ""){
  336. features["dojo.data.api.Identity"] = true;
  337. }
  338. return features; //array
  339. },
  340. getLabel: function(/* item */ item){
  341. // summary:
  342. // See dojo.data.api.Read.getLabel()
  343. if((this.label !== "") && this.isItem(item)){
  344. var label = this.getValue(item,this.label);
  345. if(label){
  346. return label.toString();
  347. }
  348. }
  349. return undefined; //undefined
  350. },
  351. getLabelAttributes: function(/* item */ item){
  352. // summary:
  353. // See dojo.data.api.Read.getLabelAttributes()
  354. if(this.label !== ""){
  355. return [this.label]; //array
  356. }
  357. return null; //null
  358. },
  359. _fetchItems: function(request, fetchHandler, errorHandler){
  360. // summary:
  361. // Fetch items (XML elements) that match to a query
  362. // description:
  363. // If 'sendQuery' is true, an XML document is loaded from
  364. // 'url' with a query string.
  365. // Otherwise, an XML document is loaded and list XML elements that
  366. // match to a query (set of element names and their text attribute
  367. // values that the items to contain).
  368. // A wildcard, "*" can be used to query values to match all
  369. // occurrences.
  370. // If 'rootItem' is specified, it is used to fetch items.
  371. // request:
  372. // A request object
  373. // fetchHandler:
  374. // A function to call for fetched items
  375. // errorHandler:
  376. // A function to call on error
  377. var url = this._getFetchUrl(request);
  378. if(!url){
  379. errorHandler(new Error("No URL specified."), request);
  380. return;
  381. }
  382. var localRequest = (!this.sendQuery ? request : {}); // use request for _getItems()
  383. var self = this;
  384. var getArgs = {
  385. url: url,
  386. handleAs: "xml",
  387. preventCache: self.urlPreventCache
  388. };
  389. var getHandler = xhr.get(getArgs);
  390. getHandler.addCallback(function(data){
  391. var items = self._getItems(data, localRequest);
  392. if(items && items.length > 0){
  393. fetchHandler(items, request);
  394. }else{
  395. fetchHandler([], request);
  396. }
  397. });
  398. getHandler.addErrback(function(data){
  399. errorHandler(data, request);
  400. });
  401. },
  402. _getFetchUrl: function(request){
  403. // summary:
  404. // Generate a URL for fetch
  405. // description:
  406. // This default implementation generates a query string in the form of
  407. // "?name1=value1&name2=value2..." off properties of 'query' object
  408. // specified in 'request' and appends it to 'url', if 'sendQuery'
  409. // is set to false.
  410. // Otherwise, 'url' is returned as is.
  411. // Sub-classes may override this method for the custom URL generation.
  412. // request:
  413. // A request object
  414. // returns:
  415. // A fetch URL
  416. if(!this.sendQuery){
  417. return this.url;
  418. }
  419. var query = request.query;
  420. if(!query){
  421. return this.url;
  422. }
  423. if(lang.isString(query)){
  424. return this.url + query;
  425. }
  426. var queryString = "";
  427. for(var name in query){
  428. var value = query[name];
  429. if(value){
  430. if(queryString){
  431. queryString += "&";
  432. }
  433. queryString += (name + "=" + value);
  434. }
  435. }
  436. if(!queryString){
  437. return this.url;
  438. }
  439. //Check to see if the URL already has query params or not.
  440. var fullUrl = this.url;
  441. if(fullUrl.indexOf("?") < 0){
  442. fullUrl += "?";
  443. }else{
  444. fullUrl += "&";
  445. }
  446. return fullUrl + queryString;
  447. },
  448. _getItems: function(document, request){
  449. // summary:
  450. // Fetch items (XML elements) in an XML document based on a request
  451. // description:
  452. // This default implementation walks through child elements of
  453. // the document element to see if all properties of 'query' object
  454. // match corresponding attributes of the element (item).
  455. // If 'request' is not specified, all child elements are returned.
  456. // Sub-classes may override this method for the custom search in
  457. // an XML document.
  458. // document:
  459. // An XML document
  460. // request:
  461. // A request object
  462. // returns:
  463. // An array of items
  464. var query = null;
  465. if(request){
  466. query = request.query;
  467. }
  468. var items = [];
  469. var nodes = null;
  470. if(this.rootItem !== ""){
  471. nodes = domQuery(this.rootItem, document);
  472. }else{
  473. nodes = document.documentElement.childNodes;
  474. }
  475. var deep = request.queryOptions ? request.queryOptions.deep : false;
  476. if(deep){
  477. nodes = this._flattenNodes(nodes);
  478. }
  479. for(var i = 0; i < nodes.length; i++){
  480. var node = nodes[i];
  481. if(node.nodeType != 1 /*ELEMENT_NODE*/){
  482. continue;
  483. }
  484. var item = this._getItem(node);
  485. if(query){
  486. var ignoreCase = request.queryOptions ? request.queryOptions.ignoreCase : false;
  487. var value;
  488. var match = false;
  489. var j;
  490. var emptyQuery = true;
  491. //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
  492. //same value for each item examined. Much more efficient.
  493. var regexpList = {};
  494. for(var key in query){
  495. value = query[key];
  496. if(typeof value === "string"){
  497. regexpList[key] = filter.patternToRegExp(value, ignoreCase);
  498. }else if(value){
  499. // It's an object, possibly regexp, so treat it as one.
  500. regexpList[key] = value;
  501. }
  502. }
  503. for(var attribute in query){
  504. emptyQuery = false;
  505. var values = this.getValues(item, attribute);
  506. for(j = 0; j < values.length; j++){
  507. value = values[j];
  508. if(value){
  509. var queryValue = query[attribute];
  510. if((typeof value) === "string" &&
  511. (regexpList[attribute])){
  512. if((value.match(regexpList[attribute])) !== null){
  513. match = true;
  514. }else{
  515. match = false;
  516. }
  517. }else if((typeof value) === "object"){
  518. if( value.toString &&
  519. (regexpList[attribute])){
  520. var stringValue = value.toString();
  521. if((stringValue.match(regexpList[attribute])) !== null){
  522. match = true;
  523. }else{
  524. match = false;
  525. }
  526. }else{
  527. if(queryValue === "*" || queryValue === value){
  528. match = true;
  529. }else{
  530. match = false;
  531. }
  532. }
  533. }
  534. }
  535. //One of the multiValue values matched,
  536. //so quit looking.
  537. if(match){
  538. break;
  539. }
  540. }
  541. if(!match){
  542. break;
  543. }
  544. }
  545. //Either the query was an empty object {}, which is match all, or
  546. //was an actual match.
  547. if(emptyQuery || match){
  548. items.push(item);
  549. }
  550. }else{
  551. //No query, everything matches.
  552. items.push(item);
  553. }
  554. }
  555. array.forEach(items,function(item){
  556. if(item.element.parentNode){
  557. item.element.parentNode.removeChild(item.element); // make it root
  558. }
  559. },this);
  560. return items;
  561. },
  562. _flattenNodes: function(nodes){
  563. // Summary:
  564. // Function used to flatten a hierarchy of XML nodes into a single list for
  565. // querying over. Used when deep = true;
  566. var flattened = [];
  567. if(nodes){
  568. var i;
  569. for(i = 0; i < nodes.length; i++){
  570. var node = nodes[i];
  571. flattened.push(node);
  572. if(node.childNodes && node.childNodes.length > 0){
  573. flattened = flattened.concat(this._flattenNodes(node.childNodes));
  574. }
  575. }
  576. }
  577. return flattened;
  578. },
  579. close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
  580. // summary:
  581. // See dojo.data.api.Read.close()
  582. },
  583. /* dojo.data.api.Write */
  584. newItem: function(/* object? */ keywordArgs, parentInfo){
  585. // summary:
  586. // Return a new dojox.data.XmlItem
  587. // description:
  588. // At least, 'keywordArgs' must contain "tagName" to be used for
  589. // the new element.
  590. // Other attributes in 'keywordArgs' are set to the new element,
  591. // including "text()", but excluding "childNodes".
  592. // keywordArgs:
  593. // An object containing initial attributes
  594. // returns:
  595. // An XML element
  596. keywordArgs = (keywordArgs || {});
  597. var tagName = keywordArgs.tagName;
  598. if(!tagName){
  599. tagName = this.rootItem;
  600. if(tagName === ""){
  601. return null;
  602. }
  603. }
  604. var document = this._getDocument();
  605. var element = document.createElement(tagName);
  606. for(var attribute in keywordArgs){
  607. var text;
  608. if(attribute === "tagName"){
  609. continue;
  610. }else if(attribute === "text()"){
  611. text = document.createTextNode(keywordArgs[attribute]);
  612. element.appendChild(text);
  613. }else{
  614. attribute = this._getAttribute(tagName, attribute);
  615. if(attribute.charAt(0) === '@'){
  616. var name = attribute.substring(1);
  617. element.setAttribute(name, keywordArgs[attribute]);
  618. }else{
  619. var child = document.createElement(attribute);
  620. text = document.createTextNode(keywordArgs[attribute]);
  621. child.appendChild(text);
  622. element.appendChild(child);
  623. }
  624. }
  625. }
  626. var item = this._getItem(element);
  627. this._newItems.push(item);
  628. var pInfo = null;
  629. if(parentInfo && parentInfo.parent && parentInfo.attribute){
  630. pInfo = {
  631. item: parentInfo.parent,
  632. attribute: parentInfo.attribute,
  633. oldValue: undefined
  634. };
  635. //See if it is multi-valued or not and handle appropriately
  636. //Generally, all attributes are multi-valued for this store
  637. //So, we only need to append if there are already values present.
  638. var values = this.getValues(parentInfo.parent, parentInfo.attribute);
  639. if(values && values.length > 0){
  640. var tempValues = values.slice(0, values.length);
  641. if(values.length === 1){
  642. pInfo.oldValue = values[0];
  643. }else{
  644. pInfo.oldValue = values.slice(0, values.length);
  645. }
  646. tempValues.push(item);
  647. this.setValues(parentInfo.parent, parentInfo.attribute, tempValues);
  648. pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
  649. }else{
  650. this.setValue(parentInfo.parent, parentInfo.attribute, item);
  651. pInfo.newValue = item;
  652. }
  653. }
  654. return item; //object
  655. },
  656. deleteItem: function(/* item */ item){
  657. // summary:
  658. // Delete an dojox.data.XmlItem (wrapper to a XML element).
  659. // item:
  660. // An XML element to delete
  661. // returns:
  662. // True
  663. var element = item.element;
  664. if(element.parentNode){
  665. this._backupItem(item);
  666. element.parentNode.removeChild(element);
  667. return true;
  668. }
  669. this._forgetItem(item);
  670. this._deletedItems.push(item);
  671. return true; //boolean
  672. },
  673. setValue: function(/* item */ item, /* attribute || string */ attribute, /* almost anything */ value){
  674. // summary:
  675. // Set an attribute value
  676. // description:
  677. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  678. // If 'attribute' specifies "tagName", nothing is set and false is
  679. // returned.
  680. // If 'attribute' specifies "childNodes", the value (XML element) is
  681. // added to the element.
  682. // If 'attribute' specifies "text()", a text node is created with
  683. // the value and set it to the element as a child.
  684. // For generic attributes, if '_attributeMap' is specified,
  685. // an actual attribute name is looked up with the tag name of
  686. // the element and 'attribute' (concatenated with '.').
  687. // Then, if 'attribute' starts with "@", the value is set to the XML
  688. // attribute.
  689. // Otherwise, a text node is created with the value and set it to
  690. // the first child element of the tag name specified with 'attribute'.
  691. // If the child element does not exist, it is created.
  692. // item:
  693. // An XML element that holds the attribute
  694. // attribute:
  695. // A tag name of a child element, An XML attribute name or one of
  696. // special names
  697. // value:
  698. // A attribute value to set
  699. // returns:
  700. // False for "tagName", otherwise true
  701. if(attribute === "tagName"){
  702. return false; //boolean
  703. }
  704. this._backupItem(item);
  705. var element = item.element;
  706. var child;
  707. var text;
  708. if(attribute === "childNodes"){
  709. child = value.element;
  710. element.appendChild(child);
  711. }else if(attribute === "text()"){
  712. while(element.firstChild){
  713. element.removeChild(element.firstChild);
  714. }
  715. text = this._getDocument(element).createTextNode(value);
  716. element.appendChild(text);
  717. }else{
  718. attribute = this._getAttribute(element.nodeName, attribute);
  719. if(attribute.charAt(0) === '@'){
  720. var name = attribute.substring(1);
  721. element.setAttribute(name, value);
  722. }else{
  723. for(var i = 0; i < element.childNodes.length; i++){
  724. var node = element.childNodes[i];
  725. if( node.nodeType === 1 /*ELEMENT_NODE*/ &&
  726. node.nodeName === attribute){
  727. child = node;
  728. break;
  729. }
  730. }
  731. var document = this._getDocument(element);
  732. if(child){
  733. while(child.firstChild){
  734. child.removeChild(child.firstChild);
  735. }
  736. }else{
  737. child = document.createElement(attribute);
  738. element.appendChild(child);
  739. }
  740. text = document.createTextNode(value);
  741. child.appendChild(text);
  742. }
  743. }
  744. return true; //boolean
  745. },
  746. setValues: function(/* item */ item, /* attribute || string */ attribute, /*array*/ values){
  747. // summary:
  748. // Set attribute values
  749. // description:
  750. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  751. // If 'attribute' specifies "tagName", nothing is set and false is
  752. // returned.
  753. // If 'attribute' specifies "childNodes", the value (array of XML
  754. // elements) is set to the element's childNodes.
  755. // If 'attribute' specifies "text()", a text node is created with
  756. // the values and set it to the element as a child.
  757. // For generic attributes, if '_attributeMap' is specified,
  758. // an actual attribute name is looked up with the tag name of
  759. // the element and 'attribute' (concatenated with '.').
  760. // Then, if 'attribute' starts with "@", the first value is set to
  761. // the XML attribute.
  762. // Otherwise, child elements of the tag name specified with
  763. // 'attribute' are replaced with new child elements and their
  764. // child text nodes of values.
  765. // item:
  766. // An XML element that holds the attribute
  767. // attribute:
  768. // A tag name of child elements, an XML attribute name or one of
  769. // special names
  770. // value:
  771. // A attribute value to set
  772. // notify:
  773. // A non-API optional argument, used to indicate if notification API should be called
  774. // or not.
  775. // returns:
  776. // False for "tagName", otherwise true
  777. if(attribute === "tagName"){
  778. return false; //boolean
  779. }
  780. this._backupItem(item);
  781. var element = item.element;
  782. var i;
  783. var child;
  784. var text;
  785. if(attribute === "childNodes"){
  786. while(element.firstChild){
  787. element.removeChild(element.firstChild);
  788. }
  789. for(i = 0; i < values.length; i++){
  790. child = values[i].element;
  791. element.appendChild(child);
  792. }
  793. }else if(attribute === "text()"){
  794. while(element.firstChild){
  795. element.removeChild(element.firstChild);
  796. }
  797. var value = "";
  798. for(i = 0; i < values.length; i++){
  799. value += values[i];
  800. }
  801. text = this._getDocument(element).createTextNode(value);
  802. element.appendChild(text);
  803. }else{
  804. attribute = this._getAttribute(element.nodeName, attribute);
  805. if(attribute.charAt(0) === '@'){
  806. var name = attribute.substring(1);
  807. element.setAttribute(name, values[0]);
  808. }else{
  809. for(i = element.childNodes.length - 1; i >= 0; i--){
  810. var node = element.childNodes[i];
  811. if( node.nodeType === 1 /*ELEMENT_NODE*/ &&
  812. node.nodeName === attribute){
  813. element.removeChild(node);
  814. }
  815. }
  816. var document = this._getDocument(element);
  817. for(i = 0; i < values.length; i++){
  818. child = document.createElement(attribute);
  819. text = document.createTextNode(values[i]);
  820. child.appendChild(text);
  821. element.appendChild(child);
  822. }
  823. }
  824. }
  825. return true; //boolean
  826. },
  827. unsetAttribute: function(/* item */ item, /* attribute || string */ attribute){
  828. // summary:
  829. // Remove an attribute
  830. // description:
  831. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  832. // 'attribute' can be an XML attribute name of the element or one of
  833. // special names described below.
  834. // If 'attribute' specifies "tagName", nothing is removed and false is
  835. // returned.
  836. // If 'attribute' specifies "childNodes" or "text()", all child nodes
  837. // are removed.
  838. // For generic attributes, if '_attributeMap' is specified,
  839. // an actual attribute name is looked up with the tag name of
  840. // the element and 'attribute' (concatenated with '.').
  841. // Then, if 'attribute' starts with "@", the XML attribute is removed.
  842. // Otherwise, child elements of the tag name specified with
  843. // 'attribute' are removed.
  844. // item:
  845. // An XML element that holds the attribute
  846. // attribute:
  847. // A tag name of child elements, an XML attribute name or one of
  848. // special names
  849. // returns:
  850. // False for "tagName", otherwise true
  851. if(attribute === "tagName"){
  852. return false; //boolean
  853. }
  854. this._backupItem(item);
  855. var element = item.element;
  856. if(attribute === "childNodes" || attribute === "text()"){
  857. while(element.firstChild){
  858. element.removeChild(element.firstChild);
  859. }
  860. }else{
  861. attribute = this._getAttribute(element.nodeName, attribute);
  862. if(attribute.charAt(0) === '@'){
  863. var name = attribute.substring(1);
  864. element.removeAttribute(name);
  865. }else{
  866. for(var i = element.childNodes.length - 1; i >= 0; i--){
  867. var node = element.childNodes[i];
  868. if( node.nodeType === 1 /*ELEMENT_NODE*/ &&
  869. node.nodeName === attribute){
  870. element.removeChild(node);
  871. }
  872. }
  873. }
  874. }
  875. return true; //boolean
  876. },
  877. save: function(/* object */ keywordArgs){
  878. // summary:
  879. // Save new and/or modified items (XML elements)
  880. // description:
  881. // 'url' is used to save XML documents for new, modified and/or
  882. // deleted XML elements.
  883. // keywordArgs:
  884. // An object for callbacks
  885. if(!keywordArgs){
  886. keywordArgs = {};
  887. }
  888. var i;
  889. for(i = 0; i < this._modifiedItems.length; i++){
  890. this._saveItem(this._modifiedItems[i], keywordArgs, "PUT");
  891. }
  892. for(i = 0; i < this._newItems.length; i++){
  893. var item = this._newItems[i];
  894. if(item.element.parentNode){ // reparented
  895. this._newItems.splice(i, 1);
  896. i--;
  897. continue;
  898. }
  899. this._saveItem(this._newItems[i], keywordArgs, "POST");
  900. }
  901. for(i = 0; i < this._deletedItems.length; i++){
  902. this._saveItem(this._deletedItems[i], keywordArgs, "DELETE");
  903. }
  904. },
  905. revert: function(){
  906. // summary:
  907. // Invalidate changes (new and/or modified elements)
  908. // returns:
  909. // True
  910. this._newItems = [];
  911. this._restoreItems(this._deletedItems);
  912. this._deletedItems = [];
  913. this._restoreItems(this._modifiedItems);
  914. this._modifiedItems = [];
  915. return true; //boolean
  916. },
  917. isDirty: function(/* item? */ item){
  918. // summary:
  919. // Check whether an item is new, modified or deleted
  920. // description:
  921. // If 'item' is specified, true is returned if the item is new,
  922. // modified or deleted.
  923. // Otherwise, true is returned if there are any new, modified
  924. // or deleted items.
  925. // item:
  926. // An item (XML element) to check
  927. // returns:
  928. // True if an item or items are new, modified or deleted, otherwise
  929. // false
  930. if(item){
  931. var element = this._getRootElement(item.element);
  932. return (this._getItemIndex(this._newItems, element) >= 0 ||
  933. this._getItemIndex(this._deletedItems, element) >= 0 ||
  934. this._getItemIndex(this._modifiedItems, element) >= 0); //boolean
  935. }else{
  936. return (this._newItems.length > 0 ||
  937. this._deletedItems.length > 0 ||
  938. this._modifiedItems.length > 0); //boolean
  939. }
  940. },
  941. _saveItem: function(item, keywordArgs, method){
  942. var url;
  943. var scope;
  944. if(method === "PUT"){
  945. url = this._getPutUrl(item);
  946. }else if(method === "DELETE"){
  947. url = this._getDeleteUrl(item);
  948. }else{ // POST
  949. url = this._getPostUrl(item);
  950. }
  951. if(!url){
  952. if(keywordArgs.onError){
  953. scope = keywordArgs.scope || winUtil.global;
  954. keywordArgs.onError.call(scope, new Error("No URL for saving content: " + this._getPostContent(item)));
  955. }
  956. return;
  957. }
  958. var saveArgs = {
  959. url: url,
  960. method: (method || "POST"),
  961. contentType: "text/xml",
  962. handleAs: "xml"
  963. };
  964. var saveHandler;
  965. if(method === "PUT"){
  966. saveArgs.putData = this._getPutContent(item);
  967. saveHandler = xhr.put(saveArgs);
  968. }else if(method === "DELETE"){
  969. saveHandler = xhr.del(saveArgs);
  970. }else{ // POST
  971. saveArgs.postData = this._getPostContent(item);
  972. saveHandler = xhr.post(saveArgs);
  973. }
  974. scope = (keywordArgs.scope || winUtil. global);
  975. var self = this;
  976. saveHandler.addCallback(function(data){
  977. self._forgetItem(item);
  978. if(keywordArgs.onComplete){
  979. keywordArgs.onComplete.call(scope);
  980. }
  981. });
  982. saveHandler.addErrback(function(error){
  983. if(keywordArgs.onError){
  984. keywordArgs.onError.call(scope, error);
  985. }
  986. });
  987. },
  988. _getPostUrl: function(item){
  989. // summary:
  990. // Generate a URL for post
  991. // description:
  992. // This default implementation just returns 'url'.
  993. // Sub-classes may override this method for the custom URL.
  994. // item:
  995. // An item to save
  996. // returns:
  997. // A post URL
  998. return this.url; //string
  999. },
  1000. _getPutUrl: function(item){
  1001. // summary:
  1002. // Generate a URL for put
  1003. // description:
  1004. // This default implementation just returns 'url'.
  1005. // Sub-classes may override this method for the custom URL.
  1006. // item:
  1007. // An item to save
  1008. // returns:
  1009. // A put URL
  1010. return this.url; //string
  1011. },
  1012. _getDeleteUrl: function(item){
  1013. // summary:
  1014. // Generate a URL for delete
  1015. // description:
  1016. // This default implementation returns 'url' with 'keyAttribute'
  1017. // as a query string.
  1018. // Sub-classes may override this method for the custom URL based on
  1019. // changes (new, deleted, or modified).
  1020. // item:
  1021. // An item to delete
  1022. // returns:
  1023. // A delete URL
  1024. var url = this.url;
  1025. if(item && this.keyAttribute !== ""){
  1026. var value = this.getValue(item, this.keyAttribute);
  1027. if(value){
  1028. var key = this.keyAttribute.charAt(0) ==='@' ? this.keyAttribute.substring(1): this.keyAttribute;
  1029. url += url.indexOf('?') < 0 ? '?' : '&';
  1030. url += key + '=' + value;
  1031. }
  1032. }
  1033. return url; //string
  1034. },
  1035. _getPostContent: function(item){
  1036. // summary:
  1037. // Generate a content to post
  1038. // description:
  1039. // This default implementation generates an XML document for one
  1040. // (the first only) new or modified element.
  1041. // Sub-classes may override this method for the custom post content
  1042. // generation.
  1043. // item:
  1044. // An item to save
  1045. // returns:
  1046. // A post content
  1047. return "<?xml version=\'1.0\'?>" + xmlParser.innerXML(item.element); //XML string
  1048. },
  1049. _getPutContent: function(item){
  1050. // summary:
  1051. // Generate a content to put
  1052. // description:
  1053. // This default implementation generates an XML document for one
  1054. // (the first only) new or modified element.
  1055. // Sub-classes may override this method for the custom put content
  1056. // generation.
  1057. // item:
  1058. // An item to save
  1059. // returns:
  1060. // A post content
  1061. return "<?xml version='1.0'?>" + xmlParser.innerXML(item.element); //XML string
  1062. },
  1063. /* internal API */
  1064. _getAttribute: function(tagName, attribute){
  1065. if(this._attributeMap){
  1066. var key = tagName + "." + attribute;
  1067. var value = this._attributeMap[key];
  1068. if(value){
  1069. attribute = value;
  1070. }else{ // look for global attribute
  1071. value = this._attributeMap[attribute];
  1072. if(value){
  1073. attribute = value;
  1074. }
  1075. }
  1076. }
  1077. return attribute; //object
  1078. },
  1079. _getItem: function(element){
  1080. try{
  1081. var q = null;
  1082. //Avoid function call if possible.
  1083. if(this.keyAttribute === ""){
  1084. q = this._getXPath(element);
  1085. }
  1086. return new XmlItem(element, this, q); //object
  1087. }catch (e){
  1088. console.log(e);
  1089. }
  1090. return null;
  1091. },
  1092. _getItemIndex: function(items, element){
  1093. for(var i = 0; i < items.length; i++){
  1094. if(items[i].element === element){
  1095. return i; //int
  1096. }
  1097. }
  1098. return -1; //int
  1099. },
  1100. _backupItem: function(item){
  1101. var element = this._getRootElement(item.element);
  1102. if( this._getItemIndex(this._newItems, element) >= 0 ||
  1103. this._getItemIndex(this._modifiedItems, element) >= 0){
  1104. return; // new or already modified
  1105. }
  1106. if(element != item.element){
  1107. item = this._getItem(element);
  1108. }
  1109. item._backup = element.cloneNode(true);
  1110. this._modifiedItems.push(item);
  1111. },
  1112. _restoreItems: function(items){
  1113. array.forEach(items,function(item){
  1114. if(item._backup){
  1115. item.element = item._backup;
  1116. item._backup = null;
  1117. }
  1118. },this);
  1119. },
  1120. _forgetItem: function(item){
  1121. var element = item.element;
  1122. var index = this._getItemIndex(this._newItems, element);
  1123. if(index >= 0){
  1124. this._newItems.splice(index, 1);
  1125. }
  1126. index = this._getItemIndex(this._deletedItems, element);
  1127. if(index >= 0){
  1128. this._deletedItems.splice(index, 1);
  1129. }
  1130. index = this._getItemIndex(this._modifiedItems, element);
  1131. if(index >= 0){
  1132. this._modifiedItems.splice(index, 1);
  1133. }
  1134. },
  1135. _getDocument: function(element){
  1136. if(element){
  1137. return element.ownerDocument; //DOMDocument
  1138. }else if(!this._document){
  1139. return xmlParser.parse(); // DOMDocument
  1140. }
  1141. return null; //null
  1142. },
  1143. _getRootElement: function(element){
  1144. while(element.parentNode){
  1145. element = element.parentNode;
  1146. }
  1147. return element; //DOMElement
  1148. },
  1149. _getXPath: function(element){
  1150. // summary:
  1151. // A function to compute the xpath of a node in a DOM document.
  1152. // description:
  1153. // A function to compute the xpath of a node in a DOM document. Used for
  1154. // Client side query handling and identity.
  1155. var xpath = null;
  1156. if(!this.sendQuery){
  1157. //xpath should be null for any server queries, as we don't have the entire
  1158. //XML dom to figure it out.
  1159. var node = element;
  1160. xpath = "";
  1161. while(node && node != element.ownerDocument){
  1162. var pos = 0;
  1163. var sibling = node;
  1164. var name = node.nodeName;
  1165. while(sibling){
  1166. sibling = sibling.previousSibling;
  1167. if(sibling && sibling.nodeName === name){
  1168. pos++;
  1169. }
  1170. }
  1171. var temp = "/" + name + "[" + pos + "]";
  1172. if(xpath){
  1173. xpath = temp + xpath;
  1174. }else{
  1175. xpath = temp;
  1176. }
  1177. node = node.parentNode;
  1178. }
  1179. }
  1180. return xpath; //string
  1181. },
  1182. /*************************************
  1183. * Dojo.data Identity implementation *
  1184. *************************************/
  1185. getIdentity: function(/* item */ item){
  1186. // summary:
  1187. // Returns a unique identifier for an item.
  1188. // item:
  1189. // The XML Item from the store from which to obtain its identifier.
  1190. if(!this.isItem(item)){
  1191. throw new Error("dojox.data.XmlStore: Object supplied to getIdentity is not an item");
  1192. }else{
  1193. var id = null;
  1194. if(this.sendQuery && this.keyAttribute !== ""){
  1195. id = this.getValue(item, this.keyAttribute).toString();
  1196. }else if(!this.serverQuery){
  1197. if(this.keyAttribute !== ""){
  1198. id = this.getValue(item,this.keyAttribute).toString();
  1199. }else{
  1200. //No specified identity, so return the dojo.query/xpath
  1201. //for the node as fallback.
  1202. id = item.q;
  1203. }
  1204. }
  1205. return id; //String.
  1206. }
  1207. },
  1208. getIdentityAttributes: function(/* item */ item){
  1209. // summary:
  1210. // Returns an array of attribute names that are used to generate the identity.
  1211. // description:
  1212. // For XmlStore, if sendQuery is false and no keyAttribute was set, then this function
  1213. // returns null, as xpath is used for the identity, which is not a public attribute of
  1214. // the item. If sendQuery is true and keyAttribute is set, then this function
  1215. // returns an array of one attribute name: keyAttribute. This means the server side
  1216. // implementation must apply a keyAttribute to a returned node that always allows
  1217. // it to be looked up again.
  1218. // item:
  1219. // The item from the store from which to obtain the array of public attributes that
  1220. // compose the identifier, if any.
  1221. if(!this.isItem(item)){
  1222. throw new Error("dojox.data.XmlStore: Object supplied to getIdentity is not an item");
  1223. }else{
  1224. if(this.keyAttribute !== ""){
  1225. return [this.keyAttribute]; //array
  1226. }else{
  1227. //Otherwise it's either using xpath (not an attribute), or the remote store
  1228. //doesn't support identity.
  1229. return null; //null
  1230. }
  1231. }
  1232. },
  1233. fetchItemByIdentity: function(/* object */ keywordArgs){
  1234. // summary:
  1235. // See dojo.data.api.Identity.fetchItemByIdentity(keywordArgs)
  1236. var handleDocument = null;
  1237. var scope = null;
  1238. var self = this;
  1239. var url = null;
  1240. var getArgs = null;
  1241. var getHandler = null;
  1242. if(!self.sendQuery){
  1243. handleDocument = function(data){
  1244. if(data){
  1245. if(self.keyAttribute !== ""){
  1246. //We have a key attribute specified. So ... we can process the items and locate the item
  1247. //that contains a matching key attribute. Its identity, as it were.
  1248. var request = {};
  1249. request.query={};
  1250. request.query[self.keyAttribute] = keywordArgs.identity;
  1251. request.queryOptions = {deep: true};
  1252. var items = self._getItems(data,request);
  1253. scope = keywordArgs.scope || winUtil.global;
  1254. if(items.length === 1){
  1255. if(keywordArgs.onItem){
  1256. keywordArgs.onItem.call(scope, items[0]);
  1257. }
  1258. }else if(items.length === 0){
  1259. if(keywordArgs.onItem){
  1260. keywordArgs.onItem.call(scope, null);
  1261. }
  1262. }else{
  1263. if(keywordArgs.onError){
  1264. keywordArgs.onError.call(scope, new Error("Items array size for identity lookup greater than 1, invalid keyAttribute."));
  1265. }
  1266. }
  1267. }else{
  1268. //Since dojo.query doesn't really support the functions needed
  1269. //to do child node selection on IE well and since xpath support
  1270. //is flakey across browsers, it's simpler to implement a
  1271. //pseudo-xpath parser here.
  1272. var qArgs = keywordArgs.identity.split("/");
  1273. var i;
  1274. var node = data;
  1275. for(i = 0; i < qArgs.length; i++){
  1276. if(qArgs[i] && qArgs[i] !== ""){
  1277. var section = qArgs[i];
  1278. section = section.substring(0,section.length - 1);
  1279. var vals = section.split("[");
  1280. var tag = vals[0];
  1281. var index = parseInt(vals[1], 10);
  1282. var pos = 0;
  1283. if(node){
  1284. var cNodes = node.childNodes;
  1285. if(cNodes){
  1286. var j;
  1287. var foundNode = null;
  1288. for(j = 0; j < cNodes.length; j++){
  1289. var pNode = cNodes[j];
  1290. if(pNode.nodeName === tag){
  1291. if(pos < index){
  1292. pos++;
  1293. }else{
  1294. foundNode = pNode;
  1295. break;
  1296. }
  1297. }
  1298. }
  1299. if(foundNode){
  1300. node = foundNode;
  1301. }else{
  1302. node = null;
  1303. }
  1304. }else{
  1305. node = null;
  1306. }
  1307. }else{
  1308. break;
  1309. }
  1310. }
  1311. }
  1312. //Return what we found, if any.
  1313. var item = null;
  1314. if(node){
  1315. item = self._getItem(node);
  1316. if(item.element.parentNode){
  1317. item.element.parentNode.removeChild(item.element);
  1318. }
  1319. }
  1320. if(keywordArgs.onItem){
  1321. scope = keywordArgs.scope || winUtil.global;
  1322. keywordArgs.onItem.call(scope, item);
  1323. }
  1324. }
  1325. }
  1326. };
  1327. url = this._getFetchUrl(null);
  1328. getArgs = {
  1329. url: url,
  1330. handleAs: "xml",
  1331. preventCache: self.urlPreventCache
  1332. };
  1333. getHandler = xhr.get(getArgs);
  1334. //Add in the callbacks for completion of data load.
  1335. getHandler.addCallback(handleDocument);
  1336. if(keywordArgs.onError){
  1337. getHandler.addErrback(function(error){
  1338. var s = keywordArgs.scope || winUtil.global;
  1339. keywordArgs.onError.call(s, error);
  1340. });
  1341. }
  1342. }else{
  1343. //Server side querying, so need to pass the keyAttribute back to the server and let it return
  1344. //what it will. It SHOULD be only one item.
  1345. if(self.keyAttribute !== ""){
  1346. var request = {query:{}};
  1347. request.query[self.keyAttribute] = keywordArgs.identity;
  1348. url = this._getFetchUrl(request);
  1349. handleDocument = function(data){
  1350. var item = null;
  1351. if(data){
  1352. var items = self._getItems(data, {});
  1353. if(items.length === 1){
  1354. item = items[0];
  1355. }else{
  1356. if(keywordArgs.onError){
  1357. var scope = keywordArgs.scope || winUtil.global;
  1358. keywordArgs.onError.call(scope, new Error("More than one item was returned from the server for the denoted identity"));
  1359. }
  1360. }
  1361. }
  1362. if(keywordArgs.onItem){
  1363. scope = keywordArgs.scope || winUtil.global;
  1364. keywordArgs.onItem.call(scope, item);
  1365. }
  1366. };
  1367. getArgs = {
  1368. url: url,
  1369. handleAs: "xml",
  1370. preventCache: self.urlPreventCache
  1371. };
  1372. getHandler = xhr.get(getArgs);
  1373. //Add in the callbacks for completion of data load.
  1374. getHandler.addCallback(handleDocument);
  1375. if(keywordArgs.onError){
  1376. getHandler.addErrback(function(error){
  1377. var s = keywordArgs.scope || winUtil.global;
  1378. keywordArgs.onError.call(s, error);
  1379. });
  1380. }
  1381. }else{
  1382. if(keywordArgs.onError){
  1383. var s = keywordArgs.scope || winUtil.global;
  1384. keywordArgs.onError.call(s, new Error("XmlStore is not told that the server to provides identity support. No keyAttribute specified."));
  1385. }
  1386. }
  1387. }
  1388. }
  1389. });
  1390. lang.extend(XmlStore,simpleFetch);
  1391. return XmlStore;
  1392. });