_Scroller.js 15 KB

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