ag-grid
Version:
Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components
270 lines (222 loc) • 9.42 kB
text/typescript
import {NumberSequence, Utils as _} from "../../utils";
import {RowNode} from "../../entities/rowNode";
import {Context} from "../../context/context";
import {BeanStub} from "../../context/beanStub";
import {RowNodeCacheParams} from "./rowNodeCache";
import {RowRenderer} from "../../rendering/rowRenderer";
import {AgEvent} from "../../events";
export interface RowNodeBlockBeans {
context: Context;
rowRenderer: RowRenderer;
}
export interface LoadCompleteEvent extends AgEvent {
success: boolean;
page: RowNodeBlock;
lastRow: number;
}
export abstract class RowNodeBlock extends BeanStub {
public static EVENT_LOAD_COMPLETE = 'loadComplete';
public static STATE_DIRTY = 'dirty';
public static STATE_LOADING = 'loading';
public static STATE_LOADED = 'loaded';
public static STATE_FAILED = 'failed';
private version = 0;
private state = RowNodeBlock.STATE_DIRTY;
private lastAccessed: number;
private readonly blockNumber: number;
private readonly startRow: number;
private readonly endRow: number;
public rowNodes: RowNode[];
// because the framework cannot wire beans in parent classes, this is a hack
// to pass bean references up. give out to niall for not getting an IoC context
// that can do this yet
private beans: RowNodeBlockBeans;
private rowNodeCacheParams: RowNodeCacheParams;
// gets base class to load, based on what the datasource type is
protected abstract loadFromDatasource(): void;
// how we set the data and id is also dependent ton the base class, as the server side row model
// is concerned with groups (so has to set keys for the group)
protected abstract setDataAndId(rowNode: RowNode, data: any, index: number): void;
// this gets the row using display indexes. for infinite scrolling, the
// local index is the same as the display index, so the override just calls
// getRowUsingLocalIndex(). however for server side row model, they are different, hence
// server side row model does logic before calling getRowUsingLocalIndex().
public abstract getRow(displayIndex: number): RowNode;
// returns the node id prefix, which is essentially the id of the cache
// that the block belongs to. this is used for debugging purposes, where the
// user can get the state of the cache, it lets us include what cache the block
// belongs to
public abstract getNodeIdPrefix(): string;
protected constructor(blockNumber: number, rowNodeCacheParams: RowNodeCacheParams) {
super();
this.rowNodeCacheParams = rowNodeCacheParams;
this.blockNumber = blockNumber;
// we don't need to calculate these now, as the inputs don't change,
// however it makes the code easier to read if we work them out up front
this.startRow = blockNumber * rowNodeCacheParams.blockSize;
this.endRow = this.startRow + rowNodeCacheParams.blockSize;
}
public isAnyNodeOpen(rowCount: number): boolean {
let result = false;
this.forEachNodeCallback( (rowNode: RowNode) => {
if (rowNode.expanded) {
result = true;
}
}, rowCount);
return result;
}
private forEachNodeCallback(callback: (rowNode: RowNode, index: number)=> void, rowCount: number): void {
for (let rowIndex = this.startRow; rowIndex < this.endRow; rowIndex++) {
// we check against rowCount as this page may be the last one, and if it is, then
// the last rows are not part of the set
if (rowIndex < rowCount) {
let rowNode = this.getRowUsingLocalIndex(rowIndex);
callback(rowNode, rowIndex);
}
}
}
private forEachNode(callback: (rowNode: RowNode, index: number)=> void, sequence: NumberSequence, rowCount: number, deep: boolean): void {
this.forEachNodeCallback( (rowNode: RowNode) => {
callback(rowNode, sequence.next());
// this will only every happen for server side row model, as infinite
// row model doesn't have groups
if (deep && rowNode.childrenCache) {
rowNode.childrenCache.forEachNodeDeep(callback, sequence);
}
}, rowCount);
}
public forEachNodeDeep(callback: (rowNode: RowNode, index: number)=> void, sequence: NumberSequence, rowCount: number): void {
this.forEachNode(callback, sequence, rowCount, true);
}
public forEachNodeShallow(callback: (rowNode: RowNode, index: number)=> void, sequence: NumberSequence, rowCount: number): void {
this.forEachNode(callback, sequence, rowCount, false);
}
public getVersion(): number {
return this.version;
}
public getLastAccessed(): number {
return this.lastAccessed;
}
public getRowUsingLocalIndex(rowIndex: number, dontTouchLastAccessed = false): RowNode {
if (!dontTouchLastAccessed) {
this.lastAccessed = this.rowNodeCacheParams.lastAccessedSequence.next();
}
let localIndex = rowIndex - this.startRow;
return this.rowNodes[localIndex];
}
protected init(beans: RowNodeBlockBeans): void {
this.beans = beans;
this.createRowNodes();
}
public getStartRow(): number {
return this.startRow;
}
public getEndRow(): number {
return this.endRow;
}
public getBlockNumber(): number {
return this.blockNumber;
}
public setDirty(): void {
// in case any current loads in progress, this will have their results ignored
this.version++;
this.state = RowNodeBlock.STATE_DIRTY;
}
public setDirtyAndPurge(): void {
this.setDirty();
this.rowNodes.forEach( rowNode => {
rowNode.setData(null);
});
}
public getState(): string {
return this.state;
}
public setRowNode(rowIndex: number, rowNode: RowNode): void {
let localIndex = rowIndex - this.startRow;
this.rowNodes[localIndex] = rowNode;
}
public setBlankRowNode(rowIndex: number): RowNode {
let localIndex = rowIndex - this.startRow;
let newRowNode = this.createBlankRowNode(rowIndex);
this.rowNodes[localIndex] = newRowNode;
return newRowNode;
}
public setNewData(rowIndex: number, dataItem: any): RowNode {
let newRowNode = this.setBlankRowNode(rowIndex);
this.setDataAndId(newRowNode, dataItem, this.startRow + rowIndex);
return newRowNode;
}
protected createBlankRowNode(rowIndex: number): RowNode {
let rowNode = new RowNode();
this.beans.context.wireBean(rowNode);
rowNode.setRowHeight(this.rowNodeCacheParams.rowHeight);
return rowNode;
}
// creates empty row nodes, data is missing as not loaded yet
protected createRowNodes(): void {
this.rowNodes = [];
for (let i = 0; i < this.rowNodeCacheParams.blockSize; i++) {
let rowIndex = this.startRow + i;
let rowNode = this.createBlankRowNode(rowIndex);
this.rowNodes.push(rowNode);
}
}
public load(): void {
this.state = RowNodeBlock.STATE_LOADING;
this.loadFromDatasource();
}
protected pageLoadFailed() {
this.state = RowNodeBlock.STATE_FAILED;
let event: LoadCompleteEvent = {
type: RowNodeBlock.EVENT_LOAD_COMPLETE,
success: false,
page: this,
lastRow: null
};
this.dispatchEvent(event);
}
private populateWithRowData(rows: any[]): void {
let rowNodesToRefresh: RowNode[] = [];
this.rowNodes.forEach( (rowNode: RowNode, index: number)=> {
let data = rows[index];
if (rowNode.stub) {
rowNodesToRefresh.push(rowNode);
}
this.setDataAndId(rowNode, data, this.startRow + index);
});
if (rowNodesToRefresh.length > 0) {
this.beans.rowRenderer.redrawRows(rowNodesToRefresh);
}
}
public destroy(): void {
super.destroy();
this.rowNodes.forEach( rowNode => {
if (rowNode.childrenCache) {
rowNode.childrenCache.destroy();
rowNode.childrenCache = null;
}
// this is needed, so row render knows to fade out the row, otherwise it
// see's row top is present, and thinks the row should be shown. maybe
// rowNode should have a flag on whether it is visible???
rowNode.clearRowTop();
});
}
protected pageLoaded(version: number, rows: any[], lastRow: number) {
// we need to check the version, in case there was an old request
// from the server that was sent before we refreshed the cache,
// if the load was done as a result of a cache refresh
if (version===this.version) {
this.state = RowNodeBlock.STATE_LOADED;
this.populateWithRowData(rows);
}
lastRow = _.cleanNumber(lastRow);
// check here if lastrow should be set
let event: LoadCompleteEvent = {
type: RowNodeBlock.EVENT_LOAD_COMPLETE,
success: true,
page: this,
lastRow: lastRow
};
this.dispatchEvent(event);
}
}