UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

811 lines (721 loc) 23.6 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ "sap/m/ResponsivePopover", "sap/m/Button", "sap/m/OverflowToolbar", "sap/m/Toolbar", "sap/m/ToolbarSpacer", "sap/m/Title", "sap/m/List", "sap/m/InputListItem", "sap/m/CustomListItem", "sap/m/Label", "sap/m/IllustratedMessage", "sap/m/IllustratedMessageType", "sap/m/IllustratedMessageSize", "sap/m/library", "sap/ui/Device", "sap/ui/core/Control", "sap/ui/core/Element", "sap/ui/core/Lib", "sap/ui/core/library", "sap/ui/core/StaticArea", "sap/ui/layout/form/Form", "sap/ui/layout/form/FormContainer", "sap/ui/layout/form/FormElement", "sap/ui/layout/form/ResponsiveGridLayout", "sap/ui/layout/GridData", "sap/ui/performance/trace/FESRHelper", "sap/ui/thirdparty/jquery", "sap/ui/dom/containsOrEquals", "sap/ui/events/ControlEvents", "sap/base/strings/capitalize", "sap/m/p13n/AbstractContainerItem", "sap/m/p13n/Container", "sap/m/table/columnmenu/MenuBase", "sap/m/table/columnmenu/MenuRenderer" ], function ( ResponsivePopover, Button, OverflowToolbar, Toolbar, ToolbarSpacer, Title, List, InputListItem, CustomListItem, Label, IllustratedMessage, IllustratedMessageType, IllustratedMessageSize, library, Device, Control, Element, Library, coreLibrary, StaticArea, Form, FormContainer, FormElement, ResponsiveGridLayout, GridData, FESRHelper, jQuery, containsOrEquals, ControlEvents, capitalize, AbstractContainerItem, Container, MenuBase, MenuRenderer ) { "use strict"; var HasPopup = coreLibrary.aria.HasPopup; var Category = library.table.columnmenu.Category; /** * Constructor for a new <code>Menu</code>. * * @param {string} [sId] ID for the new <code>Menu</code>, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new <code>Menu</code> * * @class * The <code>Menu</code> control is a popover, intended to be used by a table. * It serves as an entry point for the table personalization via the column headers. * The menu is separated into two sections: quick actions and menu items. * * The top section of the popover contains contextual quick actions for the column the menu was triggered from. * The lower section contains menu items related to generic and global table settings. * * There are control- and application-specific quick actions and menu items. * Applications can add their own quick actions and items. * * @extends sap.m.table.columnmenu.MenuBase * * @author SAP SE * @version 1.146.0 * * @public * @since 1.110 * * @alias sap.m.table.columnmenu.Menu */ var Menu = MenuBase.extend("sap.m.table.columnmenu.Menu", { metadata: { library: "sap.m", defaultAggregation: "quickActions", properties: { /** * Specifies whether the table settings button is visible. */ showTableSettingsButton: { type: "boolean", defaultValue: false } }, aggregations: { /** * Defines the quick actions of the column menu. */ quickActions: { type: "sap.m.table.columnmenu.QuickActionBase" }, /** * Defines the items of the column menu. */ items: { type: "sap.m.table.columnmenu.ItemBase" }, /** * Defines quick actions that are control-specific. * @private */ _quickActions: { type: "sap.m.table.columnmenu.QuickActionBase", visibility: "hidden" }, /** * Defines menu items that are control-specific. * @private */ _items: { type: "sap.m.table.columnmenu.ItemBase", visibility: "hidden" } }, events: { /** * Fires when the table settings button is pressed. */ tableSettingsPressed: {} } }, renderer: MenuRenderer }); var DEFAULT_KEY = "$default"; var ARIA_POPUP_TYPE = HasPopup.Dialog; Menu.prototype.init = function() { this.fAnyEventHandlerProxy = jQuery.proxy(function(oEvent){ if (!this.isOpen() || !this.getDomRef() || (oEvent.type != "mousedown" && oEvent.type != "touchstart")) { return; } this.handleOuterEvent(this.getId(), oEvent); }, this); }; /** * Opens the popover at the specified target. * * @param {sap.ui.core.Control | HTMLElement} oAnchor This is the control or HTMLElement where the popover is placed. * @param {boolean} [bSuppressEvent] Whether to suppress the beforeOpen event. * @public */ Menu.prototype.openBy = function(oAnchor, bSuppressEvent) { if (this.isOpen() && oAnchor === this._oIsOpenBy) { return; } var bExecuteDefault = true; var oControl = oAnchor; if (!(oAnchor instanceof Element)) { oControl = Element.closestTo(oAnchor, true); } if (!bSuppressEvent) { bExecuteDefault = this.fireBeforeOpen({ openBy: oControl }); } if (!bExecuteDefault) { return; } const fnOpen = () => { this._initPopover(); this._oQuickSortList = this._initQuickActionList(Category.Sort); this._oQuickFilterList = this._initQuickActionList(Category.Filter); this._oQuickGroupList = this._initQuickActionList(Category.Group); this._oQuickAggregateList = this._initQuickActionList(Category.Aggregate); this._oQuickGenericList = this._initQuickActionList(Category.Generic); this._initItemsContainer(); if (!this.getParent()) { StaticArea.getUIArea().addContent(this, true); } if (this._getAllEffectiveQuickActions().length === 0 && this._getAllEffectiveItems().length === 0) { this._initIllustratedMessage(); } this._oPopover.setInitialFocus(this._oQuickSortList || this._oQuickFilterList || this._oQuickGroupList || this._oQuickAggregateList || this._oQuickGenericList || this._oItemsContainer); this._oPopover.openBy(oAnchor); this._oIsOpenBy = oAnchor; ControlEvents.bindAnyEvent(this.fAnyEventHandlerProxy); }; if (this.isOpen()) { // If the menu is already opened, close it before rerendering content and opening it at another position // Otherwise, there is a short time frame, where users can see the popover "flicker" this._oPopover.attachEventOnce("afterClose", fnOpen); this.close(); } else { fnOpen(); } }; Menu.prototype.setShowTableSettingsButton = function(bShowTableSettingsButton) { this.setProperty("showTableSettingsButton", bShowTableSettingsButton, true); if (!this._oPopover) { return this; } if (!bShowTableSettingsButton) { this._oPopover.getEndButton()?.destroy(); this._oPopover.setEndButton(null); } else { this._oPopover.setEndButton(createTableSettingsButton(this)); } return this; }; function createTableSettingsButton(oMenu) { const oButton = new Button({ icon: "sap-icon://action-settings", tooltip: oMenu._getResourceText("table.COLUMNMENU_TABLE_SETTINGS"), press: () => { oMenu._oPopover.close(); oMenu.fireTableSettingsPressed(); } }); FESRHelper.setSemanticStepname(oButton, "press", "tbl:p13n"); return oButton; } /** * @inheritdoc */ Menu.prototype.getAriaHasPopupType = function () { return ARIA_POPUP_TYPE; }; /** * @inheritdoc */ Menu.prototype.isOpen = function () { return this._oPopover ? this._oPopover.isOpen() : false; }; /** * @inheritdoc */ Menu.prototype.close = function () { this._previousView = null; if (this._oPopover && this._oPopover.isOpen()) { if (this._oQuickSortList) { // Destroying the list does not destroy the quick action content, which may be reused somewhere else. // The list is removed from the control tree so that upon destruction its DOM is removed synchronously. this.removeDependent(this._oQuickSortList); this._oQuickSortList.destroy(); this._oQuickSortList = null; } if (this._oQuickFilterList) { this.removeDependent(this._oQuickFilterList); this._oQuickFilterList.destroy(); this._oQuickFilterList = null; } if (this._oQuickGroupList) { this.removeDependent(this._oQuickGroupList); this._oQuickGroupList.destroy(); this._oQuickGroupList = null; } if (this._oQuickAggregateList) { this.removeDependent(this._oQuickAggregateList); this._oQuickAggregateList.destroy(); this._oQuickAggregateList = null; } if (this._oQuickGenericList) { this.removeDependent(this._oQuickGenericList); this._oQuickGenericList.destroy(); this._oQuickGenericList = null; } if (this._oItemsContainer) { this.removeDependent(this._oItemsContainer); this._oItemsContainer.destroy(); this._oItemsContainer = null; } StaticArea.getUIArea().removeContent(this, true); this._oPopover.close(); ControlEvents.unbindAnyEvent(this.fAnyEventHandlerProxy); } }; Menu.prototype._onPopoverAfterClose = function () { this.fireAfterClose(); }; Menu.prototype.exit = function () { MenuBase.prototype.exit.apply(this, arguments); if (this._oPopover) { delete this._oPopover; } if (this._oQuickSortList) { delete this._oQuickSortList; } if (this._oQuickFilterList) { delete this._oQuickFilterList; } if (this._oQuickGroupList) { delete this._oQuickGroupList; } if (this._oQuickAggregateList) { delete this._oQuickAggregateList; } if (this._oQuickGenericList) { delete this._oQuickGenericList; } if (this._oItemsContainer) { delete this._oItemsContainer; } if (this._oIsOpenBy) { delete this._oIsOpenBy; } if (this._oIllustratedMessage) { this._oIllustratedMessage.destroy(); delete this._oIllustratedMessage; } ControlEvents.unbindAnyEvent(this.fAnyEventHandlerProxy); }; Menu.prototype._initPopover = function () { if (this._oPopover) { return; } this._oPopover = new ResponsivePopover({ showArrow: false, showHeader: Device.system.phone, placement: library.PlacementType.VerticalPreferredBottom, content: new AssociativeControl({control: this, height: true}), horizontalScrolling: false, verticalScrolling: true, afterClose: [this._onPopoverAfterClose, this], ariaLabelledBy: this.getId() + "-title", customHeader: new OverflowToolbar({ content: [ new Title({id: this.getId() + "-title", text: this._getResourceText("table.COLUMNMENU_TITLE")}), new ToolbarSpacer(), new Button({ icon: "sap-icon://decline", tooltip: this._getResourceText("table.COLUMNMENU_CLOSE"), press: () => { this._oPopover.close(); } }) ] }).addStyleClass("sapMTBHeader-CTX") }); if (this.getShowTableSettingsButton()) { this._oPopover.setEndButton(createTableSettingsButton(this)); } this.addDependent(this._oPopover); this._oPopover.addStyleClass("sapMTCMenuPopup"); this._oPopover.addEventDelegate({ "onsapfocusleave": this.handleFocusLeave }, this); this._oPopover._oControl.oPopup.setAutoClose(false); }; Menu.prototype.handleFocusLeave = function(oEvent){ if (!this.isOpen()) { return; } if (oEvent.relatedControlId && (!containsOrEquals(this.getDomRef(), jQuery(document.getElementById(oEvent.relatedControlId)).get(0)) && !isInControlTree(this, Element.getElementById(oEvent.relatedControlId)))) { this.close(); } }; Menu.prototype.handleOuterEvent = function(oMenuId, oEvent) { var touchEnabled = Device.support.touch || Device.system.combi; if (touchEnabled && (oEvent.isMarked("delayedMouseEvent") || oEvent.isMarked("cancelAutoClose"))) { return; } if (oEvent.type == "mousedown" || oEvent.type == "touchstart") { if (!containsOrEquals(this.getDomRef(), oEvent.target) && !containsOrEquals(this._oPopover.getDomRef(), oEvent.target) && !isInControlTree(this, Element.closestTo(oEvent.target))) { this.close(); } } }; function isInControlTree(oParent, oChild) { if (!oParent || !oChild) { return false; } var temp = oChild.getParent(); if (!temp) { return false; } else if (temp === oParent) { return true; } return isInControlTree(oParent, temp); } Menu.prototype._initItemsContainer = function () { var aMenuItems = this._getAllEffectiveItems(); var bHasitems = this._hasItems(); if (bHasitems && !this._oItemsContainer) { this._createItemsContainer(); } aMenuItems.forEach((oColumnMenuItem) => { this._addView(oColumnMenuItem); }); }; var AssociativeControl = Control.extend("sap.m.table.columnmenu.AssociativeControl", { metadata: { library: "sap.m", "final": true, properties: { height: {type: "boolean", defaultValue: false} }, associations: { control: {type: "sap.ui.core.Control"} } }, renderer: { apiVersion: 2, render: function (oRm, oControl) { oRm.openStart("div", oControl); oControl.getHeight() && oRm.style("height", "100%"); oRm.openEnd(); oRm.renderControl(Element.getElementById(oControl.getControl())); oRm.close("div"); } }, addAriaLabelledBy: function(vAriaLabelledBy) { const oControl = Element.getElementById(this.getControl()); oControl?.removeAllAssociation("ariaLabelledBy"); oControl?.addAriaLabelledBy?.(vAriaLabelledBy); return this; }, getAriaLabelledBy: function() { const oControl = Element.getElementById(this.getControl()); return oControl?.getAriaLabelledBy?.() || []; } }); Menu.prototype._addView = function (oMenuItem) { var oItem = new AbstractContainerItem({ content: new AssociativeControl({ control: oMenuItem.getContent(), height: true }), key: oMenuItem.getId(), text: oMenuItem.getLabel(), icon: oMenuItem.getIcon(), type: oMenuItem.isA("sap.m.table.columnmenu.ActionItem") ? library.ListType.Active : library.ListType.Navigation }); this._oItemsContainer.addView(oItem); this._setItemVisibility(oMenuItem, oMenuItem.getVisible()); }; Menu.prototype._createItemsContainer = function () { this._oBtnCancel = new Button({ text: this._getResourceText("table.COLUMNMENU_CANCEL"), press: [function () { var sKey = this._oItemsContainer.getCurrentViewKey(); if (this._fireEvent(Element.getElementById(sKey), "cancel")) { this.close(); } }, this] }); this._oBtnOk = new Button({ text: this._getResourceText("table.COLUMNMENU_CONFIRM"), type: library.ButtonType.Emphasized, press: [function () { var sKey = this._oItemsContainer.getCurrentViewKey(); if (this._fireEvent(Element.getElementById(sKey), "confirm")) { this.close(); } }, this] }); this._oItemsContainer = new Container({ listLayout: true, defaultView: DEFAULT_KEY, footer: new Toolbar({ content: [ new ToolbarSpacer(), this._oBtnOk, this._oBtnCancel ] }), beforeViewSwitch: [function (oEvent) { var mParameters = oEvent.getParameters(); this.invalidate(); if (mParameters.target !== "$default") { var oContainerItem = this._oItemsContainer.getView(mParameters.target); var oColumnMenuItem = this._getItemFromContainerItem(oContainerItem); if (oColumnMenuItem && !this._fireEvent(oColumnMenuItem, "press")) { oEvent.preventDefault(); } } }, this], afterViewSwitch: [function (oEvent) { var aDependents = this.getDependents(); if (aDependents) { aDependents.forEach(function (oDependent) { if (oDependent && oDependent.isA("sap.ui.core.Control")) { oDependent.invalidate(); } }); } var mParameters = oEvent.getParameters(); this._oItemsContainer.getLayout().setShowFooter(mParameters.target !== "$default"); this._previousView = mParameters.source; if (mParameters.target !== "$default") { var oContainerItem = this._oItemsContainer.getView(mParameters.target); if (oContainerItem) { var oItem = this._getItemFromContainerItem(oContainerItem); this._updateButtonState(oItem); this._focusItem(); } } else { this._focusItem(); } }, this] }); const sTitle = this._hasQuickActions() ? this._getResourceText("table.COLUMNMENU_LIST_ITEMS_TITLE") : this._getResourceText("table.COLUMNMENU_LIST_ITEMS_ONLY_TITLE"); this._oItemsContainer.setListHeader(new OverflowToolbar({ content: [ new Title({text: sTitle, level: coreLibrary.TitleLevel.H3}) ] })); this._oItemsContainer.getHeader().addContentRight(new Button({ text: this._getResourceText("table.COLUMNMENU_RESET"), press: [function () { this._fireEvent(Element.getElementById(this._oItemsContainer.getCurrentViewKey()), "reset", false); }, this] })); this.addDependent(this._oItemsContainer); }; Menu.prototype._fireEvent = function (oEntry, sEventType, bAllowPreventDefault) { var fnHook = oEntry["on" + capitalize(sEventType)]; if (bAllowPreventDefault !== false) { var oEvent = jQuery.Event(sEventType); fnHook.call(oEntry, oEvent); return !oEvent.isDefaultPrevented(); } else { fnHook.call(oEntry); return true; } }; Menu.prototype._getResourceText = function(sText, aValue) { this.oResourceBundle = this.oResourceBundle ? this.oResourceBundle : Library.getResourceBundleFor("sap.m"); return sText ? this.oResourceBundle.getText(sText, aValue) : this.oResourceBundle; }; var mSortOrder = {}; mSortOrder[Category.Sort] = 0; mSortOrder[Category.Filter] = 1; mSortOrder[Category.Group] = 2; mSortOrder[Category.Aggregate] = 3; mSortOrder[Category.Generic] = 4; Menu.prototype._getAllEffectiveQuickActions = function(bSkipImplicitSorting) { var aQuickActions = (this.getAggregation("_quickActions") || []).concat(this.getQuickActions()); aQuickActions = aQuickActions.reduce(function(aQuickActions, oQuickAction) { return aQuickActions.concat(oQuickAction ? oQuickAction.getEffectiveQuickActions() : []); }, []); if (!bSkipImplicitSorting) { aQuickActions.sort(function(oLeftQuickAction, oRightQuickAction) { return mSortOrder[oLeftQuickAction.getCategory()] - mSortOrder[oRightQuickAction.getCategory()]; }); } return aQuickActions; }; Menu.prototype._hasQuickActions = function() { return this._getAllEffectiveQuickActions(true).length > 0; }; Menu.prototype._getAllEffectiveItems = function() { var aItems = (this.getAggregation("_items") || []).concat(this.getItems()); return aItems.reduce(function(a, oItem) { return a.concat(oItem.getEffectiveItems()); }, []).filter(function (oItem) { return oItem.getVisible(); }); }; Menu.prototype._hasItems = function() { return this._getAllEffectiveItems().length > 0; }; Menu.prototype._getItemFromContainerItem = function (oContainerItem) { // Low performance as linear search has to be done return this._getAllEffectiveItems().find(function(item) { return item.getId() === oContainerItem.getKey(); }); }; Menu.prototype._updateButtonState = function (oItem) { if (!this._oItemsContainer) { return; } if (this._oItemsContainer.getCurrentViewKey() === DEFAULT_KEY) { return; } this._oItemsContainer.getHeader().getContentRight()[0].setVisible(oItem.getButtonSettings()["reset"]["visible"]); this._oItemsContainer.getHeader().getContentRight()[0].setEnabled(oItem.getButtonSettings()["reset"]["enabled"]); this._oBtnOk.setVisible(oItem.getButtonSettings()["confirm"]["visible"]); this._oBtnCancel.setVisible(oItem.getButtonSettings()["cancel"]["visible"]); }; Menu.prototype._focusItem = function () { if (this._previousView == DEFAULT_KEY) { this._oItemsContainer._getNavBackBtn().focus(); } else { var oItem = this._oItemsContainer?._getNavigationList().getItems().find(function (oItem) { return oItem.getVisible() && oItem._key === this._previousView; }.bind(this)); oItem && oItem.focus(); } }; Menu.prototype._setItemVisibility = function (oItem, bVisible) { if (!this._oItemsContainer) { return; } var oList = this._oItemsContainer?._getNavigationList().getItems(); var oListItem = oList.find(function (oListItem) { return oListItem._key == oItem.getId(); }); oListItem?.setVisible(bVisible); }; function _hasNonGenericQuickActions() { return this._getAllEffectiveQuickActions(true).filter(function(oQuickAction) { return oQuickAction.getVisible() && oQuickAction.getCategory() !== Category.Generic; }).length > 0; } Menu.prototype._initQuickActionList = function (sCategory) { var oList; var aQuickActions = this._getAllEffectiveQuickActions().filter(function (oQuickAction) { return oQuickAction.getVisible() && oQuickAction.getCategory() === sCategory; }); const sTitle = (sCategory === Category.Generic && !_hasNonGenericQuickActions.call(this)) ? this._getResourceText("table.COLUMNMENU_QUICK_GENERIC_ONLY_TITLE") : this._getResourceText("table.COLUMNMENU_QUICK_" + sCategory.toUpperCase() + "_TITLE"); if (aQuickActions.length) { oList = new List({ headerToolbar: new OverflowToolbar({ content: [new Title({text: sTitle, level: coreLibrary.TitleLevel.H3})] }), keyboardMode: "Edit", items: [] }); aQuickActions.map(function (oQuickAction) { if (oQuickAction.getContent()?.length === 1 && oQuickAction.getContent()[0].isA("sap.ui.core.IFormContent")) { oList.addItem(new InputListItem({ contentSize: oQuickAction.getContentSize(), label: oQuickAction.getLabel(), content: createAssociativeControlWrapper([].concat(oQuickAction.getContent())) })); } else { oList.addItem(new CustomListItem({ content: createAssociativeControlForm(oQuickAction) })); } }); this.addDependent(oList); } return oList; }; function createAssociativeControlForm(oQuickAction) { var oLabel = new Label({ text: oQuickAction.getLabel(), layoutData: new GridData({span: "XL4 L4 M4 S12"}), wrapping: true, width: "100%" }); // Create content var aContent = oQuickAction.getContent() || []; var oFormContainer = new FormContainer(); var oForm = new Form({ layout: new ResponsiveGridLayout({ breakpointM: 600, labelSpanXL: 4, labelSpanL: 4, labelSpanM: 4, labelSpanS: 12, columnsL: 1, columnsM: 1, adjustLabelSpan: false }), formContainers: [oFormContainer] }); var aControls = []; aContent.forEach(function(oItem, iIndex) { var oGridData, sSpan, sIndent, oControl; if (oItem.getLayoutData()) { oGridData = oItem.getLayoutData().clone(); } else { sSpan = "L8 M8 S12"; sIndent = ""; if (iIndex > 0 || (iIndex == 0 && aContent.length > 1)) { sSpan = "L4 M4 S6"; if (iIndex != 0 && (iIndex + 1) % 2 > 0) { sIndent = "L4 M4 S0"; } } oGridData = new GridData({span: sSpan, indent: sIndent}); } oItem.removeAllAssociation("ariaLabelledBy"); oItem.addAssociation("ariaLabelledBy", oLabel.getId()); oControl = new AssociativeControl({control: oItem.setWidth("100%")}); oControl.setLayoutData(oGridData); aControls.push(oControl); }, this); oFormContainer.addFormElement(new FormElement({label: oLabel, fields: aControls})); return oForm; } function createAssociativeControlWrapper(aContent) { var aControls = []; aContent.forEach(function(oItem) { var oControl = new AssociativeControl({control: oItem}); aControls.push(oControl); }); return aControls; } Menu.prototype._initIllustratedMessage = function () { if (this._oIllustratedMessage) { return; } this._oIllustratedMessage = new IllustratedMessage({ title: this._getResourceText("table.COLUMNMENU_EMPTY"), illustrationType: IllustratedMessageType.NoColumnsSet, illustrationSize: IllustratedMessageSize.ExtraSmall, enableDefaultTitleAndDescription: false }); this.addDependent(this._oIllustratedMessage); }; return Menu; });