UNPKG

ag-grid

Version:

Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components

367 lines (305 loc) 13.2 kB
import {NumberSequence, Utils as _} from "../../utils"; import {GridOptionsWrapper} from "../../gridOptionsWrapper"; import {RowNode} from "../../entities/rowNode"; import {Autowired, Bean, Context, PostConstruct, PreDestroy} from "../../context/context"; import {EventService} from "../../eventService"; import {SelectionController} from "../../selectionController"; import {IRowModel, RowBounds} from "../../interfaces/iRowModel"; import {Events, ModelUpdatedEvent} from "../../events"; import {SortController} from "../../sortController"; import {FilterManager} from "../../filter/filterManager"; import {Constants} from "../../constants"; import {IDatasource} from "../iDatasource"; import {InfiniteCache, InfiniteCacheParams} from "./infiniteCache"; import {BeanStub} from "../../context/beanStub"; import {RowNodeCache} from "../cache/rowNodeCache"; import {RowNodeBlockLoader} from "../cache/rowNodeBlockLoader"; import {RowDataTransaction} from "../clientSide/clientSideRowModel"; import {GridApi} from "../../gridApi"; import {ColumnApi} from "../../columnController/columnApi"; @Bean('rowModel') export class InfiniteRowModel extends BeanStub implements IRowModel { @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper; @Autowired('filterManager') private filterManager: FilterManager; @Autowired('sortController') private sortController: SortController; @Autowired('selectionController') private selectionController: SelectionController; @Autowired('eventService') private eventService: EventService; @Autowired('context') private context: Context; @Autowired('gridApi') private gridApi: GridApi; @Autowired('columnApi') private columnApi: ColumnApi; private infiniteCache: InfiniteCache; private rowNodeBlockLoader: RowNodeBlockLoader; private datasource: IDatasource; private rowHeight: number; public getRowBounds(index: number): RowBounds { return { rowHeight: this.rowHeight, rowTop: this.rowHeight * index }; } @PostConstruct public init(): void { if (!this.gridOptionsWrapper.isRowModelInfinite()) { return; } this.rowHeight = this.gridOptionsWrapper.getRowHeightAsNumber(); this.addEventListeners(); this.setDatasource(this.gridOptionsWrapper.getDatasource()); this.addDestroyFunc( () => this.destroyCache() ); } @PreDestroy private destroyDatasource(): void { if (this.datasource && this.datasource.destroy) { this.datasource.destroy(); } this.datasource = null; } public isLastRowFound(): boolean { return this.infiniteCache ? this.infiniteCache.isMaxRowFound() : false; } private addEventListeners(): void { this.addDestroyableEventListener(this.eventService, Events.EVENT_FILTER_CHANGED, this.onFilterChanged.bind(this)); this.addDestroyableEventListener(this.eventService, Events.EVENT_SORT_CHANGED, this.onSortChanged.bind(this)); this.addDestroyableEventListener(this.eventService, Events.EVENT_COLUMN_EVERYTHING_CHANGED, this.onColumnEverything.bind(this)); } private onFilterChanged(): void { if (this.gridOptionsWrapper.isEnableServerSideFilter()) { this.reset(); } } private onSortChanged(): void { if (this.gridOptionsWrapper.isEnableServerSideSorting()) { this.reset(); } } private onColumnEverything(): void { // if the columns get reset, then this means the sort order could be impacted if (this.gridOptionsWrapper.isEnableServerSideSorting()) { this.reset(); } } @PreDestroy public destroy(): void { super.destroy(); } public getType(): string { return Constants.ROW_MODEL_TYPE_INFINITE; } public setDatasource(datasource: IDatasource): void { this.destroyDatasource(); this.datasource = datasource; // only reset if we have a valid datasource to working with if (datasource) { this.checkForDeprecated(); this.reset(); } } private checkForDeprecated(): void { let ds = <any> this.datasource; // the number of concurrent loads we are allowed to the server if (_.exists(ds.maxConcurrentRequests)) { console.error('ag-Grid: since version 5.1.x, maxConcurrentRequests is replaced with grid property maxConcurrentDatasourceRequests'); } if (_.exists(ds.maxPagesInCache)) { console.error('ag-Grid: since version 5.1.x, maxPagesInCache is replaced with grid property maxPagesInPaginationCache'); } if (_.exists(ds.overflowSize)) { console.error('ag-Grid: since version 5.1.x, overflowSize is replaced with grid property paginationOverflowSize'); } if (_.exists(ds.blockSize)) { console.error('ag-Grid: since version 5.1.x, pageSize/blockSize is replaced with grid property infinitePageSize'); } } public isEmpty(): boolean { return _.missing(this.infiniteCache); } public isRowsToRender(): boolean { return _.exists(this.infiniteCache); } public getNodesInRangeForSelection(firstInRange: RowNode, lastInRange: RowNode): RowNode[] { return this.infiniteCache.getRowNodesInRange(firstInRange, lastInRange); } private reset() { // important to return here, as the user could be setting filter or sort before // data-source is set if (_.missing(this.datasource)) { return; } // if user is providing id's, then this means we can keep the selection between datsource hits, // as the rows will keep their unique id's even if, for example, server side sorting or filtering // is done. let userGeneratingIds = _.exists(this.gridOptionsWrapper.getRowNodeIdFunc()); if (!userGeneratingIds) { this.selectionController.reset(); } this.resetCache(); let event: ModelUpdatedEvent = this.createModelUpdatedEvent(); this.eventService.dispatchEvent(event); } private createModelUpdatedEvent(): ModelUpdatedEvent { return { type: Events.EVENT_MODEL_UPDATED, api: this.gridApi, columnApi: this.columnApi, // not sure if these should all be false - noticed if after implementing, // maybe they should be true? newPage: false, newData: false, keepRenderedRows: false, animate: false }; } private resetCache(): void { // if not first time creating a cache, need to destroy the old one this.destroyCache(); let maxConcurrentRequests = this.gridOptionsWrapper.getMaxConcurrentDatasourceRequests(); // there is a bi-directional dependency between the loader and the cache, // so we create loader here, and then pass dependencies in setDependencies() method later this.rowNodeBlockLoader = new RowNodeBlockLoader(maxConcurrentRequests); this.context.wireBean(this.rowNodeBlockLoader); let cacheSettings = <InfiniteCacheParams> { // the user provided datasource datasource: this.datasource, // sort and filter model filterModel: this.filterManager.getFilterModel(), sortModel: this.sortController.getSortModel(), rowNodeBlockLoader: this.rowNodeBlockLoader, // properties - this way we take a snapshot of them, so if user changes any, they will be // used next time we create a new cache, which is generally after a filter or sort change, // or a new datasource is set maxConcurrentRequests: maxConcurrentRequests, overflowSize: this.gridOptionsWrapper.getCacheOverflowSize(), initialRowCount: this.gridOptionsWrapper.getInfiniteInitialRowCount(), maxBlocksInCache: this.gridOptionsWrapper.getMaxBlocksInCache(), blockSize: this.gridOptionsWrapper.getCacheBlockSize(), rowHeight: this.gridOptionsWrapper.getRowHeightAsNumber(), // the cache could create this, however it is also used by the pages, so handy to create it // here as the settings are also passed to the pages lastAccessedSequence: new NumberSequence() }; // set defaults if ( !(cacheSettings.maxConcurrentRequests>=1) ) { cacheSettings.maxConcurrentRequests = 2; } // page size needs to be 1 or greater. having it at 1 would be silly, as you would be hitting the // server for one page at a time. so the default if not specified is 100. if ( !(cacheSettings.blockSize>=1) ) { cacheSettings.blockSize = 100; } // if user doesn't give initial rows to display, we assume zero if ( !(cacheSettings.initialRowCount>=1) ) { cacheSettings.initialRowCount = 0; } // if user doesn't provide overflow, we use default overflow of 1, so user can scroll past // the current page and request first row of next page if ( !(cacheSettings.overflowSize>=1) ) { cacheSettings.overflowSize = 1; } this.infiniteCache = new InfiniteCache(cacheSettings); this.context.wireBean(this.infiniteCache); this.infiniteCache.addEventListener(RowNodeCache.EVENT_CACHE_UPDATED, this.onCacheUpdated.bind(this)); } private destroyCache(): void { if (this.infiniteCache) { this.infiniteCache.destroy(); this.infiniteCache = null; } if (this.rowNodeBlockLoader) { this.rowNodeBlockLoader.destroy(); this.rowNodeBlockLoader = null; } } private onCacheUpdated(): void { let event: ModelUpdatedEvent = this.createModelUpdatedEvent(); this.eventService.dispatchEvent(event); } public getRow(rowIndex: number): RowNode { return this.infiniteCache ? this.infiniteCache.getRow(rowIndex) : null; } public getRowNode(id: string): RowNode { let result: RowNode = null; this.forEachNode(rowNode => { if(rowNode.id === id) { result = rowNode; } }); return result; } public forEachNode(callback: (rowNode: RowNode, index: number)=> void): void { if (this.infiniteCache) { this.infiniteCache.forEachNodeDeep(callback, new NumberSequence()); } } public getCurrentPageHeight(): number { return this.getRowCount() * this.rowHeight; } public getRowIndexAtPixel(pixel: number): number { if (this.rowHeight !== 0) { // avoid divide by zero error let rowIndexForPixel = Math.floor(pixel / this.rowHeight); if (rowIndexForPixel > this.getPageLastRow()) { return this.getPageLastRow(); } else { return rowIndexForPixel; } } else { return 0; } } public getPageFirstRow(): number { return 0; } public getPageLastRow(): number { return this.infiniteCache ? this.infiniteCache.getVirtualRowCount() -1 : 0; } public getRowCount(): number { return this.infiniteCache ? this.infiniteCache.getVirtualRowCount() : 0; } public updateRowData(transaction: RowDataTransaction): void { if (_.exists(transaction.remove) || _.exists(transaction.update) ) { console.warn('ag-Grid: updateRowData for InfiniteRowModel does not support remove or update, only add'); return; } if (_.missing(transaction.addIndex)) { console.warn('ag-Grid: updateRowData for InfiniteRowModel requires add and addIndex to be set'); return; } if (this.infiniteCache) { this.infiniteCache.insertItemsAtIndex(transaction.addIndex, transaction.add); } } public isRowPresent(rowNode: RowNode): boolean { return false; } public refreshCache(): void { if (this.infiniteCache) { this.infiniteCache.refreshCache(); } } public purgeCache(): void { if (this.infiniteCache) { this.infiniteCache.purgeCache(); } } public getVirtualRowCount(): number { if (this.infiniteCache) { return this.infiniteCache.getVirtualRowCount(); } else { return null; } } public isMaxRowFound(): boolean { if (this.infiniteCache) { return this.infiniteCache.isMaxRowFound(); } } public setVirtualRowCount(rowCount: number, maxRowFound?: boolean): void { if (this.infiniteCache) { this.infiniteCache.setVirtualRowCount(rowCount, maxRowFound); } } public getBlockState(): any { if (this.rowNodeBlockLoader) { return this.rowNodeBlockLoader.getBlockState(); } else { return null; } } }