UNPKG

30.6 kBPlain TextView Raw
1import {EventService} from "../eventService";
2import {
3 AgEvent, Events, RowDataChangedEvent, RowEvent, RowGroupOpenedEvent, RowSelectedEvent,
4 SelectionChangedEvent
5} from "../events";
6import {GridOptionsWrapper} from "../gridOptionsWrapper";
7import {SelectionController} from "../selectionController";
8import {ColDef} from "./colDef";
9import {Column} from "./column";
10import {ValueService} from "../valueService/valueService";
11import {ColumnController} from "../columnController/columnController";
12import {ColumnApi} from "../columnController/columnApi";
13import {Autowired, Context} from "../context/context";
14import {IRowModel} from "../interfaces/iRowModel";
15import {Constants} from "../constants";
16import {Utils as _} from "../utils";
17import {ClientSideRowModel} from "../rowModels/clientSide/clientSideRowModel";
18import {RowNodeCache, RowNodeCacheParams} from "../rowModels/cache/rowNodeCache";
19import {RowNodeBlock} from "../rowModels/cache/rowNodeBlock";
20import {IEventEmitter} from "../interfaces/iEventEmitter";
21import {ValueCache} from "../valueService/valueCache";
22import {DetailGridInfo, GridApi} from "../gridApi";
23
24export interface SetSelectedParams {
25 // true or false, whatever you want to set selection to
26 newValue: boolean;
27 // whether to remove other selections after this selection is done
28 clearSelection?: boolean;
29 // true when action is NOT on this node, ie user clicked a group and this is the child of a group
30 suppressFinishActions?: boolean;
31 // gets used when user shif-selects a range
32 rangeSelect?: boolean;
33 // used in group selection, if true, filtered out children will not be selected
34 groupSelectsFiltered?: boolean;
35}
36
37export interface RowNodeEvent extends AgEvent {
38 node: RowNode;
39}
40
41export interface DataChangedEvent extends RowNodeEvent {
42 oldData: any;
43 newData: any;
44 update: boolean;
45}
46
47export interface CellChangedEvent extends RowNodeEvent {
48 column: Column;
49 newValue: any;
50}
51
52export class RowNode implements IEventEmitter {
53
54 public static EVENT_ROW_SELECTED = 'rowSelected';
55 public static EVENT_DATA_CHANGED = 'dataChanged';
56 public static EVENT_CELL_CHANGED = 'cellChanged';
57 public static EVENT_ALL_CHILDREN_COUNT_CHANGED = 'allChildrenCountChanged';
58 public static EVENT_MOUSE_ENTER = 'mouseEnter';
59 public static EVENT_MOUSE_LEAVE = 'mouseLeave';
60 public static EVENT_HEIGHT_CHANGED = 'heightChanged';
61 public static EVENT_TOP_CHANGED = 'topChanged';
62 public static EVENT_FIRST_CHILD_CHANGED = 'firstChildChanged';
63 public static EVENT_LAST_CHILD_CHANGED = 'lastChildChanged';
64 public static EVENT_CHILD_INDEX_CHANGED = 'childIndexChanged';
65 public static EVENT_ROW_INDEX_CHANGED = 'rowIndexChanged';
66 public static EVENT_EXPANDED_CHANGED = 'expandedChanged';
67 public static EVENT_SELECTABLE_CHANGED = 'selectableChanged';
68 public static EVENT_UI_LEVEL_CHANGED = 'uiLevelChanged';
69 public static EVENT_DRAGGING_CHANGED = 'draggingChanged';
70
71 @Autowired('eventService') private mainEventService: EventService;
72 @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
73 @Autowired('selectionController') private selectionController: SelectionController;
74 @Autowired('columnController') private columnController: ColumnController;
75 @Autowired('valueService') private valueService: ValueService;
76 @Autowired('rowModel') private rowModel: IRowModel;
77 @Autowired('context') private context: Context;
78 @Autowired('valueCache') private valueCache: ValueCache;
79 @Autowired('columnApi') private columnApi: ColumnApi;
80 @Autowired('gridApi') private gridApi: GridApi;
81
82 /** Unique ID for the node. Either provided by the grid, or user can set to match the primary
83 * key in the database (or whatever data source is used). */
84 public id: string;
85 /** The group data */
86 public groupData: any;
87 /** The aggregated data */
88 public aggData: any;
89 /** The user provided data */
90 public data: any;
91 /** The parent node to this node, or empty if top level */
92 public parent: RowNode;
93 /** How many levels this node is from the top */
94 public level: number;
95 /** How many levels this node is from the top in the UI (different to the level when removing parents)*/
96 public uiLevel: number;
97 /** If doing in memory grouping, this is the index of the group column this cell is for.
98 * This will always be the same as the level, unless we are collapsing groups ie groupRemoveSingleChildren = true */
99 public rowGroupIndex: number;
100 /** True if this node is a group node (ie has children) */
101 public group: boolean;
102 /** True if this row is getting dragged */
103 public dragging: boolean;
104
105 /** True if this row is a master row, part of master / detail (ie row can be expanded to show detail) */
106 public master: boolean;
107 /** True if this row is a detail row, part of master / detail (ie child row of an expanded master row)*/
108 public detail: boolean;
109 /** If this row is a master row that was expanded, this points to the associated detail row. */
110 public detailNode: RowNode;
111 /** If master detail, this contains details about the detail grid */
112 public detailGridInfo: DetailGridInfo;
113
114 /** Same as master, kept for legacy reasons */
115 public canFlower: boolean;
116 /** Same as detail, kept for legacy reasons */
117 public flower: boolean;
118 /** Same as detailNode, kept for legacy reasons */
119 public childFlower: RowNode;
120
121 /** True if this node is a group and the group is the bottom level in the tree */
122 public leafGroup: boolean;
123 /** True if this is the first child in this group */
124 public firstChild: boolean;
125 /** True if this is the last child in this group */
126 public lastChild: boolean;
127 /** The index of this node in the group */
128 public childIndex: number;
129 /** The index of this node in the grid, only valid if node is displayed in the grid, otherwise it should be ignored as old index may be present */
130 public rowIndex: number;
131 /** Either 'top' or 'bottom' if row pinned, otherwise undefined or null */
132 public rowPinned: string;
133 /** If using quick filter, stores a string representation of the row for searching against */
134 public quickFilterAggregateText: string;
135 /** Groups only - True if row is a footer. Footers have group = true and footer = true */
136 public footer: boolean;
137 /** Groups only - The field we are grouping on eg Country*/
138 public field: string;
139 /** Groups only - the row group column for this group */
140 public rowGroupColumn: Column;
141 /** Groups only - The key for the group eg Ireland, UK, USA */
142 public key: any;
143 /** Used by server side row model, true if this row node is a stub */
144 public stub: boolean;
145
146 /** All user provided nodes */
147 public allLeafChildren: RowNode[];
148
149 /** Groups only - Children of this group */
150 public childrenAfterGroup: RowNode[];
151 /** Groups only - Filtered children of this group */
152 public childrenAfterFilter: RowNode[];
153 /** Groups only - Sorted children of this group */
154 public childrenAfterSort: RowNode[];
155 /** Groups only - Number of children and grand children */
156 public allChildrenCount: number;
157
158 /** Children mapped by the pivot columns */
159 public childrenMapped: {[key: string]: any} = {};
160
161 /** Server Side Row Model Only - the children are in an infinite cache */
162 public childrenCache: RowNodeCache<RowNodeBlock,RowNodeCacheParams>;
163
164 /** Groups only - True if group is expanded, otherwise false */
165 public expanded: boolean;
166 /** Groups only - If doing footers, reference to the footer node for this group */
167 public sibling: RowNode;
168
169 /** The height, in pixels, of this row */
170 public rowHeight: number;
171 /** The top pixel for this row */
172 public rowTop: number;
173 /** The top pixel for this row last time, makes sense if data set was ordered or filtered,
174 * it is used so new rows can animate in from their old position. */
175 public oldRowTop: number;
176 /** True if this node is a daemon. This means row is not part of the model. Can happen when then
177 * the row is selected and then the user sets a different ID onto the node. The nodes is then
178 * representing a different entity, so the selection controller, if the node is selected, takes
179 * a copy where daemon=true. */
180 public daemon: boolean;
181
182 /** True by default - can be overridden via gridOptions.isRowSelectable(rowNode) */
183 public selectable = true;
184
185 /** Used by the value service, stores values for a particular change detection turn. */
186 public __cacheData: {[colId: string]: any};
187 public __cacheVersion: number;
188
189 private selected = false;
190 private eventService: EventService;
191
192 public setData(data: any): void {
193 let oldData = this.data;
194 this.data = data;
195
196 this.valueCache.onDataChanged();
197
198 this.updateDataOnDetailNode();
199
200 this.checkRowSelectable();
201
202 let event: DataChangedEvent = this.createDataChangedEvent(data, oldData, false);
203 this.dispatchLocalEvent(event);
204 }
205
206 // when we are doing master / detail, the detail node is lazy created, but then kept around.
207 // so if we show / hide the detail, the same detail rowNode is used. so we need to keep the data
208 // in sync, otherwise expand/collapse of the detail would still show the old values.
209 private updateDataOnDetailNode(): void {
210 if (this.detailNode) {
211 this.detailNode.data = this.data;
212 }
213 }
214
215 private createDataChangedEvent(newData: any, oldData: any, update: boolean): DataChangedEvent {
216 return {
217 type: RowNode.EVENT_DATA_CHANGED,
218 node: this,
219 oldData: oldData,
220 newData: newData,
221 update: update
222 };
223 }
224
225 private createLocalRowEvent(type: string): RowNodeEvent {
226 return {
227 type: type,
228 node: this
229 };
230 }
231
232 // similar to setRowData, however it is expected that the data is the same data item. this
233 // is intended to be used with Redux type stores, where the whole data can be changed. we are
234 // guaranteed that the data is the same entity (so grid doesn't need to worry about the id of the
235 // underlying data changing, hence doesn't need to worry about selection). the grid, upon receiving
236 // dataChanged event, will refresh the cells rather than rip them all out (so user can show transitions).
237 public updateData(data: any): void {
238 let oldData = this.data;
239 this.data = data;
240
241 this.updateDataOnDetailNode();
242
243 this.checkRowSelectable();
244
245 this.updateDataOnDetailNode();
246
247 let event: DataChangedEvent = this.createDataChangedEvent(data, oldData, true);
248 this.dispatchLocalEvent(event);
249 }
250
251 public getRowIndexString(): string {
252 if (this.rowPinned===Constants.PINNED_TOP) {
253 return 't-' + this.rowIndex;
254 } else if (this.rowPinned===Constants.PINNED_BOTTOM) {
255 return 'b-' + this.rowIndex;
256 } else {
257 return this.rowIndex.toString();
258 }
259 }
260
261 private createDaemonNode(): RowNode {
262 let oldNode = new RowNode();
263 this.context.wireBean(oldNode);
264 // just copy the id and data, this is enough for the node to be used
265 // in the selection controller (the selection controller is the only
266 // place where daemon nodes can live).
267 oldNode.id = this.id;
268 oldNode.data = this.data;
269 oldNode.daemon = true;
270 oldNode.selected = this.selected;
271 oldNode.level = this.level;
272 return oldNode;
273 }
274
275 public setDataAndId(data: any, id: string): void {
276 let oldNode = _.exists(this.id) ? this.createDaemonNode() : null;
277
278 let oldData = this.data;
279 this.data = data;
280 this.updateDataOnDetailNode();
281
282 this.setId(id);
283
284 this.selectionController.syncInRowNode(this, oldNode);
285
286 this.checkRowSelectable();
287
288 let event: DataChangedEvent = this.createDataChangedEvent(data, oldData, false);
289 this.dispatchLocalEvent(event);
290 }
291
292 private checkRowSelectable() {
293 let isRowSelectableFunc = this.gridOptionsWrapper.getIsRowSelectableFunc();
294 let shouldInvokeIsRowSelectable = isRowSelectableFunc && _.exists(this);
295 this.setRowSelectable(shouldInvokeIsRowSelectable ? isRowSelectableFunc(this) : true)
296 }
297
298 public setRowSelectable(newVal: boolean) {
299 if (this.selectable !== newVal) {
300 this.selectable = newVal;
301 if (this.eventService) {
302 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_SELECTABLE_CHANGED));
303 }
304 }
305 }
306
307 public setId(id: string): void {
308 // see if user is providing the id's
309 let getRowNodeId = this.gridOptionsWrapper.getRowNodeIdFunc();
310 if (getRowNodeId) {
311 // if user is providing the id's, then we set the id only after the data has been set.
312 // this is important for virtual pagination and viewport, where empty rows exist.
313 if (this.data) {
314 this.id = getRowNodeId(this.data);
315 } else {
316 // this can happen if user has set blank into the rowNode after the row previously
317 // having data. this happens in virtual page row model, when data is delete and
318 // the page is refreshed.
319 this.id = undefined;
320 }
321 } else {
322 this.id = id;
323 }
324 }
325
326 public isPixelInRange(pixel: number): boolean {
327 return pixel >= this.rowTop && pixel < (this.rowTop + this.rowHeight);
328 }
329
330 public clearRowTop(): void {
331 this.oldRowTop = this.rowTop;
332 this.setRowTop(null);
333 }
334
335 public setFirstChild(firstChild: boolean): void {
336 if (this.firstChild === firstChild) { return; }
337 this.firstChild = firstChild;
338 if (this.eventService) {
339 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_FIRST_CHILD_CHANGED));
340 }
341 }
342
343 public setLastChild(lastChild: boolean): void {
344 if (this.lastChild === lastChild) { return; }
345 this.lastChild = lastChild;
346 if (this.eventService) {
347 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_LAST_CHILD_CHANGED));
348 }
349 }
350
351 public setChildIndex(childIndex: number): void {
352 if (this.childIndex === childIndex) { return; }
353 this.childIndex = childIndex;
354 if (this.eventService) {
355 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_CHILD_INDEX_CHANGED));
356 }
357 }
358
359 public setRowTop(rowTop: number): void {
360 if (this.rowTop === rowTop) { return; }
361 this.rowTop = rowTop;
362 if (this.eventService) {
363 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_TOP_CHANGED));
364 }
365 }
366
367 public setDragging(dragging: boolean): void {
368 if (this.dragging === dragging) { return; }
369 this.dragging = dragging;
370 if (this.eventService) {
371 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_DRAGGING_CHANGED));
372 }
373 }
374
375 public setAllChildrenCount(allChildrenCount: number): void {
376 if (this.allChildrenCount === allChildrenCount) { return; }
377 this.allChildrenCount = allChildrenCount;
378 if (this.eventService) {
379 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_ALL_CHILDREN_COUNT_CHANGED));
380 }
381 }
382
383 public setRowHeight(rowHeight: number): void {
384 this.rowHeight = rowHeight;
385 if (this.eventService) {
386 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_HEIGHT_CHANGED));
387 }
388 }
389
390 public setRowIndex(rowIndex: number): void {
391 this.rowIndex = rowIndex;
392 if (this.eventService) {
393 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_ROW_INDEX_CHANGED));
394 }
395 }
396
397 public setUiLevel(uiLevel: number): void {
398 if (this.uiLevel === uiLevel) { return; }
399
400 this.uiLevel = uiLevel;
401 if (this.eventService) {
402 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_UI_LEVEL_CHANGED));
403 }
404 }
405
406 public setExpanded(expanded: boolean): void {
407 if (this.expanded === expanded) { return; }
408
409 this.expanded = expanded;
410 if (this.eventService) {
411 this.eventService.dispatchEvent(this.createLocalRowEvent(RowNode.EVENT_EXPANDED_CHANGED));
412 }
413
414 let event: RowGroupOpenedEvent = this.createGlobalRowEvent(Events.EVENT_ROW_GROUP_OPENED);
415 this.mainEventService.dispatchEvent(event);
416
417 if (this.gridOptionsWrapper.isGroupIncludeFooter()) {
418 this.gridApi.redrawRows({rowNodes: [this]});
419 }
420 }
421
422 private createGlobalRowEvent(type: string): RowEvent {
423 let event: RowGroupOpenedEvent = {
424 type: type,
425 node: this,
426 data: this.data,
427 rowIndex: this.rowIndex,
428 rowPinned: this.rowPinned,
429 context: this.gridOptionsWrapper.getContext(),
430 api: this.gridOptionsWrapper.getApi(),
431 columnApi: this.gridOptionsWrapper.getColumnApi()
432 };
433 return event;
434 }
435
436 private dispatchLocalEvent(event: AgEvent): void {
437 if (this.eventService) {
438 this.eventService.dispatchEvent(event);
439 }
440 }
441
442 // we also allow editing the value via the editors. when it is done via
443 // the editors, no 'cell changed' event gets fired, as it's assumed that
444 // the cell knows about the change given it's in charge of the editing.
445 // this method is for the client to call, so the cell listens for the change
446 // event, and also flashes the cell when the change occurs.
447 public setDataValue(colKey: string|Column, newValue: any): void {
448 let column = this.columnController.getPrimaryColumn(colKey);
449 this.valueService.setValue(this, column, newValue);
450 this.dispatchCellChangedEvent(column, newValue);
451 }
452
453 public setGroupValue(colKey: string|Column, newValue: any): void {
454 let column = this.columnController.getGridColumn(colKey);
455
456 if (_.missing(this.groupData)) {
457 this.groupData = {};
458 }
459
460 this.groupData[column.getColId()] = newValue;
461 this.dispatchCellChangedEvent(column, newValue);
462 }
463
464 // sets the data for an aggregation
465 public setAggData(newAggData: any): void {
466
467 // find out all keys that could potentially change
468 let colIds = _.getAllKeysInObjects([this.aggData, newAggData]);
469
470 this.aggData = newAggData;
471
472 // if no event service, nobody has registered for events, so no need fire event
473 if (this.eventService) {
474 colIds.forEach( colId => {
475 let column = this.columnController.getGridColumn(colId);
476 let value = this.aggData ? this.aggData[colId] : undefined;
477 this.dispatchCellChangedEvent(column, value);
478 });
479 }
480 }
481
482 public hasChildren(): boolean {
483 // we need to return true when this.group=true, as this is used by server side row model
484 // (as children are lazy loaded and stored in a cache anyway). otherwise we return true
485 // if children exist.
486 return this.group || (this.childrenAfterGroup && this.childrenAfterGroup.length > 0);
487 }
488
489 public isEmptyFillerNode(): boolean {
490 return this.group && _.missingOrEmpty(this.childrenAfterGroup);
491 }
492
493 private dispatchCellChangedEvent(column: Column, newValue: any): void {
494 let cellChangedEvent: CellChangedEvent = {
495 type: RowNode.EVENT_CELL_CHANGED,
496 node: this,
497 column: column,
498 newValue: newValue
499 };
500 this.dispatchLocalEvent(cellChangedEvent);
501 }
502
503 public resetQuickFilterAggregateText(): void {
504 this.quickFilterAggregateText = null;
505 }
506
507 public isExpandable(): boolean {
508 return this.hasChildren() || this.master;
509 }
510
511 public isSelected(): boolean {
512 // for footers, we just return what our sibling selected state is, as cannot select a footer
513 if (this.footer) {
514 return this.sibling.isSelected();
515 }
516
517 return this.selected;
518 }
519
520 public depthFirstSearch( callback: (rowNode: RowNode) => void ): void {
521 if (this.childrenAfterGroup) {
522 this.childrenAfterGroup.forEach( child => child.depthFirstSearch(callback) );
523 }
524 callback(this);
525 }
526
527 // + rowController.updateGroupsInSelection()
528 // + selectionController.calculatedSelectedForAllGroupNodes()
529 public calculateSelectedFromChildren(): void {
530 let atLeastOneSelected = false;
531 let atLeastOneDeSelected = false;
532 let atLeastOneMixed = false;
533
534 let newSelectedValue: boolean;
535 if (this.childrenAfterGroup) {
536 for (let i = 0; i < this.childrenAfterGroup.length; i++) {
537 let child = this.childrenAfterGroup[i];
538
539 // skip non-selectable nodes to prevent inconsistent selection values
540 if (!child.selectable) continue;
541
542 let childState = child.isSelected();
543 switch (childState) {
544 case true:
545 atLeastOneSelected = true;
546 break;
547 case false:
548 atLeastOneDeSelected = true;
549 break;
550 default:
551 atLeastOneMixed = true;
552 break;
553 }
554 }
555 }
556
557 if (atLeastOneMixed) {
558 newSelectedValue = undefined;
559 } else if (atLeastOneSelected && !atLeastOneDeSelected) {
560 newSelectedValue = true;
561 } else if (!atLeastOneSelected && atLeastOneDeSelected) {
562 newSelectedValue = false;
563 } else {
564 newSelectedValue = undefined;
565 }
566 this.selectThisNode(newSelectedValue);
567 }
568
569 public setSelectedInitialValue(selected: boolean): void {
570 this.selected = selected;
571 }
572
573 public setSelected(newValue: boolean, clearSelection: boolean = false, suppressFinishActions: boolean = false) {
574 this.setSelectedParams({
575 newValue: newValue,
576 clearSelection: clearSelection,
577 suppressFinishActions: suppressFinishActions,
578 rangeSelect: false
579 });
580 }
581
582 public isRowPinned(): boolean {
583 return this.rowPinned === Constants.PINNED_TOP || this.rowPinned === Constants.PINNED_BOTTOM;
584 }
585
586 // to make calling code more readable, this is the same method as setSelected except it takes names parameters
587 public setSelectedParams(params: SetSelectedParams): number {
588
589 let groupSelectsChildren = this.gridOptionsWrapper.isGroupSelectsChildren();
590
591 let newValue = params.newValue === true;
592 let clearSelection = params.clearSelection === true;
593 let suppressFinishActions = params.suppressFinishActions === true;
594 let rangeSelect = params.rangeSelect === true;
595 // groupSelectsFiltered only makes sense when group selects children
596 let groupSelectsFiltered = groupSelectsChildren && (params.groupSelectsFiltered === true);
597
598 if (this.id===undefined) {
599 console.warn('ag-Grid: cannot select node until id for node is known');
600 return 0;
601 }
602
603 if (this.rowPinned) {
604 console.log('ag-Grid: cannot select pinned rows');
605 return 0;
606 }
607
608 // if we are a footer, we don't do selection, just pass the info
609 // to the sibling (the parent of the group)
610 if (this.footer) {
611 let count = this.sibling.setSelectedParams(params);
612 return count;
613 }
614
615 if (rangeSelect) {
616 let newRowClicked = this.selectionController.getLastSelectedNode() !== this;
617 let allowMultiSelect = this.gridOptionsWrapper.isRowSelectionMulti();
618 if (newRowClicked && allowMultiSelect) {
619 return this.doRowRangeSelection();
620 }
621 }
622
623 let updatedCount = 0;
624
625 // when groupSelectsFiltered, then this node may end up intermediate despite
626 // trying to set it to true / false. this group will be calculated further on
627 // down when we call calculatedSelectedForAllGroupNodes(). we need to skip it
628 // here, otherwise the updatedCount would include it.
629 let skipThisNode = groupSelectsFiltered && this.group;
630 if (!skipThisNode) {
631 let thisNodeWasSelected = this.selectThisNode(newValue);
632 if (thisNodeWasSelected) { updatedCount++; }
633 }
634
635 if (groupSelectsChildren && this.group) {
636 updatedCount += this.selectChildNodes(newValue, groupSelectsFiltered);
637 }
638
639 // clear other nodes if not doing multi select
640 if (!suppressFinishActions) {
641
642 let clearOtherNodes = newValue && (clearSelection || !this.gridOptionsWrapper.isRowSelectionMulti());
643 if (clearOtherNodes) {
644 updatedCount += this.selectionController.clearOtherNodes(this);
645 }
646
647 // only if we selected something, then update groups and fire events
648 if (updatedCount>0) {
649
650 this.selectionController.updateGroupsFromChildrenSelections();
651
652 // this is the very end of the 'action node', so we are finished all the updates,
653 // include any parent / child changes that this method caused
654 let event: SelectionChangedEvent = {
655 type: Events.EVENT_SELECTION_CHANGED,
656 api: this.gridApi,
657 columnApi: this.columnApi
658 };
659 this.mainEventService.dispatchEvent(event);
660 }
661
662 // so if user next does shift-select, we know where to start the selection from
663 if (newValue) {
664 this.selectionController.setLastSelectedNode(this);
665 }
666 }
667
668 return updatedCount;
669 }
670
671 // selects all rows between this node and the last selected node (or the top if this is the first selection).
672 // not to be mixed up with 'cell range selection' where you drag the mouse, this is row range selection, by
673 // holding down 'shift'.
674 private doRowRangeSelection(): number {
675 let updatedCount = 0;
676
677 let groupsSelectChildren = this.gridOptionsWrapper.isGroupSelectsChildren();
678 let lastSelectedNode = this.selectionController.getLastSelectedNode();
679
680 let nodesToSelect = this.rowModel.getNodesInRangeForSelection(this, lastSelectedNode);
681
682 nodesToSelect.forEach( rowNode => {
683 if (rowNode.group && groupsSelectChildren) { return; }
684
685 let nodeWasSelected = rowNode.selectThisNode(true);
686 if (nodeWasSelected) {
687 updatedCount++;
688 }
689 });
690
691 this.selectionController.updateGroupsFromChildrenSelections();
692
693 let event: SelectionChangedEvent = {
694 type: Events.EVENT_SELECTION_CHANGED,
695 api: this.gridApi,
696 columnApi: this.columnApi
697 };
698 this.mainEventService.dispatchEvent(event);
699
700 return updatedCount;
701 }
702
703 public isParentOfNode(potentialParent: RowNode): boolean {
704 let parentNode = this.parent;
705 while (parentNode) {
706 if (parentNode === potentialParent) {
707 return true;
708 }
709 parentNode = parentNode.parent;
710 }
711 return false;
712 }
713
714 public selectThisNode(newValue: boolean): boolean {
715 if(!this.selectable || this.selected === newValue) return false;
716
717 this.selected = newValue;
718
719 if (this.eventService) {
720 this.dispatchLocalEvent(this.createLocalRowEvent(RowNode.EVENT_ROW_SELECTED));
721 }
722
723 let event: RowSelectedEvent = this.createGlobalRowEvent(Events.EVENT_ROW_SELECTED);
724 this.mainEventService.dispatchEvent(event);
725
726 return true;
727 }
728
729 private selectChildNodes(newValue: boolean, groupSelectsFiltered: boolean): number {
730 let children = groupSelectsFiltered ? this.childrenAfterFilter : this.childrenAfterGroup;
731
732 let updatedCount = 0;
733 if (_.missing(children)) { return; }
734 for (let i = 0; i<children.length; i++) {
735 updatedCount += children[i].setSelectedParams({
736 newValue: newValue,
737 clearSelection: false,
738 suppressFinishActions: true,
739 groupSelectsFiltered
740 });
741 }
742 return updatedCount;
743 }
744
745 public addEventListener(eventType: string, listener: Function): void {
746 if (!this.eventService) { this.eventService = new EventService(); }
747 this.eventService.addEventListener(eventType, listener);
748 }
749
750 public removeEventListener(eventType: string, listener: Function): void {
751 this.eventService.removeEventListener(eventType, listener);
752 }
753
754 public onMouseEnter(): void {
755 this.dispatchLocalEvent(this.createLocalRowEvent(RowNode.EVENT_MOUSE_ENTER));
756 }
757
758 public onMouseLeave(): void {
759 this.dispatchLocalEvent(this.createLocalRowEvent(RowNode.EVENT_MOUSE_LEAVE));
760 }
761
762 public getFirstChildOfFirstChild(rowGroupColumn: Column): RowNode {
763 let currentRowNode: RowNode = this;
764
765 // if we are hiding groups, then if we are the first child, of the first child,
766 // all the way up to the column we are interested in, then we show the group cell.
767
768 let isCandidate = true;
769 let foundFirstChildPath = false;
770 let nodeToSwapIn: RowNode;
771
772 while (isCandidate && !foundFirstChildPath) {
773
774 let parentRowNode = currentRowNode.parent;
775 let firstChild = _.exists(parentRowNode) && currentRowNode.firstChild;
776
777 if (firstChild) {
778 if (parentRowNode.rowGroupColumn === rowGroupColumn) {
779 foundFirstChildPath = true;
780 nodeToSwapIn = parentRowNode;
781 }
782 } else {
783 isCandidate = false;
784 }
785
786 currentRowNode = parentRowNode;
787 }
788
789 return foundFirstChildPath ? nodeToSwapIn : null;
790 }
791}
\No newline at end of file