481f712de088e57c1ed940e268235ccd5dd74d4a.svn-base 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. if (!dojo._hasResource['dojox.grid._grid.scroller']) { // _hasResource checks
  2. // added by build. Do
  3. // not use _hasResource
  4. // directly in your
  5. // code.
  6. dojo._hasResource['dojox.grid._grid.scroller'] = true;
  7. dojo.provide('dojox.grid._grid.scroller');
  8. dojo.declare('dojox.grid.scroller.base', null, {
  9. // summary:
  10. // virtual scrollbox, abstract class
  11. // Content must in /rows/
  12. // Rows are managed in contiguous sets called /pages/
  13. // There are a fixed # of rows per page
  14. // The minimum rendered unit is a page
  15. constructor : function() {
  16. this.pageHeights = [];
  17. this.stack = [];
  18. },
  19. // specified
  20. rowCount : 0, // total number of rows to manage
  21. defaultRowHeight : 10, // default height of a row
  22. keepRows : 100, // maximum number of rows that should exist at one time
  23. contentNode : null, // node to contain pages
  24. scrollboxNode : null, // node that controls scrolling
  25. // calculated
  26. defaultPageHeight : 0, // default height of a page
  27. keepPages : 10, // maximum number of pages that should exists at one
  28. // time
  29. pageCount : 0,
  30. windowHeight : 0,
  31. firstVisibleRow : 0,
  32. lastVisibleRow : 0,
  33. // private
  34. page : 0,
  35. pageTop : 0,
  36. // init
  37. init : function(inRowCount, inKeepRows, inRowsPerPage) {
  38. switch (arguments.length) {
  39. case 3 :
  40. this.rowsPerPage = inRowsPerPage;
  41. case 2 :
  42. this.keepRows = inKeepRows;
  43. case 1 :
  44. this.rowCount = inRowCount;
  45. }
  46. this.defaultPageHeight = this.defaultRowHeight * this.rowsPerPage;
  47. // this.defaultPageHeight = this.defaultRowHeight *
  48. // Math.min(this.rowsPerPage, this.rowCount);
  49. this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage);
  50. this.keepPages = Math.max(Math.ceil(this.keepRows
  51. / this.rowsPerPage), 2);
  52. this.invalidate();
  53. if (this.scrollboxNode) {
  54. this.scrollboxNode.scrollTop = 0;
  55. this.scroll(0);
  56. this.scrollboxNode.onscroll = dojo.hitch(this, 'onscroll');
  57. }
  58. },
  59. // updating
  60. invalidate : function() {
  61. this.invalidateNodes();
  62. this.pageHeights = [];
  63. this.height = (this.pageCount ? (this.pageCount - 1)
  64. * this.defaultPageHeight + this.calcLastPageHeight() : 0);
  65. this.resize();
  66. },
  67. updateRowCount : function(inRowCount) {
  68. this.invalidateNodes();
  69. this.rowCount = inRowCount;
  70. // update page count, adjust document height
  71. oldPageCount = this.pageCount;
  72. this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage);
  73. if (this.pageCount < oldPageCount) {
  74. for (var i = oldPageCount - 1; i >= this.pageCount; i--) {
  75. this.height -= this.getPageHeight(i);
  76. delete this.pageHeights[i]
  77. }
  78. } else if (this.pageCount > oldPageCount) {
  79. this.height += this.defaultPageHeight
  80. * (this.pageCount - oldPageCount - 1)
  81. + this.calcLastPageHeight();
  82. }
  83. this.resize();
  84. },
  85. // abstract interface
  86. pageExists : function(inPageIndex) {
  87. },
  88. measurePage : function(inPageIndex) {
  89. },
  90. positionPage : function(inPageIndex, inPos) {
  91. },
  92. repositionPages : function(inPageIndex) {
  93. },
  94. installPage : function(inPageIndex) {
  95. },
  96. preparePage : function(inPageIndex, inPos, inReuseNode) {
  97. },
  98. renderPage : function(inPageIndex) {
  99. },
  100. removePage : function(inPageIndex) {
  101. },
  102. pacify : function(inShouldPacify) {
  103. },
  104. // pacification
  105. pacifying : false,
  106. pacifyTicks : 200,
  107. setPacifying : function(inPacifying) {
  108. if (this.pacifying != inPacifying) {
  109. this.pacifying = inPacifying;
  110. this.pacify(this.pacifying);
  111. }
  112. },
  113. startPacify : function() {
  114. this.startPacifyTicks = allGetServerTime().getTime();
  115. },
  116. doPacify : function() {
  117. var result = (allGetServerTime().getTime() - this.startPacifyTicks) > this.pacifyTicks;
  118. this.setPacifying(true);
  119. this.startPacify();
  120. return result;
  121. },
  122. endPacify : function() {
  123. this.setPacifying(false);
  124. },
  125. // default sizing implementation
  126. resize : function() {
  127. if (this.scrollboxNode) {
  128. this.windowHeight = this.scrollboxNode.clientHeight;
  129. }
  130. dojox.grid.setStyleHeightPx(this.contentNode, this.height);
  131. },
  132. calcLastPageHeight : function() {
  133. if (!this.pageCount) {
  134. return 0;
  135. }
  136. var lastPage = this.pageCount - 1;
  137. var lastPageHeight = ((this.rowCount % this.rowsPerPage) || (this.rowsPerPage))
  138. * this.defaultRowHeight;
  139. this.pageHeights[lastPage] = lastPageHeight;
  140. return lastPageHeight;
  141. },
  142. updateContentHeight : function(inDh) {
  143. this.height += inDh;
  144. this.resize();
  145. },
  146. updatePageHeight : function(inPageIndex) {
  147. if (this.pageExists(inPageIndex)) {
  148. var oh = this.getPageHeight(inPageIndex);
  149. var h = (this.measurePage(inPageIndex)) || (oh);
  150. this.pageHeights[inPageIndex] = h;
  151. if ((h) && (oh != h)) {
  152. this.updateContentHeight(h - oh)
  153. this.repositionPages(inPageIndex);
  154. }
  155. }
  156. },
  157. rowHeightChanged : function(inRowIndex) {
  158. this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage));
  159. },
  160. // scroller core
  161. invalidateNodes : function() {
  162. while (this.stack.length) {
  163. this.destroyPage(this.popPage());
  164. }
  165. },
  166. createPageNode : function() {
  167. var p = document.createElement('div');
  168. p.style.position = 'absolute';
  169. // p.style.width = '100%';
  170. p.style.left = '0';
  171. return p;
  172. },
  173. getPageHeight : function(inPageIndex) {
  174. var ph = this.pageHeights[inPageIndex];
  175. return (ph !== undefined ? ph : this.defaultPageHeight);
  176. },
  177. // FIXME: this is not a stack, it's a FIFO list
  178. pushPage : function(inPageIndex) {
  179. return this.stack.push(inPageIndex);
  180. },
  181. popPage : function() {
  182. return this.stack.shift();
  183. },
  184. findPage : function(inTop) {
  185. var i = 0, h = 0;
  186. for (var ph = 0; i < this.pageCount; i++, h += ph) {
  187. ph = this.getPageHeight(i);
  188. if (h + ph >= inTop) {
  189. break;
  190. }
  191. }
  192. this.page = i;
  193. this.pageTop = h;
  194. },
  195. buildPage : function(inPageIndex, inReuseNode, inPos) {
  196. this.preparePage(inPageIndex, inReuseNode);
  197. this.positionPage(inPageIndex, inPos);
  198. // order of operations is key below
  199. this.installPage(inPageIndex);
  200. this.renderPage(inPageIndex);
  201. // order of operations is key above
  202. this.pushPage(inPageIndex);
  203. },
  204. needPage : function(inPageIndex, inPos) {
  205. var h = this.getPageHeight(inPageIndex), oh = h;
  206. if (!this.pageExists(inPageIndex)) {
  207. this
  208. .buildPage(
  209. inPageIndex,
  210. (this.keepPages)
  211. && (this.stack.length >= this.keepPages),
  212. inPos);
  213. h = this.measurePage(inPageIndex) || h;
  214. this.pageHeights[inPageIndex] = h;
  215. if (h && (oh != h)) {
  216. this.updateContentHeight(h - oh)
  217. }
  218. } else {
  219. this.positionPage(inPageIndex, inPos);
  220. }
  221. return h;
  222. },
  223. onscroll : function() {
  224. this.scroll(this.scrollboxNode.scrollTop);
  225. },
  226. scroll : function(inTop) {
  227. this.startPacify();
  228. this.findPage(inTop);
  229. var h = this.height;
  230. var b = this.getScrollBottom(inTop);
  231. for (var p = this.page, y = this.pageTop; (p < this.pageCount)
  232. && ((b < 0) || (y < b)); p++) {
  233. y += this.needPage(p, y);
  234. }
  235. this.firstVisibleRow = this.getFirstVisibleRow(this.page,
  236. this.pageTop, inTop);
  237. this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b);
  238. // indicates some page size has been updated
  239. if (h != this.height) {
  240. this.repositionPages(p - 1);
  241. }
  242. this.endPacify();
  243. },
  244. getScrollBottom : function(inTop) {
  245. return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1);
  246. },
  247. // events
  248. processNodeEvent : function(e, inNode) {
  249. var t = e.target;
  250. while (t && (t != inNode) && t.parentNode
  251. && (t.parentNode.parentNode != inNode)) {
  252. t = t.parentNode;
  253. }
  254. if (!t || !t.parentNode || (t.parentNode.parentNode != inNode)) {
  255. return false;
  256. }
  257. var page = t.parentNode;
  258. e.topRowIndex = page.pageIndex * this.rowsPerPage;
  259. e.rowIndex = e.topRowIndex + dojox.grid.indexInParent(t);
  260. e.rowTarget = t;
  261. return true;
  262. },
  263. processEvent : function(e) {
  264. return this.processNodeEvent(e, this.contentNode);
  265. },
  266. dummy : 0
  267. });
  268. dojo.declare('dojox.grid.scroller', dojox.grid.scroller.base, {
  269. // summary:
  270. // virtual scroller class, makes no assumption about shape of items
  271. // being scrolled
  272. constructor : function() {
  273. this.pageNodes = [];
  274. },
  275. // virtual rendering interface
  276. renderRow : function(inRowIndex, inPageNode) {
  277. },
  278. removeRow : function(inRowIndex) {
  279. },
  280. // page node operations
  281. getDefaultNodes : function() {
  282. return this.pageNodes;
  283. },
  284. getDefaultPageNode : function(inPageIndex) {
  285. return this.getDefaultNodes()[inPageIndex];
  286. },
  287. positionPageNode : function(inNode, inPos) {
  288. inNode.style.top = inPos + 'px';
  289. },
  290. getPageNodePosition : function(inNode) {
  291. return inNode.offsetTop;
  292. },
  293. repositionPageNodes : function(inPageIndex, inNodes) {
  294. var last = 0;
  295. for (var i = 0; i < this.stack.length; i++) {
  296. last = Math.max(this.stack[i], last);
  297. }
  298. //
  299. var n = inNodes[inPageIndex];
  300. var y = (n ? this.getPageNodePosition(n)
  301. + this.getPageHeight(inPageIndex) : 0);
  302. // console.log('detected height change, repositioning from #%d (%d)
  303. // @ %d ', inPageIndex + 1, last, y, this.pageHeights[0]);
  304. //
  305. for (var p = inPageIndex + 1; p <= last; p++) {
  306. n = inNodes[p];
  307. if (n) {
  308. // console.log('#%d @ %d', inPageIndex, y,
  309. // this.getPageNodePosition(n));
  310. if (this.getPageNodePosition(n) == y) {
  311. return;
  312. }
  313. // console.log('placing page %d at %d', p, y);
  314. this.positionPage(p, y);
  315. }
  316. y += this.getPageHeight(p);
  317. }
  318. },
  319. invalidatePageNode : function(inPageIndex, inNodes) {
  320. var p = inNodes[inPageIndex];
  321. if (p) {
  322. delete inNodes[inPageIndex];
  323. this.removePage(inPageIndex, p);
  324. dojox.grid.cleanNode(p);
  325. p.innerHTML = '';
  326. }
  327. return p;
  328. },
  329. preparePageNode : function(inPageIndex, inReusePageIndex, inNodes) {
  330. var p = (inReusePageIndex === null ? this.createPageNode() : this
  331. .invalidatePageNode(inReusePageIndex, inNodes));
  332. p.pageIndex = inPageIndex;
  333. p.id = 'page-' + inPageIndex;
  334. inNodes[inPageIndex] = p;
  335. },
  336. // implementation for page manager
  337. pageExists : function(inPageIndex) {
  338. return Boolean(this.getDefaultPageNode(inPageIndex));
  339. },
  340. measurePage : function(inPageIndex) {
  341. return this.getDefaultPageNode(inPageIndex).offsetHeight;
  342. },
  343. positionPage : function(inPageIndex, inPos) {
  344. this.positionPageNode(this.getDefaultPageNode(inPageIndex), inPos);
  345. },
  346. repositionPages : function(inPageIndex) {
  347. this.repositionPageNodes(inPageIndex, this.getDefaultNodes());
  348. },
  349. preparePage : function(inPageIndex, inReuseNode) {
  350. this.preparePageNode(inPageIndex, (inReuseNode
  351. ? this.popPage()
  352. : null), this.getDefaultNodes());
  353. },
  354. installPage : function(inPageIndex) {
  355. this.contentNode.appendChild(this.getDefaultPageNode(inPageIndex));
  356. },
  357. destroyPage : function(inPageIndex) {
  358. var p = this
  359. .invalidatePageNode(inPageIndex, this.getDefaultNodes());
  360. dojox.grid.removeNode(p);
  361. },
  362. // rendering implementation
  363. renderPage : function(inPageIndex) {
  364. var node = this.pageNodes[inPageIndex];
  365. for (var i = 0, j = inPageIndex * this.rowsPerPage; (i < this.rowsPerPage)
  366. && (j < this.rowCount); i++, j++) {
  367. this.renderRow(j, node);
  368. }
  369. },
  370. removePage : function(inPageIndex) {
  371. for (var i = 0, j = inPageIndex * this.rowsPerPage; i < this.rowsPerPage; i++, j++) {
  372. this.removeRow(j);
  373. }
  374. },
  375. // scroll control
  376. getPageRow : function(inPage) {
  377. return inPage * this.rowsPerPage;
  378. },
  379. getLastPageRow : function(inPage) {
  380. return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1;
  381. },
  382. getFirstVisibleRowNodes : function(inPage, inPageTop, inScrollTop,
  383. inNodes) {
  384. var row = this.getPageRow(inPage);
  385. var rows = dojox.grid.divkids(inNodes[inPage]);
  386. for (var i = 0, l = rows.length; i < l && inPageTop < inScrollTop; i++, row++) {
  387. inPageTop += rows[i].offsetHeight;
  388. }
  389. return (row ? row - 1 : row);
  390. },
  391. getFirstVisibleRow : function(inPage, inPageTop, inScrollTop) {
  392. if (!this.pageExists(inPage)) {
  393. return 0;
  394. }
  395. return this.getFirstVisibleRowNodes(inPage, inPageTop, inScrollTop,
  396. this.getDefaultNodes());
  397. },
  398. getLastVisibleRowNodes : function(inPage, inBottom, inScrollBottom,
  399. inNodes) {
  400. var row = this.getLastPageRow(inPage);
  401. var rows = dojox.grid.divkids(inNodes[inPage]);
  402. for (var i = rows.length - 1; i >= 0 && inBottom > inScrollBottom; i--, row--) {
  403. inBottom -= rows[i].offsetHeight;
  404. }
  405. return row + 1;
  406. },
  407. getLastVisibleRow : function(inPage, inBottom, inScrollBottom) {
  408. if (!this.pageExists(inPage)) {
  409. return 0;
  410. }
  411. return this.getLastVisibleRowNodes(inPage, inBottom,
  412. inScrollBottom, this.getDefaultNodes());
  413. },
  414. findTopRowForNodes : function(inScrollTop, inNodes) {
  415. var rows = dojox.grid.divkids(inNodes[this.page]);
  416. for (var i = 0, l = rows.length, t = this.pageTop, h; i < l; i++) {
  417. h = rows[i].offsetHeight;
  418. t += h;
  419. if (t >= inScrollTop) {
  420. this.offset = h - (t - inScrollTop);
  421. return i + this.page * this.rowsPerPage;
  422. }
  423. }
  424. return -1;
  425. },
  426. findScrollTopForNodes : function(inRow, inNodes) {
  427. var rowPage = Math.floor(inRow / this.rowsPerPage);
  428. var t = 0;
  429. for (var i = 0; i < rowPage; i++) {
  430. t += this.getPageHeight(i);
  431. }
  432. this.pageTop = t;
  433. this.needPage(rowPage, this.pageTop);
  434. var rows = dojox.grid.divkids(inNodes[rowPage]);
  435. var r = inRow - this.rowsPerPage * rowPage;
  436. for (var i = 0, l = rows.length; i < l && i < r; i++) {
  437. t += rows[i].offsetHeight;
  438. }
  439. return t;
  440. },
  441. findTopRow : function(inScrollTop) {
  442. return this.findTopRowForNodes(inScrollTop, this.getDefaultNodes());
  443. },
  444. findScrollTop : function(inRow) {
  445. return this.findScrollTopForNodes(inRow, this.getDefaultNodes());
  446. },
  447. dummy : 0
  448. });
  449. dojo.declare('dojox.grid.scroller.columns', dojox.grid.scroller, {
  450. // summary:
  451. // Virtual scroller class that scrolls list of columns. Owned by grid
  452. // and used internally
  453. // for virtual scrolling.
  454. constructor : function(inContentNodes) {
  455. this.setContentNodes(inContentNodes);
  456. },
  457. // nodes
  458. setContentNodes : function(inNodes) {
  459. this.contentNodes = inNodes;
  460. this.colCount = (this.contentNodes ? this.contentNodes.length : 0);
  461. this.pageNodes = [];
  462. for (var i = 0; i < this.colCount; i++) {
  463. this.pageNodes[i] = [];
  464. }
  465. },
  466. getDefaultNodes : function() {
  467. return this.pageNodes[0] || [];
  468. },
  469. scroll : function(inTop) {
  470. if (this.colCount) {
  471. dojox.grid.scroller.prototype.scroll.call(this, inTop);
  472. }
  473. },
  474. // resize
  475. resize : function() {
  476. if (this.scrollboxNode) {
  477. this.windowHeight = this.scrollboxNode.clientHeight;
  478. }
  479. for (var i = 0; i < this.colCount; i++) {
  480. dojox.grid.setStyleHeightPx(this.contentNodes[i], this.height);
  481. }
  482. },
  483. // implementation for page manager
  484. positionPage : function(inPageIndex, inPos) {
  485. for (var i = 0; i < this.colCount; i++) {
  486. this.positionPageNode(this.pageNodes[i][inPageIndex], inPos);
  487. }
  488. },
  489. preparePage : function(inPageIndex, inReuseNode) {
  490. var p = (inReuseNode ? this.popPage() : null);
  491. for (var i = 0; i < this.colCount; i++) {
  492. this.preparePageNode(inPageIndex, p, this.pageNodes[i]);
  493. }
  494. },
  495. installPage : function(inPageIndex) {
  496. for (var i = 0; i < this.colCount; i++) {
  497. this.contentNodes[i]
  498. .appendChild(this.pageNodes[i][inPageIndex]);
  499. }
  500. },
  501. destroyPage : function(inPageIndex) {
  502. for (var i = 0; i < this.colCount; i++) {
  503. dojox.grid.removeNode(this.invalidatePageNode(inPageIndex,
  504. this.pageNodes[i]));
  505. }
  506. },
  507. // rendering implementation
  508. renderPage : function(inPageIndex) {
  509. var nodes = [];
  510. for (var i = 0; i < this.colCount; i++) {
  511. nodes[i] = this.pageNodes[i][inPageIndex];
  512. }
  513. // this.renderRows(inPageIndex*this.rowsPerPage, this.rowsPerPage,
  514. // nodes);
  515. for (var i = 0, j = inPageIndex * this.rowsPerPage; (i < this.rowsPerPage)
  516. && (j < this.rowCount); i++, j++) {
  517. this.renderRow(j, nodes);
  518. }
  519. }
  520. });
  521. }