UNPKG

igniteui-angular-sovn

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

1,308 lines (1,085 loc) 52.7 kB
import { EventEmitter } from '@angular/core'; import { cloneArray, cloneValue, IBaseEventArgs, resolveNestedPath, yieldingLoop } from '../../core/utils'; import { GridColumnDataType, DataUtil } from '../../data-operations/data-util'; import { ExportUtilities } from './export-utilities'; import { IgxExporterOptionsBase } from './exporter-options-base'; import { ITreeGridRecord } from '../../grids/tree-grid/tree-grid.interfaces'; import { TreeGridFilteringStrategy } from '../../grids/tree-grid/tree-grid.filtering.strategy'; import { IGroupingState } from '../../data-operations/groupby-state.interface'; import { getHierarchy, isHierarchyMatch } from '../../data-operations/operations'; import { IGroupByExpandState } from '../../data-operations/groupby-expand-state.interface'; import { IFilteringState } from '../../data-operations/filtering-state.interface'; import { DatePipe, FormatWidth, getLocaleCurrencyCode, getLocaleDateFormat, getLocaleDateTimeFormat } from '@angular/common'; import { IGroupByRecord } from '../../data-operations/groupby-record.interface'; import { ColumnType, GridType, IPathSegment } from '../../grids/common/grid.interface'; import { FilterUtil } from '../../data-operations/filtering-strategy'; import { IgxSummaryResult } from '../../grids/summaries/grid-summary'; import { GridSummaryCalculationMode } from '../../grids/common/enums'; export enum ExportRecordType { GroupedRecord = 'GroupedRecord', TreeGridRecord = 'TreeGridRecord', DataRecord = 'DataRecord', HierarchicalGridRecord = 'HierarchicalGridRecord', HeaderRecord = 'HeaderRecord', SummaryRecord = 'SummaryRecord', PivotGridRecord = 'PivotGridRecord' } export enum ExportHeaderType { RowHeader = 'RowHeader', ColumnHeader = 'ColumnHeader', MultiRowHeader = 'MultiRowHeader', MultiColumnHeader = 'MultiColumnHeader', } export interface IExportRecord { data: any; level: number; type: ExportRecordType; owner?: string | GridType; hidden?: boolean; summaryKey?: string; hierarchicalOwner?: string; } export interface IColumnList { columns: IColumnInfo[]; columnWidths: number[]; indexOfLastPinnedColumn: number; maxLevel?: number; maxRowLevel?: number; } export interface IColumnInfo { header: string; field: string; skip: boolean; dataType?: GridColumnDataType; skipFormatter?: boolean; formatter?: any; headerType?: ExportHeaderType; startIndex?: number; columnSpan?: number; rowSpan?: number; level?: number; exportIndex?: number; pinnedIndex?: number; columnGroupParent?: ColumnType | string; columnGroup?: ColumnType | string; currencyCode?: string; displayFormat?: string; dateFormat?: string; digitsInfo?: string; } /** * rowExporting event arguments * this.exporterService.rowExporting.subscribe((args: IRowExportingEventArgs) => { * // set args properties here * }) */ export interface IRowExportingEventArgs extends IBaseEventArgs { /** * Contains the exporting row data */ rowData: any; /** * Contains the exporting row index */ rowIndex: number; /** * Skip the exporting row when set to true */ cancel: boolean; } /** * columnExporting event arguments * ```typescript * this.exporterService.columnExporting.subscribe((args: IColumnExportingEventArgs) => { * // set args properties here * }); * ``` */ export interface IColumnExportingEventArgs extends IBaseEventArgs { /** * Contains the exporting column header */ header: string; /** * Contains the exporting column field name */ field: string; /** * Contains the exporting column index */ columnIndex: number; /** * Skip the exporting column when set to true */ cancel: boolean; /** * Export the column's data without applying its formatter, when set to true */ skipFormatter: boolean; /** * A reference to the grid owner. */ grid?: GridType; } /**hidden * A helper class used to identify whether the user has set a specific columnIndex * during columnExporting, so we can honor it at the exported file. */ class IgxColumnExportingEventArgs implements IColumnExportingEventArgs { public header: string; public field: string; public cancel: boolean; public skipFormatter: boolean; public grid?: GridType; public owner?: any; public userSetIndex? = false; private _columnIndex?: number; public get columnIndex(): number { return this._columnIndex; } public set columnIndex(value: number) { this._columnIndex = value; this.userSetIndex = true; } constructor(original: IColumnExportingEventArgs) { this.header = original.header; this.field = original.field; this.cancel = original.cancel; this.skipFormatter = original.skipFormatter; this.grid = original.grid; this.owner = original.owner; this._columnIndex = original.columnIndex; } } export const DEFAULT_OWNER = 'default'; export const GRID_ROOT_SUMMARY = 'igxGridRootSummary'; export const GRID_PARENT = 'grid-parent'; export const GRID_LEVEL_COL = 'GRID_LEVEL_COL'; const DEFAULT_COLUMN_WIDTH = 8.43; const GRID_CHILD = 'grid-child-'; export abstract class IgxBaseExporter { public exportEnded = new EventEmitter<IBaseEventArgs>(); /** * This event is emitted when a row is exported. * ```typescript * this.exporterService.rowExporting.subscribe((args: IRowExportingEventArgs) => { * // put event handler code here * }); * ``` * * @memberof IgxBaseExporter */ public rowExporting = new EventEmitter<IRowExportingEventArgs>(); /** * This event is emitted when a column is exported. * ```typescript * this.exporterService.columnExporting.subscribe((args: IColumnExportingEventArgs) => { * // put event handler code here * }); * ``` * * @memberof IgxBaseExporter */ public columnExporting = new EventEmitter<IColumnExportingEventArgs>(); protected _sort = null; protected pivotGridFilterFieldsCount: number; protected _ownersMap: Map<any, IColumnList> = new Map<any, IColumnList>(); private locale: string private _setChildSummaries = false private isPivotGridExport: boolean; private options: IgxExporterOptionsBase; private summaries: Map<string, Map<string, any[]>> = new Map<string, Map<string, IgxSummaryResult[]>>(); private rowIslandCounter = -1; private flatRecords: IExportRecord[] = []; private pivotGridColumns: IColumnInfo[] = [] private pivotGridRowDimensionsMap: Map<string, string>; private pivotGridKeyValueMap = new Map<string, string>(); /** * Method for exporting IgxGrid component's data. * ```typescript * this.exporterService.export(this.igxGridForExport, this.exportOptions); * ``` * * @memberof IgxBaseExporter */ public export(grid: any, options: IgxExporterOptionsBase): void { if (options === undefined || options === null) { throw Error('No options provided!'); } this.options = options; this.locale = grid.locale; let columns = grid.columns; if (this.options.ignoreMultiColumnHeaders) { columns = columns.filter(col => col.children === undefined); } const columnList = this.getColumns(columns); const tagName = grid.nativeElement.tagName.toLowerCase(); if (tagName === 'igx-hierarchical-grid') { this._ownersMap.set(grid, columnList); const childLayoutList = grid.childLayoutList; for (const island of childLayoutList) { this.mapHierarchicalGridColumns(island, grid.data[0]); } } else if (tagName === 'igx-pivot-grid') { this.pivotGridColumns = []; this.isPivotGridExport = true; this.pivotGridKeyValueMap = new Map<string, string>(); this.pivotGridRowDimensionsMap = new Map<string, string>(); grid.pivotConfiguration.rows.filter(r => r.enabled).forEach(rowDimension => { this.addToRowDimensionsMap(rowDimension, rowDimension.memberName); }); this._ownersMap.set(DEFAULT_OWNER, columnList); } else { this._ownersMap.set(DEFAULT_OWNER, columnList); } this.summaries = this.prepareSummaries(grid); this._setChildSummaries = this.summaries.size > 1 && grid.summaryCalculationMode !== GridSummaryCalculationMode.rootLevelOnly; this.addLevelColumns(); this.prepareData(grid); this.addLevelData(); this.addPivotGridColumns(grid); this.exportGridRecordsData(this.flatRecords, grid); } /** * Method for exporting any kind of array data. * ```typescript * this.exporterService.exportData(this.arrayForExport, this.exportOptions); * ``` * * @memberof IgxBaseExporter */ public exportData(data: any[], options: IgxExporterOptionsBase): void { if (options === undefined || options === null) { throw Error('No options provided!'); } this.options = options; const records = data.map(d => { const record: IExportRecord = { data: d, type: ExportRecordType.DataRecord, level: 0 }; return record; }); this.exportGridRecordsData(records); } private addToRowDimensionsMap(rowDimension: any, rootParentName: string) { this.pivotGridRowDimensionsMap[rowDimension.memberName] = rootParentName; if (rowDimension.childLevel) { this.addToRowDimensionsMap(rowDimension.childLevel, rootParentName) } } private exportGridRecordsData(records: IExportRecord[], grid?: GridType) { if (this._ownersMap.size === 0) { const recordsData = records.filter(r => r.type !== ExportRecordType.SummaryRecord).map(r => r.data); const keys = ExportUtilities.getKeysFromData(recordsData); const columns = keys.map((k) => ({ header: k, field: k, skip: false, headerType: ExportHeaderType.ColumnHeader, level: 0, columnSpan: 1 })); const columnWidths = new Array<number>(keys.length).fill(DEFAULT_COLUMN_WIDTH); const mapRecord: IColumnList = { columns, columnWidths, indexOfLastPinnedColumn: -1, maxLevel: 0 }; this._ownersMap.set(DEFAULT_OWNER, mapRecord); } let shouldReorderColumns = false; for (const [key, mapRecord] of this._ownersMap) { let skippedPinnedColumnsCount = 0; let columnsWithoutHeaderCount = 1; let indexOfLastPinnedColumn = mapRecord.indexOfLastPinnedColumn; mapRecord.columns.forEach((column, index) => { if (!column.skip) { const columnExportArgs: IColumnExportingEventArgs = { header: !ExportUtilities.isNullOrWhitespaces(column.header) ? column.header : 'Column' + columnsWithoutHeaderCount++, field: column.field, columnIndex: index, cancel: false, skipFormatter: false, grid: key === DEFAULT_OWNER ? grid : key }; const newColumnExportArgs = new IgxColumnExportingEventArgs(columnExportArgs); this.columnExporting.emit(newColumnExportArgs); column.header = newColumnExportArgs.header; column.skip = newColumnExportArgs.cancel; column.skipFormatter = newColumnExportArgs.skipFormatter; if (newColumnExportArgs.userSetIndex) { column.exportIndex = newColumnExportArgs.columnIndex; shouldReorderColumns = true; } if (column.skip) { if (index <= indexOfLastPinnedColumn) { skippedPinnedColumnsCount++; } this.calculateColumnSpans(column, mapRecord, column.columnSpan); const nonSkippedColumns = mapRecord.columns.filter(c => !c.skip); if (nonSkippedColumns.length > 0) { this._ownersMap.get(key).maxLevel = nonSkippedColumns.sort((a, b) => b.level - a.level)[0].level; } } if (this._sort && this._sort.fieldName === column.field) { if (column.skip) { this._sort = null; } else { this._sort.fieldName = column.header; } } } }); indexOfLastPinnedColumn -= skippedPinnedColumnsCount; // Reorder columns only if a column has been assigned a specific columnIndex during columnExporting event if (shouldReorderColumns) { mapRecord.columns = this.reorderColumns(mapRecord.columns); } } const dataToExport = new Array<IExportRecord>(); const actualData = records[0]?.data; const isSpecialData = ExportUtilities.isSpecialData(actualData); yieldingLoop(records.length, 100, (i) => { const row = records[i]; this.exportRow(dataToExport, row, i, isSpecialData); }, () => { this.exportDataImplementation(dataToExport, this.options, () => { this.resetDefaults(); }); }); } private calculateColumnSpans(column: IColumnInfo, mapRecord: IColumnList, span: number) { if (column.headerType === ExportHeaderType.MultiColumnHeader && column.skip) { const columnGroupChildren = mapRecord.columns.filter(c => c.columnGroupParent === column.columnGroup); columnGroupChildren.forEach(cgc => { if (cgc.headerType === ExportHeaderType.MultiColumnHeader) { cgc.columnSpan = 0; cgc.columnGroupParent = null; cgc.skip = true; this.calculateColumnSpans(cgc, mapRecord, cgc.columnSpan); } else { cgc.skip = true; } }); } const targetCol = mapRecord.columns.filter(c => column.columnGroupParent !== null && c.columnGroup === column.columnGroupParent)[0]; if (targetCol !== undefined) { targetCol.columnSpan -= span; if (targetCol.columnGroupParent !== null) { this.calculateColumnSpans(targetCol, mapRecord, span); } if (targetCol.columnSpan === 0) { targetCol.skip = true; } } } private exportRow(data: IExportRecord[], record: IExportRecord, index: number, isSpecialData: boolean) { if (!isSpecialData) { const owner = record.owner === undefined ? DEFAULT_OWNER : record.owner; const ownerCols = this._ownersMap.get(owner).columns; if (record.type !== ExportRecordType.HeaderRecord) { const columns = ownerCols .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip) .sort((a, b) => a.startIndex - b.startIndex) .sort((a, b) => a.pinnedIndex - b.pinnedIndex); record.data = columns.reduce((a, e) => { if (!e.skip) { let rawValue = resolveNestedPath(record.data, e.field); const shouldApplyFormatter = e.formatter && !e.skipFormatter && record.type !== ExportRecordType.GroupedRecord; const isOfDateType = e.dataType === 'date' || e.dataType === 'dateTime' || e.dataType === 'time'; if (isOfDateType && record.type !== ExportRecordType.SummaryRecord && record.type !== ExportRecordType.GroupedRecord && !(rawValue instanceof Date) && !shouldApplyFormatter && rawValue !== undefined && rawValue !== null) { rawValue = new Date(rawValue); } else if (e.dataType === 'string' && rawValue instanceof Date) { rawValue = rawValue.toString(); } let formattedValue = shouldApplyFormatter ? e.formatter(rawValue) : rawValue; if (this.isPivotGridExport && !isNaN(parseFloat(formattedValue))) { formattedValue = parseFloat(formattedValue); } a[e.field] = formattedValue; } return a; }, {}); } else { const filteredHeaders = ownerCols.filter(c => c.skip).map(c => c.header ? c.header : c.field); record.data = record.data.filter(d => filteredHeaders.indexOf(d) === -1); } } const rowArgs = { rowData: record.data, rowIndex: index, cancel: false }; this.rowExporting.emit(rowArgs); if (!rowArgs.cancel) { data.push(record); } } private reorderColumns(columns: IColumnInfo[]): IColumnInfo[] { const filteredColumns = columns.filter(c => !c.skip); const length = filteredColumns.length; const specificIndicesColumns = filteredColumns.filter((col) => !isNaN(col.exportIndex)) .sort((a,b) => a.exportIndex - b.exportIndex); const indices = specificIndicesColumns.map(col => col.exportIndex); specificIndicesColumns.forEach(col => { filteredColumns.splice(filteredColumns.indexOf(col), 1); }); const reorderedColumns = new Array(length); if (specificIndicesColumns.length > Math.max(...indices)) { return specificIndicesColumns.concat(filteredColumns); } else { indices.forEach((i, index) => { if (i < 0 || i >= length) { filteredColumns.push(specificIndicesColumns[index]); } else { let k = i; while (k < length && reorderedColumns[k] !== undefined) { ++k; } reorderedColumns[k] = specificIndicesColumns[index]; } }); for (let i = 0; i < length; i++) { if (reorderedColumns[i] === undefined) { reorderedColumns[i] = filteredColumns.splice(0, 1)[0]; } } } return reorderedColumns; } private prepareData(grid: GridType) { this.flatRecords = []; const tagName = grid.nativeElement.tagName.toLowerCase(); const hasFiltering = (grid.filteringExpressionsTree && grid.filteringExpressionsTree.filteringOperands.length > 0) || (grid.advancedFilteringExpressionsTree && grid.advancedFilteringExpressionsTree.filteringOperands.length > 0); const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions; const hasSorting = expressions && expressions.length > 0; let setSummaryOwner = false; switch (tagName) { case 'igx-pivot-grid': { this.preparePivotGridData(grid); break; } case 'igx-hierarchical-grid': { this.prepareHierarchicalGridData(grid, hasFiltering, hasSorting); setSummaryOwner = true; break; } case 'igx-tree-grid': { this.prepareTreeGridData(grid, hasFiltering, hasSorting); break; } default: { this.prepareGridData(grid, hasFiltering, hasSorting); break; } } if (this.summaries.size > 0 && grid.summaryCalculationMode !== GridSummaryCalculationMode.childLevelsOnly) { setSummaryOwner ? this.setSummaries(GRID_ROOT_SUMMARY, 0, false, grid) : this.setSummaries(GRID_ROOT_SUMMARY); } } private preparePivotGridData(grid: GridType) { for (const record of grid.filteredSortedData) { const recordData = Object.fromEntries(record.aggregationValues); record.dimensionValues.forEach((value, key) => { const actualKey = this.pivotGridRowDimensionsMap[key]; recordData[actualKey] = value; }); const pivotGridRecord: IExportRecord = { data: recordData, level: record.level, type: ExportRecordType.PivotGridRecord }; this.flatRecords.push(pivotGridRecord); } } private prepareHierarchicalGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) { const skipOperations = (!hasFiltering || !this.options.ignoreFiltering) && (!hasSorting || !this.options.ignoreSorting); if (skipOperations) { const data = grid.filteredSortedData; this.addHierarchicalGridData(grid, data); } else { let data = grid.data; if (hasFiltering && !this.options.ignoreFiltering) { const filteringState: IFilteringState = { expressionsTree: grid.filteringExpressionsTree, advancedExpressionsTree: grid.advancedFilteringExpressionsTree, strategy: grid.filterStrategy }; data = FilterUtil.filter(data, filteringState, grid); } if (hasSorting && !this.options.ignoreSorting) { this._sort = cloneValue(grid.sortingExpressions[0]); data = DataUtil.sort(data, grid.sortingExpressions, grid.sortStrategy, grid); } this.addHierarchicalGridData(grid, data); } } private addHierarchicalGridData(grid: GridType, records: any[]) { const childLayoutList = grid.childLayoutList; const columnFields = this._ownersMap.get(grid).columns.map(col => col.field); for (const entry of records) { const expansionStateVal = grid.expansionStates.has(entry) ? grid.expansionStates.get(entry) : false; const dataWithoutChildren = Object.keys(entry) .filter(k => columnFields.includes(k)) .reduce((obj, key) => { obj[key] = entry[key]; return obj; }, {}); const hierarchicalGridRecord: IExportRecord = { data: dataWithoutChildren, level: 0, type: ExportRecordType.HierarchicalGridRecord, owner: grid, hierarchicalOwner: GRID_PARENT }; this.flatRecords.push(hierarchicalGridRecord); for (const island of childLayoutList) { const path: IPathSegment = { rowID: island.primaryKey ? entry[island.primaryKey] : entry, rowIslandKey: island.key }; const islandGrid = grid?.gridAPI.getChildGrid([path]); const keyRecordData = this.prepareIslandData(island, islandGrid, entry[island.key]) || []; this.getAllChildColumnsAndData(island, keyRecordData, expansionStateVal, islandGrid); } } } private prepareSummaries(grid: any): Map<string, Map<string, IgxSummaryResult[]>> { let summaries = new Map<string, Map<string, IgxSummaryResult[]>>(); if (this.options.exportSummaries && grid.summaryService.summaryCacheMap.size > 0) { const summaryCacheMap = grid.summaryService.summaryCacheMap; switch(grid.summaryCalculationMode) { case GridSummaryCalculationMode.childLevelsOnly: summaryCacheMap.delete(GRID_ROOT_SUMMARY); break; case GridSummaryCalculationMode.rootLevelOnly: for (const k of summaryCacheMap.keys()) { if (k !== GRID_ROOT_SUMMARY) { summaryCacheMap.delete(k); } } break; } summaries = summaryCacheMap; } return summaries; } private prepareIslandData(island: any, islandGrid: GridType, data: any[]): any[] { if (islandGrid !== undefined) { const hasFiltering = (islandGrid.filteringExpressionsTree && islandGrid.filteringExpressionsTree.filteringOperands.length > 0) || (islandGrid.advancedFilteringExpressionsTree && islandGrid.advancedFilteringExpressionsTree.filteringOperands.length > 0); const hasSorting = islandGrid.sortingExpressions && islandGrid.sortingExpressions.length > 0; const skipOperations = (!hasFiltering || !this.options.ignoreFiltering) && (!hasSorting || !this.options.ignoreSorting); if (skipOperations) { data = islandGrid.filteredSortedData; } else { if (hasFiltering && !this.options.ignoreFiltering) { const filteringState: IFilteringState = { expressionsTree: islandGrid.filteringExpressionsTree, advancedExpressionsTree: islandGrid.advancedFilteringExpressionsTree, strategy: islandGrid.filterStrategy }; data = FilterUtil.filter(data, filteringState, islandGrid); } if (hasSorting && !this.options.ignoreSorting) { this._sort = cloneValue(islandGrid.sortingExpressions[0]); data = DataUtil.sort(data, islandGrid.sortingExpressions, islandGrid.sortStrategy, islandGrid); } } } else { const hasFiltering = (island.filteringExpressionsTree && island.filteringExpressionsTree.filteringOperands.length > 0) || (island.advancedFilteringExpressionsTree && island.advancedFilteringExpressionsTree.filteringOperands.length > 0); const hasSorting = island.sortingExpressions && island.sortingExpressions.length > 0; const skipOperations = (!hasFiltering || this.options.ignoreFiltering) && (!hasSorting || this.options.ignoreSorting); if (!skipOperations) { if (hasFiltering && !this.options.ignoreFiltering) { const filteringState: IFilteringState = { expressionsTree: island.filteringExpressionsTree, advancedExpressionsTree: island.advancedFilteringExpressionsTree, strategy: island.filterStrategy }; data = FilterUtil.filter(data, filteringState, island); } if (hasSorting && !this.options.ignoreSorting) { this._sort = cloneValue(island.sortingExpressions[0]); data = DataUtil.sort(data, island.sortingExpressions, island.sortStrategy, island); } } } return data; } private getAllChildColumnsAndData(island: any, childData: any[], expansionStateVal: boolean, grid: GridType) { const hierarchicalOwner = `${GRID_CHILD}${++this.rowIslandCounter}`; const columnList = this._ownersMap.get(island).columns; const columnHeader = columnList .filter(col => col.headerType === ExportHeaderType.ColumnHeader) .map(col => col.header ? col.header : col.field); const headerRecord: IExportRecord = { data: columnHeader, level: island.level, type: ExportRecordType.HeaderRecord, owner: island, hidden: !expansionStateVal, hierarchicalOwner }; if (childData && childData.length > 0) { this.flatRecords.push(headerRecord); for (const rec of childData) { const exportRecord: IExportRecord = { data: rec, level: island.level, type: ExportRecordType.HierarchicalGridRecord, owner: island, hidden: !expansionStateVal, hierarchicalOwner }; exportRecord.summaryKey = island.key; this.flatRecords.push(exportRecord); if (island.children.length > 0) { const islandExpansionStateVal = grid === undefined ? false : grid.expansionStates.has(rec) ? grid.expansionStates.get(rec) : false; for (const childIsland of island.children) { const path: IPathSegment = { rowID: childIsland.primaryKey ? rec[childIsland.primaryKey] : rec, rowIslandKey: childIsland.key }; // only defined when row is expanded in UI const childIslandGrid = grid?.gridAPI.getChildGrid([path]); const keyRecordData = this.prepareIslandData(island, childIslandGrid, rec[childIsland.key]) || []; this.getAllChildColumnsAndData(childIsland, keyRecordData, islandExpansionStateVal, childIslandGrid); } } } if (grid) { const summaries = this.prepareSummaries(grid); for (const k of summaries.keys()) { const summary = summaries.get(k); this.setSummaries(island.key, island.level, !expansionStateVal, island, summary, hierarchicalOwner) } } } } private prepareGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) { const groupedGridGroupingState: IGroupingState = { expressions: grid.groupingExpressions, expansion: grid.groupingExpansionState, defaultExpanded: grid.groupsExpanded, }; const hasGrouping = grid.groupingExpressions && grid.groupingExpressions.length > 0; const skipOperations = (!hasFiltering || !this.options.ignoreFiltering) && (!hasSorting || !this.options.ignoreSorting) && (!hasGrouping || !this.options.ignoreGrouping); if (skipOperations) { if (hasGrouping) { this.addGroupedData(grid, grid.groupsRecords, groupedGridGroupingState, true); } else { this.addFlatData(grid.filteredSortedData); } } else { let gridData = grid.data; if (hasFiltering && !this.options.ignoreFiltering) { const filteringState: IFilteringState = { expressionsTree: grid.filteringExpressionsTree, advancedExpressionsTree: grid.advancedFilteringExpressionsTree, strategy: grid.filterStrategy }; gridData = FilterUtil.filter(gridData, filteringState, grid); } if (hasSorting && !this.options.ignoreSorting) { // TODO: We should drop support for this since in a grouped grid it doesn't make sense // this._sort = !isGroupedGrid ? // cloneValue(grid.sortingExpressions[0]) : // grid.sortingExpressions.length > 1 ? // cloneValue(grid.sortingExpressions[1]) : // cloneValue(grid.sortingExpressions[0]); const expressions = grid.groupingExpressions ? grid.groupingExpressions.concat(grid.sortingExpressions || []) : grid.sortingExpressions; gridData = DataUtil.sort(gridData, expressions, grid.sortStrategy, grid); } if (hasGrouping && !this.options.ignoreGrouping) { const groupsRecords = []; DataUtil.group(cloneArray(gridData), groupedGridGroupingState, grid.groupStrategy, grid, groupsRecords); gridData = groupsRecords; } if (hasGrouping && !this.options.ignoreGrouping) { this.addGroupedData(grid, gridData, groupedGridGroupingState, true); } else { this.addFlatData(gridData); } } } private prepareTreeGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) { const skipOperations = (!hasFiltering || !this.options.ignoreFiltering) && (!hasSorting || !this.options.ignoreSorting); if (skipOperations) { this.addTreeGridData(grid.processedRootRecords); } else { let gridData = grid.rootRecords; if (hasFiltering && !this.options.ignoreFiltering) { const filteringState: IFilteringState = { expressionsTree: grid.filteringExpressionsTree, advancedExpressionsTree: grid.advancedFilteringExpressionsTree, strategy: (grid.filterStrategy) ? grid.filterStrategy : new TreeGridFilteringStrategy() }; gridData = filteringState.strategy .filter(gridData, filteringState.expressionsTree, filteringState.advancedExpressionsTree); } if (hasSorting && !this.options.ignoreSorting) { this._sort = cloneValue(grid.sortingExpressions[0]); gridData = DataUtil.treeGridSort(gridData, grid.sortingExpressions, grid.sortStrategy); } this.addTreeGridData(gridData); } } private addTreeGridData(records: ITreeGridRecord[], parentExpanded = true, hierarchicalOwner?: string) { if (!records) { return; } for (const record of records) { const treeGridRecord: IExportRecord = { data: record.data, level: record.level, hidden: !parentExpanded, type: ExportRecordType.TreeGridRecord, summaryKey: record.key, hierarchicalOwner: record.level === 0 ? GRID_PARENT : hierarchicalOwner }; this.flatRecords.push(treeGridRecord); if (record.children) { this.getTreeGridChildData(record.children, record.key, record.level, record.expanded && parentExpanded) } } } private getTreeGridChildData(recordChildren: ITreeGridRecord[], key: string, level:number, parentExpanded = true) { const hierarchicalOwner = `${GRID_CHILD}${++this.rowIslandCounter}` let summaryLevel = level; let summaryHidden = !parentExpanded; for (const rc of recordChildren) { if (rc.children && rc.children.length > 0) { this.addTreeGridData([rc], parentExpanded, hierarchicalOwner); summaryLevel = rc.level; } else { const currentRecord: IExportRecord = { data: rc.data, level: rc.level, hidden: !parentExpanded, type: ExportRecordType.DataRecord, hierarchicalOwner }; if (this._setChildSummaries) { currentRecord.summaryKey = key; } this.flatRecords.push(currentRecord); summaryLevel = rc.level; summaryHidden = !parentExpanded } } if (this._setChildSummaries) { this.setSummaries(key, summaryLevel, summaryHidden, null, null, hierarchicalOwner); } } private addFlatData(records: any) { if (!records) { return; } for (const record of records) { const data: IExportRecord = { data: record, type: ExportRecordType.DataRecord, level: 0 }; this.flatRecords.push(data); } } private setSummaries(summaryKey: string, level = 0, hidden = false, owner?: any, summary?: Map<string, IgxSummaryResult[]>, hierarchicalOwner?: string) { const rootSummary = summary ?? this.summaries.get(summaryKey); if (rootSummary) { const values = [...rootSummary.values()]; const biggest = values.sort((a, b) => b.length - a.length)[0]; for (let i = 0; i < biggest.length; i++) { const obj = {} for (const [key, value] of rootSummary) { const summaries = value.map(s => ({label: s.label, value: s.summaryResult})) obj[key] = summaries[i]; } const summaryRecord: IExportRecord = { data: obj, type: ExportRecordType.SummaryRecord, level, hidden, summaryKey, hierarchicalOwner }; if (owner) { summaryRecord.owner = owner; } this.flatRecords.push(summaryRecord); } } } private addGroupedData(grid: GridType, records: IGroupByRecord[], groupingState: IGroupingState, setGridParent: boolean, parentExpanded = true, summaryKeysArr: string[] = []) { if (!records) { return; } let previousKey = '' const firstCol = this._ownersMap.get(DEFAULT_OWNER).columns .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip) .sort((a, b) => a.startIndex - b.startIndex) .sort((a, b) => a.pinnedIndex - b.pinnedIndex)[0].field; for (const record of records) { let recordVal = record.value; const hierarchicalOwner = setGridParent ? GRID_PARENT : `${GRID_CHILD}${++this.rowIslandCounter}`; const hierarchy = getHierarchy(record); const expandState: IGroupByExpandState = groupingState.expansion.find((s) => isHierarchyMatch(s.hierarchy || [{ fieldName: record.expression.fieldName, value: recordVal }], hierarchy, grid.groupingExpressions)); const expanded = expandState ? expandState.expanded : groupingState.defaultExpanded; const isDate = recordVal instanceof Date; if (isDate) { const timeZoneOffset = recordVal.getTimezoneOffset() * 60000; const isoString = (new Date(recordVal - timeZoneOffset)).toISOString(); const pipe = new DatePipe(grid.locale); recordVal = pipe.transform(isoString); } const groupExpressionName = record.column && record.column.header ? record.column.header : record.expression.fieldName; recordVal = recordVal !== null ? recordVal : ''; const groupExpression: IExportRecord = { data: { [firstCol]: `${groupExpressionName}: ${recordVal ?? '(Blank)'} (${record.records.length})` }, level: record.level, hidden: !parentExpanded, type: ExportRecordType.GroupedRecord, hierarchicalOwner }; this.flatRecords.push(groupExpression); let currKey = ''; let summaryKey = ''; if (this._setChildSummaries) { currKey = `'${groupExpressionName}': '${recordVal}'`; summaryKeysArr = summaryKeysArr.filter(a => a !== previousKey); previousKey = currKey; summaryKeysArr.push(currKey); summaryKey = `{ ${summaryKeysArr.join(', ')} }`; groupExpression.summaryKey = summaryKey; } if (record.groups.length > 0) { this.addGroupedData(grid, record.groups, groupingState, false, expanded && parentExpanded, summaryKeysArr); } else { const rowRecords = record.records; for (const rowRecord of rowRecords) { const currentRecord: IExportRecord = { data: rowRecord, level: record.level + 1, hidden: !(expanded && parentExpanded), type: ExportRecordType.DataRecord, hierarchicalOwner }; if (summaryKey) { currentRecord.summaryKey = summaryKey; } this.flatRecords.push(currentRecord); } } if (this._setChildSummaries) { this.setSummaries(summaryKey, record.level + 1, !(expanded && parentExpanded), null, null, hierarchicalOwner); summaryKeysArr.pop(); } } } private getColumns(columns: ColumnType[]): IColumnList { const colList = []; const colWidthList = []; const hiddenColumns = []; let indexOfLastPinnedColumn = -1; let lastVisibleColumnIndex = -1; let maxLevel = 0; columns.forEach((column) => { const columnHeader = !ExportUtilities.isNullOrWhitespaces(column.header) ? column.header : column.field; const exportColumn = !column.hidden || this.options.ignoreColumnsVisibility; const index = this.options.ignoreColumnsOrder || this.options.ignoreColumnsVisibility ? column.index : column.visibleIndex; const columnWidth = Number(column.width?.slice(0, -2)) || DEFAULT_COLUMN_WIDTH; const columnLevel = !this.options.ignoreMultiColumnHeaders ? column.level : 0; const isMultiColHeader = column.columnGroup; const colSpan = isMultiColHeader ? column.allChildren .filter(ch => !(ch.columnGroup) && (!this.options.ignoreColumnsVisibility ? !ch.hidden : true)) .length : 1; const columnInfo: IColumnInfo = { header: ExportUtilities.sanitizeValue(columnHeader), dataType: column.dataType, field: column.field, skip: !exportColumn, formatter: column.formatter, skipFormatter: false, headerType: isMultiColHeader ? ExportHeaderType.MultiColumnHeader : ExportHeaderType.ColumnHeader, columnSpan: colSpan, level: columnLevel, startIndex: index, pinnedIndex: !column.pinned ? Number.MAX_VALUE : !column.hidden ? column.grid.pinnedColumns.indexOf(column) : NaN, columnGroupParent: column.parent ? column.parent : null, columnGroup: isMultiColHeader ? column : null }; if (column.dataType === 'currency') { columnInfo.currencyCode = column.pipeArgs.currencyCode ? column.pipeArgs.currencyCode : getLocaleCurrencyCode(this.locale); columnInfo.displayFormat = column.pipeArgs.display ? column.pipeArgs.display : 'symbol'; columnInfo.digitsInfo = column.pipeArgs.digitsInfo ? column.pipeArgs.digitsInfo : '1.0-2'; } if (column.dataType === 'date') { columnInfo.dateFormat = getLocaleDateFormat(this.locale, FormatWidth.Medium); } if (column.dataType === 'dateTime') { columnInfo.dateFormat = getLocaleDateTimeFormat(this.locale, FormatWidth.Medium); } if (this.options.ignoreColumnsOrder) { if (columnInfo.startIndex !== columnInfo.pinnedIndex) { columnInfo.pinnedIndex = Number.MAX_VALUE; } } if (column.level > maxLevel && !this.options.ignoreMultiColumnHeaders) { maxLevel = column.level; } if (index !== -1) { colList.push(columnInfo); colWidthList.push(columnWidth); lastVisibleColumnIndex = Math.max(lastVisibleColumnIndex, colList.indexOf(columnInfo)); } else { hiddenColumns.push(columnInfo); } if (column.pinned && exportColumn && columnInfo.headerType === ExportHeaderType.ColumnHeader) { indexOfLastPinnedColumn++; } }); //Append the hidden columns to the end of the list hiddenColumns.forEach((hiddenColumn) => { colList[++lastVisibleColumnIndex] = hiddenColumn; }); const result: IColumnList = { columns: colList, columnWidths: colWidthList, indexOfLastPinnedColumn, maxLevel }; return result; } private mapHierarchicalGridColumns(island: any, gridData: any) { let columnList: IColumnList; let keyData; if (island.autoGenerate) { keyData = gridData[island.key]; const islandKeys = island.children.map(i => i.key); const islandData = keyData.map(i => { const newItem = {}; Object.keys(i).map(k => { if (!islandKeys.includes(k)) { newItem[k] = i[k]; } }); return newItem; }); columnList = this.getAutoGeneratedColumns(islandData); } else { const islandColumnList = island.columns; columnList = this.getColumns(islandColumnList); } this._ownersMap.set(island, columnList); if (island.children.length > 0) { for (const childIsland of island.children) { const islandKeyData = keyData !== undefined ? keyData[0] : {}; this.mapHierarchicalGridColumns(childIsland, islandKeyData); } } } private getAutoGeneratedColumns(data: any[]) { const colList = []; const colWidthList = []; const keys = Object.keys(data[0]); keys.forEach((colKey, i) => { const columnInfo: IColumnInfo = { header: colKey, field: colKey, dataType: 'string', skip: false, headerType: ExportHeaderType.ColumnHeader, columnSpan: 1, level: 0, startIndex: i, pinnedIndex: Number.MAX_VALUE }; colList.push(columnInfo); colWidthList.push(DEFAULT_COLUMN_WIDTH); }); const result: IColumnList = { columns: colList, columnWidths: colWidthList, indexOfLastPinnedColumn: -1, maxLevel: 0, }; return result; } public addPivotGridColumns(grid: any) { if (grid.nativeElement.tagName.toLowerCase() !== 'igx-pivot-grid') { return; } const enabledRows = grid.pivotConfiguration.rows.filter(r => r.enabled).map((r, i) => ({ name: r.memberName, level: i })); this.preparePivotGridColumns(enabledRows); this.pivotGridFilterFieldsCount = enabledRows.length; const columnList = this._ownersMap.get(DEFAULT_OWNER); columnList.columns.unshift(...this.pivotGridColumns); columnList.columnWidths.unshift(...Array(this.pivotGridColumns.length).fill(200)); columnList.indexOfLastPinnedColumn = enabledRows.length - 1; columnList.maxRowLevel = enabledRows.length; this._ownersMap.set(DEFAULT_OWNER, columnList); } private preparePivotGridColumns(keys: any, columnGroupParent?: string): any { if (keys.length === 0) { return; } let startIndex = 0; const key = keys[0]; const records = this.flatRecords.map(r => r.data); const groupedRecords = records.reduce((hash, obj) => ({...hash, [obj[key.name]]:( hash[obj[key.name]] || []).concat(obj)}), {}) if (columnGroupParent) { const mapKeys = [...this.pivotGridKeyValueMap.keys()]; const mapValues = [...this.pivotGridKeyValueMap.values()]; for (const k of Object.keys(groupedRecords)) { groupedRecords[k] = groupedRecords[k].filter(row => mapKeys.every(mk => Object.keys(row).includes(mk)) && mapValues.every(mv => Object.val