UNPKG

55.6 kBPlain TextView Raw
1import {_} from "../utils";
2import {CellComp} from "./cellComp";
3import {CellChangedEvent, DataChangedEvent, RowNode} from "../entities/rowNode";
4import {GridOptionsWrapper} from "../gridOptionsWrapper";
5import {Column} from "../entities/column";
6import {
7 Events,
8 RowClickedEvent,
9 RowDoubleClickedEvent,
10 RowEditingStartedEvent,
11 RowEditingStoppedEvent,
12 RowEvent,
13 RowValueChangedEvent,
14 VirtualRowRemovedEvent
15} from "../events";
16import {Autowired} from "../context/context";
17import {ICellRendererComp, ICellRendererParams} from "./cellRenderers/iCellRenderer";
18import {RowContainerComponent} from "./rowContainerComponent";
19import {Component} from "../widgets/component";
20import {RefSelector} from "../widgets/componentAnnotations";
21import {Beans} from "./beans";
22import {ProcessRowParams} from "../entities/gridOptions";
23
24export class LoadingCellRenderer extends Component {
25
26 private static TEMPLATE =
27 `<div class="ag-stub-cell">
28 <span class="ag-loading-icon" ref="eLoadingIcon"></span>
29 <span class="ag-loading-text" ref="eLoadingText"></span>
30 </div>`;
31
32 @Autowired('gridOptionsWrapper') gridOptionsWrapper: GridOptionsWrapper;
33
34 @RefSelector('eLoadingIcon') private eLoadingIcon: HTMLElement;
35 @RefSelector('eLoadingText') private eLoadingText: HTMLElement;
36
37 constructor() {
38 super(LoadingCellRenderer.TEMPLATE);
39 }
40
41 public init(params: ICellRendererParams): void {
42 let eLoadingIcon = _.createIconNoSpan('groupLoading', this.gridOptionsWrapper, null);
43 this.eLoadingIcon.appendChild(eLoadingIcon);
44
45 let localeTextFunc = this.gridOptionsWrapper.getLocaleTextFunc();
46 this.eLoadingText.innerText = localeTextFunc('loadingOoo', 'Loading');
47 }
48
49 public refresh(params: any): boolean {
50 return false;
51 }
52}
53
54export class RowComp extends Component {
55
56 public static DOM_DATA_KEY_RENDERED_ROW = 'renderedRow';
57
58 private static FULL_WIDTH_CELL_RENDERER = 'fullWidthCellRenderer';
59
60 private static GROUP_ROW_RENDERER = 'groupRowRenderer';
61 private static GROUP_ROW_RENDERER_COMP_NAME = 'agGroupRowRenderer';
62
63 private static LOADING_CELL_RENDERER = 'loadingCellRenderer';
64 private static LOADING_CELL_RENDERER_COMP_NAME = 'agLoadingCellRenderer';
65
66 private static DETAIL_CELL_RENDERER = 'detailCellRenderer';
67 private static DETAIL_CELL_RENDERER_COMP_NAME = 'agDetailCellRenderer';
68
69 private rowNode: RowNode;
70
71 private beans: Beans;
72
73 private ePinnedLeftRow: HTMLElement;
74 private ePinnedRightRow: HTMLElement;
75 private eBodyRow: HTMLElement;
76 private eAllRowContainers: HTMLElement[] = [];
77
78 private eFullWidthRow: HTMLElement;
79 private eFullWidthRowBody: HTMLElement;
80 private eFullWidthRowLeft: HTMLElement;
81 private eFullWidthRowRight: HTMLElement;
82
83 private bodyContainerComp: RowContainerComponent;
84 private fullWidthContainerComp: RowContainerComponent;
85 private pinnedLeftContainerComp: RowContainerComponent;
86 private pinnedRightContainerComp: RowContainerComponent;
87
88 private fullWidthRowComponent: ICellRendererComp;
89 private fullWidthRowComponentBody: ICellRendererComp;
90 private fullWidthRowComponentLeft: ICellRendererComp;
91 private fullWidthRowComponentRight: ICellRendererComp;
92
93 private active = true;
94
95 private fullWidthRow: boolean;
96 private fullWidthRowEmbedded: boolean;
97
98 private editingRow: boolean;
99 private rowFocused: boolean;
100
101 private columnRefreshPending = false;
102
103 private cellComps: {[key: string]: CellComp} = {};
104
105 // for animations, there are bits we want done in the next VM turn, to all DOM to update first.
106 // instead of each row doing a setTimeout(func,0), we put the functions here and the rowRenderer
107 // executes them all in one timeout
108 private createSecondPassFuncs: Function[] = [];
109
110 // these get called before the row is destroyed - they set up the DOM for the remove animation (ie they
111 // set the DOM up for the animation), then the delayedDestroyFunctions get called when the animation is
112 // complete (ie removes from the dom).
113 private removeFirstPassFuncs: Function[] = [];
114
115 // for animations, these functions get called 400ms after the row is cleared, called by the rowRenderer
116 // so each row isn't setting up it's own timeout
117 private removeSecondPassFuncs: Function[] = [];
118
119 private fadeRowIn: boolean;
120 private slideRowIn: boolean;
121 private useAnimationFrameForCreate: boolean;
122
123 private rowIsEven: boolean;
124
125 private paginationPage: number;
126
127 private parentScope: any;
128 private scope: any;
129
130 private initialised = false;
131
132 constructor(parentScope: any,
133 bodyContainerComp: RowContainerComponent,
134 pinnedLeftContainerComp: RowContainerComponent,
135 pinnedRightContainerComp: RowContainerComponent,
136 fullWidthContainerComp: RowContainerComponent,
137 rowNode: RowNode,
138 beans: Beans,
139 animateIn: boolean,
140 useAnimationFrameForCreate: boolean) {
141 super();
142 this.parentScope = parentScope;
143 this.beans = beans;
144 this.bodyContainerComp = bodyContainerComp;
145 this.pinnedLeftContainerComp = pinnedLeftContainerComp;
146 this.pinnedRightContainerComp = pinnedRightContainerComp;
147 this.fullWidthContainerComp = fullWidthContainerComp;
148 this.rowNode = rowNode;
149 this.rowIsEven = this.rowNode.rowIndex % 2 === 0;
150 this.paginationPage = this.beans.paginationProxy.getCurrentPage();
151 this.useAnimationFrameForCreate = useAnimationFrameForCreate;
152
153 this.setAnimateFlags(animateIn);
154 }
155
156 public init(): void {
157 this.rowFocused = this.beans.focusedCellController.isRowFocused(this.rowNode.rowIndex, this.rowNode.rowPinned);
158
159 this.scope = this.createChildScopeOrNull(this.rowNode.data);
160
161 this.setupRowContainers();
162
163 this.addListeners();
164
165 if (this.slideRowIn) {
166 this.createSecondPassFuncs.push( () => {
167 this.onTopChanged();
168 });
169 }
170 if (this.fadeRowIn) {
171 this.createSecondPassFuncs.push( () => {
172 this.eAllRowContainers.forEach(eRow => _.removeCssClass(eRow, 'ag-opacity-zero'));
173 });
174 }
175 }
176
177 private createTemplate(contents: string, extraCssClass: string = null): string {
178 let templateParts: string[] = [];
179
180 let rowHeight = this.rowNode.rowHeight;
181 let rowClasses = this.getInitialRowClasses(extraCssClass).join(' ');
182
183 let rowIdSanitised = _.escape(this.rowNode.id);
184
185 let userRowStyles = this.preProcessStylesFromGridOptions();
186
187 let businessKey = this.getRowBusinessKey();
188 let businessKeySanitised = _.escape(businessKey);
189
190 let rowTopStyle = this.getInitialRowTopStyle();
191
192 templateParts.push(`<div`);
193 templateParts.push(` role="row"`);
194 templateParts.push(` row-index="${this.rowNode.getRowIndexString()}"`);
195 templateParts.push(rowIdSanitised ? ` row-id="${rowIdSanitised}"` : ``);
196 templateParts.push(businessKey ? ` row-business-key="${businessKeySanitised}"` : ``);
197 templateParts.push(` comp-id="${this.getCompId()}"`);
198 templateParts.push(` class="${rowClasses}"`);
199 templateParts.push(` style="height: ${rowHeight}px; ${rowTopStyle} ${userRowStyles}">`);
200
201 // add in the template for the cells
202 templateParts.push(contents);
203
204 templateParts.push(`</div>`);
205
206 return templateParts.join('');
207 }
208
209 public getCellForCol(column: Column): HTMLElement {
210 let cellComp = this.cellComps[column.getColId()];
211 if (cellComp) {
212 return cellComp.getGui();
213 } else {
214 return null;
215 }
216 }
217
218 public afterFlush(): void {
219 if (!this.initialised) {
220 this.initialised = true;
221 this.executeProcessRowPostCreateFunc();
222 }
223 }
224
225 private executeProcessRowPostCreateFunc(): void {
226 let func = this.beans.gridOptionsWrapper.getProcessRowPostCreateFunc();
227 if (func) {
228 let params: ProcessRowParams = {
229 eRow: this.eBodyRow,
230 ePinnedLeftRow: this.ePinnedLeftRow,
231 ePinnedRightRow: this.ePinnedRightRow,
232 node: this.rowNode,
233 api: this.beans.gridOptionsWrapper.getApi(),
234 rowIndex: this.rowNode.rowIndex,
235 addRenderedRowListener: this.addEventListener.bind(this),
236 columnApi: this.beans.gridOptionsWrapper.getColumnApi(),
237 context: this.beans.gridOptionsWrapper.getContext()
238 };
239 func(params);
240 }
241 }
242
243 private getInitialRowTopStyle() {
244 // if sliding in, we take the old row top. otherwise we just set the current row top.
245 let pixels = this.slideRowIn ? this.roundRowTopToBounds(this.rowNode.oldRowTop) : this.rowNode.rowTop;
246 let afterPaginationPixels = this.applyPaginationOffset(pixels);
247 let afterScalingPixels = this.beans.heightScaler.getRealPixelPosition(afterPaginationPixels);
248
249 if (this.beans.gridOptionsWrapper.isSuppressRowTransform()) {
250 return `top: ${afterScalingPixels}px; `;
251 } else {
252 return `transform: translateY(${afterScalingPixels}px); `;
253 }
254 }
255
256 private getRowBusinessKey(): string {
257 if (typeof this.beans.gridOptionsWrapper.getBusinessKeyForNodeFunc() === 'function') {
258 let businessKey = this.beans.gridOptionsWrapper.getBusinessKeyForNodeFunc()(this.rowNode);
259 return businessKey;
260 }
261 }
262
263 private lazyCreateCells(cols: Column[], eRow: HTMLElement): void {
264 if (this.active) {
265 let cellTemplatesAndComps = this.createCells(cols);
266 eRow.innerHTML = cellTemplatesAndComps.template;
267 this.callAfterRowAttachedOnCells(cellTemplatesAndComps.cellComps, eRow);
268 }
269 }
270
271 private createRowContainer(rowContainerComp: RowContainerComponent, cols: Column[],
272 callback: (eRow: HTMLElement) => void): void {
273
274 let cellTemplatesAndComps: {template: string, cellComps: CellComp[]};
275 if (this.useAnimationFrameForCreate) {
276 cellTemplatesAndComps = {cellComps: [], template: ''};
277 } else {
278 cellTemplatesAndComps = this.createCells(cols);
279 }
280
281 let rowTemplate = this.createTemplate(cellTemplatesAndComps.template);
282 rowContainerComp.appendRowTemplate(rowTemplate, ()=> {
283 let eRow: HTMLElement = rowContainerComp.getRowElement(this.getCompId());
284 this.afterRowAttached(rowContainerComp, eRow);
285 callback(eRow);
286
287 if (this.useAnimationFrameForCreate) {
288 this.beans.taskQueue.addP1Task(this.lazyCreateCells.bind(this, cols, eRow));
289 } else {
290 this.callAfterRowAttachedOnCells(cellTemplatesAndComps.cellComps, eRow);
291 }
292 });
293 }
294
295 private createChildScopeOrNull(data: any) {
296 if (this.beans.gridOptionsWrapper.isAngularCompileRows()) {
297 let newChildScope = this.parentScope.$new();
298 newChildScope.data = data;
299 newChildScope.rowNode = this.rowNode;
300 newChildScope.context = this.beans.gridOptionsWrapper.getContext();
301
302 this.addDestroyFunc(() => {
303 newChildScope.$destroy();
304 newChildScope.data = null;
305 newChildScope.rowNode = null;
306 newChildScope.context = null;
307 });
308
309 return newChildScope;
310 } else {
311 return null;
312 }
313 }
314
315 private setupRowContainers(): void {
316
317 let isFullWidthCellFunc = this.beans.gridOptionsWrapper.getIsFullWidthCellFunc();
318 let isFullWidthCell = isFullWidthCellFunc ? isFullWidthCellFunc(this.rowNode) : false;
319
320 let isDetailCell = this.beans.doingMasterDetail && this.rowNode.detail;
321
322 let isGroupSpanningRow = this.rowNode.group && this.beans.gridOptionsWrapper.isGroupUseEntireRow();
323
324 if (this.rowNode.stub) {
325 this.createFullWidthRows(RowComp.LOADING_CELL_RENDERER, RowComp.LOADING_CELL_RENDERER_COMP_NAME);
326 } else if (isDetailCell) {
327 this.createFullWidthRows(RowComp.DETAIL_CELL_RENDERER, RowComp.DETAIL_CELL_RENDERER_COMP_NAME);
328 } else if (isFullWidthCell) {
329 this.createFullWidthRows(RowComp.FULL_WIDTH_CELL_RENDERER, null);
330 } else if (isGroupSpanningRow) {
331 this.createFullWidthRows(RowComp.GROUP_ROW_RENDERER, RowComp.GROUP_ROW_RENDERER_COMP_NAME);
332 } else {
333 this.setupNormalRowContainers();
334 }
335 }
336
337 private setupNormalRowContainers(): void {
338 let centerCols = this.beans.columnController.getAllDisplayedCenterVirtualColumnsForRow(this.rowNode);
339 this.createRowContainer(this.bodyContainerComp, centerCols, eRow => this.eBodyRow = eRow);
340
341 let leftCols = this.beans.columnController.getDisplayedLeftColumnsForRow(this.rowNode);
342 let rightCols = this.beans.columnController.getDisplayedRightColumnsForRow(this.rowNode);
343 this.createRowContainer(this.pinnedRightContainerComp, rightCols, eRow => this.ePinnedRightRow = eRow);
344 this.createRowContainer(this.pinnedLeftContainerComp, leftCols, eRow => this.ePinnedLeftRow = eRow);
345 }
346
347 private createFullWidthRows(type: string, name: string): void {
348
349 this.fullWidthRow = true;
350 this.fullWidthRowEmbedded = this.beans.gridOptionsWrapper.isEmbedFullWidthRows();
351
352 if (this.fullWidthRowEmbedded) {
353
354 this.createFullWidthRowContainer(this.bodyContainerComp, null,
355 null, type, name,
356 (eRow: HTMLElement) => {
357 this.eFullWidthRowBody = eRow;
358 },
359 (cellRenderer: ICellRendererComp) => {
360 this.fullWidthRowComponentBody = cellRenderer;
361 });
362 this.createFullWidthRowContainer(this.pinnedLeftContainerComp, Column.PINNED_LEFT,
363 'ag-cell-last-left-pinned', type, name,
364 (eRow: HTMLElement) => {
365 this.eFullWidthRowLeft = eRow;
366 },
367 (cellRenderer: ICellRendererComp) => {
368 this.fullWidthRowComponentLeft = cellRenderer;
369 });
370 this.createFullWidthRowContainer(this.pinnedRightContainerComp, Column.PINNED_RIGHT,
371 'ag-cell-first-right-pinned', type, name,
372 (eRow: HTMLElement) => {
373 this.eFullWidthRowRight = eRow;
374 },
375 (cellRenderer: ICellRendererComp) => {
376 this.fullWidthRowComponentRight = cellRenderer;
377 });
378
379 } else {
380
381 // otherwise we add to the fullWidth container as normal
382 // let previousFullWidth = ensureDomOrder ? this.lastPlacedElements.eFullWidth : null;
383 this.createFullWidthRowContainer(this.fullWidthContainerComp, null,
384 null, type, name,
385 (eRow: HTMLElement) => {
386 this.eFullWidthRow = eRow;
387 },
388 (cellRenderer: ICellRendererComp) => {
389 this.fullWidthRowComponent = cellRenderer;
390 });
391 }
392 }
393
394 private setAnimateFlags(animateIn: boolean): void {
395 if (animateIn) {
396 let oldRowTopExists = _.exists(this.rowNode.oldRowTop);
397 // if the row had a previous position, we slide it in (animate row top)
398 this.slideRowIn = oldRowTopExists;
399 // if the row had no previous position, we fade it in (animate
400 this.fadeRowIn = !oldRowTopExists;
401 } else {
402 this.slideRowIn = false;
403 this.fadeRowIn = false;
404 }
405 }
406
407 public isEditing(): boolean {
408 return this.editingRow;
409 }
410
411 public stopRowEditing(cancel: boolean): void {
412 this.stopEditing(cancel);
413 }
414
415 public isFullWidth(): boolean {
416 return this.fullWidthRow;
417 }
418
419 private addListeners(): void {
420 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_HEIGHT_CHANGED, this.onRowHeightChanged.bind(this));
421 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_ROW_SELECTED, this.onRowSelected.bind(this));
422 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_ROW_INDEX_CHANGED, this.onRowIndexChanged.bind(this));
423 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_TOP_CHANGED, this.onTopChanged.bind(this));
424 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_EXPANDED_CHANGED, this.onExpandedChanged.bind(this));
425 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_DATA_CHANGED, this.onRowNodeDataChanged.bind(this));
426 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_CELL_CHANGED, this.onRowNodeCellChanged.bind(this));
427 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_DRAGGING_CHANGED, this.onRowNodeDraggingChanged.bind(this));
428
429 let eventService = this.beans.eventService;
430 this.addDestroyableEventListener(eventService, Events.EVENT_HEIGHT_SCALE_CHANGED, this.onTopChanged.bind(this));
431 this.addDestroyableEventListener(eventService, Events.EVENT_DISPLAYED_COLUMNS_CHANGED, this.onDisplayedColumnsChanged.bind(this));
432 this.addDestroyableEventListener(eventService, Events.EVENT_VIRTUAL_COLUMNS_CHANGED, this.onVirtualColumnsChanged.bind(this));
433 this.addDestroyableEventListener(eventService, Events.EVENT_COLUMN_RESIZED, this.onColumnResized.bind(this));
434 this.addDestroyableEventListener(eventService, Events.EVENT_CELL_FOCUSED, this.onCellFocusChanged.bind(this));
435 this.addDestroyableEventListener(eventService, Events.EVENT_PAGINATION_CHANGED, this.onPaginationChanged.bind(this));
436 this.addDestroyableEventListener(eventService, Events.EVENT_GRID_COLUMNS_CHANGED, this.onGridColumnsChanged.bind(this));
437 }
438
439 // when grid columns change, then all cells should be cleaned out,
440 // as the new columns could have same id as the previous columns and may conflict
441 private onGridColumnsChanged(): void {
442 let allRenderedCellIds = Object.keys(this.cellComps);
443 this.removeRenderedCells(allRenderedCellIds);
444 }
445
446 private onRowNodeDataChanged(event: DataChangedEvent): void {
447 // if this is an update, we want to refresh, as this will allow the user to put in a transition
448 // into the cellRenderer refresh method. otherwise this might be completely new data, in which case
449 // we will want to completely replace the cells
450 this.forEachCellComp(cellComp =>
451 cellComp.refreshCell({
452 suppressFlash: !event.update,
453 newData: !event.update
454 })
455 );
456
457 // check for selected also, as this could be after lazy loading of the row data, in which case
458 // the id might of just gotten set inside the row and the row selected state may of changed
459 // as a result. this is what happens when selected rows are loaded in virtual pagination.
460 // - niall note - since moving to the stub component, this may no longer be true, as replacing
461 // the stub component now replaces the entire row
462 this.onRowSelected();
463
464 // as data has changed, then the style and class needs to be recomputed
465 this.postProcessCss();
466 }
467
468 private onRowNodeCellChanged(event: CellChangedEvent): void {
469 // as data has changed, then the style and class needs to be recomputed
470 this.postProcessCss();
471 }
472
473 private postProcessCss(): void {
474 this.postProcessStylesFromGridOptions();
475 this.postProcessClassesFromGridOptions();
476 this.postProcessRowClassRules();
477 this.postProcessRowDragging();
478 }
479
480 private onRowNodeDraggingChanged(): void {
481 this.postProcessRowDragging();
482 }
483
484 private postProcessRowDragging(): void {
485 let dragging = this.rowNode.dragging;
486 this.eAllRowContainers.forEach( (row) => _.addOrRemoveCssClass(row, 'ag-row-dragging', dragging) );
487 }
488
489 private onExpandedChanged(): void {
490 if (this.rowNode.group && !this.rowNode.footer) {
491 let expanded = this.rowNode.expanded;
492 this.eAllRowContainers.forEach( row => _.addOrRemoveCssClass(row, 'ag-row-group-expanded', expanded));
493 this.eAllRowContainers.forEach( row => _.addOrRemoveCssClass(row, 'ag-row-group-contracted', !expanded));
494 }
495 }
496
497 private onDisplayedColumnsChanged(): void {
498 if (!this.fullWidthRow) {
499 this.refreshCells();
500 }
501 }
502
503 private destroyFullWidthComponents(): void {
504 if (this.fullWidthRowComponent) {
505 if (this.fullWidthRowComponent.destroy) {
506 this.fullWidthRowComponent.destroy();
507 }
508 this.fullWidthRowComponent = null;
509 }
510 if (this.fullWidthRowComponentBody) {
511 if (this.fullWidthRowComponentBody.destroy) {
512 this.fullWidthRowComponentBody.destroy();
513 }
514 this.fullWidthRowComponent = null;
515 }
516 if (this.fullWidthRowComponentLeft) {
517 if (this.fullWidthRowComponentLeft.destroy) {
518 this.fullWidthRowComponentLeft.destroy();
519 }
520 this.fullWidthRowComponentLeft = null;
521 }
522 if (this.fullWidthRowComponentRight) {
523 if (this.fullWidthRowComponentRight.destroy) {
524 this.fullWidthRowComponentRight.destroy();
525 }
526 this.fullWidthRowComponent = null;
527 }
528 }
529
530 private getContainerForCell(pinnedType: string): HTMLElement {
531 switch (pinnedType) {
532 case Column.PINNED_LEFT: return this.ePinnedLeftRow;
533 case Column.PINNED_RIGHT: return this.ePinnedRightRow;
534 default: return this.eBodyRow;
535 }
536 }
537
538 private onVirtualColumnsChanged(): void {
539 if (!this.fullWidthRow) {
540 this.refreshCells();
541 }
542 }
543
544 private onColumnResized(): void {
545 if (!this.fullWidthRow) {
546 this.refreshCells();
547 }
548 }
549
550 private refreshCells() {
551 if (this.beans.gridOptionsWrapper.isSuppressAnimationFrame()) {
552 this.refreshCellsInAnimationFrame();
553 } else {
554 if (this.columnRefreshPending) { return; }
555 this.beans.taskQueue.addP1Task(this.refreshCellsInAnimationFrame.bind(this));
556 }
557 }
558
559 private refreshCellsInAnimationFrame() {
560
561 if (!this.active) { return; }
562 this.columnRefreshPending = false;
563
564 let centerCols = this.beans.columnController.getAllDisplayedCenterVirtualColumnsForRow(this.rowNode);
565 let leftCols = this.beans.columnController.getDisplayedLeftColumnsForRow(this.rowNode);
566 let rightCols = this.beans.columnController.getDisplayedRightColumnsForRow(this.rowNode);
567
568 this.insertCellsIntoContainer(this.eBodyRow, centerCols);
569 this.insertCellsIntoContainer(this.ePinnedLeftRow, leftCols);
570 this.insertCellsIntoContainer(this.ePinnedRightRow, rightCols);
571
572 let colIdsToRemove = Object.keys(this.cellComps);
573 centerCols.forEach( (col: Column) => _.removeFromArray(colIdsToRemove, col.getId()));
574 leftCols.forEach( (col: Column) => _.removeFromArray(colIdsToRemove, col.getId()));
575 rightCols.forEach( (col: Column) => _.removeFromArray(colIdsToRemove, col.getId()));
576
577 // we never remove editing cells, as this would cause the cells to loose their values while editing
578 // as the grid is scrolling horizontally.
579 colIdsToRemove = _.filter(colIdsToRemove, this.isCellEligibleToBeRemoved.bind(this));
580
581 // remove old cells from gui, but we don't destroy them, we might use them again
582 this.removeRenderedCells(colIdsToRemove);
583 }
584
585 private removeRenderedCells(colIds: string[]): void {
586 colIds.forEach( (key: string)=> {
587 let cellComp = this.cellComps[key];
588 // could be old reference, ie removed cell
589 if (_.missing(cellComp)) { return; }
590
591 cellComp.detach();
592 cellComp.destroy();
593 this.cellComps[key] = null;
594 });
595 }
596
597 private isCellEligibleToBeRemoved(indexStr: string): boolean {
598 let displayedColumns = this.beans.columnController.getAllDisplayedColumns();
599
600 let REMOVE_CELL: boolean = true;
601 let KEEP_CELL: boolean = false;
602 let renderedCell = this.cellComps[indexStr];
603
604 if (!renderedCell) { return REMOVE_CELL; }
605
606 // always remove the cell if it's in the wrong pinned location
607 if (this.isCellInWrongRow(renderedCell)) { return REMOVE_CELL; }
608
609 // we want to try and keep editing and focused cells
610 let editing = renderedCell.isEditing();
611 let focused = this.beans.focusedCellController.isCellFocused(renderedCell.getGridCell());
612
613 let mightWantToKeepCell = editing || focused;
614
615 if (mightWantToKeepCell) {
616 let column = renderedCell.getColumn();
617 let cellStillDisplayed = displayedColumns.indexOf(column) >= 0;
618 return cellStillDisplayed ? KEEP_CELL : REMOVE_CELL;
619 } else {
620 return REMOVE_CELL;
621 }
622 }
623
624 private ensureCellInCorrectContainer(cellComp: CellComp): void {
625 let element = cellComp.getGui();
626 let column = cellComp.getColumn();
627 let pinnedType = column.getPinned();
628 let eContainer = this.getContainerForCell(pinnedType);
629
630 // if in wrong container, remove it
631 let eOldContainer = cellComp.getParentRow();
632 let inWrongRow = eOldContainer !== eContainer;
633 if (inWrongRow) {
634 // take out from old row
635 if (eOldContainer) {
636 eOldContainer.removeChild(element);
637 }
638
639 eContainer.appendChild(element);
640 cellComp.setParentRow(eContainer);
641 }
642 }
643
644 private isCellInWrongRow(cellComp: CellComp): boolean {
645 let column = cellComp.getColumn();
646 let rowWeWant = this.getContainerForCell(column.getPinned());
647
648 // if in wrong container, remove it
649 let oldRow = cellComp.getParentRow();
650 return oldRow !== rowWeWant;
651 }
652
653 private insertCellsIntoContainer(eRow: HTMLElement, cols: Column[]): void {
654 if (!eRow) { return; }
655
656 let cellTemplates: string[] = [];
657 let newCellComps: CellComp[] = [];
658
659 cols.forEach( col => {
660
661 let colId = col.getId();
662 let oldCell = this.cellComps[colId];
663
664 if (oldCell) {
665 this.ensureCellInCorrectContainer(oldCell);
666 } else {
667 this.createNewCell(col, eRow, cellTemplates, newCellComps);
668 }
669
670 });
671
672 if (cellTemplates.length>0) {
673 _.appendHtml(eRow, cellTemplates.join(''));
674 this.callAfterRowAttachedOnCells(newCellComps, eRow);
675 }
676 }
677
678 private addDomData(eRowContainer: Element): void {
679 let gow = this.beans.gridOptionsWrapper;
680 gow.setDomData(eRowContainer, RowComp.DOM_DATA_KEY_RENDERED_ROW, this);
681 this.addDestroyFunc( ()=> {
682 gow.setDomData(eRowContainer, RowComp.DOM_DATA_KEY_RENDERED_ROW, null); }
683 );
684 }
685
686 private createNewCell(col: Column, eContainer: HTMLElement, cellTemplates: string[], newCellComps: CellComp[]): void {
687 let newCellComp = new CellComp(this.scope, this.beans, col, this.rowNode, this, false);
688 let cellTemplate = newCellComp.getCreateTemplate();
689 cellTemplates.push(cellTemplate);
690 newCellComps.push(newCellComp);
691 this.cellComps[col.getId()] = newCellComp;
692 newCellComp.setParentRow(eContainer);
693 }
694
695 public onMouseEvent(eventName: string, mouseEvent: MouseEvent): void {
696 switch (eventName) {
697 case 'dblclick': this.onRowDblClick(mouseEvent); break;
698 case 'click': this.onRowClick(mouseEvent); break;
699 }
700 }
701
702 private createRowEvent(type: string, domEvent?: Event): RowEvent {
703 return {
704 type: type,
705 node: this.rowNode,
706 data: this.rowNode.data,
707 rowIndex: this.rowNode.rowIndex,
708 rowPinned: this.rowNode.rowPinned,
709 context: this.beans.gridOptionsWrapper.getContext(),
710 api: this.beans.gridOptionsWrapper.getApi(),
711 columnApi: this.beans.gridOptionsWrapper.getColumnApi(),
712 event: domEvent
713 };
714 }
715
716 private createRowEventWithSource(type: string, domEvent: Event): RowEvent {
717 let event = this.createRowEvent(type, domEvent);
718 // when first developing this, we included the rowComp in the event.
719 // this seems very weird. so when introducing the event types, i left the 'source'
720 // out of the type, and just include the source in the two places where this event
721 // was fired (rowClicked and rowDoubleClicked). it doesn't make sense for any
722 // users to be using this, as the rowComp isn't an object we expose, so would be
723 // very surprising if a user was using it.
724 (<any>event).source = this;
725 return event;
726 }
727
728 private onRowDblClick(mouseEvent: MouseEvent): void {
729 if (_.isStopPropagationForAgGrid(mouseEvent)) { return; }
730
731 let agEvent: RowDoubleClickedEvent = this.createRowEventWithSource(Events.EVENT_ROW_DOUBLE_CLICKED, mouseEvent);
732
733 this.beans.eventService.dispatchEvent(agEvent);
734 }
735
736 public onRowClick(mouseEvent: MouseEvent) {
737
738 let stop = _.isStopPropagationForAgGrid(mouseEvent);
739 if (stop) {
740 return;
741 }
742
743 let agEvent: RowClickedEvent = this.createRowEventWithSource(Events.EVENT_ROW_CLICKED, mouseEvent);
744
745 this.beans.eventService.dispatchEvent(agEvent);
746
747 // ctrlKey for windows, metaKey for Apple
748 let multiSelectKeyPressed = mouseEvent.ctrlKey || mouseEvent.metaKey;
749
750 let shiftKeyPressed = mouseEvent.shiftKey;
751
752 // we do not allow selecting groups by clicking (as the click here expands the group)
753 // so return if it's a group row
754 if (this.rowNode.group) {
755 return;
756 }
757
758 // we also don't allow selection of pinned rows
759 if (this.rowNode.rowPinned) {
760 return;
761 }
762
763 // if no selection method enabled, do nothing
764 if (!this.beans.gridOptionsWrapper.isRowSelection()) {
765 return;
766 }
767
768 // if click selection suppressed, do nothing
769 if (this.beans.gridOptionsWrapper.isSuppressRowClickSelection()) {
770 return;
771 }
772
773 let multiSelectOnClick = this.beans.gridOptionsWrapper.isRowMultiSelectWithClick();
774 let rowDeselectionWithCtrl = this.beans.gridOptionsWrapper.isRowDeselection();
775
776 if (this.rowNode.isSelected()) {
777
778 if (multiSelectOnClick) {
779 this.rowNode.setSelectedParams({newValue: false});
780 } else if (multiSelectKeyPressed) {
781 if (rowDeselectionWithCtrl) {
782 this.rowNode.setSelectedParams({newValue: false});
783 }
784 } else {
785 // selected with no multi key, must make sure anything else is unselected
786 this.rowNode.setSelectedParams({newValue: true, clearSelection: true});
787 }
788 } else {
789 let clearSelection = multiSelectOnClick ? false : !multiSelectKeyPressed;
790 this.rowNode.setSelectedParams({newValue: true, clearSelection: clearSelection, rangeSelect: shiftKeyPressed});
791 }
792 }
793
794 private createFullWidthRowContainer(rowContainerComp: RowContainerComponent, pinned: string,
795 extraCssClass: string, cellRendererType: string, cellRendererName: string,
796 eRowCallback: (eRow: HTMLElement) => void,
797 cellRendererCallback: (comp: ICellRendererComp) => void): void {
798
799 let rowTemplate = this.createTemplate('', extraCssClass);
800 rowContainerComp.appendRowTemplate(rowTemplate, ()=> {
801
802 let eRow: HTMLElement = rowContainerComp.getRowElement(this.getCompId());
803
804 let params = this.createFullWidthParams(eRow, pinned);
805
806 let callback = (cellRenderer: ICellRendererComp)=> {
807 if (this.isAlive()) {
808 let gui = cellRenderer.getGui();
809 eRow.appendChild(gui);
810 cellRendererCallback(cellRenderer);
811 } else {
812 if (cellRenderer.destroy) {
813 cellRenderer.destroy();
814 }
815 }
816 };
817
818 this.beans.componentResolver.createAgGridComponent<ICellRendererComp>(null, params, cellRendererType, params, cellRendererName).then(callback);
819
820 this.afterRowAttached(rowContainerComp, eRow);
821 eRowCallback(eRow);
822
823 this.angular1Compile(eRow);
824 });
825 }
826
827 private angular1Compile(element: Element): void {
828 if (this.scope) {
829 this.beans.$compile(element)(this.scope);
830 }
831 }
832
833 private createFullWidthParams(eRow: HTMLElement, pinned: string): any {
834 let params = {
835 fullWidth: true,
836 data: this.rowNode.data,
837 node: this.rowNode,
838 value: this.rowNode.key,
839 $scope: this.scope,
840 rowIndex: this.rowNode.rowIndex,
841 api: this.beans.gridOptionsWrapper.getApi(),
842 columnApi: this.beans.gridOptionsWrapper.getColumnApi(),
843 context: this.beans.gridOptionsWrapper.getContext(),
844 // these need to be taken out, as part of 'afterAttached' now
845 eGridCell: eRow,
846 eParentOfValue: eRow,
847 pinned: pinned,
848 addRenderedRowListener: this.addEventListener.bind(this)
849 };
850
851 return params;
852 }
853
854 private getInitialRowClasses(extraCssClass: string): string[] {
855 let classes: string[] = [];
856
857 if (_.exists(extraCssClass)) {
858 classes.push(extraCssClass);
859 }
860
861 classes.push('ag-row');
862 classes.push(this.rowFocused ? 'ag-row-focus' : 'ag-row-no-focus');
863
864 if (this.fadeRowIn) {
865 classes.push('ag-opacity-zero');
866 }
867
868 if (this.rowIsEven) {
869 classes.push('ag-row-even');
870 } else {
871 classes.push('ag-row-odd');
872 }
873
874 if (this.rowNode.isSelected()) {
875 classes.push('ag-row-selected');
876 }
877
878 if (this.rowNode.group) {
879 classes.push('ag-row-group');
880 // if a group, put the level of the group in
881 classes.push('ag-row-level-' + this.rowNode.level);
882
883 if (this.rowNode.footer) {
884 classes.push('ag-row-footer');
885 }
886 } else {
887 // if a leaf, and a parent exists, put a level of the parent, else put level of 0 for top level item
888 if (this.rowNode.parent) {
889 classes.push('ag-row-level-' + (this.rowNode.parent.level + 1));
890 } else {
891 classes.push('ag-row-level-0');
892 }
893 }
894
895 if (this.rowNode.stub) {
896 classes.push('ag-row-stub');
897 }
898
899 if (this.fullWidthRow) {
900 classes.push('ag-full-width-row');
901 }
902
903 if (this.rowNode.group && !this.rowNode.footer) {
904 classes.push(this.rowNode.expanded ? 'ag-row-group-expanded' : 'ag-row-group-contracted');
905 }
906
907 if (this.rowNode.dragging) {
908 classes.push('ag-row-dragging');
909 }
910
911 _.pushAll(classes, this.processClassesFromGridOptions());
912 _.pushAll(classes, this.preProcessRowClassRules());
913
914 return classes;
915 }
916
917 private preProcessRowClassRules(): string[] {
918 let res: string[] = [];
919
920 this.processRowClassRules(
921 (className: string)=> {
922 res.push(className);
923 },
924 (className: string)=> {
925 // not catered for, if creating, no need
926 // to remove class as it was never there
927 }
928 );
929
930 return res;
931 }
932
933 private processRowClassRules(onApplicableClass: (className: string)=>void, onNotApplicableClass?: (className: string)=>void): void {
934 this.beans.stylingService.processClassRules(
935 this.beans.gridOptionsWrapper.rowClassRules(),
936 {
937 value: undefined,
938 colDef:undefined,
939 data: this.rowNode.data,
940 node: this.rowNode,
941 rowIndex: this.rowNode.rowIndex,
942 api: this.beans.gridOptionsWrapper.getApi(),
943 $scope: this.scope,
944 context: this.beans.gridOptionsWrapper.getContext()
945 }, onApplicableClass, onNotApplicableClass);
946 }
947
948 public stopEditing(cancel = false): void {
949 this.forEachCellComp(renderedCell => {
950 renderedCell.stopEditing(cancel);
951 });
952 if (this.editingRow) {
953 if (!cancel) {
954 let event: RowValueChangedEvent = this.createRowEvent(Events.EVENT_ROW_VALUE_CHANGED);
955 this.beans.eventService.dispatchEvent(event);
956 }
957 this.setEditingRow(false);
958 }
959 }
960
961 private setEditingRow(value: boolean): void {
962 this.editingRow = value;
963 this.eAllRowContainers.forEach( (row) => _.addOrRemoveCssClass(row, 'ag-row-editing', value) );
964
965 let event: RowEvent = value ?
966 <RowEditingStartedEvent> this.createRowEvent(Events.EVENT_ROW_EDITING_STARTED)
967 : <RowEditingStoppedEvent> this.createRowEvent(Events.EVENT_ROW_EDITING_STOPPED);
968
969 this.beans.eventService.dispatchEvent(event);
970 }
971
972 public startRowEditing(keyPress: number = null, charPress: string = null, sourceRenderedCell: CellComp = null): void {
973 // don't do it if already editing
974 if (this.editingRow) { return; }
975
976 this.forEachCellComp(renderedCell => {
977 let cellStartedEdit = renderedCell === sourceRenderedCell;
978 if (cellStartedEdit) {
979 renderedCell.startEditingIfEnabled(keyPress, charPress, cellStartedEdit);
980 } else {
981 renderedCell.startEditingIfEnabled(null, null, cellStartedEdit);
982 }
983 });
984 this.setEditingRow(true);
985 }
986
987 public forEachCellComp(callback: (renderedCell: CellComp)=>void): void {
988 _.iterateObject(this.cellComps, (key: any, cellComp: CellComp)=> {
989 if (cellComp) {
990 callback(cellComp);
991 }
992 });
993 }
994
995 private postProcessClassesFromGridOptions(): void {
996 let cssClasses = this.processClassesFromGridOptions();
997 if (cssClasses) {
998 cssClasses.forEach( (classStr: string) => {
999 this.eAllRowContainers.forEach( row => _.addCssClass(row, classStr));
1000 });
1001 }
1002 }
1003
1004 private postProcessRowClassRules(): void {
1005 this.processRowClassRules(
1006 (className: string)=> {
1007 this.eAllRowContainers.forEach( row => _.addCssClass(row, className));
1008 },
1009 (className: string)=> {
1010 this.eAllRowContainers.forEach( row => _.removeCssClass(row, className));
1011 }
1012 );
1013 }
1014
1015 private processClassesFromGridOptions(): string[] {
1016 let res: string[] = [];
1017
1018 let process = (rowClass: string | string[]) => {
1019 if (typeof rowClass === 'string') {
1020 res.push(rowClass);
1021 } else if (Array.isArray(rowClass)) {
1022 rowClass.forEach( e => res.push(e) );
1023 }
1024 };
1025
1026 // part 1 - rowClass
1027 let rowClass = this.beans.gridOptionsWrapper.getRowClass();
1028 if (rowClass) {
1029 if (typeof rowClass === 'function') {
1030 console.warn('ag-Grid: rowClass should not be a function, please use getRowClass instead');
1031 return;
1032 }
1033 process(rowClass);
1034 }
1035
1036 // part 2 - rowClassFunc
1037 let rowClassFunc = this.beans.gridOptionsWrapper.getRowClassFunc();
1038 if (rowClassFunc) {
1039 let params = {
1040 node: this.rowNode,
1041 data: this.rowNode.data,
1042 rowIndex: this.rowNode.rowIndex,
1043 context: this.beans.gridOptionsWrapper.getContext(),
1044 api: this.beans.gridOptionsWrapper.getApi()
1045 };
1046 let rowClassFuncResult = rowClassFunc(params);
1047 process(rowClassFuncResult);
1048 }
1049
1050 return res;
1051 }
1052
1053 private preProcessStylesFromGridOptions(): string {
1054 let rowStyles = this.processStylesFromGridOptions();
1055 return _.cssStyleObjectToMarkup(rowStyles);
1056 }
1057
1058 private postProcessStylesFromGridOptions(): void {
1059 let rowStyles = this.processStylesFromGridOptions();
1060 this.eAllRowContainers.forEach( row => _.addStylesToElement(row, rowStyles));
1061 }
1062
1063 private processStylesFromGridOptions(): any {
1064
1065 // part 1 - rowStyle
1066 let rowStyle = this.beans.gridOptionsWrapper.getRowStyle();
1067
1068 if (rowStyle && typeof rowStyle === 'function') {
1069 console.log('ag-Grid: rowStyle should be an object of key/value styles, not be a function, use getRowStyle() instead');
1070 return;
1071 }
1072
1073 // part 1 - rowStyleFunc
1074 let rowStyleFunc = this.beans.gridOptionsWrapper.getRowStyleFunc();
1075 let rowStyleFuncResult: any;
1076 if (rowStyleFunc) {
1077 let params = {
1078 data: this.rowNode.data,
1079 node: this.rowNode,
1080 api: this.beans.gridOptionsWrapper.getApi(),
1081 context: this.beans.gridOptionsWrapper.getContext(),
1082 $scope: this.scope
1083 };
1084 rowStyleFuncResult = rowStyleFunc(params);
1085 }
1086
1087 return _.assign({}, rowStyle, rowStyleFuncResult);
1088 }
1089
1090 private createCells(cols: Column[]): {template: string, cellComps: CellComp[]} {
1091 let templateParts: string[] = [];
1092 let newCellComps: CellComp[] = [];
1093 cols.forEach( col => {
1094 let newCellComp = new CellComp(this.scope, this.beans, col, this.rowNode, this, false);
1095 let cellTemplate = newCellComp.getCreateTemplate();
1096 templateParts.push(cellTemplate);
1097 newCellComps.push(newCellComp);
1098 this.cellComps[col.getId()] = newCellComp;
1099 });
1100 let templateAndComps = {
1101 template: templateParts.join(''),
1102 cellComps: newCellComps
1103 };
1104 return templateAndComps;
1105 }
1106
1107 private onRowSelected(): void {
1108 let selected = this.rowNode.isSelected();
1109 this.eAllRowContainers.forEach( (row) => _.addOrRemoveCssClass(row, 'ag-row-selected', selected) );
1110 }
1111
1112 // called:
1113 // + after row created for first time
1114 // + after horizontal scroll, so new cells due to column virtualisation
1115 private callAfterRowAttachedOnCells(newCellComps: CellComp[], eRow: HTMLElement): void {
1116 newCellComps.forEach( cellComp => {
1117 cellComp.setParentRow(eRow);
1118 cellComp.afterAttached();
1119
1120 // if we are editing the row, then the cell needs to turn
1121 // into edit mode
1122 if (this.editingRow) {
1123 cellComp.startEditingIfEnabled();
1124 }
1125 });
1126 }
1127
1128 private afterRowAttached(rowContainerComp: RowContainerComponent, eRow: HTMLElement): void {
1129
1130 this.addDomData(eRow);
1131
1132 this.removeSecondPassFuncs.push( ()=> {
1133 // console.log(eRow);
1134 rowContainerComp.removeRowElement(eRow);
1135 });
1136 this.removeFirstPassFuncs.push( ()=> {
1137 if (_.exists(this.rowNode.rowTop)) {
1138 // the row top is updated anyway, however we set it here again
1139 // to something more reasonable for the animation - ie if the
1140 // row top is 10000px away, the row will flash out, so this
1141 // gives it a rounded value, so row animates out more slowly
1142 let rowTop = this.roundRowTopToBounds(this.rowNode.rowTop);
1143 this.setRowTop(rowTop);
1144 } else {
1145 _.addCssClass(eRow, 'ag-opacity-zero');
1146 }
1147 });
1148
1149 this.eAllRowContainers.push(eRow);
1150
1151 // adding hover functionality adds listener to this row, so we
1152 // do it lazily in an animation frame
1153 if (this.useAnimationFrameForCreate) {
1154 this.beans.taskQueue.addP1Task(this.addHoverFunctionality.bind(this, eRow));
1155 } else {
1156 this.addHoverFunctionality(eRow);
1157 }
1158 }
1159
1160 private addHoverFunctionality(eRow: HTMLElement): void {
1161
1162 // because we use animation frames to do this, it's possible the row no longer exists
1163 // by the time we get to add it
1164 if (!this.active) {
1165 return;
1166 }
1167
1168 // because mouseenter and mouseleave do not propagate, we cannot listen on the gridPanel
1169 // like we do for all the other mouse events.
1170
1171 // because of the pinning, we cannot simply add / remove the class based on the eRow. we
1172 // have to check all eRow's (body & pinned). so the trick is if any of the rows gets a
1173 // mouse hover, it sets such in the rowNode, and then all three reflect the change as
1174 // all are listening for event on the row node.
1175
1176 // step 1 - add listener, to set flag on row node
1177 this.addDestroyableEventListener(eRow, 'mouseenter', () => this.rowNode.onMouseEnter() );
1178 this.addDestroyableEventListener(eRow, 'mouseleave', () => this.rowNode.onMouseLeave() );
1179
1180 // step 2 - listen for changes on row node (which any eRow can trigger)
1181 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_MOUSE_ENTER, ()=> {
1182 // if hover turned off, we don't add the class. we do this here so that if the application
1183 // toggles this property mid way, we remove the hover form the last row, but we stop
1184 // adding hovers from that point onwards.
1185 if (!this.beans.gridOptionsWrapper.isSuppressRowHoverHighlight()) {
1186 _.addCssClass(eRow, 'ag-row-hover');
1187 }
1188 });
1189 this.addDestroyableEventListener(this.rowNode, RowNode.EVENT_MOUSE_LEAVE, ()=> {
1190 _.removeCssClass(eRow, 'ag-row-hover');
1191 });
1192 }
1193
1194 // for animation, we don't want to animate entry or exit to a very far away pixel,
1195 // otherwise the row would move so fast, it would appear to disappear. so this method
1196 // moves the row closer to the viewport if it is far away, so the row slide in / out
1197 // at a speed the user can see.
1198 private roundRowTopToBounds(rowTop: number): number {
1199 let range = this.beans.gridPanel.getVScrollPosition();
1200
1201 let minPixel = this.applyPaginationOffset(range.top, true) - 100;
1202 let maxPixel = this.applyPaginationOffset(range.bottom, true) + 100;
1203
1204 if (rowTop < minPixel) {
1205 return minPixel;
1206 } else if (rowTop > maxPixel) {
1207 return maxPixel;
1208 } else {
1209 return rowTop;
1210 }
1211 }
1212
1213 private onRowHeightChanged(): void {
1214 // check for exists first - if the user is resetting the row height, then
1215 // it will be null (or undefined) momentarily until the next time the flatten
1216 // stage is called where the row will then update again with a new height
1217 if (_.exists(this.rowNode.rowHeight)) {
1218 let heightPx = this.rowNode.rowHeight + 'px';
1219 this.eAllRowContainers.forEach( row => row.style.height = heightPx);
1220 }
1221 }
1222
1223 public addEventListener(eventType: string, listener: Function): void {
1224 if (eventType==='renderedRowRemoved' || eventType==='rowRemoved') {
1225 eventType = Events.EVENT_VIRTUAL_ROW_REMOVED;
1226 console.warn('ag-Grid: Since version 11, event renderedRowRemoved is now called ' + Events.EVENT_VIRTUAL_ROW_REMOVED);
1227 }
1228 super.addEventListener(eventType, listener);
1229 }
1230
1231 public removeEventListener(eventType: string, listener: Function): void {
1232 if (eventType==='renderedRowRemoved' || eventType==='rowRemoved') {
1233 eventType = Events.EVENT_VIRTUAL_ROW_REMOVED;
1234 console.warn('ag-Grid: Since version 11, event renderedRowRemoved and rowRemoved is now called ' + Events.EVENT_VIRTUAL_ROW_REMOVED);
1235 }
1236 super.removeEventListener(eventType, listener);
1237 }
1238
1239 public destroy(animate = false): void {
1240 super.destroy();
1241
1242 this.active = false;
1243
1244 // why do we have this method? shouldn't everything below be added as a destroy func beside
1245 // the corresponding create logic?
1246
1247 this.destroyFullWidthComponents();
1248
1249 if (animate) {
1250
1251 this.removeFirstPassFuncs.forEach( func => func() );
1252 this.removeSecondPassFuncs.push(this.destroyContainingCells.bind(this));
1253
1254 } else {
1255 this.destroyContainingCells();
1256
1257 // we are not animating, so execute the second stage of removal now.
1258 // we call getAndClear, so that they are only called once
1259 let delayedDestroyFunctions = this.getAndClearDelayedDestroyFunctions();
1260 delayedDestroyFunctions.forEach( func => func() );
1261 }
1262
1263 let event: VirtualRowRemovedEvent = this.createRowEvent(Events.EVENT_VIRTUAL_ROW_REMOVED);
1264
1265 this.dispatchEvent(event);
1266 this.beans.eventService.dispatchEvent(event);
1267 }
1268
1269 private destroyContainingCells(): void {
1270 this.forEachCellComp(renderedCell => renderedCell.destroy() );
1271 this.destroyFullWidthComponents();
1272 }
1273
1274 // we clear so that the functions are never executed twice
1275 public getAndClearDelayedDestroyFunctions(): Function[] {
1276 let result = this.removeSecondPassFuncs;
1277 this.removeSecondPassFuncs = [];
1278 return result;
1279 }
1280
1281 private onCellFocusChanged(): void {
1282 let rowFocused = this.beans.focusedCellController.isRowFocused(this.rowNode.rowIndex, this.rowNode.rowPinned);
1283 if (rowFocused !== this.rowFocused) {
1284 this.eAllRowContainers.forEach( (row) => _.addOrRemoveCssClass(row, 'ag-row-focus', rowFocused) );
1285 this.eAllRowContainers.forEach( (row) => _.addOrRemoveCssClass(row, 'ag-row-no-focus', !rowFocused) );
1286 this.rowFocused = rowFocused;
1287 }
1288
1289 // if we are editing, then moving the focus out of a row will stop editing
1290 if (!rowFocused && this.editingRow) {
1291 this.stopEditing(false);
1292 }
1293 }
1294
1295 private onPaginationChanged(): void {
1296 let currentPage = this.beans.paginationProxy.getCurrentPage();
1297 // it is possible this row is in the new page, but the page number has changed, which means
1298 // it needs to reposition itself relative to the new page
1299 if (this.paginationPage!==currentPage) {
1300 this.paginationPage = currentPage;
1301 this.onTopChanged();
1302 }
1303 }
1304
1305 private onTopChanged(): void {
1306 this.setRowTop(this.rowNode.rowTop);
1307 }
1308
1309 // applies pagination offset, eg if on second page, and page height is 500px, then removes
1310 // 500px from the top position, so a row with rowTop 600px is displayed at location 100px.
1311 // reverse will take the offset away rather than add.
1312 private applyPaginationOffset(topPx: number, reverse = false): number {
1313 if (this.rowNode.isRowPinned()) {
1314 return topPx;
1315 } else {
1316 let pixelOffset = this.beans.paginationProxy.getPixelOffset();
1317 if (reverse) {
1318 return topPx + pixelOffset;
1319 } else {
1320 return topPx - pixelOffset;
1321 }
1322 }
1323 }
1324
1325 private setRowTop(pixels: number): void {
1326 // need to make sure rowTop is not null, as this can happen if the node was once
1327 // visible (ie parent group was expanded) but is now not visible
1328 if (_.exists(pixels)) {
1329 let afterPaginationPixels = this.applyPaginationOffset(pixels);
1330
1331 let afterScalingPixels = this.beans.heightScaler.getRealPixelPosition(afterPaginationPixels);
1332
1333 let topPx = afterScalingPixels + "px";
1334 if (this.beans.gridOptionsWrapper.isSuppressRowTransform()) {
1335 this.eAllRowContainers.forEach( row => row.style.top = `${topPx}` );
1336 } else {
1337 this.eAllRowContainers.forEach( row => row.style.transform = `translateY(${topPx})` );
1338 }
1339 }
1340 }
1341
1342 // we clear so that the functions are never executed twice
1343 public getAndClearNextVMTurnFunctions(): Function[] {
1344 let result = this.createSecondPassFuncs;
1345 this.createSecondPassFuncs = [];
1346 return result;
1347 }
1348
1349 public getRowNode(): RowNode {
1350 return this.rowNode;
1351 }
1352
1353 public getRenderedCellForColumn(column: Column): CellComp {
1354 return this.cellComps[column.getColId()];
1355 }
1356
1357 private onRowIndexChanged(): void {
1358 this.onCellFocusChanged();
1359 this.updateRowIndexes();
1360 }
1361
1362 private updateRowIndexes(): void {
1363 let rowIndexStr = this.rowNode.getRowIndexString();
1364
1365 let rowIsEven = this.rowNode.rowIndex % 2 === 0;
1366 let rowIsEvenChanged = this.rowIsEven !== rowIsEven;
1367 if (rowIsEvenChanged) {
1368 this.rowIsEven = rowIsEven;
1369 }
1370
1371 this.eAllRowContainers.forEach( eRow => {
1372 eRow.setAttribute('row-index', rowIndexStr);
1373
1374 if (rowIsEvenChanged) {
1375 _.addOrRemoveCssClass(eRow, 'ag-row-even', rowIsEven);
1376 _.addOrRemoveCssClass(eRow, 'ag-row-odd', !rowIsEven);
1377 }
1378 });
1379 }
1380
1381 public ensureDomOrder(): void {
1382 let body = this.getBodyRowElement();
1383 if (body) {
1384 this.bodyContainerComp.ensureDomOrder(body);
1385 }
1386
1387 let left = this.getPinnedLeftRowElement();
1388 if (left) {
1389 this.pinnedLeftContainerComp.ensureDomOrder(left);
1390 }
1391
1392 let right = this.getPinnedRightRowElement();
1393 if (right) {
1394 this.pinnedRightContainerComp.ensureDomOrder(right);
1395 }
1396
1397 let fullWidth = this.getFullWidthRowElement();
1398 if (fullWidth) {
1399 this.fullWidthContainerComp.ensureDomOrder(fullWidth);
1400 }
1401 }
1402
1403 // returns the pinned left container, either the normal one, or the embedded full with one if exists
1404 public getPinnedLeftRowElement(): HTMLElement {
1405 return this.ePinnedLeftRow ? this.ePinnedLeftRow : this.eFullWidthRowLeft;
1406 }
1407
1408 // returns the pinned right container, either the normal one, or the embedded full with one if exists
1409 public getPinnedRightRowElement(): HTMLElement {
1410 return this.ePinnedRightRow ? this.ePinnedRightRow : this.eFullWidthRowRight;
1411 }
1412
1413 // returns the body container, either the normal one, or the embedded full with one if exists
1414 public getBodyRowElement(): HTMLElement {
1415 return this.eBodyRow ? this.eBodyRow : this.eFullWidthRowBody;
1416 }
1417
1418 // returns the full width container
1419 public getFullWidthRowElement(): HTMLElement {
1420 return this.eFullWidthRow;
1421 }
1422
1423}
\No newline at end of file