ag-grid-enterprise
Version:
ag-Grid Enterprise Features
478 lines (385 loc) • 16.6 kB
text/typescript
import {
AgEvent,
Autowired,
Bean,
BeanStub,
Column,
ColumnController,
Constants,
Context,
EventService,
FilterManager,
FilterWrapper,
GridApi,
GridOptionsWrapper,
IMenuFactory,
IRowModel,
MenuItemDef,
PopupService,
PostConstruct,
Promise,
TabbedItem,
TabbedLayout,
Utils
} from "ag-grid-community";
import {MenuList} from "./menuList";
import {MenuItemComponent} from "./menuItemComponent";
import {MenuItemMapper} from "./menuItemMapper";
import {PrimaryColsPanel} from "../sideBar/providedPanels/columns/panels/primaryColsPanel/primaryColsPanel";
export interface TabSelectedEvent extends AgEvent {
key: string;
}
export class EnterpriseMenuFactory implements IMenuFactory {
private context: Context;
private popupService: PopupService;
private gridOptionsWrapper: GridOptionsWrapper;
private lastSelectedTab: string;
private activeMenu: EnterpriseMenu;
public hideActiveMenu(): void {
if (this.activeMenu) {
this.activeMenu.destroy();
}
}
public showMenuAfterMouseEvent(column:Column, mouseEvent:MouseEvent, defaultTab?:string): void {
this.showMenu(column, (menu: EnterpriseMenu)=> {
this.popupService.positionPopupUnderMouseEvent({
column: column,
type: 'columnMenu',
mouseEvent: mouseEvent,
ePopup: menu.getGui()
});
if (defaultTab) {
menu.showTab(defaultTab);
}
}, defaultTab);
}
public showMenuAfterButtonClick(column: Column, eventSource: HTMLElement, defaultTab?:string, restrictToTabs?:string[]): void {
this.showMenu(column, (menu: EnterpriseMenu)=> {
let minDims = menu.getMinDimensions();
this.popupService.positionPopupUnderComponent({
column: column,
type: 'columnMenu',
eventSource: eventSource,
ePopup: menu.getGui(),
nudgeX: -9,
nudgeY: -26,
minWidth: minDims.width,
minHeight: minDims.height,
keepWithinBounds: true
});
if (defaultTab) {
menu.showTab(defaultTab);
}
}, defaultTab, restrictToTabs);
}
public showMenu(column: Column, positionCallback: (menu: EnterpriseMenu)=>void, defaultTab?:string, restrictToTabs?:string[]): void {
let menu = new EnterpriseMenu(column, this.lastSelectedTab, restrictToTabs);
this.context.wireBean(menu);
let eMenuGui = menu.getGui();
// need to show filter before positioning, as only after filter
// is visible can we find out what the width of it is
let hidePopup = this.popupService.addAsModalPopup(
eMenuGui,
true,
() => { // menu closed callback
menu.destroy();
column.setMenuVisible(false, "contextMenu");
}
);
menu.afterGuiAttached({
hidePopup: hidePopup
});
positionCallback(menu);
if (!defaultTab) {
menu.showTabBasedOnPreviousSelection();
}
menu.addEventListener(EnterpriseMenu.EVENT_TAB_SELECTED, (event: any) => {
this.lastSelectedTab = event.key;
} );
column.setMenuVisible(true, "contextMenu");
this.activeMenu = menu;
menu.addEventListener(BeanStub.EVENT_DESTROYED, ()=> {
if (this.activeMenu === menu) {
this.activeMenu = null;
}
});
}
public isMenuEnabled(column: Column): boolean {
return column.getMenuTabs(EnterpriseMenu.TABS_DEFAULT).length > 0;
}
}
export class EnterpriseMenu extends BeanStub {
public static EVENT_TAB_SELECTED = 'tabSelected';
public static TAB_FILTER = 'filterMenuTab';
public static TAB_GENERAL = 'generalMenuTab';
public static TAB_COLUMNS = 'columnsMenuTab';
public static TABS_DEFAULT = [EnterpriseMenu.TAB_GENERAL, EnterpriseMenu.TAB_FILTER, EnterpriseMenu.TAB_COLUMNS];
public static MENU_ITEM_SEPARATOR = 'separator';
private columnController: ColumnController;
private filterManager: FilterManager;
private context: Context;
private gridApi: GridApi;
private gridOptionsWrapper: GridOptionsWrapper;
private eventService: EventService;
private menuItemMapper: MenuItemMapper;
private rowModel: IRowModel;
private tabbedLayout: TabbedLayout;
private hidePopupFunc: Function;
private column: Column;
private mainMenuList: MenuList;
private columnSelectPanel: PrimaryColsPanel;
private tabItemFilter: TabbedItem;
private tabItemGeneral: TabbedItem;
private tabItemColumns: TabbedItem;
private initialSelection: string;
private tabFactories:{[p:string]:()=>TabbedItem} = {};
private includeChecks:{[p:string]:()=>boolean} = {};
private restrictTo ?: string[];
constructor(column: Column, initialSelection: string, restrictTo ?: string[]) {
super();
this.column = column;
this.initialSelection = initialSelection;
this.tabFactories[EnterpriseMenu.TAB_GENERAL] = this.createMainPanel.bind(this);
this.tabFactories[EnterpriseMenu.TAB_FILTER] = this.createFilterPanel.bind(this);
this.tabFactories[EnterpriseMenu.TAB_COLUMNS] = this.createColumnsPanel.bind(this);
this.includeChecks[EnterpriseMenu.TAB_GENERAL] = ()=> true;
this.includeChecks[EnterpriseMenu.TAB_FILTER] = () => {
let isFilterEnabled: boolean = this.gridOptionsWrapper.isEnableFilter();
let isFloatingFiltersEnabled: boolean = this.gridOptionsWrapper.isFloatingFilter();
let isAnyFilteringEnabled = isFilterEnabled || isFloatingFiltersEnabled;
let suppressFilterForThisColumn = this.column.getColDef().suppressFilter;
return isAnyFilteringEnabled && !suppressFilterForThisColumn;
};
this.includeChecks[EnterpriseMenu.TAB_COLUMNS] = ()=> true;
this.restrictTo = restrictTo;
}
public getMinDimensions(): {width: number, height: number} {
return this.tabbedLayout.getMinDimensions();
}
public init(): void {
let items:TabbedItem[] = this.column.getMenuTabs (this.restrictTo ? this.restrictTo : EnterpriseMenu.TABS_DEFAULT)
.filter(menuTabName=>
this.isValidMenuTabItem(menuTabName)
)
.filter(menuTabName=>
this.isNotSuppressed(menuTabName)
)
.map(menuTabName=>
this.createTab(menuTabName)
);
this.tabbedLayout = new TabbedLayout({
items: items,
cssClass: 'ag-menu',
onActiveItemClicked: this.onHidePopup.bind(this),
onItemClicked: this.onTabItemClicked.bind(this)
});
}
private isValidMenuTabItem(menuTabName: string): boolean {
let isValid: boolean = true;
let itemsToConsider: string[] = EnterpriseMenu.TABS_DEFAULT;
if (this.restrictTo != null) {
isValid = this.restrictTo.indexOf(menuTabName) > -1 ;
itemsToConsider = this.restrictTo;
}
isValid = isValid && EnterpriseMenu.TABS_DEFAULT.indexOf(menuTabName) > -1 ;
if (!isValid) console.warn(`Trying to render an invalid menu item '${menuTabName}'. Check that your 'menuTabs' contains one of [${itemsToConsider}]`);
return isValid;
}
private isNotSuppressed(menuTabName: string):boolean {
return this.includeChecks[menuTabName]();
}
private createTab(name: string):TabbedItem {
return this.tabFactories[name]();
}
public showTabBasedOnPreviousSelection(): void {
// show the tab the user was on last time they had a menu open
this.showTab(this.initialSelection);
}
public showTab(toShow:string) {
if (this.tabItemColumns && toShow === EnterpriseMenu.TAB_COLUMNS) {
this.tabbedLayout.showItem(this.tabItemColumns);
} else if (this.tabItemFilter && toShow === EnterpriseMenu.TAB_FILTER) {
this.tabbedLayout.showItem(this.tabItemFilter);
} else if (this.tabItemGeneral && toShow === EnterpriseMenu.TAB_GENERAL) {
this.tabbedLayout.showItem(this.tabItemGeneral);
} else {
this.tabbedLayout.showFirstItem();
}
}
private onTabItemClicked(event: any): void {
let key: string;
switch (event.item) {
case this.tabItemColumns: key = EnterpriseMenu.TAB_COLUMNS; break;
case this.tabItemFilter: key = EnterpriseMenu.TAB_FILTER; break;
case this.tabItemGeneral: key = EnterpriseMenu.TAB_GENERAL; break;
}
if (key) {
let ev: TabSelectedEvent = {
type: EnterpriseMenu.EVENT_TAB_SELECTED,
key: key
};
this.dispatchEvent(ev);
}
}
public destroy(): void {
if (this.columnSelectPanel) {
this.columnSelectPanel.destroy();
}
if (this.mainMenuList) {
this.mainMenuList.destroy();
}
super.destroy();
}
private getMenuItems(): (string|MenuItemDef)[] {
let defaultMenuOptions = this.getDefaultMenuOptions();
let result: (string|MenuItemDef)[];
let userFunc = this.gridOptionsWrapper.getMainMenuItemsFunc();
if (userFunc) {
let userOptions = userFunc({
column: this.column,
api: this.gridOptionsWrapper.getApi(),
columnApi: this.gridOptionsWrapper.getColumnApi(),
context: this.gridOptionsWrapper.getContext(),
defaultItems: defaultMenuOptions
});
result = userOptions;
} else {
result = defaultMenuOptions;
}
// GUI looks weird when two separators are side by side. this can happen accidentally
// if we remove items from the menu then two separators can edit up adjacent.
Utils.removeRepeatsFromArray(result, EnterpriseMenu.MENU_ITEM_SEPARATOR);
return result;
}
private getDefaultMenuOptions(): string[] {
let result: string[] = [];
let allowPinning = !this.column.isLockPinned();
let rowGroupCount = this.columnController.getRowGroupColumns().length;
let doingGrouping = rowGroupCount > 0;
let groupedByThisColumn = this.columnController.getRowGroupColumns().indexOf(this.column) >= 0;
let allowValue = this.column.isAllowValue();
let allowRowGroup = this.column.isAllowRowGroup();
let isPrimary = this.column.isPrimary();
let pivotModeOn = this.columnController.isPivotMode();
let isInMemoryRowModel = this.rowModel.getType() === Constants.ROW_MODEL_TYPE_CLIENT_SIDE;
let usingTreeData = this.gridOptionsWrapper.isTreeData();
let allowValueAgg =
// if primary, then only allow aggValue if grouping and it's a value columns
(isPrimary && doingGrouping && allowValue)
// secondary columns can always have aggValue, as it means it's a pivot value column
|| !isPrimary;
if (allowPinning) {
result.push('pinSubMenu');
}
if (allowValueAgg) {
result.push('valueAggSubMenu');
}
if (allowPinning || allowValueAgg) {
result.push(EnterpriseMenu.MENU_ITEM_SEPARATOR);
}
result.push('autoSizeThis');
result.push('autoSizeAll');
result.push(EnterpriseMenu.MENU_ITEM_SEPARATOR);
if (allowRowGroup && this.column.isPrimary()) {
if (groupedByThisColumn) {
result.push('rowUnGroup');
} else {
result.push('rowGroup');
}
}
result.push(EnterpriseMenu.MENU_ITEM_SEPARATOR);
result.push('resetColumns');
// only add grouping expand/collapse if grouping in the InMemoryRowModel
// if pivoting, we only have expandable groups if grouping by 2 or more columns
// as the lowest level group is not expandable while pivoting.
// if not pivoting, then any active row group can be expanded.
let allowExpandAndContract = false;
if (isInMemoryRowModel) {
if (usingTreeData) {
allowExpandAndContract = true;
} else {
allowExpandAndContract = pivotModeOn ? rowGroupCount > 1 : rowGroupCount > 0;
}
}
if (allowExpandAndContract) {
result.push('expandAll');
result.push('contractAll');
}
return result;
}
private createMainPanel(): TabbedItem {
this.mainMenuList = new MenuList();
this.context.wireBean(this.mainMenuList);
let menuItems = this.getMenuItems();
let menuItemsMapped = this.menuItemMapper.mapWithStockItems(menuItems, this.column);
this.mainMenuList.addMenuItems(menuItemsMapped);
this.mainMenuList.addEventListener(MenuItemComponent.EVENT_ITEM_SELECTED, this.onHidePopup.bind(this));
this.tabItemGeneral = {
title: Utils.createIconNoSpan('menu', this.gridOptionsWrapper, this.column),
bodyPromise: Promise.resolve(this.mainMenuList.getGui()),
name: EnterpriseMenu.TAB_GENERAL
};
return this.tabItemGeneral;
}
private onHidePopup(): void {
this.hidePopupFunc();
}
private createFilterPanel(): TabbedItem {
let filterWrapper:FilterWrapper = this.filterManager.getOrCreateFilterWrapper(this.column, 'COLUMN_MENU');
let afterFilterAttachedCallback: Function;
filterWrapper.filterPromise.then(filter => {
if (filter.afterGuiAttached) {
afterFilterAttachedCallback = filter.afterGuiAttached.bind(filter);
}
});
this.tabItemFilter = {
title: Utils.createIconNoSpan('filter', this.gridOptionsWrapper, this.column),
bodyPromise: filterWrapper.guiPromise.promise,
afterAttachedCallback: afterFilterAttachedCallback,
name: EnterpriseMenu.TAB_FILTER
};
return this.tabItemFilter;
}
private createColumnsPanel(): TabbedItem {
let eWrapperDiv = document.createElement('div');
Utils.addCssClass(eWrapperDiv, 'ag-menu-column-select-wrapper');
this.columnSelectPanel = new PrimaryColsPanel(false, {
suppressValues: false,
suppressPivots: false,
suppressRowGroups: false,
suppressPivotMode: false,
contractColumnSelection: false,
suppressColumnExpandAll: false,
suppressColumnFilter: false,
suppressColumnSelectAll: false,
suppressSideButtons: false,
api: this.gridApi
});
this.context.wireBean(this.columnSelectPanel);
eWrapperDiv.appendChild(this.columnSelectPanel.getGui());
this.tabItemColumns = {
title: Utils.createIconNoSpan('columns', this.gridOptionsWrapper, this.column),//createColumnsIcon(),
bodyPromise: Promise.resolve(eWrapperDiv),
name: EnterpriseMenu.TAB_COLUMNS
};
return this.tabItemColumns;
}
public afterGuiAttached(params: any): void {
this.tabbedLayout.setAfterAttachedParams({hidePopup: params.hidePopup});
this.hidePopupFunc = params.hidePopup;
// if the body scrolls, we want to hide the menu, as the menu will not appear in the right location anymore
let onBodyScroll = (event: any) => {
// if h scroll, popup is no longer over the column
if (event.direction === 'horizontal') {
params.hidePopup();
}
};
this.addDestroyFunc(params.hidePopup);
this.addDestroyableEventListener(this.eventService, 'bodyScroll', onBodyScroll);
}
public getGui(): HTMLElement {
return this.tabbedLayout.getGui();
}
}