UNPKG

@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
"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