• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 15020218610

14 May 2025 12:06PM UTC coverage: 91.608% (+0.02%) from 91.592%
15020218610

Pull #15806

github

web-flow
Merge 37b342959 into 7f1bedb2f
Pull Request #15806: Fixed Pivot grid export to excel with hierarchical row dimensions.

13007 of 15241 branches covered (85.34%)

34 of 35 new or added lines in 2 files covered. (97.14%)

1 existing line in 1 file now uncovered.

26350 of 28764 relevant lines covered (91.61%)

34021.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.58
/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts
1
import { EventEmitter } from '@angular/core';
2
import { cloneArray, cloneValue, IBaseEventArgs, resolveNestedPath, yieldingLoop } from '../../core/utils';
3
import { GridColumnDataType, DataUtil } from '../../data-operations/data-util';
4
import { ExportUtilities } from './export-utilities';
5
import { IgxExporterOptionsBase } from './exporter-options-base';
6
import { ITreeGridRecord } from '../../grids/tree-grid/tree-grid.interfaces';
7
import { TreeGridFilteringStrategy } from '../../grids/tree-grid/tree-grid.filtering.strategy';
8
import { IGroupingState } from '../../data-operations/groupby-state.interface';
9
import { getHierarchy, isHierarchyMatch } from '../../data-operations/operations';
10
import { IGroupByExpandState } from '../../data-operations/groupby-expand-state.interface';
11
import { IFilteringState } from '../../data-operations/filtering-state.interface';
12
import { DatePipe, FormatWidth, getLocaleCurrencyCode, getLocaleDateFormat, getLocaleDateTimeFormat } from '@angular/common';
13
import { IGroupByRecord } from '../../data-operations/groupby-record.interface';
14
import { ColumnType, GridType, IPathSegment } from '../../grids/common/grid.interface';
15
import { FilterUtil } from '../../data-operations/filtering-strategy';
16
import { IgxSummaryResult } from '../../grids/summaries/grid-summary';
17
import { GridSummaryCalculationMode } from '../../grids/common/enums';
18

19
export enum ExportRecordType {
2✔
20
    GroupedRecord = 'GroupedRecord',
2✔
21
    TreeGridRecord = 'TreeGridRecord',
2✔
22
    DataRecord = 'DataRecord',
2✔
23
    HierarchicalGridRecord = 'HierarchicalGridRecord',
2✔
24
    HeaderRecord = 'HeaderRecord',
2✔
25
    SummaryRecord = 'SummaryRecord',
2✔
26
    PivotGridRecord = 'PivotGridRecord'
2✔
27
}
28

29
export enum ExportHeaderType {
2✔
30
    RowHeader = 'RowHeader',
2✔
31
    ColumnHeader = 'ColumnHeader',
2✔
32
    MultiRowHeader = 'MultiRowHeader',
2✔
33
    MultiColumnHeader = 'MultiColumnHeader',
2✔
34
    PivotRowHeader = 'PivotRowHeader',
2✔
35
    PivotMergedHeader = 'PivotMergedHeader',
2✔
36
}
37

38
export interface IExportRecord {
39
    data: any;
40
    level: number;
41
    type: ExportRecordType;
42
    owner?: string | GridType;
43
    hidden?: boolean;
44
    summaryKey?: string;
45
    hierarchicalOwner?: string;
46
    references?: IColumnInfo[];
47
    /* Adding `rawData` and `dimesnionKeys` properties to support properly exporting pivot grid data to CSV. */
48
    rawData?: any;
49
    dimensionKeys?: string[];
50
}
51

52
export interface IColumnList {
53
    columns: IColumnInfo[];
54
    columnWidths: number[];
55
    indexOfLastPinnedColumn: number;
56
    maxLevel?: number;
57
    maxRowLevel?: number;
58
}
59

60
export interface IColumnInfo {
61
    header: string;
62
    field: string;
63
    skip: boolean;
64
    dataType?: GridColumnDataType;
65
    skipFormatter?: boolean;
66
    formatter?: any;
67
    headerType?: ExportHeaderType;
68
    startIndex?: number;
69
    columnSpan?: number;
70
    rowSpan?: number;
71
    level?: number;
72
    exportIndex?: number;
73
    pinnedIndex?: number;
74
    columnGroupParent?: ColumnType | string;
75
    columnGroup?: ColumnType | string;
76
    currencyCode?: string;
77
    displayFormat?: string;
78
    dateFormat?: string;
79
    digitsInfo?: string;
80
}
81
/**
82
 * rowExporting event arguments
83
 * this.exporterService.rowExporting.subscribe((args: IRowExportingEventArgs) => {
84
 * // set args properties here
85
 * })
86
 */
87
export interface IRowExportingEventArgs extends IBaseEventArgs {
88
    /**
89
     * Contains the exporting row data
90
     */
91
    rowData: any;
92

93
    /**
94
     * Contains the exporting row index
95
     */
96
    rowIndex: number;
97

98
    /**
99
     * Skip the exporting row when set to true
100
     */
101
    cancel: boolean;
102
}
103

104
/**
105
 * columnExporting event arguments
106
 * ```typescript
107
 * this.exporterService.columnExporting.subscribe((args: IColumnExportingEventArgs) => {
108
 * // set args properties here
109
 * });
110
 * ```
111
 */
112
export interface IColumnExportingEventArgs extends IBaseEventArgs {
113
    /**
114
     * Contains the exporting column header
115
     */
116
    header: string;
117

118
    /**
119
     * Contains the exporting column field name
120
     */
121
    field: string;
122

123
    /**
124
     * Contains the exporting column index
125
     */
126
    columnIndex: number;
127

128
    /**
129
     * Skip the exporting column when set to true
130
     */
131
    cancel: boolean;
132

133
    /**
134
     * Export the column's data without applying its formatter, when set to true
135
     */
136
    skipFormatter: boolean;
137

138
    /**
139
     * A reference to the grid owner.
140
     */
141
    grid?: GridType;
142
}
143

144
/**hidden
145
 * A helper class used to identify whether the user has set a specific columnIndex
146
 * during columnExporting, so we can honor it at the exported file.
147
*/
148
class IgxColumnExportingEventArgs implements IColumnExportingEventArgs {
149
    public header: string;
150
    public field: string;
151
    public cancel: boolean;
152
    public skipFormatter: boolean;
153
    public grid?: GridType;
154
    public owner?: any;
155
    public userSetIndex? = false;
1,346✔
156

157
    private _columnIndex?: number;
158

159
    public get columnIndex(): number {
160
        return this._columnIndex;
37✔
161
    }
162

163
    public set columnIndex(value: number) {
164
        this._columnIndex = value;
4✔
165
        this.userSetIndex = true;
4✔
166
    }
167

168
    constructor(original: IColumnExportingEventArgs) {
169
        this.header = original.header;
1,346✔
170
        this.field = original.field;
1,346✔
171
        this.cancel = original.cancel;
1,346✔
172
        this.skipFormatter = original.skipFormatter;
1,346✔
173
        this.grid = original.grid;
1,346✔
174
        this.owner = original.owner;
1,346✔
175
        this._columnIndex = original.columnIndex;
1,346✔
176
    }
177
}
178

179
export const DEFAULT_OWNER = 'default';
2✔
180
export const GRID_ROOT_SUMMARY = 'igxGridRootSummary';
2✔
181
export const GRID_PARENT = 'grid-parent';
2✔
182
export const GRID_LEVEL_COL = 'GRID_LEVEL_COL';
2✔
183
const DEFAULT_COLUMN_WIDTH = 8.43;
2✔
184
const GRID_CHILD = 'grid-child-';
2✔
185

186
export abstract class IgxBaseExporter {
187

188
    public exportEnded = new EventEmitter<IBaseEventArgs>();
176✔
189

190
    /**
191
     * This event is emitted when a row is exported.
192
     * ```typescript
193
     * this.exporterService.rowExporting.subscribe((args: IRowExportingEventArgs) => {
194
     * // put event handler code here
195
     * });
196
     * ```
197
     *
198
     * @memberof IgxBaseExporter
199
     */
200
    public rowExporting = new EventEmitter<IRowExportingEventArgs>();
176✔
201

202
    /**
203
     * This event is emitted when a column is exported.
204
     * ```typescript
205
     * this.exporterService.columnExporting.subscribe((args: IColumnExportingEventArgs) => {
206
     * // put event handler code here
207
     * });
208
     * ```
209
     *
210
     * @memberof IgxBaseExporter
211
     */
212
    public columnExporting = new EventEmitter<IColumnExportingEventArgs>();
176✔
213

214
    protected _sort = null;
176✔
215
    protected pivotGridFilterFieldsCount: number;
216
    protected _ownersMap: Map<any, IColumnList> = new Map<any, IColumnList>();
176✔
217

218
    private locale: string
219
    private _setChildSummaries = false
176✔
220
    private isPivotGridExport: boolean;
221
    private options: IgxExporterOptionsBase;
222
    private summaries: Map<string, Map<string, any[]>> = new Map<string, Map<string, IgxSummaryResult[]>>();
176✔
223
    private rowIslandCounter = -1;
176✔
224
    private flatRecords: IExportRecord[] = [];
176✔
225
    private pivotGridColumns: IColumnInfo[] = []
176✔
226
    private pivotGridRowDimensionsMap: Map<string, string>;
227
    private pivotGridKeyValueMap = new Map<string, string>();
176✔
228
    private ownerGrid: any;
229

230
    /* alternateName: exportGrid */
231
    /**
232
     * Method for exporting IgxGrid component's data.
233
     * ```typescript
234
     * this.exporterService.export(this.igxGridForExport, this.exportOptions);
235
     * ```
236
     *
237
     * @memberof IgxBaseExporter
238
     */
239
    public export(grid: any, options: IgxExporterOptionsBase): void {
240
        if (options === undefined || options === null) {
167!
241
            throw Error('No options provided!');
×
242
        }
243

244
        this.options = options;
167✔
245
        this.locale = grid.locale;
167✔
246
        this.ownerGrid = grid;
167✔
247
        let columns = grid.columns;
167✔
248

249
        if (this.options.ignoreMultiColumnHeaders) {
167✔
250
            columns = columns.filter(col => col.children === undefined);
16✔
251
        }
252

253
        const columnList = this.getColumns(columns);
167✔
254

255
        if (grid.type === 'hierarchical') {
167✔
256
            this._ownersMap.set(grid, columnList);
18✔
257

258
            const childLayoutList = grid.childLayoutList;
18✔
259

260
            for (const island of childLayoutList) {
18✔
261
                this.mapHierarchicalGridColumns(island, grid.data[0]);
28✔
262
            }
263
        } else if (grid.type === 'pivot') {
149✔
264
            this.pivotGridColumns = [];
6✔
265
            this.isPivotGridExport = true;
6✔
266
            this.pivotGridKeyValueMap = new Map<string, string>();
6✔
267
            this.pivotGridRowDimensionsMap = new Map<string, string>();
6✔
268

269
            grid.visibleRowDimensions.filter(r => r.enabled).forEach(rowDimension => {
15✔
270
                this.addToRowDimensionsMap(rowDimension, rowDimension.memberName);
15✔
271
            });
272

273
            this._ownersMap.set(DEFAULT_OWNER, columnList);
6✔
274
        } else {
275
            this._ownersMap.set(DEFAULT_OWNER, columnList);
143✔
276
        }
277

278
        this.summaries = this.prepareSummaries(grid);
167✔
279
        this._setChildSummaries =  this.summaries.size > 1 && grid.summaryCalculationMode !== GridSummaryCalculationMode.rootLevelOnly;
167✔
280

281
        this.addLevelColumns();
167✔
282
        this.prepareData(grid);
167✔
283
        this.addLevelData();
167✔
284
        this.addPivotGridColumns(grid);
167✔
285
        this.addPivotRowHeaders(grid);
167✔
286
        this.exportGridRecordsData(this.flatRecords, grid);
167✔
287
    }
288

289
    /**
290
     * Method for exporting any kind of array data.
291
     * ```typescript
292
     * this.exporterService.exportData(this.arrayForExport, this.exportOptions);
293
     * ```
294
     *
295
     * @memberof IgxBaseExporter
296
     */
297
    public exportData(data: any[], options: IgxExporterOptionsBase): void {
298
        if (options === undefined || options === null) {
43!
299
            throw Error('No options provided!');
×
300
        }
301

302
        this.options = options;
43✔
303

304
        const records = data.map(d => {
43✔
305
            const record: IExportRecord = {
173✔
306
                data: d,
307
                type: ExportRecordType.DataRecord,
308
                level: 0
309
            };
310

311
            return record;
173✔
312
        });
313

314
        this.exportGridRecordsData(records);
43✔
315
    }
316

317
    private addToRowDimensionsMap(rowDimension: any, rootParentName: string) {
318
        this.pivotGridRowDimensionsMap[rowDimension.memberName] = rootParentName;
23✔
319
        if (rowDimension.childLevel) {
23✔
320
            this.addToRowDimensionsMap(rowDimension.childLevel, rootParentName)
8✔
321
        }
322
    }
323

324
    private exportGridRecordsData(records: IExportRecord[], grid?: GridType) {
325
        if (this._ownersMap.size === 0) {
210✔
326
            const recordsData = records.filter(r => r.type !== ExportRecordType.SummaryRecord).map(r => r.data);
173✔
327
            const keys = ExportUtilities.getKeysFromData(recordsData);
43✔
328
            const columns = keys.map((k) =>
43✔
329
                ({ header: k, field: k, skip: false, headerType: ExportHeaderType.ColumnHeader, level: 0, columnSpan: 1 }));
55✔
330
            const columnWidths = new Array<number>(keys.length).fill(DEFAULT_COLUMN_WIDTH);
43✔
331

332
            const mapRecord: IColumnList = {
43✔
333
                columns,
334
                columnWidths,
335
                indexOfLastPinnedColumn: -1,
336
                maxLevel: 0
337
            };
338

339
            this._ownersMap.set(DEFAULT_OWNER, mapRecord);
43✔
340
        }
341

342
        let shouldReorderColumns = false;
210✔
343
        for (const [key, mapRecord] of this._ownersMap) {
210✔
344
            let skippedPinnedColumnsCount = 0;
259✔
345
            let columnsWithoutHeaderCount = 1;
259✔
346
            let indexOfLastPinnedColumn = mapRecord.indexOfLastPinnedColumn;
259✔
347

348
            mapRecord.columns.forEach((column, index) => {
259✔
349
                if (!column.skip) {
1,462✔
350
                    const columnExportArgs: IColumnExportingEventArgs = {
1,346✔
351
                        header: !ExportUtilities.isNullOrWhitespaces(column.header) ?
1,346✔
352
                            column.header :
353
                            'Column' + columnsWithoutHeaderCount++,
354
                        field: column.field,
355
                        columnIndex: index,
356
                        cancel: false,
357
                        skipFormatter: false,
358
                        grid: key === DEFAULT_OWNER ? grid : key
1,346✔
359
                    };
360

361
                    const newColumnExportArgs = new IgxColumnExportingEventArgs(columnExportArgs);
1,346✔
362
                    this.columnExporting.emit(newColumnExportArgs);
1,346✔
363

364
                    column.header = newColumnExportArgs.header;
1,346✔
365
                    column.skip = newColumnExportArgs.cancel;
1,346✔
366
                    column.skipFormatter = newColumnExportArgs.skipFormatter;
1,346✔
367

368
                    if (newColumnExportArgs.userSetIndex) {
1,346✔
369
                        column.exportIndex = newColumnExportArgs.columnIndex;
4✔
370
                        shouldReorderColumns = true;
4✔
371
                    }
372

373
                    if (column.skip) {
1,346✔
374
                        if (index <= indexOfLastPinnedColumn) {
23!
375
                            skippedPinnedColumnsCount++;
×
376
                        }
377

378
                        this.calculateColumnSpans(column, mapRecord, column.columnSpan);
23✔
379

380
                        const nonSkippedColumns = mapRecord.columns.filter(c => !c.skip);
180✔
381

382
                        if (nonSkippedColumns.length > 0) {
23✔
383
                            this._ownersMap.get(key).maxLevel = nonSkippedColumns.sort((a, b) => b.level - a.level)[0].level;
180✔
384
                        }
385
                    }
386

387
                    if (this._sort && this._sort.fieldName === column.field) {
1,346!
388
                        if (column.skip) {
×
389
                            this._sort = null;
×
390
                        } else {
391
                            this._sort.fieldName = column.header;
×
392
                        }
393
                    }
394
                }
395
            });
396

397
            indexOfLastPinnedColumn -= skippedPinnedColumnsCount;
259✔
398

399
            // Reorder columns only if a column has been assigned a specific columnIndex during columnExporting event
400
            if (shouldReorderColumns) {
259✔
401
                mapRecord.columns = this.reorderColumns(mapRecord.columns);
3✔
402
            }
403
        }
404

405
        const dataToExport = new Array<IExportRecord>();
210✔
406
        const actualData = records[0]?.data;
210✔
407
        const isSpecialData = ExportUtilities.isSpecialData(actualData);
210✔
408

409
        yieldingLoop(records.length, 100, (i) => {
210✔
410
            const row = records[i];
3,051✔
411
            this.exportRow(dataToExport, row, i, isSpecialData);
3,051✔
412
        }, () => {
413
            this.exportDataImplementation(dataToExport, this.options, () => {
210✔
414
                this.resetDefaults();
208✔
415
            });
416
        });
417
    }
418

419
    private calculateColumnSpans(column: IColumnInfo, mapRecord: IColumnList, span: number) {
420
        if (column.headerType === ExportHeaderType.MultiColumnHeader && column.skip) {
35✔
421
            const columnGroupChildren = mapRecord.columns.filter(c => c.columnGroupParent === column.columnGroup);
232✔
422

423
            columnGroupChildren.forEach(cgc => {
16✔
424
                if (cgc.headerType === ExportHeaderType.MultiColumnHeader) {
40✔
425
                    cgc.columnSpan = 0;
10✔
426
                    cgc.columnGroupParent = null;
10✔
427
                    cgc.skip = true;
10✔
428

429
                    this.calculateColumnSpans(cgc, mapRecord, cgc.columnSpan);
10✔
430
                } else {
431
                    cgc.skip = true;
30✔
432
                }
433
            });
434
        }
435

436
        const targetCol = mapRecord.columns.filter(c => column.columnGroupParent !== null && column.columnGroupParent !== undefined && c.columnGroup === column.columnGroupParent)[0];
354✔
437
        if (targetCol !== undefined) {
35✔
438
            targetCol.columnSpan -= span;
4✔
439

440
            if (targetCol.columnGroupParent !== null) {
4✔
441
                this.calculateColumnSpans(targetCol, mapRecord, span);
2✔
442
            }
443

444
            if (targetCol.columnSpan === 0) {
4!
445
                targetCol.skip = true;
×
446
            }
447
        }
448
    }
449

450
    private exportRow(data: IExportRecord[], record: IExportRecord, index: number, isSpecialData: boolean) {
451
        if (!isSpecialData) {
3,051✔
452
            const owner = record.owner === undefined ? DEFAULT_OWNER : record.owner;
3,000✔
453
            const ownerCols = this._ownersMap.get(owner).columns;
3,000✔
454
            const hasRowHeaders = ownerCols.some(c => c.headerType === ExportHeaderType.RowHeader);
20,071✔
455

456
            if (record.type !== ExportRecordType.HeaderRecord) {
3,000✔
457
                const columns = ownerCols
2,874✔
458
                    .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
42,369✔
459
                    .sort((a, b) => a.startIndex - b.startIndex)
7,642✔
460
                    .sort((a, b) => a.pinnedIndex - b.pinnedIndex);
7,330✔
461

462
                if (hasRowHeaders) {
2,874✔
463
                    record.rawData = record.data;
176✔
464
                }
465

466
                record.data = columns.reduce((a, e) => {
2,874✔
467
                    if (!e.skip) {
10,159✔
468
                        let rawValue = resolveNestedPath(record.data, e.field);
10,159✔
469

470
                        const shouldApplyFormatter = e.formatter && !e.skipFormatter && record.type !== ExportRecordType.GroupedRecord;
10,159✔
471
                        const isOfDateType = e.dataType === 'date' || e.dataType === 'dateTime' || e.dataType === 'time';
10,159✔
472

473
                        if (isOfDateType &&
10,159✔
474
                            record.type !== ExportRecordType.SummaryRecord &&
475
                            record.type !== ExportRecordType.GroupedRecord &&
476
                            !(rawValue instanceof Date) &&
477
                            !shouldApplyFormatter &&
478
                            rawValue !== undefined &&
479
                            rawValue !== null) {
480
                            rawValue = new Date(rawValue);
14✔
481
                        } else if (e.dataType === 'string' && rawValue instanceof Date) {
10,145!
482
                            rawValue = rawValue.toString();
×
483
                        }
484

485
                        let formattedValue = shouldApplyFormatter ? e.formatter(rawValue, record.data) : rawValue;
10,159✔
486

487
                        if (this.isPivotGridExport && !isNaN(parseFloat(formattedValue))) {
10,159✔
488
                            formattedValue = parseFloat(formattedValue);
364✔
489
                        }
490

491
                        a[e.field] = formattedValue;
10,159✔
492
                    }
493
                    return a;
10,159✔
494
                }, {});
495
            } else {
496
                record.data = record.data.filter((_, i) => !record.references[i].skip)
557✔
497
            }
498
        }
499

500
        const rowArgs = {
3,051✔
501
            rowData: record.data,
502
            rowIndex: index,
503
            cancel: false,
504
            owner: record.owner ?? this.ownerGrid
5,584✔
505
        };
506

507
        this.rowExporting.emit(rowArgs);
3,051✔
508

509
        if (!rowArgs.cancel) {
3,051✔
510
            data.push(record);
2,992✔
511
        }
512
    }
513

514
    private reorderColumns(columns: IColumnInfo[]): IColumnInfo[] {
515
        const filteredColumns = columns.filter(c => !c.skip);
9✔
516
        const length = filteredColumns.length;
3✔
517
        const specificIndicesColumns = filteredColumns.filter((col) => !isNaN(col.exportIndex))
9✔
518
                                                      .sort((a,b) => a.exportIndex - b.exportIndex);
1✔
519
        const indices = specificIndicesColumns.map(col => col.exportIndex);
4✔
520

521
        specificIndicesColumns.forEach(col => {
3✔
522
            filteredColumns.splice(filteredColumns.indexOf(col), 1);
4✔
523
        });
524

525
        const reorderedColumns = new Array(length);
3✔
526

527
        if (specificIndicesColumns.length > Math.max(...indices)) {
3✔
528
            return specificIndicesColumns.concat(filteredColumns);
1✔
529
        } else {
530
            indices.forEach((i, index) => {
2✔
531
                if (i < 0 || i >= length) {
3✔
532
                    filteredColumns.push(specificIndicesColumns[index]);
2✔
533
                } else {
534
                    let k = i;
1✔
535
                    while (k < length && reorderedColumns[k] !== undefined) {
1✔
536
                        ++k;
×
537
                    }
538
                    reorderedColumns[k] = specificIndicesColumns[index];
1✔
539
                }
540
            });
541

542
            for (let i = 0; i < length; i++) {
2✔
543
                if (reorderedColumns[i] === undefined) {
6✔
544
                    reorderedColumns[i] = filteredColumns.splice(0, 1)[0];
5✔
545
                }
546
            }
547

548
        }
549
        return reorderedColumns;
2✔
550
    }
551

552
    private prepareData(grid: GridType) {
553
        this.flatRecords = [];
167✔
554
        const hasFiltering = (grid.filteringExpressionsTree && grid.filteringExpressionsTree.filteringOperands.length > 0) ||
167✔
555
            (grid.advancedFilteringExpressionsTree && grid.advancedFilteringExpressionsTree.filteringOperands.length > 0);
556
        const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions;
167!
557
        const hasSorting = expressions && expressions.length > 0;
167✔
558
        let setSummaryOwner = false;
167✔
559

560
        switch (grid.type) {
167✔
561
            case 'pivot': {
562
                this.preparePivotGridData(grid);
6✔
563
                break;
6✔
564
            }
565
            case 'hierarchical': {
566
                this.prepareHierarchicalGridData(grid, hasFiltering, hasSorting);
18✔
567
                setSummaryOwner = true;
18✔
568
                break;
18✔
569
            }
570
            case 'tree': {
571
                this.prepareTreeGridData(grid, hasFiltering, hasSorting);
34✔
572
                break;
34✔
573
            }
574
            default: {
575
                this.prepareGridData(grid, hasFiltering, hasSorting);
109✔
576
                break;
109✔
577
            }
578
        }
579

580
        if (this.summaries.size > 0 && grid.summaryCalculationMode !== GridSummaryCalculationMode.childLevelsOnly) {
167✔
581
            setSummaryOwner ?
10✔
582
                this.setSummaries(GRID_ROOT_SUMMARY, 0, false, grid) :
583
                this.setSummaries(GRID_ROOT_SUMMARY);
584
        }
585
    }
586

587
    private preparePivotGridData(grid: GridType) {
588
        for (const record of grid.filteredSortedData) {
6✔
589
            const recordData = Object.fromEntries(record.aggregationValues);
176✔
590
            record.dimensionValues.forEach((value, key) => {
176✔
591
                const actualKey = this.pivotGridRowDimensionsMap[key];
502✔
592
                recordData[actualKey] = value;
502✔
593
            });
594

595
            const pivotGridRecord: IExportRecord = {
176✔
596
                data: recordData,
597
                level: record.level,
598
                type: ExportRecordType.PivotGridRecord
599
            };
600

601
            this.flatRecords.push(pivotGridRecord);
176✔
602
        }
603

604
        if (this.flatRecords.length) {
6✔
605
            this.flatRecords[0].dimensionKeys = Object.values(this.pivotGridRowDimensionsMap);
6✔
606
        }
607
    }
608

609
    private prepareHierarchicalGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
610

611
        const skipOperations =
612
            (!hasFiltering || !this.options.ignoreFiltering) &&
18✔
613
            (!hasSorting || !this.options.ignoreSorting);
614

615
        if (skipOperations) {
18✔
616
            const data = grid.filteredSortedData;
16✔
617
            this.addHierarchicalGridData(grid, data);
16✔
618
        } else {
619
            let data = grid.data;
2✔
620

621
            if (hasFiltering && !this.options.ignoreFiltering) {
2!
622
                const filteringState: IFilteringState = {
×
623
                    expressionsTree: grid.filteringExpressionsTree,
624
                    advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
625
                    strategy: grid.filterStrategy
626
                };
627

628
                data = FilterUtil.filter(data, filteringState, grid);
×
629
            }
630

631
            if (hasSorting && !this.options.ignoreSorting) {
2!
632
                this._sort = cloneValue(grid.sortingExpressions[0]);
×
633

634
                data = DataUtil.sort(data, grid.sortingExpressions, grid.sortStrategy, grid);
×
635
            }
636

637
            this.addHierarchicalGridData(grid, data);
2✔
638
        }
639
    }
640

641
    private addHierarchicalGridData(grid: GridType, records: any[]) {
642
        const childLayoutList = grid.childLayoutList;
18✔
643
        const columnFields = this._ownersMap.get(grid).columns.map(col => col.field);
126✔
644

645
        for (const entry of records) {
18✔
646
            const expansionStateVal = grid.expansionStates.has(entry) ? grid.expansionStates.get(entry) : false;
52✔
647

648
            const dataWithoutChildren = Object.keys(entry)
52✔
649
                .filter(k => columnFields.includes(k))
458✔
650
                .reduce((obj, key) => {
651
                    obj[key] = entry[key];
281✔
652
                    return obj;
281✔
653
                }, {});
654

655
            const hierarchicalGridRecord: IExportRecord = {
52✔
656
                data: dataWithoutChildren,
657
                level: 0,
658
                type: ExportRecordType.HierarchicalGridRecord,
659
                owner: grid,
660
                hierarchicalOwner: GRID_PARENT
661
            };
662

663
            this.flatRecords.push(hierarchicalGridRecord);
52✔
664

665
            for (const island of childLayoutList) {
52✔
666
                const path: IPathSegment = {
80✔
667
                    rowID: island.primaryKey ? entry[island.primaryKey] : entry,
80!
668
                    rowKey: island.primaryKey ? entry[island.primaryKey] : entry,
80!
669
                    rowIslandKey: island.key
670
                };
671

672
                const islandGrid = grid?.gridAPI.getChildGrid([path]);
80✔
673
                const keyRecordData = this.prepareIslandData(island, islandGrid, entry[island.key]) || [];
80!
674

675
                this.getAllChildColumnsAndData(island, keyRecordData, expansionStateVal, islandGrid);
80✔
676
            }
677
        }
678
    }
679

680
    private prepareSummaries(grid: any): Map<string, Map<string, IgxSummaryResult[]>> {
681
        let summaries = new Map<string, Map<string, IgxSummaryResult[]>>();
179✔
682

683
        if (this.options.exportSummaries && grid.summaryService.summaryCacheMap.size > 0) {
179✔
684
            const summaryCacheMap = grid.summaryService.summaryCacheMap;
15✔
685

686
            switch(grid.summaryCalculationMode) {
15✔
687
                case GridSummaryCalculationMode.childLevelsOnly:
688
                    summaryCacheMap.delete(GRID_ROOT_SUMMARY);
1✔
689
                    break;
1✔
690
                case GridSummaryCalculationMode.rootLevelOnly:
691
                    for (const k of summaryCacheMap.keys()) {
2✔
692
                        if (k !== GRID_ROOT_SUMMARY) {
2!
693
                            summaryCacheMap.delete(k);
×
694
                        }
695
                    }
696
                    break;
2✔
697
            }
698

699
            summaries = summaryCacheMap;
15✔
700
        }
701

702
        return summaries;
179✔
703
    }
704

705
    private prepareIslandData(island: any, islandGrid: GridType, data: any[]): any[] {
706
        if (islandGrid !== undefined) {
177✔
707
            const hasFiltering = (islandGrid.filteringExpressionsTree &&
12!
708
                islandGrid.filteringExpressionsTree.filteringOperands.length > 0) ||
709
                (islandGrid.advancedFilteringExpressionsTree &&
710
                    islandGrid.advancedFilteringExpressionsTree.filteringOperands.length > 0);
711

712
            const hasSorting = islandGrid.sortingExpressions &&
12✔
713
                islandGrid.sortingExpressions.length > 0;
714

715
            const skipOperations =
716
                (!hasFiltering || !this.options.ignoreFiltering) &&
12!
717
                (!hasSorting || !this.options.ignoreSorting);
718

719
            if (skipOperations) {
12!
720
                data = islandGrid.filteredSortedData;
12✔
721
            } else {
722
                if (hasFiltering && !this.options.ignoreFiltering) {
×
723
                    const filteringState: IFilteringState = {
×
724
                        expressionsTree: islandGrid.filteringExpressionsTree,
725
                        advancedExpressionsTree: islandGrid.advancedFilteringExpressionsTree,
726
                        strategy: islandGrid.filterStrategy
727
                    };
728

729
                    data = FilterUtil.filter(data, filteringState, islandGrid);
×
730
                }
731

732
                if (hasSorting && !this.options.ignoreSorting) {
×
733
                    this._sort = cloneValue(islandGrid.sortingExpressions[0]);
×
734

735
                    data = DataUtil.sort(data, islandGrid.sortingExpressions, islandGrid.sortStrategy, islandGrid);
×
736
                }
737
            }
738
        } else {
739
            const hasFiltering = (island.filteringExpressionsTree &&
165!
740
                island.filteringExpressionsTree.filteringOperands.length > 0) ||
741
                (island.advancedFilteringExpressionsTree &&
742
                    island.advancedFilteringExpressionsTree.filteringOperands.length > 0);
743

744
            const hasSorting = island.sortingExpressions &&
165✔
745
                island.sortingExpressions.length > 0;
746

747
            const skipOperations =
748
                (!hasFiltering || this.options.ignoreFiltering) &&
165!
749
                (!hasSorting || this.options.ignoreSorting);
750

751
            if (!skipOperations) {
165!
752
                if (hasFiltering && !this.options.ignoreFiltering) {
×
753
                    const filteringState: IFilteringState = {
×
754
                        expressionsTree: island.filteringExpressionsTree,
755
                        advancedExpressionsTree: island.advancedFilteringExpressionsTree,
756
                        strategy: island.filterStrategy
757
                    };
758

759
                    data = FilterUtil.filter(data, filteringState, island);
×
760
                }
761

762
                if (hasSorting && !this.options.ignoreSorting) {
×
763
                    this._sort = cloneValue(island.sortingExpressions[0]);
×
764

765
                    data = DataUtil.sort(data, island.sortingExpressions, island.sortStrategy, island);
×
766
                }
767
            }
768
        }
769

770
        return data;
177✔
771
    }
772

773
    private getAllChildColumnsAndData(island: any,
774
        childData: any[], expansionStateVal: boolean, grid: GridType) {
775
        const hierarchicalOwner = `${GRID_CHILD}${++this.rowIslandCounter}`;
177✔
776
        const columnList = this._ownersMap.get(island).columns;
177✔
777
        const columnHeaders = columnList.filter(col => col.headerType === ExportHeaderType.ColumnHeader);
844✔
778
        const columnHeader = columnHeaders.map(col => col.header ? col.header : col.field);
755!
779

780
        const headerRecord: IExportRecord = {
177✔
781
            data: columnHeader,
782
            level: island.level,
783
            type: ExportRecordType.HeaderRecord,
784
            owner: island,
785
            hidden: !expansionStateVal,
786
            references: columnHeaders,
787
            hierarchicalOwner
788
        };
789

790
        if (childData && childData.length > 0) {
177✔
791
            this.flatRecords.push(headerRecord);
126✔
792

793
            for (const rec of childData) {
126✔
794
                const exportRecord: IExportRecord = {
325✔
795
                    data: rec,
796
                    level: island.level,
797
                    type: ExportRecordType.HierarchicalGridRecord,
798
                    owner: island,
799
                    hidden: !expansionStateVal,
800
                    hierarchicalOwner
801
                };
802

803
                exportRecord.summaryKey = island.key;
325✔
804
                this.flatRecords.push(exportRecord);
325✔
805

806
                if (island.children.length > 0) {
325✔
807
                    const islandExpansionStateVal = grid === undefined ?
97✔
808
                        false :
809
                        grid.expansionStates.has(rec) ?
15✔
810
                            grid.expansionStates.get(rec) :
811
                            false;
812

813
                    for (const childIsland of island.children) {
97✔
814
                        const path: IPathSegment = {
97✔
815
                            rowID: childIsland.primaryKey ? rec[childIsland.primaryKey] : rec,
97!
816
                            rowKey: childIsland.primaryKey ? rec[childIsland.primaryKey] : rec,
97!
817
                            rowIslandKey: childIsland.key
818
                        };
819

820
                        // only defined when row is expanded in UI
821
                        const childIslandGrid = grid?.gridAPI.getChildGrid([path]);
97✔
822
                        const keyRecordData = this.prepareIslandData(island, childIslandGrid, rec[childIsland.key]) || [];
97✔
823

824
                        this.getAllChildColumnsAndData(childIsland, keyRecordData, islandExpansionStateVal, childIslandGrid);
97✔
825
                    }
826
                }
827
            }
828

829
            if (grid) {
126✔
830
                const summaries = this.prepareSummaries(grid);
12✔
831
                for (const k of summaries.keys()) {
12✔
832
                    const summary = summaries.get(k);
4✔
833
                    this.setSummaries(island.key, island.level, !expansionStateVal, island, summary, hierarchicalOwner)
4✔
834
                }
835
            }
836
        }
837
    }
838

839
    private prepareGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
840
        const groupedGridGroupingState: IGroupingState = {
109✔
841
            expressions: grid.groupingExpressions,
842
            expansion: grid.groupingExpansionState,
843
            defaultExpanded: grid.groupsExpanded,
844
        };
845

846
        const hasGrouping = grid.groupingExpressions &&
109✔
847
            grid.groupingExpressions.length > 0;
848

849
        const skipOperations =
850
            (!hasFiltering || !this.options.ignoreFiltering) &&
109✔
851
            (!hasSorting || !this.options.ignoreSorting) &&
852
            (!hasGrouping || !this.options.ignoreGrouping);
853

854
        if (skipOperations) {
109✔
855
            if (hasGrouping) {
104✔
856
                this.addGroupedData(grid, grid.groupsRecords, groupedGridGroupingState, true);
6✔
857
            } else {
858
                this.addFlatData(grid.filteredSortedData);
98✔
859
            }
860
        } else {
861
            let gridData = grid.data;
5✔
862

863
            if (hasFiltering && !this.options.ignoreFiltering) {
5!
864
                const filteringState: IFilteringState = {
×
865
                    expressionsTree: grid.filteringExpressionsTree,
866
                    advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
867
                    strategy: grid.filterStrategy
868
                };
869

870
                gridData = FilterUtil.filter(gridData, filteringState, grid);
×
871
            }
872

873
            if (hasSorting && !this.options.ignoreSorting) {
5✔
874
                // TODO: We should drop support for this since in a grouped grid it doesn't make sense
875
                // this._sort = !isGroupedGrid ?
876
                //     cloneValue(grid.sortingExpressions[0]) :
877
                //     grid.sortingExpressions.length > 1 ?
878
                //         cloneValue(grid.sortingExpressions[1]) :
879
                //         cloneValue(grid.sortingExpressions[0]);
880
                const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions;
2!
881
                gridData = DataUtil.sort(gridData, expressions, grid.sortStrategy, grid);
2✔
882
            }
883

884
            if (hasGrouping && !this.options.ignoreGrouping) {
5✔
885
                const groupsRecords = [];
2✔
886
                DataUtil.group(cloneArray(gridData), groupedGridGroupingState, grid.groupStrategy, grid, groupsRecords);
2✔
887
                gridData = groupsRecords;
2✔
888
            }
889

890
            if (hasGrouping && !this.options.ignoreGrouping) {
5✔
891
                this.addGroupedData(grid, gridData, groupedGridGroupingState, true);
2✔
892
            } else {
893
                this.addFlatData(gridData);
3✔
894
            }
895
        }
896
    }
897

898
    private prepareTreeGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
899
        const skipOperations =
900
            (!hasFiltering || !this.options.ignoreFiltering) &&
34✔
901
            (!hasSorting || !this.options.ignoreSorting);
902

903
        if (skipOperations) {
34✔
904
            this.addTreeGridData(grid.processedRootRecords);
28✔
905
        } else {
906
            let gridData = grid.rootRecords;
6✔
907

908
            if (hasFiltering && !this.options.ignoreFiltering) {
6!
909
                const filteringState: IFilteringState = {
×
910
                    expressionsTree: grid.filteringExpressionsTree,
911
                    advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
912
                    strategy: (grid.filterStrategy) ? grid.filterStrategy : new TreeGridFilteringStrategy()
×
913
                };
914

915
                gridData = filteringState.strategy
×
916
                    .filter(gridData, filteringState.expressionsTree, filteringState.advancedExpressionsTree);
917
            }
918

919
            if (hasSorting && !this.options.ignoreSorting) {
6!
920
                this._sort = cloneValue(grid.sortingExpressions[0]);
×
921

922
                gridData = DataUtil.treeGridSort(gridData, grid.sortingExpressions, grid.sortStrategy);
×
923
            }
924

925
            this.addTreeGridData(gridData);
6✔
926
        }
927
    }
928

929
    private addTreeGridData(records: ITreeGridRecord[], parentExpanded = true, hierarchicalOwner?: string) {
34✔
930
        if (!records) {
86!
931
            return;
×
932
        }
933

934
        for (const record of records) {
86✔
935
            const treeGridRecord: IExportRecord = {
144✔
936
                data: record.data,
937
                level: record.level,
938
                hidden: !parentExpanded,
939
                type: ExportRecordType.TreeGridRecord,
940
                summaryKey: record.key,
941
                hierarchicalOwner: record.level === 0 ? GRID_PARENT : hierarchicalOwner
144✔
942
            };
943

944
            this.flatRecords.push(treeGridRecord);
144✔
945

946
            if (record.children) {
144✔
947
                this.getTreeGridChildData(record.children, record.key, record.level, record.expanded && parentExpanded)
138✔
948
            }
949
        }
950
    }
951

952
    private getTreeGridChildData(recordChildren: ITreeGridRecord[], key: string, level:number, parentExpanded = true) {
×
953
        const hierarchicalOwner = `${GRID_CHILD}${++this.rowIslandCounter}`
138✔
954
        let summaryLevel = level;
138✔
955
        let summaryHidden = !parentExpanded;
138✔
956

957
        for (const rc of recordChildren) {
138✔
958
            if (rc.children && rc.children.length > 0) {
174✔
959
                this.addTreeGridData([rc], parentExpanded, hierarchicalOwner);
52✔
960
                summaryLevel = rc.level;
52✔
961
            } else {
962

963
                const currentRecord: IExportRecord = {
122✔
964
                    data: rc.data,
965
                    level: rc.level,
966
                    hidden: !parentExpanded,
967
                    type: ExportRecordType.DataRecord,
968
                    hierarchicalOwner
969
                };
970

971
                if (this._setChildSummaries) {
122✔
972
                    currentRecord.summaryKey = key;
11✔
973
                }
974

975
                this.flatRecords.push(currentRecord);
122✔
976
                summaryLevel = rc.level;
122✔
977
                summaryHidden = !parentExpanded
122✔
978
            }
979
        }
980

981
        if (this._setChildSummaries) {
138✔
982
            this.setSummaries(key, summaryLevel, summaryHidden, null, null, hierarchicalOwner);
7✔
983
        }
984
    }
985

986
    private addFlatData(records: any) {
987
        if (!records) {
101!
988
            return;
×
989
        }
990
        for (const record of records) {
101✔
991
            const data: IExportRecord = {
1,097✔
992
                data: record,
993
                type: ExportRecordType.DataRecord,
994
                level: 0
995
            };
996

997
            this.flatRecords.push(data);
1,097✔
998
        }
999
    }
1000

1001
    private setSummaries(summaryKey: string, level = 0, hidden = false, owner?: any, summary?: Map<string, IgxSummaryResult[]>, hierarchicalOwner?: string) {
18✔
1002
        const rootSummary = summary ?? this.summaries.get(summaryKey);
113✔
1003

1004
        if (rootSummary) {
113✔
1005
            const values = [...rootSummary.values()];
109✔
1006
            const biggest = values.sort((a, b) => b.length - a.length)[0];
513✔
1007

1008
            for (let i = 0; i < biggest.length; i++) {
109✔
1009
                const obj = {}
520✔
1010

1011
                for (const [key, value] of rootSummary) {
520✔
1012
                    const summaries = value.map(s => ({label: s.label, value: s.summaryResult}))
4,186✔
1013
                    obj[key] = summaries[i];
2,044✔
1014
                }
1015

1016
                const summaryRecord: IExportRecord = {
520✔
1017
                    data: obj,
1018
                    type: ExportRecordType.SummaryRecord,
1019
                    level,
1020
                    hidden,
1021
                    summaryKey,
1022
                    hierarchicalOwner
1023
                };
1024

1025
                if (owner) {
520✔
1026
                    summaryRecord.owner = owner;
15✔
1027
                }
1028

1029
                this.flatRecords.push(summaryRecord);
520✔
1030
            }
1031
        }
1032
    }
1033

1034
    private addGroupedData(grid: GridType, records: IGroupByRecord[], groupingState: IGroupingState, setGridParent: boolean, parentExpanded = true, summaryKeysArr: string[] = []) {
16✔
1035
        if (!records) {
80!
1036
            return;
×
1037
        }
1038

1039
        let previousKey = ''
80✔
1040
        const firstCol = this._ownersMap.get(DEFAULT_OWNER).columns
80✔
1041
            .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
401✔
1042
            .sort((a, b) => a.startIndex - b.startIndex)
86✔
1043
            .sort((a, b) => a.pinnedIndex - b.pinnedIndex)[0].field;
86✔
1044

1045
        for (const record of records) {
80✔
1046
            let recordVal = record.value;
176✔
1047
            const hierarchicalOwner = setGridParent ? GRID_PARENT : `${GRID_CHILD}${++this.rowIslandCounter}`;
176✔
1048
            const hierarchy = getHierarchy(record);
176✔
1049
            const expandState: IGroupByExpandState = groupingState.expansion.find((s) =>
176✔
1050
                isHierarchyMatch(s.hierarchy || [{ fieldName: record.expression.fieldName, value: recordVal }],
30!
1051
                hierarchy,
1052
                grid.groupingExpressions));
1053
            const expanded = expandState ? expandState.expanded : groupingState.defaultExpanded;
176✔
1054

1055
            const isDate = recordVal instanceof Date;
176✔
1056

1057
            if (isDate) {
176!
1058
                const timeZoneOffset = recordVal.getTimezoneOffset() * 60000;
×
1059
                const isoString = (new Date(recordVal - timeZoneOffset)).toISOString();
×
1060
                const pipe = new DatePipe(grid.locale);
×
1061
                recordVal = pipe.transform(isoString);
×
1062
            }
1063

1064
            const groupExpressionName = record.column && record.column.header ?
176!
1065
                record.column.header :
1066
                record.expression.fieldName;
1067

1068
            recordVal = recordVal !== null ? recordVal : '';
176!
1069

1070
            const groupExpression: IExportRecord = {
176✔
1071
                data: { [firstCol]: `${groupExpressionName}: ${recordVal ?? '(Blank)'} (${record.records.length})` },
179✔
1072
                level: record.level,
1073
                hidden: !parentExpanded,
1074
                type: ExportRecordType.GroupedRecord,
1075
                hierarchicalOwner
1076
            };
1077

1078
            this.flatRecords.push(groupExpression);
176✔
1079

1080
            let currKey = '';
176✔
1081
            let summaryKey = '';
176✔
1082

1083
            if (this._setChildSummaries) {
176✔
1084
                currKey = `'${record.expression.fieldName}': '${recordVal}'`;
92✔
1085
                summaryKeysArr = summaryKeysArr.filter(a => a !== previousKey);
136✔
1086
                previousKey = currKey;
92✔
1087
                summaryKeysArr.push(currKey);
92✔
1088
                summaryKey = `{ ${summaryKeysArr.join(', ')} }`;
92✔
1089
                groupExpression.summaryKey = summaryKey;
92✔
1090
            }
1091

1092
            if (record.groups.length > 0) {
176✔
1093
                this.addGroupedData(grid, record.groups, groupingState, false, expanded && parentExpanded, summaryKeysArr);
72✔
1094
            } else {
1095
                const rowRecords = record.records;
104✔
1096

1097
                for (const rowRecord of rowRecords) {
104✔
1098
                    const currentRecord: IExportRecord = {
140✔
1099
                        data: rowRecord,
1100
                        level: record.level + 1,
1101
                        hidden: !(expanded && parentExpanded),
278✔
1102
                        type: ExportRecordType.DataRecord,
1103
                        hierarchicalOwner
1104
                    };
1105

1106
                    if (summaryKey) {
140✔
1107
                        currentRecord.summaryKey = summaryKey;
54✔
1108
                    }
1109

1110
                    this.flatRecords.push(currentRecord);
140✔
1111
                }
1112
            }
1113

1114
            if (this._setChildSummaries) {
176✔
1115
                this.setSummaries(summaryKey, record.level + 1, !(expanded && parentExpanded), null, null, hierarchicalOwner);
92✔
1116
                summaryKeysArr.pop();
92✔
1117
            }
1118
        }
1119
    }
1120

1121
    private getColumns(columns: ColumnType[]): IColumnList {
1122
        const colList = [];
216✔
1123
        const colWidthList = [];
216✔
1124
        const hiddenColumns = [];
216✔
1125
        let indexOfLastPinnedColumn = -1;
216✔
1126
        let lastVisibleColumnIndex = -1;
216✔
1127
        let maxLevel = 0;
216✔
1128

1129
        columns.forEach((column) => {
216✔
1130
            const columnHeader = !ExportUtilities.isNullOrWhitespaces(column.header) ? column.header : column.field;
1,112✔
1131
            const exportColumn = !column.hidden || this.options.ignoreColumnsVisibility;
1,112✔
1132
            const index = this.options.ignoreColumnsOrder || this.options.ignoreColumnsVisibility ? column.index : column.visibleIndex;
1,112✔
1133
            const columnWidth = Number(column.width?.slice(0, -2)) || DEFAULT_COLUMN_WIDTH;
1,112✔
1134
            const columnLevel = !this.options.ignoreMultiColumnHeaders ? column.level : 0;
1,112✔
1135

1136
            const isMultiColHeader = column.columnGroup;
1,112✔
1137
            const colSpan = isMultiColHeader ?
1,112✔
1138
                column.allChildren
1139
                    .filter(ch => !(ch.columnGroup) && (!this.options.ignoreColumnsVisibility ? !ch.hidden : true))
498✔
1140
                    .length :
1141
                1;
1142

1143
            const columnInfo: IColumnInfo = {
1,112✔
1144
                header: ExportUtilities.sanitizeValue(columnHeader),
1145
                dataType: column.dataType,
1146
                field: column.field,
1147
                skip: !exportColumn,
1148
                formatter: column.formatter,
1149
                skipFormatter: false,
1150

1151
                headerType: isMultiColHeader ? ExportHeaderType.MultiColumnHeader : ExportHeaderType.ColumnHeader,
1,112✔
1152
                columnSpan: colSpan,
1153
                level: columnLevel,
1154
                startIndex: index,
1155
                pinnedIndex: !column.pinned ?
1,112✔
1156
                    Number.MAX_VALUE :
1157
                    !column.hidden ?
15✔
1158
                        column.grid.pinnedColumns.indexOf(column)
1159
                        : NaN,
1160
                columnGroupParent: column.parent ? column.parent : null,
1,112✔
1161
                columnGroup: isMultiColHeader ? column : null
1,112✔
1162
            };
1163

1164
            if (column.dataType === 'currency') {
1,112✔
1165
                columnInfo.currencyCode = column.pipeArgs.currencyCode
21!
1166
                    ? column.pipeArgs.currencyCode
1167
                    : getLocaleCurrencyCode(this.locale);
1168

1169
                columnInfo.displayFormat = column.pipeArgs.display
21!
1170
                    ? column.pipeArgs.display
1171
                    : 'symbol';
1172

1173
                columnInfo.digitsInfo = column.pipeArgs.digitsInfo
21!
1174
                    ? column.pipeArgs.digitsInfo
1175
                    : '1.0-2';
1176
            }
1177

1178
            if (column.dataType === 'date') {
1,112✔
1179
                columnInfo.dateFormat = getLocaleDateFormat(this.locale, FormatWidth.Medium);
25✔
1180
            }
1181

1182
            if (column.dataType === 'dateTime') {
1,112✔
1183
                columnInfo.dateFormat = getLocaleDateTimeFormat(this.locale, FormatWidth.Medium);
1✔
1184
            }
1185

1186
            if (this.options.ignoreColumnsOrder) {
1,112✔
1187
                if (columnInfo.startIndex !== columnInfo.pinnedIndex) {
15✔
1188
                    columnInfo.pinnedIndex = Number.MAX_VALUE;
15✔
1189
                }
1190
            }
1191

1192
            if (column.level > maxLevel && !this.options.ignoreMultiColumnHeaders) {
1,112✔
1193
                maxLevel = column.level;
48✔
1194
            }
1195

1196
            if (index !== -1) {
1,112✔
1197
                colList.push(columnInfo);
791✔
1198
                colWidthList.push(columnWidth);
791✔
1199
                lastVisibleColumnIndex = Math.max(lastVisibleColumnIndex, colList.indexOf(columnInfo));
791✔
1200
            } else {
1201
                hiddenColumns.push(columnInfo);
321✔
1202
            }
1203

1204
            if (column.pinned && exportColumn && columnInfo.headerType === ExportHeaderType.ColumnHeader) {
1,112✔
1205
                indexOfLastPinnedColumn++;
12✔
1206
            }
1207

1208
        });
1209

1210
        //Append the hidden columns to the end of the list
1211
        hiddenColumns.forEach((hiddenColumn) => {
216✔
1212
            colList[++lastVisibleColumnIndex] = hiddenColumn;
321✔
1213
        });
1214

1215
        const result: IColumnList = {
216✔
1216
            columns: colList,
1217
            columnWidths: colWidthList,
1218
            indexOfLastPinnedColumn,
1219
            maxLevel
1220
        };
1221

1222
        return result;
216✔
1223
    }
1224

1225
    private mapHierarchicalGridColumns(island: any, gridData: any) {
1226
        let columnList: IColumnList;
1227
        let keyData;
1228

1229
        if (island.autoGenerate) {
49!
1230
            keyData = gridData[island.key];
×
1231
            const islandKeys = island.children.map(i => i.key);
×
1232

1233
            const islandData = keyData.map(i => {
×
1234
                const newItem = {};
×
1235

1236
                Object.keys(i).map(k => {
×
1237
                    if (!islandKeys.includes(k)) {
×
1238
                        newItem[k] = i[k];
×
1239
                    }
1240
                });
1241

1242
                return newItem;
×
1243
            });
1244

1245
            columnList = this.getAutoGeneratedColumns(islandData);
×
1246
        } else {
1247
            const islandColumnList = island.columns;
49✔
1248
            columnList = this.getColumns(islandColumnList);
49✔
1249
        }
1250

1251
        this._ownersMap.set(island, columnList);
49✔
1252

1253
        if (island.children.length > 0) {
49✔
1254
            for (const childIsland of island.children) {
21✔
1255
                const islandKeyData = keyData !== undefined ? keyData[0] : {};
21!
1256
                this.mapHierarchicalGridColumns(childIsland, islandKeyData);
21✔
1257
            }
1258
        }
1259
    }
1260

1261
    private getAutoGeneratedColumns(data: any[]) {
1262
        const colList = [];
×
1263
        const colWidthList = [];
×
1264
        const keys = Object.keys(data[0]);
×
1265

1266
        keys.forEach((colKey, i) => {
×
1267
            const columnInfo: IColumnInfo = {
×
1268
                header: colKey,
1269
                field: colKey,
1270
                dataType: 'string',
1271
                skip: false,
1272
                headerType: ExportHeaderType.ColumnHeader,
1273
                columnSpan: 1,
1274
                level: 0,
1275
                startIndex: i,
1276
                pinnedIndex: Number.MAX_VALUE
1277
            };
1278

1279
            colList.push(columnInfo);
×
1280
            colWidthList.push(DEFAULT_COLUMN_WIDTH);
×
1281
        });
1282

1283
        const result: IColumnList = {
×
1284
            columns: colList,
1285
            columnWidths: colWidthList,
1286
            indexOfLastPinnedColumn: -1,
1287
            maxLevel: 0,
1288
        };
1289

1290
        return result;
×
1291
    }
1292

1293
    private addPivotRowHeaders(grid: any) {
1294
        if (grid?.pivotUI?.showRowHeaders) {
167✔
1295
            const headersList = this._ownersMap.get(DEFAULT_OWNER);
3✔
1296
            const enabledRows = grid.visibleRowDimensions.filter(r => r.enabled).map((r, index) => ({ name: r.displayName || r.memberName, level: index }));
9✔
1297
            let startIndex = 0;
3✔
1298
            enabledRows.forEach(x => {
3✔
1299
                headersList.columns.unshift({
9✔
1300
                    rowSpan: headersList.maxLevel + 1,
1301
                    field: x.name,
1302
                    header: x.name,
1303
                    startIndex: startIndex,
1304
                    skip: false,
1305
                    pinnedIndex: 0,
1306
                    level: x.level,
1307
                    dataType: 'string',
1308
                    headerType: ExportHeaderType.PivotRowHeader
1309
                });
1310
                startIndex += 1;
9✔
1311
            });
1312
            headersList.columnWidths.unshift(...Array(enabledRows.length).fill(200));
3✔
1313
        }
1314
    }
1315

1316
    private addPivotGridColumns(grid: any) {
1317
        if (grid.type !== 'pivot') {
167✔
1318
            return;
161✔
1319
        }
1320

1321
        const enabledRows = grid.visibleRowDimensions.map((r, i) => ({ name: r.memberName, level: i }));
15✔
1322

1323
        this.preparePivotGridColumns(enabledRows);
6✔
1324
        this.pivotGridFilterFieldsCount = enabledRows.length;
6✔
1325

1326
        const columnList = this._ownersMap.get(DEFAULT_OWNER);
6✔
1327
        columnList.columns.unshift(...this.pivotGridColumns);
6✔
1328
        columnList.columnWidths.unshift(...Array(this.pivotGridColumns.length).fill(200));
6✔
1329
        columnList.indexOfLastPinnedColumn = enabledRows.length - 1;
6✔
1330
        columnList.maxRowLevel = enabledRows.length;
6✔
1331
        this._ownersMap.set(DEFAULT_OWNER, columnList);
6✔
1332
    }
1333

1334
    private preparePivotGridColumns(keys: any, columnGroupParent?: string): any {
1335
        if (keys.length === 0) {
6!
1336
            return;
×
1337
        }
1338

1339
        const records = this.flatRecords.map(r => r.data);
176✔
1340
        const groupedRecords = this.groupByKeys(records, keys);
6✔
1341

1342
        this.createRowDimension(groupedRecords, keys, columnGroupParent);
6✔
1343
    }
1344

1345
    private groupByKeys(items: any[], keys: any[]): any {
1346
        const group = (data: any[], groupKeys: any[]): any => {
6✔
1347
          if (groupKeys.length === 0) return data;
279✔
1348
      
1349
          const newKeys = [...groupKeys];
103✔
1350
          const key = newKeys.shift().name;
103✔
1351
          const map = new Map<string, any>();
103✔
1352
      
1353
          for (const item of data) {
103✔
1354
            const keyValue = item[key];
502✔
1355
            if (!map.has(keyValue)) {
502✔
1356
              map.set(keyValue, []);
273✔
1357
            }
1358
            map.get(keyValue).push(item);
502✔
1359
          }
1360
      
1361
          for (const [keyValue, value] of map) {
103✔
1362
            map.set(keyValue, group(value, newKeys));
273✔
1363
          }
1364
      
1365
          return map;
103✔
1366
        };
1367
      
1368
        return group(items, keys);
6✔
1369
    }
1370

1371
    private calculateRowSpan(value: any): number {
1372
        if (value instanceof Map) {
666✔
1373
            return Array.from(value.values()).reduce(
164✔
1374
                (total, current) => total + this.calculateRowSpan(current),
393✔
1375
                0
1376
            )
1377
        } else if (Array.isArray(value)) {
502✔
1378
            return value.length;
502✔
1379
        }
1380

NEW
1381
        return 0;
×
1382
    }
1383

1384
    private createRowDimension(node: any, keys: any[], columnGroupParent?: string) {
1385
        if (!(node instanceof Map)) return;
279✔
1386

1387
        const key = keys[0];        
103✔
1388
        const newKeys = keys.filter(k => k.level > key.level);
135✔
1389
        let startIndex = 0;
103✔
1390
        for (const k of node.keys()) {
103✔
1391
            let groupKey = k;
273✔
1392
            const rowSpan = this.calculateRowSpan(node.get(k));
273✔
1393

1394
            const rowDimensionColumn: IColumnInfo = {
273✔
1395
                columnSpan: 1,
1396
                rowSpan,
1397
                field: groupKey,
1398
                header: groupKey,
1399
                startIndex,
1400
                skip: false,
1401
                pinnedIndex: 0,
1402
                level: key.level,
1403
                dataType: 'string',
1404
                headerType: rowSpan > 1 ? ExportHeaderType.MultiRowHeader : ExportHeaderType.RowHeader,
273✔
1405
            };
1406
            
1407
            if (!groupKey) {
273✔
1408
                // if (this.pivotGridColumns?.length)
1409
                //     this.pivotGridColumns[this.pivotGridColumns.length - 1].columnSpan += 1;
1410
                rowDimensionColumn.headerType = ExportHeaderType.PivotMergedHeader;
61✔
1411
                groupKey = columnGroupParent;
61✔
1412
            }
1413
            if (key.level > 0) {
273✔
1414
                rowDimensionColumn.columnGroupParent = columnGroupParent;
239✔
1415
            } else {
1416
                rowDimensionColumn.columnGroup = groupKey;
34✔
1417
            }
1418

1419
            this.pivotGridColumns.push(rowDimensionColumn);
273✔
1420
            startIndex += rowSpan;
273✔
1421
        }
1422

1423
        for (const k of node.keys()) {
103✔
1424
            this.createRowDimension(node.get(k), newKeys, columnGroupParent);            
273✔
1425
        }
1426
    }
1427

1428
    private addLevelColumns() {
1429
        if (this.options.exportSummaries && this.summaries.size > 0) {
167✔
1430
            this._ownersMap.forEach(om => {
11✔
1431
                const levelCol: IColumnInfo = {
13✔
1432
                    header: GRID_LEVEL_COL,
1433
                    dataType: 'number',
1434
                    field: GRID_LEVEL_COL,
1435
                    skip: false,
1436
                    skipFormatter: false,
1437
                    headerType: ExportHeaderType.ColumnHeader,
1438
                    columnSpan: 1,
1439
                    level: 0,
1440
                };
1441

1442
                om.columns.push(levelCol);
13✔
1443
                om.columnWidths.push(20);
13✔
1444
            })
1445
        }
1446
    }
1447

1448
    private addLevelData() {
1449
        if (this.options.exportSummaries && this.summaries.size > 0) {
167✔
1450
            for(const r of this.flatRecords){
11✔
1451
                if (r.type === ExportRecordType.DataRecord || r.type === ExportRecordType.TreeGridRecord || r.type === ExportRecordType.HierarchicalGridRecord) {
913✔
1452
                    r.data[GRID_LEVEL_COL] = r.level;
248✔
1453
                }
1454
            }
1455
        }
1456
    }
1457

1458
    private resetDefaults() {
1459
        this._sort = null;
208✔
1460
        this.flatRecords = [];
208✔
1461
        this.options = {} as IgxExporterOptionsBase;
208✔
1462
        this._ownersMap.clear();
208✔
1463
        this.rowIslandCounter = 0;
208✔
1464
    }
1465

1466
    protected abstract exportDataImplementation(data: any[], options: IgxExporterOptionsBase, done: () => void): void;
1467
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc