Created
September 28, 2019 18:20
-
-
Save branflake2267/b27de8b65e1f9756af372403103b6a1e to your computer and use it in GitHub Desktop.
Ext scrolling override - 500k
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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