/** * This class manages a page of records in a virtual store's `PageMap`. It is created * with the page `number` (0-based) and uses the store's `pageSize` to calculate the * record span covered by it and stores these as `begin` and `end` properties. These * aspects of the `Page` as well as the owning `PageMap` are expected to be immutable * throughout the instance's life-cycle. * * The normal use for a `Page` is by a `Range`. Ranges acquire and `lock` the pages they * span. As they move on, they `release` these locks. The act of locking pages schedules * them for loading. Unlocking pages allows them to be evicted from the `PageMap` to * reclaim memory for other pages. * * @private * @since 6.5.0 */Ext.define('Ext.data.virtual.Page', { isVirtualPage: true, /** * @property {Number} begin * The start index of the records that this page represents. * Inclusive. * @readonly */ begin: 0, /** * @property {Number} end * The end index of the records that this page represents. * Exclusive. * @readonly */ end: 0, /** * @property {Error} error * The error instance if the page load `operation` failed. If this property is set, * the `state` will be "error". * @readonly */ error: null, /** * @property {"active"/"prefetch"} locked * This property is managed by the `lock` and `release` methods. It is set to `null` * if the page is not locked or it will be set to the string describing the type of * the current lock. * * When pages are `locked` for the first time, they are scheduled for loading by the * owning `pageMap`. * * Locked pages are not eligible for removal from the `PageMap`. * @readonly */ locked: null, /** * @cfg {Number} number * The 0-based page number of this page. * @readonly */ number: 0, /** * @property {Ext.data.operation.Read} operation * The pending read of the records for this page. This property is only set when the * page is in the "loading" `state`. * @readonly */ operation: null, /** * @property {Ext.data.virtual.PageMap} pageMap * The owning `PageMap` instance. * @readonly */ pageMap: null, /** * @property {Ext.data.Model[]} records * The array of records loaded for this page. The `records[0]` item corresponds to * the record at index `begin` in the overall result set. * @readonly */ records: null, /** * @property {"loading"/"loaded"/"error"} state * This property describes the current life-cycle state for this page. At creation, * this property will be `null` for the "initial" state. * @readonly */ state: null, constructor: function(config) { var me = this, pageSize; Ext.apply(me, config); pageSize = me.pageMap.store.getPageSize(); me.begin = me.number * pageSize; me.end = me.begin + pageSize; me.locks = { active: 0, prefetch: 0 }; }, destroy: function() { var me = this, operation = me.operation; me.state = 'destroyed'; if (operation) { operation.abort(); } me.callParent(); }, /** * Acquires or releases the specified type of lock to this page. If this causes the * `locked` property to transition to a new value, the `pageMap` is informed to * enqueue or dequeue this page from the loading queues. * @param {"active"/"prefetch"} kind The type of lock. * @param {Number} delta A value of `1` to lock or `-1` to release. */ adjustLock: function(kind, delta) { var me = this, locks = me.locks, pageMap = me.pageMap, locked = null, lockedWas = me.locked; //<debug> if (!(kind in locks)) { Ext.raise('Bad lock type (expected "active" or "prefetch"): "' + kind + '"'); } if (delta !== 1 && delta !== -1) { Ext.raise('Invalid lock count delta (should be 1 or -1): ' + delta); } //</debug> locks[kind] += delta; if (locks.active) { locked = 'active'; } else if (locks.prefetch) { locked = 'prefetch'; } if (locked !== lockedWas) { me.locked = locked; if (pageMap) { pageMap.onPageLockChange(me, locked, lockedWas); } } }, clearRecords: function(out, by) { var me = this, begin = me.begin, records = me.records, i, n; // If we don't have records then fillRecords() could not have filled anything if (records) { n = records.length; if (by) { for (i = 0; i < n; ++i) { delete out[records[i][by]]; } } else { for (i = 0; i < n; ++i) { delete out[begin + i]; } } } }, fillRecords: function(out, by, withIndex) { var me = this, records = me.records, begin = me.begin, store = me.pageMap.store, i, n, record; if (records) { // The last page will not likely have a full page worth, so always // limit our loops by the actually available records... n = records.length; if (by) { for (i = 0; i < n; ++i) { record = records[i]; record.join(store); out[record[by]] = withIndex ? begin + i : record; } } else { for (i = 0; i < n; ++i) { records[i].join(store); out[begin + i] = records[i]; } } } }, isInitial: function() { return this.state === null; }, isLoaded: function() { return this.state === 'loaded'; }, isLoading: function() { return this.state === 'loading'; }, load: function(options) { var me = this, operation; me.state = 'loading'; operation = me.pageMap.store.loadVirtualPage(me, me.onLoad, me, options); // Memory proxy will synchronously load pages... if (me.state === 'loading') { me.operation = operation; } }, privates: { onLoad: function(operation) { var me = this; me.operation = null; if (!me.destroyed) { if (!(me.error = operation.getError())) { me.records = operation.getRecords(); me.state = 'loaded'; } else { me.state = 'error'; // TODO fireEvent or something from the store? } me.pageMap.onPageLoad(me); } } }});