2c5db34760ca2e9cc10a9aa184f2f7008c4d0b18.svn-base 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. if (!dojo._hasResource['dojox.grid._data.model']) { // _hasResource checks added
  2. // by build. Do not use
  3. // _hasResource directly in
  4. // your code.
  5. dojo._hasResource['dojox.grid._data.model'] = true;
  6. dojo.provide('dojox.grid._data.model');
  7. dojo.require('dojox.grid._data.fields');
  8. dojo.declare("dojox.grid.data.Model", null, {
  9. // summary:
  10. // Base abstract grid data model.
  11. // Makes no assumptions about the structure of grid data.
  12. constructor : function(inFields, inData) {
  13. this.observers = [];
  14. this.fields = new dojox.grid.data.Fields();
  15. if (inFields) {
  16. this.fields.set(inFields);
  17. }
  18. this.setData(inData);
  19. },
  20. count : 0,
  21. updating : 0,
  22. // observers
  23. observer : function(inObserver, inPrefix) {
  24. this.observers.push({
  25. o : inObserver,
  26. p : inPrefix || 'model'
  27. });
  28. },
  29. notObserver : function(inObserver) {
  30. for (var i = 0, m, o; (o = this.observers[i]); i++) {
  31. if (o.o == inObserver) {
  32. this.observers.splice(i, 1);
  33. return;
  34. }
  35. }
  36. },
  37. notify : function(inMsg, inArgs) {
  38. if (!this.isUpdating()) {
  39. var a = inArgs || [];
  40. for (var i = 0, m, o; (o = this.observers[i]); i++) {
  41. m = o.p + inMsg, o = o.o;
  42. (m in o) && (o[m].apply(o, a));
  43. }
  44. }
  45. },
  46. // updates
  47. clear : function() {
  48. this.fields.clear();
  49. this.clearData();
  50. },
  51. beginUpdate : function() {
  52. this.updating++;
  53. },
  54. endUpdate : function() {
  55. if (this.updating) {
  56. this.updating--;
  57. }
  58. /*
  59. * if(this.updating){ if(!(--this.updating)){ this.change(); } } }
  60. */
  61. },
  62. isUpdating : function() {
  63. return Boolean(this.updating);
  64. },
  65. // data
  66. clearData : function() {
  67. this.setData(null);
  68. },
  69. // observer events
  70. change : function() {
  71. this.notify("Change", arguments);
  72. },
  73. insertion : function(/* index */) {
  74. this.notify("Insertion", arguments);
  75. this.notify("Change", arguments);
  76. },
  77. removal : function(/* keys */) {
  78. this.notify("Removal", arguments);
  79. this.notify("Change", arguments);
  80. },
  81. // insert
  82. insert : function(inData /* , index */) {
  83. if (!this._insert.apply(this, arguments)) {
  84. return false;
  85. }
  86. this.insertion.apply(this, dojo._toArray(arguments, 1));
  87. return true;
  88. },
  89. // remove
  90. remove : function(inData /* , index */) {
  91. if (!this._remove.apply(this, arguments)) {
  92. return false;
  93. }
  94. this.removal.apply(this, arguments);
  95. return true;
  96. },
  97. // sort
  98. canSort : function(/* (+|-)column_index+1, ... */) {
  99. return this.sort != null;
  100. },
  101. makeComparator : function(inIndices) {
  102. var idx, col, field, result = null;
  103. for (var i = inIndices.length - 1; i >= 0; i--) {
  104. idx = inIndices[i];
  105. col = Math.abs(idx) - 1;
  106. if (col >= 0) {
  107. field = this.fields.get(col);
  108. result = this.generateComparator(field.compare,
  109. field.key, idx > 0, result);
  110. }
  111. }
  112. return result;
  113. },
  114. sort : null,
  115. dummy : 0
  116. });
  117. dojo.declare("dojox.grid.data.Rows", dojox.grid.data.Model, {
  118. // observer events
  119. allChange : function() {
  120. this.notify("AllChange", arguments);
  121. this.notify("Change", arguments);
  122. },
  123. rowChange : function() {
  124. this.notify("RowChange", arguments);
  125. },
  126. datumChange : function() {
  127. this.notify("DatumChange", arguments);
  128. },
  129. // copyRow: function(inRowIndex); // abstract
  130. // update
  131. beginModifyRow : function(inRowIndex) {
  132. if (!this.cache[inRowIndex]) {
  133. this.cache[inRowIndex] = this.copyRow(inRowIndex);
  134. }
  135. },
  136. endModifyRow : function(inRowIndex) {
  137. var cache = this.cache[inRowIndex];
  138. if (cache) {
  139. var data = this.getRow(inRowIndex);
  140. if (!dojox.grid.arrayCompare(cache, data)) {
  141. this.update(cache, data, inRowIndex);
  142. }
  143. delete this.cache[inRowIndex];
  144. }
  145. },
  146. cancelModifyRow : function(inRowIndex) {
  147. var cache = this.cache[inRowIndex];
  148. if (cache) {
  149. this.setRow(cache, inRowIndex);
  150. delete this.cache[inRowIndex];
  151. }
  152. },
  153. generateComparator : function(inCompare, inField,
  154. inTrueForAscend, inSubCompare) {
  155. return function(a, b) {
  156. var ineq = inCompare(a[inField], b[inField]);
  157. return ineq
  158. ? (inTrueForAscend ? ineq : -ineq)
  159. : inSubCompare && inSubCompare(a, b);
  160. }
  161. }
  162. });
  163. dojo.declare("dojox.grid.data.Table", dojox.grid.data.Rows, {
  164. // summary:
  165. // Basic grid data model for static data in the form of an array of rows
  166. // that are arrays of cell data
  167. constructor : function() {
  168. this.cache = [];
  169. },
  170. colCount : 0, // tables introduce cols
  171. data : null,
  172. cache : null,
  173. // morphology
  174. measure : function() {
  175. this.count = this.getRowCount();
  176. this.colCount = this.getColCount();
  177. this.allChange();
  178. // this.notify("Measure");
  179. },
  180. getRowCount : function() {
  181. return (this.data ? this.data.length : 0);
  182. },
  183. getColCount : function() {
  184. return (this.data && this.data.length
  185. ? this.data[0].length
  186. : this.fields.count());
  187. },
  188. badIndex : function(inCaller, inDescriptor) {
  189. console.debug('dojox.grid.data.Table: badIndex');
  190. },
  191. isGoodIndex : function(inRowIndex, inColIndex) {
  192. return (inRowIndex >= 0 && inRowIndex < this.count && (arguments.length < 2 || (inColIndex >= 0 && inColIndex < this.colCount)));
  193. },
  194. // access
  195. getRow : function(inRowIndex) {
  196. return this.data[inRowIndex];
  197. },
  198. copyRow : function(inRowIndex) {
  199. return this.getRow(inRowIndex).slice(0);
  200. },
  201. getDatum : function(inRowIndex, inColIndex) {
  202. return this.data[inRowIndex][inColIndex];
  203. },
  204. get : function() {
  205. throw ('Plain "get" no longer supported. Use "getRow" or "getDatum".');
  206. },
  207. setData : function(inData) {
  208. this.data = (inData || []);
  209. this.allChange();
  210. },
  211. setRow : function(inData, inRowIndex) {
  212. this.data[inRowIndex] = inData;
  213. this.rowChange(inData, inRowIndex);
  214. this.change();
  215. },
  216. setDatum : function(inDatum, inRowIndex, inColIndex) {
  217. this.data[inRowIndex][inColIndex] = inDatum;
  218. this.datumChange(inDatum, inRowIndex, inColIndex);
  219. },
  220. set : function() {
  221. throw ('Plain "set" no longer supported. Use "setData", "setRow", or "setDatum".');
  222. },
  223. setRows : function(inData, inRowIndex) {
  224. for (var i = 0, l = inData.length, r = inRowIndex; i < l; i++, r++) {
  225. this.setRow(inData[i], r);
  226. }
  227. },
  228. // update
  229. update : function(inOldData, inNewData, inRowIndex) {
  230. // delete this.cache[inRowIndex];
  231. // this.setRow(inNewData, inRowIndex);
  232. return true;
  233. },
  234. // insert
  235. _insert : function(inData, inRowIndex) {
  236. dojox.grid.arrayInsert(this.data, inRowIndex, inData);
  237. this.count++;
  238. return true;
  239. },
  240. // remove
  241. _remove : function(inKeys) {
  242. for (var i = inKeys.length - 1; i >= 0; i--) {
  243. dojox.grid.arrayRemove(this.data, inKeys[i]);
  244. }
  245. this.count -= inKeys.length;
  246. return true;
  247. },
  248. // sort
  249. sort : function(/* (+|-)column_index+1, ... */) {
  250. this.data.sort(this.makeComparator(arguments));
  251. },
  252. swap : function(inIndexA, inIndexB) {
  253. dojox.grid.arraySwap(this.data, inIndexA, inIndexB);
  254. this.rowChange(this.getRow(inIndexA), inIndexA);
  255. this.rowChange(this.getRow(inIndexB), inIndexB);
  256. this.change();
  257. },
  258. dummy : 0
  259. });
  260. dojo.declare("dojox.grid.data.Objects", dojox.grid.data.Table, {
  261. constructor : function(inFields, inData, inKey) {
  262. if (!inFields) {
  263. this.autoAssignFields();
  264. }
  265. },
  266. autoAssignFields : function() {
  267. var d = this.data[0], i = 0;
  268. for (var f in d) {
  269. this.fields.get(i++).key = f;
  270. }
  271. },
  272. getDatum : function(inRowIndex, inColIndex) {
  273. return this.data[inRowIndex][this.fields.get(inColIndex).key];
  274. }
  275. });
  276. dojo.declare("dojox.grid.data.Dynamic", dojox.grid.data.Table, {
  277. // summary:
  278. // Grid data model for dynamic data such as data retrieved from a
  279. // server.
  280. // Retrieves data automatically when requested and provides notification
  281. // when data is received
  282. constructor : function() {
  283. this.page = [];
  284. this.pages = [];
  285. },
  286. page : null,
  287. pages : null,
  288. rowsPerPage : 100,
  289. requests : 0,
  290. bop : -1,
  291. eop : -1,
  292. // data
  293. clearData : function() {
  294. this.pages = [];
  295. this.bop = this.eop = -1;
  296. this.setData([]);
  297. },
  298. getRowCount : function() {
  299. return this.count;
  300. },
  301. getColCount : function() {
  302. return this.fields.count();
  303. },
  304. setRowCount : function(inCount) {
  305. this.count = inCount;
  306. this.change();
  307. },
  308. // paging
  309. requestsPending : function(inBoolean) {
  310. },
  311. rowToPage : function(inRowIndex) {
  312. return (this.rowsPerPage ? Math
  313. .floor(inRowIndex / this.rowsPerPage) : inRowIndex);
  314. },
  315. pageToRow : function(inPageIndex) {
  316. return (this.rowsPerPage
  317. ? this.rowsPerPage * inPageIndex
  318. : inPageIndex);
  319. },
  320. requestRows : function(inRowIndex, inCount) {
  321. // summary:
  322. // stub. Fill in to perform actual data row fetching logic. The
  323. // returning logic must provide the data back to the system via
  324. // setRow
  325. },
  326. rowsProvided : function(inRowIndex, inCount) {
  327. this.requests--;
  328. if (this.requests == 0) {
  329. this.requestsPending(false);
  330. }
  331. },
  332. requestPage : function(inPageIndex) {
  333. var row = this.pageToRow(inPageIndex);
  334. var count = Math.min(this.rowsPerPage, this.count - row);
  335. if (count > 0) {
  336. this.requests++;
  337. this.requestsPending(true);
  338. setTimeout(dojo.hitch(this, "requestRows", row, count), 1);
  339. // this.requestRows(row, count);
  340. }
  341. },
  342. needPage : function(inPageIndex) {
  343. if (!this.pages[inPageIndex]) {
  344. this.pages[inPageIndex] = true;
  345. this.requestPage(inPageIndex);
  346. }
  347. },
  348. preparePage : function(inRowIndex, inColIndex) {
  349. if (inRowIndex < this.bop || inRowIndex >= this.eop) {
  350. var pageIndex = this.rowToPage(inRowIndex);
  351. this.needPage(pageIndex);
  352. this.bop = pageIndex * this.rowsPerPage;
  353. this.eop = this.bop + (this.rowsPerPage || this.count);
  354. }
  355. },
  356. isRowLoaded : function(inRowIndex) {
  357. return Boolean(this.data[inRowIndex]);
  358. },
  359. // removal
  360. removePages : function(inRowIndexes) {
  361. for (var i = 0, r; ((r = inRowIndexes[i]) != undefined); i++) {
  362. this.pages[this.rowToPage(r)] = false;
  363. }
  364. this.bop = this.eop = -1;
  365. },
  366. remove : function(inRowIndexes) {
  367. this.removePages(inRowIndexes);
  368. dojox.grid.data.Table.prototype.remove.apply(this, arguments);
  369. },
  370. // access
  371. getRow : function(inRowIndex) {
  372. var row = this.data[inRowIndex];
  373. if (!row) {
  374. this.preparePage(inRowIndex);
  375. }
  376. return row;
  377. },
  378. getDatum : function(inRowIndex, inColIndex) {
  379. var row = this.getRow(inRowIndex);
  380. return (row ? row[inColIndex] : this.fields.get(inColIndex).na);
  381. },
  382. setDatum : function(inDatum, inRowIndex, inColIndex) {
  383. var row = this.getRow(inRowIndex);
  384. if (row) {
  385. row[inColIndex] = inDatum;
  386. this.datumChange(inDatum, inRowIndex, inColIndex);
  387. } else {
  388. console
  389. .debug('['
  390. + this.declaredClass
  391. + '] dojox.grid.data.dynamic.set: cannot set data on an non-loaded row');
  392. }
  393. },
  394. // sort
  395. canSort : function() {
  396. return false;
  397. }
  398. });
  399. // FIXME: deprecated: (included for backward compatibility only)
  400. dojox.grid.data.table = dojox.grid.data.Table;
  401. dojox.grid.data.dynamic = dojox.grid.data.Dyanamic;
  402. // we treat dojo.data stores as dynamic stores because no matter how they
  403. // got
  404. // here, they should always fill that contract
  405. dojo.declare("dojox.grid.data.DojoData", dojox.grid.data.Dynamic, {
  406. // summary:
  407. // A grid data model for dynamic data retreived from a store which
  408. // implements the dojo.data API set. Retrieves data automatically when
  409. // requested and provides notification when data is received
  410. // description:
  411. // This store subclasses the Dynamic grid data object in order to
  412. // provide paginated data access support, notification and view
  413. // updates for stores which support those features, and simple
  414. // field/column mapping for all dojo.data stores.
  415. constructor : function(inFields, inData, args) {
  416. this.count = 1;
  417. this._rowIdentities = {};
  418. if (args) {
  419. dojo.mixin(this, args);
  420. }
  421. if (this.store) {
  422. // NOTE: we assume Read and Identity APIs for all stores!
  423. var f = this.store.getFeatures();
  424. this._canNotify = f['dojo.data.api.Notification'];
  425. this._canWrite = f['dojo.data.api.Write'];
  426. if (this._canNotify) {
  427. dojo
  428. .connect(this.store, "onSet", this,
  429. "_storeDatumChange");
  430. }
  431. }
  432. },
  433. markupFactory : function(args, node) {
  434. return new dojox.grid.data.DojoData(null, null, args);
  435. },
  436. query : {
  437. name : "*"
  438. }, // default, stupid query
  439. store : null,
  440. _canNotify : false,
  441. _canWrite : false,
  442. _rowIdentities : {},
  443. clientSort : false,
  444. // data
  445. setData : function(inData) {
  446. this.store = inData;
  447. this.data = [];
  448. this.allChange();
  449. },
  450. setRowCount : function(inCount) {
  451. // console.debug("inCount:", inCount);
  452. this.count = inCount;
  453. this.allChange();
  454. },
  455. beginReturn : function(inCount) {
  456. if (this.count != inCount) {
  457. // this.setRowCount(0);
  458. // this.clear();
  459. // console.debug(this.count, inCount);
  460. this.setRowCount(inCount);
  461. }
  462. },
  463. _setupFields : function(dataItem) {
  464. // abort if we already have setup fields
  465. if (this.fields._nameMaps) {
  466. return;
  467. }
  468. // set up field/index mappings
  469. var m = {};
  470. // console.debug("setting up fields", m);
  471. var fields = dojo.map(this.store.getAttributes(dataItem), function(
  472. item, idx) {
  473. m[item] = idx;
  474. m[idx + ".idx"] = item;
  475. // name == display name, key = property name
  476. return {
  477. name : item,
  478. key : item
  479. };
  480. }, this);
  481. this.fields._nameMaps = m;
  482. // console.debug("new fields:", fields);
  483. this.fields.set(fields);
  484. this.notify("FieldsChange");
  485. },
  486. _getRowFromItem : function(item) {
  487. // gets us the row object (and row index) of an item
  488. },
  489. processRows : function(items, store) {
  490. // console.debug(arguments);
  491. if (!items) {
  492. return;
  493. }
  494. this._setupFields(items[0]);
  495. dojo.forEach(items, function(item, idx) {
  496. var row = {};
  497. row.__dojo_data_item = item;
  498. dojo.forEach(this.fields.values, function(a) {
  499. row[a.name] = this.store.getValue(item,
  500. a.name)
  501. || "";
  502. }, this);
  503. // FIXME: where else do we need to keep this in sync?
  504. this._rowIdentities[this.store.getIdentity(item)] = store.start
  505. + idx;
  506. this.setRow(row, store.start + idx);
  507. }, this);
  508. // FIXME:
  509. // Q: scott, steve, how the hell do we actually get this to update
  510. // the visible UI for these rows?
  511. // A: the goal is that Grid automatically updates to reflect changes
  512. // in model. In this case, setRow -> rowChanged -> (observed by)
  513. // Grid -> modelRowChange -> updateRow
  514. },
  515. // request data
  516. requestRows : function(inRowIndex, inCount) {
  517. var row = inRowIndex || 0;
  518. var params = {
  519. start : row,
  520. count : this.rowsPerPage,
  521. query : this.query,
  522. onBegin : dojo.hitch(this, "beginReturn"),
  523. // onItem: dojo.hitch(console, "debug"),
  524. onComplete : dojo.hitch(this, "processRows")
  525. // add to deferred?
  526. }
  527. // console.debug("requestRows:", row, this.rowsPerPage);
  528. this.store.fetch(params);
  529. },
  530. getDatum : function(inRowIndex, inColIndex) {
  531. // console.debug("getDatum", inRowIndex, inColIndex);
  532. var row = this.getRow(inRowIndex);
  533. var field = this.fields.values[inColIndex];
  534. return row && field ? row[field.name] : field ? field.na : '?';
  535. // var idx = row && this.fields._nameMaps[inColIndex+".idx"];
  536. // return (row ? row[idx] : this.fields.get(inColIndex).na);
  537. },
  538. setDatum : function(inDatum, inRowIndex, inColIndex) {
  539. var n = this.fields._nameMaps[inColIndex + ".idx"];
  540. // console.debug("setDatum:", "n:"+n, inDatum, inRowIndex,
  541. // inColIndex);
  542. if (n) {
  543. this.data[inRowIndex][n] = inDatum;
  544. this.datumChange(inDatum, inRowIndex, inColIndex);
  545. }
  546. },
  547. // modification, update and store eventing
  548. copyRow : function(inRowIndex) {
  549. var row = {};
  550. var backstop = {};
  551. var src = this.getRow(inRowIndex);
  552. for (var x in src) {
  553. if (src[x] != backstop[x]) {
  554. row[x] = src[x];
  555. }
  556. }
  557. return row;
  558. },
  559. _attrCompare : function(cache, data) {
  560. dojo.forEach(this.fields.values, function(a) {
  561. if (cache[a.name] != data[a.name]) {
  562. return false;
  563. }
  564. }, this);
  565. return true;
  566. },
  567. endModifyRow : function(inRowIndex) {
  568. var cache = this.cache[inRowIndex];
  569. if (cache) {
  570. var data = this.getRow(inRowIndex);
  571. if (!this._attrCompare(cache, data)) {
  572. this.update(cache, data, inRowIndex);
  573. }
  574. delete this.cache[inRowIndex];
  575. }
  576. },
  577. cancelModifyRow : function(inRowIndex) {
  578. // console.debug("cancelModifyRow", arguments);
  579. var cache = this.cache[inRowIndex];
  580. if (cache) {
  581. this.setRow(cache, inRowIndex);
  582. delete this.cache[inRowIndex];
  583. }
  584. },
  585. _storeDatumChange : function(item, attr, oldVal, newVal) {
  586. // the store has changed some data under us, need to update the
  587. // display
  588. var rowId = this._rowIdentities[this.store.getIdentity(item)];
  589. var row = this.getRow(rowId);
  590. row[attr] = newVal;
  591. var colId = this.fields._nameMaps[attr];
  592. this.notify("DatumChange", [newVal, rowId, colId]);
  593. },
  594. datumChange : function(value, rowIdx, colIdx) {
  595. if (this._canWrite) {
  596. // we're chaning some data, which means we need to write back
  597. var row = this.getRow(rowIdx);
  598. var field = this.fields._nameMaps[colIdx + ".idx"];
  599. this.store.setValue(row.__dojo_data_item, field, value);
  600. // we don't need to call DatumChange, an eventing store will
  601. // tell
  602. // us about the row change events
  603. } else {
  604. // we can't write back, so just go ahead and change our local
  605. // copy
  606. // of the data
  607. this.notify("DatumChange", arguments);
  608. }
  609. },
  610. insertion : function(/* index */) {
  611. console.debug("Insertion", arguments);
  612. this.notify("Insertion", arguments);
  613. this.notify("Change", arguments);
  614. },
  615. removal : function(/* keys */) {
  616. console.debug("Removal", arguments);
  617. this.notify("Removal", arguments);
  618. this.notify("Change", arguments);
  619. },
  620. // sort
  621. canSort : function() {
  622. // Q: Return true and re-issue the queries?
  623. // A: Return true only. Re-issue the query in 'sort'.
  624. return this.clientSort;
  625. }
  626. });
  627. }