XmlStore.js 45 KB

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