UNPKG

ag-grid

Version:

Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components

1,030 lines 50.6 kB
/** * ag-grid - Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components * @version v18.1.2 * @link http://www.ag-grid.com/ * @license MIT */ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); var utils_1 = require("../utils"); var gridOptionsWrapper_1 = require("../gridOptionsWrapper"); var expressionService_1 = require("../valueService/expressionService"); var templateService_1 = require("../templateService"); var valueService_1 = require("../valueService/valueService"); var eventService_1 = require("../eventService"); var rowComp_1 = require("./rowComp"); var events_1 = require("../events"); var constants_1 = require("../constants"); var cellComp_1 = require("./cellComp"); var context_1 = require("../context/context"); var gridCore_1 = require("../gridCore"); var columnApi_1 = require("../columnController/columnApi"); var columnController_1 = require("../columnController/columnController"); var logger_1 = require("../logger"); var focusedCellController_1 = require("../focusedCellController"); var cellNavigationService_1 = require("../cellNavigationService"); var gridCell_1 = require("../entities/gridCell"); var beanStub_1 = require("../context/beanStub"); var paginationProxy_1 = require("../rowModels/paginationProxy"); var gridApi_1 = require("../gridApi"); var pinnedRowModel_1 = require("../rowModels/pinnedRowModel"); var beans_1 = require("./beans"); var animationFrameService_1 = require("../misc/animationFrameService"); var heightScaler_1 = require("./heightScaler"); var RowRenderer = (function (_super) { __extends(RowRenderer, _super); function RowRenderer() { var _this = _super !== null && _super.apply(this, arguments) || this; // map of row ids to row objects. keeps track of which elements // are rendered for which rows in the dom. _this.rowCompsByIndex = {}; _this.floatingTopRowComps = []; _this.floatingBottomRowComps = []; // we only allow one refresh at a time, otherwise the internal memory structure here // will get messed up. this can happen if the user has a cellRenderer, and inside the // renderer they call an API method that results in another pass of the refresh, // then it will be trying to draw rows in the middle of a refresh. _this.refreshInProgress = false; return _this; } RowRenderer.prototype.agWire = function (loggerFactory) { this.logger = loggerFactory.create("RowRenderer"); }; RowRenderer.prototype.registerGridComp = function (gridPanel) { this.gridPanel = gridPanel; this.rowContainers = this.gridPanel.getRowContainers(); this.addDestroyableEventListener(this.eventService, events_1.Events.EVENT_PAGINATION_CHANGED, this.onPageLoaded.bind(this)); this.addDestroyableEventListener(this.eventService, events_1.Events.EVENT_PINNED_ROW_DATA_CHANGED, this.onPinnedRowDataChanged.bind(this)); this.addDestroyableEventListener(this.eventService, events_1.Events.EVENT_DISPLAYED_COLUMNS_CHANGED, this.onDisplayedColumnsChanged.bind(this)); this.addDestroyableEventListener(this.eventService, events_1.Events.EVENT_BODY_SCROLL, this.redrawAfterScroll.bind(this)); this.addDestroyableEventListener(this.eventService, events_1.Events.EVENT_BODY_HEIGHT_CHANGED, this.redrawAfterScroll.bind(this)); this.redrawAfterModelUpdate(); }; RowRenderer.prototype.onPageLoaded = function (refreshEvent) { if (utils_1.Utils.missing(refreshEvent)) { refreshEvent = { type: events_1.Events.EVENT_MODEL_UPDATED, api: this.gridApi, columnApi: this.columnApi, animate: false, keepRenderedRows: false, newData: false, newPage: false }; } this.onModelUpdated(refreshEvent); }; RowRenderer.prototype.getAllCellsForColumn = function (column) { var eCells = []; utils_1.Utils.iterateObject(this.rowCompsByIndex, callback); utils_1.Utils.iterateObject(this.floatingBottomRowComps, callback); utils_1.Utils.iterateObject(this.floatingTopRowComps, callback); function callback(key, rowComp) { var eCell = rowComp.getCellForCol(column); if (eCell) { eCells.push(eCell); } } return eCells; }; RowRenderer.prototype.refreshFloatingRowComps = function () { this.refreshFloatingRows(this.floatingTopRowComps, this.pinnedRowModel.getPinnedTopRowData(), this.rowContainers.floatingTopPinnedLeft, this.rowContainers.floatingTopPinnedRight, this.rowContainers.floatingTop, this.rowContainers.floatingTopFullWidth); this.refreshFloatingRows(this.floatingBottomRowComps, this.pinnedRowModel.getPinnedBottomRowData(), this.rowContainers.floatingBottomPinnedLeft, this.rowContainers.floatingBottomPinnedRight, this.rowContainers.floatingBottom, this.rowContainers.floatingBottomFullWith); }; RowRenderer.prototype.refreshFloatingRows = function (rowComps, rowNodes, pinnedLeftContainerComp, pinnedRightContainerComp, bodyContainerComp, fullWidthContainerComp) { var _this = this; rowComps.forEach(function (row) { row.destroy(); }); rowComps.length = 0; if (rowNodes) { rowNodes.forEach(function (node) { var rowComp = new rowComp_1.RowComp(_this.$scope, bodyContainerComp, pinnedLeftContainerComp, pinnedRightContainerComp, fullWidthContainerComp, node, _this.beans, false, false); rowComp.init(); rowComps.push(rowComp); }); } this.flushContainers(rowComps); }; RowRenderer.prototype.onPinnedRowDataChanged = function () { // recycling rows in order to ensure cell editing is not cancelled var params = { recycleRows: true }; this.redrawAfterModelUpdate(params); }; RowRenderer.prototype.onModelUpdated = function (refreshEvent) { var params = { recycleRows: refreshEvent.keepRenderedRows, animate: refreshEvent.animate, newData: refreshEvent.newData, newPage: refreshEvent.newPage, // because this is a model updated event (not pinned rows), we // can skip updating the pinned rows. this is needed so that if user // is doing transaction updates, the pinned rows are not getting constantly // trashed - or editing cells in pinned rows are not refreshed and put into read mode onlyBody: true }; this.redrawAfterModelUpdate(params); }; // if the row nodes are not rendered, no index is returned RowRenderer.prototype.getRenderedIndexesForRowNodes = function (rowNodes) { var result = []; if (utils_1.Utils.missing(rowNodes)) { return result; } utils_1.Utils.iterateObject(this.rowCompsByIndex, function (index, renderedRow) { var rowNode = renderedRow.getRowNode(); if (rowNodes.indexOf(rowNode) >= 0) { result.push(index); } }); return result; }; RowRenderer.prototype.redrawRows = function (rowNodes) { if (!rowNodes || rowNodes.length == 0) { return; } // we only need to be worried about rendered rows, as this method is // called to whats rendered. if the row isn't rendered, we don't care var indexesToRemove = this.getRenderedIndexesForRowNodes(rowNodes); // remove the rows this.removeRowComps(indexesToRemove); // add draw them again this.redrawAfterModelUpdate({ recycleRows: true }); }; RowRenderer.prototype.getCellToRestoreFocusToAfterRefresh = function (params) { var focusedCell = params.suppressKeepFocus ? null : this.focusedCellController.getFocusCellToUseAfterRefresh(); if (utils_1.Utils.missing(focusedCell)) { return null; } // if the dom is not actually focused on a cell, then we don't try to refocus. the problem this // solves is with editing - if the user is editing, eg focus is on a text field, and not on the // cell itself, then the cell can be registered as having focus, however it's the text field that // has the focus and not the cell div. therefore, when the refresh is finished, the grid will focus // the cell, and not the textfield. that means if the user is in a text field, and the grid refreshes, // the focus is lost from the text field. we do not want this. var activeElement = document.activeElement; var domData = this.gridOptionsWrapper.getDomData(activeElement, cellComp_1.CellComp.DOM_DATA_KEY_CELL_COMP); var elementIsNotACellDev = utils_1.Utils.missing(domData); if (elementIsNotACellDev) { return null; } return focusedCell; }; // gets called after changes to the model. RowRenderer.prototype.redrawAfterModelUpdate = function (params) { if (params === void 0) { params = {}; } this.getLockOnRefresh(); var focusedCell = this.getCellToRestoreFocusToAfterRefresh(params); this.sizeContainerToPageHeight(); this.scrollToTopIfNewData(params); var recycleRows = params.recycleRows; var animate = params.animate && this.gridOptionsWrapper.isAnimateRows(); var rowsToRecycle = this.binRowComps(recycleRows); this.redraw(rowsToRecycle, animate); if (!params.onlyBody) { this.refreshFloatingRowComps(); } this.restoreFocusedCell(focusedCell); this.releaseLockOnRefresh(); }; RowRenderer.prototype.scrollToTopIfNewData = function (params) { var scrollToTop = params.newData || params.newPage; var suppressScrollToTop = this.gridOptionsWrapper.isSuppressScrollOnNewData(); if (scrollToTop && !suppressScrollToTop) { this.gridPanel.scrollToTop(); } }; RowRenderer.prototype.sizeContainerToPageHeight = function () { var containerHeight = this.paginationProxy.getCurrentPageHeight(); // we need at least 1 pixel for the horizontal scroll to work. so if there are now rows, // we still want the scroll to be present, otherwise there would be no way to access the columns // on the RHS - and if that was where the filter was that cause no rows to be presented, there // is no way to remove the filter. if (containerHeight === 0) { containerHeight = 1; } this.heightScaler.setModelHeight(containerHeight); var realHeight = this.heightScaler.getUiContainerHeight(); this.rowContainers.body.setHeight(realHeight); this.rowContainers.fullWidth.setHeight(realHeight); this.rowContainers.pinnedLeft.setHeight(realHeight); this.rowContainers.pinnedRight.setHeight(realHeight); }; RowRenderer.prototype.getLockOnRefresh = function () { if (this.refreshInProgress) { throw new Error("ag-Grid: cannot get grid to draw rows when it is in the middle of drawing rows. " + "Your code probably called a grid API method while the grid was in the render stage. To overcome " + "this, put the API call into a timeout, eg instead of api.refreshView(), " + "call setTimeout(function(){api.refreshView(),0}). To see what part of your code " + "that caused the refresh check this stacktrace."); } this.refreshInProgress = true; }; RowRenderer.prototype.releaseLockOnRefresh = function () { this.refreshInProgress = false; }; // sets the focus to the provided cell, if the cell is provided. this way, the user can call refresh without // worry about the focus been lost. this is important when the user is using keyboard navigation to do edits // and the cellEditor is calling 'refresh' to get other cells to update (as other cells might depend on the // edited cell). RowRenderer.prototype.restoreFocusedCell = function (gridCell) { if (gridCell) { this.focusedCellController.setFocusedCell(gridCell.rowIndex, gridCell.column, gridCell.floating, true); } }; RowRenderer.prototype.stopEditing = function (cancel) { if (cancel === void 0) { cancel = false; } this.forEachRowComp(function (key, rowComp) { rowComp.stopEditing(cancel); }); }; RowRenderer.prototype.forEachCellComp = function (callback) { this.forEachRowComp(function (key, rowComp) { return rowComp.forEachCellComp(callback); }); }; RowRenderer.prototype.forEachRowComp = function (callback) { utils_1.Utils.iterateObject(this.rowCompsByIndex, callback); utils_1.Utils.iterateObject(this.floatingTopRowComps, callback); utils_1.Utils.iterateObject(this.floatingBottomRowComps, callback); }; RowRenderer.prototype.addRenderedRowListener = function (eventName, rowIndex, callback) { var rowComp = this.rowCompsByIndex[rowIndex]; if (rowComp) { rowComp.addEventListener(eventName, callback); } }; RowRenderer.prototype.flashCells = function (params) { if (params === void 0) { params = {}; } this.forEachCellCompFiltered(params.rowNodes, params.columns, function (cellComp) { return cellComp.flashCell(); }); }; RowRenderer.prototype.refreshCells = function (params) { if (params === void 0) { params = {}; } var refreshCellParams = { forceRefresh: params.force, newData: false }; this.forEachCellCompFiltered(params.rowNodes, params.columns, function (cellComp) { return cellComp.refreshCell(refreshCellParams); }); }; RowRenderer.prototype.getCellRendererInstances = function (params) { var res = []; this.forEachCellCompFiltered(params.rowNodes, params.columns, function (cellComp) { var cellRenderer = cellComp.getCellRenderer(); if (cellRenderer) { res.push(cellRenderer); } }); return res; }; RowRenderer.prototype.getCellEditorInstances = function (params) { var res = []; this.forEachCellCompFiltered(params.rowNodes, params.columns, function (cellComp) { var cellEditor = cellComp.getCellEditor(); if (cellEditor) { res.push(cellEditor); } }); return res; }; RowRenderer.prototype.getEditingCells = function () { var res = []; this.forEachCellComp(function (cellComp) { if (cellComp.isEditing()) { var gridCellDef = cellComp.getGridCell().getGridCellDef(); res.push(gridCellDef); } }); return res; }; // calls the callback for each cellComp that match the provided rowNodes and columns. eg if one row node // and two columns provided, that identifies 4 cells, so callback gets called 4 times, once for each cell. RowRenderer.prototype.forEachCellCompFiltered = function (rowNodes, columns, callback) { var _this = this; var rowIdsMap; if (utils_1.Utils.exists(rowNodes)) { rowIdsMap = { top: {}, bottom: {}, normal: {} }; rowNodes.forEach(function (rowNode) { if (rowNode.rowPinned === constants_1.Constants.PINNED_TOP) { rowIdsMap.top[rowNode.id] = true; } else if (rowNode.rowPinned === constants_1.Constants.PINNED_BOTTOM) { rowIdsMap.bottom[rowNode.id] = true; } else { rowIdsMap.normal[rowNode.id] = true; } }); } var colIdsMap; if (utils_1.Utils.exists(columns)) { colIdsMap = {}; columns.forEach(function (colKey) { var column = _this.columnController.getGridColumn(colKey); if (utils_1.Utils.exists(column)) { colIdsMap[column.getId()] = true; } }); } var processRow = function (rowComp) { var rowNode = rowComp.getRowNode(); var id = rowNode.id; var floating = rowNode.rowPinned; // skip this row if it is missing from the provided list if (utils_1.Utils.exists(rowIdsMap)) { if (floating === constants_1.Constants.PINNED_BOTTOM) { if (!rowIdsMap.bottom[id]) { return; } } else if (floating === constants_1.Constants.PINNED_TOP) { if (!rowIdsMap.top[id]) { return; } } else { if (!rowIdsMap.normal[id]) { return; } } } rowComp.forEachCellComp(function (cellComp) { var colId = cellComp.getColumn().getId(); var excludeColFromRefresh = colIdsMap && !colIdsMap[colId]; if (excludeColFromRefresh) { return; } callback(cellComp); }); }; utils_1.Utils.iterateObject(this.rowCompsByIndex, function (index, rowComp) { processRow(rowComp); }); if (this.floatingTopRowComps) { this.floatingTopRowComps.forEach(processRow); } if (this.floatingBottomRowComps) { this.floatingBottomRowComps.forEach(processRow); } }; RowRenderer.prototype.destroy = function () { _super.prototype.destroy.call(this); var rowIndexesToRemove = Object.keys(this.rowCompsByIndex); this.removeRowComps(rowIndexesToRemove); }; RowRenderer.prototype.binRowComps = function (recycleRows) { var _this = this; var indexesToRemove; var rowsToRecycle = {}; if (recycleRows) { indexesToRemove = []; utils_1.Utils.iterateObject(this.rowCompsByIndex, function (index, rowComp) { var rowNode = rowComp.getRowNode(); if (utils_1.Utils.exists(rowNode.id)) { rowsToRecycle[rowNode.id] = rowComp; delete _this.rowCompsByIndex[index]; } else { indexesToRemove.push(index); } }); } else { indexesToRemove = Object.keys(this.rowCompsByIndex); } this.removeRowComps(indexesToRemove); return rowsToRecycle; }; // takes array of row indexes RowRenderer.prototype.removeRowComps = function (rowsToRemove) { var _this = this; // if no fromIndex then set to -1, which will refresh everything // let realFromIndex = -1; rowsToRemove.forEach(function (indexToRemove) { var renderedRow = _this.rowCompsByIndex[indexToRemove]; renderedRow.destroy(); delete _this.rowCompsByIndex[indexToRemove]; }); }; // gets called when rows don't change, but viewport does, so after: // 1) height of grid body changes, ie number of displayed rows has changed // 2) grid scrolled to new position // 3) ensure index visible (which is a scroll) RowRenderer.prototype.redrawAfterScroll = function () { this.getLockOnRefresh(); this.redraw(null, false, true); this.releaseLockOnRefresh(); }; RowRenderer.prototype.removeRowCompsNotToDraw = function (indexesToDraw) { // for speedy lookup, dump into map var indexesToDrawMap = {}; indexesToDraw.forEach(function (index) { return (indexesToDrawMap[index] = true); }); var existingIndexes = Object.keys(this.rowCompsByIndex); var indexesNotToDraw = utils_1.Utils.filter(existingIndexes, function (index) { return !indexesToDrawMap[index]; }); this.removeRowComps(indexesNotToDraw); }; RowRenderer.prototype.calculateIndexesToDraw = function () { var _this = this; // all in all indexes in the viewport var indexesToDraw = utils_1.Utils.createArrayOfNumbers(this.firstRenderedRow, this.lastRenderedRow); // add in indexes of rows we want to keep, because they are currently editing utils_1.Utils.iterateObject(this.rowCompsByIndex, function (indexStr, rowComp) { var index = Number(indexStr); if (index < _this.firstRenderedRow || index > _this.lastRenderedRow) { if (_this.keepRowBecauseEditing(rowComp)) { indexesToDraw.push(index); } } }); indexesToDraw.sort(function (a, b) { return a - b; }); return indexesToDraw; }; RowRenderer.prototype.redraw = function (rowsToRecycle, animate, afterScroll) { var _this = this; if (animate === void 0) { animate = false; } if (afterScroll === void 0) { afterScroll = false; } this.heightScaler.update(); this.workOutFirstAndLastRowsToRender(); // the row can already exist and be in the following: // rowsToRecycle -> if model change, then the index may be different, however row may // exist here from previous time (mapped by id). // this.rowCompsByIndex -> if just a scroll, then this will contain what is currently in the viewport // this is all the indexes we want, including those that already exist, so this method // will end up going through each index and drawing only if the row doesn't already exist var indexesToDraw = this.calculateIndexesToDraw(); this.removeRowCompsNotToDraw(indexesToDraw); // add in new rows var nextVmTurnFunctions = []; var rowComps = []; indexesToDraw.forEach(function (rowIndex) { var rowComp = _this.createOrUpdateRowComp(rowIndex, rowsToRecycle, animate, afterScroll); if (utils_1.Utils.exists(rowComp)) { rowComps.push(rowComp); utils_1.Utils.pushAll(nextVmTurnFunctions, rowComp.getAndClearNextVMTurnFunctions()); } }); this.flushContainers(rowComps); utils_1.Utils.executeNextVMTurn(nextVmTurnFunctions); if (afterScroll && !this.gridOptionsWrapper.isSuppressAnimationFrame()) { this.beans.taskQueue.addP2Task(this.destroyRowComps.bind(this, rowsToRecycle, animate)); } else { this.destroyRowComps(rowsToRecycle, animate); } this.checkAngularCompile(); }; RowRenderer.prototype.flushContainers = function (rowComps) { utils_1.Utils.iterateObject(this.rowContainers, function (key, rowContainerComp) { if (rowContainerComp) { rowContainerComp.flushRowTemplates(); } }); rowComps.forEach(function (rowComp) { return rowComp.afterFlush(); }); }; RowRenderer.prototype.onDisplayedColumnsChanged = function () { var pinningLeft = this.columnController.isPinningLeft(); var pinningRight = this.columnController.isPinningRight(); var atLeastOneChanged = this.pinningLeft !== pinningLeft || pinningRight !== this.pinningRight; if (atLeastOneChanged) { this.pinningLeft = pinningLeft; this.pinningRight = pinningRight; if (this.gridOptionsWrapper.isEmbedFullWidthRows()) { this.redrawFullWidthEmbeddedRows(); } } }; // when embedding, what gets showed in each section depends on what is pinned. eg if embedding group expand / collapse, // then it should go into the pinned left area if pinning left, or the center area if not pinning. RowRenderer.prototype.redrawFullWidthEmbeddedRows = function () { // if either of the pinned panels has shown / hidden, then need to redraw the fullWidth bits when // embedded, as what appears in each section depends on whether we are pinned or not var rowsToRemove = []; utils_1.Utils.iterateObject(this.rowCompsByIndex, function (id, rowComp) { if (rowComp.isFullWidth()) { var rowIndex = rowComp.getRowNode().rowIndex; rowsToRemove.push(rowIndex.toString()); } }); this.refreshFloatingRowComps(); this.removeRowComps(rowsToRemove); this.redrawAfterScroll(); }; RowRenderer.prototype.createOrUpdateRowComp = function (rowIndex, rowsToRecycle, animate, afterScroll) { var rowNode; var rowComp = this.rowCompsByIndex[rowIndex]; // if no row comp, see if we can get it from the previous rowComps if (!rowComp) { rowNode = this.paginationProxy.getRow(rowIndex); if (utils_1.Utils.exists(rowNode) && utils_1.Utils.exists(rowsToRecycle) && rowsToRecycle[rowNode.id]) { rowComp = rowsToRecycle[rowNode.id]; rowsToRecycle[rowNode.id] = null; } } var creatingNewRowComp = !rowComp; if (creatingNewRowComp) { // create a new one if (!rowNode) { rowNode = this.paginationProxy.getRow(rowIndex); } if (utils_1.Utils.exists(rowNode)) { rowComp = this.createRowComp(rowNode, animate, afterScroll); } else { // this should never happen - if somehow we are trying to create // a row for a rowNode that does not exist. return; } } else { // ensure row comp is in right position in DOM rowComp.ensureDomOrder(); } this.rowCompsByIndex[rowIndex] = rowComp; return rowComp; }; RowRenderer.prototype.destroyRowComps = function (rowCompsMap, animate) { var delayedFuncs = []; utils_1.Utils.iterateObject(rowCompsMap, function (nodeId, rowComp) { // if row was used, then it's null if (!rowComp) { return; } rowComp.destroy(animate); utils_1.Utils.pushAll(delayedFuncs, rowComp.getAndClearDelayedDestroyFunctions()); }); utils_1.Utils.executeInAWhile(delayedFuncs); }; RowRenderer.prototype.checkAngularCompile = function () { var _this = this; // if we are doing angular compiling, then do digest the scope here if (this.gridOptionsWrapper.isAngularCompileRows()) { // we do it in a timeout, in case we are already in an apply setTimeout(function () { _this.$scope.$apply(); }, 0); } }; RowRenderer.prototype.workOutFirstAndLastRowsToRender = function () { var newFirst; var newLast; if (!this.paginationProxy.isRowsToRender()) { newFirst = 0; newLast = -1; // setting to -1 means nothing in range } else { var pageFirstRow = this.paginationProxy.getPageFirstRow(); var pageLastRow = this.paginationProxy.getPageLastRow(); var pixelOffset = this.paginationProxy ? this.paginationProxy.getPixelOffset() : 0; var heightOffset = this.heightScaler.getOffset(); var bodyVRange = this.gridPanel.getVScrollPosition(); var topPixel = bodyVRange.top; var bottomPixel = bodyVRange.bottom; var realPixelTop = topPixel + pixelOffset + heightOffset; var realPixelBottom = bottomPixel + pixelOffset + heightOffset; var first = this.paginationProxy.getRowIndexAtPixel(realPixelTop); var last = this.paginationProxy.getRowIndexAtPixel(realPixelBottom); //add in buffer var buffer = this.gridOptionsWrapper.getRowBuffer(); first = first - buffer; last = last + buffer; // adjust, in case buffer extended actual size if (first < pageFirstRow) { first = pageFirstRow; } if (last > pageLastRow) { last = pageLastRow; } newFirst = first; newLast = last; } var firstDiffers = newFirst !== this.firstRenderedRow; var lastDiffers = newLast !== this.lastRenderedRow; if (firstDiffers || lastDiffers) { this.firstRenderedRow = newFirst; this.lastRenderedRow = newLast; var event_1 = { type: events_1.Events.EVENT_VIEWPORT_CHANGED, firstRow: newFirst, lastRow: newLast, api: this.gridApi, columnApi: this.columnApi }; this.eventService.dispatchEvent(event_1); } }; RowRenderer.prototype.getFirstVirtualRenderedRow = function () { return this.firstRenderedRow; }; RowRenderer.prototype.getLastVirtualRenderedRow = function () { return this.lastRenderedRow; }; // check that none of the rows to remove are editing or focused as: // a) if editing, we want to keep them, otherwise the user will loose the context of the edit, // eg user starts editing, enters some text, then scrolls down and then up, next time row rendered // the edit is reset - so we want to keep it rendered. // b) if focused, we want ot keep keyboard focus, so if user ctrl+c, it goes to clipboard, // otherwise the user can range select and drag (with focus cell going out of the viewport) // and then ctrl+c, nothing will happen if cell is removed from dom. RowRenderer.prototype.keepRowBecauseEditing = function (rowComp) { var REMOVE_ROW = false; var KEEP_ROW = true; var rowNode = rowComp.getRowNode(); var rowHasFocus = this.focusedCellController.isRowNodeFocused(rowNode); var rowIsEditing = rowComp.isEditing(); var mightWantToKeepRow = rowHasFocus || rowIsEditing; // if we deffo don't want to keep it, if (!mightWantToKeepRow) { return REMOVE_ROW; } // editing row, only remove if it is no longer rendered, eg filtered out or new data set. // the reason we want to keep is if user is scrolling up and down, we don't want to loose // the context of the editing in process. var rowNodePresent = this.paginationProxy.isRowPresent(rowNode); return rowNodePresent ? KEEP_ROW : REMOVE_ROW; }; RowRenderer.prototype.createRowComp = function (rowNode, animate, afterScroll) { var useAnimationFrameForCreate = afterScroll && !this.gridOptionsWrapper.isSuppressAnimationFrame(); var rowComp = new rowComp_1.RowComp(this.$scope, this.rowContainers.body, this.rowContainers.pinnedLeft, this.rowContainers.pinnedRight, this.rowContainers.fullWidth, rowNode, this.beans, animate, useAnimationFrameForCreate); rowComp.init(); return rowComp; }; RowRenderer.prototype.getRenderedNodes = function () { var renderedRows = this.rowCompsByIndex; return Object.keys(renderedRows).map(function (key) { return renderedRows[key].getRowNode(); }); }; // we use index for rows, but column object for columns, as the next column (by index) might not // be visible (header grouping) so it's not reliable, so using the column object instead. RowRenderer.prototype.navigateToNextCell = function (event, key, previousCell, allowUserOverride) { var nextCell = previousCell; // we keep searching for a next cell until we find one. this is how the group rows get skipped while (true) { nextCell = this.cellNavigationService.getNextCellToFocus(key, nextCell); if (utils_1.Utils.missing(nextCell)) { break; } var skipGroupRows = this.gridOptionsWrapper.isGroupUseEntireRow(); if (skipGroupRows) { var rowNode = this.paginationProxy.getRow(nextCell.rowIndex); if (!rowNode.group) { break; } } else { break; } } // allow user to override what cell to go to next. when doing normal cell navigation (with keys) // we allow this, however if processing 'enter after edit' we don't allow override if (allowUserOverride) { var userFunc = this.gridOptionsWrapper.getNavigateToNextCellFunc(); if (utils_1.Utils.exists(userFunc)) { var params = { key: key, previousCellDef: previousCell, nextCellDef: nextCell ? nextCell.getGridCellDef() : null, event: event }; var nextCellDef = userFunc(params); if (utils_1.Utils.exists(nextCellDef)) { nextCell = new gridCell_1.GridCell(nextCellDef); } else { nextCell = null; } } } // no next cell means we have reached a grid boundary, eg left, right, top or bottom of grid if (!nextCell) { return; } this.ensureCellVisible(nextCell); this.focusedCellController.setFocusedCell(nextCell.rowIndex, nextCell.column, nextCell.floating, true); if (this.rangeController) { var gridCell = new gridCell_1.GridCell({ rowIndex: nextCell.rowIndex, floating: nextCell.floating, column: nextCell.column }); this.rangeController.setRangeToCell(gridCell); } }; RowRenderer.prototype.ensureCellVisible = function (gridCell) { // this scrolls the row into view if (utils_1.Utils.missing(gridCell.floating)) { this.gridPanel.ensureIndexVisible(gridCell.rowIndex); } if (!gridCell.column.isPinned()) { this.gridPanel.ensureColumnVisible(gridCell.column); } // need to nudge the scrolls for the floating items. otherwise when we set focus on a non-visible // floating cell, the scrolls get out of sync this.gridPanel.horizontallyScrollHeaderCenterAndFloatingCenter(); // need to flush frames, to make sure the correct cells are rendered this.animationFrameService.flushAllFrames(); }; RowRenderer.prototype.startEditingCell = function (gridCell, keyPress, charPress) { var cell = this.getComponentForCell(gridCell); if (cell) { cell.startRowOrCellEdit(keyPress, charPress); } }; RowRenderer.prototype.getComponentForCell = function (gridCell) { var rowComponent; switch (gridCell.floating) { case constants_1.Constants.PINNED_TOP: rowComponent = this.floatingTopRowComps[gridCell.rowIndex]; break; case constants_1.Constants.PINNED_BOTTOM: rowComponent = this.floatingBottomRowComps[gridCell.rowIndex]; break; default: rowComponent = this.rowCompsByIndex[gridCell.rowIndex]; break; } if (!rowComponent) { return null; } var cellComponent = rowComponent.getRenderedCellForColumn(gridCell.column); return cellComponent; }; RowRenderer.prototype.onTabKeyDown = function (previousRenderedCell, keyboardEvent) { var backwards = keyboardEvent.shiftKey; var success = this.moveToCellAfter(previousRenderedCell, backwards); if (success) { keyboardEvent.preventDefault(); } }; RowRenderer.prototype.tabToNextCell = function (backwards) { var focusedCell = this.focusedCellController.getFocusedCell(); // if no focus, then cannot navigate if (utils_1.Utils.missing(focusedCell)) { return false; } var renderedCell = this.getComponentForCell(focusedCell); // if cell is not rendered, means user has scrolled away from the cell if (utils_1.Utils.missing(renderedCell)) { return false; } var result = this.moveToCellAfter(renderedCell, backwards); return result; }; RowRenderer.prototype.moveToCellAfter = function (previousRenderedCell, backwards) { var editing = previousRenderedCell.isEditing(); var res; if (editing) { if (this.gridOptionsWrapper.isFullRowEdit()) { res = this.moveToNextEditingRow(previousRenderedCell, backwards); } else { res = this.moveToNextEditingCell(previousRenderedCell, backwards); } } else { res = this.moveToNextCellNotEditing(previousRenderedCell, backwards); } return res; }; RowRenderer.prototype.moveToNextEditingCell = function (previousRenderedCell, backwards) { var gridCell = previousRenderedCell.getGridCell(); // need to do this before getting next cell to edit, in case the next cell // has editable function (eg colDef.editable=func() ) and it depends on the // result of this cell, so need to save updates from the first edit, in case // the value is referenced in the function. previousRenderedCell.stopEditing(); // find the next cell to start editing var nextRenderedCell = this.findNextCellToFocusOn(gridCell, backwards, true); var foundCell = utils_1.Utils.exists(nextRenderedCell); // only prevent default if we found a cell. so if user is on last cell and hits tab, then we default // to the normal tabbing so user can exit the grid. if (foundCell) { nextRenderedCell.startEditingIfEnabled(null, null, true); nextRenderedCell.focusCell(false); } return foundCell; }; RowRenderer.prototype.moveToNextEditingRow = function (previousRenderedCell, backwards) { var gridCell = previousRenderedCell.getGridCell(); // find the next cell to start editing var nextRenderedCell = this.findNextCellToFocusOn(gridCell, backwards, true); var foundCell = utils_1.Utils.exists(nextRenderedCell); // only prevent default if we found a cell. so if user is on last cell and hits tab, then we default // to the normal tabbing so user can exit the grid. if (foundCell) { this.moveEditToNextCellOrRow(previousRenderedCell, nextRenderedCell); } return foundCell; }; RowRenderer.prototype.moveToNextCellNotEditing = function (previousRenderedCell, backwards) { var gridCell = previousRenderedCell.getGridCell(); // find the next cell to start editing var nextRenderedCell = this.findNextCellToFocusOn(gridCell, backwards, false); var foundCell = utils_1.Utils.exists(nextRenderedCell); // only prevent default if we found a cell. so if user is on last cell and hits tab, then we default // to the normal tabbing so user can exit the grid. if (foundCell) { nextRenderedCell.focusCell(true); } return foundCell; }; RowRenderer.prototype.moveEditToNextCellOrRow = function (previousRenderedCell, nextRenderedCell) { var pGridCell = previousRenderedCell.getGridCell(); var nGridCell = nextRenderedCell.getGridCell(); var rowsMatch = pGridCell.rowIndex === nGridCell.rowIndex && pGridCell.floating === nGridCell.floating; if (rowsMatch) { // same row, so we don't start / stop editing, we just move the focus along previousRenderedCell.setFocusOutOnEditor(); nextRenderedCell.setFocusInOnEditor(); } else { var pRow = previousRenderedCell.getRenderedRow(); var nRow = nextRenderedCell.getRenderedRow(); previousRenderedCell.setFocusOutOnEditor(); pRow.stopEditing(); nRow.startRowEditing(); nextRenderedCell.setFocusInOnEditor(); } nextRenderedCell.focusCell(); }; // called by the cell, when tab is pressed while editing. // @return: RenderedCell when navigation successful, otherwise null RowRenderer.prototype.findNextCellToFocusOn = function (gridCell, backwards, startEditing) { var nextCell = gridCell; while (true) { nextCell = this.cellNavigationService.getNextTabbedCell(nextCell, backwards); // allow user to override what cell to go to next var userFunc = this.gridOptionsWrapper.getTabToNextCellFunc(); if (utils_1.Utils.exists(userFunc)) { var params = { backwards: backwards, editing: startEditing, previousCellDef: gridCell.getGridCellDef(), nextCellDef: nextCell ? nextCell.getGridCellDef() : null }; var nextCellDef = userFunc(params); if (utils_1.Utils.exists(nextCellDef)) { nextCell = new gridCell_1.GridCell(nextCellDef); } else { nextCell = null; } } // if no 'next cell', means we have got to last cell of grid, so nothing to move to, // so bottom right cell going forwards, or top left going backwards if (!nextCell) { return null; } // if editing, but cell not editable, skip cell. we do this before we do all of // the 'ensure index visible' and 'flush all frames', otherwise if we are skipping // a bunch of cells (eg 10 rows) then all the work on ensuring cell visible is useless // (except for the last one) which causes grid to stall for a while. if (startEditing) { var rowNode = this.paginationProxy.getRow(nextCell.rowIndex); var cellIsEditable = nextCell.column.isCellEditable(rowNode); if (!cellIsEditable) { continue; } } // this scrolls the row into view var cellIsNotFloating = utils_1.Utils.missing(nextCell.floating); if (cellIsNotFloating) { this.gridPanel.ensureIndexVisible(nextCell.rowIndex); } // pinned columns don't scroll, so no need to ensure index visible if (!nextCell.column.isPinned()) { this.gridPanel.ensureColumnVisible(nextCell.column); } // need to nudge the scrolls for the floating items. otherwise when we set focus on a non-visible // floating cell, the scrolls get out of sync this.gridPanel.horizontallyScrollHeaderCenterAndFloatingCenter(); // get the grid panel to flush all animation frames - otherwise the call below to get the cellComp // could fail, if we just scrolled the grid (to make a cell visible) and the rendering hasn't finished. this.animationFrameService.flushAllFrames(); // we have to call this after ensureColumnVisible - otherwise it could be a virtual column // or row that is not currently in view, hence the renderedCell would not exist var nextCellComp = this.getComponentForCell(nextCell); // if next cell is fullWidth row, then no rendered cell, // as fullWidth rows have no cells, so we skip it if (utils_1.Utils.missing(nextCellComp)) { continue; } if (nextCellComp.isSuppressNavigable()) { continue; } // by default, when we click a cell, it gets selected into a range, so to keep keyboard navigation // consistent, we set into range here also. if (this.rangeController) { var gridCell_2 = new gridCell_1.GridCell({ rowIndex: nextCell.rowIndex, floating: nextCell.floating, column: nextCell.column }); this.rangeController.setRangeToCell(gridCell_2); } // we successfully tabbed onto a grid cell, so return true return nextCellComp; } }; __decorate([ context_1.Autowired("paginationProxy"), __metadata("design:type", paginationProxy_1.PaginationProxy) ], RowRenderer.prototype, "paginationProxy", void 0); __decorate([ context_1.Autowired("columnController"), __metadata("design:type", columnController_1.ColumnController) ], RowRenderer.prototype, "columnController", void 0); __decorate([ context_1.Autowired("gridOptionsWrapper"), __metadata("design:type", gridOptionsWrapper_1.GridOptionsWrapper) ], RowRenderer.prototype, "gridOptionsWrapper", void 0); __decorate([ context_1.Autowired("gridCore"), __metadata("design:type", gridCore_1.GridCore) ], RowRenderer.prototype, "gridCore", void 0); __decorate([ context_1.Autowired("$scope"), __metadata("design:type", Object) ], RowRenderer.prototype, "$scope", void 0); __decorate([ context_1.Autowired("expressionService"), __metadata("design:type", expressionService_1.ExpressionService) ], RowRenderer.prototype, "expressionService", void 0); __decorate([ context_1.Autowired("templateService"), __metadata("design:type", templateService_1.TemplateService) ], RowRenderer.prototype, "templateService", void 0); __decorate([ context_1.Autowired("valueService"), __metadata("design:type", valueService_1.ValueService) ], RowRenderer.prototype, "valueService", void 0); __decorate([ context_1.Autowired("eventService"), __metadata("design:type", eventService_1.EventService) ], RowRenderer.prototype, "eventService", void 0); __decorate([ context_1.Autowired("pinnedRowModel"), __metadata("design:type", pinnedRowModel_1.PinnedRowModel) ], RowRenderer.prototype, "pinnedRowModel", void 0); __decorate([ context_1.Autowired("context"), __metadata("design:type", context_1.Context) ], RowRenderer.prototype, "context", void 0); __decorate([ context_1.Autowired("loggerFactory"), __metadata("design:type", logger_1.LoggerFactory) ], RowRenderer.prototype, "loggerFactory", void 0); __decorate([ context_1.Autowired("focusedCellController"), __metadata("design:type", focusedCellController_1.FocusedCellController) ], RowRenderer.prototype, "focusedCellController", void 0); __decorate([ context_1.Autowired("cellNavigationService"), __metadata("design:type", cellNavigationService_1.CellNavigationService) ], RowRenderer.prototype, "cellNavigationService", void 0); __decorate([ context_1.Autowired("columnApi"), __metadata("design:type", columnApi_1.ColumnApi) ], RowRenderer.prototype, "columnApi", void 0); __decorate([ context_1.Autowired("gridApi"), __metadata("design:type", gridApi_1.GridApi) ], RowRenderer.prototype, "gridApi", void 0); __decorate([ context_1.Autowired("beans"), __metadata("design:type", beans_1.Beans) ], RowRenderer.prototype, "beans", void 0); __decorate([ context_1.Autowired("heightScaler"), __metadata("design:type", heightScaler_1.HeightScaler) ], RowRenderer.prototype, "heightScaler", void 0); __decorate([ context_1.Autowired("animationFrameService"), __metadata("design:type", animationFrameService_1.AnimationFrameService) ], RowRenderer.prototype, "animationFrameService", void 0); __decorate([ context_1.Optional("rangeController"), __metadata("design:type", Object) ], RowRenderer.prototype, "rangeController", void 0); __decorate([ __param(0, context_1.Qualifier("loggerFactory")), __metadata("design:type