UNPKG

ag-grid

Version:

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

387 lines (313 loc) 14.4 kB
import {RowNode} from "../../entities/rowNode"; import {Utils as _} from "../../utils"; import {GridOptionsWrapper} from "../../gridOptionsWrapper"; import {Autowired, Context} from "../../context/context"; import {GetNodeChildDetails, IsRowMaster} from "../../entities/gridOptions"; import {EventService} from "../../eventService"; import {RowDataTransaction, RowNodeTransaction} from "./clientSideRowModel"; import {ColumnController} from "../../columnController/columnController"; import {Events, SelectionChangedEvent} from "../../events"; import {GridApi} from "../../gridApi"; import {ColumnApi} from "../../columnController/columnApi"; import {SelectionController} from "../../selectionController"; export class ClientSideNodeManager { private static TOP_LEVEL = 0; private rootNode: RowNode; private gridOptionsWrapper: GridOptionsWrapper; private context: Context; private eventService: EventService; private columnController: ColumnController; private selectionController: SelectionController; private nextId = 0; private static ROOT_NODE_ID = 'ROOT_NODE_ID'; private getNodeChildDetails: GetNodeChildDetails; private doesDataFlower: (data: any) => boolean; private isRowMasterFunc: IsRowMaster; private suppressParentsInRowNodes: boolean; private doingLegacyTreeData: boolean; private doingMasterDetail: boolean; // when user is provide the id's, we also keep a map of ids to row nodes for convenience private allNodesMap: {[id:string]: RowNode} = {}; private columnApi: ColumnApi; private gridApi: GridApi; constructor(rootNode: RowNode, gridOptionsWrapper: GridOptionsWrapper, context: Context, eventService: EventService, columnController: ColumnController, gridApi: GridApi, columnApi: ColumnApi, selectionController: SelectionController) { this.rootNode = rootNode; this.gridOptionsWrapper = gridOptionsWrapper; this.context = context; this.eventService = eventService; this.columnController = columnController; this.gridApi = gridApi; this.columnApi = columnApi; this.selectionController = selectionController; this.rootNode.group = true; this.rootNode.level = -1; this.rootNode.id = ClientSideNodeManager.ROOT_NODE_ID; this.rootNode.allLeafChildren = []; this.rootNode.childrenAfterGroup = []; this.rootNode.childrenAfterSort = []; this.rootNode.childrenAfterFilter = []; // if we make this class a bean, then can annotate postConstruct this.postConstruct(); } // @PostConstruct - this is not a bean, so postConstruct called by constructor public postConstruct(): void { // func below doesn't have 'this' pointer, so need to pull out these bits this.getNodeChildDetails = this.gridOptionsWrapper.getNodeChildDetailsFunc(); this.suppressParentsInRowNodes = this.gridOptionsWrapper.isSuppressParentsInRowNodes(); this.doesDataFlower = this.gridOptionsWrapper.getDoesDataFlowerFunc(); this.isRowMasterFunc = this.gridOptionsWrapper.getIsRowMasterFunc(); this.doingLegacyTreeData = _.exists(this.getNodeChildDetails); this.doingMasterDetail = this.gridOptionsWrapper.isMasterDetail(); } public getCopyOfNodesMap(): {[id:string]: RowNode} { let result: {[id:string]: RowNode} = _.cloneObject(this.allNodesMap); return result; } public getRowNode(id: string): RowNode { return this.allNodesMap[id]; } public setRowData(rowData: any[]): RowNode[] { this.rootNode.childrenAfterFilter = null; this.rootNode.childrenAfterGroup = null; this.rootNode.childrenAfterSort = null; this.rootNode.childrenMapped = null; this.nextId = 0; this.allNodesMap = {}; if (!rowData) { this.rootNode.allLeafChildren = []; this.rootNode.childrenAfterGroup = []; return; } // kick off recursion let result = this.recursiveFunction(rowData, null, ClientSideNodeManager.TOP_LEVEL); if (this.doingLegacyTreeData) { this.rootNode.childrenAfterGroup = result; this.setLeafChildren(this.rootNode); } else { this.rootNode.allLeafChildren = result; } } public updateRowData(rowDataTran: RowDataTransaction, rowNodeOrder: {[id:string]: number}): RowNodeTransaction { if (this.isLegacyTreeData()) { return null; } let {add, addIndex, remove, update} = rowDataTran; let rowNodeTransaction: RowNodeTransaction = { remove: [], update: [], add: [] }; if (_.exists(add)) { let useIndex = typeof addIndex === 'number' && addIndex >= 0; if (useIndex) { // items get inserted in reverse order for index insertion add.reverse().forEach( item => { let newRowNode: RowNode = this.addRowNode(item, addIndex); rowNodeTransaction.add.push(newRowNode); }); } else { add.forEach( item => { let newRowNode: RowNode = this.addRowNode(item); rowNodeTransaction.add.push(newRowNode); }); } } if (_.exists(remove)) { let anyNodesSelected = false; remove.forEach( item => { let rowNode = this.lookupRowNode(item); if (!rowNode) { return; } if (rowNode.isSelected()) { anyNodesSelected = true; } this.updatedRowNode(rowNode, item,false); rowNodeTransaction.remove.push(rowNode); }); if (anyNodesSelected) { this.selectionController.updateGroupsFromChildrenSelections(); let event: SelectionChangedEvent = { type: Events.EVENT_SELECTION_CHANGED, api: this.gridApi, columnApi: this.columnApi }; this.eventService.dispatchEvent(event); } } if (_.exists(update)) { update.forEach( item => { let rowNode = this.lookupRowNode(item); if (!rowNode) { return; } this.updatedRowNode(rowNode, item,true); rowNodeTransaction.update.push(rowNode); }); } if (rowNodeOrder) { _.sortRowNodesByOrder(this.rootNode.allLeafChildren, rowNodeOrder); } return rowNodeTransaction; } private addRowNode(data: any, index?: number): RowNode { let newNode = this.createNode(data, null, ClientSideNodeManager.TOP_LEVEL); if (_.exists(index)) { _.insertIntoArray(this.rootNode.allLeafChildren, newNode, index); } else { this.rootNode.allLeafChildren.push(newNode); } return newNode; } private lookupRowNode(data: any): RowNode { let rowNodeIdFunc = this.gridOptionsWrapper.getRowNodeIdFunc(); let rowNode: RowNode; if (_.exists(rowNodeIdFunc)) { // find rowNode using id let id: string = rowNodeIdFunc(data); rowNode = this.allNodesMap[id]; if (!rowNode) { console.error(`ag-Grid: could not find row id=${id}, data item was not found for this id`); return null; } } else { // find rowNode using object references rowNode = _.find(this.rootNode.allLeafChildren, rowNode => rowNode.data === data); if (!rowNode) { console.error(`ag-Grid: could not find data item as object was not found`, data); return null; } } return rowNode; } private updatedRowNode(rowNode: RowNode, data: any, update: boolean): void { if (update) { // do update rowNode.updateData(data); } else { // do delete - setting 'tailingNodeInSequence = true' to ensure EVENT_SELECTION_CHANGED is not raised for // each row node updated, instead it is raised once by the calling code if any selected nodes exist. rowNode.setSelected(false, false, true); // so row renderer knows to fade row out (and not reposition it) rowNode.clearRowTop(); _.removeFromArray(this.rootNode.allLeafChildren, rowNode); this.allNodesMap[rowNode.id] = undefined; } } private recursiveFunction(rowData: any[], parent: RowNode, level: number): RowNode[] { // make sure the rowData is an array and not a string of json - this was a commonly reported problem on the forum if (typeof rowData === 'string') { console.warn('ag-Grid: rowData must be an array, however you passed in a string. If you are loading JSON, make sure you convert the JSON string to JavaScript objects first'); return; } let rowNodes: RowNode[] = []; rowData.forEach( (dataItem)=> { let node = this.createNode(dataItem, parent, level); rowNodes.push(node); }); return rowNodes; } private createNode(dataItem: any, parent: RowNode, level: number): RowNode { let node = new RowNode(); this.context.wireBean(node); let doingTreeData = this.gridOptionsWrapper.isTreeData(); let doingLegacyTreeData = !doingTreeData && _.exists(this.getNodeChildDetails); let nodeChildDetails = doingLegacyTreeData ? this.getNodeChildDetails(dataItem) : null; if (nodeChildDetails && nodeChildDetails.group) { node.group = true; node.childrenAfterGroup = this.recursiveFunction(nodeChildDetails.children, node, level + 1); node.expanded = nodeChildDetails.expanded === true; node.field = nodeChildDetails.field; node.key = nodeChildDetails.key; node.canFlower = node.master; // deprecated, is now 'master' // pull out all the leaf children and add to our node this.setLeafChildren(node); } else { node.group = false; if (doingTreeData) { node.master = false; node.expanded = false; } else { // this is the default, for when doing grid data if (this.doesDataFlower) { node.master = this.doesDataFlower(dataItem); } else if (this.doingMasterDetail) { // if we are doing master detail, then the // default is that everything can flower. if (this.isRowMasterFunc) { node.master = this.isRowMasterFunc(dataItem); } else { node.master = true; } } else { node.master = false; } let rowGroupColumns = this.columnController.getRowGroupColumns(); let numRowGroupColumns = rowGroupColumns ? rowGroupColumns.length : 0; // need to take row group into account when determining level let masterRowLevel = level + numRowGroupColumns; node.expanded = node.master ? this.isExpanded(masterRowLevel) : false; } } // support for backwards compatibility, canFlow is now called 'master' node.canFlower = node.master; if (parent && !this.suppressParentsInRowNodes) { node.parent = parent; } node.level = level; node.setDataAndId(dataItem, this.nextId.toString()); this.allNodesMap[node.id] = node; this.nextId++; return node; } private isExpanded(level: any) { let expandByDefault = this.gridOptionsWrapper.getGroupDefaultExpanded(); if (expandByDefault===-1) { return true; } else { return level < expandByDefault; } } // this is only used for doing legacy tree data private setLeafChildren(node: RowNode): void { node.allLeafChildren = []; if (node.childrenAfterGroup) { node.childrenAfterGroup.forEach( childAfterGroup => { if (childAfterGroup.group) { if (childAfterGroup.allLeafChildren) { childAfterGroup.allLeafChildren.forEach( leafChild => node.allLeafChildren.push(leafChild) ); } } else { node.allLeafChildren.push(childAfterGroup) } }); } } public insertItemsAtIndex(index: number, rowData: any[]): RowNode[] { if (this.isLegacyTreeData()) { return null; } let nodeList = this.rootNode.allLeafChildren; if (index > nodeList.length) { console.warn(`ag-Grid: invalid index ${index}, max index is ${nodeList.length}`); return; } let newNodes: RowNode[] = []; // go through the items backwards, otherwise they get added in reverse order for (let i = rowData.length - 1; i >= 0; i--) { let data = rowData[i]; let newNode = this.createNode(data, null, ClientSideNodeManager.TOP_LEVEL); _.insertIntoArray(nodeList, newNode, index); newNodes.push(newNode); } return newNodes.length > 0 ? newNodes : null; } public addItems(items: any): RowNode[] { let nodeList = this.rootNode.allLeafChildren; return this.insertItemsAtIndex(nodeList.length, items); } public isLegacyTreeData(): boolean { let rowsAlreadyGrouped = _.exists(this.gridOptionsWrapper.getNodeChildDetailsFunc()); if (rowsAlreadyGrouped) { console.warn('ag-Grid: adding and removing rows is not supported when using nodeChildDetailsFunc, ie it is not ' + 'supported for legacy tree data. Please see the docs on the new preferred way of providing tree data that works with delta updates.'); return true; } else { return false; } } }