UNPKG

12.1 kBPlain TextView Raw
1import {Utils as _} from './utils';
2import {RowNode} from "./entities/rowNode";
3import {Bean} from "./context/context";
4import {Qualifier} from "./context/context";
5import {Logger} from "./logger";
6import {LoggerFactory} from "./logger";
7import {EventService} from "./eventService";
8import {Events, SelectionChangedEvent} from "./events";
9import {Autowired} from "./context/context";
10import {IRowModel} from "./interfaces/iRowModel";
11import {GridOptionsWrapper} from "./gridOptionsWrapper";
12import {PostConstruct} from "./context/context";
13import {Constants} from "./constants";
14import {ClientSideRowModel} from "./rowModels/clientSide/clientSideRowModel";
15import {ColumnApi} from "./columnController/columnApi";
16import {GridApi} from "./gridApi";
17
18@Bean('selectionController')
19export class SelectionController {
20
21 @Autowired('eventService') private eventService: EventService;
22 @Autowired('rowModel') private rowModel: IRowModel;
23 @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
24 @Autowired('columnApi') private columnApi: ColumnApi;
25 @Autowired('gridApi') private gridApi: GridApi;
26
27 private selectedNodes: {[key: string]: RowNode};
28 private logger: Logger;
29
30 // used for shift selection, so we know where to start the range selection from
31 private lastSelectedNode: RowNode;
32
33 private groupSelectsChildren: boolean;
34
35 private setBeans(@Qualifier('loggerFactory') loggerFactory: LoggerFactory) {
36 this.logger = loggerFactory.create('SelectionController');
37 this.reset();
38
39 if (this.gridOptionsWrapper.isRowModelDefault()) {
40 this.eventService.addEventListener(Events.EVENT_ROW_DATA_CHANGED, this.reset.bind(this));
41 } else {
42 this.logger.log('dont know what to do here');
43 }
44 }
45
46 @PostConstruct
47 public init(): void {
48 this.groupSelectsChildren = this.gridOptionsWrapper.isGroupSelectsChildren();
49 this.eventService.addEventListener(Events.EVENT_ROW_SELECTED, this.onRowSelected.bind(this));
50 }
51
52 public setLastSelectedNode(rowNode: RowNode): void {
53 this.lastSelectedNode = rowNode;
54 }
55
56 public getLastSelectedNode(): RowNode {
57 return this.lastSelectedNode;
58 }
59
60 public getSelectedNodes() {
61 let selectedNodes: RowNode[] = [];
62 _.iterateObject(this.selectedNodes, (key: string, rowNode: RowNode) => {
63 if (rowNode) {
64 selectedNodes.push(rowNode);
65 }
66 });
67 return selectedNodes;
68 }
69
70 public getSelectedRows() {
71 let selectedRows: any[] = [];
72 _.iterateObject(this.selectedNodes, (key: string, rowNode: RowNode) => {
73 if (rowNode && rowNode.data) {
74 selectedRows.push(rowNode.data);
75 }
76 });
77 return selectedRows;
78 }
79
80 public removeGroupsFromSelection(): void {
81 _.iterateObject(this.selectedNodes, (key: string, rowNode: RowNode) => {
82 if (rowNode && rowNode.group) {
83 this.selectedNodes[rowNode.id] = undefined;
84 }
85 });
86 }
87
88 // should only be called if groupSelectsChildren=true
89 public updateGroupsFromChildrenSelections(): void {
90 // we only do this when group selection state depends on selected children
91 if (!this.gridOptionsWrapper.isGroupSelectsChildren()) { return; }
92 // also only do it if CSRM (code should never allow this anyway)
93 if (this.rowModel.getType()!==Constants.ROW_MODEL_TYPE_CLIENT_SIDE) { return; }
94
95 let clientSideRowModel = <ClientSideRowModel> this.rowModel;
96 clientSideRowModel.getTopLevelNodes().forEach( (rowNode: RowNode) => {
97 rowNode.depthFirstSearch( (rowNode)=> {
98 if (rowNode.group) {
99 rowNode.calculateSelectedFromChildren();
100 }
101 });
102 });
103 }
104
105 public getNodeForIdIfSelected(id: number): RowNode {
106 return this.selectedNodes[id];
107 }
108
109 public clearOtherNodes(rowNodeToKeepSelected: RowNode): number {
110 let groupsToRefresh: any = {};
111 let updatedCount = 0;
112 _.iterateObject(this.selectedNodes, (key: string, otherRowNode: RowNode)=> {
113 if (otherRowNode && otherRowNode.id !== rowNodeToKeepSelected.id) {
114 let rowNode = this.selectedNodes[otherRowNode.id];
115 updatedCount += rowNode.setSelectedParams({newValue: false, clearSelection: false, suppressFinishActions: true});
116 if (this.groupSelectsChildren && otherRowNode.parent) {
117 groupsToRefresh[otherRowNode.parent.id] = otherRowNode.parent;
118 }
119 }
120 });
121 _.iterateObject(groupsToRefresh, (key: string, group: RowNode) => {
122 group.calculateSelectedFromChildren();
123 });
124 return updatedCount;
125 }
126
127 private onRowSelected(event: any): void {
128 let rowNode = event.node;
129
130 // we do not store the group rows when the groups select children
131 if (this.groupSelectsChildren && rowNode.group) { return; }
132
133 if (rowNode.isSelected()) {
134 this.selectedNodes[rowNode.id] = rowNode;
135 } else {
136 this.selectedNodes[rowNode.id] = undefined;
137 }
138 }
139
140 public syncInRowNode(rowNode: RowNode, oldNode: RowNode): void {
141 this.syncInOldRowNode(rowNode, oldNode);
142 this.syncInNewRowNode(rowNode);
143 }
144
145 // if the id has changed for the node, then this means the rowNode
146 // is getting used for a different data item, which breaks
147 // our selectedNodes, as the node now is mapped by the old id
148 // which is inconsistent. so to keep the old node as selected,
149 // we swap in the clone (with the old id and old data). this means
150 // the oldNode is effectively a daemon we keep a reference to,
151 // so if client calls api.getSelectedNodes(), it gets the daemon
152 // in the result. when the client un-selects, the reference to the
153 // daemon is removed. the daemon, because it's an oldNode, is not
154 // used by the grid for rendering, it's a copy of what the node used
155 // to be like before the id was changed.
156 private syncInOldRowNode(rowNode: RowNode, oldNode: RowNode): void {
157 let oldNodeHasDifferentId = _.exists(oldNode) && (rowNode.id !== oldNode.id);
158 if (oldNodeHasDifferentId) {
159 let oldNodeSelected = _.exists(this.selectedNodes[oldNode.id]);
160 if (oldNodeSelected) {
161 this.selectedNodes[oldNode.id] = oldNode;
162 }
163 }
164 }
165
166 private syncInNewRowNode(rowNode: RowNode): void {
167 if (_.exists(this.selectedNodes[rowNode.id])) {
168 rowNode.setSelectedInitialValue(true);
169 this.selectedNodes[rowNode.id] = rowNode;
170 } else {
171 rowNode.setSelectedInitialValue(false);
172 }
173 }
174
175 public reset(): void {
176 this.logger.log('reset');
177 this.selectedNodes = {};
178 this.lastSelectedNode = null;
179 }
180
181 // returns a list of all nodes at 'best cost' - a feature to be used
182 // with groups / trees. if a group has all it's children selected,
183 // then the group appears in the result, but not the children.
184 // Designed for use with 'children' as the group selection type,
185 // where groups don't actually appear in the selection normally.
186 public getBestCostNodeSelection() {
187
188 if (this.rowModel.getType()!==Constants.ROW_MODEL_TYPE_CLIENT_SIDE) {
189 console.warn('getBestCostNodeSelection is only avilable when using normal row model');
190 }
191
192 let clientSideRowModel = <ClientSideRowModel> this.rowModel;
193
194 let topLevelNodes = clientSideRowModel.getTopLevelNodes();
195
196 if (topLevelNodes===null) {
197 console.warn('selectAll not available doing rowModel=virtual');
198 return;
199 }
200
201 let result: any = [];
202
203 // recursive function, to find the selected nodes
204 function traverse(nodes: any) {
205 for (let i = 0, l = nodes.length; i < l; i++) {
206 let node = nodes[i];
207 if (node.isSelected()) {
208 result.push(node);
209 } else {
210 // if not selected, then if it's a group, and the group
211 // has children, continue to search for selections
212 if (node.group && node.children) {
213 traverse(node.children);
214 }
215 }
216 }
217 }
218
219 traverse(topLevelNodes);
220
221 return result;
222 }
223
224 public setRowModel(rowModel: any) {
225 this.rowModel = rowModel;
226 }
227
228 public isEmpty(): boolean {
229 let count = 0;
230 _.iterateObject(this.selectedNodes, (nodeId: string, rowNode: RowNode) => {
231 if (rowNode) {
232 count++;
233 }
234 });
235 return count === 0;
236 }
237
238 public deselectAllRowNodes(justFiltered = false) {
239
240 let callback = (rowNode: RowNode) => rowNode.selectThisNode(false);
241 let rowModelClientSide = this.rowModel.getType() === Constants.ROW_MODEL_TYPE_CLIENT_SIDE;
242
243 if (justFiltered) {
244 if (!rowModelClientSide) {
245 console.error('ag-Grid: selecting just filtered only works with In Memory Row Model');
246 return;
247 }
248 let clientSideRowModel = <ClientSideRowModel> this.rowModel;
249 clientSideRowModel.forEachNodeAfterFilter(callback);
250 } else {
251 _.iterateObject(this.selectedNodes, (id: string, rowNode: RowNode) => {
252 // remember the reference can be to null, as we never 'delete' from the map
253 if (rowNode) {
254 callback(rowNode);
255 }
256 });
257 // this clears down the map (whereas above only sets the items in map to 'undefined')
258 this.reset();
259 }
260
261 // the above does not clean up the parent rows if they are selected
262 if (rowModelClientSide && this.groupSelectsChildren) {
263 this.updateGroupsFromChildrenSelections();
264 }
265
266 let event: SelectionChangedEvent = {
267 type: Events.EVENT_SELECTION_CHANGED,
268 api: this.gridApi,
269 columnApi: this.columnApi
270 };
271
272 this.eventService.dispatchEvent(event);
273 }
274
275 public selectAllRowNodes(justFiltered = false) {
276 if (this.rowModel.getType()!==Constants.ROW_MODEL_TYPE_CLIENT_SIDE) {
277 throw `selectAll only available with normal row model, ie not ${this.rowModel.getType()}`;
278 }
279
280 let clientSideRowModel = <ClientSideRowModel> this.rowModel;
281 let callback = (rowNode: RowNode) => rowNode.selectThisNode(true);
282
283 if (justFiltered) {
284 clientSideRowModel.forEachNodeAfterFilter(callback);
285 } else {
286 clientSideRowModel.forEachNode(callback);
287 }
288
289 // the above does not clean up the parent rows if they are selected
290 if (this.rowModel.getType()===Constants.ROW_MODEL_TYPE_CLIENT_SIDE && this.groupSelectsChildren) {
291 this.updateGroupsFromChildrenSelections();
292 }
293
294 let event: SelectionChangedEvent = {
295 type: Events.EVENT_SELECTION_CHANGED,
296 api: this.gridApi,
297 columnApi: this.columnApi
298 };
299 this.eventService.dispatchEvent(event);
300 }
301
302 // Deprecated method
303 public selectNode(rowNode: RowNode, tryMulti: boolean) {
304 rowNode.setSelectedParams({newValue: true, clearSelection: !tryMulti});
305 }
306
307 // Deprecated method
308 public deselectIndex(rowIndex: number) {
309 let node = this.rowModel.getRow(rowIndex);
310 this.deselectNode(node);
311 }
312
313 // Deprecated method
314 public deselectNode(rowNode: RowNode) {
315 rowNode.setSelectedParams({newValue: false, clearSelection: false});
316 }
317
318 // Deprecated method
319 public selectIndex(index: any, tryMulti: boolean) {
320 let node = this.rowModel.getRow(index);
321 this.selectNode(node, tryMulti);
322 }
323
324}
\No newline at end of file