Code:
// much faster implementation of `fillNode` method for buffered case which uses `node.appendChild` with `suppressEvent` option
// and bypasses all the events fireing/bubbling machinery, calling the `onNodeAdded` directly
fillNode : function (node, records) {
if (node.isRoot()) {
this.fireEvent('root-fill-start', this, node, records);
}
var me = this,
ln = records ? records.length : 0,
i = 0, sortCollection;
if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
sortCollection = Ext.create('Ext.util.MixedCollection');
sortCollection.addAll(records);
sortCollection.sort(me.sorters.items);
records = sortCollection.items;
}
node.set('loaded', true);
if (this.buffered) {
for (; i < ln; i++) {
// suppress the events -------|
// \/
node.appendChild(records[i], true, true);
// directly call 'onNodeAdded'
this.onNodeAdded(null, records[i]);
// register the node in tree (for `getNodeById` to work properly)
this.tree.registerNode(records[i]);
}
} else {
for (; i < ln; i++) {
node.appendChild(records[i], false, true);
}
}
if (node.isRoot()) {
this.fireEvent('root-fill-end', this, node, records);
}
return records;
},
Note, it will also fires the additional events "root-fill-start/end".
Code:
/**
* A copy of Ext.grid.PagingScroller which always uses the own store reference (not from panel/view)
*
* @class Sch.scroller.Paging
* @extends Ext.grid.Scroller
*
* @private
*/
Ext.define('Sch.scroller.Paging', {
extend : 'Ext.grid.Scroller',
alias : 'widget.schpagingscroller',
//renderTpl: null,
//tpl: [
// '<tpl for="pages">',
// '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
// '</tpl>'
//],
/**
* @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
* at what percentage to begin fetching the next page. For example if the pageSize is 100
* and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
* when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
*/
percentageFromEdge: 0.35,
/**
* @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
* when scrolling the PagingScrollbar.
*/
scrollToLoadBuffer: 200,
activePrefetch: true,
chunkSize: 50,
snapIncrement: 25,
syncScroll: true,
initComponent: function() {
var me = this,
ds = me.store;
ds.on('guaranteedrange', this.onGuaranteedRange, this);
this.callParent(arguments);
},
onGuaranteedRange: function(range, start, end) {
var me = this,
ds = me.store,
rs;
// this should never happen
if (range.length && me.visibleStart < range[0].index) {
return;
}
ds.loadRecords(range);
// HACK: letting the others know that new portion of records were loaded
ds.fireEvent('bufferchange', ds, range, start, end, this)
// EOF HACK
if (!me.firstLoad) {
if (me.rendered) {
me.invalidate();
} else {
me.on('afterrender', this.invalidate, this, {single: true});
}
me.firstLoad = true;
} else {
// adjust to visible
me.syncTo();
}
},
syncTo: function() {
var me = this,
pnl = me.getPanel(),
store = this.store,
scrollerElDom = this.scrollEl.dom,
rowOffset = me.visibleStart - store.guaranteedStart,
scrollBy = rowOffset * me.rowHeight,
scrollHeight = scrollerElDom.scrollHeight,
clientHeight = scrollerElDom.clientHeight,
scrollTop = scrollerElDom.scrollTop,
useMaximum;
// BrowserBug: clientHeight reports 0 in IE9 StrictMode
// Instead we are using offsetHeight and hardcoding borders
if (Ext.isIE9 && Ext.isStrict) {
clientHeight = scrollerElDom.offsetHeight + 2;
}
// This should always be zero or greater than zero but staying
// safe and less than 0 we'll scroll to the bottom.
useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
this.setViewScrollTop(scrollBy, useMaximum);
},
getPageData : function(){
var panel = this.getPanel(),
store = this.store,
totalCount = store.getTotalCount();
return {
total : totalCount,
currentPage : store.currentPage,
pageCount: Math.ceil(totalCount / store.pageSize),
fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
};
},
onElScroll: function(e, t) {
var me = this,
panel = me.getPanel(),
store = this.store,
pageSize = store.pageSize,
guaranteedStart = store.guaranteedStart,
guaranteedEnd = store.guaranteedEnd,
totalCount = store.getTotalCount(),
numFromEdge = Math.ceil(me.percentageFromEdge * store.pageSize),
position = t.scrollTop,
visibleStart = Math.floor(position / me.rowHeight),
view = panel.down('tableview'),
viewEl = view.el,
visibleHeight = viewEl.getHeight(),
visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
visibleEnd = visibleStart + visibleAhead,
prevPage = Math.floor(visibleStart / store.pageSize),
nextPage = Math.floor(visibleEnd / store.pageSize) + 2,
lastPage = Math.ceil(totalCount / store.pageSize),
//requestStart = visibleStart,
requestStart = Math.floor(visibleStart / me.snapIncrement) * me.snapIncrement,
requestEnd = requestStart + pageSize - 1,
activePrefetch = me.activePrefetch;
me.visibleStart = visibleStart;
me.visibleEnd = visibleEnd;
me.syncScroll = true;
if (totalCount >= pageSize) {
// end of request was past what the total is, grab from the end back a pageSize
if (requestEnd > totalCount - 1) {
this.cancelLoad();
if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
me.syncScroll = true;
}
store.guaranteeRange(totalCount - pageSize, totalCount - 1);
// Out of range, need to reset the current data set
} else if (visibleStart < guaranteedStart || visibleEnd > guaranteedEnd) {
if (store.rangeSatisfied(requestStart, requestEnd)) {
this.cancelLoad();
store.guaranteeRange(requestStart, requestEnd);
} else {
store.mask();
me.attemptLoad(requestStart, requestEnd);
}
// dont sync the scroll view immediately, sync after the range has been guaranteed
me.syncScroll = false;
} else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
me.syncScroll = true;
store.prefetchPage(prevPage);
} else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
me.syncScroll = true;
store.prefetchPage(nextPage);
}
}
if (me.syncScroll) {
me.syncTo();
}
},
getSizeCalculation: function() {
// Use the direct ownerCt here rather than the scrollerOwner
// because we are calculating widths/heights.
var owner = this.ownerGrid,
view = owner.getView(),
store = this.store,
dock = this.dock,
elDom = this.el.dom,
width = 1,
height = 1;
if (!this.rowHeight) {
this.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
}
// If the Store is *locally* filtered, use the filtered count from getCount.
height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * this.rowHeight;
if (isNaN(width)) {
width = 1;
}
if (isNaN(height)) {
height = 1;
}
return {
width: width,
height: height
};
},
attemptLoad: function(start, end) {
var me = this;
if (!me.loadTask) {
me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
}
me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
},
cancelLoad: function() {
if (this.loadTask) {
this.loadTask.cancel();
}
},
doAttemptLoad: function(start, end) {
var store = this.store;
store.guaranteeRange(start, end);
},
setViewScrollTop: function(scrollTop, useMax) {
var owner = this.getPanel(),
items = owner.query('tableview'),
i = 0,
len = items.length,
center,
centerEl,
calcScrollTop,
maxScrollTop,
scrollerElDom = this.el.dom;
var store = this.store;
owner.virtualScrollTop = scrollTop;
center = items[1] || items[0];
centerEl = center.el.dom;
maxScrollTop = ((store.pageSize * this.rowHeight) - centerEl.clientHeight);
calcScrollTop = (scrollTop % ((store.pageSize * this.rowHeight) + 1));
if (useMax) {
calcScrollTop = maxScrollTop;
}
if (calcScrollTop > maxScrollTop) {
//Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
return;
// calcScrollTop = maxScrollTop;
}
for (; i < len; i++) {
items[i].el.dom.scrollTop = calcScrollTop;
}
}
});
See: