/*! * SAPUI5 * Copyright (c) 2025 SAP SE or an SAP affiliate company. All rights reserved. */ import * as SearchHelper from "sap/esh/search/ui/SearchHelper"; import { URI as sapURI } from "sap/ui/core/library"; import Link, { $LinkSettings } from "sap/m/Link"; import { NavigationTarget } from "../sinaNexTS/sina/NavigationTarget"; import { MetadataOptions, PropertyBindingInfo } from "sap/ui/base/ManagedObject"; import { NavigationTarget as NavigationTargetApi } from "../ResultSetApi"; // reduced sina-NavigationTarget () import type SearchModel from "../SearchModel"; import { emptyValueUnicode, initialValueUnicode } from "../uiConstants"; import ErrorHandler from "../error/ErrorHandler"; import { ProgramError } from "../error/errors"; export interface $SearchLinkSettings extends $LinkSettings { navigationTarget: NavigationTarget | PropertyBindingInfo | NavigationTargetApi; forceNavigationAnchorElementNotProcessed?: boolean; // when SearchLink is used on sap.m.GenericTile, the a-element does not call its href, we need to let the function 'performNavigation' of item's navigationTarget do the job visualisation?: string | PropertyBindingInfo; } /** * Display search result attribute (with navigation) on custom grid control tiles * */ /** * Constructs a new SearchText to display an attribute (with navigation) on custom grid control tiles. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [navigationTarget] Object instance of NavigationTarget which contains all relevant information for navigation * * @class * * This is the SAPUI5 search link control to display attributes of search results. * For attribute without navigation use control sap.esh.search.ui.controls.resultview.SearchText. * Use in combination with control SearchCompositeControl by the Enterprise Search Team. * * @extends sap.m.Link * * @author SAP SE * @version 1.150.1 * * @see https://help.sap.com/viewer/691cb949c1034198800afde3e5be6570/2.0.08/en-US/ce86ef2fd97610149eaaaa0244ca4d36.html SAP HANA Search Developer Guide * @see https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE/90b263323d0a4976bcda415e6bd20ea4/d93d53ef27074bbf9a4849c4a4c3e360.html Enterprise Search in SAP S/4HANA * * * @constructor * @public * @alias sap.esh.search.ui.controls.SearchLink * @since 1.93.0 * @name sap.esh.search.ui.controls.SearchLink * */ /** * @namespace sap.esh.search.ui.controls */ export default class SearchLink extends Link { static readonly metadata: MetadataOptions = { library: "sap.esh.search.ui.controls", interfaces: ["sap.m.IOverflowToolbarContent"], properties: { /** * An object containing all data needed to define navigation of a attribute (result item) * @since 1.93.0 */ navigationTarget: { type: "object", group: "Data", }, /** * When SearchLink is used on sap.m.GenericTile, the a-element does not call its href, we need to let the function 'performNavigation' of item's navigationTarget do the job * @since 1.143.0 */ forceNavigationAnchorElementNotProcessed: { type: "boolean", group: "Behavior", defaultValue: false, }, /** * Visualisation style of search navigation * @since 1.144.0 */ visualisation: { type: "string", defaultValue: "hyperLink", // "hyperLink" (default), "hyperLinkCentered", "buttonCentered" }, }, }; private _pressHandlerAttached = false; constructor(sId?: string, settings?: $SearchLinkSettings) { super(sId, settings); this.addStyleClass("sapUshellSearchLink"); } getOverflowToolbarConfig() { return { canOverflow: true, autoCloseEvents: ["press"], }; } setVisualisation(sVisualisation: string): void { this.setProperty("visualisation", sVisualisation); this.updateVisualisationStyleClasses(); } deriveVisualisation(): void { if (this.getIcon() && this.getText() === emptyValueUnicode) { // 'icon-only' navigation target this.setVisualisation("buttonCentered"); // this.setVisualisation("hyperLinkCentered"); } else { this.setVisualisation("hyperLink"); } } private updateVisualisationStyleClasses(): void { const sVisualisation = this.getProperty("visualisation"); if (sVisualisation === "hyperLink") { // this.addStyleClass("sapUshellSearchLinkHyperlink"); // default, no dedicated class needed for now this.removeStyleClass("sapUshellSearchLinkHyperlinkCentered"); this.removeStyleClass("sapUshellSearchLinkButtonCentered"); } else if (sVisualisation === "hyperLinkCentered") { this.addStyleClass("sapUshellSearchLinkHyperlinkCentered"); // this.removeStyleClass("sapUshellSearchLinkHyperlink"); // default, no dedicated class needed for now this.removeStyleClass("sapUshellSearchLinkButtonCentered"); } else if (sVisualisation === "buttonCentered") { this.addStyleClass("sapUshellSearchLinkButtonCentered"); // this.removeStyleClass("sapUshellSearchLinkHyperlink"); // default, no dedicated class needed for now this.removeStyleClass("sapUshellSearchLinkHyperlinkCentered"); } } pressHandlerSearchLink(oEvent): void { const navTarget = this.getNavigationTarget(); const model = this.getModel() as SearchModel; if (model && model.config?.clearObjectSelectionOnSearchLinkClick) { (this.getModel() as SearchModel).resetKeyStore(); } try { if (navTarget.targetUrl && !this.getProperty("forceNavigationAnchorElementNotProcessed")) { // 1) navigation target has target url // - navigation itself is performed by clicking on tag // - performNavigation is just called for tracking navTarget.performNavigation({ trackingOnly: true, event: oEvent, }); } else { // 2) no target url instead there is a js target function // performNavigation does tracking + navigation (window.open...) oEvent.preventDefault(); // really necessary? navTarget.performNavigation({ event: oEvent, }); } } catch (e) { const err = e instanceof Error ? e : new Error(String(e)); ErrorHandler.getInstance().onError(new ProgramError(err)); } // oEvent.preventDefault does work for // -desktop // -mobile in case targetUrl=empty // oEvent.preventDefault does not work for mobile in case targetUrl is filled // reason: for mobile there is a UI5 async event simulation so preventDefault does not work // for sap.m.Link this a special logic which makes preventDefault working for mobile in case // targetUrl=empty (href='#') } setNavigationTarget(navigationTarget: NavigationTarget): this { this.setProperty("navigationTarget", navigationTarget); // calculate enabled const text = this.getText(); if ( ((typeof navigationTarget?.targetUrl !== "string" || navigationTarget?.targetUrl?.length === 0) && typeof navigationTarget?.targetFunction !== "function") || !(typeof text === "string" && text !== initialValueUnicode) // dash ) { this.setProperty("enabled", false); } else { this.setProperty("enabled", true); } // set href const navigationTargetHref = navigationTarget?.targetUrl; if (typeof navigationTargetHref === "string" && navigationTargetHref?.length > 0) { this.setProperty("href", navigationTargetHref); } else { this.setProperty("href", ""); } // set target const navigationTargetTarget = navigationTarget?.target; if (typeof navigationTargetTarget === "string" && navigationTargetTarget?.length > 0) { this.setProperty("target", navigationTargetTarget); } else { this.setProperty("target", "_self"); } // set icon const navigationTargetIcon = navigationTarget?.icon; if (!this.getIcon() && typeof navigationTargetIcon === "string" && navigationTargetIcon?.length > 0) { this.setIcon(navigationTargetIcon); } // set tooltip const navigationTargetTooltip = navigationTarget?.tooltip; if ( !this.getTooltip() && typeof navigationTargetTooltip === "string" && navigationTargetTooltip?.length > 0 ) { this.setTooltip(navigationTargetTooltip); } // 'icon-only' navigation target this.deriveVisualisation(); return this; } setText(sText?: string): this { this.setProperty("text", sText); const navigationTarget = this.getNavigationTarget(); if (!(navigationTarget instanceof NavigationTarget)) { return this; } if ( ((typeof navigationTarget?.targetUrl !== "string" || navigationTarget?.targetUrl?.length === 0) && typeof navigationTarget?.targetFunction !== "function") || !(typeof sText === "string" && sText !== initialValueUnicode) // dash ) { this.setProperty("enabled", false); } // 'icon-only' navigation target this.deriveVisualisation(); return this; } getNavigationTarget(): NavigationTarget { return this.getProperty("navigationTarget"); } setEnabled(bEnabled?: boolean): this { if (bEnabled === true) { const navigationTarget = this.getNavigationTarget(); const text = this.getText(); if ( (navigationTarget instanceof NavigationTarget && (typeof navigationTarget.targetUrl !== "string" || navigationTarget.targetUrl?.length === 0) && typeof navigationTarget.targetFunction !== "function") || !(typeof text === "string" && text !== initialValueUnicode) // dash ) { this.setProperty("enabled", false); return this; } } this.setProperty("enabled", bEnabled); return this; } setIcon(sIcon?: sapURI): this { if (!this.getIcon() && typeof sIcon === "string" && sIcon.startsWith("sap-icon://")) { super.setIcon(sIcon); } return this; } onAfterRendering(oEvent): void { super.onAfterRendering(oEvent); if (this.isDestroyed()) { return; } if (!this._pressHandlerAttached) { this.attachPress(this.pressHandlerSearchLink, this); this._pressHandlerAttached = true; } const linkDomRef = this.getDomRef() as HTMLElement; // sap.m.Link renders href="#" for empty href. Remove it for programmatic targets // so clicking the busy overlay doesn't navigate to the home page. const navTarget = this.getNavigationTarget(); if ( linkDomRef && navTarget && (!navTarget.targetUrl || this.getProperty("forceNavigationAnchorElementNotProcessed")) ) { if (linkDomRef.getAttribute("href") === "#") { linkDomRef.removeAttribute("href"); } } // recover bold tag with the help of text() in a safe way SearchHelper.boldTagUnescaper(linkDomRef); SearchHelper.calculateTooltipForwordEllipsis(linkDomRef); } _handlePress(oEvent) { // in case of highlighting the target property of the event is a element inside the Link. // therefore setting it manually to Links DomRef / parentElement of the target. if (oEvent.target.localName === "b") { const oTarget = this.getDomRef() ? this.getDomRef() : oEvent.target.parentElement; oEvent.target = oTarget; } // eslint-disable-next-line prefer-rest-params Link.prototype["_handlePress"].apply(this, arguments); } // overwrite necessary because in sap.m.Link: Link.prototype.onsapenter = Link.prototype._handlePress; onsapenter(oEvent) { this._handlePress(oEvent); } // overwrite necessary because in sap.m.Link: Link.prototype.onsapenter = Link.prototype._handlePress; onclick(oEvent) { this._handlePress(oEvent); } static renderer = { apiVersion: 2, }; }