ag-grid
Version:
Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components
324 lines (273 loc) • 12.1 kB
text/typescript
import {Utils as _} from './utils';
import {RowNode} from "./entities/rowNode";
import {Bean} from "./context/context";
import {Qualifier} from "./context/context";
import {Logger} from "./logger";
import {LoggerFactory} from "./logger";
import {EventService} from "./eventService";
import {Events, SelectionChangedEvent} from "./events";
import {Autowired} from "./context/context";
import {IRowModel} from "./interfaces/iRowModel";
import {GridOptionsWrapper} from "./gridOptionsWrapper";
import {PostConstruct} from "./context/context";
import {Constants} from "./constants";
import {ClientSideRowModel} from "./rowModels/clientSide/clientSideRowModel";
import {ColumnApi} from "./columnController/columnApi";
import {GridApi} from "./gridApi";
export class SelectionController {
private eventService: EventService;
private rowModel: IRowModel;
private gridOptionsWrapper: GridOptionsWrapper;
private columnApi: ColumnApi;
private gridApi: GridApi;
private selectedNodes: {[key: string]: RowNode};
private logger: Logger;
// used for shift selection, so we know where to start the range selection from
private lastSelectedNode: RowNode;
private groupSelectsChildren: boolean;
private setBeans( loggerFactory: LoggerFactory) {
this.logger = loggerFactory.create('SelectionController');
this.reset();
if (this.gridOptionsWrapper.isRowModelDefault()) {
this.eventService.addEventListener(Events.EVENT_ROW_DATA_CHANGED, this.reset.bind(this));
} else {
this.logger.log('dont know what to do here');
}
}
public init(): void {
this.groupSelectsChildren = this.gridOptionsWrapper.isGroupSelectsChildren();
this.eventService.addEventListener(Events.EVENT_ROW_SELECTED, this.onRowSelected.bind(this));
}
public setLastSelectedNode(rowNode: RowNode): void {
this.lastSelectedNode = rowNode;
}
public getLastSelectedNode(): RowNode {
return this.lastSelectedNode;
}
public getSelectedNodes() {
let selectedNodes: RowNode[] = [];
_.iterateObject(this.selectedNodes, (key: string, rowNode: RowNode) => {
if (rowNode) {
selectedNodes.push(rowNode);
}
});
return selectedNodes;
}
public getSelectedRows() {
let selectedRows: any[] = [];
_.iterateObject(this.selectedNodes, (key: string, rowNode: RowNode) => {
if (rowNode && rowNode.data) {
selectedRows.push(rowNode.data);
}
});
return selectedRows;
}
public removeGroupsFromSelection(): void {
_.iterateObject(this.selectedNodes, (key: string, rowNode: RowNode) => {
if (rowNode && rowNode.group) {
this.selectedNodes[rowNode.id] = undefined;
}
});
}
// should only be called if groupSelectsChildren=true
public updateGroupsFromChildrenSelections(): void {
// we only do this when group selection state depends on selected children
if (!this.gridOptionsWrapper.isGroupSelectsChildren()) { return; }
// also only do it if CSRM (code should never allow this anyway)
if (this.rowModel.getType()!==Constants.ROW_MODEL_TYPE_CLIENT_SIDE) { return; }
let clientSideRowModel = <ClientSideRowModel> this.rowModel;
clientSideRowModel.getTopLevelNodes().forEach( (rowNode: RowNode) => {
rowNode.depthFirstSearch( (rowNode)=> {
if (rowNode.group) {
rowNode.calculateSelectedFromChildren();
}
});
});
}
public getNodeForIdIfSelected(id: number): RowNode {
return this.selectedNodes[id];
}
public clearOtherNodes(rowNodeToKeepSelected: RowNode): number {
let groupsToRefresh: any = {};
let updatedCount = 0;
_.iterateObject(this.selectedNodes, (key: string, otherRowNode: RowNode)=> {
if (otherRowNode && otherRowNode.id !== rowNodeToKeepSelected.id) {
let rowNode = this.selectedNodes[otherRowNode.id];
updatedCount += rowNode.setSelectedParams({newValue: false, clearSelection: false, suppressFinishActions: true});
if (this.groupSelectsChildren && otherRowNode.parent) {
groupsToRefresh[otherRowNode.parent.id] = otherRowNode.parent;
}
}
});
_.iterateObject(groupsToRefresh, (key: string, group: RowNode) => {
group.calculateSelectedFromChildren();
});
return updatedCount;
}
private onRowSelected(event: any): void {
let rowNode = event.node;
// we do not store the group rows when the groups select children
if (this.groupSelectsChildren && rowNode.group) { return; }
if (rowNode.isSelected()) {
this.selectedNodes[rowNode.id] = rowNode;
} else {
this.selectedNodes[rowNode.id] = undefined;
}
}
public syncInRowNode(rowNode: RowNode, oldNode: RowNode): void {
this.syncInOldRowNode(rowNode, oldNode);
this.syncInNewRowNode(rowNode);
}
// if the id has changed for the node, then this means the rowNode
// is getting used for a different data item, which breaks
// our selectedNodes, as the node now is mapped by the old id
// which is inconsistent. so to keep the old node as selected,
// we swap in the clone (with the old id and old data). this means
// the oldNode is effectively a daemon we keep a reference to,
// so if client calls api.getSelectedNodes(), it gets the daemon
// in the result. when the client un-selects, the reference to the
// daemon is removed. the daemon, because it's an oldNode, is not
// used by the grid for rendering, it's a copy of what the node used
// to be like before the id was changed.
private syncInOldRowNode(rowNode: RowNode, oldNode: RowNode): void {
let oldNodeHasDifferentId = _.exists(oldNode) && (rowNode.id !== oldNode.id);
if (oldNodeHasDifferentId) {
let oldNodeSelected = _.exists(this.selectedNodes[oldNode.id]);
if (oldNodeSelected) {
this.selectedNodes[oldNode.id] = oldNode;
}
}
}
private syncInNewRowNode(rowNode: RowNode): void {
if (_.exists(this.selectedNodes[rowNode.id])) {
rowNode.setSelectedInitialValue(true);
this.selectedNodes[rowNode.id] = rowNode;
} else {
rowNode.setSelectedInitialValue(false);
}
}
public reset(): void {
this.logger.log('reset');
this.selectedNodes = {};
this.lastSelectedNode = null;
}
// returns a list of all nodes at 'best cost' - a feature to be used
// with groups / trees. if a group has all it's children selected,
// then the group appears in the result, but not the children.
// Designed for use with 'children' as the group selection type,
// where groups don't actually appear in the selection normally.
public getBestCostNodeSelection() {
if (this.rowModel.getType()!==Constants.ROW_MODEL_TYPE_CLIENT_SIDE) {
console.warn('getBestCostNodeSelection is only avilable when using normal row model');
}
let clientSideRowModel = <ClientSideRowModel> this.rowModel;
let topLevelNodes = clientSideRowModel.getTopLevelNodes();
if (topLevelNodes===null) {
console.warn('selectAll not available doing rowModel=virtual');
return;
}
let result: any = [];
// recursive function, to find the selected nodes
function traverse(nodes: any) {
for (let i = 0, l = nodes.length; i < l; i++) {
let node = nodes[i];
if (node.isSelected()) {
result.push(node);
} else {
// if not selected, then if it's a group, and the group
// has children, continue to search for selections
if (node.group && node.children) {
traverse(node.children);
}
}
}
}
traverse(topLevelNodes);
return result;
}
public setRowModel(rowModel: any) {
this.rowModel = rowModel;
}
public isEmpty(): boolean {
let count = 0;
_.iterateObject(this.selectedNodes, (nodeId: string, rowNode: RowNode) => {
if (rowNode) {
count++;
}
});
return count === 0;
}
public deselectAllRowNodes(justFiltered = false) {
let callback = (rowNode: RowNode) => rowNode.selectThisNode(false);
let rowModelClientSide = this.rowModel.getType() === Constants.ROW_MODEL_TYPE_CLIENT_SIDE;
if (justFiltered) {
if (!rowModelClientSide) {
console.error('ag-Grid: selecting just filtered only works with In Memory Row Model');
return;
}
let clientSideRowModel = <ClientSideRowModel> this.rowModel;
clientSideRowModel.forEachNodeAfterFilter(callback);
} else {
_.iterateObject(this.selectedNodes, (id: string, rowNode: RowNode) => {
// remember the reference can be to null, as we never 'delete' from the map
if (rowNode) {
callback(rowNode);
}
});
// this clears down the map (whereas above only sets the items in map to 'undefined')
this.reset();
}
// the above does not clean up the parent rows if they are selected
if (rowModelClientSide && this.groupSelectsChildren) {
this.updateGroupsFromChildrenSelections();
}
let event: SelectionChangedEvent = {
type: Events.EVENT_SELECTION_CHANGED,
api: this.gridApi,
columnApi: this.columnApi
};
this.eventService.dispatchEvent(event);
}
public selectAllRowNodes(justFiltered = false) {
if (this.rowModel.getType()!==Constants.ROW_MODEL_TYPE_CLIENT_SIDE) {
throw `selectAll only available with normal row model, ie not ${this.rowModel.getType()}`;
}
let clientSideRowModel = <ClientSideRowModel> this.rowModel;
let callback = (rowNode: RowNode) => rowNode.selectThisNode(true);
if (justFiltered) {
clientSideRowModel.forEachNodeAfterFilter(callback);
} else {
clientSideRowModel.forEachNode(callback);
}
// the above does not clean up the parent rows if they are selected
if (this.rowModel.getType()===Constants.ROW_MODEL_TYPE_CLIENT_SIDE && this.groupSelectsChildren) {
this.updateGroupsFromChildrenSelections();
}
let event: SelectionChangedEvent = {
type: Events.EVENT_SELECTION_CHANGED,
api: this.gridApi,
columnApi: this.columnApi
};
this.eventService.dispatchEvent(event);
}
// Deprecated method
public selectNode(rowNode: RowNode, tryMulti: boolean) {
rowNode.setSelectedParams({newValue: true, clearSelection: !tryMulti});
}
// Deprecated method
public deselectIndex(rowIndex: number) {
let node = this.rowModel.getRow(rowIndex);
this.deselectNode(node);
}
// Deprecated method
public deselectNode(rowNode: RowNode) {
rowNode.setSelectedParams({newValue: false, clearSelection: false});
}
// Deprecated method
public selectIndex(index: any, tryMulti: boolean) {
let node = this.rowModel.getRow(index);
this.selectNode(node, tryMulti);
}
}