Skip to content

Instantly share code, notes, and snippets.

@branflake2267
Created September 28, 2019 18:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save branflake2267/b27de8b65e1f9756af372403103b6a1e to your computer and use it in GitHub Desktop.
Save branflake2267/b27de8b65e1f9756af372403103b6a1e to your computer and use it in GitHub Desktop.
Ext scrolling override - 500k
Ext.Loader.setConfig({
enabled: true
});
Ext.Loader.setPath('Ext.ux', 'ext/ux');
Ext.require(['Ext.ux.ajax.JsonSimlet', 'Ext.ux.ajax.SimManager']);
Ext.define('Ext.ux.ajax.JsonSimletWithTotal', {
extend: 'Ext.ux.ajax.DataSimlet',
alias: 'simlet.jsonwithtotal',
doGet: function (ctx) {
var me = this,
page = [],
i,
j,
reader = ctx.xhr.options.proxy.reader,
ret = me.callParent(arguments),
response = {};
for (i = ctx.params.start, j = 0; i < ctx.params.start + ctx.params.limit; i++ , j++) {
page[j] = {
name: 'User ' + Ext.util.Format.number(i + 1, '0,000')
};
}
response[reader.root] = page;
response[reader.totalProperty] = 500000;
ret.responseText = Ext.encode(response);
return ret;
}
});
Ext.onReady(function () {
Ext.ux.ajax.SimManager.register({
'remote-data.php': {
stype: 'jsonwithtotal'
}
});
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [{
xtype: 'grid',
title: '1,000,000 Users',
store: {
autoLoad: true,
buffered: true,
pageSize: 100,
fields: [{
name: 'name'
}],
proxy: {
type: 'ajax',
url: 'remote-data.php',
reader: {
type: 'json',
root: 'data'
}
}
},
selModel: Ext.create('Ext.selection.CheckboxModel', {
}),
columns: [{
xtype: 'rownumberer',
width: 60,
sortable: false
}, {
text: 'Name',
dataIndex: 'name',
width: 300
}],
viewConfig: {
rowHeight: 16,
trackOver: false,
stripeRows: false,
loadMask: true,
preserveScrollOnRefresh: true,
enableTextSelection: true
},
verticalScroller: {
scrollToLoadBuffer: 700,
trailingBufferZone: 10,
leadingBufferZone: 20
},
scrollable: true,
columnLines: true
}]
});
});
var MAX_EL_HEIGHT = 1500000; // ie height, simulate constrained scroller height
//MAX_EL_HEIGHT = 9500000; // row height that works for chrome, test formulas in chrome
//--------------------
// Ext.grid.PagingScroller ln: 43001
/**
* This class monitors scrolling of the {@link Ext.view.Table TableView} within a
* {@link Ext.grid.Panel GridPanel} which is using a buffered store to only cache
* and render a small section of a very large dataset.
*
* The GridPanel will instantiate this to perform monitoring, this class should
* never be instantiated by user code.
*/
Ext.define('Ext.grid.PagingScroller', {
/**
* @cfg
* @deprecated This config is now ignored.
*/
percentageFromEdge: 0.35,
/**
* @cfg
* The zone which causes a refresh of the rendered viewport. As soon as the edge
* of the rendered grid is this number of rows from the edge of the viewport, the view is moved.
*/
numFromEdge: 2,
/**
* @cfg
* The number of extra rows to render on the trailing side of scrolling
* **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
*/
trailingBufferZone: 5,
/**
* @cfg
* The number of extra rows to render on the leading side of scrolling
* **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
*/
leadingBufferZone: 15,
/**
* @cfg
* This is the time in milliseconds to buffer load requests when scrolling the PagingScrollbar.
*/
scrollToLoadBuffer: 200,
// private. Initial value of zero.
viewSize: 0,
// private. Start at default value
rowHeight: 21,
// private. Table extent at startup time
tableStart: 0,
tableEnd: 0,
constructor: function (config) {
var me = this;
me.variableRowHeight = config.variableRowHeight;
me.bindView(config.view);
Ext.apply(me, config);
me.callParent(arguments);
},
bindView: function (view) {
var me = this,
viewListeners = {
scroll: {
fn: me.onViewScroll,
element: 'el',
scope: me
},
render: me.onViewRender,
resize: me.onViewResize,
boxready: {
fn: me.onViewResize,
scope: me,
single: true
},
refresh: me.onViewRefresh,
scope: me
},
storeListeners = {
guaranteedrange: me.onGuaranteedRange,
scope: me
},
gridListeners = {
reconfigure: me.onGridReconfigure,
scope: me
};
console.log("bindView");
// If there are variable row heights, then in beforeRefresh, we have to find a common
// row so that we can synchronize the table's top position after the refresh
if (me.variableRowHeight) {
viewListeners.beforerefresh = me.beforeViewRefresh;
}
// If we need unbinding...
if (me.view) {
me.view.el.un('scroll', me.onViewScroll, me); // un does not understand the element options
me.view.un(viewListeners);
me.store.un(storeListeners);
if (me.grid) {
me.grid.un(gridListeners);
}
delete me.view.refreshSize; // Remove the injected refreshSize implementation
}
me.view = view;
me.grid = me.view.up('tablepanel');
me.store = view.store;
if (view.rendered) {
me.viewSize = me.store.viewSize = Math.ceil(view.getHeight() / me.rowHeight) + me.trailingBufferZone + (me.numFromEdge * 2) + me.leadingBufferZone;
}
// 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
// table just larger than that, so removing the layout call improves efficiency and removes the flicker when the
// HeaderContainer is reset to scrollLeft:0, and then resynced on the very next "scroll" event.
me.view.refreshSize = Ext.Function.createInterceptor(me.view.refreshSize, me.beforeViewrefreshSize, me);
/**
* @property {Number} position
* Current pixel scroll position of the associated {@link Ext.view.Table View}.
*/
me.position = 0;
// We are created in View constructor. There won't be an ownerCt at this time.
if (me.grid) {
me.grid.on(gridListeners);
} else {
me.view.on({
added: function () {
me.grid = me.view.up('tablepanel');
me.grid.on(gridListeners);
},
single: true
});
}
me.view.on(me.viewListeners = viewListeners);
me.store.on(storeListeners);
},
onGridReconfigure: function (grid) {
this.bindView(grid.view);
},
// Ensure that the stretcher element is inserted into the View as the first element.
onViewRender: function () {
var me = this,
el = me.view.el;
//console.log("onViewRender creatingStretcher ####### ");
el.setStyle('position', 'relative');
me.stretcher = el.createChild({
style: {
position: 'absolute',
width: '1px',
height: 0,
top: 0,
left: 0
}
}, el.dom.firstChild);
},
onViewResize: function (view, width, height) {
var me = this,
newViewSize;
//console.log("onViewResize ######");
newViewSize = Math.ceil(height / me.rowHeight) + me.trailingBufferZone + (me.numFromEdge * 2) + me.leadingBufferZone;
if (newViewSize > me.viewSize) {
me.viewSize = me.store.viewSize = newViewSize;
me.handleViewScroll(me.lastScrollDirection || 1);
}
},
// Used for variable row heights. Try to find the offset from scrollTop of a common row
beforeViewRefresh: function () {
var me = this,
view = me.view,
rows,
direction = me.lastScrollDirection;
me.commonRecordIndex = undefined;
// If we are refreshing in response to a scroll,
// And we know where the previous start was,
// and we're not teleporting out of visible range
if (direction && (me.previousStart !== undefined) && (me.scrollProportion === undefined)) {
rows = view.getNodes();
// We have scrolled downwards
if (direction === 1) {
// If the ranges overlap, we are going to be able to position the table exactly
if (me.tableStart <= me.previousEnd) {
me.commonRecordIndex = rows.length - 1;
}
}
// We have scrolled upwards
else if (direction === -1) {
// If the ranges overlap, we are going to be able to position the table exactly
if (me.tableEnd >= me.previousStart) {
me.commonRecordIndex = 0;
}
}
// Cache the old offset of the common row from the scrollTop
me.scrollOffset = -view.el.getOffsetsTo(rows[me.commonRecordIndex])[1];
// In the new table the common row is at a different index
me.commonRecordIndex -= (me.tableStart - me.previousStart);
} else {
me.scrollOffset = undefined;
}
},
// Used for variable row heights. Try to find the offset from scrollTop of a common row
// Ensure, upon each refresh, that the stretcher element is the correct height
onViewRefresh: function () {
var me = this,
store = me.store,
newScrollHeight,
view = me.view,
viewEl = view.el,
viewDom = viewEl.dom,
rows,
newScrollOffset,
scrollDelta,
table = viewEl.child('table', true),
tableTop,
scrollTop;
// Scroll events caused by processing in here must be ignored, so disable for the duration
me.disabled = true;
// No scroll monitoring is needed if
// All data is in view OR
// Store is filtered locally.
// - scrolling a locally filtered page is obv a local operation within the context of a huge set of pages
// so local scrolling is appropriate.
if (store.getCount() === store.getTotalCount() || (store.isFiltered() && !store.remoteFilter)) {
//console.log("onViewRefresh start");
me.stretcher.setHeight(0);
me.position = viewDom.scrollTop = 0;
// Chrome's scrolling went crazy upon zeroing of the stretcher, and left the view's scrollTop stuck at -15
// This is the only thing that fixes that
table.style.position = 'absolute';
// We remain disabled now because no scrolling is needed - we have the full dataset in the Store
return;
}
me.stretcher.setHeight(newScrollHeight = me.getScrollHeight());
//console.log("onViewRefresh: stretcher height=" + newScrollHeight);
scrollTop = viewDom.scrollTop;
// Flag to the refreshSize interceptor that regular refreshSize postprocessing should be vetoed.
me.isScrollRefresh = (scrollTop > 0);
// If we have had to calculate the store position from the pure scroll bar position,
// then we must calculate the table's vertical position from the scrollProportion
// ~~~ workaround set to true
if (true) {
me.scrollProportion = scrollTop / (newScrollHeight - table.offsetHeight);
table.style.position = 'absolute';
var tableTop = (me.scrollProportion ? (newScrollHeight * me.scrollProportion) - (table.offsetHeight * me.scrollProportion) : 0);
//console.log("tableTop=" + tableTop);
table.style.top = tableTop + 'px';
}
else {
table.style.position = 'absolute';
table.style.top = (tableTop = (me.tableStart || 0) * me.rowHeight) + 'px';
// ScrollOffset to a common row was calculated in beforeViewRefresh, so we can synch table position with how it was before
if (me.scrollOffset) {
rows = view.getNodes();
newScrollOffset = -viewEl.getOffsetsTo(rows[me.commonRecordIndex])[1];
scrollDelta = newScrollOffset - me.scrollOffset;
me.position = (scrollTop += scrollDelta);
}
// If the table is not fully in view view, scroll to where it is in view.
// This will happen when the page goes out of view unexpectedly, outside the
// control of the PagingScroller. For example, a refresh caused by a remote sort or filter reverting
// back to page 1.
// Note that with buffered Stores, only remote sorting is allowed, otherwise the locally
// sorted page will be out of order with the whole dataset.
else if ((tableTop > scrollTop) || ((tableTop + table.offsetHeight) < scrollTop + viewDom.clientHeight)) {
me.lastScrollDirection = -1;
me.position = viewDom.scrollTop = tableTop;
}
}
// Re-enable upon function exit
me.disabled = false;
},
beforeViewrefreshSize: function () {
// Veto the refreshSize if the refresh is due to a scroll.
if (this.isScrollRefresh) {
return (this.isScrollRefresh = false);
}
},
onGuaranteedRange: function (range, start, end) {
var me = this,
ds = me.store;
// this should never happen
if (range.length && me.visibleStart < range[0].index) {
return;
}
// Cache last table position in dataset so that if we are using variableRowHeight,
// we can attempt to locate a common row to align the table on.
me.previousStart = me.tableStart;
me.previousEnd = me.tableEnd;
me.tableStart = start;
me.tableEnd = end;
ds.loadRecords(range);
},
onViewScroll: function (e, t) {
var me = this,
view = me.view,
lastPosition = me.position;
me.position = view.el.dom.scrollTop;
//console.log("onViewScroll: scrollTop=" + view.el.dom.scrollTop);
// Only check for nearing the edge if we are enabled.
// If there is no paging to be done (Store's dataset is all in memory) we will be disabled.
if (!me.disabled) {
me.lastScrollDirection = me.position > lastPosition ? 1 : -1;
// Check the position so we ignore horizontal scrolling
if (lastPosition !== me.position) {
me.handleViewScroll(me.lastScrollDirection);
}
}
},
handleViewScroll: function (direction) {
var me = this,
store = me.store,
view = me.view,
viewSize = me.viewSize,
totalCount = store.getTotalCount(),
highestStartPoint = totalCount - viewSize,
visibleStart = me.getFirstVisibleRowIndex(),
visibleEnd = me.getLastVisibleRowIndex(),
requestStart,
requestEnd;
//console.log("visibleStart=" + visibleStart + " visibleEnd=" + visibleEnd + " highestStartPoint=" + highestStartPoint);
// Only process if the total rows is larger than the visible page size
if (totalCount >= viewSize) {
// This is only set if we are using variable row height, and the thumb is dragged so that
// There are no remaining visible rows to vertically anchor the new table to.
// In this case we use the scrollProprtion to anchor the table to the correct relative
// position on the vertical axis.
me.scrollProportion = undefined;
// We're scrolling up
if (direction == -1) {
if (visibleStart !== undefined) {
if (visibleStart < (me.tableStart + me.numFromEdge)) {
requestStart = Math.max(0, visibleEnd + me.trailingBufferZone - viewSize);
}
}
// The only way we can end up without a visible start is if, in variableRowHeight mode, the user drags
// the thumb up out of the visible range. In this case, we have to estimate the start row index
else {
// ~~~ TODO
//debugger;
// If we have no visible rows to orientate with, then use the scroll proportion
me.scrollProportion = view.el.dom.scrollTop / (view.el.dom.scrollHeight - view.el.dom.clientHeight);
requestStart = Math.max(0, totalCount * me.scrollProportion - (viewSize / 2) - me.numFromEdge - ((me.leadingBufferZone + me.trailingBufferZone) / 2));
}
}
// We're scrolling down
else {
if (visibleStart !== undefined) {
if (visibleEnd > (me.tableEnd - me.numFromEdge)) {
requestStart = Math.max(0, visibleStart - me.trailingBufferZone);
}
}
// The only way we can end up without a visible end is if, in variableRowHeight mode, the user drags
// the thumb down out of the visible range. In this case, we have to estimate the start row index
else {
// ~~~ TODO
//debugger;
// If we have no visible rows to orientate with, then use the scroll proportion
me.scrollProportion = view.el.dom.scrollTop / (view.el.dom.scrollHeight - view.el.dom.clientHeight);
requestStart = totalCount * me.scrollProportion - (viewSize / 2) - me.numFromEdge - ((me.leadingBufferZone + me.trailingBufferZone) / 2);
}
}
// We scrolled close to the edge and the Store needs reloading
if (requestStart !== undefined) {
// The calculation walked off the end; Request the highest possible chunk which starts on an even row count (Because of row striping)
if (requestStart > highestStartPoint) {
requestStart = highestStartPoint & ~1;
requestEnd = totalCount - 1;
}
// Make sure first row is even to ensure correct even/odd row striping
else {
requestStart = requestStart & ~1;
requestEnd = requestStart + viewSize - 1;
}
console.log("requestStart=" + requestStart + " requestEnd=" + requestEnd + " rangeCount=" + (requestEnd - requestStart) + " viewSize=" + viewSize);
// If range is satsfied within the prefetch buffer, then just draw it from the prefetch buffer
if (store.rangeCached(requestStart, requestEnd)) {
me.cancelLoad();
store.guaranteeRange(requestStart, requestEnd);
}
// Required range is not in the prefetch buffer. Ask the store to prefetch it.
// We will recieve a guaranteedrange event when that is done.
else {
me.attemptLoad(requestStart, requestEnd);
}
}
}
},
getFirstVisibleRowIndex: function () {
var me = this,
store = me.store,
view = me.view,
scrollTop = view.el.dom.scrollTop,
rows,
count,
i,
rowBottom;
if (me.variableRowHeight) {
// ~~~ TODO
rows = view.getNodes();
count = store.getCount();
for (i = 0; i < count; i++) {
rowBottom = Ext.fly(rows[i]).getOffsetsTo(view.el)[1] + rows[i].offsetHeight;
// Searching for the first visible row, and off the bottom of the clientArea, then there's no visible first row!
if (rowBottom > view.el.dom.clientHeight) {
return;
}
if (rowBottom > 0) {
return i + me.tableStart;
}
}
} else {
var pos = Math.floor(scrollTop / me.rowHeight);
// ~~~ workaround - get proportion from scroll
var proportion = (MAX_EL_HEIGHT / (me.store.getTotalCount() * me.rowHeight));
// ~~~ workaround for end of record set, OR add to scroll height so it has more range.
//proportion -= .001;
var adjustedScrollTop = Math.ceil(scrollTop / proportion);
pos = Math.floor(adjustedScrollTop / me.rowHeight);
console.log("adjustedScrollTop=" + adjustedScrollTop + " scrollTop=" + scrollTop + " MAX_EL_HEIGHT=" + MAX_EL_HEIGHT + " TRH=" + (me.store.getTotalCount() * me.rowHeight) + " proportion=" + proportion);
return pos;
}
},
getLastVisibleRowIndex: function () {
var me = this,
store = me.store,
view = me.view,
clientHeight = view.el.dom.clientHeight,
rows,
count,
i,
rowTop;
if (me.variableRowHeight) {
// ~~~ TODO
rows = view.getNodes();
count = store.getCount();
for (i = count - 1; i >= 0; i--) {
rowTop = Ext.fly(rows[i]).getOffsetsTo(view.el)[1];
// Searching for the last visible row, and off the top of the clientArea, then there's no visible last row!
if (rowTop < 0) {
return;
}
if (rowTop < clientHeight) {
return i + me.tableStart;
}
}
} else {
var end = me.getFirstVisibleRowIndex() + Math.ceil(clientHeight / me.rowHeight) + 1;
//console.log("getLastVisibleRowIndex: end=" + end + " clientHeight=" + clientHeight);
return end;
}
},
getScrollHeight: function () {
var me = this,
view = me.view,
table,
firstRow,
store = me.store,
deltaHeight = 0,
doCalcHeight = !me.hasOwnProperty('rowHeight');
if (me.variableRowHeight) {
table = me.view.el.down('table', true);
if (doCalcHeight) {
me.initialTableHeight = table.offsetHeight;
me.rowHeight = me.initialTableHeight / me.store.getCount();
} else {
deltaHeight = table.offsetHeight - me.initialTableHeight;
}
} else if (doCalcHeight) {
firstRow = view.el.down(view.getItemSelector());
if (firstRow) {
me.rowHeight = firstRow.getHeight(false, true);
}
}
var sh = Math.floor(store.getTotalCount() * me.rowHeight) + deltaHeight;;
// ~~~ workaround set scroll height;
// Constrain scroll at all times
// ~~~ workaround add some range so we can go a bit further for the end, 1000
sh = MAX_EL_HEIGHT;
return sh;
},
attemptLoad: function (start, end) {
var me = this;
if (me.scrollToLoadBuffer) {
if (!me.loadTask) {
me.loadTask = new Ext.util.DelayedTask(me.doAttemptLoad, me, []);
}
me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
} else {
me.store.guaranteeRange(start, end);
}
},
cancelLoad: function () {
if (this.loadTask) {
this.loadTask.cancel();
}
},
doAttemptLoad: function (start, end) {
this.store.guaranteeRange(start, end);
},
destroy: function () {
var me = this,
scrollListener = me.viewListeners.scroll;
me.store.un({
guaranteedrange: me.onGuaranteedRange,
scope: me
});
me.view.un(me.viewListeners);
if (me.view.rendered) {
me.stretcher.remove();
me.view.el.un('scroll', scrollListener.fn, scrollListener.scope);
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment