PagingScroller.html 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-grid-PagingScroller'>/**
  19. </span> * Implements infinite scrolling of a grid, allowing users can scroll
  20. * through thousands of records without the performance penalties of
  21. * renderering all the records on screen at once. The grid should be
  22. * bound to a *buffered* store with a pageSize specified.
  23. *
  24. * The number of rows rendered outside the visible area, and the
  25. * buffering of pages of data from the remote server for immediate
  26. * rendering upon scroll can be controlled by configuring the
  27. * {@link Ext.grid.PagingScroller #verticalScroller}.
  28. *
  29. * You can tell it to create a larger table to provide more scrolling
  30. * before a refresh is needed, and also to keep more pages of records
  31. * in memory for faster refreshing when scrolling.
  32. *
  33. * var myStore = Ext.create('Ext.data.Store', {
  34. * // ...
  35. * buffered: true,
  36. * pageSize: 100,
  37. * // ...
  38. * });
  39. *
  40. * var grid = Ext.create('Ext.grid.Panel', {
  41. * // ...
  42. * autoLoad: true,
  43. * verticalScroller: {
  44. * trailingBufferZone: 200, // Keep 200 records buffered in memory behind scroll
  45. * leadingBufferZone: 5000 // Keep 5000 records buffered in memory ahead of scroll
  46. * },
  47. * // ...
  48. * });
  49. *
  50. * ## Implementation notes
  51. *
  52. * This class monitors scrolling of the {@link Ext.view.Table
  53. * TableView} within a {@link Ext.grid.Panel GridPanel} which is using
  54. * a buffered store to only cache and render a small section of a very
  55. * large dataset.
  56. *
  57. * **NB!** The GridPanel will instantiate this to perform monitoring,
  58. * this class should never be instantiated by user code. Always use the
  59. * {@link Ext.panel.Table#verticalScroller verticalScroller} config.
  60. *
  61. */
  62. Ext.define('Ext.grid.PagingScroller', {
  63. <span id='Ext-grid-PagingScroller-cfg-percentageFromEdge'> /**
  64. </span> * @cfg
  65. * @deprecated This config is now ignored.
  66. */
  67. percentageFromEdge: 0.35,
  68. <span id='Ext-grid-PagingScroller-cfg-numFromEdge'> /**
  69. </span> * @cfg
  70. * The zone which causes a refresh of the rendered viewport. As soon as the edge
  71. * of the rendered grid is this number of rows from the edge of the viewport, the view is moved.
  72. */
  73. numFromEdge: 2,
  74. <span id='Ext-grid-PagingScroller-cfg-trailingBufferZone'> /**
  75. </span> * @cfg
  76. * The number of extra rows to render on the trailing side of scrolling
  77. * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
  78. */
  79. trailingBufferZone: 5,
  80. <span id='Ext-grid-PagingScroller-cfg-leadingBufferZone'> /**
  81. </span> * @cfg
  82. * The number of extra rows to render on the leading side of scrolling
  83. * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
  84. */
  85. leadingBufferZone: 15,
  86. <span id='Ext-grid-PagingScroller-cfg-scrollToLoadBuffer'> /**
  87. </span> * @cfg
  88. * This is the time in milliseconds to buffer load requests when scrolling the PagingScrollbar.
  89. */
  90. scrollToLoadBuffer: 200,
  91. // private. Initial value of zero.
  92. viewSize: 0,
  93. // private. Start at default value
  94. rowHeight: 21,
  95. // private. Table extent at startup time
  96. tableStart: 0,
  97. tableEnd: 0,
  98. constructor: function(config) {
  99. var me = this;
  100. me.variableRowHeight = config.variableRowHeight;
  101. me.bindView(config.view);
  102. Ext.apply(me, config);
  103. me.callParent(arguments);
  104. },
  105. bindView: function(view) {
  106. var me = this,
  107. viewListeners = {
  108. scroll: {
  109. fn: me.onViewScroll,
  110. element: 'el',
  111. scope: me
  112. },
  113. render: me.onViewRender,
  114. resize: me.onViewResize,
  115. boxready: {
  116. fn: me.onViewResize,
  117. scope: me,
  118. single: true
  119. },
  120. // If there are variable row heights, then in beforeRefresh, we have to find a common
  121. // row so that we can synchronize the table's top position after the refresh.
  122. // Also flag whether the grid view has focus so that it can be refocused after refresh.
  123. beforerefresh: me.beforeViewRefresh,
  124. refresh: me.onViewRefresh,
  125. scope: me
  126. },
  127. storeListeners = {
  128. guaranteedrange: me.onGuaranteedRange,
  129. scope: me
  130. },
  131. gridListeners = {
  132. reconfigure: me.onGridReconfigure,
  133. scope: me
  134. }, partner;
  135. // If we need unbinding...
  136. if (me.view) {
  137. if (me.view.el) {
  138. me.view.el.un('scroll', me.onViewScroll, me); // un does not understand the element options
  139. }
  140. partner = view.lockingPartner;
  141. if (partner) {
  142. partner.un('refresh', me.onLockRefresh, me);
  143. }
  144. me.view.un(viewListeners);
  145. me.store.un(storeListeners);
  146. if (me.grid) {
  147. me.grid.un(gridListeners);
  148. }
  149. delete me.view.refreshSize; // Remove the injected refreshSize implementation
  150. }
  151. me.view = view;
  152. me.grid = me.view.up('tablepanel');
  153. me.store = view.store;
  154. if (view.rendered) {
  155. me.viewSize = me.store.viewSize = Math.ceil(view.getHeight() / me.rowHeight) + me.trailingBufferZone + (me.numFromEdge * 2) + me.leadingBufferZone;
  156. }
  157. partner = view.lockingPartner;
  158. if (partner) {
  159. partner.on('refresh', me.onLockRefresh, me);
  160. }
  161. me.view.mon(me.store.pageMap, {
  162. scope: me,
  163. clear: me.onCacheClear
  164. });
  165. // During scrolling we do not need to refresh the height - the Grid height must be set by config or layout in order to create a scrollable
  166. // table just larger than that, so removing the layout call improves efficiency and removes the flicker when the
  167. // HeaderContainer is reset to scrollLeft:0, and then resynced on the very next &quot;scroll&quot; event.
  168. me.view.refreshSize = Ext.Function.createInterceptor(me.view.refreshSize, me.beforeViewrefreshSize, me);
  169. <span id='Ext-grid-PagingScroller-property-position'> /**
  170. </span> * @property {Number} position
  171. * Current pixel scroll position of the associated {@link Ext.view.Table View}.
  172. */
  173. me.position = 0;
  174. // We are created in View constructor. There won't be an ownerCt at this time.
  175. if (me.grid) {
  176. me.grid.on(gridListeners);
  177. } else {
  178. me.view.on({
  179. added: function() {
  180. me.grid = me.view.up('tablepanel');
  181. me.grid.on(gridListeners);
  182. },
  183. single: true
  184. });
  185. }
  186. me.view.on(me.viewListeners = viewListeners);
  187. me.store.on(storeListeners);
  188. },
  189. onCacheClear: function() {
  190. var me = this;
  191. // Do not do anything if view is not rendered, or if the reason for cache clearing is store destruction
  192. if (me.view.rendered &amp;&amp; !me.store.isDestroyed) {
  193. // Temporarily disable scroll monitoring until the scroll event caused by any following *change* of scrollTop has fired.
  194. // Otherwise it will attempt to process a scroll on a stale view
  195. me.ignoreNextScrollEvent = me.view.el.dom.scrollTop !== 0;
  196. me.view.el.dom.scrollTop = 0;
  197. delete me.lastScrollDirection;
  198. delete me.scrollOffset;
  199. delete me.scrollProportion;
  200. }
  201. },
  202. onGridReconfigure: function (grid) {
  203. this.bindView(grid.view);
  204. },
  205. // Ensure that the stretcher element is inserted into the View as the first element.
  206. onViewRender: function() {
  207. var me = this,
  208. view = me.view,
  209. el = me.view.el,
  210. stretcher;
  211. me.stretcher = me.createStretcher(view);
  212. view = view.lockingPartner;
  213. if (view) {
  214. stretcher = me.stretcher;
  215. me.stretcher = new Ext.CompositeElement(stretcher);
  216. me.stretcher.add(me.createStretcher(view));
  217. }
  218. },
  219. createStretcher: function(view) {
  220. var el = view.el;
  221. el.setStyle('position', 'relative');
  222. return el.createChild({
  223. style:{
  224. position: 'absolute',
  225. width: '1px',
  226. height: 0,
  227. top: 0,
  228. left: 0
  229. }
  230. }, el.dom.firstChild);
  231. },
  232. onViewResize: function(view, width, height) {
  233. var me = this,
  234. newViewSize;
  235. newViewSize = Math.ceil(height / me.rowHeight) + me.trailingBufferZone + (me.numFromEdge * 2) + me.leadingBufferZone;
  236. if (newViewSize &gt; me.viewSize) {
  237. me.viewSize = me.store.viewSize = newViewSize;
  238. me.handleViewScroll(me.lastScrollDirection || 1);
  239. }
  240. },
  241. // Used for variable row heights. Try to find the offset from scrollTop of a common row
  242. beforeViewRefresh: function() {
  243. var me = this,
  244. view = me.view,
  245. rows,
  246. direction;
  247. // Refreshing can cause loss of focus.
  248. me.focusOnRefresh = Ext.Element.getActiveElement === view.el.dom;
  249. // Only need all this is variableRowHeight
  250. if (me.variableRowHeight) {
  251. direction = me.lastScrollDirection;
  252. me.commonRecordIndex = undefined;
  253. // If we are refreshing in response to a scroll,
  254. // And we know where the previous start was,
  255. // and we're not teleporting out of visible range
  256. // and the view is not empty
  257. if (direction &amp;&amp; (me.previousStart !== undefined) &amp;&amp; (me.scrollProportion === undefined) &amp;&amp; (rows = view.getNodes()).length) {
  258. // We have scrolled downwards
  259. if (direction === 1) {
  260. // If the ranges overlap, we are going to be able to position the table exactly
  261. if (me.tableStart &lt;= me.previousEnd) {
  262. me.commonRecordIndex = rows.length - 1;
  263. }
  264. }
  265. // We have scrolled upwards
  266. else if (direction === -1) {
  267. // If the ranges overlap, we are going to be able to position the table exactly
  268. if (me.tableEnd &gt;= me.previousStart) {
  269. me.commonRecordIndex = 0;
  270. }
  271. }
  272. // Cache the old offset of the common row from the scrollTop
  273. me.scrollOffset = -view.el.getOffsetsTo(rows[me.commonRecordIndex])[1];
  274. // In the new table the common row is at a different index
  275. me.commonRecordIndex -= (me.tableStart - me.previousStart);
  276. } else {
  277. me.scrollOffset = undefined;
  278. }
  279. }
  280. },
  281. onLockRefresh: function(view) {
  282. view.table.dom.style.position = 'absolute';
  283. },
  284. // Used for variable row heights. Try to find the offset from scrollTop of a common row
  285. // Ensure, upon each refresh, that the stretcher element is the correct height
  286. onViewRefresh: function() {
  287. var me = this,
  288. store = me.store,
  289. newScrollHeight,
  290. view = me.view,
  291. viewEl = view.el,
  292. viewDom = viewEl.dom,
  293. rows,
  294. newScrollOffset,
  295. scrollDelta,
  296. table = view.table.dom,
  297. tableTop,
  298. scrollTop;
  299. // Refresh causes loss of focus
  300. if (me.focusOnRefresh) {
  301. viewEl.focus();
  302. me.focusOnRefresh = false;
  303. }
  304. // Scroll events caused by processing in here must be ignored, so disable for the duration
  305. me.disabled = true;
  306. // No scroll monitoring is needed if
  307. // All data is in view OR
  308. // Store is filtered locally.
  309. // - scrolling a locally filtered page is obv a local operation within the context of a huge set of pages
  310. // so local scrolling is appropriate.
  311. if (store.getCount() === store.getTotalCount() || (store.isFiltered() &amp;&amp; !store.remoteFilter)) {
  312. me.stretcher.setHeight(0);
  313. me.position = viewDom.scrollTop = 0;
  314. // Chrome's scrolling went crazy upon zeroing of the stretcher, and left the view's scrollTop stuck at -15
  315. // This is the only thing that fixes that
  316. me.setTablePosition('absolute');
  317. // We remain disabled now because no scrolling is needed - we have the full dataset in the Store
  318. return;
  319. }
  320. me.stretcher.setHeight(newScrollHeight = me.getScrollHeight());
  321. scrollTop = viewDom.scrollTop;
  322. // Flag to the refreshSize interceptor that regular refreshSize postprocessing should be vetoed.
  323. me.isScrollRefresh = (scrollTop &gt; 0);
  324. // If we have had to calculate the store position from the pure scroll bar position,
  325. // then we must calculate the table's vertical position from the scrollProportion
  326. if (me.scrollProportion !== undefined) {
  327. me.setTablePosition('absolute');
  328. me.setTableTop((me.scrollProportion ? (newScrollHeight * me.scrollProportion) - (table.offsetHeight * me.scrollProportion) : 0) + 'px');
  329. } else {
  330. me.setTablePosition('absolute');
  331. me.setTableTop((tableTop = (me.tableStart||0) * me.rowHeight) + 'px');
  332. // ScrollOffset to a common row was calculated in beforeViewRefresh, so we can synch table position with how it was before
  333. if (me.scrollOffset) {
  334. rows = view.getNodes();
  335. newScrollOffset = -viewEl.getOffsetsTo(rows[me.commonRecordIndex])[1];
  336. scrollDelta = newScrollOffset - me.scrollOffset;
  337. me.position = (viewDom.scrollTop += scrollDelta);
  338. }
  339. // If the table is not fully in view view, scroll to where it is in view.
  340. // This will happen when the page goes out of view unexpectedly, outside the
  341. // control of the PagingScroller. For example, a refresh caused by a remote sort or filter reverting
  342. // back to page 1.
  343. // Note that with buffered Stores, only remote sorting is allowed, otherwise the locally
  344. // sorted page will be out of order with the whole dataset.
  345. else if ((tableTop &gt; scrollTop) || ((tableTop + table.offsetHeight) &lt; scrollTop + viewDom.clientHeight)) {
  346. me.lastScrollDirection = -1;
  347. me.position = viewDom.scrollTop = tableTop;
  348. }
  349. }
  350. // Re-enable upon function exit
  351. me.disabled = false;
  352. },
  353. setTablePosition: function(position) {
  354. this.setViewTableStyle(this.view, 'position', position);
  355. },
  356. setTableTop: function(top){
  357. this.setViewTableStyle(this.view, 'top', top);
  358. },
  359. setViewTableStyle: function(view, prop, value) {
  360. view.el.child('table', true).style[prop] = value;
  361. view = view.lockingPartner;
  362. if (view) {
  363. view.el.child('table', true).style[prop] = value;
  364. }
  365. },
  366. beforeViewrefreshSize: function() {
  367. // Veto the refreshSize if the refresh is due to a scroll.
  368. if (this.isScrollRefresh) {
  369. // If we're vetoing refreshSize, attach the table DOM to the View's Flyweight.
  370. this.view.table.attach(this.view.el.child('table', true));
  371. return (this.isScrollRefresh = false);
  372. }
  373. },
  374. onGuaranteedRange: function(range, start, end) {
  375. var me = this,
  376. ds = me.store;
  377. // this should never happen
  378. if (range.length &amp;&amp; me.visibleStart &lt; range[0].index) {
  379. return;
  380. }
  381. // Cache last table position in dataset so that if we are using variableRowHeight,
  382. // we can attempt to locate a common row to align the table on.
  383. me.previousStart = me.tableStart;
  384. me.previousEnd = me.tableEnd;
  385. me.tableStart = start;
  386. me.tableEnd = end;
  387. ds.loadRecords(range, {
  388. start: start
  389. });
  390. },
  391. onViewScroll: function(e, t) {
  392. var me = this,
  393. view = me.view,
  394. lastPosition = me.position;
  395. me.position = view.el.dom.scrollTop;
  396. // Flag set when the scrollTop is programatically set to zero upon cache clear.
  397. // We must not attempt to process that as a scroll event.
  398. if (me.ignoreNextScrollEvent) {
  399. me.ignoreNextScrollEvent = false;
  400. return;
  401. }
  402. // Only check for nearing the edge if we are enabled.
  403. // If there is no paging to be done (Store's dataset is all in memory) we will be disabled.
  404. if (!me.disabled) {
  405. me.lastScrollDirection = me.position &gt; lastPosition ? 1 : -1;
  406. // Check the position so we ignore horizontal scrolling
  407. if (lastPosition !== me.position) {
  408. me.handleViewScroll(me.lastScrollDirection);
  409. }
  410. }
  411. },
  412. handleViewScroll: function(direction) {
  413. var me = this,
  414. store = me.store,
  415. view = me.view,
  416. viewSize = me.viewSize,
  417. totalCount = store.getTotalCount(),
  418. highestStartPoint = totalCount - viewSize,
  419. visibleStart = me.getFirstVisibleRowIndex(),
  420. visibleEnd = me.getLastVisibleRowIndex(),
  421. el = view.el.dom,
  422. requestStart,
  423. requestEnd;
  424. // Only process if the total rows is larger than the visible page size
  425. if (totalCount &gt;= viewSize) {
  426. // This is only set if we are using variable row height, and the thumb is dragged so that
  427. // There are no remaining visible rows to vertically anchor the new table to.
  428. // In this case we use the scrollProprtion to anchor the table to the correct relative
  429. // position on the vertical axis.
  430. me.scrollProportion = undefined;
  431. // We're scrolling up
  432. if (direction == -1) {
  433. // If table starts at record zero, we have nothing to do
  434. if (me.tableStart) {
  435. if (visibleStart !== undefined) {
  436. if (visibleStart &lt; (me.tableStart + me.numFromEdge)) {
  437. requestStart = Math.max(0, visibleEnd + me.trailingBufferZone - viewSize);
  438. }
  439. }
  440. // The only way we can end up without a visible start is if, in variableRowHeight mode, the user drags
  441. // the thumb up out of the visible range. In this case, we have to estimate the start row index
  442. else {
  443. // If we have no visible rows to orientate with, then use the scroll proportion
  444. me.scrollProportion = el.scrollTop / (el.scrollHeight - el.clientHeight);
  445. requestStart = Math.max(0, totalCount * me.scrollProportion - (viewSize / 2) - me.numFromEdge - ((me.leadingBufferZone + me.trailingBufferZone) / 2));
  446. }
  447. }
  448. }
  449. // We're scrolling down
  450. else {
  451. if (visibleStart !== undefined) {
  452. if (visibleEnd &gt; (me.tableEnd - me.numFromEdge)) {
  453. requestStart = Math.max(0, visibleStart - me.trailingBufferZone);
  454. }
  455. }
  456. // The only way we can end up without a visible end is if, in variableRowHeight mode, the user drags
  457. // the thumb down out of the visible range. In this case, we have to estimate the start row index
  458. else {
  459. // If we have no visible rows to orientate with, then use the scroll proportion
  460. me.scrollProportion = el.scrollTop / (el.scrollHeight - el.clientHeight);
  461. requestStart = totalCount * me.scrollProportion - (viewSize / 2) - me.numFromEdge - ((me.leadingBufferZone + me.trailingBufferZone) / 2);
  462. }
  463. }
  464. // We scrolled close to the edge and the Store needs reloading
  465. if (requestStart !== undefined) {
  466. // The calculation walked off the end; Request the highest possible chunk which starts on an even row count (Because of row striping)
  467. if (requestStart &gt; highestStartPoint) {
  468. requestStart = highestStartPoint &amp; ~1;
  469. requestEnd = totalCount - 1;
  470. }
  471. // Make sure first row is even to ensure correct even/odd row striping
  472. else {
  473. requestStart = requestStart &amp; ~1;
  474. requestEnd = requestStart + viewSize - 1;
  475. }
  476. // If range is satsfied within the prefetch buffer, then just draw it from the prefetch buffer
  477. if (store.rangeCached(requestStart, requestEnd)) {
  478. me.cancelLoad();
  479. store.guaranteeRange(requestStart, requestEnd);
  480. }
  481. // Required range is not in the prefetch buffer. Ask the store to prefetch it.
  482. // We will recieve a guaranteedrange event when that is done.
  483. else {
  484. me.attemptLoad(requestStart, requestEnd);
  485. }
  486. }
  487. }
  488. },
  489. getFirstVisibleRowIndex: function() {
  490. var me = this,
  491. view = me.view,
  492. scrollTop = view.el.dom.scrollTop,
  493. rows,
  494. count,
  495. i,
  496. rowBottom;
  497. if (me.variableRowHeight) {
  498. rows = view.getNodes();
  499. count = rows.length;
  500. if (!count) {
  501. return;
  502. }
  503. rowBottom = Ext.fly(rows[0]).getOffsetsTo(view.el)[1];
  504. for (i = 0; i &lt; count; i++) {
  505. rowBottom += rows[i].offsetHeight;
  506. // Searching for the first visible row, and off the bottom of the clientArea, then there's no visible first row!
  507. if (rowBottom &gt; view.el.dom.clientHeight) {
  508. return;
  509. }
  510. // Return the index *within the total dataset* of the first visible row
  511. // We cannot use the loop index to offset from the table's start index because of possible intervening group headers.
  512. if (rowBottom &gt; 0) {
  513. return view.getRecord(rows[i]).index;
  514. }
  515. }
  516. } else {
  517. return Math.floor(scrollTop / me.rowHeight);
  518. }
  519. },
  520. getLastVisibleRowIndex: function() {
  521. var me = this,
  522. store = me.store,
  523. view = me.view,
  524. clientHeight = view.el.dom.clientHeight,
  525. rows,
  526. count,
  527. i,
  528. rowTop;
  529. if (me.variableRowHeight) {
  530. rows = view.getNodes();
  531. if (!rows.length) {
  532. return;
  533. }
  534. count = store.getCount() - 1;
  535. rowTop = Ext.fly(rows[count]).getOffsetsTo(view.el)[1] + rows[count].offsetHeight;
  536. for (i = count; i &gt;= 0; i--) {
  537. rowTop -= rows[i].offsetHeight;
  538. // Searching for the last visible row, and off the top of the clientArea, then there's no visible last row!
  539. if (rowTop &lt; 0) {
  540. return;
  541. }
  542. // Return the index *within the total dataset* of the last visible row.
  543. // We cannot use the loop index to offset from the table's start index because of possible intervening group headers.
  544. if (rowTop &lt; clientHeight) {
  545. return view.getRecord(rows[i]).index;
  546. }
  547. }
  548. } else {
  549. return me.getFirstVisibleRowIndex() + Math.ceil(clientHeight / me.rowHeight) + 1;
  550. }
  551. },
  552. getScrollHeight: function() {
  553. var me = this,
  554. view = me.view,
  555. table,
  556. firstRow,
  557. store = me.store,
  558. deltaHeight = 0,
  559. doCalcHeight = !me.hasOwnProperty('rowHeight');
  560. if (me.variableRowHeight) {
  561. table = me.view.table.dom;
  562. if (doCalcHeight) {
  563. me.initialTableHeight = table.offsetHeight;
  564. me.rowHeight = me.initialTableHeight / me.store.getCount();
  565. } else {
  566. deltaHeight = table.offsetHeight - me.initialTableHeight;
  567. // Store size has been bumped because of odd end row.
  568. if (store.getCount() &gt; me.viewSize) {
  569. deltaHeight -= me.rowHeight;
  570. }
  571. }
  572. } else if (doCalcHeight) {
  573. firstRow = view.el.down(view.getItemSelector());
  574. if (firstRow) {
  575. me.rowHeight = firstRow.getHeight(false, true);
  576. }
  577. }
  578. return Math.floor(store.getTotalCount() * me.rowHeight) + deltaHeight;
  579. },
  580. attemptLoad: function(start, end) {
  581. var me = this;
  582. if (me.scrollToLoadBuffer) {
  583. if (!me.loadTask) {
  584. me.loadTask = new Ext.util.DelayedTask(me.doAttemptLoad, me, []);
  585. }
  586. me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
  587. } else {
  588. me.store.guaranteeRange(start, end);
  589. }
  590. },
  591. cancelLoad: function() {
  592. if (this.loadTask) {
  593. this.loadTask.cancel();
  594. }
  595. },
  596. doAttemptLoad: function(start, end) {
  597. this.store.guaranteeRange(start, end);
  598. },
  599. destroy: function() {
  600. var me = this,
  601. scrollListener = me.viewListeners.scroll;
  602. me.store.un({
  603. guaranteedrange: me.onGuaranteedRange,
  604. scope: me
  605. });
  606. me.view.un(me.viewListeners);
  607. if (me.view.rendered) {
  608. me.stretcher.remove();
  609. me.view.el.un('scroll', scrollListener.fn, scrollListener.scope);
  610. }
  611. }
  612. });
  613. </pre>
  614. </body>
  615. </html>