CdfStore.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  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.CdfStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.data.CdfStore"] = true;
  8. dojo.provide("dojox.data.CdfStore");
  9. dojo.require("dojo.data.util.sorter");
  10. dojox.data.ASYNC_MODE = 0;
  11. dojox.data.SYNC_MODE = 1;
  12. dojo.declare("dojox.data.CdfStore", null, {
  13. // summary:
  14. // IMPORTANT: The CDF Store is designed to work with Tibco GI, and references Tibco's
  15. // JSX3 JavaScript library and will not work without it.
  16. //
  17. // The CDF Store implements dojo.data.Read, Write, and Identity api's. It is a local
  18. // (in memory) store that handles XML documents formatted according to the
  19. // Common Data Format (CDF) spec:
  20. // http://www.tibco.com/devnet/resources/gi/3_1/tips_and_techniques/CommonDataFormatCDF.pdf
  21. //
  22. // The purpose of this store is to provide a glue between a jsx3 CDF file and a Dijit.
  23. //
  24. // While a CDF document is an XML file, other than the initial input, all data returned
  25. // from and written to this store should be in object format.
  26. //
  27. // identity: [const] String
  28. // The unique identifier for each item. Defaults to "jsxid" which is standard for a CDF
  29. // document. Should not be changed.
  30. identity: "jsxid",
  31. //
  32. // url : String
  33. // The location from which to fetch the XML (CDF) document.
  34. url: "",
  35. //
  36. // xmlStr: String
  37. // A string that can be parsed into an XML document and should be formatted according
  38. // to the CDF spec.
  39. // example:
  40. // | '<data jsxid="jsxroot"><record jsxtext="A"/><record jsxtext="B" jsxid="2" jsxid="2"/></data>'
  41. xmlStr:"",
  42. //
  43. // data: Object
  44. // A object that will be converted into the xmlStr property, and then parsed into a CDF.
  45. data:null,
  46. //
  47. // label: String
  48. // The property within each item used to define the item.
  49. label: "",
  50. //
  51. // mode [const]: dojox.data.ASYNC_MODE | dojox.data.SYNC_MODE
  52. // This store supports syncronous fetches if this property is set to dojox.data.SYNC_MODE.
  53. mode:dojox.data.ASYNC_MODE,
  54. constructor: function(/* Object */ args){
  55. // summary:
  56. // Constructor for the CDF store. Instantiate a new CdfStore.
  57. //
  58. if(args){
  59. this.url = args.url;
  60. this.xmlStr = args.xmlStr || args.str;
  61. if(args.data){
  62. this.xmlStr = this._makeXmlString(args.data);
  63. }
  64. this.identity = args.identity || this.identity;
  65. this.label = args.label || this.label;
  66. this.mode = args.mode !== undefined ? args.mode : this.mode;
  67. }
  68. this._modifiedItems = {};
  69. this.byId = this.fetchItemByIdentity;
  70. },
  71. /* dojo.data.api.Read */
  72. getValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* value? */ defaultValue){
  73. // summary:
  74. // Return an property value of an item
  75. //
  76. return item.getAttribute(property) || defaultValue; // anything
  77. },
  78. getValues: function(/* jsx3.xml.Entity */ item, /* String */ property){
  79. // summary:
  80. // Return an array of values
  81. //
  82. // TODO!!! Can't find an example of an array in any CDF files
  83. //
  84. var v = this.getValue(item, property, []);
  85. return dojo.isArray(v) ? v : [v];
  86. },
  87. getAttributes: function(/* jsx3.xml.Entity */ item){
  88. // summary:
  89. // Return an array of property names
  90. //
  91. return item.getAttributeNames(); // Array
  92. },
  93. hasAttribute: function(/* jsx3.xml.Entity */ item, /* String */ property){
  94. // summary:
  95. // Check whether an item has a property
  96. //
  97. return (this.getValue(item, property) !== undefined); // Boolean
  98. },
  99. hasProperty: function(/* jsx3.xml.Entity */ item, /* String */ property){
  100. // summary:
  101. // Alias for hasAttribute
  102. return this.hasAttribute(item, property);
  103. },
  104. containsValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* anything */ value){
  105. // summary:
  106. // Check whether an item contains a value
  107. //
  108. var values = this.getValues(item, property);
  109. for(var i = 0; i < values.length; i++){
  110. if(values[i] === null){ continue; }
  111. if((typeof value === "string")){
  112. if(values[i].toString && values[i].toString() === value){
  113. return true;
  114. }
  115. }else if(values[i] === value){
  116. return true; //boolean
  117. }
  118. }
  119. return false;//boolean
  120. },
  121. isItem: function(/* anything */ something){
  122. // summary:
  123. // Check whether the object is an item (jsx3.xml.Entity)
  124. //
  125. if(something.getClass && something.getClass().equals(jsx3.xml.Entity.jsxclass)){
  126. return true; //boolean
  127. }
  128. return false; //boolran
  129. },
  130. isItemLoaded: function(/* anything */ something){
  131. // summary:
  132. // Check whether the object is a jsx3.xml.Entity object and loaded
  133. //
  134. return this.isItem(something); // Boolean
  135. },
  136. loadItem: function(/* object */ keywordArgs){
  137. // summary:
  138. // Load an item
  139. // description:
  140. // The store always loads all items, so if it's an item, then it's loaded.
  141. },
  142. getFeatures: function(){
  143. // summary:
  144. // Return supported data APIs
  145. //
  146. return {
  147. "dojo.data.api.Read": true,
  148. "dojo.data.api.Write": true,
  149. "dojo.data.api.Identity":true
  150. }; // Object
  151. },
  152. getLabel: function(/* jsx3.xml.Entity */ item){
  153. // summary:
  154. // See dojo.data.api.Read.getLabel()
  155. //
  156. if((this.label !== "") && this.isItem(item)){
  157. var label = this.getValue(item,this.label);
  158. if(label){
  159. return label.toString();
  160. }
  161. }
  162. return undefined; //undefined
  163. },
  164. getLabelAttributes: function(/* jsx3.xml.Entity */ item){
  165. // summary:
  166. // returns an array of what properties of the item that were used
  167. // to generate its label
  168. // See dojo.data.api.Read.getLabelAttributes()
  169. //
  170. if(this.label !== ""){
  171. return [this.label]; //array
  172. }
  173. return null; //null
  174. },
  175. fetch: function(/* Object? */ request){
  176. // summary:
  177. // Returns an Array of items based on the request arguments.
  178. // description:
  179. // Returns an Array of items based on the request arguments.
  180. // If the store is in ASYNC mode, the items should be expected in an onComplete
  181. // method passed in the request object. If store is in SYNC mode, the items will
  182. // be return directly as well as within the onComplete method.
  183. // note:
  184. // The mode can be set on store initialization or during a fetch as one of the
  185. // parameters.
  186. //
  187. // query: String
  188. // The items in the store are treated as objects, but this is reading an XML
  189. // document. Further, the actual querying of the items takes place in Tibco GI's
  190. // jsx3.xml.Entity. Therefore, we are using their syntax which is xpath.
  191. // Note:
  192. // As conforming to a CDF document, most, if not all nodes are considered "records"
  193. // and their tagNames are as such. The root node is named "data".
  194. //
  195. // examples:
  196. // All items:
  197. // | store.fetch({query:"*"});
  198. // Item with a jsxid attribute equal to "1" (note you could use byId for this)
  199. // | store.fetch({query:"//record[@jsxid='1']"});
  200. // All items with any jsxid attribute:
  201. // | "//record[@jsxid='*']"
  202. // The items with a jsxid of '1' or '4':
  203. // | "//record[@jsxid='4' or @jsxid='1']"
  204. // All children within a "group" node (could be multiple group nodes):
  205. // "//group/record"
  206. // All children within a specific group node:
  207. // "//group[@name='mySecondGroup']/record"
  208. // Any record, anywhere in the document:
  209. // | "//record"
  210. // Only the records beneath the root (data) node:
  211. // | "//data/record"
  212. //
  213. // See:
  214. // http://www.tibco.com/devnet/resources/gi/3_7/api/html/jsx3/xml/Entity.html#method:selectNodes
  215. // http://www.w3.org/TR/xpath
  216. // http://msdn.microsoft.com/en-us/library/ms256086.aspx
  217. //
  218. // See dojo.data.Read.fetch():
  219. // onBegin
  220. // onComplete
  221. // onItem
  222. // onError
  223. // scope
  224. // start
  225. // count
  226. // sort
  227. //
  228. request = request || {};
  229. if(!request.store){
  230. request.store = this;
  231. }
  232. if(request.mode !== undefined){
  233. this.mode = request.mode;
  234. }
  235. var self = this;
  236. var errorHandler = function(errorData){
  237. if(request.onError){
  238. var scope = request.scope || dojo.global;
  239. request.onError.call(scope, errorData, request);
  240. }else{
  241. console.error("cdfStore Error:", errorData);
  242. }
  243. };
  244. var fetchHandler = function(items, requestObject){
  245. requestObject = requestObject || request;
  246. var oldAbortFunction = requestObject.abort || null;
  247. var aborted = false;
  248. var startIndex = requestObject.start?requestObject.start:0;
  249. var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
  250. requestObject.abort = function(){
  251. aborted = true;
  252. if(oldAbortFunction){
  253. oldAbortFunction.call(requestObject);
  254. }
  255. };
  256. var scope = requestObject.scope || dojo.global;
  257. if(!requestObject.store){
  258. requestObject.store = self;
  259. }
  260. if(requestObject.onBegin){
  261. requestObject.onBegin.call(scope, items.length, requestObject);
  262. }
  263. if(requestObject.sort){
  264. items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
  265. }
  266. if(requestObject.onItem){
  267. for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
  268. var item = items[i];
  269. if(!aborted){
  270. requestObject.onItem.call(scope, item, requestObject);
  271. }
  272. }
  273. }
  274. if(requestObject.onComplete && !aborted){
  275. if(!requestObject.onItem){
  276. items = items.slice(startIndex, endIndex);
  277. if(requestObject.byId){
  278. items = items[0];
  279. }
  280. }
  281. requestObject.onComplete.call(scope, items, requestObject);
  282. }else{
  283. items = items.slice(startIndex, endIndex);
  284. if(requestObject.byId){
  285. items = items[0];
  286. }
  287. }
  288. return items;
  289. };
  290. if(!this.url && !this.data && !this.xmlStr){
  291. errorHandler(new Error("No URL or data specified."));
  292. return false;
  293. }
  294. var localRequest = request || "*"; // use request for _getItems()
  295. if(this.mode == dojox.data.SYNC_MODE){
  296. // sync mode. items returned directly
  297. var res = this._loadCDF();
  298. if(res instanceof Error){
  299. if(request.onError){
  300. request.onError.call(request.scope || dojo.global, res, request);
  301. }else{
  302. console.error("CdfStore Error:", res);
  303. }
  304. return res;
  305. }
  306. this.cdfDoc = res;
  307. var items = this._getItems(this.cdfDoc, localRequest);
  308. if(items && items.length > 0){
  309. items = fetchHandler(items, request);
  310. }else{
  311. items = fetchHandler([], request);
  312. }
  313. return items;
  314. }else{
  315. // async mode. Return a Deferred.
  316. var dfd = this._loadCDF();
  317. dfd.addCallbacks(dojo.hitch(this, function(cdfDoc){
  318. var items = this._getItems(this.cdfDoc, localRequest);
  319. if(items && items.length > 0){
  320. fetchHandler(items, request);
  321. }else{
  322. fetchHandler([], request);
  323. }
  324. }),
  325. dojo.hitch(this, function(err){
  326. errorHandler(err, request);
  327. }));
  328. return dfd; // Object
  329. }
  330. },
  331. _loadCDF: function(){
  332. // summary:
  333. // Internal method.
  334. // If a cdfDoc exists, return it. Otherwise, get one from JSX3,
  335. // load the data or url, and return the doc or a deferred.
  336. var dfd = new dojo.Deferred();
  337. if(this.cdfDoc){
  338. if(this.mode == dojox.data.SYNC_MODE){
  339. return this.cdfDoc; // jsx3.xml.CDF
  340. }else{
  341. setTimeout(dojo.hitch(this, function(){
  342. dfd.callback(this.cdfDoc);
  343. }), 0);
  344. return dfd; // dojo.Deferred
  345. }
  346. }
  347. this.cdfDoc = jsx3.xml.CDF.Document.newDocument();
  348. this.cdfDoc.subscribe("response", this, function(evt){
  349. dfd.callback(this.cdfDoc);
  350. });
  351. this.cdfDoc.subscribe("error", this, function(err){
  352. dfd.errback(err);
  353. });
  354. this.cdfDoc.setAsync(!this.mode);
  355. if(this.url){
  356. this.cdfDoc.load(this.url);
  357. }else if(this.xmlStr){
  358. this.cdfDoc.loadXML(this.xmlStr);
  359. if(this.cdfDoc.getError().code){
  360. return new Error(this.cdfDoc.getError().description); // Error
  361. }
  362. }
  363. if(this.mode == dojox.data.SYNC_MODE){
  364. return this.cdfDoc; // jsx3.xml.CDF
  365. }else{
  366. return dfd; // dojo.Deferred
  367. }
  368. },
  369. _getItems: function(/* jsx3.xml.Entity */cdfDoc, /* Object */request){
  370. // summary:
  371. // Internal method.
  372. // Requests the items from jsx3.xml.Entity with an xpath query.
  373. //
  374. var itr = cdfDoc.selectNodes(request.query, false, 1);
  375. var items = [];
  376. while(itr.hasNext()){
  377. items.push(itr.next());
  378. }
  379. return items;
  380. },
  381. close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
  382. // summary:
  383. // See dojo.data.api.Read.close()
  384. },
  385. /* dojo.data.api.Write */
  386. newItem: function(/* object? */ keywordArgs, /* object? || String? */parentInfo){
  387. // summary:
  388. // Creates a jsx3.xml.Entity item and inserts it either inside the
  389. // parent or appends it to the root
  390. //
  391. keywordArgs = (keywordArgs || {});
  392. if(keywordArgs.tagName){
  393. // record tagName is automatic and this would add it
  394. // as a property
  395. if(keywordArgs.tagName!="record"){
  396. // TODO: How about some sort of group?
  397. console.warn("Only record inserts are supported at this time");
  398. }
  399. delete keywordArgs.tagName;
  400. }
  401. keywordArgs.jsxid = keywordArgs.jsxid || this.cdfDoc.getKey();
  402. if(this.isItem(parentInfo)){
  403. parentInfo = this.getIdentity(parentInfo);
  404. }
  405. var item = this.cdfDoc.insertRecord(keywordArgs, parentInfo);
  406. this._makeDirty(item);
  407. return item; // jsx3.xml.Entity
  408. },
  409. deleteItem: function(/* jsx3.xml.Entity */ item){
  410. // summary:
  411. // Delete an jsx3.xml.Entity (wrapper to a XML element).
  412. //
  413. this.cdfDoc.deleteRecord(this.getIdentity(item));
  414. this._makeDirty(item);
  415. return true; //boolean
  416. },
  417. setValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* almost anything */ value){
  418. // summary:
  419. // Set an property value
  420. //
  421. this._makeDirty(item);
  422. item.setAttribute(property, value);
  423. return true; // Boolean
  424. },
  425. setValues: function(/* jsx3.xml.Entity */ item, /* String */ property, /*array*/ values){
  426. // summary:
  427. // Set property values
  428. // TODO: Needs to be fully implemented.
  429. //
  430. this._makeDirty(item);
  431. console.warn("cdfStore.setValues only partially implemented.");
  432. return item.setAttribute(property, values);
  433. },
  434. unsetAttribute: function(/* jsx3.xml.Entity */ item, /* String */ property){
  435. // summary:
  436. // Remove an property
  437. //
  438. this._makeDirty(item);
  439. item.removeAttribute(property);
  440. return true; // Boolean
  441. },
  442. revert: function(){
  443. // summary:
  444. // Invalidate changes (new and/or modified elements)
  445. // Resets data by simply deleting the reference to the cdfDoc.
  446. // Subsequent fetches will load the new data.
  447. // Note:
  448. // Any items outside the store will no longer be valid and may cause errors.
  449. //
  450. delete this.cdfDoc;
  451. this._modifiedItems = {};
  452. return true; //boolean
  453. },
  454. isDirty: function(/* jsx3.xml.Entity ? */ item){
  455. // summary:
  456. // Check whether an item is new, modified or deleted.
  457. // If no item is passed, checks if anything in the store has changed.
  458. //
  459. if(item){
  460. return !!this._modifiedItems[this.getIdentity(item)]; // Boolean
  461. }else{
  462. var _dirty = false;
  463. for(var nm in this._modifiedItems){ _dirty = true; break; }
  464. return _dirty; // Boolean
  465. }
  466. },
  467. /* internal API */
  468. _makeDirty: function(item){
  469. // summary:
  470. // Internal method.
  471. // Marks items as modified, deleted or new.
  472. var id = this.getIdentity(item);
  473. this._modifiedItems[id] = item;
  474. },
  475. _makeXmlString: function(obj){
  476. // summary:
  477. // Internal method.
  478. // Converts an object into an XML string.
  479. //
  480. var parseObj = function(obj, name){
  481. var xmlStr = "";
  482. var nm;
  483. if(dojo.isArray(obj)){
  484. for(var i=0;i<obj.length;i++){
  485. xmlStr += parseObj(obj[i], name);
  486. }
  487. }else if(dojo.isObject(obj)){
  488. xmlStr += '<'+name+' ';
  489. for(nm in obj){
  490. if(!dojo.isObject(obj[nm])){
  491. xmlStr += nm+'="'+obj[nm]+'" ';
  492. }
  493. }
  494. xmlStr +='>';
  495. for(nm in obj){
  496. if(dojo.isObject(obj[nm])){
  497. xmlStr += parseObj(obj[nm], nm);
  498. }
  499. }
  500. xmlStr += '</'+name+'>';
  501. }
  502. return xmlStr;
  503. };
  504. return parseObj(obj, "data");
  505. },
  506. /*************************************
  507. * Dojo.data Identity implementation *
  508. *************************************/
  509. getIdentity: function(/* jsx3.xml.Entity */ item){
  510. // summary:
  511. // Returns the identifier for an item.
  512. //
  513. return this.getValue(item, this.identity); // String
  514. },
  515. getIdentityAttributes: function(/* jsx3.xml.Entity */ item){
  516. // summary:
  517. // Returns the property used for the identity.
  518. //
  519. return [this.identity]; // Array
  520. },
  521. fetchItemByIdentity: function(/* Object || String */ args){
  522. // summary:
  523. // See dojo.data.api.Identity.fetchItemByIdentity(keywordArgs)
  524. //
  525. // Note:
  526. // This method can be synchronous if mode is set.
  527. // Also, there is a more finger friendly alias of this method, byId();
  528. if(dojo.isString(args)){
  529. var id = args;
  530. args = {query:"//record[@jsxid='"+id+"']", mode: dojox.data.SYNC_MODE};
  531. }else{
  532. if(args){
  533. args.query = "//record[@jsxid='"+args.identity+"']";
  534. }
  535. if(!args.mode){args.mode = this.mode;}
  536. }
  537. args.byId = true;
  538. return this.fetch(args); // dojo.Deferred || Array
  539. },
  540. byId: function(/* Object || String */ args){
  541. // stub. See fetchItemByIdentity
  542. }
  543. });
  544. }