@adaptabletools/adaptable-cjs
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
941 lines (938 loc) • 168 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdaptableAgGrid = void 0;
const tslib_1 = require("tslib");
const throttle_1 = tslib_1.__importDefault(require("lodash/throttle"));
const debounce_1 = tslib_1.__importDefault(require("lodash/debounce"));
const ag_grid_enterprise_1 = require("ag-grid-enterprise");
const AdaptableLogger_1 = require("./AdaptableLogger");
const DocumentationLinkConstants_1 = require("../Utilities/Constants/DocumentationLinkConstants");
const StringExtensions_1 = tslib_1.__importDefault(require("../Utilities/Extensions/StringExtensions"));
const Emitter_1 = tslib_1.__importDefault(require("../Utilities/Emitter"));
const DefaultAdaptableOptions_1 = require("../AdaptableOptions/DefaultAdaptableOptions");
const AgGridAdapter_1 = require("./AgGridAdapter");
const GeneralConstants = tslib_1.__importStar(require("../Utilities/Constants/GeneralConstants"));
const GeneralConstants_1 = require("../Utilities/Constants/GeneralConstants");
const DataService_1 = require("../Utilities/Services/DataService");
const AdaptableStore_1 = require("../Redux/Store/AdaptableStore");
const AdaptableApiImpl_1 = require("../Api/Implementation/AdaptableApiImpl");
const Fdc3Service_1 = require("../Utilities/Services/Fdc3Service");
const AnnotationsService_1 = require("../Utilities/Services/AnnotationsService");
const ChartingService_1 = require("../Utilities/Services/ChartingService");
const ThemeService_1 = require("../Utilities/Services/ThemeService");
const ValidationService_1 = require("../Utilities/Services/ValidationService");
const ModuleService_1 = require("../Utilities/Services/ModuleService");
const CalculatedColumnExpressionService_1 = require("../Utilities/Services/CalculatedColumnExpressionService");
const QueryLanguageService_1 = require("../Utilities/Services/QueryLanguageService");
const AlertService_1 = require("../Utilities/Services/AlertService");
const TeamSharingService_1 = require("../Utilities/Services/TeamSharingService");
const MetamodelService_1 = require("../Utilities/Services/MetamodelService");
const LicenseService_1 = require("../Utilities/Services/LicenseService");
const Types_1 = require("../AdaptableState/Common/Types");
const ModuleConstants = tslib_1.__importStar(require("../Utilities/Constants/ModuleConstants"));
const ModuleConstants_1 = require("../Utilities/Constants/ModuleConstants");
const DashboardModule_1 = require("../Strategy/DashboardModule");
const AlertModule_1 = require("../Strategy/AlertModule");
const FlashingCellModule_1 = require("../Strategy/FlashingCellModule");
const BulkUpdateModule_1 = require("../Strategy/BulkUpdateModule");
const CalculatedColumnModule_1 = require("../Strategy/CalculatedColumnModule");
const CellSummaryModule_1 = require("../Strategy/CellSummaryModule");
const CustomSortModule_1 = require("../Strategy/CustomSortModule");
const DataChangeHistoryModule_1 = require("../Strategy/DataChangeHistoryModule");
const DataImportModule_1 = require("../Strategy/DataImportModule");
const DataSetModule_1 = require("../Strategy/DataSetModule");
const ExportModule_1 = require("../Strategy/ExportModule");
const ColumnFilterModule_1 = require("../Strategy/ColumnFilterModule");
const FormatColumnModule_1 = require("../Strategy/FormatColumnModule");
const FreeTextColumnModule_1 = require("../Strategy/FreeTextColumnModule");
const LayoutModule_1 = require("../Strategy/LayoutModule");
const PlusMinusModule_1 = require("../Strategy/PlusMinusModule");
const QuickSearchModule_1 = require("../Strategy/QuickSearchModule");
const ScheduleModule_1 = require("../Strategy/ScheduleModule");
const SmartEditModule_1 = require("../Strategy/SmartEditModule");
const ShortcutModule_1 = require("../Strategy/ShortcutModule");
const StateManagementModule_1 = require("../Strategy/StateManagementModule");
const TeamSharingModule_1 = require("../Strategy/TeamSharingModule");
const ToolPanelModule_1 = require("../Strategy/ToolPanelModule");
const SystemStatusModule_1 = require("../Strategy/SystemStatusModule");
const ThemeModule_1 = require("../Strategy/ThemeModule");
const GridInfoModule_1 = require("../Strategy/GridInfoModule");
const ColumnInfoModule_1 = require("../Strategy/ColumnInfoModule");
const SettingsPanelModule_1 = require("../Strategy/SettingsPanelModule");
const StatusBarModule_1 = require("../Strategy/StatusBarModule");
const ChartingModule_1 = require("../Strategy/ChartingModule");
const NoteModule_1 = require("../Strategy/NoteModule");
const StyledColumnModule_1 = require("../Strategy/StyledColumnModule");
const Fdc3Module_1 = require("../Strategy/Fdc3Module");
const GridFilterModule_1 = require("../Strategy/GridFilterModule");
const NamedQueryModule_1 = require("../Strategy/NamedQueryModule");
const CommentModule_1 = require("../Strategy/CommentModule");
const Helper_1 = require("../Utilities/Helpers/Helper");
const uuid_1 = require("../components/utils/uuid");
const UIHelper_1 = tslib_1.__importDefault(require("../View/UIHelper"));
const AdaptableToolPanel_1 = require("../View/Components/ToolPanel/AdaptableToolPanel");
const StatusBarState_1 = require("../AdaptableState/StatusBarState");
const AdaptableStatusBar_1 = require("../View/StatusBar/AdaptableStatusBar");
const ArrayExtensions_1 = tslib_1.__importDefault(require("../Utilities/Extensions/ArrayExtensions"));
const AgGridMenuAdapter_1 = require("./AgGridMenuAdapter");
const AdaptableView_1 = require("../View/AdaptableView");
const renderReactRoot_1 = require("../renderReactRoot");
const AgGridOptionsService_1 = require("./AgGridOptionsService");
const DateHelper_1 = require("../Utilities/Helpers/DateHelper");
const AgGridColumnAdapter_1 = require("./AgGridColumnAdapter");
const getScrollbarSize_1 = tslib_1.__importDefault(require("../Utilities/getScrollbarSize"));
const AggregationColumns_1 = require("../AdaptableState/Common/AggregationColumns");
const RowFormService_1 = require("../Utilities/Services/RowFormService");
const Enums_1 = require("../AdaptableState/Common/Enums");
const EnvVars_1 = require("../EnvVars");
const AdaptableUpgradeHelper_1 = require("../migration/AdaptableUpgradeHelper");
const Modal_1 = require("../components/Modal");
const AdaptableLoadingScreen_1 = require("../View/Components/Popups/AdaptableLoadingScreen");
const react_1 = require("react");
const createAgStatusPanelComponent_1 = require("../Utilities/createAgStatusPanelComponent");
const weightedAverage_1 = require("../Utilities/weightedAverage");
const RowSummary_1 = require("../AdaptableState/Common/RowSummary");
const FlashingCellService_1 = require("../Utilities/Services/FlashingCellService");
const AgGridExportAdapter_1 = require("./AgGridExportAdapter");
const LayoutHelpers_1 = require("../Api/Implementation/LayoutHelpers");
const src_1 = require("../layout-manager/src");
const isPivotLayoutModel_1 = require("../layout-manager/src/isPivotLayoutModel");
const AdaptableColumn_1 = require("../AdaptableState/Common/AdaptableColumn");
const agGridDataTypeDefinitions_1 = require("./agGridDataTypeDefinitions");
const AgGridThemeAdapter_1 = require("./AgGridThemeAdapter");
const VersionUpgrade20_1 = require("../migration/VersionUpgrade20");
const adaptableOverrideCheck_1 = require("../Utilities/adaptableOverrideCheck");
const LocalEventService_Prototype = ag_grid_enterprise_1.LocalEventService.prototype;
const LocalEventService_dispatchEvent = LocalEventService_Prototype.dispatchEvent;
LocalEventService_Prototype.dispatchEvent = function (event) {
const agGridApi = event.api;
if (agGridApi?.isDestroyed()) {
// do nothing if AG Grid was destroyed in the meantime
return;
}
LocalEventService_dispatchEvent.apply(this, arguments);
if (event.type === 'cellChanged' || event.type === 'dataChanged') {
const eventRowNode = event.node;
const extractGridApiFromRowNode = (rowNode) => {
const rowNodeApi = rowNode?.beans?.gridApi;
if (!rowNodeApi) {
AdaptableLogger_1.AdaptableLogger.consoleErrorBase(`No GridAPI found in passed RowNode, this should never happen!`, rowNode);
}
return rowNodeApi;
};
// we don't know from which instance of aggrid this is coming,
// as this fn is shared by all instances
if (eventRowNode) {
AdaptableAgGrid.forEachAdaptable((adaptable) => {
if (extractGridApiFromRowNode(eventRowNode) !== adaptable.agGridAdapter?.getAgGridApi(true)) {
// the event is coming from another aggrid instance
// so IGNORE IT
return;
}
if (adaptable.isDestroyed) {
// do nothing if adaptable is destroyed (this is a rare case and happens when Adaptable is quickly destroyed and recreated)
return;
}
// we're on the correct instance, so do this
//@ts-ignore
const fn = adaptable.rowListeners ? adaptable.rowListeners[event.type] : null;
if (fn) {
fn(event);
}
});
}
}
};
const adaptableInstances = {};
const publishTimestamp = Number(EnvVars_1.ADAPTABLE_PUBLISH_TIMESTAMP);
class AdaptableAgGrid {
constructor(config) {
this.columnMinMaxValuesCache = {};
this.renderReactRoot = (node, container) => (0, renderReactRoot_1.renderReactRoot)(node, container);
/**
* Temporary, these are MIGRATION technical debts, and should be removed as soon as possible
*/
this.adaptableStatusPanelKeys = [];
// only for our private / internal events used within Adaptable
// public events are emitted through the EventApi
this._emit = (eventName, data) => {
if (this.emitter) {
return this.emitter.emit(eventName, data);
}
};
this._emitSync = (eventName, data) => {
if (this.emitter) {
return this.emitter.emitSync(eventName, data);
}
};
this._on = (eventName, callback) => {
if (!this.emitter) {
return () => { };
}
return this.emitter.on(eventName, callback);
};
this._onIncludeFired = (eventName, callback) => {
if (!this.emitter) {
return () => { };
}
return this.emitter.onIncludeFired(eventName, callback);
};
this.lifecycleState = 'initial';
this.emitter = new Emitter_1.default();
this.agGridOptionsService = new AgGridOptionsService_1.AgGridOptionsService(this);
this.agGridAdapter = new AgGridAdapter_1.AgGridAdapter(this, config);
this.agGridMenuAdapter = new AgGridMenuAdapter_1.AgGridMenuAdapter(this);
this.agGridColumnAdapter = new AgGridColumnAdapter_1.AgGridColumnAdapter(this);
this.agGridExportAdapter = new AgGridExportAdapter_1.AgGridExportAdapter(this);
this.agGridThemeAdapter = new AgGridThemeAdapter_1.AgGridThemeAdapter(this);
this.DataService = new DataService_1.DataService(this);
}
static forEachAdaptable(fn) {
Object.keys(adaptableInstances).forEach((key) => {
fn(adaptableInstances[key]);
});
}
static collectInstance(adaptable, adaptableId) {
adaptable._id = adaptableId;
adaptableInstances[adaptable._id] = adaptable;
}
static dismissInstance(adaptable) {
delete adaptableInstances[adaptable._id];
}
get isAgGridInitialising() {
return this.lifecycleState === 'initAgGrid';
}
get isReady() {
return this.lifecycleState === 'ready';
}
get isAvailable() {
return this.lifecycleState === 'available' || this.lifecycleState === 'ready';
}
get isDestroyed() {
return this.lifecycleState === 'preDestroyed';
}
/**
* Internal initializer for Adaptable, directly called by the React and Angular Adaptable wrappers
* @private
*/
static async _initInternal(config) {
let promise = null;
if (Array.isArray(config.adaptableOptions.plugins)) {
const agGridOptions = {
gridOptions: config.gridOptions,
modules: config.modules,
};
for (let plugin of config.adaptableOptions.plugins) {
promise =
promise && promise.then
? promise.then(() => {
return plugin.beforeInit(config.adaptableOptions, agGridOptions);
})
: plugin.beforeInit(config.adaptableOptions, agGridOptions);
}
// if gridOptions changed, we need to update the runtimeConfig
if (agGridOptions.gridOptions !== config.gridOptions) {
// This allows plugins to modify
// FIXME AFL MIG: clarify if this is still needed (for NoCode Plugin?)
// it looks like a code smell, ideally we should get rid of it
config.gridOptions = agGridOptions.gridOptions;
}
}
const doInit = (adaptableInstance) => {
return adaptableInstance._initAdaptableAgGrid(config).then((api) => {
if (Array.isArray(config.adaptableOptions.plugins)) {
config.adaptableOptions.plugins.forEach((plugin) => {
plugin.afterInit(adaptableInstance);
});
}
return api;
});
};
if (promise && promise.then) {
return promise.then(() => {
const adaptableInstance = new AdaptableAgGrid({
getAgGridColumnApiModuleReference: config.getAgGridColumnApiModuleReference,
});
return doInit(adaptableInstance);
});
}
else {
const adaptableInstance = new AdaptableAgGrid({
getAgGridColumnApiModuleReference: config.getAgGridColumnApiModuleReference,
});
return doInit(adaptableInstance);
}
}
async _initAdaptableAgGrid(config) {
// Phase 1: Preprocess Adaptable Options
this._isDetailGrid = config.isDetailGrid === true;
this._isDetailGridForIndex = config.isDetailGridForRowIndex;
this.lifecycleState = 'preprocessOptions';
this._rawAdaptableOptions = config.adaptableOptions;
if (StringExtensions_1.default.IsNullOrEmptyOrWhiteSpace(this._rawAdaptableOptions.adaptableId)) {
this._rawAdaptableOptions.adaptableId = `adaptable_id_${Date.now()}`;
}
this.logger = this.logger ?? new AdaptableLogger_1.AdaptableLogger(this._rawAdaptableOptions.adaptableId);
const perfInitAdaptableAgGrid = this.logger.beginPerf(`Adaptable._initAdaptableAgGrid()`);
AdaptableAgGrid.collectInstance(this, this._rawAdaptableOptions.adaptableId);
this.variant = config.variant;
this.initWithLazyData =
config.gridOptions.rowData == undefined || config.gridOptions.rowData.length === 0;
this.hasAutogeneratedPrimaryKey = !!this._rawAdaptableOptions.autogeneratePrimaryKey;
this.adaptableOptions = (0, DefaultAdaptableOptions_1.applyDefaultAdaptableOptions)(this._rawAdaptableOptions);
this.adaptableOptions = this.normalizeAdaptableOptions(this.adaptableOptions);
const { showLoadingScreen, loadingScreenDelay, loadingScreenText, loadingScreenTitle } = this.adaptableOptions.userInterfaceOptions.loadingScreenOptions;
if (showLoadingScreen) {
this.logger.info(`Show Loading Screen`);
// it's important to use ensureLoadingScreenPortalElement
// and not ensurePortalElement, because multiple adaptable instances share the same portal element
// so when displaying the second one, the react root associated to the portal element
// seems to be somewhat shared via the html element, so the portal element of the first one is destroyed
// resulting in the settings popup not being displayed anymore
const portalElement = (0, Modal_1.ensureLoadingScreenPortalElement)();
if (portalElement) {
this.unmountLoadingScreen = this.renderReactRoot((0, react_1.createElement)(AdaptableLoadingScreen_1.AdaptableLoadingScreen, {
showLoadingScreen,
loadingScreenDelay,
loadingScreenText,
loadingScreenTitle,
}), portalElement);
}
else {
this.logger.consoleError(`Adaptable failed to show the loading screen!`);
}
}
this.forPlugins((plugin) => plugin.afterInitOptions(this, this.adaptableOptions));
this.api = new AdaptableApiImpl_1.AdaptableApiImpl(this);
this.forPlugins((plugin) => plugin.afterInitApi(this, this.api));
this.lifecycleState = 'initAdaptableState';
this.initServices();
this.forPlugins((plugin) => plugin.afterInitServices(this));
this.adaptableModules = this.initModules();
this.forPlugins((plugin) => plugin.afterInitModules(this, this.adaptableModules));
const perfLoadStore = this.logger.beginPerf(`loadStore()`);
this.adaptableStore = this.initAdaptableStore();
this.forPlugins((plugin) => plugin.afterInitStore(this));
await this.adaptableStore.loadStore({
adaptable: this,
adaptableStateKey: this.adaptableOptions.adaptableStateKey,
/**
* This method is called after the store is loaded;
* it allows to modify the state before it is used by the application
* e.g. migrating deprecated state, etc.
*/
postLoadHook: (state) => {
if (this.adaptableOptions.stateOptions.autoMigrateState) {
this.api.logError;
const config = {
// version 16 actually includes all versions up until 16
fromVersion: 16,
logger: this.logger,
};
state = AdaptableUpgradeHelper_1.AdaptableUpgradeHelper.migrateAdaptableState(state, config);
}
state = this.normalizeAdaptableState(state, config.gridOptions);
return state;
},
});
perfLoadStore.end();
// just in case Adaptable was destroyed while loading the store (which is an async operation)
if (this.isDestroyed) {
this.midwayDestroy();
return Promise.reject('Adaptable was destroyed while loading the store.');
// FIXME AFL MIG: is this enough?! talk with the team
}
this.forPlugins((plugin) => plugin.afterInitialStateLoaded(this));
// do this now so it sets module entitlements
this.api.entitlementApi.internalApi.setModulesEntitlements();
/**
* At this point it's mandatory to have the ALL the Adaptable blocks initialized:
* Store, APIs, Services, Modules
*/
this.lifecycleState = 'setupAgGrid';
const gridOptions = config.gridOptions;
// Needed here because special column defs are required for deriving the adaptable column state
const columnDefs = this.agGridAdapter.getColumnDefinitionsInclSpecialColumns(gridOptions.columnDefs || []);
gridOptions.columnDefs = columnDefs;
this.setInitialGridOptions(gridOptions, config.variant);
const { gridState: initialGridState, layoutModel } = this.mapAdaptableStateToAgGridState(this.adaptableStore.TheStore.getState(), gridOptions.columnDefs, { isTree: !!gridOptions.treeData });
gridOptions.initialState = initialGridState;
if (layoutModel) {
if ((0, isPivotLayoutModel_1.isPivotLayoutModel)(layoutModel)) {
gridOptions.pivotDefaultExpanded = layoutModel.PivotExpandLevel;
}
else {
gridOptions.groupDisplayType =
layoutModel.RowGroupDisplayType === 'multi' ? 'multipleColumns' : 'singleColumn';
}
}
this.lifecycleState = 'initAgGrid';
this.agGridAdapter.initialGridOptions = gridOptions;
const perfInitAgGrid = this.logger.beginPerf(`initAgGrid()`);
// AG Grid evaluates early on the floatingFilter params, so we need to "suppress" the floating filter temporarily
// we will reset it once Adaptable is ready
this.agGridColumnAdapter.setupColumnFloatingFilterTemporarily(gridOptions);
this.validateColumnDefTypes(gridOptions.columnDefs);
const agGridApi = await this.initializeAgGrid(gridOptions, config.modules, config.renderAgGridFrameworkComponent);
if (agGridApi === false) {
this.midwayDestroy();
this.logger.consoleError(`Adaptable failed to initialize AG Grid!`);
return Promise.reject('Adaptable failed to initialize AG Grid!');
}
this.layoutManager = new src_1.LayoutManager({
gridApi: agGridApi,
debugId: this.adaptableOptions.adaptableId,
});
this.silentUpdateCurrentLayoutModel(layoutModel);
// this shouldn't be needed
// but AG Grid has a bug, and in pivot layout,
// even if we provide an initial AG Grid state with
// the aggregations in the correct order,
// they will end up in the wrong order
// so we need to force the layout to be applied again
if ((0, isPivotLayoutModel_1.isPivotLayoutModel)(layoutModel)) {
this.layoutManager.setLayout(layoutModel, {
force: true,
});
}
this.layoutManager.onChange((layoutModel) => {
const newLayoutObject = (0, LayoutHelpers_1.layoutModelToLayoutState)(layoutModel);
this.onLayoutChange(newLayoutObject);
});
this.layoutManager.onColumnDefsChanged(() => {
this.updateColumnModelAndRefreshGrid();
});
this.logger.info(`Hide Loading Screen`);
this.unmountLoadingScreen?.();
perfInitAgGrid.end();
// we need to intercept several AG Grid Api methods and trigger Adaptable state changes
this.agGridAdapter.setAgGridApi(agGridApi);
this.agGridAdapter.monkeyPatchingGridOptionsUpdates();
this.agGridAdapter.monkeyPatchingAggColumnFilters();
this.lifecycleState = 'agGridReady';
this.logger.info(`Registered AG Grid modules: `, this.agGridAdapter.getAgGridRegisteredModuleNames().sort());
/**
* At this point AG Grid is initialized!
*/
this.deriveAdaptableColumnStateFromAgGrid();
this.agGridColumnAdapter.setupColumns();
// we need this because we need the internal Column state to be ready before doing any extra business logic
this.lifecycleState = 'available';
this.api.themeApi.applyCurrentTheme();
this.validatePrimaryKey();
// TODO AFL MIG: we could just patch the defautl Layout on init? instead
this.checkShouldClearExistingFiltersOrSearches();
this.applyFiltering();
// apply quick search if there is one
// yes, we could have put this on the gridOptions.findSearchValue
// but we need to wait for setupColumns to run first so as to correctly
// determine on which columns the quick search should be applied or not
// and also to setup the quick search style
const quickSearchState = this.api.stateApi.getQuickSearchState();
if (quickSearchState.QuickSearchText) {
this.setAgGridFindSearchValue(quickSearchState.QuickSearchText);
}
this.addGridEventListeners();
this.temporaryAdaptableStateUpdates();
this.redrawBody();
this.refreshHeader();
const currentLayout = this.api.layoutApi.getCurrentLayout();
(0, LayoutHelpers_1.checkForDuplicateColumns)(currentLayout);
this._prevLayout = currentLayout;
this.__prevLayoutForRefresh = currentLayout;
if ((0, LayoutHelpers_1.isPivotLayout)(currentLayout)) {
// this is very very strange!
// for some projects, if the initial layout is pivot, the columnDefs of the pivot resutl columns are NOT derived correctly from the main colDefs
// doing the following line fixes the issue because it foces the pivot columns to be created again
// this proj works without the hack: /tests/pages/format-column/initial-pivot-layout.page.tsx
// but this proj needs the hack: /tests/pages/format-column/initial-pivot-layout-docs.page.tsx
this.agGridAdapter.setGridOption('pivotMode', false);
this.agGridAdapter.setGridOption('pivotMode', true);
// also quick search is not working initially, although the setupColumns is called correctly
// so we need to do this to make it work
// see test /tests/pages/quick-search/pivot-search.spec.ts
this.updateColumnModelAndRefreshGrid();
}
const layoutModelForApply = (0, LayoutHelpers_1.layoutStateToLayoutModel)(currentLayout);
this.layoutManager.applyRowGroupValues(layoutModelForApply.RowGroupValues, layoutModelForApply.RowGroupedColumns);
this.layoutManager.applyColumnGroupCollapseExpandState(layoutModelForApply);
this.autoSizeLayoutIfNeeded();
this.ModuleService.createModuleUIItems();
const adaptableContainerElem = this.getAdaptableContainerElement();
if (adaptableContainerElem != null) {
adaptableContainerElem.innerHTML = '';
if (this.variant === 'react') {
/**
* #no_additional_react_root
* This is only used for the React variant
* Where we don't want to create a new React render tree here
* by rendering it as a React root, but instead we want to
* render it as is in the React tree of our AdaptableReact component
*/
this._PRIVATE_adaptableJSXElement = (0, AdaptableView_1.AdaptableApp)({ Adaptable: this });
}
else {
this.unmountReactRoot = this.renderReactRoot((0, AdaptableView_1.AdaptableApp)({ Adaptable: this }), adaptableContainerElem);
}
}
this.lifecycleState = 'ready';
this.forPlugins((plugin) => plugin.onAdaptableReady(this, this.adaptableOptions));
setTimeout(() => {
// without the setTimeout, calling autoSizeAllColumns immediately in the onAdaptableReady
// does not work. (I prefer setTimeout to rAF, as raf is not running when you switch tabs)
//
// it also makes it possible to listen to CALCULATED_COLUMN_READY, DASHBOARD_READY, etc.
// in onAdaptableReady - without this those event listeners are not triggered
this.api?.eventApi?.emit('AdaptableReady', {
adaptableApi: this.api,
agGridApi: this.agGridAdapter.getAgGridApi(),
});
});
perfInitAdaptableAgGrid.end();
return Promise.resolve(this.api);
}
midwayDestroy() {
this.destroy({
destroyAgGrid: false,
unmount: false,
});
}
normalizeAdaptableState(state, agGridOptions) {
state = this.normaliseLayoutState(state, agGridOptions);
state = this.normaliseToolPanelState(state);
return state;
}
normaliseLayoutState(state, gridOptions) {
const layoutState = state.Layout;
// ensure that at least one Layout has been provided
if (!layoutState || !layoutState.Layouts?.length) {
this.logger
.consoleError(`You have not defined any Layout in your InitialState.Layout.Layouts[] state!
You need to define at least one Layout!`);
}
// ensure CurrentLayout is valid
if (!layoutState.CurrentLayout ||
!layoutState.Layouts.find((l) => l.Name === layoutState.CurrentLayout)) {
layoutState.CurrentLayout = layoutState.Layouts?.[0]?.Name;
}
/**
* Viewport mode does not support a few AG Grid features which are contained in a Layout
* Accordingly we remove this when using this Row Model
*/
if (gridOptions.rowModelType === 'viewport') {
if (state.Layout.Layouts) {
state.Layout.Layouts = state.Layout.Layouts.filter((layout) => {
if ((0, LayoutHelpers_1.isPivotLayout)(layout)) {
return false;
}
if (layout.RowGroupedColumns) {
delete layout.RowGroupedColumns;
}
if (layout.TableAggregationColumns) {
delete layout.TableAggregationColumns;
}
return true;
});
}
}
if (state.Layout.Layouts) {
const normalizeOptions = {
isTree: !!gridOptions.treeData,
};
// it's very important that we do this here
// as the layout may not be fully specified in the initialState
// eg: might not include the generated row group columns in the column order
// but the normalization does this for us
state.Layout.Layouts = state.Layout.Layouts.map((layout) => (0, LayoutHelpers_1.normalizeLayout)(layout, normalizeOptions));
}
return state;
}
normaliseToolPanelState(state) {
if (state?.ToolPanel?.ToolPanels) {
return state;
}
// no Initial Adaptable State provided, we will display all the panels collapsed (custom & module)
const defaultToolPanels = [];
this.adaptableOptions.toolPanelOptions?.customToolPanels?.forEach((customToolPanel) => defaultToolPanels.push({ Name: customToolPanel.name }));
Types_1.ALL_TOOL_PANELS.forEach((moduleToolPanel) => defaultToolPanels.push({ Name: moduleToolPanel }));
const toolPanelState = state.ToolPanel || {};
toolPanelState.ToolPanels = defaultToolPanels;
state.ToolPanel = toolPanelState;
return state;
}
getCurrentLayoutModel() {
const currentLayout = this.api.layoutApi.getCurrentLayout();
if (!currentLayout) {
return null;
}
return (0, LayoutHelpers_1.layoutStateToLayoutModel)(currentLayout);
}
silentUpdateCurrentLayoutModel(layoutModel = this.getCurrentLayoutModel()) {
if (!layoutModel) {
return;
}
this.layoutManager.silentSetCurrentLayout(layoutModel, {
normalize: true,
});
}
applyFiltering() {
const agGridApi = this.agGridAdapter.getAgGridApi();
this._emit('AdapTableFiltersApplied');
this.refreshSelectedCellsState();
this.refreshSelectedRowsState();
this.agGridAdapter.updateColumnFilterActiveState();
agGridApi.onFilterChanged();
}
// refreshAgGridWithAdaptableState() {
// this.refreshColDefs();
// this.api.themeApi.applyCurrentTheme();
// this.api.internalApi.setTreeMode(this.agGridAdapter.initialGridOptions.treeData);
// this.checkShouldClearExistingFiltersOrSearches();
// this.applyColumnFiltering();
// }
showQuickFilter() {
const height = this.api.optionsApi.getFilterOptions().columnFilterOptions.quickFilterHeight;
this.agGridAdapter.getAgGridApi().setGridOption('floatingFiltersHeight', height);
}
hideQuickFilter() {
this.agGridAdapter.getAgGridApi().setGridOption('floatingFiltersHeight', 0);
}
normalizeAdaptableOptions(adaptableOptions) {
if (this.hasAutogeneratedPrimaryKey) {
this.logger
.warn(`Autogenerated primary key (adaptableOptions.autogeneratedPrimaryKey = TRUE) should be used only as a last resort,
when no unique column is available, as it limits some Adaptable functionalities!
For more details see: ${DocumentationLinkConstants_1.PrimaryKeyDocsLink}`);
this.adaptableOptions.primaryKey = GeneralConstants_1.AUTOGENERATED_PK_COLUMN;
return this.adaptableOptions;
}
if (StringExtensions_1.default.IsNullOrEmpty(adaptableOptions.primaryKey)) {
this.logger.consoleError(`AdaptableOptions.primaryKey is required and cannot be empty or null!
As a fallback, you can set adaptableOptions.autogeneratedPrimaryKey = TRUE
For more details see: ${DocumentationLinkConstants_1.PrimaryKeyDocsLink}`);
}
return adaptableOptions;
}
setInitialGridOptions(gridOptions, variant) {
/**
* set Adaptable instance on the AG Grid context
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'context', (original_context) => {
const userContext = original_context || {};
return {
...userContext,
__adaptable: this,
adaptableApi: this.api,
};
});
/**
* `gridId`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'gridId', (original_gridId) => {
let agGridId = original_gridId || this.adaptableOptions.adaptableId;
if (this._isDetailGridForIndex != null) {
agGridId = `${agGridId}_detail-${this._isDetailGridForIndex}`;
}
this.agGridAdapter.setAgGridId(agGridId);
return agGridId;
});
/**
* `defaultColDef`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'defaultColDef', (original_defaultColDef) => {
if (original_defaultColDef?.headerValueGetter) {
this.logger.warn(`defaultColDef.headerValueGetter and overrides the Adaptable custom header mechanism! We recommend using a ColumnOptions.columnHeader instead!`);
return original_defaultColDef;
}
// #customize_header
const defaultColDef = { ...original_defaultColDef };
defaultColDef.headerValueGetter = (0, adaptableOverrideCheck_1.tagProvidedByAdaptable)((params) => {
const columnHeaderName = this.api.columnApi.internalApi.getColumnHeaderName(params);
const formattedHeaderName = this.api.formatColumnApi.internalApi.formatColumnHeaderName(columnHeaderName, params);
return formattedHeaderName;
});
return defaultColDef;
});
/**
* `defaultColGroupDef`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'defaultColGroupDef', (original_defaultColGroupDef) => {
if (original_defaultColGroupDef?.headerValueGetter) {
this.logger.warn(`defaultColGroupDef.headerValueGetter and overrides the Adaptable custom header mechanism! We recommend using a ColumnOptions.tableColumnHeader instead!`);
return original_defaultColGroupDef;
}
const defaultColGroupDef = { ...original_defaultColGroupDef };
defaultColGroupDef.headerValueGetter = (0, adaptableOverrideCheck_1.tagProvidedByAdaptable)((params) => {
return this.api.columnApi.internalApi.getColumnHeaderName(params);
});
return defaultColGroupDef;
});
/**
* `theme`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'theme', (original_theme) => {
this.agGridThemeAdapter.setAgGridThemeMode(original_theme === 'legacy' ? 'legacy' : 'themingApi');
return original_theme;
});
/**
* `getRowId`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'getRowId', (original_getRowId) => {
if (original_getRowId) {
return original_getRowId;
}
const primaryKey = this.adaptableOptions.primaryKey;
if (StringExtensions_1.default.IsNullOrEmpty(primaryKey)) {
// if no valid PK then do nothing
return original_getRowId;
}
if (this.hasAutogeneratedPrimaryKey) {
return (params) => {
// if the PK value is autogenerated, we need to make sure that the rowData has a valid PK value
// this should be taken care of in the Adaptable.[loadDataSource/setDataSource/updateRows]() methods, but the users will always make silly decisions
// so just to be safe we'll check here and add a PK value is missing
// thus adding a side-effect in a getter, but what can we do against it?! :)
if (Helper_1.Helper.objectNotExists(params.data[primaryKey])) {
params.data[primaryKey] = (0, uuid_1.createUuid)();
}
return params.data[primaryKey];
};
}
return (params) => {
if (params.data?.[primaryKey]) {
const primaryKeyValue = params.data[primaryKey];
return typeof primaryKeyValue === 'number'
? `${primaryKeyValue}`
: params.data[primaryKey];
}
// might be a summary row
if (params.data?.[RowSummary_1.ROW_SUMMARY_ROW_ID]) {
return params.data[RowSummary_1.ROW_SUMMARY_ROW_ID];
}
// AFL 2024.08.17 - no idea why is this here and when it's used
// might be a group row
const parentKeys = params.parentKeys ?? [];
const values = Object.values(params.data);
if (values.length) {
const id = [...parentKeys, values[0]].join('/');
return id;
}
};
});
// this is necessary here for the initialisation of the LayoutManager
/**
* `grandTotalRow`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'grandTotalRow', (original_grandTotalRow) => {
const currentLayout = this.api.layoutApi.getCurrentLayout();
if (!currentLayout) {
return original_grandTotalRow;
}
return currentLayout.GrandTotalRow === true
? 'top'
: currentLayout.GrandTotalRow === false
? undefined
: currentLayout.GrandTotalRow;
});
// this is necessary here for the initialisation of the LayoutManager
/**
* `suppressAggFuncInHeader`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'suppressAggFuncInHeader', (original_suppressAggFuncInHeader) => {
const currentLayout = this.api.layoutApi.getCurrentLayout();
if (!currentLayout) {
return original_suppressAggFuncInHeader;
}
return currentLayout.SuppressAggFuncInHeader;
});
/**
* `aggFuncs`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'aggFuncs', (original_aggFuncs) => {
const aggregationFunctions = original_aggFuncs || {};
aggregationFunctions[AggregationColumns_1.WEIGHTED_AVERAGE_AGG_FN_NAME] = (params) => {
const columnId = params.column.getColId();
const adaptableAggFunc = this.getActiveAdaptableAggFuncForCol(columnId);
if (!adaptableAggFunc) {
return undefined;
}
if (adaptableAggFunc.type === 'weightedAverage') {
return (0, weightedAverage_1.weightedAverage)(params, params.colDef.colId, adaptableAggFunc.weightedColumnId);
}
return undefined;
};
return aggregationFunctions;
});
/**
* `allowContextMenuWithControlKey`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'allowContextMenuWithControlKey', (original_allowContextMenuWithControlKey) => {
return original_allowContextMenuWithControlKey === undefined
? true
: original_allowContextMenuWithControlKey;
});
/**
* `isExternalFilterPresent`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'isExternalFilterPresent', (original_isExternalFilterPresent) => {
return (params) => {
if (!this.isAvailable) {
return true;
}
const isGridFilterActive = StringExtensions_1.default.IsNotNullOrEmpty(this.api.filterApi.gridFilterApi.getCurrentGridFilterExpression());
const isTableColumnFiltersActive = ArrayExtensions_1.default.IsNotNullOrEmpty(this.api.filterApi.columnFilterApi
.getActiveColumnFilters()
.filter((columnFilter) => !this.api.columnApi.isPivotResultColumn(columnFilter.ColumnId)));
return (isGridFilterActive ||
// only Table ColumnFilters are handled here
// see #doAggregateFiltersPassMonkeyPatcher for filtering Pivot Columns
isTableColumnFiltersActive ||
// it means that userPropertyValue will be called so we re-init that collection
(original_isExternalFilterPresent ? original_isExternalFilterPresent(params) : false));
};
});
/**
* `doesExternalFilterPass`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'doesExternalFilterPass', (original_doesExternalFilterPass) => {
return (node) => {
if (!this.isAvailable) {
return true;
}
if (this.isGroupRowNode(node)) {
if (this.api.gridApi.isTreeDataGrid()) {
if (node.data == undefined) {
// Filler Groups are not filterable
// see https://www.ag-grid.com/javascript-data-grid/tree-data-paths/#filler-groups
return false;
}
}
else {
// for non-Tree Data Grids, we don't filter Group Rows
// not sure if that's 100% correct, but we did it like this in the past
// so we keep it like this for now until bugs are reported
return true;
}
}
// first assess if the Row i s filterable - if not, then return true so it appears in Grid
const isRowFilterable = this.api.optionsApi.getFilterOptions().isRowFilterable;
if (typeof isRowFilterable === 'function') {
const rowFilterableContext = {
...this.api.internalApi.buildBaseContext(),
rowNode: node,
data: node.data,
};
if (!isRowFilterable(rowFilterableContext)) {
return true;
}
}
// get the Primary Key Value for the Row Node being evaluated
const primaryKey = this.getPrimaryKeyValueFromRowNode(node);
// next we assess a Grid Filter (if its running locally)
const currentGridFilterExpression = this.api.filterApi.gridFilterApi.getCurrentGridFilterExpression();
if (StringExtensions_1.default.IsNotNullOrEmpty(currentGridFilterExpression)) {
const evaluateGridFilterOnClient = this.api.expressionApi.internalApi.evaluateExpressionInAdaptableQL('GridFilter', undefined, currentGridFilterExpression);
if (evaluateGridFilterOnClient) {
const isCurrentGridFilterValid = this.api.expressionApi.isValidBooleanExpression(currentGridFilterExpression, ModuleConstants_1.GridFilterModuleId, `Invalid Grid Filter '${currentGridFilterExpression}'`);
// Not sure about this - what should we do with an invalid Grid Filter?
// Here we essentially clear the Grid for invalid Grid Filter by returning false for each row
if (!isCurrentGridFilterValid) {
return false;
}
const gridFilterEvaluationResult = this.api.internalApi
.getQueryLanguageService()
.evaluateBooleanExpression(currentGridFilterExpression, ModuleConstants_1.GridFilterModuleId, node);
if (!gridFilterEvaluationResult) {
return false;
}
}
}
// finally we evaluate column filters
const tableColumnFilters = this.api.filterApi.columnFilterApi
.getActiveColumnFilters()
.filter((columnFilter) => !this.api.columnApi.isPivotResultColumn(columnFilter.ColumnId));
try {
// only Table ColumnFilters are handled here
// see #doAggregateFiltersPassMonkeyPatcher for filtering Pivot Columns
if (tableColumnFilters.length > 0) {
for (const columnFilter of tableColumnFilters) {
const evaluateColumnFilterOnClient = this.api.expressionApi.internalApi.shouldEvaluatePredicatesInAdaptableQL('ColumnFilter', columnFilter, columnFilter.Predicates);
if (evaluateColumnFilterOnClient) {
const columnFilterEvaluationResult = this.api.filterApi.columnFilterApi.internalApi.evaluateColumnFilter(columnFilter, node);
if (!columnFilterEvaluationResult) {
return false;
}
}
}
}
}
catch (ex) {
this.logger.error(ex);
return false;
}
const result = original_doesExternalFilterPass
? original_doesExternalFilterPass(node)
: true;
return result;
};
});
/**
* `getMainMenuItems`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'getMainMenuItems', (original_getMainMenuItems) => {
return (params) => {
// couldnt find a way to listen for menu close. There is a Menu Item Select, but you can also close menu from filter and clicking outside menu....
return this.agGridMenuAdapter.buildColumnMenu(params, original_getMainMenuItems);
};
});
/**
* `getContextMenuItems`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'getContextMenuItems', (original_getContextMenuItems) => {
return (params) => {
return this.agGridMenuAdapter.buildContextMenu(params, original_getContextMenuItems);
};
});
/**
* `components`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'components', (original_components) => {
const AdaptableToolPanel = (0, AdaptableToolPanel_1.getAdaptableToolPanelAgGridComponent)(this);
const components = original_components || {};
const adaptableComponents = {
...components,
AdaptableToolPanel,
};
return adaptableComponents;
});
if (variant === 'react') {
// TODO very soon we have to transition to reactiveCustomComponents in React
// but for now, if we simply set it to true, it will break our editors, etc
// this.agGridOptionsService.setGridOptionsProperty(
// gridOptions,
// 'reactiveCustomComponents',
// () => true
// );
}
/**
* `sidebar`
*/
this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'sideBar', (original_sideBar) => {
if (!original_sideBar) {
// lucky us, no sideBar is defined, so we don't have to do anything
return original_sideBar;
}
const isAdaptableToolPanelHidden = this.api.entitlementApi.isModuleHiddenEntitlement('ToolPanel');
const adaptableToolPanelDef = {
id: GeneralConstants.ADAPTABLE_TOOLPANEL_ID,
toolPanel: GeneralConstants.ADAPTABLE_TOOLPANEL_COMPONENT,
labelDefault: GeneralConstants.ADAPTABLE,
labelKey: 'adaptable',
iconKey: 'menu',
width: UIHelper_1.default.getAdaptableToolPanelWidth(),
minWidth: UIHelper_1.default.getAdaptableToolPanelWidth(),
// maxWidth = undefined,
};
const mapToolPanelDefs = (toolPanelDefs = []) => {
// if it's an alias for the adaptable tool panel, map it to a ToolPanelDef, otherwise return it as it is
return tool