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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

3.76
/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
}
47

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

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

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

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

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

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

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

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

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

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

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

153
    private _columnIndex?: number;
154

155
    public get columnIndex(): number {
UNCOV
156
        return this._columnIndex;
×
157
    }
158

159
    public set columnIndex(value: number) {
UNCOV
160
        this._columnIndex = value;
×
UNCOV
161
        this.userSetIndex = true;
×
162
    }
163

164
    constructor(original: IColumnExportingEventArgs) {
UNCOV
165
        this.header = original.header;
×
UNCOV
166
        this.field = original.field;
×
UNCOV
167
        this.cancel = original.cancel;
×
UNCOV
168
        this.skipFormatter = original.skipFormatter;
×
UNCOV
169
        this.grid = original.grid;
×
UNCOV
170
        this.owner = original.owner;
×
UNCOV
171
        this._columnIndex = original.columnIndex;
×
172
    }
173
}
174

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

182
export abstract class IgxBaseExporter {
183

UNCOV
184
    public exportEnded = new EventEmitter<IBaseEventArgs>();
×
185

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

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

UNCOV
210
    protected _sort = null;
×
211
    protected pivotGridFilterFieldsCount: number;
UNCOV
212
    protected _ownersMap: Map<any, IColumnList> = new Map<any, IColumnList>();
×
213

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

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

UNCOV
240
        this.options = options;
×
UNCOV
241
        this.locale = grid.locale;
×
UNCOV
242
        this.ownerGrid = grid;
×
UNCOV
243
        let columns = grid.columns;
×
244

UNCOV
245
        if (this.options.ignoreMultiColumnHeaders) {
×
UNCOV
246
            columns = columns.filter(col => col.children === undefined);
×
247
        }
248

UNCOV
249
        const columnList = this.getColumns(columns);
×
250

UNCOV
251
        if (grid.type === 'hierarchical') {
×
UNCOV
252
            this._ownersMap.set(grid, columnList);
×
253

UNCOV
254
            const childLayoutList = grid.childLayoutList;
×
255

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

UNCOV
265
            grid.visibleRowDimensions.filter(r => r.enabled).forEach(rowDimension => {
×
UNCOV
266
                this.addToRowDimensionsMap(rowDimension, rowDimension.memberName);
×
267
            });
268

UNCOV
269
            this._ownersMap.set(DEFAULT_OWNER, columnList);
×
270
        } else {
UNCOV
271
            this._ownersMap.set(DEFAULT_OWNER, columnList);
×
272
        }
273

UNCOV
274
        this.summaries = this.prepareSummaries(grid);
×
UNCOV
275
        this._setChildSummaries =  this.summaries.size > 1 && grid.summaryCalculationMode !== GridSummaryCalculationMode.rootLevelOnly;
×
276

UNCOV
277
        this.addLevelColumns();
×
UNCOV
278
        this.prepareData(grid);
×
UNCOV
279
        this.addLevelData();
×
UNCOV
280
        this.addPivotGridColumns(grid);
×
UNCOV
281
        this.addPivotRowHeaders(grid);
×
UNCOV
282
        this.exportGridRecordsData(this.flatRecords, grid);
×
283
    }
284

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

UNCOV
298
        this.options = options;
×
299

UNCOV
300
        const records = data.map(d => {
×
UNCOV
301
            const record: IExportRecord = {
×
302
                data: d,
303
                type: ExportRecordType.DataRecord,
304
                level: 0
305
            };
306

UNCOV
307
            return record;
×
308
        });
309

UNCOV
310
        this.exportGridRecordsData(records);
×
311
    }
312

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

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

UNCOV
328
            const mapRecord: IColumnList = {
×
329
                columns,
330
                columnWidths,
331
                indexOfLastPinnedColumn: -1,
332
                maxLevel: 0
333
            };
334

UNCOV
335
            this._ownersMap.set(DEFAULT_OWNER, mapRecord);
×
336
        }
337

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

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

UNCOV
357
                    const newColumnExportArgs = new IgxColumnExportingEventArgs(columnExportArgs);
×
UNCOV
358
                    this.columnExporting.emit(newColumnExportArgs);
×
359

UNCOV
360
                    column.header = newColumnExportArgs.header;
×
UNCOV
361
                    column.skip = newColumnExportArgs.cancel;
×
UNCOV
362
                    column.skipFormatter = newColumnExportArgs.skipFormatter;
×
363

UNCOV
364
                    if (newColumnExportArgs.userSetIndex) {
×
UNCOV
365
                        column.exportIndex = newColumnExportArgs.columnIndex;
×
UNCOV
366
                        shouldReorderColumns = true;
×
367
                    }
368

UNCOV
369
                    if (column.skip) {
×
UNCOV
370
                        if (index <= indexOfLastPinnedColumn) {
×
371
                            skippedPinnedColumnsCount++;
×
372
                        }
373

UNCOV
374
                        this.calculateColumnSpans(column, mapRecord, column.columnSpan);
×
375

UNCOV
376
                        const nonSkippedColumns = mapRecord.columns.filter(c => !c.skip);
×
377

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

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

UNCOV
393
            indexOfLastPinnedColumn -= skippedPinnedColumnsCount;
×
394

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

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

UNCOV
405
        yieldingLoop(records.length, 100, (i) => {
×
UNCOV
406
            const row = records[i];
×
UNCOV
407
            this.exportRow(dataToExport, row, i, isSpecialData);
×
408
        }, () => {
UNCOV
409
            this.exportDataImplementation(dataToExport, this.options, () => {
×
UNCOV
410
                this.resetDefaults();
×
411
            });
412
        });
413
    }
414

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

UNCOV
419
            columnGroupChildren.forEach(cgc => {
×
UNCOV
420
                if (cgc.headerType === ExportHeaderType.MultiColumnHeader) {
×
UNCOV
421
                    cgc.columnSpan = 0;
×
UNCOV
422
                    cgc.columnGroupParent = null;
×
UNCOV
423
                    cgc.skip = true;
×
424

UNCOV
425
                    this.calculateColumnSpans(cgc, mapRecord, cgc.columnSpan);
×
426
                } else {
UNCOV
427
                    cgc.skip = true;
×
428
                }
429
            });
430
        }
431

UNCOV
432
        const targetCol = mapRecord.columns.filter(c => column.columnGroupParent !== null && column.columnGroupParent !== undefined && c.columnGroup === column.columnGroupParent)[0];
×
UNCOV
433
        if (targetCol !== undefined) {
×
UNCOV
434
            targetCol.columnSpan -= span;
×
435

UNCOV
436
            if (targetCol.columnGroupParent !== null) {
×
UNCOV
437
                this.calculateColumnSpans(targetCol, mapRecord, span);
×
438
            }
439

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

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

UNCOV
451
            if (record.type !== ExportRecordType.HeaderRecord) {
×
UNCOV
452
                const columns = ownerCols
×
UNCOV
453
                    .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
×
UNCOV
454
                    .sort((a, b) => a.startIndex - b.startIndex)
×
UNCOV
455
                    .sort((a, b) => a.pinnedIndex - b.pinnedIndex);
×
456

UNCOV
457
                record.data = columns.reduce((a, e) => {
×
UNCOV
458
                    if (!e.skip) {
×
UNCOV
459
                        let rawValue = resolveNestedPath(record.data, e.field);
×
460

UNCOV
461
                        const shouldApplyFormatter = e.formatter && !e.skipFormatter && record.type !== ExportRecordType.GroupedRecord;
×
UNCOV
462
                        const isOfDateType = e.dataType === 'date' || e.dataType === 'dateTime' || e.dataType === 'time';
×
463

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

UNCOV
476
                        let formattedValue = shouldApplyFormatter ? e.formatter(rawValue, record.data) : rawValue;
×
477

UNCOV
478
                        if (this.isPivotGridExport && !isNaN(parseFloat(formattedValue))) {
×
UNCOV
479
                            formattedValue = parseFloat(formattedValue);
×
480
                        }
481

UNCOV
482
                        a[e.field] = formattedValue;
×
483
                    }
UNCOV
484
                    return a;
×
485
                }, {});
486
            } else {
UNCOV
487
                const filteredHeaders = ownerCols.filter(c => c.skip).map(c => c.header ? c.header : c.field);
×
UNCOV
488
                record.data = record.data.filter(d => filteredHeaders.indexOf(d) === -1);
×
489
            }
490
        }
491

UNCOV
492
        const rowArgs = {
×
493
            rowData: record.data,
494
            rowIndex: index,
495
            cancel: false,
496
            owner: record.owner ?? this.ownerGrid
×
497
        };
498

UNCOV
499
        this.rowExporting.emit(rowArgs);
×
500

UNCOV
501
        if (!rowArgs.cancel) {
×
UNCOV
502
            data.push(record);
×
503
        }
504
    }
505

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

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

UNCOV
517
        const reorderedColumns = new Array(length);
×
518

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

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

540
        }
UNCOV
541
        return reorderedColumns;
×
542
    }
543

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

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

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

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

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

UNCOV
593
            this.flatRecords.push(pivotGridRecord);
×
594
        }
595
    }
596

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

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

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

UNCOV
609
            if (hasFiltering && !this.options.ignoreFiltering) {
×
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

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

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

UNCOV
625
            this.addHierarchicalGridData(grid, data);
×
626
        }
627
    }
628

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

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

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

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

UNCOV
651
            this.flatRecords.push(hierarchicalGridRecord);
×
652

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

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

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

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

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

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

UNCOV
687
            summaries = summaryCacheMap;
×
688
        }
689

UNCOV
690
        return summaries;
×
691
    }
692

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

UNCOV
700
            const hasSorting = islandGrid.sortingExpressions &&
×
701
                islandGrid.sortingExpressions.length > 0;
702

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

UNCOV
707
            if (skipOperations) {
×
UNCOV
708
                data = islandGrid.filteredSortedData;
×
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 {
UNCOV
727
            const hasFiltering = (island.filteringExpressionsTree &&
×
728
                island.filteringExpressionsTree.filteringOperands.length > 0) ||
729
                (island.advancedFilteringExpressionsTree &&
730
                    island.advancedFilteringExpressionsTree.filteringOperands.length > 0);
731

UNCOV
732
            const hasSorting = island.sortingExpressions &&
×
733
                island.sortingExpressions.length > 0;
734

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

UNCOV
739
            if (!skipOperations) {
×
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

UNCOV
758
        return data;
×
759
    }
760

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

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

UNCOV
778
        if (childData && childData.length > 0) {
×
UNCOV
779
            this.flatRecords.push(headerRecord);
×
780

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

UNCOV
791
                exportRecord.summaryKey = island.key;
×
UNCOV
792
                this.flatRecords.push(exportRecord);
×
793

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

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

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

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

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

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

UNCOV
834
        const hasGrouping = grid.groupingExpressions &&
×
835
            grid.groupingExpressions.length > 0;
836

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

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

UNCOV
851
            if (hasFiltering && !this.options.ignoreFiltering) {
×
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

UNCOV
861
            if (hasSorting && !this.options.ignoreSorting) {
×
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]);
UNCOV
868
                const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions;
×
UNCOV
869
                gridData = DataUtil.sort(gridData, expressions, grid.sortStrategy, grid);
×
870
            }
871

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

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

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

UNCOV
891
        if (skipOperations) {
×
UNCOV
892
            this.addTreeGridData(grid.processedRootRecords);
×
893
        } else {
UNCOV
894
            let gridData = grid.rootRecords;
×
895

UNCOV
896
            if (hasFiltering && !this.options.ignoreFiltering) {
×
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

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

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

UNCOV
913
            this.addTreeGridData(gridData);
×
914
        }
915
    }
916

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

UNCOV
922
        for (const record of records) {
×
UNCOV
923
            const treeGridRecord: IExportRecord = {
×
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
×
930
            };
931

UNCOV
932
            this.flatRecords.push(treeGridRecord);
×
933

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

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

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

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

UNCOV
959
                if (this._setChildSummaries) {
×
UNCOV
960
                    currentRecord.summaryKey = key;
×
961
                }
962

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

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

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

UNCOV
985
            this.flatRecords.push(data);
×
986
        }
987
    }
988

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

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

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

UNCOV
999
                for (const [key, value] of rootSummary) {
×
UNCOV
1000
                    const summaries = value.map(s => ({label: s.label, value: s.summaryResult}))
×
UNCOV
1001
                    obj[key] = summaries[i];
×
1002
                }
1003

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

UNCOV
1013
                if (owner) {
×
UNCOV
1014
                    summaryRecord.owner = owner;
×
1015
                }
1016

UNCOV
1017
                this.flatRecords.push(summaryRecord);
×
1018
            }
1019
        }
1020
    }
1021

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

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

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

UNCOV
1043
            const isDate = recordVal instanceof Date;
×
1044

UNCOV
1045
            if (isDate) {
×
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

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

UNCOV
1056
            recordVal = recordVal !== null ? recordVal : '';
×
1057

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

UNCOV
1066
            this.flatRecords.push(groupExpression);
×
1067

UNCOV
1068
            let currKey = '';
×
UNCOV
1069
            let summaryKey = '';
×
1070

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

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

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

UNCOV
1094
                    if (summaryKey) {
×
UNCOV
1095
                        currentRecord.summaryKey = summaryKey;
×
1096
                    }
1097

UNCOV
1098
                    this.flatRecords.push(currentRecord);
×
1099
                }
1100
            }
1101

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

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

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

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

UNCOV
1131
            const columnInfo: IColumnInfo = {
×
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,
×
1140
                columnSpan: colSpan,
1141
                level: columnLevel,
1142
                startIndex: index,
1143
                pinnedIndex: !column.pinned ?
×
1144
                    Number.MAX_VALUE :
1145
                    !column.hidden ?
×
1146
                        column.grid.pinnedColumns.indexOf(column)
1147
                        : NaN,
1148
                columnGroupParent: column.parent ? column.parent : null,
×
1149
                columnGroup: isMultiColHeader ? column : null
×
1150
            };
1151

UNCOV
1152
            if (column.dataType === 'currency') {
×
UNCOV
1153
                columnInfo.currencyCode = column.pipeArgs.currencyCode
×
1154
                    ? column.pipeArgs.currencyCode
1155
                    : getLocaleCurrencyCode(this.locale);
1156

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

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

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

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

UNCOV
1174
            if (this.options.ignoreColumnsOrder) {
×
UNCOV
1175
                if (columnInfo.startIndex !== columnInfo.pinnedIndex) {
×
UNCOV
1176
                    columnInfo.pinnedIndex = Number.MAX_VALUE;
×
1177
                }
1178
            }
1179

UNCOV
1180
            if (column.level > maxLevel && !this.options.ignoreMultiColumnHeaders) {
×
UNCOV
1181
                maxLevel = column.level;
×
1182
            }
1183

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

UNCOV
1192
            if (column.pinned && exportColumn && columnInfo.headerType === ExportHeaderType.ColumnHeader) {
×
UNCOV
1193
                indexOfLastPinnedColumn++;
×
1194
            }
1195

1196
        });
1197

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

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

UNCOV
1210
        return result;
×
1211
    }
1212

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

UNCOV
1217
        if (island.autoGenerate) {
×
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 {
UNCOV
1235
            const islandColumnList = island.columns;
×
UNCOV
1236
            columnList = this.getColumns(islandColumnList);
×
1237
        }
1238

UNCOV
1239
        this._ownersMap.set(island, columnList);
×
1240

UNCOV
1241
        if (island.children.length > 0) {
×
UNCOV
1242
            for (const childIsland of island.children) {
×
UNCOV
1243
                const islandKeyData = keyData !== undefined ? keyData[0] : {};
×
UNCOV
1244
                this.mapHierarchicalGridColumns(childIsland, islandKeyData);
×
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) {
UNCOV
1282
        if (grid?.pivotUI?.showRowHeaders) {
×
UNCOV
1283
            const headersList = this._ownersMap.get(DEFAULT_OWNER);
×
UNCOV
1284
            const enabledRows = grid.visibleRowDimensions.filter(r => r.enabled).map((r, index) => ({ name: r.displayName || r.memberName, level: index }));
×
UNCOV
1285
            let startIndex = 0;
×
UNCOV
1286
            enabledRows.forEach(x => {
×
UNCOV
1287
                headersList.columns.unshift({
×
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
                });
UNCOV
1298
                startIndex += 1;
×
1299
            });
UNCOV
1300
            headersList.columnWidths.unshift(...Array(enabledRows.length).fill(200));
×
1301
        }
1302
    }
1303

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

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

UNCOV
1311
        this.preparePivotGridColumns(enabledRows);
×
UNCOV
1312
        this.pivotGridFilterFieldsCount = enabledRows.length;
×
1313

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

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

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

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

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

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

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

1357

UNCOV
1358
            const rowDimensionColumn: IColumnInfo = {
×
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,
×
1369
            };
UNCOV
1370
            if (groupKey === 'undefined') {
×
1371
                this.pivotGridColumns[this.pivotGridColumns.length - 1].columnSpan += 1;
×
1372
                rowDimensionColumn.headerType = ExportHeaderType.PivotMergedHeader;
×
1373
                groupKey = columnGroupParent;
×
1374
            }
UNCOV
1375
            if (columnGroupParent) {
×
UNCOV
1376
                rowDimensionColumn.columnGroupParent = columnGroupParent;
×
1377
            } else {
UNCOV
1378
                rowDimensionColumn.columnGroup = groupKey;
×
1379
            }
1380

UNCOV
1381
            this.pivotGridColumns.push(rowDimensionColumn);
×
1382

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

UNCOV
1392
            startIndex += rowSpan;
×
1393
        }
1394
    }
1395

1396
    private addLevelColumns() {
UNCOV
1397
        if (this.options.exportSummaries && this.summaries.size > 0) {
×
UNCOV
1398
            this._ownersMap.forEach(om => {
×
UNCOV
1399
                const levelCol: IColumnInfo = {
×
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

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

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

1426
    private resetDefaults() {
UNCOV
1427
        this._sort = null;
×
UNCOV
1428
        this.flatRecords = [];
×
UNCOV
1429
        this.options = {} as IgxExporterOptionsBase;
×
UNCOV
1430
        this._ownersMap.clear();
×
UNCOV
1431
        this.rowIslandCounter = 0;
×
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