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);
    }
}