UNPKG

@openui5/sap.tnt

Version:

OpenUI5 UI Library sap.tnt

646 lines (549 loc) 18 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.tnt.NavigationList sap.ui.define([ "./library", "sap/ui/core/Lib", "sap/ui/core/Theming", "sap/ui/core/Element", "sap/ui/core/Control", "sap/ui/core/ResizeHandler", "sap/ui/core/Popup", "sap/m/library", "sap/m/Popover", "sap/ui/core/delegate/ItemNavigation", "sap/ui/core/InvisibleText", "./NavigationListItem", "./NavigationListMenuItem", "./NavigationListRenderer", "sap/m/Menu", "sap/base/Log" ], function ( library, Lib, Theming, Element, Control, ResizeHandler, Popup, mLibrary, Popover, ItemNavigation, InvisibleText, NavigationListItem, NavigationListMenuItem, NavigationListRenderer, Menu, Log ) { "use strict"; // shortcut for sap.m.PlacementType var PlacementType = mLibrary.PlacementType; /** * Constructor for a new <code>NavigationList</code>. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * The NavigationList control is an interactive control, which provides a choice of * different items, ordered as a list. * @extends sap.ui.core.Control * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.34 * @alias sap.tnt.NavigationList */ const NavigationList = Control.extend("sap.tnt.NavigationList", /** @lends sap.tnt.NavigationList.prototype */ { metadata: { library: "sap.tnt", properties: { /** * Specifies the width of the control. */ width: { type: "sap.ui.core.CSSSize", group: "Dimension" }, /** * Specifies if the control is in expanded or collapsed mode. */ expanded: { type: "boolean", group: "Misc", defaultValue: true }, /** * Specifies the currently selected key. * * @since 1.62.0 */ selectedKey: { type: "string", group: "Data" } }, defaultAggregation: "items", aggregations: { /** * The items displayed in the list. */ items: { type: "sap.tnt.NavigationListItemBase", multiple: true, singularName: "item" }, /** * The overflow item. */ _overflowItem: { type: "sap.tnt.NavigationListItem", multiple: false, visibility: "hidden" } }, associations: { /** * Association to controls / IDs, which describe this control (see WAI-ARIA attribute aria-describedby). */ ariaDescribedBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaDescribedBy" }, /** * Association to controls / IDs, which label this control (see WAI-ARIA attribute aria-labelledby). */ ariaLabelledBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy" }, /** * The currently selected <code>NavigationListItem</code>. * * @since 1.52.0 */ selectedItem: { type: "sap.tnt.NavigationListItem", multiple: false } }, events: { /** * Fired when an item is selected. */ itemSelect: { parameters: { /** * The selected item. */ item: { type: "sap.ui.core.Item" } } }, /** * Fired when an item is pressed. */ itemPress: { allowPreventDefault: true, parameters: { /** * The pressed item. */ item: { type: "sap.ui.core.Item" }, /** * Indicates whether the CTRL key was pressed when the link was selected. * @since 1.137 */ ctrlKey: { type: "boolean" }, /** * Indicates whether the Shift key was pressed when the link was selected. * @since 1.137 */ shiftKey: { type: "boolean" }, /** * Indicates whether the Alt key was pressed when the link was selected. * @since 1.137 */ altKey: { type: "boolean" }, /** * Indicates whether the "meta" key was pressed when the link was selected. * * On Macintosh keyboards, this is the command key (⌘). * On Windows keyboards, this is the windows key (⊞). * * @since 1.137 */ metaKey: { type: "boolean" } } } } }, renderer: NavigationListRenderer }); /** * Initializes the control. * @private * @override */ NavigationList.prototype.init = function () { this._oItemNavigation = new ItemNavigation(); this._oItemNavigation.setCycling(false) .setPageSize(10) .setDisabledModifiers({ sapnext: ["alt", "meta"], sapprevious: ["alt", "meta"] }); this.addDelegate(this._oItemNavigation); this._handleThemeAppliedBound = this._handleThemeApplied.bind(this); }; /** * Clears the control dependencies. * @private */ NavigationList.prototype.exit = function () { if (this._oItemNavigation) { this.removeDelegate(this._oItemNavigation); this._oItemNavigation.destroy(); this._oItemNavigation = null; } if (this._oPopover) { this._oPopover.destroy(); this._oPopover = null; } this._deregisterResizeHandler(); Theming.detachApplied(this._handleThemeAppliedBound); }; /** * Called before the control is rendered. */ NavigationList.prototype.onBeforeRendering = function () { this._deregisterResizeHandler(); // make sure the initial selected item (if any) is correct const sSelectedKey = this.getSelectedKey(); this.setSelectedKey(sSelectedKey); }; /** * Called after the control is rendered. */ NavigationList.prototype.onAfterRendering = function () { this._oItemNavigation.setRootDomRef(this.getDomRef()); this._animateExpandCollapse(); this._updateNavItems(); if (this.getExpanded()) { return; } // clear the vertical scroll when collapsed this.getDomRef().scrollTop = 0; this._sResizeListenerId = ResizeHandler.register(this.getDomRef().parentNode, this._resize.bind(this)); Theming.attachApplied(this._handleThemeAppliedBound); }; NavigationList.prototype._animateExpandCollapse = function () { const aAllItems = this.getItems().flatMap((item) => [item, ...item.getItems()]); const oAnimateExpandItem = aAllItems.find((oItem) => oItem._animateExpand); if (oAnimateExpandItem) { oAnimateExpandItem._animateExpand = false; oAnimateExpandItem.$() .find(".sapTntNLIItemsContainer") .first() .stop(true, true) .slideDown("fast", () => { oAnimateExpandItem.$().find(".sapTntNLIItemsContainer").first().removeClass("sapTntNLIItemsContainerHidden"); }); } const oAnimateCollapseItem = aAllItems.find((oItem) => oItem._animateCollapse); if (oAnimateCollapseItem) { oAnimateCollapseItem._animateCollapse = false; oAnimateCollapseItem.$() .find(".sapTntNLIItemsContainer") .first() .stop(true, true) .slideUp("fast", () => { oAnimateCollapseItem.$().find(".sapTntNLIItemsContainer").first().addClass("sapTntNLIItemsContainerHidden"); }); } }; NavigationList.prototype._deregisterResizeHandler = function () { if (this._sResizeListenerId) { ResizeHandler.deregister(this._sResizeListenerId); this._sResizeListenerId = null; } }; NavigationList.prototype._handleThemeApplied = function () { Theming.detachApplied(this._handleThemeAppliedBound); this._updateOverflowItems(); }; NavigationList.prototype._resize = function () { this._updateOverflowItems(); }; NavigationList.prototype._updateOverflowItems = function () { var oDomRef = this.getDomRef(); if (this.getExpanded() || !oDomRef) { return; } const oOverflowItemRef = oDomRef.querySelector(".sapTntNLOverflow"); if (!oOverflowItemRef) { return; } oOverflowItemRef.classList.add("sapTntNLIHidden"); const aItemsRefs = [...oDomRef.querySelectorAll("ul > :not(.sapTntNLOverflow)")]; let iItemsHeight = aItemsRefs.reduce((iSum, oItemRef) => { oItemRef.classList.remove("sapTntNLIHidden"); return iSum + oItemRef.offsetHeight; }, 0); const { paddingTop, paddingBottom } = window.getComputedStyle(oDomRef); const iListHeight = oDomRef.offsetHeight - parseFloat(paddingTop) - parseFloat(paddingBottom); if (iListHeight >= iItemsHeight) { return; } oOverflowItemRef.classList.remove("sapTntNLIHidden"); iItemsHeight = oOverflowItemRef.offsetHeight; let oSelectedItemRef = oDomRef.querySelector(".sapTntNLISelected"); if (oSelectedItemRef) { oSelectedItemRef = oSelectedItemRef.parentNode; const { marginTop, marginBottom } = window.getComputedStyle(oSelectedItemRef); iItemsHeight += oSelectedItemRef.offsetHeight + parseFloat(marginTop) + parseFloat(marginBottom); } aItemsRefs.forEach((oItemRef) => { if (oItemRef === oSelectedItemRef) { return; } const { marginTop, marginBottom } = window.getComputedStyle(oItemRef); iItemsHeight += oItemRef.offsetHeight + parseFloat(marginTop) + parseFloat(marginBottom); if (iItemsHeight >= iListHeight) { oItemRef.classList.add("sapTntNLIHidden"); } }); }; NavigationList.prototype._getOverflowItem = function () { let oOverflowItem = this.getAggregation("_overflowItem"); if (!oOverflowItem) { oOverflowItem = new NavigationListItem({ text: Lib.getResourceBundleFor("sap.tnt").getText("NAVIGATION_LIST_NAVIGATION_OVERFLOW"), icon: "sap-icon://overflow", selectable: false, select: this._overflowPress.bind(this) }); oOverflowItem._isOverflow = true; this.setAggregation("_overflowItem", oOverflowItem); } return oOverflowItem; }; NavigationList.prototype._overflowPress = function (oEvent) { const oOpener = oEvent.getSource(); oOpener.getDomRef().querySelector(".sapTntNLI").classList.add("sapTntNLIActive"); const oMenu = this._createOverflowMenu(oOpener); const oPopover = oMenu._getPopover(); oPopover.setPlacement(PlacementType.Right); oMenu.openBy(oOpener, false, Popup.Dock.EndCenter); }; NavigationList.prototype._createOverflowMenu = function (opener) { const oMenu = new Menu({ closed: function () { opener.getDomRef().querySelector(".sapTntNLI").classList.remove("sapTntNLIActive"); } }); oMenu.applySettings({ items: this._createNavigationMenuItems(oMenu) }); oMenu.addStyleClass("sapTntNLMenu"); const defaultAriaLabelId = oMenu._getPopover().getAriaLabelledBy()[0]; oMenu._getPopover().removeAriaLabelledBy(defaultAriaLabelId); const ariaLabel = InvisibleText.getStaticId("sap.tnt", "NAVIGATION_LIST_ADDITIONAL_ITEMS_MENU"); oMenu._getPopover().addAriaLabelledBy(ariaLabel); this.addDependent(oMenu); return oMenu; }; NavigationList.prototype._createNavigationMenuItems = function (oMenu) { var items = [], menuItems = []; this.getItems().forEach((item) => { if (item.isA("sap.tnt.NavigationListGroup")) { items.push(...item.getItems()); } else { items.push(item); } }); items.forEach(function (item) { if (!item.getVisible() || !item.getDomRef().classList.contains("sapTntNLIHidden")) { return; } var menuItem = new NavigationListMenuItem({ icon: item.getIcon(), text: item.getText(), tooltip: item.getTooltip_AsString() || item.getText(), visible: item.getVisible(), enabled: item.getEnabled(), href: item.getHref(), target: item.getTarget() }); menuItem._navItem = item; menuItem._oMenu = oMenu; // add parent item to the sub-menu if (item.getSelectable() && item.getItems().length) { var subMenuParentItem = new NavigationListMenuItem({ text: item.getText(), enabled: item.getEnabled(), href: item.getHref(), target: item.getTarget() }); subMenuParentItem._navItem = item; subMenuParentItem._oMenu = oMenu; menuItem.addItem(subMenuParentItem); } item.getItems().forEach(function (subItem) { var subMenuItem = new NavigationListMenuItem({ icon: subItem.getIcon(), text: subItem.getText(), tooltip: subItem.getTooltip_AsString() || subItem.getText(), visible: subItem.getVisible(), enabled: subItem.getEnabled(), href: subItem.getHref(), target: subItem.getTarget() }); subMenuItem._navItem = subItem; subMenuItem._oMenu = oMenu; menuItem.addItem(subMenuItem); }); menuItems.push(menuItem); }); return menuItems; }; NavigationList.prototype._updateNavItems = function () { const aDomRefs = this._getFocusDomRefs(); this._oItemNavigation.setItemDomRefs(aDomRefs); }; /** * Gets DOM references of the navigation items. * @private */ NavigationList.prototype._getFocusDomRefs = function () { const aDomRefs = this.getItems().flatMap((oItem) => oItem._getFocusDomRefs()), oOverflowDomRef = this._getOverflowItem().getDomRef("a"); if (!this.getExpanded() && oOverflowDomRef) { aDomRefs.push(oOverflowDomRef); } return aDomRefs; }; /** * Adapts popover position. * @private */ NavigationList.prototype._adaptPopoverPositionParams = function () { if (this.getShowArrow()) { this._marginLeft = 10; this._marginRight = 10; this._marginBottom = 10; this._arrowOffset = 8; this._offsets = ["0 -8", "8 0", "0 8", "-8 0"]; this._myPositions = ["center bottom", "begin top", "center top", "end top"]; this._atPositions = ["center top", "end top", "center bottom", "begin top"]; } else { this._marginTop = 0; this._marginLeft = 0; this._marginRight = 0; this._marginBottom = 0; this._arrowOffset = 0; this._offsets = ["0 0", "8 0", "0 0", "0 0"]; this._myPositions = ["begin bottom", "begin top", "begin top", "end top"]; this._atPositions = ["begin top", "end top", "begin bottom", "begin top"]; } }; /** * Selects an item. * @private */ NavigationList.prototype._selectItem = function (oParams) { this.fireItemSelect(oParams); this.setSelectedItem(oParams.item); }; NavigationList.prototype._findItemByKey = function (sKey) { const aAllItems = this.findAggregatedObjects(true, (oObject) => oObject.isA("sap.tnt.NavigationListItem")); return aAllItems.find((oItem) => oItem._getUniqueKey() === sKey); }; /** * Sets the selected item based on a key. * @public * @param {string} sSelectedKey The key of the item to be selected * @return {this} this pointer for chaining */ NavigationList.prototype.setSelectedKey = function (sSelectedKey) { const oItem = this._findItemByKey(sSelectedKey); this.setSelectedItem(oItem); return this.setProperty("selectedKey", sSelectedKey, true); }; /** * Gets the currently selected <code>NavigationListItem</code>. * @public * @return {sap.tnt.NavigationListItem|null} The selected item or <code>null</code> if nothing is selected */ NavigationList.prototype.getSelectedItem = function () { return Element.getElementById(this.getAssociation("selectedItem")) || null; }; /** * Sets the association for selectedItem. Set <code>null</code> to deselect. * @public * @param {sap.ui.core.ID|sap.tnt.NavigationListItem} oItem The control to be set as selected * @return {sap.tnt.NavigationList|null} The <code>selectedItem</code> association */ NavigationList.prototype.setSelectedItem = function (oItem) { let oSelectedItem; if (this._selectedItem) { this._selectedItem._toggle(false); const oPrevParent = this._selectedItem.getParent(); if (oPrevParent && oPrevParent.isA && oPrevParent.isA("sap.tnt.NavigationListItem") && oPrevParent.getExpanded() === false && oPrevParent._getVisibleItems(oPrevParent).includes(this._selectedItem)) { if (!oPrevParent._getVisibleItems(oPrevParent).includes(oItem)) { oPrevParent._toggle(false); } } } if (!oItem) { this._selectedItem = null; } const bValidItemType = oItem && oItem.isA && oItem.isA("sap.tnt.NavigationListItem"); if (typeof oItem != "string" && !bValidItemType) { this.setAssociation("selectedItem", null, true); this._updateOverflowItems(); oItem = null; } this.setAssociation("selectedItem", oItem, true); if (typeof oItem == "string") { oSelectedItem = Element.getElementById(oItem); } else if (bValidItemType) { oSelectedItem = oItem; } else { Log.warning("Type of selectedItem association should be a valid NavigationListItem object or ID. New value was not set."); return this; } this.setProperty("selectedKey", oSelectedItem._getUniqueKey(), true); oSelectedItem._toggle(true); this._selectedItem = oSelectedItem; this._updateOverflowItems(); return this; }; /** * Opens a popover. * @private */ NavigationList.prototype._openPopover = function (oOpener, oList) { const oOpenerMainRef = oOpener.getDomRef().querySelector(".sapTntNLI"); oOpenerMainRef.classList.add("sapTntNLIActive"); let oSelectedItem = oList.getSelectedItem(); if (oSelectedItem && oList.isGroupSelected) { oSelectedItem = null; } if (this._oPopover) { this._oPopover.destroy(); } this._oPopover = new Popover({ showHeader: false, horizontalScrolling: false, verticalScrolling: true, showArrow: false, initialFocus: oSelectedItem, afterClose: () => { if (this._oPopover) { this._oPopover.destroy(); this._oPopover = null; oOpenerMainRef.classList.remove("sapTntNLIActive"); } }, content: oList, ariaLabelledBy: InvisibleText.getStaticId("sap.tnt", "NAVIGATION_LIST_DIALOG_TITLE") }).addStyleClass("sapContrast sapContrastPlus sapTntNLPopover"); this.addDependent(this._oPopover); this._oPopover._adaptPositionParams = this._adaptPopoverPositionParams; this._oPopover.openBy(oOpener.getDomRef("a")); }; NavigationList.prototype._closePopover = function () { if (this._oPopover) { this._oPopover.close(); } }; NavigationList.prototype._containsIcon = function () { const aFound = this.findAggregatedObjects(true, (oObject) => oObject.isA("sap.tnt.NavigationListItem") && !oObject._isOverflow && oObject.getProperty("icon") ); return !!aFound.length; }; return NavigationList; });