/**
*
* @since 6.5.0
*/
Ext.define('Ext.data.virtual.Range', {
extend: 'Ext.data.Range',
isVirtualRange: true,
/**
* @cfg {String/Function} callback
* The callback to call when new records in this range become available.
*/
callback: null,
/**
* @cfg {Number} leadingBufferZone
* The number of records to fetch beyond the active range in the direction of movement.
* If the range is advancing forward, the additional records are beyond `end`. If
* advancing backwards, they are before `begin`.
*/
/**
* @cfg {Boolean} prefetch
* Specify `true` to enable prefetching for this range.
*/
prefetch: false,
/**
* @cfg {Object} scope
* The object that implements the supplied `callback` method.
*/
scope: null,
/**
* @cfg {Number} trailingBufferZone
* The number of records to fetch beyond the active trailing the direction of movement.
* If the range is advancing forward, the additional records are before `begin`. If
* advancing backwards, they are beyond `end`.
*/
/**
* @property {Number} direction
* This property is set to `1` if the range was last moved forward and `-1` if it
* was last moved backwards. This value is used to determine the "leading" and
* "trailing" buffer zones.
* @private
*/
direction: 1,
constructor: function(config) {
this.adjustingPages = [];
this.callParent([config]);
},
reset: function() {
var me = this;
me.records = {};
me.activePages = me.prefetchPages = null;
},
privates: {
adjustPageLocks: function(kind, adjustment) {
var me = this,
pages = me.adjustingPages,
n = pages.length,
i;
// Consider:
//
// -->
// --+----------+==========+--------------------------+----
// ... | trailing | active | leading | ...
// --+----------+==========+--------------------------+----
//
// :------: :------: :++++++: :++++++:
//
// ---------+----------+==========+--------------------------+----
// ... | trailing | active | leading | ...
// ---------+----------+==========+--------------------------+----
//
// The newly released pages (esp the prefetch pages) should be released
// such that the closest ones are MRU vs the farthest ones. Releasing
// them in forward order will do that.
//
// New consider:
//
// <--
// ---------+--------------------------+==========+----------+----
// ... | leading | active | trailing | ...
// ---------+--------------------------+==========+----------+----
//
// :++++++: :++++++: :------: :------:
//
// --+--------------------------+==========+----------+---------
// ... | leading | active | trailing | ...
// --+--------------------------+==========+----------+---------
//
// When going backwards, we want to release the pages backwards (we'll
// just sort them that way). That way the page with least index will be
// released last and be MRU than the others.
if (n > 1) {
// Since pages are in objects keyed by page number, there is no
// order during our set operations... so we sort the array now by
// page number (ordered by our current direction).
pages.sort(me.direction < 0 ? me.pageSortBackFn : me.pageSortFwdFn);
}
for (i = 0; i < n; ++i) {
pages[i].adjustLock(kind, adjustment);
}
pages.length = 0;
},
doGoto: function() {
var me = this,
begin = me.begin,
end = me.end,
prefetch = me.prefetch,
records = me.records,
store = me.store,
pageMap = store.pageMap,
limit = store.totalCount,
beginWas = me.lastBegin,
endWas = me.lastEnd,
activePagesWas = me.activePages,
prefetchPagesWas = me.prefetchPages,
beginBufferZone = me.trailingBufferZone,
endBufferZone = me.leadingBufferZone,
adjustingPages = me.adjustingPages,
activePages, page, pg, direction,
prefetchBegin, prefetchEnd, prefetchPages;
adjustingPages.length = 0;
// Forwards
//
// Most likely case:
//
// beginWas endWas
// |=================|
// :---: :+++:
// |=================|
// begin end
//
// Big step forwards:
//
// beginWas endWas
// |=================|
// :-----------------: :+++++++++++++++++:
// |=================|
// begin end
//
// Interesting case:
//
// beginWas endWas
// |=================|
// :---: :---:
// |=========|
// begin end
// Backwards
//
// Most likely case:
//
// beginWas endWas
// |=================|
// :++: :--:
// |=================|
// begin end
//
// Big step back:
// beginWas endWas
// |=================|
// :+++++++++++++++++: :-----------------:
// |=================|
// begin end
//
// Interesting case:
//
// beginWas endWas
// |=================|
// :++: :++:
// |=======================|
// begin end
// Retain the direction if narrowing or widening the range
if ((begin > beginWas && end < endWas) || (begin < beginWas && end > endWas)) {
direction = me.direction;
}
else {
direction = (begin < beginWas) ? -1 : ((begin > beginWas) ? 1 : me.direction);
}
if (direction < 0) { // if (backwards)
pg = beginBufferZone;
beginBufferZone = endBufferZone;
endBufferZone = pg;
}
me.direction = direction;
me.activePages = activePages = pageMap.getPages(begin, end);
if (prefetch) {
me.prefetchBegin = prefetchBegin = Math.max(0, begin - beginBufferZone);
// If we don't know the size of the store yet, don't try and limit the pages
if (limit === null) {
limit = Number.MAX_VALUE;
}
me.prefetchEnd = prefetchEnd = Math.min(limit, end + endBufferZone);
me.prefetchPages = prefetchPages = pageMap.getPages(prefetchBegin, prefetchEnd);
}
// In set terms we want to do this:
//
// A = activePages
// Aw = activePagesWas
// P = prefetchPages
// Pw = prefetchPagesWas
//
// P -= A; (activePages start out also in prefetchPages)
//
// foreach page p in (A - Aw), p.lock('active') and p.fill(records)
//
// foreach page p in (P - Pw), p.lock('prefetch')
//
// foreach page p in (Aw - A), p.release('active') and p.clear(records)
//
// foreach page p in (Pw - P), p.release('prefetch')
//
for (pg in activePages) {
page = activePages[pg];
// Any pages that we will be actively locking, we don't want to mark as
// prefetch:
if (prefetchPages) {
delete prefetchPages[pg];
}
if (activePagesWas && pg in activePagesWas) {
// We will unlock any activePages we no longer need so remove
// those we will be keeping:
delete activePagesWas[pg];
}
else {
// For pages that weren't previously active, lock them now.
page.adjustLock('active', 1);
page.fillRecords(records);
}
}
if (prefetchPages) {
for (pg in prefetchPages) {
if (prefetchPagesWas && pg in prefetchPagesWas) {
// If page was previously locked for prefetch, we don't want to
// release it...
delete prefetchPagesWas[pg];
}
else {
prefetchPages[pg].adjustLock('prefetch', 1);
}
}
}
// What's left in our "was" maps are those active or prefetch pages that we
// previously had need of but no longer need them in that same way. Release
// our previously prefetched pages first in case this is their final lock (we
// want them to be retained but at a lower priority then previously active
// pages).
if (prefetchPagesWas) {
for (pg in prefetchPagesWas) {
adjustingPages.push(prefetchPagesWas[pg]);
}
if (adjustingPages.length) {
me.adjustPageLocks('prefetch', -1);
}
}
if (activePagesWas) {
for (pg in activePagesWas) {
adjustingPages.push(page = activePagesWas[pg]);
page.clearRecords(records);
}
if (adjustingPages.length) {
me.adjustPageLocks('active', -1);
}
}
if (prefetchPages) {
pageMap.prioritizePrefetch(direction, pageMap.getPageIndex(begin),
pageMap.getPageIndex(end - 1));
}
me.lastBegin = begin;
me.lastEnd = end;
// This can occur if the store reloads empty. As such, trigger the callback
// immediately since there won't be any loads.
if (begin === end && begin === 0) {
this.triggerCallback(0, 0);
}
},
onPageDestroy: function(page) {
var n = page.number,
activePages = this.activePages,
prefetchPages = this.prefetchPages;
if (activePages) {
delete activePages[n];
}
if (prefetchPages) {
delete prefetchPages[n];
}
},
onPageLoad: function(page) {
var me = this,
wait = me.activeWait,
first, last;
if (me.activePages && me.activePages[page.number]) {
page.fillRecords(me.records);
// Clip the range to our actually active range for the sake of
// the user:
first = Math.max(me.begin, page.begin);
last = Math.min(me.end, page.end);
me.triggerCallback(first, last);
if (wait) {
wait.got++;
me.resolveWaitIfSatisfied();
}
}
},
pageSortBackFn: function(page1, page2) {
return page2.number - page1.number;
},
pageSortFwdFn: function(page1, page2) {
return page1.number - page2.number;
},
refresh: function() {
// ... we don't want to reset this.records
this.records = this.records || {};
},
reload: function() {
var me = this,
begin = me.begin,
end = me.end;
me.begin = me.end = 0;
me.direction = 1;
me.prefetchPages = me.activePages = null;
me.goto(begin, end);
},
resolveWaitIfSatisfied: function() {
var wait = this.activeWait;
if (wait && wait.got === wait.needed) {
this.resolveWait(this);
}
},
setupWait: function(begin, end) {
var result = this.callParent([begin, end]),
pages = this.store.pageMap.getPages(begin, end),
needed = 0,
p;
for (p in pages) {
needed += pages[p].isLoaded() ? 0 : 1;
}
result.got = 0;
result.needed = needed;
return result;
},
triggerCallback: function(first, last) {
var callback = this.callback;
if (callback) {
Ext.callback(callback, this.scope, [this, first, last]);
}
}
}
});