UNPKG

ag-grid-enterprise

Version:

ag-Grid Enterprise Features

465 lines (372 loc) 16.2 kB
import { _, Autowired, Context, RowRenderer, IServerSideGetRowsParams, IServerSideGetRowsRequest, Logger, NumberSequence, PostConstruct, RowNodeBlock, LoggerFactory, Qualifier, RowNode, Column, ColumnController, ValueService, GridOptionsWrapper, RowBounds } from "ag-grid-community"; import {ServerSideCache, ServerSideCacheParams} from "./serverSideCache"; export class ServerSideBlock extends RowNodeBlock { @Autowired('context') private context: Context; @Autowired('rowRenderer') private rowRenderer: RowRenderer; @Autowired('columnController') private columnController: ColumnController; @Autowired('valueService') private valueService: ValueService; @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper; private logger: Logger; private displayIndexStart: number; private displayIndexEnd: number; private blockTop: number; private blockHeight: number; private params: ServerSideCacheParams; private parentCache: ServerSideCache; private parentRowNode: RowNode; private level: number; private groupLevel: boolean; private leafGroup: boolean; private groupField: string; private rowGroupColumn: Column; private nodeIdPrefix: string; private usingTreeData: boolean; constructor(pageNumber: number, parentRowNode: RowNode, params: ServerSideCacheParams, parentCache: ServerSideCache) { super(pageNumber, params); this.params = params; this.parentRowNode = parentRowNode; this.parentCache = parentCache; this.level = parentRowNode.level + 1; this.groupLevel = params.rowGroupCols ? this.level < params.rowGroupCols.length : undefined; this.leafGroup = params.rowGroupCols ? this.level === params.rowGroupCols.length - 1 : false; } @PostConstruct protected init(): void { this.usingTreeData = this.gridOptionsWrapper.isTreeData(); if (!this.usingTreeData && this.groupLevel) { let groupColVo = this.params.rowGroupCols[this.level]; this.groupField = groupColVo.field; this.rowGroupColumn = this.columnController.getRowGroupColumns()[this.level]; } this.createNodeIdPrefix(); super.init({ context: this.context, rowRenderer: this.rowRenderer }); } private setBeans(@Qualifier('loggerFactory') loggerFactory: LoggerFactory) { this.logger = loggerFactory.create('ServerSideBlock'); } private createNodeIdPrefix(): void { let parts: string[] = []; let rowNode = this.parentRowNode; // pull keys from all parent nodes, but do not include the root node while (rowNode.level >= 0) { parts.push(rowNode.key); rowNode = rowNode.parent; } if (parts.length>0) { this.nodeIdPrefix = parts.reverse().join('-') + '-'; } } protected createIdForIndex(index: number): string { if (_.exists(this.nodeIdPrefix)) { return this.nodeIdPrefix + index.toString(); } else { return index.toString(); } } public getNodeIdPrefix(): string { return this.nodeIdPrefix; } public getRow(displayRowIndex: number): RowNode { // do binary search of tree // http://oli.me.uk/2013/06/08/searching-javascript-arrays-with-a-binary-search/ let bottomPointer = this.getStartRow(); // the end row depends on whether all this block is used or not. if the virtual row count // is before the end, then not all the row is used let virtualRowCount = this.parentCache.getVirtualRowCount(); let endRow = this.getEndRow(); let actualEnd = (virtualRowCount < endRow) ? virtualRowCount : endRow; let topPointer = actualEnd - 1; if (_.missing(topPointer) || _.missing(bottomPointer)) { console.warn(`ag-grid: error: topPointer = ${topPointer}, bottomPointer = ${bottomPointer}`); return null; } while (true) { let midPointer = Math.floor((bottomPointer + topPointer) / 2); let currentRowNode = super.getRowUsingLocalIndex(midPointer); if (currentRowNode.rowIndex === displayRowIndex) { return currentRowNode; } let childrenCache = <ServerSideCache> currentRowNode.childrenCache; if (currentRowNode.rowIndex === displayRowIndex) { return currentRowNode; } else if (currentRowNode.expanded && childrenCache && childrenCache.isDisplayIndexInCache(displayRowIndex)) { return childrenCache.getRow(displayRowIndex); } else if (currentRowNode.rowIndex < displayRowIndex) { bottomPointer = midPointer + 1; } else if (currentRowNode.rowIndex > displayRowIndex) { topPointer = midPointer - 1; } } } protected setDataAndId(rowNode: RowNode, data: any, index: number): void { rowNode.stub = false; if (_.exists(data)) { // if the user is not providing id's, then we build an id based on the index. // for infinite scrolling, the index is used on it's own. for Server Side Row Model, // we combine the index with the level and group key, so that the id is // unique across the set. // // unique id is needed for selection (so selection can be maintained when // doing server side sorting / filtering) - if user is not providing id's // (and we use the indexes) then selection will not work between sorting & // filtering. // // id's are also used by the row renderer for updating the dom as it identifies // rowNodes by id let idToUse = this.createIdForIndex(index); rowNode.setDataAndId(data, idToUse); rowNode.setRowHeight(this.gridOptionsWrapper.getRowHeightForNode(rowNode)); if (this.usingTreeData) { let getServerSideGroupKey = this.gridOptionsWrapper.getServerSideGroupKeyFunc(); if (_.exists(getServerSideGroupKey)) { rowNode.key = getServerSideGroupKey(rowNode.data); } let isServerSideGroup = this.gridOptionsWrapper.getIsServerSideGroupFunc(); if (_.exists(isServerSideGroup)) { rowNode.group = isServerSideGroup(rowNode.data); } } else if (rowNode.group) { rowNode.key = this.valueService.getValue(this.rowGroupColumn, rowNode); if (rowNode.key===null || rowNode.key===undefined) { _.doOnce( ()=> { console.warn(`null and undefined values are not allowed for server side row model keys`); if (this.rowGroupColumn) { console.warn(`column = ${this.rowGroupColumn.getId()}`);} console.warn(`data is `, rowNode.data); }, 'ServerSideBlock-CannotHaveNullOrUndefinedForKey'); } } } else { rowNode.setDataAndId(undefined, undefined); rowNode.key = null; } if (this.usingTreeData || this.groupLevel) { this.setGroupDataIntoRowNode(rowNode); this.setChildCountIntoRowNode(rowNode); } } private setChildCountIntoRowNode(rowNode: RowNode): void { let getChildCount = this.gridOptionsWrapper.getChildCountFunc(); if (getChildCount) { rowNode.allChildrenCount = getChildCount(rowNode.data); } } private setGroupDataIntoRowNode(rowNode: RowNode): void { let groupDisplayCols: Column[] = this.columnController.getGroupDisplayColumns(); let usingTreeData = this.gridOptionsWrapper.isTreeData(); groupDisplayCols.forEach(col => { if (usingTreeData) { if (_.missing(rowNode.groupData)) { rowNode.groupData = {}; } rowNode.groupData[col.getColId()] = rowNode.key; } else if (col.isRowGroupDisplayed(this.rowGroupColumn.getId())) { let groupValue = this.valueService.getValue(this.rowGroupColumn, rowNode); if (_.missing(rowNode.groupData)) { rowNode.groupData = {}; } rowNode.groupData[col.getColId()] = groupValue; } }); } protected loadFromDatasource(): void { let params = this.createLoadParams(); setTimeout(()=> { this.params.datasource.getRows(params); }, 0); } protected createBlankRowNode(rowIndex: number): RowNode { let rowNode = super.createBlankRowNode(rowIndex); rowNode.group = this.groupLevel; rowNode.leafGroup = this.leafGroup; rowNode.level = this.level; rowNode.uiLevel = this.level; rowNode.parent = this.parentRowNode; // stub gets set to true here, and then false when this rowNode gets it's data rowNode.stub = true; if (rowNode.group) { rowNode.expanded = false; rowNode.field = this.groupField; rowNode.rowGroupColumn = this.rowGroupColumn; } return rowNode; } private createGroupKeys(groupNode: RowNode): string[] { let keys: string[] = []; let pointer = groupNode; while (pointer.level >= 0) { keys.push(pointer.key); pointer = pointer.parent; } keys.reverse(); return keys; } public isPixelInRange(pixel: number): boolean { return pixel >= this.blockTop && pixel < (this.blockTop + this.blockHeight); } public getRowBounds(index: number, virtualRowCount: number): RowBounds { let start = this.getStartRow(); let end = this.getEndRow(); for (let i = start; i<=end; i++) { // the blocks can have extra rows in them, if they are the last block // in the cache and the virtual row count doesn't divide evenly by the if (i >= virtualRowCount) { continue; } let rowNode = this.getRowUsingLocalIndex(i); if (rowNode) { if (rowNode.rowIndex === index) { return { rowHeight: rowNode.rowHeight, rowTop: rowNode.rowTop }; } if (rowNode.group && rowNode.expanded && _.exists(rowNode.childrenCache)) { let serverSideCache = <ServerSideCache> rowNode.childrenCache; if (serverSideCache.isDisplayIndexInCache(index)) { return serverSideCache.getRowBounds(index); } } } } console.error(`ag-Grid: looking for invalid row index in Server Side Row Model, index=${index}`); return null; } public getRowIndexAtPixel(pixel: number, virtualRowCount: number): number { let start = this.getStartRow(); let end = this.getEndRow(); for (let i = start; i<=end; i++) { // the blocks can have extra rows in them, if they are the last block // in the cache and the virtual row count doesn't divide evenly by the if (i >= virtualRowCount) { continue; } let rowNode = this.getRowUsingLocalIndex(i); if (rowNode) { if (rowNode.isPixelInRange(pixel)) { return rowNode.rowIndex; } if (rowNode.group && rowNode.expanded && _.exists(rowNode.childrenCache)) { let serverSideCache = <ServerSideCache> rowNode.childrenCache; if (serverSideCache.isPixelInRange(pixel)) { return serverSideCache.getRowIndexAtPixel(pixel); } } } } console.warn(`ag-Grid: invalid pixel range for server side block ${pixel}`); return 0; } public clearRowTops(virtualRowCount: number): void { this.forEachRowNode(virtualRowCount, rowNode => { rowNode.clearRowTop(); let hasChildCache = rowNode.group && _.exists(rowNode.childrenCache); if (hasChildCache) { let serverSideCache = <ServerSideCache> rowNode.childrenCache; serverSideCache.clearRowTops(); } }); } public setDisplayIndexes(displayIndexSeq: NumberSequence, virtualRowCount: number, nextRowTop: {value: number}): void { this.displayIndexStart = displayIndexSeq.peek(); this.blockTop = nextRowTop.value; this.forEachRowNode(virtualRowCount, rowNode => { let rowIndex = displayIndexSeq.next(); rowNode.setRowIndex(rowIndex); rowNode.setRowTop(nextRowTop.value); nextRowTop.value += rowNode.rowHeight; let hasChildCache = rowNode.group && _.exists(rowNode.childrenCache); if (hasChildCache) { let serverSideCache = <ServerSideCache> rowNode.childrenCache; if (rowNode.expanded) { serverSideCache.setDisplayIndexes(displayIndexSeq, nextRowTop); } else { // we need to clear the row tops, as the row renderer depends on // this to know if the row should be faded out serverSideCache.clearRowTops(); } } }); this.displayIndexEnd = displayIndexSeq.peek(); this.blockHeight = nextRowTop.value - this.blockTop; } private forEachRowNode(virtualRowCount: number, callback: (rowNode: RowNode)=>void): void { let start = this.getStartRow(); let end = this.getEndRow(); for (let i = start; i<=end; i++) { // the blocks can have extra rows in them, if they are the last block // in the cache and the virtual row count doesn't divide evenly by the if (i >= virtualRowCount) { continue; } let rowNode = this.getRowUsingLocalIndex(i); if (rowNode) { callback(rowNode); } } } private createLoadParams(): IServerSideGetRowsParams { let groupKeys = this.createGroupKeys(this.parentRowNode); let request: IServerSideGetRowsRequest = { startRow: this.getStartRow(), endRow: this.getEndRow(), rowGroupCols: this.params.rowGroupCols, valueCols: this.params.valueCols, pivotCols: this.params.pivotCols, pivotMode: this.params.pivotMode, groupKeys: groupKeys, filterModel: this.params.filterModel, sortModel: this.params.sortModel }; let params = <IServerSideGetRowsParams> { successCallback: this.pageLoaded.bind(this, this.getVersion()), failCallback: this.pageLoadFailed.bind(this), request: request, parentNode: this.parentRowNode }; return params; } public isDisplayIndexInBlock(displayIndex: number): boolean { return displayIndex >= this.displayIndexStart && displayIndex < this.displayIndexEnd; } public isBlockBefore(displayIndex: number): boolean { return displayIndex >= this.displayIndexEnd; } public getDisplayIndexStart(): number { return this.displayIndexStart; } public getDisplayIndexEnd(): number { return this.displayIndexEnd; } public getBlockHeight(): number { return this.blockHeight; } public getBlockTop(): number { return this.blockTop; } public isGroupLevel(): boolean { return this.groupLevel; } public getGroupField(): string { return this.groupField; } }