UNPKG

15 kBPlain TextView Raw
1import {Column} from "./entities/column";
2import {Autowired, Bean} from "./context/context";
3import {ColumnController} from "./columnController/columnController";
4import {Constants} from "./constants";
5import {IRowModel} from "./interfaces/iRowModel";
6import {Utils as _} from "./utils";
7import {RowNode} from "./entities/rowNode";
8import {SelectionController} from "./selectionController";
9import {ValueService} from "./valueService/valueService";
10import {GridOptionsWrapper} from "./gridOptionsWrapper";
11import {
12 BaseExportParams,
13 ExportParams,
14 ProcessCellForExportParams,
15 ProcessHeaderForExportParams,
16 ShouldRowBeSkippedParams
17} from "./exportParams";
18import {DisplayedGroupCreator} from "./columnController/displayedGroupCreator";
19import {BalancedColumnTreeBuilder} from "./columnController/balancedColumnTreeBuilder";
20import {GroupInstanceIdCreator} from "./columnController/groupInstanceIdCreator";
21import {ColumnGroupChild} from "./entities/columnGroupChild";
22import {ColumnGroup} from "./entities/columnGroup";
23import {GridApi} from "./gridApi";
24import {ClientSideRowModel} from "./rowModels/clientSide/clientSideRowModel";
25import {PinnedRowModel} from "./rowModels/pinnedRowModel";
26
27/**
28 * This interface works in conjuction with the GridSerializer. When serializing a grid, an instance that implements this interface
29 * must be passed in, the serializer will call back to the provided methods and finally call to parse to obtain the final result
30 * of the serialization.
31 *
32 * The lifecycle of a serializer with a GridSerializingSession is as follows.
33 *
34 * --1 Call to prepare method. An opportunity to do any required work before the call to accumulate data for the rows are about to happen.
35 * --2 Call to the row methods as the serializer loops through the different rows of the grid will call these methods so that the data
36 * can be accumulated. The methods. if there is relevant data will be called in the following order:
37 * a) addCustomHeader
38 * b) onNewHeaderGroupingRow
39 * c) onNewHeader
40 * d) onNewBodyRow
41 * e) addCustomFooter
42 * IF ANY OF THIS METHODS RETURN A ROW ACCUMULATOR, YOU CAN EXPECT THE SERIALIZER TO CALL ON THAT ACCUMULATOR WITH THE DATA FOR THAT ROW
43 * IMMEDIATELY AFTER IT HAS RECEIVED THE OBJECT AND BEFORE IT CALLS YOU TO OBTAIN A NEW ROW ACCUMULATOR
44 * --3 Call to parse method. This method is the last one to be called and is expected to return whatever accumulated
45 * parsed string is to be returned as a result of the serialization
46 *
47 * This interface is closely related to the RowAccumulator and RowSpanningAccumulator interfaces as every time a new row is about
48 * to be created a new instances of RowAccumulator or RowSpanningAccumulator need to be provided.
49
50 */
51
52export interface GridSerializingSession<T> {
53 /**
54 * INITIAL METHOD
55 */
56 prepare(columnsToExport: Column[]) : void;
57
58
59 /**
60 * ROW METHODS
61 */
62 addCustomHeader(customHeader: T): void;
63
64 onNewHeaderGroupingRow ():RowSpanningAccumulator;
65
66 onNewHeaderRow (): RowAccumulator;
67
68 onNewBodyRow (): RowAccumulator;
69
70 addCustomFooter(customFooter: T): void;
71
72 /**
73 * FINAL RESULT
74 */
75 parse (): string;
76}
77
78export interface RowAccumulator {
79 onColumn(column: Column, index: number, node?:RowNode):void;
80}
81
82export interface RowSpanningAccumulator {
83 onColumn(header: string, index: number, span:number):void;
84}
85
86export abstract class BaseGridSerializingSession<T> implements GridSerializingSession<T>{
87 constructor(
88 public columnController:ColumnController,
89 public valueService:ValueService,
90 public gridOptionsWrapper:GridOptionsWrapper,
91 public processCellCallback?:(params: ProcessCellForExportParams)=>string,
92 public processHeaderCallback?:(params: ProcessHeaderForExportParams)=>string,
93 public cellAndHeaderEscaper?:(rawValue:string)=>string
94 ){}
95
96 abstract prepare(columnsToExport: Column[]) : void;
97
98 abstract addCustomHeader(customHeader: T): void;
99
100 abstract addCustomFooter(customFooter: T): void;
101
102 abstract onNewHeaderGroupingRow (): RowSpanningAccumulator;
103
104 abstract onNewHeaderRow (): RowAccumulator;
105
106 abstract onNewBodyRow (): RowAccumulator;
107
108 abstract parse (): string;
109
110 public extractHeaderValue(column: Column): string {
111 let nameForCol = this.getHeaderName(this.processHeaderCallback, column);
112 if (nameForCol === null || nameForCol === undefined) {
113 nameForCol = '';
114 }
115 return this.cellAndHeaderEscaper? this.cellAndHeaderEscaper(nameForCol) : nameForCol;
116 }
117
118 public extractRowCellValue (column: Column, index: number, type: string, node?:RowNode){
119 let isRowGrouping = this.columnController.getRowGroupColumns().length > 0;
120
121 let valueForCell: any;
122 if (node.group && isRowGrouping && index === 0) {
123 valueForCell = this.createValueForGroupNode(node);
124 } else {
125 valueForCell = this.valueService.getValue(column, node);
126 }
127 valueForCell = this.processCell(node, column, valueForCell, this.processCellCallback, type);
128 if (valueForCell === null || valueForCell === undefined) {
129 valueForCell = '';
130 }
131
132 return this.cellAndHeaderEscaper? this.cellAndHeaderEscaper(valueForCell) : valueForCell;
133 }
134
135 private getHeaderName(callback: (params: ProcessHeaderForExportParams)=>string, column: Column): string {
136 if (callback) {
137 return callback({
138 column: column,
139 api: this.gridOptionsWrapper.getApi(),
140 columnApi: this.gridOptionsWrapper.getColumnApi(),
141 context: this.gridOptionsWrapper.getContext()
142 });
143 } else {
144 return this.columnController.getDisplayNameForColumn(column, 'csv', true);
145 }
146 }
147
148
149 private createValueForGroupNode(node: RowNode): string {
150 let keys = [node.key];
151 while (node.parent) {
152 node = node.parent;
153 keys.push(node.key);
154 }
155 return keys.reverse().join(' -> ');
156 }
157
158 private processCell(rowNode: RowNode, column: Column, value: any, processCellCallback:(params: ProcessCellForExportParams)=>string, type: string): any {
159 if (processCellCallback) {
160 return processCellCallback({
161 column: column,
162 node: rowNode,
163 value: value,
164 api: this.gridOptionsWrapper.getApi(),
165 columnApi: this.gridOptionsWrapper.getColumnApi(),
166 context: this.gridOptionsWrapper.getContext(),
167 type: type
168 });
169 } else {
170 return value;
171 }
172 }
173}
174
175
176
177@Bean("gridSerializer")
178export class GridSerializer {
179 @Autowired('displayedGroupCreator') private displayedGroupCreator: DisplayedGroupCreator;
180 @Autowired('columnController') private columnController: ColumnController;
181 @Autowired('rowModel') private rowModel: IRowModel;
182 @Autowired('pinnedRowModel') private pinnedRowModel: PinnedRowModel;
183 @Autowired('selectionController') private selectionController: SelectionController;
184 @Autowired('balancedColumnTreeBuilder') private balancedColumnTreeBuilder: BalancedColumnTreeBuilder;
185 @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
186
187 public serialize<T>(gridSerializingSession: GridSerializingSession<T>, params?: ExportParams<T>): string {
188
189 let dontSkipRows= (): boolean =>false;
190
191 let skipGroups = params && params.skipGroups;
192 let skipHeader = params && params.skipHeader;
193 let columnGroups = params && params.columnGroups;
194 let skipFooters = params && params.skipFooters;
195 let skipPinnedTop = params && params.skipPinnedTop;
196 let skipPinnedBottom = params && params.skipPinnedBottom;
197 let includeCustomHeader = params && params.customHeader;
198 let includeCustomFooter = params && params.customFooter;
199 let allColumns = params && params.allColumns;
200 let onlySelected = params && params.onlySelected;
201 let columnKeys = params && params.columnKeys;
202 let onlySelectedAllPages = params && params.onlySelectedAllPages;
203 let rowSkipper:(params: ShouldRowBeSkippedParams)=> boolean = (params && params.shouldRowBeSkipped) || dontSkipRows;
204 let api:GridApi = this.gridOptionsWrapper.getApi();
205 let context:any = this.gridOptionsWrapper.getContext();
206
207 // when in pivot mode, we always render cols on screen, never 'all columns'
208 let isPivotMode = this.columnController.isPivotMode();
209 let rowModelNormal = this.rowModel.getType() === Constants.ROW_MODEL_TYPE_CLIENT_SIDE;
210
211 let onlySelectedNonStandardModel = !rowModelNormal && onlySelected;
212
213
214 let columnsToExport: Column[];
215 if (_.existsAndNotEmpty(columnKeys)) {
216 columnsToExport = this.columnController.getGridColumns(columnKeys);
217 } else if (allColumns && !isPivotMode) {
218 columnsToExport = this.columnController.getAllPrimaryColumns();
219 } else {
220 columnsToExport = this.columnController.getAllDisplayedColumns();
221 }
222
223 if (!columnsToExport || columnsToExport.length === 0) {
224 return '';
225 }
226
227 gridSerializingSession.prepare(columnsToExport);
228
229 if (includeCustomHeader) {
230 gridSerializingSession.addCustomHeader (params.customHeader);
231 }
232
233 // first pass, put in the header names of the cols
234 if (columnGroups) {
235 let groupInstanceIdCreator: GroupInstanceIdCreator = new GroupInstanceIdCreator();
236 let displayedGroups: ColumnGroupChild[] = this.displayedGroupCreator.createDisplayedGroups(
237 columnsToExport,
238 this.columnController.getGridBalancedTree(),
239 groupInstanceIdCreator
240 );
241 this.recursivelyAddHeaderGroups(displayedGroups, gridSerializingSession);
242 }
243
244 if (!skipHeader){
245 let gridRowIterator = gridSerializingSession.onNewHeaderRow();
246 columnsToExport.forEach((column, index)=>{
247 gridRowIterator.onColumn (column, index, null)
248 });
249 }
250
251 this.pinnedRowModel.forEachPinnedTopRow(processRow);
252
253 if (isPivotMode) {
254 if ((<any>this.rowModel).forEachPivotNode){
255 (<ClientSideRowModel>this.rowModel).forEachPivotNode(processRow);
256 } else{
257 //Must be enterprise, so we can just loop through all the nodes
258 this.rowModel.forEachNode(processRow);
259 }
260 } else {
261 // onlySelectedAllPages: user doing pagination and wants selected items from
262 // other pages, so cannot use the standard row model as it won't have rows from
263 // other pages.
264 // onlySelectedNonStandardModel: if user wants selected in non standard row model
265 // (eg viewport) then again rowmodel cannot be used, so need to use selected instead.
266 if (onlySelectedAllPages || onlySelectedNonStandardModel) {
267 let selectedNodes = this.selectionController.getSelectedNodes();
268 selectedNodes.forEach((node:RowNode)=>{
269 processRow(node)
270 });
271 } else {
272 // here is everything else - including standard row model and selected. we don't use
273 // the selection model even when just using selected, so that the result is the order
274 // of the rows appearing on the screen.
275 if (rowModelNormal){
276 (<ClientSideRowModel>this.rowModel).forEachNodeAfterFilterAndSort(processRow);
277 } else {
278 this.rowModel.forEachNode(processRow);
279 }
280 }
281 }
282
283 this.pinnedRowModel.forEachPinnedBottomRow(processRow);
284
285 if (includeCustomFooter) {
286 gridSerializingSession.addCustomFooter (params.customFooter);
287 }
288
289 function processRow(node: RowNode): void {
290 if (skipGroups && node.group) {
291 return;
292 }
293
294 if (skipFooters && node.footer) {
295 return;
296 }
297
298 if (onlySelected && !node.isSelected()) {
299 return;
300 }
301
302 if (skipPinnedTop && node.rowPinned === 'top') {
303 return;
304 }
305
306 if (skipPinnedBottom && node.rowPinned === 'bottom') {
307 return;
308 }
309
310 // if we are in pivotMode, then the grid will show the root node only
311 // if it's not a leaf group
312 let nodeIsRootNode = node.level === -1;
313 if (nodeIsRootNode && !node.leafGroup) {
314 return;
315 }
316
317 let shouldRowBeSkipped:boolean = rowSkipper({
318 node: node,
319 api: api,
320 context: context
321 });
322
323 if (shouldRowBeSkipped) return;
324
325 let rowAccumulator: RowAccumulator = gridSerializingSession.onNewBodyRow();
326 columnsToExport.forEach((column: Column, index: number) => {
327 rowAccumulator.onColumn(column, index, node);
328 });
329 }
330
331 return gridSerializingSession.parse();
332 }
333
334 recursivelyAddHeaderGroups<T> (displayedGroups:ColumnGroupChild[], gridSerializingSession:GridSerializingSession<T>):void{
335 let directChildrenHeaderGroups:ColumnGroupChild[] = [];
336 displayedGroups.forEach((columnGroupChild: ColumnGroupChild) => {
337 let columnGroup: ColumnGroup = columnGroupChild as ColumnGroup;
338 if (!columnGroup.getChildren) return;
339 columnGroup.getChildren().forEach(it=>directChildrenHeaderGroups.push(it));
340 });
341
342 if (displayedGroups.length > 0 && displayedGroups[0] instanceof ColumnGroup) {
343 this.doAddHeaderHeader(gridSerializingSession, displayedGroups);
344 }
345
346 if (directChildrenHeaderGroups && directChildrenHeaderGroups.length > 0){
347 this.recursivelyAddHeaderGroups(directChildrenHeaderGroups, gridSerializingSession);
348 }
349 }
350
351 private doAddHeaderHeader<T>(gridSerializingSession: GridSerializingSession<T>, displayedGroups: ColumnGroupChild[]) {
352 let gridRowIterator: RowSpanningAccumulator = gridSerializingSession.onNewHeaderGroupingRow();
353 let columnIndex: number = 0;
354 displayedGroups.forEach((columnGroupChild: ColumnGroupChild) => {
355 let columnGroup: ColumnGroup = columnGroupChild as ColumnGroup;
356 let colDef = columnGroup.getDefinition();
357
358 let columnName = this.columnController.getDisplayNameForColumnGroup(columnGroup, 'header');
359 gridRowIterator.onColumn(columnName, columnIndex++, columnGroup.getLeafColumns().length - 1);
360 });
361 }
362}
363
364export enum RowType {
365 HEADER_GROUPING, HEADER, BODY
366}
\No newline at end of file