UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

629 lines (628 loc) 28.9 kB
import { isAdaptableElementIcon } from '../components/Icon'; import { iconToString } from '../components/icons'; import ArrayExtensions from '../Utilities/Extensions/ArrayExtensions'; import { MENU_SEPARATOR } from '../Utilities/Constants/GeneralConstants'; export class AgGridMenuAdapter { constructor(_adaptableInstance) { this._adaptableInstance = _adaptableInstance; } get adaptableOptions() { return this._adaptableInstance.adaptableOptions; } get adaptableApi() { return this._adaptableInstance.api; } destroy() { this._adaptableInstance = null; } buildColumnMenu(params, originalGetMainMenuItems) { const columnMenuOptions = this.adaptableOptions.columnMenuOptions; const agGridMenuItems = (params.defaultItems ? [...params.defaultItems] : []); if (!params.column) { // return only AG Grid context if the Adaptable column is not found (should not happen) return agGridMenuItems; } const adaptableColumn = this.adaptableApi.columnApi.getColumnWithColumnId(params.column?.getColId()); const menuContext = this.createColumnMenuContextObject(adaptableColumn, params.column); /** * AG Grid items */ const agGridDefaultStructure = agGridMenuItems.map((itemName) => this.mapAgGridItemTypeToAgGridMenuItem(itemName)); const adaptableMenuItems = this.createAdaptableColumnMenuItems(menuContext); const defaultColumnMenuStructure = this.buildColumnMenuDefaultStructure(adaptableMenuItems, menuContext); // 1. first check if there is a custom column menu defined if (typeof columnMenuOptions.customColumnMenu === 'function') { const defaultAgGridMenuItems = agGridMenuItems.map((itemName) => this.mapAgGridItemTypeToAgGridMenuItem(itemName)); const defaultAdaptableMenuItems = adaptableMenuItems.map((adaptableItem) => ({ menuType: 'Adaptable', ...adaptableItem })); const customMenuItems = columnMenuOptions .customColumnMenu({ ...menuContext, defaultAgGridMenuItems, defaultAdaptableMenuItems, defaultAdaptableMenuStructure: this.mapAdaptableMenuItemToSystemMenuItems(defaultColumnMenuStructure), defaultAgGridMenuStructure: agGridDefaultStructure, }) .filter(Boolean); return customMenuItems .map((customMenuItem) => this.mapCustomMenuItemToAgGridMenuDefinition(customMenuItem, menuContext)) .filter(Boolean); } // 2. if not, return the default context menu const defaultContextMenu = [ ...agGridMenuItems, 'separator', ...defaultColumnMenuStructure.map((adaptableItem) => this.mapAdaptableMenuItemToAgGridMenuDefinition(adaptableItem)), ]; return this.removeConsecutiveSeparators(defaultContextMenu); } buildContextMenu(params, originalGetContextMenuItems) { if (!params.column) { return []; } // we do this in order to refresh the internal state of selected cells (technically query the AG Grid cellRanges) // (right-click selected the current cell, but this was not reflected in the internal state of the selected cells) this._adaptableInstance.refreshSelectedCellsState(); const contextMenuOptions = this.adaptableOptions.contextMenuOptions; const adaptableColumn = this.adaptableApi.columnApi.getColumnWithColumnId(params.column?.getColId()); const menuContext = this.createContextMenuContextObject(params, adaptableColumn); /** * AG Grid Items */ const agGridMenuItems = (params.defaultItems ? [...params.defaultItems] : []); const agGridCopyItems = agGridMenuItems.filter((item) => ['copy', 'copyWithHeaders', 'copyWithGroupHeaders', 'cut', 'paste'].includes(item)); const otherAgGridItems = agGridMenuItems.filter((item) => !agGridCopyItems.includes(item) && // we provide Adaptable exports in the context menu !['export', 'csvExport', 'excelExport'].includes(item)); const agGridDefaultStructure = [ { menuType: 'Group', label: 'Copy & Paste', icon: { name: 'copy', }, subMenuItems: agGridCopyItems.map((item) => this.mapAgGridItemTypeToAgGridMenuItem(item)), }, ...otherAgGridItems .filter((itemName) => itemName !== 'separator') .map((itemName) => this.mapAgGridItemTypeToAgGridMenuItem(itemName)), ]; /** * Adaptable Items */ const adaptableMenuItems = this.createAdaptableContextMenuItems(menuContext); const adaptableDefaultStructure = this.buildContextMenuDefaultStructure(adaptableMenuItems, menuContext); /** * Build the context menu */ // 1. first check if there is a custom context menu defined if (typeof contextMenuOptions.customContextMenu === 'function') { const defaultAgGridMenuItems = agGridMenuItems.map((itemName) => this.mapAgGridItemTypeToAgGridMenuItem(itemName)); const defaultAdaptableMenuItems = adaptableMenuItems.map((adaptableItem) => ({ menuType: 'Adaptable', ...adaptableItem })); const defaultAdaptableMenuStructure = this.mapAdaptableMenuItemToSystemMenuItems(adaptableDefaultStructure); const defaultAgGridMenuStructure = agGridDefaultStructure; const customMenuItems = contextMenuOptions .customContextMenu({ ...menuContext, defaultAgGridMenuItems, defaultAdaptableMenuItems, defaultAdaptableMenuStructure, defaultAgGridMenuStructure, }) .filter(Boolean); return customMenuItems .map((customMenuItem) => this.mapCustomMenuItemToAgGridMenuDefinition(customMenuItem, menuContext)) .filter(Boolean); } // 2. if not, return the default context menu const defaultContextMenu = [ ...agGridDefaultStructure.map((agGridItem) => this.mapCustomMenuItemToAgGridMenuDefinition(agGridItem, menuContext)), 'separator', ...adaptableDefaultStructure.map((adaptableItem) => this.mapAdaptableMenuItemToAgGridMenuDefinition(adaptableItem)), ]; return this.removeConsecutiveSeparators(defaultContextMenu); } mapAgGridItemTypeToAgGridMenuItem(itemName) { return { menuType: 'AgGrid', name: itemName, }; } // due to entitlements or other reasons, some menu items might be hidden, leading to consecutive separators removeConsecutiveSeparators(menuItems, separator = 'separator') { return menuItems.reduce((acc, item, index, array) => { if (item === separator && array[index + 1] === separator) { return acc; } acc.push(item); return acc; }, []); } createColumnMenuContextObject(adaptableColumn, agGridColumn) { return { ...this.adaptableApi.internalApi.buildBaseContext(), adaptableColumn: adaptableColumn, agGridColumn: agGridColumn, isRowGroupColumn: this.adaptableApi.columnApi.isAutoRowGroupColumn(agGridColumn.getColId()), }; } createAdaptableContextMenuItems(menuContext) { let contextMenuItems = []; this.adaptableApi.internalApi.getModules().forEach((module) => { let menuItems = module.createContextMenuItems(menuContext); if (menuItems) { contextMenuItems.push(...menuItems.filter(Boolean).filter((item) => item.isVisible !== false)); } }); this._adaptableInstance._emitSync('CreateAdaptableContextMenuItems', { items: contextMenuItems, menuContext: menuContext, }); return contextMenuItems; } createContextMenuContextObject(params, adaptableColumn) { // lets build a picture of what has been right clicked. Will take time to get right but lets start let isSingleSelectedColumn = false; let isSelectedCell = false; let isSelectedRow = false; // row group columns dont provide an AdapTable Column so return bare minimum if (!adaptableColumn) { return { ...this.adaptableApi.internalApi.buildBaseContext(), isSelectedCell: false, isSelectedRow: false, gridCell: undefined, adaptableColumn: undefined, agGridColumn: params.column, rowNode: params.node, isGroupedNode: params.node ? params.node.group : false, isSingleSelectedColumn: false, isSingleSelectedCell: false, primaryKeyValue: undefined, selectedCellInfo: undefined, selectedRowInfo: undefined, isRowGroupColumn: this.adaptableApi.columnApi.isAutoRowGroupColumn(params.column.getColId()), }; } const clickedCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, adaptableColumn.columnId); const selectedCellInfo = this.adaptableApi.gridApi.getSelectedCellInfo(); if (selectedCellInfo) { let matchedCell = selectedCellInfo.gridCells.find((gc) => gc != null && gc.column == clickedCell.column && gc.primaryKeyValue == clickedCell.primaryKeyValue); isSelectedCell = matchedCell != null; if (isSelectedCell) { isSingleSelectedColumn = ArrayExtensions.CorrectLength(selectedCellInfo.columns, 1); } } const selectedRowInfo = this.adaptableApi.gridApi.getSelectedRowInfo(); if (selectedRowInfo) { const matchedPKValue = selectedRowInfo.gridRows.find((gr) => gr != null && gr.primaryKeyValue == clickedCell.primaryKeyValue); isSelectedRow = matchedPKValue != null; } return { ...this.adaptableApi.internalApi.buildBaseContext(), isSelectedCell: isSelectedCell, isSelectedRow: isSelectedRow, gridCell: clickedCell, adaptableColumn: adaptableColumn, agGridColumn: params.column, rowNode: params.node, isGroupedNode: params.node ? params.node.group : false, isSingleSelectedColumn: isSingleSelectedColumn, isSingleSelectedCell: isSelectedCell && selectedCellInfo?.gridCells.length == 1, primaryKeyValue: clickedCell ? clickedCell.primaryKeyValue : undefined, selectedCellInfo: selectedCellInfo, selectedRowInfo: selectedRowInfo, isRowGroupColumn: this.adaptableApi.columnApi.isAutoRowGroupColumn(params.column.getColId()), }; } mapAdaptableMenuItemToAgGridMenuDefinition(adaptableMenuItem) { if (adaptableMenuItem === MENU_SEPARATOR) { return 'separator'; } const fullMenuItem = adaptableMenuItem; return { name: fullMenuItem.label, action: fullMenuItem.onClick ? fullMenuItem.onClick : fullMenuItem.reduxAction ? () => this.adaptableApi.internalApi.dispatchReduxAction(fullMenuItem.reduxAction) : undefined, icon: this.mapAdaptableIconToAgGridIcon(fullMenuItem.icon, { fill: 'var(--ab-color-text-on-primary)', }), subMenu: fullMenuItem.subItems?.map((subMenuItem) => this.mapAdaptableMenuItemToAgGridMenuDefinition(subMenuItem)), }; } mapCustomMenuItemToAgGridMenuDefinition(customMenuItem, menuContext) { if (customMenuItem === '-') { return 'separator'; } if (customMenuItem.menuType === 'Group') { return { name: customMenuItem.label, icon: this.mapAdaptableIconToAgGridIcon(customMenuItem.icon, { fill: 'var(--ab-color-text-on-primary)', }), disabled: customMenuItem.disabled, subMenu: !customMenuItem.disabled ? customMenuItem.subMenuItems ?.map((subMenuItem) => this.mapCustomMenuItemToAgGridMenuDefinition(subMenuItem, menuContext)) .filter(Boolean) : undefined, }; } if (customMenuItem.menuType === 'AgGrid') { return customMenuItem.name; } if (customMenuItem.menuType === 'Adaptable') { return this.mapAdaptableMenuItemToAgGridMenuDefinition(customMenuItem); } if (customMenuItem.menuType === 'User') { return this.mapUserMenuItemToAgGridMenuDefinition(customMenuItem, menuContext); } } mapUserMenuItemToAgGridMenuDefinition(userMenuItem, menuContext) { if (userMenuItem.hidden) { return; } return { name: userMenuItem.label, action: () => (userMenuItem.onClick ? userMenuItem.onClick(menuContext) : null), icon: this.mapAdaptableIconToAgGridIcon(userMenuItem.icon, { fill: 'var(--ab-color-text-on-primary)', }), disabled: userMenuItem.disabled, subMenu: userMenuItem.subMenuItems ?.map((subMenuItem) => { return this.mapCustomMenuItemToAgGridMenuDefinition(subMenuItem, menuContext); }) .filter(Boolean), }; } buildContextMenuDefaultStructure(availableMenuItems, menuContext) { // Alert const alertMenuItems = this.getModuleSpecificStructure('Alert', availableMenuItems); // BulkUpdate const bulkUpdateMenuItems = this.getModuleSpecificStructure('BulkUpdate', availableMenuItems); // CalculatedColumn const calculatedColumnMenuItems = this.getModuleSpecificStructure('CalculatedColumn', availableMenuItems); // CellSummary const cellSummaryMenuItems = this.getModuleSpecificStructure('CellSummary', availableMenuItems); // ColumnFilter const columnFilterMenuItems = this.getModuleSpecificStructure('ColumnFilter', availableMenuItems); // ColumnInfo const columnInfoMenuItems = this.getModuleSpecificStructure('ColumnInfo', availableMenuItems); // Comment const commentMenuItems = this.getModuleSpecificStructure('Comment', availableMenuItems); // Dashboard const dashboardMenuItems = this.getModuleSpecificStructure('Dashboard', availableMenuItems, 'dashboard-group'); // DataImport const dataImportMenuItems = this.getModuleSpecificStructure('DataImport', availableMenuItems); // Export const exportMenuItems = this.getModuleSpecificStructure('Export', availableMenuItems); // FDC3 const fdc3MenuItems = this.getModuleSpecificStructure('Fdc3', availableMenuItems); // FlashingCell const flashingCellMenuItems = this.getModuleSpecificStructure('FlashingCell', availableMenuItems); // GridInfo const gridInfoMenuItems = this.getModuleSpecificStructure('GridInfo', availableMenuItems); // Layout const [gridActionItems, otherLayoutItems] = this.getLayoutContextMenuStructure(availableMenuItems); // Note const noteMenuItems = this.getModuleSpecificStructure('Note', availableMenuItems); // SettingsPanel const settingsPanelMenuItems = this.getModuleSpecificStructure('SettingsPanel', availableMenuItems); // SmartEdit const smartEditMenuItems = this.getModuleSpecificStructure('SmartEdit', availableMenuItems); // SystemStatus const systemStatusMenuItems = this.getModuleSpecificStructure('SystemStatus', availableMenuItems); /** * Custom structures */ const gridMenuItem = { name: 'grid-group', label: 'Grid', category: 'Group', isVisible: true, icon: { name: 'grid', }, subItems: [ ...gridActionItems, ...otherLayoutItems, ...cellSummaryMenuItems, ...dataImportMenuItems, ...systemStatusMenuItems, ...gridInfoMenuItems, ], }; const editMenuItem = { name: 'edit-group', label: 'Edit', category: 'Group', isVisible: true, icon: { name: 'edit-table', }, subItems: [...bulkUpdateMenuItems, ...smartEditMenuItems], }; return this.removeConsecutiveSeparators([ ...exportMenuItems, '-', ...calculatedColumnMenuItems, ...noteMenuItems, ...commentMenuItems, ...columnFilterMenuItems, ...flashingCellMenuItems, ...alertMenuItems, ...fdc3MenuItems, '-', ...settingsPanelMenuItems, ...dashboardMenuItems, '-', ...this.normalizeMenuGroup(editMenuItem), '-', ...this.normalizeMenuGroup(gridMenuItem), ...columnInfoMenuItems, ], '-'); } /** * Hide menu group with no subitems or elevate single subitem to parent level */ normalizeMenuGroup(menuItem) { if (!menuItem.subItems?.length) { return []; } if (menuItem.subItems?.length === 1) { return [menuItem.subItems[0]]; } return [menuItem]; } /** * Default strategy for menu items: return as is if there is only one item, otherwise group them under a parent item */ getModuleSpecificStructure(module, menuItems, groupName) { const moduleItems = menuItems.filter((menuItem) => menuItem.category === module); if (moduleItems.length > 1) { return [this.buildMenuGroupParent(module, moduleItems, { groupName })]; } else { return moduleItems; } } getLayoutContextMenuStructure(menuItems) { const layoutMenuItems = menuItems.filter((menuItem) => menuItem.category === 'Layout'); if (!layoutMenuItems.length) { return [[], []]; } const gridActionsItemNames = [ 'layout-clear-selection', 'layout-select-all', 'layout-auto-size', ]; const gridActionsItems = layoutMenuItems.filter((item) => gridActionsItemNames.includes(item.name)); const otherLayoutItems = layoutMenuItems.filter((item) => !gridActionsItemNames.includes(item.name)); return [gridActionsItems, otherLayoutItems]; } buildMenuGroupParent(module, menuItems, config) { const moduleInfo = this.adaptableApi.internalApi .getModuleService() .getModuleInfoByModule(module); const icon = config && config.icon === false ? undefined : { name: config?.icon ?? moduleInfo.Glyph }; return { name: config?.groupName ?? 'menu-group', label: config?.label ?? moduleInfo.FriendlyName, isVisible: true, category: moduleInfo.ModuleName, icon, subItems: menuItems, }; } buildColumnMenuDefaultStructure(availableMenuItems, menuContext) { // CalculatedColumn const calculatedColumnMenuItems = this.getModuleSpecificStructure('CalculatedColumn', availableMenuItems); // CellSummary const cellSummaryMenuItems = this.getModuleSpecificStructure('CellSummary', availableMenuItems); // Chart const chartMenuItems = this.getModuleSpecificStructure('Charting', availableMenuItems); // ColumnFilter const [columnFilterGroup, filterVisibilityItems] = this.getColumnFilterColumnMenuStructure(availableMenuItems); // ColumnInfo const columnInfoMenuItems = this.getModuleSpecificStructure('ColumnInfo', availableMenuItems); // CustomSort const customSortMenuItems = this.getModuleSpecificStructure('CustomSort', availableMenuItems); // Dashboard const dashboardMenuItems = this.getModuleSpecificStructure('Dashboard', availableMenuItems, 'dashboard-group'); // DataImport const dataImportMenuItems = this.getModuleSpecificStructure('DataImport', availableMenuItems); // FlashingCell const flashingCellMenuItems = this.getModuleSpecificStructure('FlashingCell', availableMenuItems); // FormatColumn const formatColumnMenuItems = this.getModuleSpecificStructure('FormatColumn', availableMenuItems); // FreeTextColumn const freeTextColumnMenuItems = this.getModuleSpecificStructure('FreeTextColumn', availableMenuItems); // GridInfo const gridInfoMenuItems = this.getModuleSpecificStructure('GridInfo', availableMenuItems); // Layout const [gridSelectItems, columnSelectItems, columnActionGroup, otherLayoutItems] = this.getLayoutColumnMenuStructure(availableMenuItems); // PlusMinus const plusMinusMenuItems = this.getModuleSpecificStructure('PlusMinus', availableMenuItems); // SettingsPanel const settingsPanelMenuItems = this.getModuleSpecificStructure('SettingsPanel', availableMenuItems); // StyledColumn const styledColumnMenuItems = this.getStyledColumnColumnMenuStructure(availableMenuItems); // SystemStatus const systemStatusMenuItems = this.getModuleSpecificStructure('SystemStatus', availableMenuItems); /** * Custom structures */ const gridMenuItem = { name: 'grid-group', label: 'Grid', category: 'Group', isVisible: true, icon: { name: 'grid', }, subItems: [ ...otherLayoutItems, ...filterVisibilityItems, ...gridSelectItems, ...cellSummaryMenuItems, ...chartMenuItems, ...dataImportMenuItems, ...systemStatusMenuItems, ...gridInfoMenuItems, ], }; /* const calculatedColumnMenuItem: AdaptableMenuItem = { name: 'calculated-column-group', label: 'Calculated Column', module: 'CalculatedColumn', isVisible: true, icon: { name: 'columns', }, subItems: [ ...calculatedColumnMenuItems, ], }; */ const columnMenuItem = { name: 'column-group', label: 'Column', category: 'Group', isVisible: true, icon: { name: 'columns', }, subItems: [ ...columnActionGroup, ...freeTextColumnMenuItems, ...customSortMenuItems, ...plusMinusMenuItems, ...columnSelectItems, ...columnInfoMenuItems, ], }; const createStyleMenuItem = { name: 'styling-group', label: 'Styling', category: 'Group', isVisible: true, icon: { name: 'brush', }, subItems: [...formatColumnMenuItems, ...styledColumnMenuItems, ...flashingCellMenuItems], }; return this.removeConsecutiveSeparators([ ...calculatedColumnMenuItems, ...settingsPanelMenuItems, ...dashboardMenuItems, ...columnFilterGroup, ...this.normalizeMenuGroup(createStyleMenuItem), ...this.normalizeMenuGroup(gridMenuItem), ...this.normalizeMenuGroup(columnMenuItem), ], '-'); } getColumnFilterColumnMenuStructure(menuItems) { const columnFilterMenuItems = menuItems.filter((menuItem) => menuItem.category === 'ColumnFilter'); const filterVisibilityItems = columnFilterMenuItems.filter((item) => ['column-filter-bar-hide', 'column-filter-bar-show'].includes(item.name)); const filterActionItems = columnFilterMenuItems.filter((item) => ['column-filter-clear', 'column-filter-suspend', 'column-filter-unsuspend'].includes(item.name)); const columnFilterGroup = filterActionItems.length ? [ { name: 'column-filter-group', label: 'Filter', category: 'ColumnFilter', isVisible: true, icon: { name: 'filter', }, subItems: filterActionItems, }, ] : []; return [columnFilterGroup, filterVisibilityItems]; } getLayoutColumnMenuStructure(menuItems) { const layoutMenuItems = menuItems.filter((menuItem) => menuItem.category === 'Layout'); if (!layoutMenuItems.length) { return [[], [], [], []]; } const columnSelectItemNames = [ 'layout-column-select-preserve', 'layout-column-select-reset', 'layout-column-select', ]; const columnSelectItems = layoutMenuItems.filter((item) => columnSelectItemNames.includes(item.name)); const gridSelectItems = layoutMenuItems.filter((item) => item.name === 'layout-grid-select'); const columnActionGroup = layoutMenuItems.filter((item) => ['layout-column-caption-change', 'layout-column-hide'].includes(item.name)); const otherLayoutItems = layoutMenuItems.filter((item) => !columnSelectItemNames.includes(item.name) && !['layout-column-caption-change', 'layout-column-hide'].includes(item.name) && item.name !== 'layout-grid-select'); return [gridSelectItems, columnSelectItems, columnActionGroup, otherLayoutItems]; } getStyledColumnColumnMenuStructure(menuItems) { const styledColumnMenuItems = menuItems.filter((menuItem) => menuItem.category === 'StyledColumn'); return styledColumnMenuItems; } mapAdaptableMenuItemToSystemMenuItems(adaptableMenuItems) { if (!adaptableMenuItems) { return; } return adaptableMenuItems.map((menuItem) => { if (menuItem === '-') { return menuItem; } // @ts-ignore const subItems = this.mapAdaptableMenuItemToSystemMenuItems(menuItem.subItems); return { ...menuItem, menuType: 'Adaptable', subItems, }; }); } createAdaptableColumnMenuItems(menuContext) { let columnMenuItems = []; this.adaptableApi.internalApi.getModules().forEach((s) => { let menuItems = s.createColumnMenuItems(menuContext.adaptableColumn); if (menuItems) { columnMenuItems.push(...menuItems.filter(Boolean).filter((item) => item.isVisible !== false)); } }); return columnMenuItems; } // TODO AFL MIG: pretty sure this logic is duplicated in several other places mapAdaptableIconToAgGridIcon(adaptableIcon, style) { const icon = this.adaptableApi.userInterfaceApi.internalApi.prepareAdaptableIconDef(adaptableIcon); if (isAdaptableElementIcon(icon)) { let element = icon.element; if (typeof element === 'string') { return element; } // THe element neets to be cloned. // when it is used in more than one plce the element is removed from the DOM return element.cloneNode(true); } else { return iconToString(icon, { fill: 'var(--ab-color-text-on-primary)', }); } } /** * The output of this function is used to build the column header menu if the AG Grid Menu Module is NOT present * This is controlled by the AdaptableAgGrid.embedColumnMenu property */ buildStandaloneColumnHeader(adaptableColumn) { const agGridColumn = this._adaptableInstance.getAgGridColumnForColumnId(adaptableColumn.columnId); const menuContext = this.createColumnMenuContextObject(adaptableColumn, agGridColumn); return this.createAdaptableColumnMenuItems(menuContext); } }