_Scroller.js 15 KB

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