UNPKG

ag-grid

Version:

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

1,039 lines 70.8 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 __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var utils_1 = require("../utils"); var column_1 = require("../entities/column"); var rowNode_1 = require("../entities/rowNode"); var constants_1 = require("../constants"); var events_1 = require("../events"); var gridCell_1 = require("../entities/gridCell"); var component_1 = require("../widgets/component"); var checkboxSelectionComponent_1 = require("./checkboxSelectionComponent"); var rowDragComp_1 = require("./rowDragComp"); var CellComp = (function (_super) { __extends(CellComp, _super); function CellComp(scope, beans, column, rowNode, rowComp, autoHeightCell) { var _this = _super.call(this) || this; _this.editingCell = false; // every time we go into edit mode, or back again, this gets incremented. // it's the components way of dealing with the async nature of framework components, // so if a framework component takes a while to be created, we know if the object // is still relevant when creating is finished. eg we could click edit / unedit 20 // times before the first React edit component comes back - we should discard // the first 19. _this.cellEditorVersion = 0; _this.cellRendererVersion = 0; _this.scope = scope; _this.beans = beans; _this.column = column; _this.rowNode = rowNode; _this.rowComp = rowComp; _this.autoHeightCell = autoHeightCell; _this.createGridCellVo(); _this.rangeSelectionEnabled = beans.enterprise && beans.gridOptionsWrapper.isEnableRangeSelection(); _this.cellFocused = _this.beans.focusedCellController.isCellFocused(_this.gridCell); _this.firstRightPinned = _this.column.isFirstRightPinned(); _this.lastLeftPinned = _this.column.isLastLeftPinned(); if (_this.rangeSelectionEnabled) { _this.rangeCount = _this.beans.rangeController.getCellRangeCount(_this.gridCell); } _this.getValueAndFormat(); _this.setUsingWrapper(); _this.chooseCellRenderer(); _this.setupColSpan(); _this.rowSpan = _this.column.getRowSpan(_this.rowNode); return _this; } CellComp.prototype.getCreateTemplate = function () { var templateParts = []; var col = this.column; var width = this.getCellWidth(); var left = col.getLeft(); var valueToRender = this.getInitialValueToRender(); var valueSanitised = utils_1._.get(this.column, 'colDef.template', null) ? valueToRender : utils_1._.escape(valueToRender); this.tooltip = this.getToolTip(); var tooltipSanitised = utils_1._.escape(this.tooltip); var colIdSanitised = utils_1._.escape(col.getId()); var wrapperStartTemplate; var wrapperEndTemplate; var stylesFromColDef = this.preProcessStylesFromColDef(); var cssClasses = this.getInitialCssClasses(); var stylesForRowSpanning = this.getStylesForRowSpanning(); if (this.usingWrapper) { wrapperStartTemplate = '<span ref="eCellWrapper" class="ag-cell-wrapper"><span ref="eCellValue" class="ag-cell-value">'; wrapperEndTemplate = '</span></span>'; } // hey, this looks like React!!! templateParts.push("<div"); templateParts.push(" tabindex=\"-1\""); templateParts.push(" role=\"gridcell\""); templateParts.push(" comp-id=\"" + this.getCompId() + "\" "); templateParts.push(" col-id=\"" + colIdSanitised + "\""); templateParts.push(" class=\"" + cssClasses.join(' ') + "\""); templateParts.push(tooltipSanitised ? " title=\"" + tooltipSanitised + "\"" : ""); templateParts.push(" style=\"width: " + width + "px; left: " + left + "px; " + stylesFromColDef + " " + stylesForRowSpanning + "\" >"); templateParts.push(wrapperStartTemplate); templateParts.push(valueSanitised); templateParts.push(wrapperEndTemplate); templateParts.push("</div>"); return templateParts.join(''); }; CellComp.prototype.getStylesForRowSpanning = function () { if (this.rowSpan === 1) { return ''; } var singleRowHeight = this.beans.gridOptionsWrapper.getRowHeightAsNumber(); var totalRowHeight = singleRowHeight * this.rowSpan; return "height: " + totalRowHeight + "px; z-index: 1;"; }; CellComp.prototype.afterAttached = function () { var querySelector = "[comp-id=\"" + this.getCompId() + "\"]"; var eGui = this.eParentRow.querySelector(querySelector); this.setGui(eGui); // all of these have dependencies on the eGui, so only do them after eGui is set this.addDomData(); this.populateTemplate(); this.attachCellRenderer(); this.angular1Compile(); this.addDestroyableEventListener(this.beans.eventService, events_1.Events.EVENT_CELL_FOCUSED, this.onCellFocused.bind(this)); this.addDestroyableEventListener(this.beans.eventService, events_1.Events.EVENT_FLASH_CELLS, this.onFlashCells.bind(this)); this.addDestroyableEventListener(this.beans.eventService, events_1.Events.EVENT_COLUMN_HOVER_CHANGED, this.onColumnHover.bind(this)); this.addDestroyableEventListener(this.rowNode, rowNode_1.RowNode.EVENT_ROW_INDEX_CHANGED, this.onRowIndexChanged.bind(this)); this.addDestroyableEventListener(this.rowNode, rowNode_1.RowNode.EVENT_CELL_CHANGED, this.onCellChanged.bind(this)); this.addDestroyableEventListener(this.column, column_1.Column.EVENT_LEFT_CHANGED, this.onLeftChanged.bind(this)); this.addDestroyableEventListener(this.column, column_1.Column.EVENT_WIDTH_CHANGED, this.onWidthChanged.bind(this)); this.addDestroyableEventListener(this.column, column_1.Column.EVENT_FIRST_RIGHT_PINNED_CHANGED, this.onFirstRightPinnedChanged.bind(this)); this.addDestroyableEventListener(this.column, column_1.Column.EVENT_LAST_LEFT_PINNED_CHANGED, this.onLastLeftPinnedChanged.bind(this)); // if not doing enterprise, then range selection service would be missing // so need to check before trying to use it if (this.rangeSelectionEnabled) { this.addDestroyableEventListener(this.beans.eventService, events_1.Events.EVENT_RANGE_SELECTION_CHANGED, this.onRangeSelectionChanged.bind(this)); } }; CellComp.prototype.onColumnHover = function () { var isHovered = this.beans.columnHoverService.isHovered(this.column); utils_1._.addOrRemoveCssClass(this.getGui(), 'ag-column-hover', isHovered); }; CellComp.prototype.onCellChanged = function (event) { var eventImpactsThisCell = event.column === this.column; if (eventImpactsThisCell) { this.refreshCell({}); } }; CellComp.prototype.getCellLeft = function () { var mostLeftCol; if (this.beans.gridOptionsWrapper.isEnableRtl() && this.colsSpanning) { mostLeftCol = this.colsSpanning[this.colsSpanning.length - 1]; } else { mostLeftCol = this.column; } return mostLeftCol.getLeft(); }; CellComp.prototype.getCellWidth = function () { if (this.colsSpanning) { var result_1 = 0; this.colsSpanning.forEach(function (col) { return result_1 += col.getActualWidth(); }); return result_1; } else { return this.column.getActualWidth(); } }; CellComp.prototype.onFlashCells = function (event) { var cellId = this.gridCell.createId(); var shouldFlash = event.cells[cellId]; if (shouldFlash) { this.animateCell('highlight'); } }; CellComp.prototype.setupColSpan = function () { // if no col span is active, then we don't set it up, as it would be wasteful of CPU if (utils_1._.missing(this.column.getColDef().colSpan)) { return; } // because we are col spanning, a reorder of the cols can change what cols we are spanning over this.addDestroyableEventListener(this.beans.eventService, events_1.Events.EVENT_DISPLAYED_COLUMNS_CHANGED, this.onDisplayColumnsChanged.bind(this)); // because we are spanning over multiple cols, we check for width any time any cols width changes. // this is expensive - really we should be explicitly checking only the cols we are spanning over // instead of every col, however it would be tricky code to track the cols we are spanning over, so // because hardly anyone will be using colSpan, am favoring this easier way for more maintainable code. this.addDestroyableEventListener(this.beans.eventService, events_1.Events.EVENT_DISPLAYED_COLUMNS_WIDTH_CHANGED, this.onWidthChanged.bind(this)); this.colsSpanning = this.getColSpanningList(); }; CellComp.prototype.getColSpanningList = function () { var colSpan = this.column.getColSpan(this.rowNode); var colsSpanning = []; // if just one col, the col span is just the column we are in if (colSpan === 1) { colsSpanning.push(this.column); } else { var pointer = this.column; var pinned = this.column.getPinned(); for (var i = 0; i < colSpan; i++) { colsSpanning.push(pointer); pointer = this.beans.columnController.getDisplayedColAfter(pointer); if (utils_1._.missing(pointer)) { break; } // we do not allow col spanning to span outside of pinned areas if (pinned !== pointer.getPinned()) { break; } } } return colsSpanning; }; CellComp.prototype.onDisplayColumnsChanged = function () { var colsSpanning = this.getColSpanningList(); if (!utils_1._.compareArrays(this.colsSpanning, colsSpanning)) { this.colsSpanning = colsSpanning; this.onWidthChanged(); this.onLeftChanged(); // left changes when doing RTL } }; CellComp.prototype.getInitialCssClasses = function () { var cssClasses = ["ag-cell", "ag-cell-not-inline-editing"]; // if we are putting the cell into a dummy container, to work out it's height, // then we don't put the height css in, as we want cell to fit height in that case. if (!this.autoHeightCell) { cssClasses.push('ag-cell-with-height'); } var doingFocusCss = !this.beans.gridOptionsWrapper.isSuppressCellSelection(); if (doingFocusCss) { // otherwise the class depends on the focus state cssClasses.push(this.cellFocused ? 'ag-cell-focus' : 'ag-cell-no-focus'); } else { // if we are not doing cell selection, then ag-cell-no-focus gets put onto every cell cssClasses.push('ag-cell-no-focus'); } if (this.firstRightPinned) { cssClasses.push('ag-cell-first-right-pinned'); } if (this.lastLeftPinned) { cssClasses.push('ag-cell-last-left-pinned'); } if (this.beans.columnHoverService.isHovered(this.column)) { cssClasses.push('ag-column-hover'); } utils_1._.pushAll(cssClasses, this.preProcessClassesFromColDef()); utils_1._.pushAll(cssClasses, this.preProcessCellClassRules()); utils_1._.pushAll(cssClasses, this.getRangeClasses()); // if using the wrapper, this class goes on the wrapper instead if (!this.usingWrapper) { cssClasses.push('ag-cell-value'); } return cssClasses; }; CellComp.prototype.getInitialValueToRender = function () { // if using a cellRenderer, then render the html from the cell renderer if it exists if (this.usingCellRenderer) { if (typeof this.cellRendererGui === 'string') { return this.cellRendererGui; } else { return ''; } } var colDef = this.column.getColDef(); if (colDef.template) { // template is really only used for angular 1 - as people using ng1 are used to providing templates with // bindings in it. in ng2, people will hopefully want to provide components, not templates. return colDef.template; } else if (colDef.templateUrl) { // likewise for templateUrl - it's for ng1 really - when we move away from ng1, we can take these out. // niall was pro angular 1 when writing template and templateUrl, if writing from scratch now, would // not do these, but would follow a pattern that was friendly towards components, not templates. var template = this.beans.templateService.getTemplate(colDef.templateUrl, this.refreshCell.bind(this, true)); if (template) { return template; } else { return ''; } } else { return this.getValueToUse(); } }; CellComp.prototype.getRenderedRow = function () { return this.rowComp; }; CellComp.prototype.isSuppressNavigable = function () { return this.column.isSuppressNavigable(this.rowNode); }; CellComp.prototype.getCellRenderer = function () { return this.cellRenderer; }; CellComp.prototype.getCellEditor = function () { return this.cellEditor; }; // + stop editing {forceRefresh: true, suppressFlash: true} // + event cellChanged {} // + cellRenderer.params.refresh() {} -> method passes 'as is' to the cellRenderer, so params could be anything // + rowComp: event dataChanged {animate: update, newData: !update} // + rowComp: api refreshCells() {animate: true/false} // + rowRenderer: api softRefreshView() {} CellComp.prototype.refreshCell = function (params) { if (this.editingCell) { return; } var newData = params && params.newData; var suppressFlash = (params && params.suppressFlash) || this.column.getColDef().suppressCellFlash; var forceRefresh = params && params.forceRefresh; var oldValue = this.value; this.getValueAndFormat(); // for simple values only (not pojo's), see if the value is the same, and if it is, skip the refresh. // when never allow skipping after an edit, as after editing, we need to put the GUI back to the way // if was before the edit. var valuesDifferent = !this.valuesAreEqual(oldValue, this.value); var dataNeedsUpdating = forceRefresh || valuesDifferent; if (dataNeedsUpdating) { var cellRendererRefreshed = void 0; // if it's 'new data', then we don't refresh the cellRenderer, even if refresh method is available. // this is because if the whole data is new (ie we are showing stock price 'BBA' now and not 'SSD') // then we are not showing a movement in the stock price, rather we are showing different stock. if (newData || suppressFlash) { cellRendererRefreshed = false; } else { cellRendererRefreshed = this.attemptCellRendererRefresh(); } // we do the replace if not doing refresh, or if refresh was unsuccessful. // the refresh can be unsuccessful if we are using a framework (eg ng2 or react) and the framework // wrapper has the refresh method, but the underlying component doesn't if (!cellRendererRefreshed) { this.replaceContentsAfterRefresh(); } if (!suppressFlash) { var flashCell = this.beans.gridOptionsWrapper.isEnableCellChangeFlash() || this.column.getColDef().enableCellChangeFlash; if (flashCell) { this.flashCell(); } } // need to check rules. note, we ignore colDef classes and styles, these are assumed to be static this.postProcessStylesFromColDef(); this.postProcessClassesFromColDef(); } this.refreshToolTip(); // we do cellClassRules even if the value has not changed, so that users who have rules that // look at other parts of the row (where the other part of the row might of changed) will work. this.postProcessCellClassRules(); }; // user can also call this via API CellComp.prototype.flashCell = function () { this.animateCell('data-changed'); }; CellComp.prototype.animateCell = function (cssName) { var fullName = 'ag-cell-' + cssName; var animationFullName = 'ag-cell-' + cssName + '-animation'; var element = this.getGui(); // we want to highlight the cells, without any animation utils_1._.addCssClass(element, fullName); utils_1._.removeCssClass(element, animationFullName); // then once that is applied, we remove the highlight with animation setTimeout(function () { utils_1._.removeCssClass(element, fullName); utils_1._.addCssClass(element, animationFullName); setTimeout(function () { // and then to leave things as we got them, we remove the animation utils_1._.removeCssClass(element, animationFullName); }, 1000); }, 500); }; CellComp.prototype.replaceContentsAfterRefresh = function () { // otherwise we rip out the cell and replace it utils_1._.removeAllChildren(this.eParentOfValue); // remove old renderer component if it exists if (this.cellRenderer && this.cellRenderer.destroy) { this.cellRenderer.destroy(); } this.cellRenderer = null; this.cellRendererGui = null; // populate this.putDataIntoCellAfterRefresh(); this.angular1Compile(); }; CellComp.prototype.angular1Compile = function () { // if angular compiling, then need to also compile the cell again (angular compiling sucks, please wait...) if (this.beans.gridOptionsWrapper.isAngularCompileRows()) { var eGui = this.getGui(); var compiledElement_1 = this.beans.$compile(eGui)(this.scope); this.addDestroyFunc(function () { compiledElement_1.remove(); }); } }; CellComp.prototype.postProcessStylesFromColDef = function () { var stylesToUse = this.processStylesFromColDef(); if (stylesToUse) { utils_1._.addStylesToElement(this.getGui(), stylesToUse); } }; CellComp.prototype.preProcessStylesFromColDef = function () { var stylesToUse = this.processStylesFromColDef(); return utils_1._.cssStyleObjectToMarkup(stylesToUse); }; CellComp.prototype.processStylesFromColDef = function () { var colDef = this.column.getColDef(); if (colDef.cellStyle) { var cssToUse = void 0; if (typeof colDef.cellStyle === 'function') { var cellStyleParams = { value: this.value, data: this.rowNode.data, node: this.rowNode, colDef: colDef, column: this.column, $scope: this.scope, context: this.beans.gridOptionsWrapper.getContext(), api: this.beans.gridOptionsWrapper.getApi() }; var cellStyleFunc = colDef.cellStyle; cssToUse = cellStyleFunc(cellStyleParams); } else { cssToUse = colDef.cellStyle; } return cssToUse; } }; CellComp.prototype.postProcessClassesFromColDef = function () { var _this = this; this.processClassesFromColDef(function (className) { return utils_1._.addCssClass(_this.getGui(), className); }); }; CellComp.prototype.preProcessClassesFromColDef = function () { var res = []; this.processClassesFromColDef(function (className) { return res.push(className); }); return res; }; CellComp.prototype.processClassesFromColDef = function (onApplicableClass) { this.beans.stylingService.processStaticCellClasses(this.column.getColDef(), { value: this.value, data: this.rowNode.data, node: this.rowNode, colDef: this.column.getColDef(), rowIndex: this.rowNode.rowIndex, $scope: this.scope, api: this.beans.gridOptionsWrapper.getApi(), context: this.beans.gridOptionsWrapper.getContext() }, onApplicableClass); }; CellComp.prototype.putDataIntoCellAfterRefresh = function () { // template gets preference, then cellRenderer, then do it ourselves var colDef = this.column.getColDef(); if (colDef.template) { // template is really only used for angular 1 - as people using ng1 are used to providing templates with // bindings in it. in ng2, people will hopefully want to provide components, not templates. this.eParentOfValue.innerHTML = colDef.template; } else if (colDef.templateUrl) { // likewise for templateUrl - it's for ng1 really - when we move away from ng1, we can take these out. // niall was pro angular 1 when writing template and templateUrl, if writing from scratch now, would // not do these, but would follow a pattern that was friendly towards components, not templates. var template = this.beans.templateService.getTemplate(colDef.templateUrl, this.refreshCell.bind(this, true)); if (template) { this.eParentOfValue.innerHTML = template; } // use cell renderer if it exists } else if (this.usingCellRenderer) { this.attachCellRenderer(); } else { var valueToUse = this.getValueToUse(); if (valueToUse !== null && valueToUse !== undefined) { this.eParentOfValue.innerText = valueToUse; } } }; CellComp.prototype.attemptCellRendererRefresh = function () { if (utils_1._.missing(this.cellRenderer) || utils_1._.missing(this.cellRenderer.refresh)) { return false; } // if the cell renderer has a refresh method, we call this instead of doing a refresh // note: should pass in params here instead of value?? so that client has formattedValue var params = this.createCellRendererParams(); var result = this.cellRenderer.refresh(params); // NOTE on undefined: previous version of the cellRenderer.refresh() interface // returned nothing, if the method existed, we assumed it refreshed. so for // backwards compatibility, we assume if method exists and returns nothing, // that it was successful. return result === true || result === undefined; }; CellComp.prototype.refreshToolTip = function () { var newTooltip = this.getToolTip(); if (this.tooltip !== newTooltip) { this.tooltip = newTooltip; if (utils_1._.exists(newTooltip)) { var tooltipSanitised = utils_1._.escape(this.tooltip); this.eParentOfValue.setAttribute('title', tooltipSanitised); } else { this.eParentOfValue.removeAttribute('title'); } } }; CellComp.prototype.valuesAreEqual = function (val1, val2) { // if the user provided an equals method, use that, otherwise do simple comparison var colDef = this.column.getColDef(); var equalsMethod = colDef ? colDef.equals : null; if (equalsMethod) { return equalsMethod(val1, val2); } else { return val1 === val2; } }; CellComp.prototype.getToolTip = function () { var colDef = this.column.getColDef(); var data = this.rowNode.data; if (colDef.tooltipField && utils_1._.exists(data)) { return utils_1._.getValueUsingField(data, colDef.tooltipField, this.column.isTooltipFieldContainsDots()); } else if (colDef.tooltip) { return colDef.tooltip({ value: this.value, valueFormatted: this.valueFormatted, data: this.rowNode.data, node: this.rowNode, colDef: this.column.getColDef(), api: this.beans.gridOptionsWrapper.getApi(), $scope: this.scope, context: this.beans.gridOptionsWrapper.getContext(), rowIndex: this.gridCell.rowIndex }); } else { return null; } }; CellComp.prototype.processCellClassRules = function (onApplicableClass, onNotApplicableClass) { this.beans.stylingService.processClassRules(this.column.getColDef().cellClassRules, { value: this.value, data: this.rowNode.data, node: this.rowNode, colDef: this.column.getColDef(), rowIndex: this.gridCell.rowIndex, api: this.beans.gridOptionsWrapper.getApi(), $scope: this.scope, context: this.beans.gridOptionsWrapper.getContext() }, onApplicableClass, onNotApplicableClass); }; CellComp.prototype.postProcessCellClassRules = function () { var _this = this; this.processCellClassRules(function (className) { utils_1._.addCssClass(_this.getGui(), className); }, function (className) { utils_1._.removeCssClass(_this.getGui(), className); }); }; CellComp.prototype.preProcessCellClassRules = function () { var res = []; this.processCellClassRules(function (className) { res.push(className); }, function (className) { // not catered for, if creating, no need // to remove class as it was never there }); return res; }; // a wrapper is used when we are putting a selection checkbox in the cell with the value CellComp.prototype.setUsingWrapper = function () { var colDef = this.column.getColDef(); // never allow selection or dragging on pinned rows if (this.rowNode.rowPinned) { this.usingWrapper = false; this.includeSelectionComponent = false; this.includeRowDraggingComponent = false; return; } var cbSelectionIsFunc = typeof colDef.checkboxSelection === 'function'; var rowDraggableIsFunc = typeof colDef.rowDrag === 'function'; this.includeSelectionComponent = cbSelectionIsFunc || colDef.checkboxSelection === true; this.includeRowDraggingComponent = rowDraggableIsFunc || colDef.rowDrag === true; this.usingWrapper = this.includeRowDraggingComponent || this.includeSelectionComponent; }; CellComp.prototype.chooseCellRenderer = function () { // template gets preference, then cellRenderer, then do it ourselves var colDef = this.column.getColDef(); // templates are for ng1, ideally we wouldn't have these, they are ng1 support // inside the core which is bad if (colDef.template || colDef.templateUrl) { this.usingCellRenderer = false; return; } var params = this.createCellRendererParams(); var cellRenderer = this.beans.componentResolver.getComponentToUse(colDef, 'cellRenderer', params, null); var pinnedRowCellRenderer = this.beans.componentResolver.getComponentToUse(colDef, 'pinnedRowCellRenderer', params, null); if (pinnedRowCellRenderer && this.rowNode.rowPinned) { this.cellRendererType = 'pinnedRowCellRenderer'; this.usingCellRenderer = true; } else if (cellRenderer) { this.cellRendererType = 'cellRenderer'; this.usingCellRenderer = true; } else { this.usingCellRenderer = false; } }; CellComp.prototype.createCellRendererInstance = function () { var params = this.createCellRendererParams(); this.cellRendererVersion++; var callback = this.afterCellRendererCreated.bind(this, this.cellRendererVersion); this.beans.componentResolver.createAgGridComponent(this.column.getColDef(), params, this.cellRendererType, params, null).then(callback); }; CellComp.prototype.afterCellRendererCreated = function (cellRendererVersion, cellRenderer) { // see if daemon if (!this.isAlive() || (cellRendererVersion !== this.cellRendererVersion)) { if (cellRenderer.destroy) { cellRenderer.destroy(); } return; } this.cellRenderer = cellRenderer; this.cellRendererGui = this.cellRenderer.getGui(); if (utils_1._.missing(this.cellRendererGui)) { return; } // if async components, then it's possible the user started editing since // this call was made if (!this.editingCell) { this.eParentOfValue.appendChild(this.cellRendererGui); } }; CellComp.prototype.attachCellRenderer = function () { if (!this.usingCellRenderer) { return; } this.createCellRendererInstance(); }; CellComp.prototype.createCellRendererParams = function () { var _this = this; var params = { value: this.value, valueFormatted: this.valueFormatted, getValue: this.getValue.bind(this), setValue: function (value) { _this.beans.valueService.setValue(_this.rowNode, _this.column, value); }, formatValue: this.formatValue.bind(this), data: this.rowNode.data, node: this.rowNode, colDef: this.column.getColDef(), column: this.column, $scope: this.scope, rowIndex: this.gridCell.rowIndex, api: this.beans.gridOptionsWrapper.getApi(), columnApi: this.beans.gridOptionsWrapper.getColumnApi(), context: this.beans.gridOptionsWrapper.getContext(), refreshCell: this.refreshCell.bind(this), eGridCell: this.getGui(), eParentOfValue: this.eParentOfValue, // these bits are not documented anywhere, so we could drop them? // it was in the olden days to allow user to register for when rendered // row was removed (the row comp was removed), however now that the user // can provide components for cells, the destroy method gets call when this // happens so no longer need to fire event. addRowCompListener: this.rowComp ? this.rowComp.addEventListener.bind(this.rowComp) : null, addRenderedRowListener: function (eventType, listener) { console.warn('ag-Grid: since ag-Grid .v11, params.addRenderedRowListener() is now params.addRowCompListener()'); if (_this.rowComp) { _this.rowComp.addEventListener(eventType, listener); } } }; return params; }; CellComp.prototype.formatValue = function (value) { var valueFormatted = this.beans.valueFormatterService.formatValue(this.column, this.rowNode, this.scope, value); var valueFormattedExists = valueFormatted !== null && valueFormatted !== undefined; return valueFormattedExists ? valueFormatted : value; }; CellComp.prototype.getValueToUse = function () { var valueFormattedExists = this.valueFormatted !== null && this.valueFormatted !== undefined; return valueFormattedExists ? this.valueFormatted : this.value; }; CellComp.prototype.getValueAndFormat = function () { this.value = this.getValue(); this.valueFormatted = this.beans.valueFormatterService.formatValue(this.column, this.rowNode, this.scope, this.value); }; CellComp.prototype.getValue = function () { // if we don't check this, then the grid will render leaf groups as open even if we are not // allowing the user to open leaf groups. confused? remember for pivot mode we don't allow // opening leaf groups, so we have to force leafGroups to be closed in case the user expanded // them via the API, or user user expanded them in the UI before turning on pivot mode var lockedClosedGroup = this.rowNode.leafGroup && this.beans.columnController.isPivotMode(); var isOpenGroup = this.rowNode.group && this.rowNode.expanded && !this.rowNode.footer && !lockedClosedGroup; if (isOpenGroup && this.beans.gridOptionsWrapper.isGroupIncludeFooter()) { // if doing grouping and footers, we don't want to include the agg value // in the header when the group is open return this.beans.valueService.getValue(this.column, this.rowNode, false, true); } else { return this.beans.valueService.getValue(this.column, this.rowNode); } }; CellComp.prototype.onMouseEvent = function (eventName, mouseEvent) { if (utils_1._.isStopPropagationForAgGrid(mouseEvent)) { return; } switch (eventName) { case 'click': this.onCellClicked(mouseEvent); break; case 'mousedown': this.onMouseDown(mouseEvent); break; case 'dblclick': this.onCellDoubleClicked(mouseEvent); break; case 'mouseout': this.onMouseOut(mouseEvent); break; case 'mouseover': this.onMouseOver(mouseEvent); break; } }; CellComp.prototype.dispatchCellContextMenuEvent = function (event) { var colDef = this.column.getColDef(); var cellContextMenuEvent = this.createEvent(event, events_1.Events.EVENT_CELL_CONTEXT_MENU); this.beans.eventService.dispatchEvent(cellContextMenuEvent); if (colDef.onCellContextMenu) { // to make the callback async, do in a timeout setTimeout(function () { return colDef.onCellContextMenu(cellContextMenuEvent); }, 0); } }; CellComp.prototype.createEvent = function (domEvent, eventType) { var event = { node: this.rowNode, data: this.rowNode.data, value: this.value, column: this.column, colDef: this.column.getColDef(), context: this.beans.gridOptionsWrapper.getContext(), api: this.beans.gridApi, columnApi: this.beans.columnApi, rowPinned: this.rowNode.rowPinned, event: domEvent, type: eventType, rowIndex: this.rowNode.rowIndex }; // because we are hacking in $scope for angular 1, we have to de-reference if (this.scope) { event.$scope = this.scope; } return event; }; CellComp.prototype.onMouseOut = function (mouseEvent) { var cellMouseOutEvent = this.createEvent(mouseEvent, events_1.Events.EVENT_CELL_MOUSE_OUT); this.beans.eventService.dispatchEvent(cellMouseOutEvent); this.beans.columnHoverService.clearMouseOver(); }; CellComp.prototype.onMouseOver = function (mouseEvent) { var cellMouseOverEvent = this.createEvent(mouseEvent, events_1.Events.EVENT_CELL_MOUSE_OVER); this.beans.eventService.dispatchEvent(cellMouseOverEvent); this.beans.columnHoverService.setMouseOver([this.column]); }; CellComp.prototype.onCellDoubleClicked = function (mouseEvent) { var colDef = this.column.getColDef(); // always dispatch event to eventService var cellDoubleClickedEvent = this.createEvent(mouseEvent, events_1.Events.EVENT_CELL_DOUBLE_CLICKED); this.beans.eventService.dispatchEvent(cellDoubleClickedEvent); // check if colDef also wants to handle event if (typeof colDef.onCellDoubleClicked === 'function') { // to make the callback async, do in a timeout setTimeout(function () { return colDef.onCellDoubleClicked(cellDoubleClickedEvent); }, 0); } var editOnDoubleClick = !this.beans.gridOptionsWrapper.isSingleClickEdit() && !this.beans.gridOptionsWrapper.isSuppressClickEdit(); if (editOnDoubleClick) { this.startRowOrCellEdit(); } }; // called by rowRenderer when user navigates via tab key CellComp.prototype.startRowOrCellEdit = function (keyPress, charPress) { if (this.beans.gridOptionsWrapper.isFullRowEdit()) { this.rowComp.startRowEditing(keyPress, charPress, this); } else { this.startEditingIfEnabled(keyPress, charPress, true); } }; CellComp.prototype.isCellEditable = function () { return this.column.isCellEditable(this.rowNode); }; // either called internally if single cell editing, or called by rowRenderer if row editing CellComp.prototype.startEditingIfEnabled = function (keyPress, charPress, cellStartedEdit) { if (keyPress === void 0) { keyPress = null; } if (charPress === void 0) { charPress = null; } if (cellStartedEdit === void 0) { cellStartedEdit = false; } // don't do it if not editable if (!this.isCellEditable()) { return; } // don't do it if already editing if (this.editingCell) { return; } this.editingCell = true; this.cellEditorVersion++; var callback = this.afterCellEditorCreated.bind(this, this.cellEditorVersion); var params = this.createCellEditorParams(keyPress, charPress, cellStartedEdit); this.beans.cellEditorFactory.createCellEditor(this.column.getColDef(), params).then(callback); // if we don't do this, and editor component is async, then there will be a period // when the component isn't present and keyboard navigation won't work - so example // of user hitting tab quickly (more quickly than renderers getting created) won't work var cellEditorAsync = utils_1._.missing(this.cellEditor); if (cellEditorAsync && cellStartedEdit) { this.focusCell(true); } }; CellComp.prototype.afterCellEditorCreated = function (cellEditorVersion, cellEditor) { // if editingCell=false, means user cancelled the editor before component was ready. // if versionMismatch, then user cancelled the edit, then started the edit again, and this // is the first editor which is now stale. var versionMismatch = cellEditorVersion !== this.cellEditorVersion; if (versionMismatch || !this.editingCell) { if (cellEditor.destroy) { cellEditor.destroy(); } return; } if (cellEditor.isCancelBeforeStart && cellEditor.isCancelBeforeStart()) { if (cellEditor.destroy) { cellEditor.destroy(); } this.editingCell = false; return; } if (!cellEditor.getGui) { console.warn("ag-Grid: cellEditor for column " + this.column.getId() + " is missing getGui() method"); // no getGui, for React guys, see if they attached a react component directly if (cellEditor.render) { console.warn("ag-Grid: we found 'render' on the component, are you trying to set a React renderer but added it as colDef.cellEditor instead of colDef.cellEditorFmk?"); } if (cellEditor.destroy) { cellEditor.destroy(); } this.editingCell = false; return; } this.cellEditor = cellEditor; this.cellEditorInPopup = cellEditor.isPopup && cellEditor.isPopup(); this.setInlineEditingClass(); if (this.cellEditorInPopup) { this.addPopupCellEditor(); } else { this.addInCellEditor(); } if (cellEditor.afterGuiAttached) { cellEditor.afterGuiAttached(); } var event = this.createEvent(null, events_1.Events.EVENT_CELL_EDITING_STARTED); this.beans.eventService.dispatchEvent(event); }; CellComp.prototype.addInCellEditor = function () { utils_1._.removeAllChildren(this.getGui()); this.getGui().appendChild(this.cellEditor.getGui()); this.angular1Compile(); }; CellComp.prototype.addPopupCellEditor = function () { var _this = this; var ePopupGui = this.cellEditor.getGui(); this.hideEditorPopup = this.beans.popupService.addAsModalPopup(ePopupGui, true, // callback for when popup disappears function () { _this.onPopupEditorClosed(); }); this.beans.popupService.positionPopupOverComponent({ column: this.column, rowNode: this.rowNode, type: 'popupCellEditor', eventSource: this.getGui(), ePopup: ePopupGui, keepWithinBounds: true }); this.angular1Compile(); }; CellComp.prototype.onPopupEditorClosed = function () { // we only call stopEditing if we are editing, as // it's possible the popup called 'stop editing' // before this, eg if 'enter key' was pressed on // the editor. if (this.editingCell) { // note: this only happens when use clicks outside of the grid. if use clicks on another // cell, then the editing will have already stopped on this cell this.stopRowOrCellEdit(); // we only focus cell again if this cell is still focused. it is possible // it is not focused if the user cancelled the edit by clicking on another // cell outside of this one if (this.beans.focusedCellController.isCellFocused(this.gridCell)) { this.focusCell(true); } } }; // if we are editing inline, then we don't have the padding in the cell (set in the themes) // to allow the text editor full access to the entire cell CellComp.prototype.setInlineEditingClass = function () { // ag-cell-inline-editing - appears when user is inline editing // ag-cell-not-inline-editing - appears when user is no inline editing // ag-cell-popup-editing - appears when user is editing cell in popup (appears on the cell, not on the popup) // note: one of {ag-cell-inline-editing, ag-cell-not-inline-editing} is always present, they toggle. // however {ag-cell-popup-editing} shows when popup, so you have both {ag-cell-popup-editing} // and {ag-cell-not-inline-editing} showing at the same time. var editingInline = this.editingCell && !this.cellEditorInPopup; var popupEditorShowing = this.editingCell && this.cellEditorInPopup; utils_1._.addOrRemoveCssClass(this.getGui(), "ag-cell-inline-editing", editingInline); utils_1._.addOrRemoveCssClass(this.getGui(), "ag-cell-not-inline-editing", !editingInline); utils_1._.addOrRemoveCssClass(this.getGui(), "ag-cell-popup-editing", popupEditorShowing); utils_1._.addOrRemoveCssClass(this.getGui().parentNode, "ag-row-inline-editing", editingInline); utils_1._.addOrRemoveCssClass(this.getGui().parentNode, "ag-row-not-inline-editing", !editingInline); }; CellComp.prototype.createCellEditorParams = function (keyPress, charPress, cellStartedEdit) { var params = { value: this.getValue(), keyPress: keyPress, charPress: charPress, column: this.column, rowIndex: this.gridCell.rowIndex, node: this.rowNode, api: this.beans.gridOptionsWrapper.getApi(), cellStartedEdit: cellStartedEdit, columnApi: this.beans.gridOptionsWrapper.getColumnApi(), context: this.beans.gridOptionsWrapper.getContext(), $scope: this.scope, onKeyDown: this.onKeyDown.bind(this), stopEditing: this.stopEditingAndFocus.bind(this), eGridCell: this.getGui(), parseValue: this.parseValue.bind(this), formatValue: this.formatValue.bind(this) }; return params; }; // cell editors call this, when they want to stop for reasons other // than what we pick up on. eg selecting from a dropdown ends editing. CellComp.prototype.stopEditingAndFocus = function (suppressNavigateAfterEdit) { if (suppressNavigateAfterEdit === void 0) { suppressNavigateAfterEdit = false; } this.stopRowOrCellEdit(); this.focusCell(true); if (!suppressNavigateAfterEdit) { this.navigateAfterEdit(); } }; CellComp.prototype.parseValue = function (newValue) { var params = { node: this.rowNode, data: this.rowNode.data, oldValue: this.value, newValue: newValue, colDef: this.column.getColDef(), column: this.column, api: this.beans.gridOptionsWrapper.getApi(), columnApi: this.beans.gridOptionsWrapper.getColumnApi(), context: this.beans.gridOptionsWrapper.getContext() }; var valueParser = this.column.getColDef().valueParser; return utils_1._.exists(valueParser) ? this.beans.expressionService.evaluate(valueParser, params) : newValue; }; CellComp.prototype.focusCell = function (forceBrowserFocus) { if (forceBrowserFocus === void 0) { forceBrowserFocus = false; } this.beans.focusedCellController.setFocusedCell(this.gridCell.rowIndex, this.column, this.rowNode.rowPinned, forceBrowserFocus); }; CellComp.prototype.setFocusInOnEditor = function () { if (this.editingCell) { if (this.cellEditor && this.cellEditor.focusIn) { // if the editor is present, then we just focus it this.cellEditor.focusIn(); } else { // if the editor is not present, it means async cell editor (eg React fibre) // and we are trying to set focus before the cell editor is present, so we // focus the cell instead this.focusCell(true); } } }; CellComp.prototype.isEditing = function () { return this.editingCell; }; CellComp.prototype.onKeyDown = function (event) { var key = event.which || event.keyCode; // give user a chance to cancel event processing if (this.doesUserWantToCancelKeyboardEvent(event)) { return; } switch (key) { case constants_1.Constants.KEY_ENTER: this.onEnterKeyDown(); break; case constants_1.Constants.KEY_F2: this.onF2KeyDown(); break; case constants_1.Constants.KEY_ESCAPE: this.onEscapeKeyDown(); break; case constants_1.Constants.KEY_TAB: this.onTabKeyDown(event); break; case constants_1.Constants.KEY_BACKSPACE: case constants_1.Constants.KEY_DELETE: this.onBackspaceOrDeleteKeyPressed(key); break; case constants_1.Constants.KEY_DOWN: case constants_1.Constants.KEY_UP: case constants_1.Constants.KEY_RIGHT: case constants_1.Constants.KEY_LEFT: this.onNavigationKeyPressed(event, key); break; } }; CellComp.prototype.doesUserWantToCancelKeyboardEvent = function (event) { var callback = this.column.getColDef().suppressKeyboardEvent; if (utils_1._.missing(callback)) { return false; } else { // if editing is null or undefined, this sets it to false var params = { event: event, editing: this.editingCell, column: this.column, api: this.beans.gridOptionsWrapper.getApi(), node: this.rowNode, data: this.rowNode.data, colDef: this.column.getColDef(), context: this.beans.gridOptionsWrapper.getContext(), columnApi: this.beans.gridOptionsWrapper.getColumnApi() }; return callback(params); } }; CellComp.prototype.setFocusOutOnEditor = function () { if (this.editingCell && this.cellEditor && this.cellEditor.focusOut) { this.cellEditor.focusOut(); } }; CellComp.prototype.onNaviga