PagingScroller.js 25 KB

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