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

IgniteUI / igniteui-angular / 13548823408

26 Feb 2025 04:36PM CUT coverage: 91.555% (-0.04%) from 91.599%
13548823408

Pull #15223

github

web-flow
Merge 3ac8087aa into 786685675
Pull Request #15223: fix(pivot-grid): added createRow method for grid based events - 19.0

12997 of 15250 branches covered (85.23%)

0 of 18 new or added lines in 2 files covered. (0.0%)

135 existing lines in 23 files now uncovered.

26344 of 28774 relevant lines covered (91.55%)

34046.37 hits per line

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

89.03
/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
}
48

49
export interface IColumnList {
50
    columns: IColumnInfo[];
51
    columnWidths: number[];
52
    indexOfLastPinnedColumn: number;
53
    maxLevel?: number;
54
    maxRowLevel?: number;
55
}
56

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

90
    /**
91
     * Contains the exporting row index
92
     */
93
    rowIndex: number;
94

95
    /**
96
     * Skip the exporting row when set to true
97
     */
98
    cancel: boolean;
99
}
100

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

115
    /**
116
     * Contains the exporting column field name
117
     */
118
    field: string;
119

120
    /**
121
     * Contains the exporting column index
122
     */
123
    columnIndex: number;
124

125
    /**
126
     * Skip the exporting column when set to true
127
     */
128
    cancel: boolean;
129

130
    /**
131
     * Export the column's data without applying its formatter, when set to true
132
     */
133
    skipFormatter: boolean;
134

135
    /**
136
     * A reference to the grid owner.
137
     */
138
    grid?: GridType;
139
}
140

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

154
    private _columnIndex?: number;
155

156
    public get columnIndex(): number {
157
        return this._columnIndex;
37✔
158
    }
159

160
    public set columnIndex(value: number) {
161
        this._columnIndex = value;
4✔
162
        this.userSetIndex = true;
4✔
163
    }
164

165
    constructor(original: IColumnExportingEventArgs) {
166
        this.header = original.header;
1,141✔
167
        this.field = original.field;
1,141✔
168
        this.cancel = original.cancel;
1,141✔
169
        this.skipFormatter = original.skipFormatter;
1,141✔
170
        this.grid = original.grid;
1,141✔
171
        this.owner = original.owner;
1,141✔
172
        this._columnIndex = original.columnIndex;
1,141✔
173
    }
174
}
175

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

183
export abstract class IgxBaseExporter {
184

185
    public exportEnded = new EventEmitter<IBaseEventArgs>();
174✔
186

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

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

211
    protected _sort = null;
174✔
212
    protected pivotGridFilterFieldsCount: number;
213
    protected _ownersMap: Map<any, IColumnList> = new Map<any, IColumnList>();
174✔
214

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

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

241
        this.options = options;
165✔
242
        this.locale = grid.locale;
165✔
243
        this.ownerGrid = grid;
165✔
244
        let columns = grid.columns;
165✔
245

246
        if (this.options.ignoreMultiColumnHeaders) {
165✔
247
            columns = columns.filter(col => col.children === undefined);
16✔
248
        }
249

250
        const columnList = this.getColumns(columns);
165✔
251

252
        if (grid.type === 'hierarchical') {
165✔
253
            this._ownersMap.set(grid, columnList);
18✔
254

255
            const childLayoutList = grid.childLayoutList;
18✔
256

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

266
            grid.visibleRowDimensions.filter(r => r.enabled).forEach(rowDimension => {
11✔
267
                this.addToRowDimensionsMap(rowDimension, rowDimension.memberName);
11✔
268
            });
269

270
            this._ownersMap.set(DEFAULT_OWNER, columnList);
4✔
271
        } else {
272
            this._ownersMap.set(DEFAULT_OWNER, columnList);
143✔
273
        }
274

275
        this.summaries = this.prepareSummaries(grid);
165✔
276
        this._setChildSummaries =  this.summaries.size > 1 && grid.summaryCalculationMode !== GridSummaryCalculationMode.rootLevelOnly;
165✔
277

278
        this.addLevelColumns();
165✔
279
        this.prepareData(grid);
165✔
280
        this.addLevelData();
165✔
281
        this.addPivotGridColumns(grid);
165✔
282
        this.addPivotRowHeaders(grid);
165✔
283
        this.exportGridRecordsData(this.flatRecords, grid);
165✔
284
    }
285

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

299
        this.options = options;
43✔
300

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

308
            return record;
173✔
309
        });
310

311
        this.exportGridRecordsData(records);
43✔
312
    }
313

314
    private addToRowDimensionsMap(rowDimension: any, rootParentName: string) {
315
        this.pivotGridRowDimensionsMap[rowDimension.memberName] = rootParentName;
16✔
316
        if (rowDimension.childLevel) {
16✔
317
            this.addToRowDimensionsMap(rowDimension.childLevel, rootParentName)
5✔
318
        }
319
    }
320

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

329
            const mapRecord: IColumnList = {
43✔
330
                columns,
331
                columnWidths,
332
                indexOfLastPinnedColumn: -1,
333
                maxLevel: 0
334
            };
335

336
            this._ownersMap.set(DEFAULT_OWNER, mapRecord);
43✔
337
        }
338

339
        let shouldReorderColumns = false;
208✔
340
        for (const [key, mapRecord] of this._ownersMap) {
208✔
341
            let skippedPinnedColumnsCount = 0;
257✔
342
            let columnsWithoutHeaderCount = 1;
257✔
343
            let indexOfLastPinnedColumn = mapRecord.indexOfLastPinnedColumn;
257✔
344

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

358
                    const newColumnExportArgs = new IgxColumnExportingEventArgs(columnExportArgs);
1,141✔
359
                    this.columnExporting.emit(newColumnExportArgs);
1,141✔
360

361
                    column.header = newColumnExportArgs.header;
1,141✔
362
                    column.skip = newColumnExportArgs.cancel;
1,141✔
363
                    column.skipFormatter = newColumnExportArgs.skipFormatter;
1,141✔
364

365
                    if (newColumnExportArgs.userSetIndex) {
1,141✔
366
                        column.exportIndex = newColumnExportArgs.columnIndex;
4✔
367
                        shouldReorderColumns = true;
4✔
368
                    }
369

370
                    if (column.skip) {
1,141✔
371
                        if (index <= indexOfLastPinnedColumn) {
23!
UNCOV
372
                            skippedPinnedColumnsCount++;
×
373
                        }
374

375
                        this.calculateColumnSpans(column, mapRecord, column.columnSpan);
23✔
376

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

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

384
                    if (this._sort && this._sort.fieldName === column.field) {
1,141!
385
                        if (column.skip) {
×
UNCOV
386
                            this._sort = null;
×
387
                        } else {
UNCOV
388
                            this._sort.fieldName = column.header;
×
389
                        }
390
                    }
391
                }
392
            });
393

394
            indexOfLastPinnedColumn -= skippedPinnedColumnsCount;
257✔
395

396
            // Reorder columns only if a column has been assigned a specific columnIndex during columnExporting event
397
            if (shouldReorderColumns) {
257✔
398
                mapRecord.columns = this.reorderColumns(mapRecord.columns);
3✔
399
            }
400
        }
401

402
        const dataToExport = new Array<IExportRecord>();
208✔
403
        const actualData = records[0]?.data;
208✔
404
        const isSpecialData = ExportUtilities.isSpecialData(actualData);
208✔
405

406
        yieldingLoop(records.length, 100, (i) => {
208✔
407
            const row = records[i];
2,914✔
408
            this.exportRow(dataToExport, row, i, isSpecialData);
2,914✔
409
        }, () => {
410
            this.exportDataImplementation(dataToExport, this.options, () => {
208✔
411
                this.resetDefaults();
206✔
412
            });
413
        });
414
    }
415

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

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

426
                    this.calculateColumnSpans(cgc, mapRecord, cgc.columnSpan);
10✔
427
                } else {
428
                    cgc.skip = true;
30✔
429
                }
430
            });
431
        }
432

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

437
            if (targetCol.columnGroupParent !== null) {
4✔
438
                this.calculateColumnSpans(targetCol, mapRecord, span);
2✔
439
            }
440

441
            if (targetCol.columnSpan === 0) {
4!
UNCOV
442
                targetCol.skip = true;
×
443
            }
444
        }
445
    }
446

447
    private exportRow(data: IExportRecord[], record: IExportRecord, index: number, isSpecialData: boolean) {
448
        if (!isSpecialData) {
2,914✔
449
            const owner = record.owner === undefined ? DEFAULT_OWNER : record.owner;
2,863✔
450
            const ownerCols = this._ownersMap.get(owner).columns;
2,863✔
451

452
            if (record.type !== ExportRecordType.HeaderRecord) {
2,863✔
453
                const columns = ownerCols
2,737✔
454
                    .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
16,007✔
455
                    .sort((a, b) => a.startIndex - b.startIndex)
7,501✔
456
                    .sort((a, b) => a.pinnedIndex - b.pinnedIndex);
7,189✔
457

458
                record.data = columns.reduce((a, e) => {
2,737✔
459
                    if (!e.skip) {
9,881✔
460
                        let rawValue = resolveNestedPath(record.data, e.field);
9,881✔
461

462
                        const shouldApplyFormatter = e.formatter && !e.skipFormatter && record.type !== ExportRecordType.GroupedRecord;
9,881✔
463
                        const isOfDateType = e.dataType === 'date' || e.dataType === 'dateTime' || e.dataType === 'time';
9,881✔
464

465
                        if (isOfDateType &&
9,881✔
466
                            record.type !== ExportRecordType.SummaryRecord &&
467
                            record.type !== ExportRecordType.GroupedRecord &&
468
                            !(rawValue instanceof Date) &&
469
                            !shouldApplyFormatter &&
470
                            rawValue !== undefined &&
471
                            rawValue !== null) {
472
                            rawValue = new Date(rawValue);
14✔
473
                        } else if (e.dataType === 'string' && rawValue instanceof Date) {
9,867!
UNCOV
474
                            rawValue = rawValue.toString();
×
475
                        }
476

477
                        let formattedValue = shouldApplyFormatter ? e.formatter(rawValue, record.data) : rawValue;
9,881✔
478

479
                        if (this.isPivotGridExport && !isNaN(parseFloat(formattedValue))) {
9,881✔
480
                            formattedValue = parseFloat(formattedValue);
92✔
481
                        }
482

483
                        a[e.field] = formattedValue;
9,881✔
484
                    }
485
                    return a;
9,881✔
486
                }, {});
487
            } else {
488
                record.data = record.data.filter((_, i) => !record.references[i].skip)
557✔
489
            }
490
        }
491

492
        const rowArgs = {
2,914✔
493
            rowData: record.data,
494
            rowIndex: index,
495
            cancel: false,
496
            owner: record.owner ?? this.ownerGrid
5,310✔
497
        };
498

499
        this.rowExporting.emit(rowArgs);
2,914✔
500

501
        if (!rowArgs.cancel) {
2,914✔
502
            data.push(record);
2,855✔
503
        }
504
    }
505

506
    private reorderColumns(columns: IColumnInfo[]): IColumnInfo[] {
507
        const filteredColumns = columns.filter(c => !c.skip);
9✔
508
        const length = filteredColumns.length;
3✔
509
        const specificIndicesColumns = filteredColumns.filter((col) => !isNaN(col.exportIndex))
9✔
510
                                                      .sort((a,b) => a.exportIndex - b.exportIndex);
1✔
511
        const indices = specificIndicesColumns.map(col => col.exportIndex);
4✔
512

513
        specificIndicesColumns.forEach(col => {
3✔
514
            filteredColumns.splice(filteredColumns.indexOf(col), 1);
4✔
515
        });
516

517
        const reorderedColumns = new Array(length);
3✔
518

519
        if (specificIndicesColumns.length > Math.max(...indices)) {
3✔
520
            return specificIndicesColumns.concat(filteredColumns);
1✔
521
        } else {
522
            indices.forEach((i, index) => {
2✔
523
                if (i < 0 || i >= length) {
3✔
524
                    filteredColumns.push(specificIndicesColumns[index]);
2✔
525
                } else {
526
                    let k = i;
1✔
527
                    while (k < length && reorderedColumns[k] !== undefined) {
1✔
528
                        ++k;
×
529
                    }
530
                    reorderedColumns[k] = specificIndicesColumns[index];
1✔
531
                }
532
            });
533

534
            for (let i = 0; i < length; i++) {
2✔
535
                if (reorderedColumns[i] === undefined) {
6✔
536
                    reorderedColumns[i] = filteredColumns.splice(0, 1)[0];
5✔
537
                }
538
            }
539

540
        }
541
        return reorderedColumns;
2✔
542
    }
543

544
    private prepareData(grid: GridType) {
545
        this.flatRecords = [];
165✔
546
        const hasFiltering = (grid.filteringExpressionsTree && grid.filteringExpressionsTree.filteringOperands.length > 0) ||
165✔
547
            (grid.advancedFilteringExpressionsTree && grid.advancedFilteringExpressionsTree.filteringOperands.length > 0);
548
        const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions;
165!
549
        const hasSorting = expressions && expressions.length > 0;
165✔
550
        let setSummaryOwner = false;
165✔
551

552
        switch (grid.type) {
165✔
553
            case 'pivot': {
554
                this.preparePivotGridData(grid);
4✔
555
                break;
4✔
556
            }
557
            case 'hierarchical': {
558
                this.prepareHierarchicalGridData(grid, hasFiltering, hasSorting);
18✔
559
                setSummaryOwner = true;
18✔
560
                break;
18✔
561
            }
562
            case 'tree': {
563
                this.prepareTreeGridData(grid, hasFiltering, hasSorting);
34✔
564
                break;
34✔
565
            }
566
            default: {
567
                this.prepareGridData(grid, hasFiltering, hasSorting);
109✔
568
                break;
109✔
569
            }
570
        }
571

572
        if (this.summaries.size > 0 && grid.summaryCalculationMode !== GridSummaryCalculationMode.childLevelsOnly) {
165✔
573
            setSummaryOwner ?
10✔
574
                this.setSummaries(GRID_ROOT_SUMMARY, 0, false, grid) :
575
                this.setSummaries(GRID_ROOT_SUMMARY);
576
        }
577
    }
578

579
    private preparePivotGridData(grid: GridType) {
580
        for (const record of grid.filteredSortedData) {
4✔
581
            const recordData = Object.fromEntries(record.aggregationValues);
39✔
582
            record.dimensionValues.forEach((value, key) => {
39✔
583
                const actualKey = this.pivotGridRowDimensionsMap[key];
99✔
584
                recordData[actualKey] = value;
99✔
585
            });
586

587
            const pivotGridRecord: IExportRecord = {
39✔
588
                data: recordData,
589
                level: record.level,
590
                type: ExportRecordType.PivotGridRecord
591
            };
592

593
            this.flatRecords.push(pivotGridRecord);
39✔
594
        }
595
    }
596

597
    private prepareHierarchicalGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
598

599
        const skipOperations =
600
            (!hasFiltering || !this.options.ignoreFiltering) &&
18✔
601
            (!hasSorting || !this.options.ignoreSorting);
602

603
        if (skipOperations) {
18✔
604
            const data = grid.filteredSortedData;
16✔
605
            this.addHierarchicalGridData(grid, data);
16✔
606
        } else {
607
            let data = grid.data;
2✔
608

609
            if (hasFiltering && !this.options.ignoreFiltering) {
2!
610
                const filteringState: IFilteringState = {
×
611
                    expressionsTree: grid.filteringExpressionsTree,
612
                    advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
613
                    strategy: grid.filterStrategy
614
                };
615

616
                data = FilterUtil.filter(data, filteringState, grid);
×
617
            }
618

619
            if (hasSorting && !this.options.ignoreSorting) {
2!
620
                this._sort = cloneValue(grid.sortingExpressions[0]);
×
621

622
                data = DataUtil.sort(data, grid.sortingExpressions, grid.sortStrategy, grid);
×
623
            }
624

625
            this.addHierarchicalGridData(grid, data);
2✔
626
        }
627
    }
628

629
    private addHierarchicalGridData(grid: GridType, records: any[]) {
630
        const childLayoutList = grid.childLayoutList;
18✔
631
        const columnFields = this._ownersMap.get(grid).columns.map(col => col.field);
126✔
632

633
        for (const entry of records) {
18✔
634
            const expansionStateVal = grid.expansionStates.has(entry) ? grid.expansionStates.get(entry) : false;
52✔
635

636
            const dataWithoutChildren = Object.keys(entry)
52✔
637
                .filter(k => columnFields.includes(k))
458✔
638
                .reduce((obj, key) => {
639
                    obj[key] = entry[key];
281✔
640
                    return obj;
281✔
641
                }, {});
642

643
            const hierarchicalGridRecord: IExportRecord = {
52✔
644
                data: dataWithoutChildren,
645
                level: 0,
646
                type: ExportRecordType.HierarchicalGridRecord,
647
                owner: grid,
648
                hierarchicalOwner: GRID_PARENT
649
            };
650

651
            this.flatRecords.push(hierarchicalGridRecord);
52✔
652

653
            for (const island of childLayoutList) {
52✔
654
                const path: IPathSegment = {
80✔
655
                    rowID: island.primaryKey ? entry[island.primaryKey] : entry,
80!
656
                    rowKey: island.primaryKey ? entry[island.primaryKey] : entry,
80!
657
                    rowIslandKey: island.key
658
                };
659

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

663
                this.getAllChildColumnsAndData(island, keyRecordData, expansionStateVal, islandGrid);
80✔
664
            }
665
        }
666
    }
667

668
    private prepareSummaries(grid: any): Map<string, Map<string, IgxSummaryResult[]>> {
669
        let summaries = new Map<string, Map<string, IgxSummaryResult[]>>();
177✔
670

671
        if (this.options.exportSummaries && grid.summaryService.summaryCacheMap.size > 0) {
177✔
672
            const summaryCacheMap = grid.summaryService.summaryCacheMap;
15✔
673

674
            switch(grid.summaryCalculationMode) {
15✔
675
                case GridSummaryCalculationMode.childLevelsOnly:
676
                    summaryCacheMap.delete(GRID_ROOT_SUMMARY);
1✔
677
                    break;
1✔
678
                case GridSummaryCalculationMode.rootLevelOnly:
679
                    for (const k of summaryCacheMap.keys()) {
2✔
680
                        if (k !== GRID_ROOT_SUMMARY) {
2!
681
                            summaryCacheMap.delete(k);
×
682
                        }
683
                    }
684
                    break;
2✔
685
            }
686

687
            summaries = summaryCacheMap;
15✔
688
        }
689

690
        return summaries;
177✔
691
    }
692

693
    private prepareIslandData(island: any, islandGrid: GridType, data: any[]): any[] {
694
        if (islandGrid !== undefined) {
177✔
695
            const hasFiltering = (islandGrid.filteringExpressionsTree &&
12!
696
                islandGrid.filteringExpressionsTree.filteringOperands.length > 0) ||
697
                (islandGrid.advancedFilteringExpressionsTree &&
698
                    islandGrid.advancedFilteringExpressionsTree.filteringOperands.length > 0);
699

700
            const hasSorting = islandGrid.sortingExpressions &&
12✔
701
                islandGrid.sortingExpressions.length > 0;
702

703
            const skipOperations =
704
                (!hasFiltering || !this.options.ignoreFiltering) &&
12!
705
                (!hasSorting || !this.options.ignoreSorting);
706

707
            if (skipOperations) {
12!
708
                data = islandGrid.filteredSortedData;
12✔
709
            } else {
710
                if (hasFiltering && !this.options.ignoreFiltering) {
×
711
                    const filteringState: IFilteringState = {
×
712
                        expressionsTree: islandGrid.filteringExpressionsTree,
713
                        advancedExpressionsTree: islandGrid.advancedFilteringExpressionsTree,
714
                        strategy: islandGrid.filterStrategy
715
                    };
716

717
                    data = FilterUtil.filter(data, filteringState, islandGrid);
×
718
                }
719

720
                if (hasSorting && !this.options.ignoreSorting) {
×
721
                    this._sort = cloneValue(islandGrid.sortingExpressions[0]);
×
722

723
                    data = DataUtil.sort(data, islandGrid.sortingExpressions, islandGrid.sortStrategy, islandGrid);
×
724
                }
725
            }
726
        } else {
727
            const hasFiltering = (island.filteringExpressionsTree &&
165!
728
                island.filteringExpressionsTree.filteringOperands.length > 0) ||
729
                (island.advancedFilteringExpressionsTree &&
730
                    island.advancedFilteringExpressionsTree.filteringOperands.length > 0);
731

732
            const hasSorting = island.sortingExpressions &&
165✔
733
                island.sortingExpressions.length > 0;
734

735
            const skipOperations =
736
                (!hasFiltering || this.options.ignoreFiltering) &&
165!
737
                (!hasSorting || this.options.ignoreSorting);
738

739
            if (!skipOperations) {
165!
740
                if (hasFiltering && !this.options.ignoreFiltering) {
×
741
                    const filteringState: IFilteringState = {
×
742
                        expressionsTree: island.filteringExpressionsTree,
743
                        advancedExpressionsTree: island.advancedFilteringExpressionsTree,
744
                        strategy: island.filterStrategy
745
                    };
746

747
                    data = FilterUtil.filter(data, filteringState, island);
×
748
                }
749

750
                if (hasSorting && !this.options.ignoreSorting) {
×
751
                    this._sort = cloneValue(island.sortingExpressions[0]);
×
752

753
                    data = DataUtil.sort(data, island.sortingExpressions, island.sortStrategy, island);
×
754
                }
755
            }
756
        }
757

758
        return data;
177✔
759
    }
760

761
    private getAllChildColumnsAndData(island: any,
762
        childData: any[], expansionStateVal: boolean, grid: GridType) {
763
        const hierarchicalOwner = `${GRID_CHILD}${++this.rowIslandCounter}`;
177✔
764
        const columnList = this._ownersMap.get(island).columns;
177✔
765
        const columnHeaders = columnList.filter(col => col.headerType === ExportHeaderType.ColumnHeader);
844✔
766
        const columnHeader = columnHeaders.map(col => col.header ? col.header : col.field);
755!
767

768
        const headerRecord: IExportRecord = {
177✔
769
            data: columnHeader,
770
            level: island.level,
771
            type: ExportRecordType.HeaderRecord,
772
            owner: island,
773
            hidden: !expansionStateVal,
774
            references: columnHeaders,
775
            hierarchicalOwner
776
        };
777

778
        if (childData && childData.length > 0) {
177✔
779
            this.flatRecords.push(headerRecord);
126✔
780

781
            for (const rec of childData) {
126✔
782
                const exportRecord: IExportRecord = {
325✔
783
                    data: rec,
784
                    level: island.level,
785
                    type: ExportRecordType.HierarchicalGridRecord,
786
                    owner: island,
787
                    hidden: !expansionStateVal,
788
                    hierarchicalOwner
789
                };
790

791
                exportRecord.summaryKey = island.key;
325✔
792
                this.flatRecords.push(exportRecord);
325✔
793

794
                if (island.children.length > 0) {
325✔
795
                    const islandExpansionStateVal = grid === undefined ?
97✔
796
                        false :
797
                        grid.expansionStates.has(rec) ?
15✔
798
                            grid.expansionStates.get(rec) :
799
                            false;
800

801
                    for (const childIsland of island.children) {
97✔
802
                        const path: IPathSegment = {
97✔
803
                            rowID: childIsland.primaryKey ? rec[childIsland.primaryKey] : rec,
97!
804
                            rowKey: childIsland.primaryKey ? rec[childIsland.primaryKey] : rec,
97!
805
                            rowIslandKey: childIsland.key
806
                        };
807

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

812
                        this.getAllChildColumnsAndData(childIsland, keyRecordData, islandExpansionStateVal, childIslandGrid);
97✔
813
                    }
814
                }
815
            }
816

817
            if (grid) {
126✔
818
                const summaries = this.prepareSummaries(grid);
12✔
819
                for (const k of summaries.keys()) {
12✔
820
                    const summary = summaries.get(k);
4✔
821
                    this.setSummaries(island.key, island.level, !expansionStateVal, island, summary, hierarchicalOwner)
4✔
822
                }
823
            }
824
        }
825
    }
826

827
    private prepareGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
828
        const groupedGridGroupingState: IGroupingState = {
109✔
829
            expressions: grid.groupingExpressions,
830
            expansion: grid.groupingExpansionState,
831
            defaultExpanded: grid.groupsExpanded,
832
        };
833

834
        const hasGrouping = grid.groupingExpressions &&
109✔
835
            grid.groupingExpressions.length > 0;
836

837
        const skipOperations =
838
            (!hasFiltering || !this.options.ignoreFiltering) &&
109✔
839
            (!hasSorting || !this.options.ignoreSorting) &&
840
            (!hasGrouping || !this.options.ignoreGrouping);
841

842
        if (skipOperations) {
109✔
843
            if (hasGrouping) {
104✔
844
                this.addGroupedData(grid, grid.groupsRecords, groupedGridGroupingState, true);
6✔
845
            } else {
846
                this.addFlatData(grid.filteredSortedData);
98✔
847
            }
848
        } else {
849
            let gridData = grid.data;
5✔
850

851
            if (hasFiltering && !this.options.ignoreFiltering) {
5!
852
                const filteringState: IFilteringState = {
×
853
                    expressionsTree: grid.filteringExpressionsTree,
854
                    advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
855
                    strategy: grid.filterStrategy
856
                };
857

858
                gridData = FilterUtil.filter(gridData, filteringState, grid);
×
859
            }
860

861
            if (hasSorting && !this.options.ignoreSorting) {
5✔
862
                // TODO: We should drop support for this since in a grouped grid it doesn't make sense
863
                // this._sort = !isGroupedGrid ?
864
                //     cloneValue(grid.sortingExpressions[0]) :
865
                //     grid.sortingExpressions.length > 1 ?
866
                //         cloneValue(grid.sortingExpressions[1]) :
867
                //         cloneValue(grid.sortingExpressions[0]);
868
                const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions;
2!
869
                gridData = DataUtil.sort(gridData, expressions, grid.sortStrategy, grid);
2✔
870
            }
871

872
            if (hasGrouping && !this.options.ignoreGrouping) {
5✔
873
                const groupsRecords = [];
2✔
874
                DataUtil.group(cloneArray(gridData), groupedGridGroupingState, grid.groupStrategy, grid, groupsRecords);
2✔
875
                gridData = groupsRecords;
2✔
876
            }
877

878
            if (hasGrouping && !this.options.ignoreGrouping) {
5✔
879
                this.addGroupedData(grid, gridData, groupedGridGroupingState, true);
2✔
880
            } else {
881
                this.addFlatData(gridData);
3✔
882
            }
883
        }
884
    }
885

886
    private prepareTreeGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
887
        const skipOperations =
888
            (!hasFiltering || !this.options.ignoreFiltering) &&
34✔
889
            (!hasSorting || !this.options.ignoreSorting);
890

891
        if (skipOperations) {
34✔
892
            this.addTreeGridData(grid.processedRootRecords);
28✔
893
        } else {
894
            let gridData = grid.rootRecords;
6✔
895

896
            if (hasFiltering && !this.options.ignoreFiltering) {
6!
897
                const filteringState: IFilteringState = {
×
898
                    expressionsTree: grid.filteringExpressionsTree,
899
                    advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
900
                    strategy: (grid.filterStrategy) ? grid.filterStrategy : new TreeGridFilteringStrategy()
×
901
                };
902

903
                gridData = filteringState.strategy
×
904
                    .filter(gridData, filteringState.expressionsTree, filteringState.advancedExpressionsTree);
905
            }
906

907
            if (hasSorting && !this.options.ignoreSorting) {
6!
908
                this._sort = cloneValue(grid.sortingExpressions[0]);
×
909

910
                gridData = DataUtil.treeGridSort(gridData, grid.sortingExpressions, grid.sortStrategy);
×
911
            }
912

913
            this.addTreeGridData(gridData);
6✔
914
        }
915
    }
916

917
    private addTreeGridData(records: ITreeGridRecord[], parentExpanded = true, hierarchicalOwner?: string) {
34✔
918
        if (!records) {
86!
919
            return;
×
920
        }
921

922
        for (const record of records) {
86✔
923
            const treeGridRecord: IExportRecord = {
144✔
924
                data: record.data,
925
                level: record.level,
926
                hidden: !parentExpanded,
927
                type: ExportRecordType.TreeGridRecord,
928
                summaryKey: record.key,
929
                hierarchicalOwner: record.level === 0 ? GRID_PARENT : hierarchicalOwner
144✔
930
            };
931

932
            this.flatRecords.push(treeGridRecord);
144✔
933

934
            if (record.children) {
144✔
935
                this.getTreeGridChildData(record.children, record.key, record.level, record.expanded && parentExpanded)
138✔
936
            }
937
        }
938
    }
939

940
    private getTreeGridChildData(recordChildren: ITreeGridRecord[], key: string, level:number, parentExpanded = true) {
×
941
        const hierarchicalOwner = `${GRID_CHILD}${++this.rowIslandCounter}`
138✔
942
        let summaryLevel = level;
138✔
943
        let summaryHidden = !parentExpanded;
138✔
944

945
        for (const rc of recordChildren) {
138✔
946
            if (rc.children && rc.children.length > 0) {
174✔
947
                this.addTreeGridData([rc], parentExpanded, hierarchicalOwner);
52✔
948
                summaryLevel = rc.level;
52✔
949
            } else {
950

951
                const currentRecord: IExportRecord = {
122✔
952
                    data: rc.data,
953
                    level: rc.level,
954
                    hidden: !parentExpanded,
955
                    type: ExportRecordType.DataRecord,
956
                    hierarchicalOwner
957
                };
958

959
                if (this._setChildSummaries) {
122✔
960
                    currentRecord.summaryKey = key;
11✔
961
                }
962

963
                this.flatRecords.push(currentRecord);
122✔
964
                summaryLevel = rc.level;
122✔
965
                summaryHidden = !parentExpanded
122✔
966
            }
967
        }
968

969
        if (this._setChildSummaries) {
138✔
970
            this.setSummaries(key, summaryLevel, summaryHidden, null, null, hierarchicalOwner);
7✔
971
        }
972
    }
973

974
    private addFlatData(records: any) {
975
        if (!records) {
101!
976
            return;
×
977
        }
978
        for (const record of records) {
101✔
979
            const data: IExportRecord = {
1,097✔
980
                data: record,
981
                type: ExportRecordType.DataRecord,
982
                level: 0
983
            };
984

985
            this.flatRecords.push(data);
1,097✔
986
        }
987
    }
988

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

992
        if (rootSummary) {
113✔
993
            const values = [...rootSummary.values()];
109✔
994
            const biggest = values.sort((a, b) => b.length - a.length)[0];
513✔
995

996
            for (let i = 0; i < biggest.length; i++) {
109✔
997
                const obj = {}
520✔
998

999
                for (const [key, value] of rootSummary) {
520✔
1000
                    const summaries = value.map(s => ({label: s.label, value: s.summaryResult}))
4,186✔
1001
                    obj[key] = summaries[i];
2,044✔
1002
                }
1003

1004
                const summaryRecord: IExportRecord = {
520✔
1005
                    data: obj,
1006
                    type: ExportRecordType.SummaryRecord,
1007
                    level,
1008
                    hidden,
1009
                    summaryKey,
1010
                    hierarchicalOwner
1011
                };
1012

1013
                if (owner) {
520✔
1014
                    summaryRecord.owner = owner;
15✔
1015
                }
1016

1017
                this.flatRecords.push(summaryRecord);
520✔
1018
            }
1019
        }
1020
    }
1021

1022
    private addGroupedData(grid: GridType, records: IGroupByRecord[], groupingState: IGroupingState, setGridParent: boolean, parentExpanded = true, summaryKeysArr: string[] = []) {
16✔
1023
        if (!records) {
80!
1024
            return;
×
1025
        }
1026

1027
        let previousKey = ''
80✔
1028
        const firstCol = this._ownersMap.get(DEFAULT_OWNER).columns
80✔
1029
            .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
401✔
1030
            .sort((a, b) => a.startIndex - b.startIndex)
86✔
1031
            .sort((a, b) => a.pinnedIndex - b.pinnedIndex)[0].field;
86✔
1032

1033
        for (const record of records) {
80✔
1034
            let recordVal = record.value;
176✔
1035
            const hierarchicalOwner = setGridParent ? GRID_PARENT : `${GRID_CHILD}${++this.rowIslandCounter}`;
176✔
1036
            const hierarchy = getHierarchy(record);
176✔
1037
            const expandState: IGroupByExpandState = groupingState.expansion.find((s) =>
176✔
1038
                isHierarchyMatch(s.hierarchy || [{ fieldName: record.expression.fieldName, value: recordVal }],
30!
1039
                hierarchy,
1040
                grid.groupingExpressions));
1041
            const expanded = expandState ? expandState.expanded : groupingState.defaultExpanded;
176✔
1042

1043
            const isDate = recordVal instanceof Date;
176✔
1044

1045
            if (isDate) {
176!
1046
                const timeZoneOffset = recordVal.getTimezoneOffset() * 60000;
×
1047
                const isoString = (new Date(recordVal - timeZoneOffset)).toISOString();
×
1048
                const pipe = new DatePipe(grid.locale);
×
1049
                recordVal = pipe.transform(isoString);
×
1050
            }
1051

1052
            const groupExpressionName = record.column && record.column.header ?
176!
1053
                record.column.header :
1054
                record.expression.fieldName;
1055

1056
            recordVal = recordVal !== null ? recordVal : '';
176!
1057

1058
            const groupExpression: IExportRecord = {
176✔
1059
                data: { [firstCol]: `${groupExpressionName}: ${recordVal ?? '(Blank)'} (${record.records.length})` },
179✔
1060
                level: record.level,
1061
                hidden: !parentExpanded,
1062
                type: ExportRecordType.GroupedRecord,
1063
                hierarchicalOwner
1064
            };
1065

1066
            this.flatRecords.push(groupExpression);
176✔
1067

1068
            let currKey = '';
176✔
1069
            let summaryKey = '';
176✔
1070

1071
            if (this._setChildSummaries) {
176✔
1072
                currKey = `'${record.expression.fieldName}': '${recordVal}'`;
92✔
1073
                summaryKeysArr = summaryKeysArr.filter(a => a !== previousKey);
136✔
1074
                previousKey = currKey;
92✔
1075
                summaryKeysArr.push(currKey);
92✔
1076
                summaryKey = `{ ${summaryKeysArr.join(', ')} }`;
92✔
1077
                groupExpression.summaryKey = summaryKey;
92✔
1078
            }
1079

1080
            if (record.groups.length > 0) {
176✔
1081
                this.addGroupedData(grid, record.groups, groupingState, false, expanded && parentExpanded, summaryKeysArr);
72✔
1082
            } else {
1083
                const rowRecords = record.records;
104✔
1084

1085
                for (const rowRecord of rowRecords) {
104✔
1086
                    const currentRecord: IExportRecord = {
140✔
1087
                        data: rowRecord,
1088
                        level: record.level + 1,
1089
                        hidden: !(expanded && parentExpanded),
278✔
1090
                        type: ExportRecordType.DataRecord,
1091
                        hierarchicalOwner
1092
                    };
1093

1094
                    if (summaryKey) {
140✔
1095
                        currentRecord.summaryKey = summaryKey;
54✔
1096
                    }
1097

1098
                    this.flatRecords.push(currentRecord);
140✔
1099
                }
1100
            }
1101

1102
            if (this._setChildSummaries) {
176✔
1103
                this.setSummaries(summaryKey, record.level + 1, !(expanded && parentExpanded), null, null, hierarchicalOwner);
92✔
1104
                summaryKeysArr.pop();
92✔
1105
            }
1106
        }
1107
    }
1108

1109
    private getColumns(columns: ColumnType[]): IColumnList {
1110
        const colList = [];
214✔
1111
        const colWidthList = [];
214✔
1112
        const hiddenColumns = [];
214✔
1113
        let indexOfLastPinnedColumn = -1;
214✔
1114
        let lastVisibleColumnIndex = -1;
214✔
1115
        let maxLevel = 0;
214✔
1116

1117
        columns.forEach((column) => {
214✔
1118
            const columnHeader = !ExportUtilities.isNullOrWhitespaces(column.header) ? column.header : column.field;
1,107✔
1119
            const exportColumn = !column.hidden || this.options.ignoreColumnsVisibility;
1,107✔
1120
            const index = this.options.ignoreColumnsOrder || this.options.ignoreColumnsVisibility ? column.index : column.visibleIndex;
1,107✔
1121
            const columnWidth = Number(column.width?.slice(0, -2)) || DEFAULT_COLUMN_WIDTH;
1,107✔
1122
            const columnLevel = !this.options.ignoreMultiColumnHeaders ? column.level : 0;
1,107✔
1123

1124
            const isMultiColHeader = column.columnGroup;
1,107✔
1125
            const colSpan = isMultiColHeader ?
1,107✔
1126
                column.allChildren
1127
                    .filter(ch => !(ch.columnGroup) && (!this.options.ignoreColumnsVisibility ? !ch.hidden : true))
498✔
1128
                    .length :
1129
                1;
1130

1131
            const columnInfo: IColumnInfo = {
1,107✔
1132
                header: ExportUtilities.sanitizeValue(columnHeader),
1133
                dataType: column.dataType,
1134
                field: column.field,
1135
                skip: !exportColumn,
1136
                formatter: column.formatter,
1137
                skipFormatter: false,
1138

1139
                headerType: isMultiColHeader ? ExportHeaderType.MultiColumnHeader : ExportHeaderType.ColumnHeader,
1,107✔
1140
                columnSpan: colSpan,
1141
                level: columnLevel,
1142
                startIndex: index,
1143
                pinnedIndex: !column.pinned ?
1,107✔
1144
                    Number.MAX_VALUE :
1145
                    !column.hidden ?
15✔
1146
                        column.grid.pinnedColumns.indexOf(column)
1147
                        : NaN,
1148
                columnGroupParent: column.parent ? column.parent : null,
1,107✔
1149
                columnGroup: isMultiColHeader ? column : null
1,107✔
1150
            };
1151

1152
            if (column.dataType === 'currency') {
1,107✔
1153
                columnInfo.currencyCode = column.pipeArgs.currencyCode
21!
1154
                    ? column.pipeArgs.currencyCode
1155
                    : getLocaleCurrencyCode(this.locale);
1156

1157
                columnInfo.displayFormat = column.pipeArgs.display
21!
1158
                    ? column.pipeArgs.display
1159
                    : 'symbol';
1160

1161
                columnInfo.digitsInfo = column.pipeArgs.digitsInfo
21!
1162
                    ? column.pipeArgs.digitsInfo
1163
                    : '1.0-2';
1164
            }
1165

1166
            if (column.dataType === 'date') {
1,107✔
1167
                columnInfo.dateFormat = getLocaleDateFormat(this.locale, FormatWidth.Medium);
25✔
1168
            }
1169

1170
            if (column.dataType === 'dateTime') {
1,107✔
1171
                columnInfo.dateFormat = getLocaleDateTimeFormat(this.locale, FormatWidth.Medium);
1✔
1172
            }
1173

1174
            if (this.options.ignoreColumnsOrder) {
1,107✔
1175
                if (columnInfo.startIndex !== columnInfo.pinnedIndex) {
15✔
1176
                    columnInfo.pinnedIndex = Number.MAX_VALUE;
15✔
1177
                }
1178
            }
1179

1180
            if (column.level > maxLevel && !this.options.ignoreMultiColumnHeaders) {
1,107✔
1181
                maxLevel = column.level;
48✔
1182
            }
1183

1184
            if (index !== -1) {
1,107✔
1185
                colList.push(columnInfo);
786✔
1186
                colWidthList.push(columnWidth);
786✔
1187
                lastVisibleColumnIndex = Math.max(lastVisibleColumnIndex, colList.indexOf(columnInfo));
786✔
1188
            } else {
1189
                hiddenColumns.push(columnInfo);
321✔
1190
            }
1191

1192
            if (column.pinned && exportColumn && columnInfo.headerType === ExportHeaderType.ColumnHeader) {
1,107✔
1193
                indexOfLastPinnedColumn++;
12✔
1194
            }
1195

1196
        });
1197

1198
        //Append the hidden columns to the end of the list
1199
        hiddenColumns.forEach((hiddenColumn) => {
214✔
1200
            colList[++lastVisibleColumnIndex] = hiddenColumn;
321✔
1201
        });
1202

1203
        const result: IColumnList = {
214✔
1204
            columns: colList,
1205
            columnWidths: colWidthList,
1206
            indexOfLastPinnedColumn,
1207
            maxLevel
1208
        };
1209

1210
        return result;
214✔
1211
    }
1212

1213
    private mapHierarchicalGridColumns(island: any, gridData: any) {
1214
        let columnList: IColumnList;
1215
        let keyData;
1216

1217
        if (island.autoGenerate) {
49!
1218
            keyData = gridData[island.key];
×
1219
            const islandKeys = island.children.map(i => i.key);
×
1220

1221
            const islandData = keyData.map(i => {
×
1222
                const newItem = {};
×
1223

1224
                Object.keys(i).map(k => {
×
1225
                    if (!islandKeys.includes(k)) {
×
1226
                        newItem[k] = i[k];
×
1227
                    }
1228
                });
1229

1230
                return newItem;
×
1231
            });
1232

1233
            columnList = this.getAutoGeneratedColumns(islandData);
×
1234
        } else {
1235
            const islandColumnList = island.columns;
49✔
1236
            columnList = this.getColumns(islandColumnList);
49✔
1237
        }
1238

1239
        this._ownersMap.set(island, columnList);
49✔
1240

1241
        if (island.children.length > 0) {
49✔
1242
            for (const childIsland of island.children) {
21✔
1243
                const islandKeyData = keyData !== undefined ? keyData[0] : {};
21!
1244
                this.mapHierarchicalGridColumns(childIsland, islandKeyData);
21✔
1245
            }
1246
        }
1247
    }
1248

1249
    private getAutoGeneratedColumns(data: any[]) {
1250
        const colList = [];
×
1251
        const colWidthList = [];
×
1252
        const keys = Object.keys(data[0]);
×
1253

1254
        keys.forEach((colKey, i) => {
×
1255
            const columnInfo: IColumnInfo = {
×
1256
                header: colKey,
1257
                field: colKey,
1258
                dataType: 'string',
1259
                skip: false,
1260
                headerType: ExportHeaderType.ColumnHeader,
1261
                columnSpan: 1,
1262
                level: 0,
1263
                startIndex: i,
1264
                pinnedIndex: Number.MAX_VALUE
1265
            };
1266

1267
            colList.push(columnInfo);
×
1268
            colWidthList.push(DEFAULT_COLUMN_WIDTH);
×
1269
        });
1270

1271
        const result: IColumnList = {
×
1272
            columns: colList,
1273
            columnWidths: colWidthList,
1274
            indexOfLastPinnedColumn: -1,
1275
            maxLevel: 0,
1276
        };
1277

1278
        return result;
×
1279
    }
1280

1281
    private addPivotRowHeaders(grid: any) {
1282
        if (grid?.pivotUI?.showRowHeaders) {
165✔
1283
            const headersList = this._ownersMap.get(DEFAULT_OWNER);
2✔
1284
            const enabledRows = grid.visibleRowDimensions.filter(r => r.enabled).map((r, index) => ({ name: r.displayName || r.memberName, level: index }));
6✔
1285
            let startIndex = 0;
2✔
1286
            enabledRows.forEach(x => {
2✔
1287
                headersList.columns.unshift({
6✔
1288
                    rowSpan: headersList.maxLevel + 1,
1289
                    field: x.name,
1290
                    header: x.name,
1291
                    startIndex: startIndex,
1292
                    skip: false,
1293
                    pinnedIndex: 0,
1294
                    level: x.level,
1295
                    dataType: 'string',
1296
                    headerType: ExportHeaderType.PivotRowHeader
1297
                });
1298
                startIndex += 1;
6✔
1299
            });
1300
            headersList.columnWidths.unshift(...Array(enabledRows.length).fill(200));
2✔
1301
        }
1302
    }
1303

1304
    private addPivotGridColumns(grid: any) {
1305
        if (grid.type !== 'pivot') {
165✔
1306
            return;
161✔
1307
        }
1308

1309
        const enabledRows = grid.visibleRowDimensions.map((r, i) => ({ name: r.memberName, level: i }));
11✔
1310

1311
        this.preparePivotGridColumns(enabledRows);
4✔
1312
        this.pivotGridFilterFieldsCount = enabledRows.length;
4✔
1313

1314
        const columnList = this._ownersMap.get(DEFAULT_OWNER);
4✔
1315
        columnList.columns.unshift(...this.pivotGridColumns);
4✔
1316
        columnList.columnWidths.unshift(...Array(this.pivotGridColumns.length).fill(200));
4✔
1317
        columnList.indexOfLastPinnedColumn = enabledRows.length - 1;
4✔
1318
        columnList.maxRowLevel = enabledRows.length;
4✔
1319
        this._ownersMap.set(DEFAULT_OWNER, columnList);
4✔
1320
    }
1321

1322
    private preparePivotGridColumns(keys: any, columnGroupParent?: string): any {
1323
        if (keys.length === 0) {
41!
1324
            return;
×
1325
        }
1326

1327
        let startIndex = 0;
41✔
1328
        const key = keys[0];
41✔
1329
        const records = this.flatRecords.map(r => r.data);
375✔
1330
        const groupedRecords = {};
41✔
1331
        records.forEach(obj => {
41✔
1332
            const keyValue = obj[key.name];
375✔
1333
            if (!groupedRecords[keyValue]) {
375✔
1334
                groupedRecords[keyValue] = [];
216✔
1335
            }
1336
            groupedRecords[keyValue].push(obj);
375✔
1337
        });
1338

1339
        if (columnGroupParent) {
41✔
1340
            const mapKeys = [...this.pivotGridKeyValueMap.keys()];
37✔
1341
            const mapValues = [...this.pivotGridKeyValueMap.values()];
37✔
1342

1343
            for (const k of Object.keys(groupedRecords)) {
37✔
1344
                groupedRecords[k] = groupedRecords[k].filter(row => mapKeys.every(mk => Object.keys(row).includes(mk))
462✔
1345
                    && mapValues.every(mv => Object.values(row).includes(mv)));
381✔
1346

1347
                if (groupedRecords[k].length === 0) {
197✔
1348
                    delete groupedRecords[k];
140✔
1349
                }
1350
            }
1351
        }
1352

1353
        for (const k of Object.keys(groupedRecords)) {
41✔
1354
            let groupKey = k;
76✔
1355
            const rowSpan = groupedRecords[k].length;
76✔
1356

1357

1358
            const rowDimensionColumn: IColumnInfo = {
76✔
1359
                columnSpan: 1,
1360
                rowSpan,
1361
                field: groupKey,
1362
                header: groupKey,
1363
                startIndex,
1364
                skip: false,
1365
                pinnedIndex: 0,
1366
                level: key.level,
1367
                dataType: 'string',
1368
                headerType: groupedRecords[groupKey].length > 1 ? ExportHeaderType.MultiRowHeader : ExportHeaderType.RowHeader,
76✔
1369
            };
1370
            if (groupKey === 'undefined') {
76!
1371
                this.pivotGridColumns[this.pivotGridColumns.length - 1].columnSpan += 1;
×
1372
                rowDimensionColumn.headerType = ExportHeaderType.PivotMergedHeader;
×
1373
                groupKey = columnGroupParent;
×
1374
            }
1375
            if (columnGroupParent) {
76✔
1376
                rowDimensionColumn.columnGroupParent = columnGroupParent;
57✔
1377
            } else {
1378
                rowDimensionColumn.columnGroup = groupKey;
19✔
1379
            }
1380

1381
            this.pivotGridColumns.push(rowDimensionColumn);
76✔
1382

1383
            if (keys.length > 1) {
76✔
1384
                if (groupKey !== columnGroupParent) {
37✔
1385
                    this.pivotGridKeyValueMap.set(key.name, groupKey);
37✔
1386
                }
1387
                const newKeys = keys.filter(kdd => kdd !== key);
86✔
1388
                this.preparePivotGridColumns(newKeys, groupKey)
37✔
1389
                this.pivotGridKeyValueMap.delete(key.name);
37✔
1390
            }
1391

1392
            startIndex += rowSpan;
76✔
1393
        }
1394
    }
1395

1396
    private addLevelColumns() {
1397
        if (this.options.exportSummaries && this.summaries.size > 0) {
165✔
1398
            this._ownersMap.forEach(om => {
11✔
1399
                const levelCol: IColumnInfo = {
13✔
1400
                    header: GRID_LEVEL_COL,
1401
                    dataType: 'number',
1402
                    field: GRID_LEVEL_COL,
1403
                    skip: false,
1404
                    skipFormatter: false,
1405
                    headerType: ExportHeaderType.ColumnHeader,
1406
                    columnSpan: 1,
1407
                    level: 0,
1408
                };
1409

1410
                om.columns.push(levelCol);
13✔
1411
                om.columnWidths.push(20);
13✔
1412
            })
1413
        }
1414
    }
1415

1416
    private addLevelData() {
1417
        if (this.options.exportSummaries && this.summaries.size > 0) {
165✔
1418
            for(const r of this.flatRecords){
11✔
1419
                if (r.type === ExportRecordType.DataRecord || r.type === ExportRecordType.TreeGridRecord || r.type === ExportRecordType.HierarchicalGridRecord) {
913✔
1420
                    r.data[GRID_LEVEL_COL] = r.level;
248✔
1421
                }
1422
            }
1423
        }
1424
    }
1425

1426
    private resetDefaults() {
1427
        this._sort = null;
206✔
1428
        this.flatRecords = [];
206✔
1429
        this.options = {} as IgxExporterOptionsBase;
206✔
1430
        this._ownersMap.clear();
206✔
1431
        this.rowIslandCounter = 0;
206✔
1432
    }
1433

1434
    protected abstract exportDataImplementation(data: any[], options: IgxExporterOptionsBase, done: () => void): void;
1435
}
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