CdfStore.js 16 KB

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