UNPKG

102 kBPlain TextView Raw
1import {Utils as _} from "../utils";
2import {ColumnGroup} from "../entities/columnGroup";
3import {Column} from "../entities/column";
4import {AbstractColDef, ColDef, ColGroupDef, IAggFunc} from "../entities/colDef";
5import {ColumnGroupChild} from "../entities/columnGroupChild";
6import {GridOptionsWrapper} from "../gridOptionsWrapper";
7import {ExpressionService} from "../valueService/expressionService";
8import {BalancedColumnTreeBuilder} from "./balancedColumnTreeBuilder";
9import {DisplayedGroupCreator} from "./displayedGroupCreator";
10import {AutoWidthCalculator} from "../rendering/autoWidthCalculator";
11import {OriginalColumnGroupChild} from "../entities/originalColumnGroupChild";
12import {EventService} from "../eventService";
13import {ColumnUtils} from "./columnUtils";
14import {Logger, LoggerFactory} from "../logger";
15import {
16 ColumnEvent, ColumnEventType, ColumnEverythingChangedEvent,
17 ColumnGroupOpenedEvent,
18 ColumnMovedEvent,
19 ColumnPinnedEvent,
20 ColumnPivotModeChangedEvent,
21 ColumnResizedEvent,
22 ColumnRowGroupChangedEvent,
23 ColumnValueChangedEvent,
24 ColumnVisibleEvent,
25 DisplayedColumnsChangedEvent,
26 DisplayedColumnsWidthChangedEvent,
27 Events,
28 GridColumnsChangedEvent,
29 NewColumnsLoadedEvent,
30 VirtualColumnsChangedEvent
31} from "../events";
32import {OriginalColumnGroup} from "../entities/originalColumnGroup";
33import {GroupInstanceIdCreator} from "./groupInstanceIdCreator";
34import {Autowired, Bean, Context, Optional, PostConstruct, Qualifier} from "../context/context";
35import {GridPanel} from "../gridPanel/gridPanel";
36import {IAggFuncService} from "../interfaces/iAggFuncService";
37import {ColumnAnimationService} from "../rendering/columnAnimationService";
38import {AutoGroupColService} from "./autoGroupColService";
39import {RowNode} from "../entities/rowNode";
40import {ValueCache} from "../valueService/valueCache";
41import {GridApi} from "../gridApi";
42import {ColumnApi} from "./columnApi";
43
44export interface ColumnResizeSet {
45 columns: Column[];
46 ratios: number[];
47 width: number;
48}
49
50export interface ColumnState {
51 colId: string,
52 hide?: boolean,
53 aggFunc?: string | IAggFunc,
54 width?: number,
55 pivotIndex?: number,
56 pinned?: boolean | string | "left" | "right",
57 rowGroupIndex?: number
58}
59
60@Bean('columnController')
61export class ColumnController {
62
63 @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
64 @Autowired('expressionService') private expressionService: ExpressionService;
65 @Autowired('balancedColumnTreeBuilder') private balancedColumnTreeBuilder: BalancedColumnTreeBuilder;
66 @Autowired('displayedGroupCreator') private displayedGroupCreator: DisplayedGroupCreator;
67 @Autowired('autoWidthCalculator') private autoWidthCalculator: AutoWidthCalculator;
68 @Autowired('eventService') private eventService: EventService;
69 @Autowired('columnUtils') private columnUtils: ColumnUtils;
70 @Autowired('context') private context: Context;
71 @Autowired('columnAnimationService') private columnAnimationService: ColumnAnimationService;
72 @Autowired('autoGroupColService') private autoGroupColService: AutoGroupColService;
73 @Optional('aggFuncService') private aggFuncService: IAggFuncService;
74 @Optional('valueCache') private valueCache: ValueCache;
75
76 @Autowired('columnApi') private columnApi: ColumnApi;
77 @Autowired('gridApi') private gridApi: GridApi;
78
79 // these are the columns provided by the client. this doesn't change, even if the
80 // order or state of the columns and groups change. it will only change if the client
81 // provides a new set of column definitions. otherwise this tree is used to build up
82 // the groups for displaying.
83 private primaryBalancedTree: OriginalColumnGroupChild[];
84 // header row count, based on user provided columns
85 private primaryHeaderRowCount = 0;
86 // all columns provided by the user. basically it's the leaf level nodes of the
87 // tree above (originalBalancedTree)
88 private primaryColumns: Column[]; // every column available
89
90 // if pivoting, these are the generated columns as a result of the pivot
91 private secondaryBalancedTree: OriginalColumnGroupChild[];
92 private secondaryColumns: Column[];
93 private secondaryHeaderRowCount = 0;
94 private secondaryColumnsPresent = false;
95
96 // the columns the quick filter should use. this will be all primary columns
97 // plus the autoGroupColumns if any exist
98 private columnsForQuickFilter: Column[];
99
100 // these are all columns that are available to the grid for rendering after pivot
101 private gridBalancedTree: OriginalColumnGroupChild[];
102 private gridColumns: Column[];
103 // header row count, either above, or based on pivoting if we are pivoting
104 private gridHeaderRowCount = 0;
105
106 private lastPrimaryOrder: Column[];
107 private gridColsArePrimary: boolean;
108
109 // these are the columns actually shown on the screen. used by the header renderer,
110 // as header needs to know about column groups and the tree structure.
111 private displayedLeftColumnTree: ColumnGroupChild[];
112 private displayedRightColumnTree: ColumnGroupChild[];
113 private displayedCentreColumnTree: ColumnGroupChild[];
114
115 private displayedLeftHeaderRows: {[row: number]: ColumnGroupChild[]};
116 private displayedRightHeaderRows: {[row: number]: ColumnGroupChild[]};
117 private displayedCentreHeaderRows: {[row: number]: ColumnGroupChild[]};
118
119 // these are the lists used by the rowRenderer to render nodes. almost the leaf nodes of the above
120 // displayed trees, however it also takes into account if the groups are open or not.
121 private displayedLeftColumns: Column[] = [];
122 private displayedRightColumns: Column[] = [];
123 private displayedCenterColumns: Column[] = [];
124 // all three lists above combined
125 private allDisplayedColumns: Column[] = [];
126 // same as above, except trimmed down to only columns within the viewport
127 private allDisplayedVirtualColumns: Column[] = [];
128 private allDisplayedCenterVirtualColumns: Column[] = [];
129
130 // true if we are doing column spanning
131 private colSpanActive: boolean;
132
133 // primate columns that have colDef.autoHeight set
134 private autoRowHeightColumns: Column[];
135
136 private suppressColumnVirtualisation: boolean;
137
138 private rowGroupColumns: Column[] = [];
139 private valueColumns: Column[] = [];
140 private pivotColumns: Column[] = [];
141
142 private groupAutoColumns: Column[];
143
144 private groupDisplayColumns: Column[];
145
146 private ready = false;
147 private logger: Logger;
148
149 private autoGroupsNeedBuilding = false;
150
151 private pivotMode = false;
152 private usingTreeData: boolean;
153
154 // for horizontal visualisation of columns
155 private scrollWidth: number;
156 private scrollPosition: number;
157
158 private bodyWidth = 0;
159 private leftWidth = 0;
160 private rightWidth = 0;
161
162 private bodyWidthDirty = true;
163
164 private viewportLeft: number;
165 private viewportRight: number;
166
167 @PostConstruct
168 public init(): void {
169 let pivotMode = this.gridOptionsWrapper.isPivotMode();
170 this.suppressColumnVirtualisation = this.gridOptionsWrapper.isSuppressColumnVirtualisation();
171
172 if (this.isPivotSettingAllowed(pivotMode)) {
173 this.pivotMode = pivotMode;
174 }
175 this.usingTreeData = this.gridOptionsWrapper.isTreeData();
176 }
177
178 public isAutoRowHeightActive(): boolean {
179 return this.autoRowHeightColumns && this.autoRowHeightColumns.length > 0;
180 }
181
182 public getAllAutoRowHeightCols(): Column[] {
183 return this.autoRowHeightColumns;
184 }
185
186 private setVirtualViewportLeftAndRight(): void {
187 if (this.gridOptionsWrapper.isEnableRtl()) {
188 this.viewportLeft = this.bodyWidth - this.scrollPosition - this.scrollWidth;
189 this.viewportRight = this.bodyWidth - this.scrollPosition;
190 } else {
191 this.viewportLeft = this.scrollPosition;
192 this.viewportRight = this.scrollWidth + this.scrollPosition;
193 }
194 }
195
196 // used by clipboard service, to know what columns to paste into
197 public getDisplayedColumnsStartingAt(column: Column): Column[] {
198 let currentColumn = column;
199 let result: Column[] = [];
200 while (_.exists(currentColumn)) {
201 result.push(currentColumn);
202 currentColumn = this.getDisplayedColAfter(currentColumn);
203 }
204 return result;
205 }
206
207 // checks what columns are currently displayed due to column virtualisation. fires an event
208 // if the list of columns has changed.
209 // + setColumnWidth(), setVirtualViewportPosition(), setColumnDefs(), sizeColumnsToFit()
210 private checkDisplayedVirtualColumns(): void {
211 // check displayCenterColumnTree exists first, as it won't exist when grid is initialising
212 if (_.exists(this.displayedCenterColumns)) {
213 let hashBefore = this.allDisplayedVirtualColumns.map( column => column.getId() ).join('#');
214 this.updateVirtualSets();
215 let hashAfter = this.allDisplayedVirtualColumns.map( column => column.getId() ).join('#');
216 if (hashBefore !== hashAfter) {
217 let event: VirtualColumnsChangedEvent = {
218 type: Events.EVENT_VIRTUAL_COLUMNS_CHANGED,
219 api: this.gridApi,
220 columnApi: this.columnApi
221 };
222 this.eventService.dispatchEvent(event);
223 }
224 }
225 }
226
227 public setVirtualViewportPosition(scrollWidth: number, scrollPosition: number): void {
228 if (scrollWidth!==this.scrollWidth || scrollPosition!==this.scrollPosition || this.bodyWidthDirty) {
229 this.scrollWidth = scrollWidth;
230 this.scrollPosition = scrollPosition;
231 // we need to call setVirtualViewportLeftAndRight() at least once after the body width changes,
232 // as the viewport can stay the same, but in RTL, if body width changes, we need to work out the
233 // virtual columns again
234 this.bodyWidthDirty = true;
235 this.setVirtualViewportLeftAndRight();
236 if (this.ready) {
237 this.checkDisplayedVirtualColumns();
238 }
239 }
240 }
241
242 public isPivotMode(): boolean {
243 return this.pivotMode;
244 }
245
246 private isPivotSettingAllowed(pivot: boolean): boolean {
247 if (pivot) {
248 if (this.gridOptionsWrapper.isTreeData()) {
249 console.warn("ag-Grid: Pivot mode not available in conjunction Tree Data i.e. 'gridOptions.treeData: true'");
250 return false;
251 } else {
252 return true;
253 }
254 } else {
255 return true;
256 }
257 }
258
259 public setPivotMode(pivotMode: boolean, source: ColumnEventType = "api"): void {
260 if (pivotMode === this.pivotMode) { return; }
261
262 if (!this.isPivotSettingAllowed(this.pivotMode)) { return; }
263
264 this.pivotMode = pivotMode;
265 this.updateDisplayedColumns(source);
266 let event: ColumnPivotModeChangedEvent = {
267 type: Events.EVENT_COLUMN_PIVOT_MODE_CHANGED,
268 api: this.gridApi,
269 columnApi: this.columnApi
270 };
271 this.eventService.dispatchEvent(event);
272 }
273
274 public getSecondaryPivotColumn(pivotKeys: string[], valueColKey: Column|string): Column {
275
276 if (!this.secondaryColumnsPresent) {
277 return null;
278 }
279
280 let valueColumnToFind = this.getPrimaryColumn(valueColKey);
281
282 let foundColumn: Column = null;
283 this.secondaryColumns.forEach( column => {
284
285 let thisPivotKeys = column.getColDef().pivotKeys;
286 let pivotValueColumn = column.getColDef().pivotValueColumn;
287
288 let pivotKeyMatches = _.compareArrays(thisPivotKeys, pivotKeys);
289 let pivotValueMatches = pivotValueColumn === valueColumnToFind;
290
291 if (pivotKeyMatches && pivotValueMatches) {
292 foundColumn = column;
293 }
294 });
295
296 return foundColumn;
297 }
298
299 private setBeans(@Qualifier('loggerFactory') loggerFactory: LoggerFactory) {
300 this.logger = loggerFactory.create('ColumnController');
301 }
302
303 private setFirstRightAndLastLeftPinned(source: ColumnEventType): void {
304 let lastLeft: Column;
305 let firstRight: Column;
306
307 if (this.gridOptionsWrapper.isEnableRtl()) {
308 lastLeft = this.displayedLeftColumns ? this.displayedLeftColumns[0] : null;
309 firstRight = this.displayedRightColumns ? this.displayedRightColumns[this.displayedRightColumns.length - 1] : null;
310 } else {
311 lastLeft = this.displayedLeftColumns ? this.displayedLeftColumns[this.displayedLeftColumns.length - 1] : null;
312 firstRight = this.displayedRightColumns ? this.displayedRightColumns[0] : null;
313 }
314
315 this.gridColumns.forEach( (column: Column) => {
316 column.setLastLeftPinned(column === lastLeft, source);
317 column.setFirstRightPinned(column === firstRight, source);
318 } );
319 }
320
321 public autoSizeColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
322 // because of column virtualisation, we can only do this function on columns that are
323 // actually rendered, as non-rendered columns (outside the viewport and not rendered
324 // due to column virtualisation) are not present. this can result in all rendered columns
325 // getting narrowed, which in turn introduces more rendered columns on the RHS which
326 // did not get autosized in the original run, leaving the visible grid with columns on
327 // the LHS sized, but RHS no. so we keep looping through teh visible columns until
328 // no more cols are available (rendered) to be resized
329
330 // keep track of which cols we have resized in here
331 let columnsAutosized: Column[] = [];
332 // initialise with anything except 0 so that while loop executs at least once
333 let changesThisTimeAround = -1;
334
335 while (changesThisTimeAround!==0) {
336 changesThisTimeAround = 0;
337 this.actionOnGridColumns(keys, (column: Column): boolean => {
338 // if already autosized, skip it
339 if (columnsAutosized.indexOf(column) >= 0) { return; }
340 // get how wide this col should be
341 let preferredWidth = this.autoWidthCalculator.getPreferredWidthForColumn(column);
342 // preferredWidth = -1 if this col is not on the screen
343 if (preferredWidth>0) {
344 let newWidth = this.normaliseColumnWidth(column, preferredWidth);
345 column.setActualWidth(newWidth, source);
346 columnsAutosized.push(column);
347 changesThisTimeAround++;
348 }
349 return true;
350 }, source);
351 }
352
353 if (columnsAutosized.length > 0) {
354 let event: ColumnResizedEvent = {
355 type: Events.EVENT_COLUMN_RESIZED,
356 columns: columnsAutosized,
357 column: columnsAutosized.length === 1 ? columnsAutosized[0] : null,
358 finished: true,
359 api: this.gridApi,
360 columnApi: this.columnApi,
361 source: "autosizeColumns"
362 };
363 this.eventService.dispatchEvent(event);
364 }
365 }
366
367 public autoSizeColumn(key: string|Column, source: ColumnEventType = "api"): void {
368 this.autoSizeColumns([key], source);
369 }
370
371 public autoSizeAllColumns(source: ColumnEventType = "api"): void {
372 let allDisplayedColumns = this.getAllDisplayedColumns();
373 this.autoSizeColumns(allDisplayedColumns, source);
374 }
375
376 private getColumnsFromTree(rootColumns: OriginalColumnGroupChild[]): Column[] {
377 let result: Column[] = [];
378 recursiveFindColumns(rootColumns);
379 return result;
380
381 function recursiveFindColumns(childColumns: OriginalColumnGroupChild[]): void {
382 for (let i = 0; i<childColumns.length; i++) {
383 let child = childColumns[i];
384 if (child instanceof Column) {
385 result.push(<Column>child);
386 } else if (child instanceof OriginalColumnGroup) {
387 recursiveFindColumns((<OriginalColumnGroup>child).getChildren());
388 }
389 }
390 }
391 }
392
393 public getAllDisplayedColumnGroups(): ColumnGroupChild[] {
394 if (this.displayedLeftColumnTree && this.displayedRightColumnTree && this.displayedCentreColumnTree) {
395 return this.displayedLeftColumnTree
396 .concat(this.displayedCentreColumnTree)
397 .concat(this.displayedRightColumnTree);
398 } else {
399 return null;
400 }
401 }
402
403 // + columnSelectPanel
404 public getPrimaryColumnTree(): OriginalColumnGroupChild[] {
405 return this.primaryBalancedTree;
406 }
407
408 // + gridPanel -> for resizing the body and setting top margin
409 public getHeaderRowCount(): number {
410 return this.gridHeaderRowCount;
411 }
412
413 // + headerRenderer -> setting pinned body width
414 public getLeftDisplayedColumnGroups(): ColumnGroupChild[] {
415 return this.displayedLeftColumnTree;
416 }
417 // + headerRenderer -> setting pinned body width
418 public getRightDisplayedColumnGroups(): ColumnGroupChild[] {
419 return this.displayedRightColumnTree;
420 }
421 // + headerRenderer -> setting pinned body width
422 public getCenterDisplayedColumnGroups(): ColumnGroupChild[] {
423 return this.displayedCentreColumnTree;
424 }
425
426 public getDisplayedColumnGroups(type: string): ColumnGroupChild[] {
427 switch (type) {
428 case Column.PINNED_LEFT: return this.getLeftDisplayedColumnGroups();
429 case Column.PINNED_RIGHT: return this.getRightDisplayedColumnGroups();
430 default: return this.getCenterDisplayedColumnGroups();
431 }
432 }
433
434 // gridPanel -> ensureColumnVisible
435 public isColumnDisplayed(column: Column): boolean {
436 return this.getAllDisplayedColumns().indexOf(column) >= 0;
437 }
438
439 // + csvCreator
440 public getAllDisplayedColumns(): Column[] {
441 return this.allDisplayedColumns;
442 }
443
444 public getAllDisplayedVirtualColumns(): Column[] {
445 return this.allDisplayedVirtualColumns;
446 }
447
448 public getDisplayedLeftColumnsForRow(rowNode: RowNode): Column[] {
449 if (!this.colSpanActive) {
450 return this.displayedLeftColumns;
451 } else {
452 return this.getDisplayedColumnsForRow(rowNode, this.displayedLeftColumns);
453 }
454 }
455
456 public getDisplayedRightColumnsForRow(rowNode: RowNode): Column[] {
457 if (!this.colSpanActive) {
458 return this.displayedRightColumns;
459 } else {
460 return this.getDisplayedColumnsForRow(rowNode, this.displayedRightColumns);
461 }
462 }
463
464 private getDisplayedColumnsForRow(rowNode: RowNode, displayedColumns: Column[],
465 filterCallback?: (column: Column)=>boolean,
466 emptySpaceBeforeColumn?: (column: Column)=>boolean): Column[] {
467
468 let result: Column[] = [];
469 let lastConsideredCol: Column = null;
470
471 for (let i = 0; i<displayedColumns.length; i++) {
472
473 let col = displayedColumns[i];
474
475 let colSpan = col.getColSpan(rowNode);
476 let columnsToCheckFilter: Column[] = [col];
477 if (colSpan > 1) {
478 let colsToRemove = colSpan - 1;
479
480 for (let j = 1; j<=colsToRemove; j++) {
481 columnsToCheckFilter.push(displayedColumns[i+j]);
482 }
483
484 i += colsToRemove;
485 }
486
487 // see which cols we should take out for column virtualisation
488 let filterPasses: boolean;
489 if (filterCallback) {
490 // if user provided a callback, means some columns may not be in the viewport.
491 // the user will NOT provide a callback if we are talking about pinned areas,
492 // as pinned areas have no horizontal scroll and do not virtualise the columns.
493 // if lots of columns, that means column spanning, and we set filterPasses = true
494 // if one or more of the columns spanned pass the filter.
495 filterPasses = false;
496 columnsToCheckFilter.forEach( colForFilter => {
497 if (filterCallback(colForFilter)) filterPasses = true;
498 });
499 } else {
500 filterPasses = true
501 }
502
503 if (filterPasses) {
504
505 if (result.length===0 && lastConsideredCol) {
506 let gapBeforeColumn = emptySpaceBeforeColumn ? emptySpaceBeforeColumn(col) : false;
507 if (gapBeforeColumn) {
508 result.push(lastConsideredCol);
509 }
510 }
511
512 result.push(col);
513 }
514
515 lastConsideredCol = col;
516 }
517
518 return result;
519 }
520
521 // + rowRenderer
522 // if we are not column spanning, this just returns back the virtual centre columns,
523 // however if we are column spanning, then different rows can have different virtual
524 // columns, so we have to work out the list for each individual row.
525 public getAllDisplayedCenterVirtualColumnsForRow(rowNode: RowNode): Column[] {
526
527 if (!this.colSpanActive) {
528 return this.allDisplayedCenterVirtualColumns;
529 }
530
531 let emptySpaceBeforeColumn = (col: Column) => col.getLeft() > this.viewportLeft;
532
533 // if doing column virtualisation, then we filter based on the viewport.
534 let filterCallback = this.suppressColumnVirtualisation ? null : this.isColumnInViewport.bind(this);
535
536 return this.getDisplayedColumnsForRow(rowNode, this.displayedCenterColumns,
537 filterCallback, emptySpaceBeforeColumn);
538 }
539
540 private isColumnInViewport(col: Column): boolean {
541 let columnLeft = col.getLeft();
542 let columnRight = col.getLeft() + col.getActualWidth();
543 let columnToMuchLeft = columnLeft < this.viewportLeft && columnRight < this.viewportLeft;
544 let columnToMuchRight = columnLeft > this.viewportRight && columnRight > this.viewportRight;
545
546 return !columnToMuchLeft && !columnToMuchRight;
547 }
548
549 // used by:
550 // + angularGrid -> setting pinned body width
551 // note: this should be cached
552 public getPinnedLeftContainerWidth() {
553 return this.getWidthOfColsInList(this.displayedLeftColumns);
554 }
555 // note: this should be cached
556 public getPinnedRightContainerWidth() {
557 return this.getWidthOfColsInList(this.displayedRightColumns);
558 }
559
560 public updatePrimaryColumnList( keys: (string|Column)[],
561 masterList: Column[],
562 actionIsAdd: boolean,
563 columnCallback: (column: Column)=>void,
564 eventType: string,
565 source: ColumnEventType = "api") {
566
567 if (_.missingOrEmpty(keys)) { return; }
568
569 let atLeastOne = false;
570
571 keys.forEach( key => {
572 let columnToAdd = this.getPrimaryColumn(key);
573 if (!columnToAdd) {return;}
574
575 if (actionIsAdd) {
576 if (masterList.indexOf(columnToAdd)>=0) {return;}
577 masterList.push(columnToAdd);
578 } else {
579 if (masterList.indexOf(columnToAdd)<0) {return;}
580 _.removeFromArray(masterList, columnToAdd);
581 }
582
583 columnCallback(columnToAdd);
584 atLeastOne = true;
585 });
586
587 if (!atLeastOne) { return; }
588
589 if (this.autoGroupsNeedBuilding) {
590 this.updateGridColumns();
591 }
592
593 this.updateDisplayedColumns(source);
594
595 let event: ColumnEvent = {
596 type: eventType,
597 columns: masterList,
598 column: masterList.length === 1 ? masterList[0] : null,
599 api: this.gridApi,
600 columnApi: this.columnApi,
601 source: source
602 };
603
604 this.eventService.dispatchEvent(event);
605 }
606
607 public setRowGroupColumns(colKeys: (string|Column)[], source: ColumnEventType = "api"): void {
608 this.autoGroupsNeedBuilding = true;
609 this.setPrimaryColumnList(colKeys, this.rowGroupColumns,
610 Events.EVENT_COLUMN_ROW_GROUP_CHANGED,
611 this.setRowGroupActive.bind(this),
612 source);
613 }
614
615 private setRowGroupActive(active: boolean, column: Column, source: ColumnEventType): void {
616 if (active === column.isRowGroupActive()) {return;}
617 column.setRowGroupActive(active, source);
618 if (!active && !this.gridOptionsWrapper.isSuppressMakeColumnVisibleAfterUnGroup()) {
619 column.setVisible(true, source);
620 }
621 }
622
623 public addRowGroupColumn(key: string|Column, source: ColumnEventType = "api"): void {
624 this.addRowGroupColumns([key], source);
625 }
626
627 public addRowGroupColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
628 this.autoGroupsNeedBuilding = true;
629 this.updatePrimaryColumnList(keys, this.rowGroupColumns, true,
630 this.setRowGroupActive.bind(this, true),
631 Events.EVENT_COLUMN_ROW_GROUP_CHANGED,
632 source);
633 }
634
635 public removeRowGroupColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
636 this.autoGroupsNeedBuilding = true;
637 this.updatePrimaryColumnList(keys, this.rowGroupColumns, false,
638 this.setRowGroupActive.bind(this, false),
639 Events.EVENT_COLUMN_ROW_GROUP_CHANGED,
640 source);
641 }
642
643 public removeRowGroupColumn(key: string|Column, source: ColumnEventType = "api"): void {
644 this.removeRowGroupColumns([key], source);
645 }
646
647 public addPivotColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
648 this.updatePrimaryColumnList(keys, this.pivotColumns, true,
649 column => column.setPivotActive(true, source),
650 Events.EVENT_COLUMN_PIVOT_CHANGED, source);
651 }
652
653 public setPivotColumns(colKeys: (string|Column)[], source: ColumnEventType = "api"): void {
654 this.setPrimaryColumnList(colKeys, this.pivotColumns, Events.EVENT_COLUMN_PIVOT_CHANGED,
655 (added: boolean, column: Column) => {
656 column.setPivotActive(added, source);
657 }, source
658 );
659 }
660
661 public addPivotColumn(key: string|Column, source: ColumnEventType = "api"): void {
662 this.addPivotColumns([key], source);
663 }
664
665 public removePivotColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
666 this.updatePrimaryColumnList(keys, this.pivotColumns, false,
667 column => column.setPivotActive(false, source),
668 Events.EVENT_COLUMN_PIVOT_CHANGED,
669 source);
670 }
671
672 public removePivotColumn(key: string|Column, source: ColumnEventType = "api"): void {
673 this.removePivotColumns([key], source);
674 }
675
676 private setPrimaryColumnList(colKeys: (string|Column)[],
677 masterList: Column[],
678 eventName: string,
679 columnCallback: (added: boolean, column: Column)=>void,
680 source: ColumnEventType
681 ): void {
682 masterList.length = 0;
683 if (_.exists(colKeys)) {
684 colKeys.forEach( key => {
685 let column = this.getPrimaryColumn(key);
686 masterList.push(column);
687 });
688 }
689
690 this.primaryColumns.forEach( column => {
691 let added = masterList.indexOf(column) >= 0;
692 columnCallback(added, column);
693 });
694
695 if (this.autoGroupsNeedBuilding) {
696 this.updateGridColumns();
697 }
698 this.updateDisplayedColumns(source);
699
700 let event: ColumnEvent = {
701 type: eventName,
702 columns: masterList,
703 column: masterList.length === 1 ? masterList[0] : null,
704 api: this.gridApi,
705 columnApi: this.columnApi,
706 source: source
707 };
708
709 this.eventService.dispatchEvent(event);
710 }
711
712 public setValueColumns(colKeys: (string|Column)[], source: ColumnEventType = "api"): void {
713 this.setPrimaryColumnList(colKeys, this.valueColumns,
714 Events.EVENT_COLUMN_VALUE_CHANGED,
715 this.setValueActive.bind(this),
716 source);
717 }
718
719 private setValueActive(active: boolean, column: Column, source: ColumnEventType): void {
720 if (active === column.isValueActive()) {return;}
721 column.setValueActive(active, source);
722 if (active && !column.getAggFunc()) {
723 let defaultAggFunc = this.aggFuncService.getDefaultAggFunc(column);
724 column.setAggFunc(defaultAggFunc);
725 }
726 }
727
728 public addValueColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
729 this.updatePrimaryColumnList(keys, this.valueColumns, true,
730 this.setValueActive.bind(this, true),
731 Events.EVENT_COLUMN_VALUE_CHANGED,
732 source);
733 }
734
735 public addValueColumn(colKey: (string|Column), source: ColumnEventType = "api"): void {
736 this.addValueColumns([colKey], source);
737 }
738
739 public removeValueColumn(colKey: (string|Column), source: ColumnEventType = "api"): void {
740 this.removeValueColumns([colKey], source);
741 }
742
743 public removeValueColumns(keys: (string|Column)[], source: ColumnEventType = "api"): void {
744 this.updatePrimaryColumnList(keys, this.valueColumns, false,
745 this.setValueActive.bind(this, false),
746 Events.EVENT_COLUMN_VALUE_CHANGED,
747 source);
748 }
749
750 // returns the width we can set to this col, taking into consideration min and max widths
751 private normaliseColumnWidth(column: Column, newWidth: number): number {
752 if (newWidth < column.getMinWidth()) {
753 newWidth = column.getMinWidth();
754 }
755
756 if (column.isGreaterThanMax(newWidth)) {
757 newWidth = column.getMaxWidth();
758 }
759
760 return newWidth;
761 }
762
763 private getPrimaryOrGridColumn(key: string|Column): Column {
764 let column = this.getPrimaryColumn(key);
765 if (column) {
766 return column;
767 } else {
768 return this.getGridColumn(key);
769 }
770 }
771
772 public setColumnWidth(
773 key: string|Column, // @key - the column who's size we want to change
774 newWidth: number, // @newWidth - width in pixels
775 shiftKey: boolean, // @takeFromAdjacent - if user has 'shift' pressed, then pixels are taken from adjacent column
776 finished: boolean, // @finished - ends up in the event, tells the user if more events are to come
777 source: ColumnEventType = "api"): void {
778
779 let col = this.getPrimaryOrGridColumn(key);
780 if (!col) {
781 return;
782 }
783
784 let sets: ColumnResizeSet[] = [];
785
786 sets.push({
787 width: newWidth,
788 ratios: [1],
789 columns: [col]
790 });
791
792 // if user wants to do shift resize by default, then we invert the shift operation
793 let defaultIsShift = this.gridOptionsWrapper.getColResizeDefault() === 'shift';
794 if (defaultIsShift) {
795 shiftKey = !shiftKey;
796 }
797
798 if (shiftKey) {
799 let otherCol = this.getDisplayedColAfter(col);
800 if (!otherCol) { return; }
801
802 let widthDiff = col.getActualWidth() - newWidth;
803 let otherColWidth = otherCol.getActualWidth() + widthDiff;
804
805 sets.push({
806 width: otherColWidth,
807 ratios: [1],
808 columns: [otherCol]
809 })
810 }
811
812 this.resizeColumnSets(sets, finished, source);
813 }
814
815 private checkMinAndMaxWidthsForSet(columnResizeSet: ColumnResizeSet): boolean {
816
817 let {columns, width} = columnResizeSet;
818
819 // every col has a min width, so sum them all up and see if we have enough room
820 // for all the min widths
821 let minWidthAccumulated = 0;
822 let maxWidthAccumulated = 0;
823 let maxWidthActive = true;
824 columns.forEach( col => {
825 minWidthAccumulated += col.getMinWidth();
826 if (col.getMaxWidth() > 0) {
827 maxWidthAccumulated += col.getMaxWidth();
828 } else {
829 // if at least one columns has no max width, it means the group of columns
830 // then has no max width, as at least one column can take as much width as possible
831 maxWidthActive = false;
832 }
833 });
834
835 let minWidthPasses = width >= minWidthAccumulated;
836
837 let maxWidthPasses = !maxWidthActive || (width <= maxWidthAccumulated);
838
839 return minWidthPasses && maxWidthPasses;
840 }
841
842 // method takes sets of columns and resizes them. either all sets will be resized, or nothing
843 // be resized. this is used for example when user tries to resize a group and holds shift key,
844 // then both the current group (grows), and the adjacent group (shrinks), will get resized,
845 // so that's two sets for this method.
846 public resizeColumnSets(resizeSets: ColumnResizeSet[],
847 finished: boolean,
848 source: ColumnEventType): void {
849
850 let passMinMaxCheck = _.every(resizeSets, this.checkMinAndMaxWidthsForSet.bind(this));
851
852 if (!passMinMaxCheck) { return; }
853
854 let changedCols: Column[] = [];
855 let allCols: Column[] = [];
856
857 resizeSets.forEach( set => {
858
859 let {width, columns, ratios} = set;
860
861 // keep track of pixels used, and last column gets the remaining,
862 // to cater for rounding errors, and min width adjustments
863 let newWidths: {[colId: string]: number} = {};
864
865 let finishedCols: {[colId: string]: boolean} = {};
866
867 columns.forEach( col => allCols.push(col) );
868
869 // the loop below goes through each col. if a col exceeds it's min/max width,
870 // it then gets set to its min/max width and the column is removed marked as 'finished'
871 // and the calculation is done again leaving this column out. take for example columns
872 // {A, width: 50, maxWidth: 100}
873 // {B, width: 50}
874 // {C, width: 50}
875 // and then the set is set to width 600 - on the first pass the grid tries to set each column
876 // to 200. it checks A and sees 200 > 100 and so sets the width to 100. col A is then marked
877 // as 'finished' and the calculation is done again with the remaining cols B and C, which end up
878 // splitting the remaining 500 pixels.
879 let finishedColsGrew = true;
880 let loopCount = 0;
881
882 while (finishedColsGrew) {
883
884 loopCount++;
885 if (loopCount>1000) {
886 // this should never happen, but in the future, someone might introduce a bug here,
887 // so we stop the browser from hanging and report bug properly
888 console.error('ag-Grid: infinite loop in resizeColumnSets');
889 break;
890 }
891
892 finishedColsGrew = false;
893
894 let subsetCols: Column[] = [];
895 let subsetRatios: number[] = [];
896 let subsetRatioTotal = 0;
897 let pixelsToDistribute = width;
898
899 columns.forEach( (col: Column, index: number) => {
900 let thisColFinished = finishedCols[col.getId()];
901 if (thisColFinished) {
902 pixelsToDistribute -= newWidths[col.getId()];
903 } else {
904 subsetCols.push(col);
905 let ratioThisCol = ratios[index];
906 subsetRatioTotal += ratioThisCol;
907 subsetRatios.push(ratioThisCol);
908 }
909 });
910
911 // because we are not using all of the ratios (cols can be missing),
912 // we scale the ratio. if all columns are included, then subsetRatioTotal=1,
913 // and so the ratioScale will be 1.
914 let ratioScale = 1 / subsetRatioTotal;
915
916 subsetCols.forEach( (col: Column, index: number) => {
917 let lastCol = index === (subsetCols.length - 1);
918 let colNewWidth: number;
919
920 if (lastCol) {
921 colNewWidth = pixelsToDistribute;
922 } else {
923 colNewWidth = Math.round(ratios[index] * width * ratioScale);
924 pixelsToDistribute -= colNewWidth;
925 }
926
927 if (colNewWidth < col.getMinWidth()) {
928 colNewWidth = col.getMinWidth();
929 finishedCols[col.getId()] = true;
930 finishedColsGrew = true;
931 } else if (col.getMaxWidth() > 0 && colNewWidth > col.getMaxWidth()) {
932 colNewWidth = col.getMaxWidth();
933 finishedCols[col.getId()] = true;
934 finishedColsGrew = true;
935 }
936
937 newWidths[col.getId()] = colNewWidth;
938 });
939 }
940
941 columns.forEach( col => {
942 let newWidth = newWidths[col.getId()];
943 if (col.getActualWidth() !== newWidth) {
944 col.setActualWidth(newWidth);
945 changedCols.push(col);
946 }
947 });
948 });
949
950 // if no cols changed, then no need to update more or send event.
951 let atLeastOneColChanged = changedCols.length > 0;
952
953 if (atLeastOneColChanged) {
954 this.setLeftValues(source);
955 this.updateBodyWidths();
956 this.checkDisplayedVirtualColumns();
957 }
958
959 // check for change first, to avoid unnecessary firing of events
960 // however we always fire 'finished' events. this is important
961 // when groups are resized, as if the group is changing slowly,
962 // eg 1 pixel at a time, then each change will fire change events
963 // in all the columns in the group, but only one with get the pixel.
964 if (atLeastOneColChanged || finished) {
965 let event: ColumnResizedEvent = {
966 type: Events.EVENT_COLUMN_RESIZED,
967 columns: allCols,
968 column: allCols.length === 1 ? allCols[0] : null,
969 finished: finished,
970 api: this.gridApi,
971 columnApi: this.columnApi,
972 source: source
973 };
974 this.eventService.dispatchEvent(event);
975 }
976 }
977
978 public setColumnAggFunc(column: Column, aggFunc: string, source: ColumnEventType = "api"): void {
979 column.setAggFunc(aggFunc);
980 let event: ColumnValueChangedEvent = {
981 type: Events.EVENT_COLUMN_VALUE_CHANGED,
982 columns: [column],
983 column: column,
984 api: this.gridApi,
985 columnApi: this.columnApi,
986 source: source
987 };
988 this.eventService.dispatchEvent(event);
989 }
990
991 public moveRowGroupColumn(fromIndex: number, toIndex: number, source: ColumnEventType = "api"): void {
992 let column = this.rowGroupColumns[fromIndex];
993 this.rowGroupColumns.splice(fromIndex, 1);
994 this.rowGroupColumns.splice(toIndex, 0, column);
995 let event: ColumnRowGroupChangedEvent = {
996 type: Events.EVENT_COLUMN_ROW_GROUP_CHANGED,
997 columns: this.rowGroupColumns,
998 column: this.rowGroupColumns.length === 1 ? this.rowGroupColumns[0] : null,
999 api: this.gridApi,
1000 columnApi: this.columnApi,
1001 source: source
1002 };
1003 this.eventService.dispatchEvent(event);
1004 }
1005
1006 public moveColumns(columnsToMoveKeys: (string|Column)[], toIndex: number, source: ColumnEventType = "api"): void {
1007 this.columnAnimationService.start();
1008
1009 if (toIndex > this.gridColumns.length - columnsToMoveKeys.length) {
1010 console.warn('ag-Grid: tried to insert columns in invalid location, toIndex = ' + toIndex);
1011 console.warn('ag-Grid: remember that you should not count the moving columns when calculating the new index');
1012 return;
1013 }
1014
1015 // we want to pull all the columns out first and put them into an ordered list
1016 let columnsToMove = this.getGridColumns(columnsToMoveKeys);
1017
1018 let failedRules = !this.doesMovePassRules(columnsToMove, toIndex);
1019 if (failedRules) { return; }
1020
1021 _.moveInArray(this.gridColumns, columnsToMove, toIndex);
1022
1023 this.updateDisplayedColumns(source);
1024
1025 let event: ColumnMovedEvent = {
1026 type: Events.EVENT_COLUMN_MOVED,
1027 columns: columnsToMove,
1028 column: columnsToMove.length === 1 ? columnsToMove[0] : null,
1029 toIndex: toIndex,
1030 api: this.gridApi,
1031 columnApi: this.columnApi,
1032 source: source
1033 };
1034 this.eventService.dispatchEvent(event);
1035
1036 this.columnAnimationService.finish();
1037 }
1038
1039 public doesMovePassRules(columnsToMove: Column[], toIndex: number): boolean {
1040
1041 // make a copy of what the grid columns would look like after the move
1042 let proposedColumnOrder = this.gridColumns.slice();
1043 _.moveInArray(proposedColumnOrder, columnsToMove, toIndex);
1044
1045 // then check that the new proposed order of the columns passes all rules
1046 if (!this.doesMovePassMarryChildren(proposedColumnOrder)) { return false; }
1047 if (!this.doesMovePassLockedPositions(proposedColumnOrder)) { return false; }
1048
1049 return true;
1050 }
1051
1052 public doesMovePassLockedPositions(proposedColumnOrder: Column[]): boolean {
1053
1054 let foundNonLocked = false;
1055 let rulePassed = true;
1056
1057 // go though the cols, see if any non-locked appear before any locked
1058 proposedColumnOrder.forEach( col => {
1059 if (col.isLockPosition()) {
1060 if (foundNonLocked) {
1061 rulePassed = false;
1062 }
1063 } else {
1064 foundNonLocked = true;
1065 }
1066 });
1067
1068 return rulePassed;
1069 }
1070
1071 public doesMovePassMarryChildren(allColumnsCopy: Column[]): boolean {
1072 let rulePassed = true;
1073
1074 this.columnUtils.depthFirstOriginalTreeSearch(this.gridBalancedTree, child => {
1075 if (!(child instanceof OriginalColumnGroup)) { return; }
1076
1077 let columnGroup = <OriginalColumnGroup> child;
1078
1079 let marryChildren = columnGroup.getColGroupDef() && columnGroup.getColGroupDef().marryChildren;
1080 if (!marryChildren) { return; }
1081
1082 let newIndexes: number[] = [];
1083 columnGroup.getLeafColumns().forEach( col => {
1084 let newColIndex = allColumnsCopy.indexOf(col);
1085 newIndexes.push(newColIndex);
1086 } );
1087
1088 let maxIndex = Math.max.apply(Math, newIndexes);
1089 let minIndex = Math.min.apply(Math, newIndexes);
1090
1091 // spread is how far the first column in this group is away from the last column
1092 let spread = maxIndex - minIndex;
1093 let maxSpread = columnGroup.getLeafColumns().length - 1;
1094
1095 // if the columns
1096 if (spread > maxSpread) {
1097 rulePassed = false;
1098 }
1099
1100 // console.log(`maxIndex = ${maxIndex}, minIndex = ${minIndex}, spread = ${spread}, maxSpread = ${maxSpread}, fail = ${spread > (count-1)}`)
1101 // console.log(allColumnsCopy.map( col => col.getColDef().field).join(','));
1102 });
1103
1104 return rulePassed;
1105 }
1106
1107 public moveColumn(key: string|Column, toIndex: number, source: ColumnEventType = "api") {
1108 this.moveColumns([key], toIndex, source);
1109 }
1110
1111 public moveColumnByIndex(fromIndex: number, toIndex: number, source: ColumnEventType = "api"): void {
1112 let column = this.gridColumns[fromIndex];
1113 this.moveColumn(column, toIndex, source);
1114 }
1115
1116 // used by:
1117 // + angularGrid -> for setting body width
1118 // + rowController -> setting main row widths (when inserting and resizing)
1119 // need to cache this
1120 public getBodyContainerWidth(): number {
1121 return this.bodyWidth;
1122 }
1123
1124 public getContainerWidth(pinned: string): number {
1125 switch (pinned) {
1126 case Column.PINNED_LEFT: return this.leftWidth;
1127 case Column.PINNED_RIGHT: return this.rightWidth;
1128 default: return this.bodyWidth;
1129 }
1130 }
1131
1132 // after setColumnWidth or updateGroupsAndDisplayedColumns
1133 private updateBodyWidths(): void {
1134 let newBodyWidth = this.getWidthOfColsInList(this.displayedCenterColumns);
1135 let newLeftWidth = this.getWidthOfColsInList(this.displayedLeftColumns);
1136 let newRightWidth = this.getWidthOfColsInList(this.displayedRightColumns);
1137
1138 // this is used by virtual col calculation, for RTL only, as a change to body width can impact displayed
1139 // columns, due to RTL inverting the y coordinates
1140 this.bodyWidthDirty = this.bodyWidth !== newBodyWidth;
1141
1142 let atLeastOneChanged = this.bodyWidth !== newBodyWidth || this.leftWidth !== newLeftWidth || this.rightWidth !== newRightWidth;
1143
1144 if (atLeastOneChanged) {
1145 this.bodyWidth = newBodyWidth;
1146 this.leftWidth = newLeftWidth;
1147 this.rightWidth = newRightWidth;
1148 // when this fires, it is picked up by the gridPanel, which ends up in
1149 // gridPanel calling setWidthAndScrollPosition(), which in turn calls setVirtualViewportPosition()
1150 let event: DisplayedColumnsWidthChangedEvent = {
1151 type: Events.EVENT_DISPLAYED_COLUMNS_WIDTH_CHANGED,
1152 api: this.gridApi,
1153 columnApi: this.columnApi
1154 };
1155 this.eventService.dispatchEvent(event);
1156 }
1157 }
1158
1159 // + rowController
1160 public getValueColumns(): Column[] {
1161 return this.valueColumns ? this.valueColumns : [];
1162 }
1163
1164 // + rowController
1165 public getPivotColumns(): Column[] {
1166 return this.pivotColumns ? this.pivotColumns : [];
1167 }
1168
1169 // + clientSideRowModel
1170 public isPivotActive(): boolean {
1171 return this.pivotColumns && this.pivotColumns.length > 0 && this.pivotMode;
1172 }
1173
1174 // + toolPanel
1175 public getRowGroupColumns(): Column[] {
1176 return this.rowGroupColumns ? this.rowGroupColumns : [];
1177 }
1178
1179 // + rowController -> while inserting rows
1180 public getDisplayedCenterColumns(): Column[] {
1181 return this.displayedCenterColumns;
1182 }
1183 // + rowController -> while inserting rows
1184 public getDisplayedLeftColumns(): Column[] {
1185 return this.displayedLeftColumns;
1186 }
1187 public getDisplayedRightColumns(): Column[] {
1188 return this.displayedRightColumns;
1189 }
1190
1191 public getDisplayedColumns(type: string): Column[] {
1192 switch (type) {
1193 case Column.PINNED_LEFT: return this.getDisplayedLeftColumns();
1194 case Column.PINNED_RIGHT: return this.getDisplayedRightColumns();
1195 default: return this.getDisplayedCenterColumns();
1196 }
1197 }
1198
1199 // used by:
1200 // + clientSideRowController -> sorting, building quick filter text
1201 // + headerRenderer -> sorting (clearing icon)
1202 public getAllPrimaryColumns(): Column[] {
1203 return this.primaryColumns;
1204 }
1205
1206 public getAllColumnsForQuickFilter(): Column[] {
1207 return this.columnsForQuickFilter;
1208 }
1209
1210 // + moveColumnController
1211 public getAllGridColumns(): Column[] {
1212 return this.gridColumns;
1213 }
1214
1215 public isEmpty(): boolean {
1216 return _.missingOrEmpty(this.gridColumns);
1217 }
1218
1219 public isRowGroupEmpty(): boolean {
1220 return _.missingOrEmpty(this.rowGroupColumns);
1221 }
1222
1223 public setColumnVisible(key: string|Column, visible: boolean, source: ColumnEventType = "api"): void {
1224 this.setColumnsVisible([key], visible, source);
1225 }
1226
1227 public setColumnsVisible(keys: (string|Column)[], visible: boolean, source: ColumnEventType = "api"): void {
1228 this.columnAnimationService.start();
1229 this.actionOnGridColumns(keys, (column: Column): boolean => {
1230 if (column.isVisible()!==visible) {
1231 column.setVisible(visible, source);
1232 return true;
1233 } else {
1234 return false;
1235 }
1236 }, source,()=> {
1237 let event: ColumnVisibleEvent = {
1238 type: Events.EVENT_COLUMN_VISIBLE,
1239 visible: visible,
1240 column: null,
1241 columns: null,
1242 api: this.gridApi,
1243 columnApi: this.columnApi,
1244 source: source
1245 };
1246 return event;
1247 });
1248 this.columnAnimationService.finish();
1249 }
1250
1251 public setColumnPinned(key: string|Column, pinned: string|boolean, source: ColumnEventType = "api"): void {
1252 this.setColumnsPinned([key], pinned, source);
1253 }
1254
1255 public setColumnsPinned(keys: (string|Column)[], pinned: string|boolean, source: ColumnEventType = "api"): void {
1256 this.columnAnimationService.start();
1257
1258 let actualPinned: string;
1259 if (pinned === true || pinned === Column.PINNED_LEFT) {
1260 actualPinned = Column.PINNED_LEFT;
1261 } else if (pinned === Column.PINNED_RIGHT) {
1262 actualPinned = Column.PINNED_RIGHT;
1263 } else {
1264 actualPinned = null;
1265 }
1266
1267 this.actionOnGridColumns(keys, (col: Column): boolean => {
1268 if (col.getPinned() !== actualPinned) {
1269 col.setPinned(actualPinned);
1270 return true;
1271 } else {
1272 return false;
1273 }
1274 }, source,()=> {
1275 let event: ColumnPinnedEvent = {
1276 type: Events.EVENT_COLUMN_PINNED,
1277 pinned: actualPinned,
1278 column: null,
1279 columns: null,
1280 api: this.gridApi,
1281 columnApi: this.columnApi,
1282 source: source
1283 };
1284 return event;
1285 });
1286
1287 this.columnAnimationService.finish();
1288 }
1289
1290 // does an action on a set of columns. provides common functionality for looking up the
1291 // columns based on key, getting a list of effected columns, and then updated the event
1292 // with either one column (if it was just one col) or a list of columns
1293 // used by: autoResize, setVisible, setPinned
1294 private actionOnGridColumns(// the column keys this action will be on
1295 keys: (string|Column)[],
1296 // the action to do - if this returns false, the column was skipped
1297 // and won't be included in the event
1298 action: (column: Column) => boolean,
1299 // should return back a column event of the right type
1300 source: ColumnEventType,
1301 createEvent?: ()=> ColumnEvent,
1302 ): void {
1303
1304 if (_.missingOrEmpty(keys)) { return; }
1305
1306 let updatedColumns: Column[] = [];
1307
1308 keys.forEach( (key: string|Column)=> {
1309 let column = this.getGridColumn(key);
1310 if (!column) {return;}
1311 // need to check for false with type (ie !== instead of !=)
1312 // as not returning anything (undefined) would also be false
1313 let resultOfAction = action(column);
1314 if (resultOfAction!==false) {
1315 updatedColumns.push(column);
1316 }
1317 });
1318
1319 if (updatedColumns.length===0) { return; }
1320
1321 this.updateDisplayedColumns(source);
1322
1323 if (_.exists(createEvent)) {
1324
1325 let event = createEvent();
1326
1327 event.columns = updatedColumns;
1328 event.column = updatedColumns.length === 1 ? updatedColumns[0] : null;
1329
1330 this.eventService.dispatchEvent(event);
1331 }
1332 }
1333
1334 public getDisplayedColBefore(col: Column): Column {
1335 let allDisplayedColumns = this.getAllDisplayedColumns();
1336 let oldIndex = allDisplayedColumns.indexOf(col);
1337 if (oldIndex > 0) {
1338 return allDisplayedColumns[oldIndex - 1];
1339 } else {
1340 return null;
1341 }
1342 }
1343
1344 // used by:
1345 // + rowRenderer -> for navigation
1346 public getDisplayedColAfter(col: Column): Column {
1347 let allDisplayedColumns = this.getAllDisplayedColumns();
1348 let oldIndex = allDisplayedColumns.indexOf(col);
1349 if (oldIndex < (allDisplayedColumns.length - 1)) {
1350 return allDisplayedColumns[oldIndex + 1];
1351 } else {
1352 return null;
1353 }
1354 }
1355
1356 public getDisplayedGroupAfter(columnGroup: ColumnGroup): ColumnGroup {
1357
1358 // pick one col in this group at random
1359 let col = columnGroup.getDisplayedLeafColumns()[0];
1360 let requiredLevel = columnGroup.getOriginalColumnGroup().getLevel();
1361
1362 while (true) {
1363 // keep moving to the next col, until we get to another group
1364 col = this.getDisplayedColAfter(col);
1365
1366 // if no col after, means no group after
1367 if (!col) { return null; }
1368
1369 // get group at same level as the one we are looking for
1370 let groupPointer = col.getParent();
1371 while (groupPointer.getOriginalColumnGroup().getLevel() !== requiredLevel) {
1372 groupPointer = groupPointer.getParent();
1373 }
1374
1375 if (groupPointer!==columnGroup) {
1376 return groupPointer;
1377 }
1378 }
1379 }
1380
1381 public isPinningLeft(): boolean {
1382 return this.displayedLeftColumns.length > 0;
1383 }
1384
1385 public isPinningRight(): boolean {
1386 return this.displayedRightColumns.length > 0;
1387 }
1388
1389 public getPrimaryAndSecondaryAndAutoColumns(): Column[] {
1390 let result = this.primaryColumns ? this.primaryColumns.slice(0) : [];
1391 if (_.exists(this.groupAutoColumns)) {
1392 this.groupAutoColumns.forEach( col => result.push(col) );
1393 }
1394 if (this.secondaryColumnsPresent) {
1395 this.secondaryColumns.forEach( column => result.push(column) );
1396 }
1397 return result;
1398 }
1399
1400 private createStateItemFromColumn(column: Column): ColumnState {
1401 let rowGroupIndex = column.isRowGroupActive() ? this.rowGroupColumns.indexOf(column) : null;
1402 let pivotIndex = column.isPivotActive() ? this.pivotColumns.indexOf(column) : null;
1403 let aggFunc = column.isValueActive() ? column.getAggFunc() : null;
1404 return {
1405 colId: column.getColId(),
1406 hide: !column.isVisible(),
1407 aggFunc: aggFunc,
1408 width: column.getActualWidth(),
1409 pivotIndex: pivotIndex,
1410 pinned: column.getPinned(),
1411 rowGroupIndex: rowGroupIndex
1412 };
1413 }
1414
1415 public getColumnState(): ColumnState[] {
1416 if (_.missing(this.primaryColumns)) {
1417 return [];
1418 }
1419
1420 let columnStateList: ColumnState[]
1421 = <ColumnState[]> this.primaryColumns.map(this.createStateItemFromColumn.bind(this));
1422
1423 if (!this.pivotMode) {
1424 this.orderColumnStateList(columnStateList);
1425 }
1426
1427 return columnStateList;
1428 }
1429
1430 private orderColumnStateList(columnStateList: any[]): void {
1431 let gridColumnIds = this.gridColumns.map( column => column.getColId() );
1432 columnStateList.sort( (itemA: any, itemB: any) => {
1433 let posA = gridColumnIds.indexOf(itemA.colId);
1434 let posB = gridColumnIds.indexOf(itemB.colId);
1435 return posA - posB;
1436 });
1437 }
1438
1439 public resetColumnState(source: ColumnEventType = "api"): void {
1440 // we can't use 'allColumns' as the order might of messed up, so get the primary ordered list
1441 let primaryColumns = this.getColumnsFromTree(this.primaryBalancedTree);
1442 let state: any[] = [];
1443
1444 if (primaryColumns) {
1445 primaryColumns.forEach( (column) => {
1446 state.push({
1447 colId: column.getColId(),
1448 aggFunc: column.getColDef().aggFunc,
1449 hide: column.getColDef().hide,
1450 pinned: column.getColDef().pinned,
1451 rowGroupIndex: column.getColDef().rowGroupIndex,
1452 pivotIndex: column.getColDef().pivotIndex,
1453 width: column.getColDef().width
1454 });
1455 });
1456 }
1457 this.setColumnState(state, source);
1458 }
1459
1460 public setColumnState(columnState: ColumnState[], source: ColumnEventType = "api"): boolean {
1461 if (_.missingOrEmpty(this.primaryColumns)) {
1462 return false;
1463 }
1464
1465 this.autoGroupsNeedBuilding = true;
1466
1467 // at the end below, this list will have all columns we got no state for
1468 let columnsWithNoState = this.primaryColumns.slice();
1469
1470 this.rowGroupColumns = [];
1471 this.valueColumns = [];
1472 this.pivotColumns = [];
1473
1474 let success = true;
1475
1476 let rowGroupIndexes: { [key: string]: number } = {};
1477 let pivotIndexes: { [key: string]: number } = {};
1478
1479 if (columnState) {
1480 columnState.forEach((stateItem: ColumnState) => {
1481 let column = this.getPrimaryColumn(stateItem.colId);
1482 if (!column) {
1483 console.warn('ag-grid: column ' + stateItem.colId + ' not found');
1484 success = false;
1485 } else {
1486 this.syncColumnWithStateItem(column, stateItem, rowGroupIndexes, pivotIndexes, source);
1487 _.removeFromArray(columnsWithNoState, column);
1488 }
1489 });
1490 }
1491
1492 // anything left over, we got no data for, so add in the column as non-value, non-rowGroup and hidden
1493 columnsWithNoState.forEach(this.syncColumnWithNoState.bind(this));
1494
1495 // sort the lists according to the indexes that were provided
1496 this.rowGroupColumns.sort(this.sortColumnListUsingIndexes.bind(this, rowGroupIndexes));
1497 this.pivotColumns.sort(this.sortColumnListUsingIndexes.bind(this, pivotIndexes));
1498
1499 this.updateGridColumns();
1500
1501 if (columnState) {
1502 let orderOfColIds = columnState.map(stateItem => stateItem.colId);
1503 this.gridColumns.sort((colA: Column, colB: Column) => {
1504 let indexA = orderOfColIds.indexOf(colA.getId());
1505 let indexB = orderOfColIds.indexOf(colB.getId());
1506 return indexA - indexB;
1507 });
1508 }
1509
1510 this.updateDisplayedColumns(source);
1511
1512 let event: ColumnEverythingChangedEvent = {
1513 type: Events.EVENT_COLUMN_EVERYTHING_CHANGED,
1514 api: this.gridApi,
1515 columnApi: this.columnApi,
1516 source: source
1517 };
1518 this.eventService.dispatchEvent(event);
1519
1520 return success;
1521 }
1522
1523 private sortColumnListUsingIndexes(indexes: {[key: string]: number}, colA: Column, colB: Column): number {
1524 let indexA = indexes[colA.getId()];
1525 let indexB = indexes[colB.getId()];
1526 return indexA - indexB;
1527 }
1528
1529 private syncColumnWithNoState(column: Column, source: ColumnEventType): void {
1530 column.setVisible(false, source);
1531 column.setAggFunc(null);
1532 column.setPinned(null);
1533 column.setRowGroupActive(false, source);
1534 column.setPivotActive(false, source);
1535 column.setValueActive(false, source);
1536 }
1537
1538 private syncColumnWithStateItem(column: Column, stateItem: ColumnState,
1539 rowGroupIndexes: {[key: string]: number},
1540 pivotIndexes: {[key: string]: number},
1541 source: ColumnEventType): void {
1542 // following ensures we are left with boolean true or false, eg converts (null, undefined, 0) all to true
1543 column.setVisible(!stateItem.hide, source);
1544 // sets pinned to 'left' or 'right'
1545 column.setPinned(stateItem.pinned);
1546 // if width provided and valid, use it, otherwise stick with the old width
1547 if (stateItem.width >= this.gridOptionsWrapper.getMinColWidth()) {
1548 column.setActualWidth(stateItem.width, source);
1549 }
1550
1551 if (typeof stateItem.aggFunc === 'string') {
1552 column.setAggFunc(stateItem.aggFunc);
1553 column.setValueActive(true, source);
1554 this.valueColumns.push(column);
1555 } else {
1556 if (_.exists(stateItem.aggFunc)) {
1557 console.warn('ag-Grid: stateItem.aggFunc must be a string. if using your own aggregation ' +
1558 'functions, register the functions first before using them in get/set state. This is because it is' +
1559 'intended for the column state to be stored and retrieved as simple JSON.');
1560 }
1561 column.setAggFunc(null);
1562 column.setValueActive(false, source);
1563 }
1564
1565 if (typeof stateItem.rowGroupIndex === 'number') {
1566 this.rowGroupColumns.push(column);
1567 column.setRowGroupActive(true, source);
1568 rowGroupIndexes[column.getId()] = stateItem.rowGroupIndex;
1569 } else {
1570 column.setRowGroupActive(false, source);
1571 }
1572
1573 if (typeof stateItem.pivotIndex === 'number') {
1574 this.pivotColumns.push(column);
1575 column.setPivotActive(true, source);
1576 pivotIndexes[column.getId()] = stateItem.pivotIndex;
1577 } else {
1578 column.setPivotActive(false, source);
1579 }
1580 }
1581
1582 public getGridColumns(keys: (string|Column)[]): Column[] {
1583 return this.getColumns(keys, this.getGridColumn.bind(this));
1584 }
1585
1586 private getColumns(keys: (string|Column)[], columnLookupCallback: (key: string|Column)=>Column ): Column[] {
1587 let foundColumns: Column[] = [];
1588 if (keys) {
1589 keys.forEach( (key: (string|Column)) => {
1590 let column = columnLookupCallback(key);
1591 if (column) {
1592 foundColumns.push(column);
1593 }
1594 });
1595 }
1596 return foundColumns;
1597 }
1598
1599 // used by growGroupPanel
1600 public getColumnWithValidation(key: string|Column): Column {
1601 let column = this.getPrimaryColumn(key);
1602 if (!column) {
1603 console.warn('ag-Grid: could not find column ' + column);
1604 }
1605 return column;
1606 }
1607
1608 public getPrimaryColumn(key: string|Column): Column {
1609 return this.getColumn(key, this.primaryColumns);
1610 }
1611
1612 public getGridColumn(key: string|Column): Column {
1613 return this.getColumn(key, this.gridColumns);
1614 }
1615
1616 private getColumn(key: string|Column, columnList: Column[]): Column {
1617 if (!key) {return null;}
1618
1619 for (let i = 0; i < columnList.length; i++) {
1620 if (this.columnsMatch(columnList[i], key)) {
1621 return columnList[i];
1622 }
1623 }
1624
1625 return this.getAutoColumn(key);
1626 }
1627
1628 private getAutoColumn(key: string|Column): Column {
1629 if (!_.exists(this.groupAutoColumns) || _.missing(this.groupAutoColumns)) { return null; }
1630 return _.find(this.groupAutoColumns, groupCol => {
1631 return this.columnsMatch(groupCol, key);
1632 });
1633 }
1634
1635 private columnsMatch(column: Column, key: string|Column): boolean {
1636 let columnMatches = column === key;
1637 let colDefMatches = column.getColDef() === key;
1638 let idMatches = column.getColId() == key;
1639 return columnMatches || colDefMatches || idMatches;
1640 }
1641
1642 public getDisplayNameForColumn(column: Column, location: string, includeAggFunc = false): string {
1643 let headerName = this.getHeaderName(column.getColDef(), column, null, null, location);
1644 if (includeAggFunc) {
1645 return this.wrapHeaderNameWithAggFunc(column, headerName);
1646 } else {
1647 return headerName;
1648 }
1649 }
1650
1651 public getDisplayNameForOriginalColumnGroup(columnGroup: ColumnGroup, originalColumnGroup: OriginalColumnGroup, location: string): string {
1652 let colGroupDef = originalColumnGroup.getColGroupDef();
1653 if (colGroupDef) {
1654 return this.getHeaderName(colGroupDef, null, columnGroup, originalColumnGroup, location);
1655 } else {
1656 return null;
1657 }
1658 }
1659
1660 public getDisplayNameForColumnGroup(columnGroup: ColumnGroup, location: string): string {
1661 return this.getDisplayNameForOriginalColumnGroup(columnGroup, columnGroup.getOriginalColumnGroup(), location);
1662 }
1663
1664 // location is where the column is going to appear, ie who is calling us
1665 private getHeaderName(colDef: AbstractColDef, column: Column, columnGroup: ColumnGroup,
1666 originalColumnGroup: OriginalColumnGroup, location: string): string {
1667 let headerValueGetter = colDef.headerValueGetter;
1668
1669 if (headerValueGetter) {
1670 let params = {
1671 colDef: colDef,
1672 column: column,
1673 columnGroup: columnGroup,
1674 originalColumnGroup: originalColumnGroup,
1675 location: location,
1676 api: this.gridOptionsWrapper.getApi(),
1677 context: this.gridOptionsWrapper.getContext()
1678 };
1679
1680 if (typeof headerValueGetter === 'function') {
1681 // valueGetter is a function, so just call it
1682 return headerValueGetter(params);
1683 } else if (typeof headerValueGetter === 'string') {
1684 // valueGetter is an expression, so execute the expression
1685 return this.expressionService.evaluate(headerValueGetter, params);
1686 } else {
1687 console.warn('ag-grid: headerValueGetter must be a function or a string');
1688 return '';
1689 }
1690 } else if (colDef.headerName != null) {
1691 return colDef.headerName;
1692 } else if ((<ColDef>colDef).field) {
1693 return _.camelCaseToHumanText((<ColDef>colDef).field);
1694 } else {
1695 return '';
1696 }
1697 }
1698
1699 /*
1700 private getHeaderGroupName(columnGroup: ColumnGroup): string {
1701 let colGroupDef = columnGroup.getOriginalColumnGroup().getColGroupDef();
1702 let headerValueGetter = colGroupDef.headerValueGetter;
1703
1704 if (headerValueGetter) {
1705 let params = {
1706 columnGroup: columnGroup,
1707 colDef: colGroupDef,
1708 api: this.gridOptionsWrapper.getApi(),
1709 context: this.gridOptionsWrapper.getContext()
1710 };
1711
1712 if (typeof headerValueGetter === 'function') {
1713 // valueGetter is a function, so just call it
1714 return headerValueGetter(params);
1715 } else if (typeof headerValueGetter === 'string') {
1716 // valueGetter is an expression, so execute the expression
1717 return this.expressionService.evaluate(headerValueGetter, params);
1718 } else {
1719 console.warn('ag-grid: headerValueGetter must be a function or a string');
1720 return '';
1721 }
1722 } else {
1723 return colGroupDef.headerName;
1724 }
1725 }
1726 */
1727
1728 private wrapHeaderNameWithAggFunc(column: Column, headerName: string): string {
1729 if (this.gridOptionsWrapper.isSuppressAggFuncInHeader()) {
1730 return headerName;
1731 }
1732
1733 // only columns with aggregation active can have aggregations
1734 let pivotValueColumn = column.getColDef().pivotValueColumn;
1735 let pivotActiveOnThisColumn = _.exists(pivotValueColumn);
1736 let aggFunc: string | IAggFunc = null;
1737 let aggFuncFound: boolean;
1738
1739 // otherwise we have a measure that is active, and we are doing aggregation on it
1740 if (pivotActiveOnThisColumn) {
1741 aggFunc = pivotValueColumn.getAggFunc();
1742 aggFuncFound = true;
1743 } else {
1744 let measureActive = column.isValueActive();
1745 let aggregationPresent = this.pivotMode || !this.isRowGroupEmpty();
1746
1747 if (measureActive && aggregationPresent) {
1748 aggFunc = column.getAggFunc();
1749 aggFuncFound = true;
1750 } else {
1751 aggFuncFound = false;
1752 }
1753 }
1754
1755 if (aggFuncFound) {
1756 let aggFuncString = (typeof aggFunc === 'string') ? <string> aggFunc : 'func';
1757 let localeTextFunc = this.gridOptionsWrapper.getLocaleTextFunc();
1758 let aggFuncStringTranslated = localeTextFunc (aggFuncString, aggFuncString);
1759 return `${aggFuncStringTranslated}(${headerName})`;
1760 } else {
1761 return headerName;
1762 }
1763 }
1764
1765 // returns the group with matching colId and instanceId. If instanceId is missing,
1766 // matches only on the colId.
1767 public getColumnGroup(colId: string|ColumnGroup, instanceId?: number): ColumnGroup {
1768
1769 if (!colId) {return null;}
1770
1771 if (colId instanceof ColumnGroup) {
1772 return colId;
1773 }
1774
1775 let allColumnGroups = this.getAllDisplayedColumnGroups();
1776 let checkInstanceId = typeof instanceId === 'number';
1777 let result: ColumnGroup = null;
1778
1779 this.columnUtils.depthFirstAllColumnTreeSearch(allColumnGroups, (child: ColumnGroupChild)=> {
1780 if (child instanceof ColumnGroup) {
1781 let columnGroup = <ColumnGroup> child;
1782 let matched: boolean;
1783 if (checkInstanceId) {
1784 matched = colId === columnGroup.getGroupId() && instanceId === columnGroup.getInstanceId();
1785 } else {
1786 matched = colId === columnGroup.getGroupId();
1787 }
1788 if (matched) {
1789 result = columnGroup;
1790 }
1791 }
1792 });
1793
1794 return result;
1795 }
1796
1797 public setColumnDefs(columnDefs: (ColDef|ColGroupDef)[], source: ColumnEventType = "api") {
1798 // always invalidate cache on changing columns, as the column id's for the new columns
1799 // could overlap with the old id's, so the cache would return old values for new columns.
1800 this.valueCache.expire();
1801
1802 // NOTE ==================
1803 // we should be destroying the existing columns and groups if they exist, for example, the original column
1804 // group adds a listener to the columns, it should be also removing the listeners
1805
1806 this.autoGroupsNeedBuilding = true;
1807
1808 let balancedTreeResult = this.balancedColumnTreeBuilder.createBalancedColumnGroups(columnDefs, true);
1809 this.primaryBalancedTree = balancedTreeResult.balancedTree;
1810 this.primaryHeaderRowCount = balancedTreeResult.treeDept + 1;
1811
1812 this.primaryColumns = this.getColumnsFromTree(this.primaryBalancedTree);
1813 this.autoRowHeightColumns = this.primaryColumns.filter( col => col.getColDef().autoHeight );
1814 this.extractRowGroupColumns(source);
1815 this.extractPivotColumns(source);
1816 this.createValueColumns(source);
1817
1818 this.updateGridColumns();
1819
1820 this.updateDisplayedColumns(source);
1821 this.checkDisplayedVirtualColumns();
1822
1823 this.ready = true;
1824 let eventEverythingChanged: ColumnEverythingChangedEvent = {
1825 type: Events.EVENT_COLUMN_EVERYTHING_CHANGED,
1826 api: this.gridApi,
1827 columnApi: this.columnApi,
1828 source: source
1829 };
1830 this.eventService.dispatchEvent(eventEverythingChanged);
1831
1832 let newColumnsLoadedEvent: NewColumnsLoadedEvent = {
1833 type: Events.EVENT_NEW_COLUMNS_LOADED,
1834 api: this.gridApi,
1835 columnApi: this.columnApi
1836 };
1837 this.eventService.dispatchEvent(newColumnsLoadedEvent);
1838 }
1839
1840 public isReady(): boolean {
1841 return this.ready;
1842 }
1843
1844 private extractRowGroupColumns(source: ColumnEventType): void {
1845 this.rowGroupColumns.forEach( column => column.setRowGroupActive(false, source) );
1846 this.rowGroupColumns = [];
1847 // pull out items with rowGroupIndex
1848 this.primaryColumns.forEach( column => {
1849 if (typeof column.getColDef().rowGroupIndex === 'number') {
1850 this.rowGroupColumns.push(column);
1851 column.setRowGroupActive(true, source);
1852 }
1853 });
1854 // then sort them
1855 this.rowGroupColumns.sort(function(colA: Column, colB: Column): number {
1856 return colA.getColDef().rowGroupIndex - colB.getColDef().rowGroupIndex;
1857 });
1858 // now just pull out items rowGroup, they will be added at the end
1859 // after the indexed ones, but in the order the columns appear
1860 this.primaryColumns.forEach( column => {
1861 if (column.getColDef().rowGroup) {
1862 // if user already specified rowGroupIndex then we skip it as this col already included
1863 if (this.rowGroupColumns.indexOf(column)>=0) { return; }
1864
1865 this.rowGroupColumns.push(column);
1866 column.setRowGroupActive(true, source);
1867 }
1868 });
1869 }
1870
1871 private extractPivotColumns(source: ColumnEventType): void {
1872 this.pivotColumns.forEach( column => column.setPivotActive(false, source) );
1873 this.pivotColumns = [];
1874 // pull out items with pivotIndex
1875 this.primaryColumns.forEach( (column: Column) => {
1876 if (typeof column.getColDef().pivotIndex === 'number') {
1877 this.pivotColumns.push(column);
1878 column.setPivotActive(true, source);
1879 }
1880 });
1881 // then sort them
1882 this.pivotColumns.sort(function(colA: Column, colB: Column): number {
1883 return colA.getColDef().pivotIndex - colB.getColDef().pivotIndex;
1884 });
1885 // now check the boolean equivalent
1886 this.primaryColumns.forEach( (column: Column) => {
1887 if (column.getColDef().pivot) {
1888 // if user already specified pivotIndex then we skip it as this col already included
1889 if (this.pivotColumns.indexOf(column)>=0) { return; }
1890
1891 this.pivotColumns.push(column);
1892 column.setPivotActive(true, source);
1893 }
1894 });
1895 }
1896
1897 public resetColumnGroupState(source: ColumnEventType = "api"): void {
1898 let stateItems: {groupId: string, open: boolean}[] = [];
1899
1900 this.columnUtils.depthFirstOriginalTreeSearch(this.primaryBalancedTree, child => {
1901 if (child instanceof OriginalColumnGroup) {
1902 let groupState = {
1903 groupId: child.getGroupId(),
1904 open: child.getColGroupDef().openByDefault
1905 };
1906 stateItems.push(groupState);
1907 }
1908 });
1909
1910 this.setColumnGroupState(stateItems, source);
1911 }
1912
1913 public getColumnGroupState(): {groupId: string, open: boolean}[] {
1914 let columnGroupState: {groupId: string, open: boolean}[] = [];
1915 this.columnUtils.depthFirstOriginalTreeSearch(this.gridBalancedTree, node => {
1916 if (node instanceof OriginalColumnGroup) {
1917 let originalColumnGroup = <OriginalColumnGroup> node;
1918 columnGroupState.push({
1919 groupId: originalColumnGroup.getGroupId(),
1920 open: originalColumnGroup.isExpanded()
1921 });
1922 }
1923 });
1924 return columnGroupState;
1925 }
1926
1927 public setColumnGroupState(stateItems: {groupId: string, open: boolean}[], source: ColumnEventType = "api"): void {
1928 this.columnAnimationService.start();
1929
1930 let impactedGroups: OriginalColumnGroup[] = [];
1931
1932 stateItems.forEach( stateItem => {
1933 let groupKey = stateItem.groupId;
1934 let newValue = stateItem.open;
1935
1936 let originalColumnGroup: OriginalColumnGroup = this.getOriginalColumnGroup(groupKey);
1937 if (!originalColumnGroup) { return; }
1938
1939 if (originalColumnGroup.isExpanded() === newValue) { return; }
1940
1941 this.logger.log('columnGroupOpened(' + originalColumnGroup.getGroupId() + ',' + newValue + ')');
1942 originalColumnGroup.setExpanded(newValue);
1943 impactedGroups.push(originalColumnGroup);
1944 });
1945
1946 this.updateGroupsAndDisplayedColumns(source);
1947
1948 impactedGroups.forEach( originalColumnGroup => {
1949 let event: ColumnGroupOpenedEvent = {
1950 type: Events.EVENT_COLUMN_GROUP_OPENED,
1951 columnGroup: originalColumnGroup,
1952 api: this.gridApi,
1953 columnApi: this.columnApi
1954 };
1955 this.eventService.dispatchEvent(event);
1956 });
1957
1958 this.columnAnimationService.finish();
1959 }
1960
1961 // called by headerRenderer - when a header is opened or closed
1962 public setColumnGroupOpened(key: OriginalColumnGroup|string, newValue: boolean, source: ColumnEventType = "api"): void {
1963 let keyAsString: string;
1964 if (key instanceof OriginalColumnGroup) {
1965 keyAsString = (<OriginalColumnGroup>key).getId();
1966 } else {
1967 keyAsString = <string>key;
1968 }
1969 this.setColumnGroupState([{groupId: keyAsString, open: newValue}], source);
1970 }
1971
1972 public getOriginalColumnGroup(key: OriginalColumnGroup|string): OriginalColumnGroup {
1973 if (key instanceof OriginalColumnGroup) {
1974 return <OriginalColumnGroup> key;
1975 }
1976
1977 if (typeof key !== 'string') {
1978 console.error('ag-Grid: group key must be a string');
1979 }
1980
1981 // otherwise, search for the column group by id
1982 let res: OriginalColumnGroup = null;
1983 this.columnUtils.depthFirstOriginalTreeSearch(this.gridBalancedTree, node => {
1984 if (node instanceof OriginalColumnGroup) {
1985 let originalColumnGroup = <OriginalColumnGroup> node;
1986 if (originalColumnGroup.getId() === key) {
1987 res = originalColumnGroup;
1988 }
1989 }
1990 });
1991
1992 return res;
1993 }
1994
1995 private calculateColumnsForDisplay(): Column[] {
1996
1997 let columnsForDisplay: Column[];
1998
1999 if (this.pivotMode && !this.secondaryColumnsPresent) {
2000 // pivot mode is on, but we are not pivoting, so we only
2001 // show columns we are aggregating on
2002 columnsForDisplay = _.filter(this.gridColumns, column => {
2003 let isAutoGroupCol = this.groupAutoColumns && this.groupAutoColumns.indexOf(column) >= 0;
2004 let isValueCol = this.valueColumns && this.valueColumns.indexOf(column) >= 0;
2005 return isAutoGroupCol || isValueCol;
2006 });
2007
2008 } else {
2009 // otherwise continue as normal. this can be working on the primary
2010 // or secondary columns, whatever the gridColumns are set to
2011 columnsForDisplay = _.filter(this.gridColumns, column => {
2012 // keep col if a) it's auto-group or b) it's visible
2013 let isAutoGroupCol = this.groupAutoColumns && this.groupAutoColumns.indexOf(column) >= 0;
2014 return isAutoGroupCol || column.isVisible();
2015 } );
2016 }
2017
2018 return columnsForDisplay;
2019 }
2020
2021 private checkColSpanActiveInCols(columns: Column[]): boolean {
2022 let result = false;
2023 columns.forEach( col => {
2024 if (_.exists(col.getColDef().colSpan)) {
2025 result = true;
2026 }
2027 });
2028 return result;
2029 }
2030
2031 private calculateColumnsForGroupDisplay(): void {
2032 this.groupDisplayColumns = [];
2033 let checkFunc = (col: Column) => {
2034 let colDef = col.getColDef();
2035 if (colDef && _.exists(colDef.showRowGroup)) {
2036 this.groupDisplayColumns.push(col);
2037 }
2038 };
2039
2040 this.gridColumns.forEach(checkFunc);
2041 if (this.groupAutoColumns) {
2042 this.groupAutoColumns.forEach(checkFunc);
2043 }
2044 }
2045
2046 public getGroupDisplayColumns(): Column[] {
2047 return this.groupDisplayColumns;
2048 }
2049
2050 private updateDisplayedColumns(source: ColumnEventType): void {
2051
2052 let columnsForDisplay = this.calculateColumnsForDisplay();
2053
2054 this.buildDisplayedTrees(columnsForDisplay);
2055
2056 this.calculateColumnsForGroupDisplay();
2057
2058 // this is also called when a group is opened or closed
2059 this.updateGroupsAndDisplayedColumns(source);
2060
2061 this.setFirstRightAndLastLeftPinned(source);
2062 }
2063
2064 public isSecondaryColumnsPresent(): boolean {
2065 return this.secondaryColumnsPresent;
2066 }
2067
2068 public setSecondaryColumns(colDefs: (ColDef|ColGroupDef)[], source: ColumnEventType = "api"): void {
2069 let newColsPresent = colDefs && colDefs.length>0;
2070
2071 // if not cols passed, and we had to cols anyway, then do nothing
2072 if (!newColsPresent && !this.secondaryColumnsPresent) { return; }
2073
2074 if (newColsPresent) {
2075 this.processSecondaryColumnDefinitions(colDefs);
2076 let balancedTreeResult = this.balancedColumnTreeBuilder.createBalancedColumnGroups(colDefs, false);
2077 this.secondaryBalancedTree = balancedTreeResult.balancedTree;
2078 this.secondaryHeaderRowCount = balancedTreeResult.treeDept + 1;
2079 this.secondaryColumns = this.getColumnsFromTree(this.secondaryBalancedTree);
2080 this.secondaryColumnsPresent = true;
2081 } else {
2082 this.secondaryBalancedTree = null;
2083 this.secondaryHeaderRowCount = -1;
2084 this.secondaryColumns = null;
2085 this.secondaryColumnsPresent = false;
2086 }
2087
2088 this.updateGridColumns();
2089 this.updateDisplayedColumns(source);
2090 }
2091
2092 private processSecondaryColumnDefinitions(colDefs: (ColDef|ColGroupDef)[]): (ColDef|ColGroupDef)[] {
2093
2094 let columnCallback = this.gridOptionsWrapper.getProcessSecondaryColDefFunc();
2095 let groupCallback = this.gridOptionsWrapper.getProcessSecondaryColGroupDefFunc();
2096
2097 if (!columnCallback && !groupCallback) { return; }
2098
2099 searchForColDefs(colDefs);
2100
2101 function searchForColDefs(colDefs2: (ColDef|ColGroupDef)[]): void {
2102 colDefs2.forEach( function(abstractColDef: AbstractColDef) {
2103 let isGroup = _.exists((<any>abstractColDef).children);
2104 if (isGroup) {
2105 let colGroupDef = <ColGroupDef> abstractColDef;
2106 if (groupCallback) {
2107 groupCallback(colGroupDef);
2108 }
2109 searchForColDefs(colGroupDef.children);
2110 } else {
2111 let colDef = <ColGroupDef> abstractColDef;
2112 if (columnCallback) {
2113 columnCallback(colDef);
2114 }
2115 }
2116 });
2117 }
2118 }
2119
2120 // called from: setColumnState, setColumnDefs, setSecondaryColumns
2121 private updateGridColumns(): void {
2122 if (this.gridColsArePrimary) {
2123 this.lastPrimaryOrder = this.gridColumns;
2124 }
2125
2126 if (this.secondaryColumns) {
2127 this.gridBalancedTree = this.secondaryBalancedTree.slice();
2128 this.gridHeaderRowCount = this.secondaryHeaderRowCount;
2129 this.gridColumns = this.secondaryColumns.slice();
2130 this.gridColsArePrimary = false;
2131 } else {
2132 this.gridBalancedTree = this.primaryBalancedTree.slice();
2133 this.gridHeaderRowCount = this.primaryHeaderRowCount;
2134 this.gridColumns = this.primaryColumns.slice();
2135 this.gridColsArePrimary = true;
2136
2137 // updateGridColumns gets called after user adds a row group. we want to maintain the order of the columns
2138 // when this happens (eg if user moved a column) rather than revert back to the original column order.
2139 // likewise if changing in/out of pivot mode, we want to maintain the order of the primary cols
2140 this.orderGridColsLikeLastPrimary();
2141 }
2142
2143 this.addAutoGroupToGridColumns();
2144
2145 this.putFixedColumnsFirst();
2146
2147 this.setupQuickFilterColumns();
2148
2149 this.clearDisplayedColumns();
2150
2151 this.colSpanActive = this.checkColSpanActiveInCols(this.gridColumns);
2152
2153 let event: GridColumnsChangedEvent = {
2154 type: Events.EVENT_GRID_COLUMNS_CHANGED,
2155 api: this.gridApi,
2156 columnApi: this.columnApi
2157 };
2158 this.eventService.dispatchEvent(event);
2159 }
2160
2161 private orderGridColsLikeLastPrimary(): void {
2162 if (_.missing(this.lastPrimaryOrder)) { return; }
2163
2164 // only do the sort if all columns are accounted for. columns will be not accounted for
2165 // if changing from secondary to primary columns
2166 let oneMissing = false;
2167 this.gridColumns.forEach( col => {
2168 if (this.lastPrimaryOrder.indexOf(col)<0) { oneMissing = true; }
2169 });
2170 if (oneMissing) { return; }
2171
2172 this.gridColumns.sort( (colA: Column, colB: Column): number => {
2173 let indexA = this.lastPrimaryOrder.indexOf(colA);
2174 let indexB = this.lastPrimaryOrder.indexOf(colB);
2175 return indexA - indexB;
2176 });
2177 }
2178
2179 public isPrimaryColumnGroupsPresent(): boolean {
2180 return this.primaryHeaderRowCount > 1;
2181 }
2182
2183 // if we are using autoGroupCols, then they should be included for quick filter. this covers the
2184 // following scenarios:
2185 // a) user provides 'field' into autoGroupCol of normal grid, so now because a valid col to filter leafs on
2186 // b) using tree data and user depends on autoGroupCol for first col, and we also want to filter on this
2187 // (tree data is a bit different, as parent rows can be filtered on, unlike row grouping)
2188 private setupQuickFilterColumns(): void {
2189 if (this.groupAutoColumns) {
2190 this.columnsForQuickFilter = this.primaryColumns.concat(this.groupAutoColumns);
2191 } else {
2192 this.columnsForQuickFilter = this.primaryColumns;
2193 }
2194 }
2195
2196 private putFixedColumnsFirst(): void {
2197 let locked = this.gridColumns.filter(c => c.isLockPosition());
2198 let unlocked = this.gridColumns.filter(c => !c.isLockPosition());
2199 this.gridColumns = locked.concat(unlocked);
2200 }
2201
2202 private addAutoGroupToGridColumns(): void {
2203 // add in auto-group here
2204 this.createGroupAutoColumnsIfNeeded();
2205
2206 if (_.missing(this.groupAutoColumns)) { return; }
2207
2208 this.gridColumns = this.groupAutoColumns.concat(this.gridColumns);
2209
2210 let autoColBalancedTree = this.balancedColumnTreeBuilder.createForAutoGroups(this.groupAutoColumns, this.gridBalancedTree);
2211
2212 this.gridBalancedTree = autoColBalancedTree.concat(this.gridBalancedTree);
2213 }
2214
2215 // gets called after we copy down grid columns, to make sure any part of the gui
2216 // that tries to draw, eg the header, it will get empty lists of columns rather
2217 // than stale columns. for example, the header will received gridColumnsChanged
2218 // event, so will try and draw, but it will draw successfully when it acts on the
2219 // virtualColumnsChanged event
2220 private clearDisplayedColumns(): void {
2221 this.displayedLeftColumnTree = [];
2222 this.displayedRightColumnTree = [];
2223 this.displayedCentreColumnTree = [];
2224
2225 this.displayedLeftHeaderRows = {};
2226 this.displayedRightHeaderRows = {};
2227 this.displayedCentreHeaderRows = {};
2228
2229 this.displayedLeftColumns = [];
2230 this.displayedRightColumns = [];
2231 this.displayedCenterColumns = [];
2232 this.allDisplayedColumns = [];
2233 this.allDisplayedVirtualColumns = [];
2234 }
2235
2236 private updateGroupsAndDisplayedColumns(source: ColumnEventType) {
2237 this.updateOpenClosedVisibilityInColumnGroups();
2238 this.updateDisplayedColumnsFromTrees(source);
2239 this.updateVirtualSets();
2240 this.updateBodyWidths();
2241 // this event is picked up by the gui, headerRenderer and rowRenderer, to recalculate what columns to display
2242
2243 let event: DisplayedColumnsChangedEvent = {
2244 type: Events.EVENT_DISPLAYED_COLUMNS_CHANGED,
2245 api: this.gridApi,
2246 columnApi: this.columnApi
2247 };
2248 this.eventService.dispatchEvent(event);
2249 }
2250
2251 private updateDisplayedColumnsFromTrees(source: ColumnEventType): void {
2252 this.addToDisplayedColumns(this.displayedLeftColumnTree, this.displayedLeftColumns);
2253 this.addToDisplayedColumns(this.displayedCentreColumnTree, this.displayedCenterColumns);
2254 this.addToDisplayedColumns(this.displayedRightColumnTree, this.displayedRightColumns);
2255 this.setupAllDisplayedColumns();
2256 this.setLeftValues(source);
2257 }
2258
2259 private setupAllDisplayedColumns(): void {
2260
2261 if (this.gridOptionsWrapper.isEnableRtl()) {
2262 this.allDisplayedColumns = this.displayedRightColumns
2263 .concat(this.displayedCenterColumns)
2264 .concat(this.displayedLeftColumns);
2265 } else {
2266 this.allDisplayedColumns = this.displayedLeftColumns
2267 .concat(this.displayedCenterColumns)
2268 .concat(this.displayedRightColumns);
2269 }
2270 }
2271
2272 // sets the left pixel position of each column
2273 private setLeftValues(source: ColumnEventType): void {
2274 this.setLeftValuesOfColumns(source);
2275 this.setLeftValuesOfGroups();
2276 }
2277
2278 private setLeftValuesOfColumns(source: ColumnEventType): void {
2279 // go through each list of displayed columns
2280 let allColumns = this.primaryColumns.slice(0);
2281
2282 // let totalColumnWidth = this.getWidthOfColsInList()
2283 let doingRtl = this.gridOptionsWrapper.isEnableRtl();
2284
2285 [this.displayedLeftColumns,this.displayedRightColumns,this.displayedCenterColumns].forEach( columns => {
2286 if (doingRtl) {
2287 // when doing RTL, we start at the top most pixel (ie RHS) and work backwards
2288 let left = this.getWidthOfColsInList(columns);
2289 columns.forEach( column => {
2290 left -= column.getActualWidth();
2291 column.setLeft(left, source);
2292 });
2293 } else {
2294 // otherwise normal LTR, we start at zero
2295 let left = 0;
2296 columns.forEach( column => {
2297 column.setLeft(left, source);
2298 left += column.getActualWidth();
2299 });
2300 }
2301 _.removeAllFromArray(allColumns, columns);
2302 });
2303
2304 // items left in allColumns are columns not displayed, so remove the left position. this is
2305 // important for the rows, as if a col is made visible, then taken out, then made visible again,
2306 // we don't want the animation of the cell floating in from the old position, whatever that was.
2307 allColumns.forEach( (column: Column) => {
2308 column.setLeft(null, source);
2309 });
2310 }
2311
2312 private setLeftValuesOfGroups(): void {
2313 // a groups left value is the lest left value of it's children
2314 [this.displayedLeftColumnTree,this.displayedRightColumnTree,this.displayedCentreColumnTree].forEach( columns => {
2315 columns.forEach( column => {
2316 if (column instanceof ColumnGroup) {
2317 let columnGroup = <ColumnGroup> column;
2318 columnGroup.checkLeft();
2319 }
2320 });
2321 });
2322 }
2323
2324 private addToDisplayedColumns(displayedColumnTree: ColumnGroupChild[], displayedColumns: Column[]): void {
2325 displayedColumns.length = 0;
2326 this.columnUtils.depthFirstDisplayedColumnTreeSearch(displayedColumnTree, (child: ColumnGroupChild)=> {
2327 if (child instanceof Column) {
2328 displayedColumns.push(child);
2329 }
2330 });
2331 }
2332
2333 private updateDisplayedCenterVirtualColumns(): {[key: string]: boolean} {
2334
2335 if (this.suppressColumnVirtualisation) {
2336 // no virtualisation, so don't filter
2337 this.allDisplayedCenterVirtualColumns = this.displayedCenterColumns;
2338 } else {
2339 // filter out what should be visible
2340 this.allDisplayedCenterVirtualColumns = this.filterOutColumnsWithinViewport();
2341 }
2342
2343 this.allDisplayedVirtualColumns = this.allDisplayedCenterVirtualColumns
2344 .concat(this.displayedLeftColumns)
2345 .concat(this.displayedRightColumns);
2346
2347 // return map of virtual col id's, for easy lookup when building the groups.
2348 // the map will be colId=>true, ie col id's mapping to 'true'.
2349 let result: any = {};
2350 this.allDisplayedVirtualColumns.forEach( (col: Column) => {
2351 result[col.getId()] = true;
2352 });
2353 return result;
2354 }
2355
2356 public getVirtualHeaderGroupRow(type: string, dept: number): ColumnGroupChild[] {
2357 let result: ColumnGroupChild[];
2358 switch (type) {
2359 case Column.PINNED_LEFT:
2360 result = this.displayedLeftHeaderRows[dept];
2361 break;
2362 case Column.PINNED_RIGHT:
2363 result = this.displayedRightHeaderRows[dept];
2364 break;
2365 default:
2366 result = this.displayedCentreHeaderRows[dept];
2367 break;
2368 }
2369 if (_.missing(result)) {
2370 result = [];
2371 }
2372 return result;
2373 }
2374
2375 private updateDisplayedVirtualGroups(virtualColIds: any): void {
2376
2377 // go through each group, see if any of it's cols are displayed, and if yes,
2378 // then this group is included
2379
2380 this.displayedLeftHeaderRows = {};
2381 this.displayedRightHeaderRows = {};
2382 this.displayedCentreHeaderRows = {};
2383
2384 testGroup(this.displayedLeftColumnTree, this.displayedLeftHeaderRows, 0);
2385 testGroup(this.displayedRightColumnTree, this.displayedRightHeaderRows, 0);
2386 testGroup(this.displayedCentreColumnTree, this.displayedCentreHeaderRows, 0);
2387
2388 function testGroup(children: ColumnGroupChild[], result: {[row: number]: ColumnGroupChild[]}, dept: number): boolean {
2389 let returnValue = false;
2390
2391 for (let i = 0; i<children.length; i++) {
2392 // see if this item is within viewport
2393 let child = children[i];
2394 let addThisItem: boolean;
2395 if (child instanceof Column) {
2396 // for column, test if column is included
2397 addThisItem = virtualColIds[child.getId()] === true;
2398 } else {
2399 // if group, base decision on children
2400 let columnGroup = <ColumnGroup> child;
2401 addThisItem = testGroup(columnGroup.getDisplayedChildren(), result, dept+1);
2402 }
2403
2404 if (addThisItem) {
2405 returnValue = true;
2406 if (!result[dept]) {
2407 result[dept] = [];
2408 }
2409 result[dept].push(child);
2410 }
2411 }
2412
2413 return returnValue;
2414 }
2415 }
2416
2417 private updateVirtualSets(): void {
2418 let virtualColIds = this.updateDisplayedCenterVirtualColumns();
2419 this.updateDisplayedVirtualGroups(virtualColIds);
2420 }
2421
2422 private filterOutColumnsWithinViewport(): Column[] {
2423 return _.filter(this.displayedCenterColumns, this.isColumnInViewport.bind(this));
2424 }
2425
2426 // called from api
2427 public sizeColumnsToFit(gridWidth: any, source: ColumnEventType = "api"): void {
2428 // avoid divide by zero
2429 let allDisplayedColumns = this.getAllDisplayedColumns();
2430
2431 if (gridWidth <= 0 || allDisplayedColumns.length === 0) {
2432 return;
2433 }
2434
2435 let colsToNotSpread = _.filter(allDisplayedColumns, (column: Column): boolean => {
2436 return column.getColDef().suppressSizeToFit === true;
2437 });
2438 let colsToSpread = _.filter(allDisplayedColumns, (column: Column): boolean => {
2439 return column.getColDef().suppressSizeToFit !== true;
2440 });
2441
2442 // make a copy of the cols that are going to be resized
2443 let colsToFireEventFor = colsToSpread.slice(0);
2444
2445 let finishedResizing = false;
2446 while (!finishedResizing) {
2447 finishedResizing = true;
2448 let availablePixels = gridWidth - this.getWidthOfColsInList(colsToNotSpread);
2449 if (availablePixels <= 0) {
2450 // no width, set everything to minimum
2451 colsToSpread.forEach( (column: Column) => {
2452 column.setMinimum(source);
2453 });
2454 } else {
2455 let scale = availablePixels / this.getWidthOfColsInList(colsToSpread);
2456 // we set the pixels for the last col based on what's left, as otherwise
2457 // we could be a pixel or two short or extra because of rounding errors.
2458 let pixelsForLastCol = availablePixels;
2459 // backwards through loop, as we are removing items as we go
2460 for (let i = colsToSpread.length - 1; i >= 0; i--) {
2461 let column = colsToSpread[i];
2462 let newWidth = Math.round(column.getActualWidth() * scale);
2463 if (newWidth < column.getMinWidth()) {
2464 column.setMinimum(source);
2465 moveToNotSpread(column);
2466 finishedResizing = false;
2467 } else if (column.isGreaterThanMax(newWidth)) {
2468 column.setActualWidth(column.getMaxWidth(), source);
2469 moveToNotSpread(column);
2470 finishedResizing = false;
2471 } else {
2472 let onLastCol = i === 0;
2473 if (onLastCol) {
2474 column.setActualWidth(pixelsForLastCol, source);
2475 } else {
2476 column.setActualWidth(newWidth, source);
2477 }
2478 }
2479 pixelsForLastCol -= newWidth;
2480 }
2481 }
2482 }
2483
2484 this.setLeftValues(source);
2485 this.updateBodyWidths();
2486
2487 colsToFireEventFor.forEach( (column: Column) => {
2488 let event: ColumnResizedEvent = {
2489 type: Events.EVENT_COLUMN_RESIZED,
2490 column: column,
2491 columns: [column],
2492 finished: true,
2493 api: this.gridApi,
2494 columnApi: this.columnApi,
2495 source: "sizeColumnsToFit"
2496 };
2497 this.eventService.dispatchEvent(event);
2498 });
2499
2500 function moveToNotSpread(column: Column) {
2501 _.removeFromArray(colsToSpread, column);
2502 colsToNotSpread.push(column);
2503 }
2504 }
2505
2506 private buildDisplayedTrees(visibleColumns: Column[]) {
2507 let leftVisibleColumns = _.filter(visibleColumns, (column)=> {
2508 return column.getPinned() === 'left';
2509 });
2510
2511 let rightVisibleColumns = _.filter(visibleColumns, (column)=> {
2512 return column.getPinned() === 'right';
2513 });
2514
2515 let centerVisibleColumns = _.filter(visibleColumns, (column)=> {
2516 return column.getPinned() !== 'left' && column.getPinned() !== 'right';
2517 });
2518
2519 let groupInstanceIdCreator = new GroupInstanceIdCreator();
2520
2521 this.displayedLeftColumnTree = this.displayedGroupCreator.createDisplayedGroups(
2522 leftVisibleColumns, this.gridBalancedTree, groupInstanceIdCreator, this.displayedLeftColumnTree);
2523 this.displayedRightColumnTree = this.displayedGroupCreator.createDisplayedGroups(
2524 rightVisibleColumns, this.gridBalancedTree, groupInstanceIdCreator, this.displayedRightColumnTree);
2525 this.displayedCentreColumnTree = this.displayedGroupCreator.createDisplayedGroups(
2526 centerVisibleColumns, this.gridBalancedTree, groupInstanceIdCreator, this.displayedCentreColumnTree);
2527 }
2528
2529 private updateOpenClosedVisibilityInColumnGroups(): void {
2530 let allColumnGroups = this.getAllDisplayedColumnGroups();
2531 this.columnUtils.depthFirstAllColumnTreeSearch(allColumnGroups, child => {
2532 if (child instanceof ColumnGroup) {
2533 let columnGroup = <ColumnGroup> child;
2534 columnGroup.calculateDisplayedColumns();
2535 }
2536 });
2537 }
2538
2539 public getGroupAutoColumns(): Column[] {
2540 return this.groupAutoColumns;
2541 }
2542
2543 private createGroupAutoColumnsIfNeeded(): void {
2544
2545 if (!this.autoGroupsNeedBuilding) { return; }
2546 this.autoGroupsNeedBuilding = false;
2547
2548 // see if we need to insert the default grouping column
2549 let needAutoColumns = (this.rowGroupColumns.length > 0 || this.usingTreeData)
2550 && !this.gridOptionsWrapper.isGroupSuppressAutoColumn()
2551 && !this.gridOptionsWrapper.isGroupUseEntireRow()
2552 && !this.gridOptionsWrapper.isGroupSuppressRow();
2553
2554 if (needAutoColumns) {
2555 this.groupAutoColumns = this.autoGroupColService.createAutoGroupColumns(this.rowGroupColumns);
2556 } else {
2557 this.groupAutoColumns = null;
2558 }
2559 }
2560
2561 private createValueColumns(source: ColumnEventType): void {
2562 this.valueColumns.forEach( column => column.setValueActive(false, source) );
2563 this.valueColumns = [];
2564
2565 // override with columns that have the aggFunc specified explicitly
2566 for (let i = 0; i < this.primaryColumns.length; i++) {
2567 let column = this.primaryColumns[i];
2568 if (column.getColDef().aggFunc) {
2569 column.setAggFunc(column.getColDef().aggFunc);
2570 this.valueColumns.push(column);
2571 column.setValueActive(true, source);
2572 }
2573 }
2574 }
2575
2576 private getWidthOfColsInList(columnList: Column[]) {
2577 let result = 0;
2578 for (let i = 0; i<columnList.length; i++) {
2579 result += columnList[i].getActualWidth();
2580 }
2581 return result;
2582 }
2583
2584 public getGridBalancedTree(): OriginalColumnGroupChild[] {
2585 return this.gridBalancedTree;
2586 }
2587}