/*!
* 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,
};
}