ag-grid-enterprise
Version:
ag-Grid Enterprise Features
268 lines (234 loc) • 9.79 kB
text/typescript
import {
BaseGridSerializingSession,
Column,
Constants,
GridSerializingParams,
RowAccumulator,
RowNode,
RowSpanningAccumulator,
RowType,
Utils,
_,
ExcelOOXMLDataType
} from 'ag-grid-community';
import {
ExcelCell,
ExcelColumn,
ExcelDataType,
ExcelRow,
ExcelStyle,
ExcelWorksheet,
} from 'ag-grid-community';
import {ExcelMixedStyle} from './excelCreator';
import {ExcelXmlFactory} from './excelXmlFactory';
import {ExcelXlsxFactory} from './excelXlsxFactory';
export interface ExcelGridSerializingParams extends GridSerializingParams {
sheetName:string;
excelFactory: ExcelXmlFactory | ExcelXlsxFactory;
baseExcelStyles: ExcelStyle[];
styleLinker: (rowType: RowType, rowIndex: number, colIndex: number, value: string, column: Column, node: RowNode) => string[];
suppressTextAsCDATA: boolean;
}
export class ExcelXmlSerializingSession extends BaseGridSerializingSession<ExcelCell[][]> {
protected stylesByIds: any;
protected mixedStyles: { [key: string]: ExcelMixedStyle } = {};
protected mixedStyleCounter: number = 0;
protected excelStyles: ExcelStyle[];
protected customHeader: ExcelCell[][];
protected customFooter: ExcelCell[][];
protected sheetName:string;
protected suppressTextAsCDATA:boolean;
protected rows: ExcelRow[] = [];
protected cols: ExcelColumn[];
protected excelFactory: ExcelXmlFactory | ExcelXlsxFactory;
baseExcelStyles: ExcelStyle[];
protected styleLinker: (rowType: RowType, rowIndex: number, colIndex: number, value: string, column: Column, node: RowNode) => string[];
constructor(config: ExcelGridSerializingParams) {
super({
columnController: config.columnController,
valueService: config.valueService,
gridOptionsWrapper: config.gridOptionsWrapper,
processCellCallback: config.processCellCallback,
processHeaderCallback: config.processHeaderCallback,
cellAndHeaderEscaper: (raw: string) => raw
});
const {sheetName, excelFactory, baseExcelStyles, styleLinker, suppressTextAsCDATA} = config;
this.sheetName = sheetName;
this.excelFactory = excelFactory;
this.baseExcelStyles = baseExcelStyles || [];
this.styleLinker = styleLinker;
this.suppressTextAsCDATA = suppressTextAsCDATA;
this.stylesByIds = {};
this.baseExcelStyles.forEach((it: ExcelStyle) => {
this.stylesByIds[it.id] = it;
});
this.excelStyles = [...this.baseExcelStyles];
}
public addCustomHeader(customHeader: ExcelCell[][]): void {
this.customHeader = customHeader;
}
public addCustomFooter(customFooter: ExcelCell[][]): void {
this.customFooter = customFooter;
}
public prepare(columnsToExport: Column[]): void {
this.cols = Utils.map(columnsToExport, (it: Column) => {
it.getColDef().cellStyle;
return {
width: it.getActualWidth()
};
});
}
public onNewHeaderGroupingRow(): RowSpanningAccumulator {
let currentCells: ExcelCell[] = [];
let that = this;
this.rows.push({
cells: currentCells
});
return {
onColumn: (header: string, index: number, span: number) => {
let styleIds: string[] = that.styleLinker(RowType.HEADER_GROUPING, 1, index, "grouping-" + header, null, null);
currentCells.push(that.createMergedCell(styleIds.length > 0 ? styleIds[0] : null, "String", header, span));
}
};
}
public onNewHeaderRow(): RowAccumulator {
return this.onNewRow(this.onNewHeaderColumn);
}
public onNewBodyRow(): RowAccumulator {
return this.onNewRow(this.onNewBodyColumn);
}
onNewRow(onNewColumnAccumulator: (rowIndex: number, currentCells: ExcelCell[]) => (column: Column, index: number, node?: RowNode) => void): RowAccumulator {
let currentCells: ExcelCell[] = [];
this.rows.push({
cells: currentCells
});
return {
onColumn: onNewColumnAccumulator.bind(this, this.rows.length, currentCells)()
};
}
onNewHeaderColumn(rowIndex: number, currentCells: ExcelCell[]): (column: Column, index: number, node?: RowNode) => void {
let that = this;
return (column: Column, index: number, node?: RowNode) => {
let nameForCol = this.extractHeaderValue(column);
let styleIds: string[] = that.styleLinker(RowType.HEADER, rowIndex, index, nameForCol, column, null);
currentCells.push(this.createCell(styleIds.length > 0 ? styleIds[0] : null, 'String', nameForCol));
};
}
public parse(): string {
function join(header: ExcelCell[][], body: ExcelRow[], footer: ExcelCell[][]): ExcelRow[] {
let all: ExcelRow[] = [];
if (header) {
header.forEach(rowArray => all.push({cells: rowArray}));
}
body.forEach(it => all.push(it));
if (footer) {
footer.forEach(rowArray => all.push({cells: rowArray}));
}
return all;
}
let data: ExcelWorksheet [] = [{
name: this.sheetName,
table: {
columns: this.cols,
rows: join(this.customHeader, this.rows, this.customFooter)
}
}];
return this.excelFactory.createExcel(this.excelStyles, data, []);
}
onNewBodyColumn(rowIndex: number, currentCells: ExcelCell[]): (column: Column, index: number, node?: RowNode) => void {
let that = this;
return (column: Column, index: number, node?: RowNode) => {
let valueForCell = this.extractRowCellValue(column, index, Constants.EXPORT_TYPE_EXCEL, node);
let styleIds: string[] = that.styleLinker(RowType.BODY, rowIndex, index, valueForCell, column, node);
let excelStyleId: string = null;
if (styleIds && styleIds.length == 1) {
excelStyleId = styleIds [0];
} else if (styleIds && styleIds.length > 1) {
let key: string = styleIds.join("-");
if (!this.mixedStyles[key]) {
this.addNewMixedStyle(styleIds);
}
excelStyleId = this.mixedStyles[key].excelID;
}
let type: ExcelDataType = Utils.isNumeric(valueForCell) ? 'Number' : 'String';
currentCells.push(that.createCell(excelStyleId, type, valueForCell));
};
}
addNewMixedStyle(styleIds: string[]): void {
this.mixedStyleCounter += 1;
let excelId = 'mixedStyle' + this.mixedStyleCounter;
let resultantStyle: ExcelStyle = {};
styleIds.forEach((styleId: string) => {
this.excelStyles.forEach((excelStyle: ExcelStyle) => {
if (excelStyle.id === styleId) {
Utils.mergeDeep(resultantStyle, Utils.deepCloneObject(excelStyle));
}
});
});
resultantStyle['id'] = excelId;
resultantStyle['name'] = excelId;
let key: string = styleIds.join("-");
this.mixedStyles[key] = {
excelID: excelId,
key: key,
result: resultantStyle
};
this.excelStyles.push(resultantStyle);
this.stylesByIds[excelId] = resultantStyle;
}
protected styleExists(styleId: string): boolean {
if (styleId == null) return false;
return this.stylesByIds[styleId];
}
protected createCell(styleId: string, type: ExcelDataType | ExcelOOXMLDataType, value: string): ExcelCell {
let actualStyle: ExcelStyle = this.stylesByIds[styleId];
let styleExists: boolean = actualStyle != null;
function getType(): ExcelDataType {
if (
styleExists &&
actualStyle.dataType
) switch (actualStyle.dataType) {
case 'string':
return 'String';
case 'number':
return 'Number';
case 'dateTime':
return 'DateTime';
case 'error':
return 'Error';
case 'boolean':
return 'Boolean';
default:
console.warn(`ag-grid: Unrecognized data type for excel export [${actualStyle.id}.dataType=${actualStyle.dataType}]`);
}
return type as ExcelDataType;
}
let typeTransformed: ExcelDataType = getType();
let massageText = (val:string) => this.suppressTextAsCDATA ? _.escape(val) : `<![CDATA[${val}]]>`;
let convertBoolean = (val: boolean | string): string => {
if (!val || val === '0' || val === 'false') return '0';
return '1';
};
return {
styleId: styleExists ? styleId : null,
data: {
type: typeTransformed,
value:
typeTransformed === 'String' ? massageText(value):
typeTransformed === 'Number' ? Number(value).valueOf() + '' :
typeTransformed === 'Boolean' ? convertBoolean(value) :
value
}
};
}
protected createMergedCell(styleId: string, type: ExcelDataType | ExcelOOXMLDataType, value: string, numOfCells: number): ExcelCell {
return {
styleId: this.styleExists(styleId) ? styleId : null,
data: {
type: type,
value: value
},
mergeAcross: numOfCells
};
}
}