/**
* This class provides access to a range of records in a {@link Ext.data.Store store}.
* Instances of this class are not created directly but are rather returned by a store's
* {@link Ext.data.AbstractStore#createActiveRange createActiveRange} method.
*
* This class is useful for buffered rendering where only a portion of the total set of
* records are needed. By passing that information to a `Range`, the access to records
* can be abstracted even across {@link Ext.data.virtual.Store virtual stores} where
* only those records needed by views are fetched from the server.
* @since 6.5.0
*/
Ext.define('Ext.data.Range', {
requires: [
'Ext.util.DelayedTask',
'Ext.Deferred'
],
isDataRange: true,
/**
* @cfg {Number} begin
* The first record index of interest.
*
* This property is set by the `goto` method and is stored on the instance for
* readonly use.
* @readonly
*/
begin: 0,
/**
* @cfg {Number} buffer
* The buffer to execute server requests.
*/
buffer: 0,
/**
* @cfg {Number} end
* The first record beyond the range of interest. This is to make "length" of the
* range simply `end - begin`.
*
* This property is set by the `goto` method and is stored on the instance for
* readonly use.
*/
end: 0,
/**
* @property (Number} length
* The number of records in the range of `[begin, end)`. This is equal to the
* difference `end - begin`.
*
* This property is maintained by the `goto` method and is stored on the instance for
* readonly use.
* @readonly
*/
length: 0,
/**
* @property {Ext.data.Model[]} records
* The records corresponding to the `begin` and `end` of this range. For normal
* stores this is the standard array of records.
*
* For a {@link Ext.data.virtual.Store virtual store} this is a sparse object of
* available records bounded by the limits of this range.
*
* In all cases, this object is keyed by the record index and (except for the
* `length` property) should be treated as an array.
* @readonly
*/
/**
* @cfg {Ext.data.AbstractStore} store
* The associated store. This config must be supplied at construction and cannot
* be changed after that time.
* @readonly
*/
store: null,
/**
* @cfg {Number} waitTimeout
* A timeout to wait for promises to complete returned from `goto`. `null` for
* the timeout to be infinite.
*/
waitTimeout: 10000,
// private
activeWait: null,
constructor: function(config) {
var me = this,
activeRanges, store;
Ext.apply(me, config);
store = me.store;
if (!(activeRanges = store.activeRanges)) {
store.activeRanges = activeRanges = [];
}
activeRanges.push(me);
me.refresh();
if ('begin' in config) {
me.begin = me.end = 0; // Applied on us above, so clear it
me.goto(config.begin, config.end).catch(function() {
// Do nothing, since this was passed as a config, we can't
// reasonably return a failure here
});
}
},
destroy: function() {
var me = this,
store = me.store,
activeRanges = store && store.activeRanges;
Ext.destroy(me.storeListeners);
if (activeRanges) {
Ext.Array.remove(activeRanges, me);
}
me.cancelActiveWait();
me.callParent();
},
/**
* Go to a particular point in the range.
* @param {Number} begin The start index, inclusive.
* @param {Number} end The end index, exclusive.
* @return Ext.Promise The promise. Resolves with `this` when the range is resolved
* successfully. Resolves with `null` if the range times out, or a new range is requested
* before the current one completes.
*/
goto: function(begin, end) {
var me = this,
buffer = me.buffer,
task = me.task,
promise;
me.begin = begin;
me.end = end;
me.length = end - begin;
me.cancelActiveWait();
me.activeWait = me.setupWait(begin, end);
// Need to assign this here. In a synchronous range, doGoto will resolve immediately.
promise = me.activeWait.promise;
me.resolveWaitIfSatisfied();
if (buffer > 0) {
if (!task) {
me.task = task = new Ext.util.DelayedTask(me.doGoto, me);
}
task.delay(buffer);
}
else {
me.doGoto();
}
return promise;
},
privates: {
lastBegin: 0,
lastEnd: 0,
cancelActiveWait: function() {
this.resolveWait(null);
},
doGoto: Ext.privateFn,
refresh: function() {
this.records = this.store.getData().items;
},
resolveWait: function(value) {
var wait = this.activeWait;
if (wait) {
wait.deferred.resolve(value);
this.activeWait = null;
}
return value;
},
resolveWaitIfSatisfied: function() {
this.resolveWait(this);
},
setupWait: function(begin, end) {
var me = this,
timeout = me.waitTimeout,
deferred = new Ext.Deferred(),
promise = deferred.promise;
if (timeout) {
promise = Ext.Deferred.timeout(promise, timeout).catch(function() {
return me.resolveWait(null);
});
}
return {
deferred: deferred,
promise: promise
};
}
}
});