UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

774 lines (711 loc) 29.9 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/base/Log", "sap/ui/base/BindingInfo", "sap/ui/base/ManagedObject", "sap/ui/core/Element", "sap/ui/core/ElementRegistry", "sap/ui/core/LabelEnablement" ], function (Log, BindingInfo, ManagedObject, Element, ElementRegistry, LabelEnablement) { "use strict"; const sClassName = "sap/ui/core/fieldhelp/FieldHelp"; const sDocumentationRef = "com.sap.vocabularies.Common.v1.DocumentationRef"; /** * The singleton instance. * @type {module:sap/ui/core/fieldhelp/FieldHelp} */ let oFieldHelp; const sURNPrefix = "urn:sap-com:documentation:key?="; /** * Returns whether the two given sets are identical. * * @param {Set<any>} oSet0 The first set * @param {Set<any>} oSet1 The second set * @returns {boolean} Whether the given sets are identical */ function isSetIdentical(oSet0, oSet1) { if (oSet0.size !== oSet1.size) { return false; } for (const oItem of oSet0) { if (!oSet1.has(oItem)) { return false; } } return true; } /** * Replacement for <code>ManagedObject.prototype.updateFieldHelp</code> to update the field help information * for the given control property name of <code>this</code> control instance, if the corresponding binding has been * created or destroyed, or its context has been changed. * * @param {string} sPropertyName * The name of the control property for which the field help information has to be updated */ function updateFieldHelp(sPropertyName) { // "this" is the managed object on which this function is called if (sPropertyName) { oFieldHelp._updateProperty(this, sPropertyName); } else { oFieldHelp._updateElement(this); } } /** * @typedef {object} module:sap/ui/core/fieldhelp/BackendHelpKey * @description The back-end help key as used by the SAP Companion to retrieve the field help. * @property {string} id The ID of the back-end help key * @property {string} [origin] The origin of the back end * @property {string} type The type of the help key * * @private * @ui5-restricted sap.ui.core */ /** * @typedef {object} module:sap/ui/core/fieldhelp/FieldHelpInfo * @description The label, the control ID and the back-end help key as required by the SAP Companion to display * the field help for the control with the given ID. * @property {module:sap/ui/core/fieldhelp/BackendHelpKey} backendHelpKey The back-end help key * @property {string} hotspotId The ID of the control * @property {string} labelText The label text of the control * * @private * @ui5-restricted sap.ui.core */ /** * DO NOT call this private constructor for <code>FieldHelp</code>; use <code>FieldHelp.getInstance</code> instead. * Singleton class to provide field help support for controls as used by the SAP Companion. * * @alias module:sap/ui/core/fieldhelp/FieldHelp * @author SAP SE * @class * * @hideconstructor * @private * @since 1.125.0 */ class FieldHelp { /** * Whether the field help support is active. * * @default false * @type {boolean} */ #bActive = false; /** * The callback function that is called if the field help hotspots have changed. * * @default null * @type {function(module:sap/ui/core/fieldhelp/FieldHelpInfo[])} */ #fnUpdateHotspotsCallback = null; /** * Maps a control ID to an object mapping a control property to an array of back-end help key URNs. * * @default {} * @type {Object<string, Object<string, string[]>>} */ mDocuRefControlToFieldHelp = {}; /** * A boolean that is <code>true</code> as long as the hotspots are updated. * * @default false * @type {boolean} */ #bUpdateHotspotsPending = false; /** * @typedef {Map<string,Map<string,string>>} sap.ui.core.fieldhelp.Text2IdByType * * Map of a text property path to a map of the qualified name of the type to the ID property path for the text * property which is used with the following semantics: * If the given text property path is used relative to a path P of the given type, the path * for field help lookup is the ID property path appended to P. * Sample mappings: * "ProductName" => { "com.sap....PackagedDangerousGoodWorkItemType" => "Product" }, * "toBusinessPartner/CompanyName" => { * "com.sap....SalesOrderType" => "BusinessPartnerID", * "com.sap....SalesOrderItemType"=> "BusinessPartnerID" * } * Sample for path used to lookup field help resulting from these mappings: Binding path * "SalesOrderSet('42')/toBusinessPartner/CompanyName" is mapped to path * "SalesOrderSet('42')/BusinessPartnerID" to look up field help */ // map of meta model -> promise on text to ID mapping // Map(<string, Promise<sap.ui.core.fieldhelp.Text2IdByType>>) #mMetaModel2TextMappingPromise = new Map(); /** * @typedef {object} sap.ui.core.fieldhelp.TextPropertyInfo * * Information about the text to ID property mapping for a meta model including the visited types, that is the * types, for which this mapping has already been computed. * * @property {Set<string>} visitedTypes * Set of QNames of already visited types for the meta model * @property {sap.ui.core.fieldhelp.Text2IdByType} text2IdByType * The mapping of text properties to id properties by type for the meta model */ // metamodel -> text property info // Map<sap.ui.model.odata.ODataMetaModel|sap.ui.model.odata.v4.ODataMetaModel, // sap.ui.core.fieldhelp.TextPropertyInfo> #mMetamodel2TextPropertyInfo = new Map(); /** * @interface * @name sap.ui.core.fieldhelp.MetaModelInterface * @description Interface for uniform access to OData V4 and OData V2 meta models to retrieve information * required to compute text to ID property mappings. */ /** * Gets the string value of the <code>com.sap.vocabulary.Common.DocumentationRef</code> annotation for the * property with the given path. * Calling this method requires that {@link #requestTypes} has been called before, so that metadata is * loaded and can be accessed synchronously. * * @function * @name sap.ui.core.fieldhelp.MetaModelInterface#getDocumentationRef * @param {string} sPropertyPath The property path * @returns {string|undefined} The string value of the <code>com.sap.vocabulary.Common.DocumentationRef</code> * annotation targeting the given property or <code>undefined</code> if the property has no such annotation */ /** * Gets the array of property names for the given entity type or complex type. * Calling this method requires that {@link #requestTypes} has been called before, so that metadata is * loaded and can be accessed synchronously. * * @function * @name sap.ui.core.fieldhelp.MetaModelInterface#getProperties * @param {object} oType The type object * @returns {string[]} Array of property names */ /** * Gets the path of the <code>com.sap.vocabulary.Common.Text</code> annotation for the given property in the * given entity type or complex type. * Calling this method requires that {@link #requestTypes} has been called before, so that metadata is * loaded and can be accessed synchronously. * * @function * @name sap.ui.core.fieldhelp.MetaModelInterface#getTextPropertyPath * @param {string} sType The type name * @param {string} sProperty The property name * @param {number} [iTypeIndex] (OData V2 only) The type index in the corresponding collection metamodel data * @param {number} [iPropertyIndex] (OData V2 only) The property index in the type object's property array * @param {boolean} [bComplexType] (OData V2 only) Whether the type is a complex type * @returns {string|undefined} The path value of the <code>com.sap.vocabulary.Common.Text</code> annotation * targeting the given property or <code>undefined</code> if the property has no such annotation */ /** * Gets the qualified name of the type for the given resolved data path. * Calling this method requires that {@link #requestTypes} has been called before, so that metadata is * loaded and can be accessed synchronously. * * @function * @name sap.ui.core.fieldhelp.MetaModelInterface#getTypeQName * @param {string} sResolvedPath The resolved data path * @returns {string|undefined} * The qualified type name for the resolved path or <code>undefined</code>, if the path does not correspond to * a type * @throws {Error} In case the resolved path does not comply to the metadata */ /** * Requests the entity types and complex types for this meta model. * * @async * @function * @name sap.ui.core.fieldhelp.MetaModelInterface#requestTypes * @returns {Promise<Array<Map<string, object>>>} * A promise resolving with an array with two maps of the type's qualified name to the type object; the first * map for entity types, the second for complex types. The promise rejects, if the requested metadata cannot * be loaded. */ /** * Implementation of the meta model interface for OData V4. * * @implements {sap.ui.core.fieldhelp.MetaModelInterface} */ static #oMetaModelInterfaceV4 = { getDocumentationRef(sPropertyPath) { return this.oMetaModel.getObject("@" + sDocumentationRef, this.oMetaModel.getMetaContext(sPropertyPath)); }, getProperties(oType) { return Object.keys(oType).filter((sKey) => oType[sKey].$kind === "Property"); }, getTextPropertyPath(sType, sProperty) { return this.oMetaModel.getObject(`/${sType}/${sProperty}@com.sap.vocabularies.Common.v1.Text/$Path`); }, getTypeQName(sResolvedPath) { return this.oMetaModel.getObject(this.oMetaModel.getMetaPath(sResolvedPath) + "/$Type"); }, async requestTypes() { const mEntityTypes = new Map(); const mComplexTypes = new Map(); const mScope = await this.oMetaModel.requestObject("/$"); Object.entries(mScope).forEach(([sKey, oValue]) => { if (oValue.$kind === "EntityType") { mEntityTypes.set(sKey, oValue); } else if (oValue.$kind === "ComplexType") { mComplexTypes.set(sKey, oValue); } }); return [mEntityTypes, mComplexTypes]; } }; static #oMetaModelInterfaceV2 = { getDocumentationRef(sPropertyPath) { return this.oMetaModel.getObject("", this.oMetaModel.getMetaContext(sPropertyPath)) [sDocumentationRef]?.String; }, getProperties(oType) { return oType.property?.map((oProperty) => oProperty.name) ?? []; }, getTextPropertyPath(sType, sProperty, iTypeIndex, iPropertyIndex, bComplexType) { const sKindOfType = bComplexType ? "complexType" : "entityType"; // do not access "Path" property of Text annotation directly via metamodel path, as this leads to // a console warning in case there is no Text annotation return this.oMetaModel.getObject( `/dataServices/schema/0/${sKindOfType}/${iTypeIndex}/property/${iPropertyIndex}` + "/com.sap.vocabularies.Common.v1.Text")?.Path; }, getTypeQName(sResolvedPath) { let oMetaContext; try { oMetaContext = this.oMetaModel.getMetaContext(sResolvedPath); } catch (oCause) { const oError = new Error(`Failed to determine type QName for path '${sResolvedPath}'`); oError.cause = oCause; throw oError; } const oMetaObject = this.oMetaModel.getObject("", oMetaContext); if (oMetaObject.namespace) { return `${oMetaObject.namespace}.${oMetaObject.name}`; // path to entity or entity collection } return oMetaObject.type; // path to (possibly complex typed) property }, async requestTypes() { await this.oMetaModel.loaded(); const oSchema = this.oMetaModel.getObject("/dataServices/schema/0"); const createTypeMap = (aTypes) => new Map(aTypes?.map((oType) => [`${oType.namespace}.${oType.name}`, oType]) ?? []); return [createTypeMap(oSchema.entityType), createTypeMap(oSchema.complexType)]; } }; /** * Gets the meta model interface for the given OData model's meta model or <code>undefined</code>, if the model * is not a <code>sap.ui.model.odata.v4.ODataModel</code> or <code>sap.ui.model.odata.v2.ODataModel</code>. * * @param {sap.ui.model.Model} [oModel] The model * @returns {sap.ui.core.fieldhelp.MetaModelInterface|undefined} The meta model interface of the given model's * meta model or <code>undefined</code>, if the model is not set or is not an OData V4 or OData V2 model */ static _getMetamodelInterface(oModel) { if (!oModel) { return undefined; } let oInterface; if (oModel.isA("sap.ui.model.odata.v4.ODataModel")) { oInterface = Object.create(FieldHelp.#oMetaModelInterfaceV4); } else if (oModel.isA("sap.ui.model.odata.v2.ODataModel")) { oInterface = Object.create(FieldHelp.#oMetaModelInterfaceV2); } else { return undefined; } oInterface.oMetaModel = oModel.getMetaModel(); return oInterface; } /** * Requests the ID property path for the given meta model interface and the given resolved path; if the * resolved path does not point to a text property, the resolved path itself is returned. * * @param {sap.ui.core.fieldhelp.MetaModelInterface} oMetaModelInterface * The OData meta model interface * @param {string} sResolvedPath * The resolved path * @returns {Promise<string>} * A promise that resolves with the ID property path for the given resolved path, if it points to a text * property or the given resolved path otherwise. The promise rejects, if the requested metadata cannot * be loaded or - for OData V2 only - in case of a resolved path which does not comply to the metadata. */ async _requestIDPropertyPath(oMetaModelInterface, sResolvedPath) { const mText2IdByType = await this._requestText2IdByType(oMetaModelInterface); const [sFirstSegment, ...aSuffixSegments] = sResolvedPath.slice(1).split("/"); const aPrefixSegments = [sFirstSegment]; while (aSuffixSegments.length) { const sPrefixPath = "/" + aPrefixSegments.join("/"); const sTypeQName = oMetaModelInterface.getTypeQName(sPrefixPath); const sTextPropertyPath = aSuffixSegments.join("/"); const sIDPropertyPath = mText2IdByType.get(sTextPropertyPath)?.get(sTypeQName); if (sIDPropertyPath) { return sPrefixPath + "/" + sIDPropertyPath; } aPrefixSegments.push(aSuffixSegments.shift()); } return sResolvedPath; } /** * Requests the mapping of text properties to ID properties for the given meta model interface. * * @param {sap.ui.core.fieldhelp.MetaModelInterface} oMetaModelInterface * The meta model interface * @returns {Promise<sap.ui.core.fieldhelp.Text2IdByType>} * A promise on the mapping of text to ID properties by type. The promise rejects, if the requested metadata * cannot be loaded. */ _requestText2IdByType(oMetaModelInterface) { const oMetaModel = oMetaModelInterface.oMetaModel; if (this.#mMetaModel2TextMappingPromise.has(oMetaModel)) { return this.#mMetaModel2TextMappingPromise.get(oMetaModel); } const oTextMappingPromise = oMetaModelInterface.requestTypes().then(([mEntityTypes, mComplexTypes]) => { const oTextPropertyInfo = this.#mMetamodel2TextPropertyInfo.get(oMetaModel); const mText2IdByType = oTextPropertyInfo?.text2IdByType ?? new Map(); const oVisitedTypes = oTextPropertyInfo?.visitedTypes ?? new Set(); const mAllTypes = new Map([...mEntityTypes, ...mComplexTypes]); const iEntityTypesCount = mEntityTypes.size; let i = 0; for (const [sType, oType] of mAllTypes) { const bComplexType = i >= iEntityTypesCount; const iTypeIndex = bComplexType ? i - iEntityTypesCount : i; i += 1; if (oVisitedTypes.has(sType)) { continue; } oVisitedTypes.add(sType); oMetaModelInterface.getProperties(oType).forEach((sProperty, iPropertyIndex) => { const sTextPropertyPath = oMetaModelInterface.getTextPropertyPath(sType, sProperty, iTypeIndex, iPropertyIndex, bComplexType); if (!sTextPropertyPath) { return; } const mIdByType = mText2IdByType.get(sTextPropertyPath) ?? mText2IdByType.set(sTextPropertyPath, new Map()).get(sTextPropertyPath); mIdByType.set(sType, sProperty); }); } this.#mMetamodel2TextPropertyInfo.set(oMetaModel, { text2IdByType: mText2IdByType, visitedTypes: oVisitedTypes }); return mText2IdByType; }); this.#mMetaModel2TextMappingPromise.set(oMetaModelInterface.oMetaModel, oTextMappingPromise); return oTextMappingPromise; } /** * Requests the <code>String</code> value of the <code>com.sap.vocabularies.Common.v1.DocumentationRef</code> * annotation for the given binding. * * @param {sap.ui.model.Binding} oBinding The binding * @returns {Promise<string|undefined>} * If the binding is destroyed, or does not belong to an OData model, or the resolved path of the binding is a * meta model path or referencing an annotation, a promise resolving with <code>undefined</code> is returned; * if the binding belongs to an OData model the <code>com.sap.vocabularies.Common.v1.DocumentationRef</code> * annotation value for the binding is asynchronously requested via the OData meta model and the resulting * <code>Promise</code> either resolves with the <code>String</code> value of that annotation or with * <code>undefined</code> if the annotation is not available; the <code>Promise</code> never rejects */ async _requestDocumentationRef(oBinding) { if (oBinding.isDestroyed()) { return undefined; } let sResolvedPath = oBinding.getResolvedPath(); if (!sResolvedPath || sResolvedPath.includes("#") /*meta model path*/ || sResolvedPath.includes("@") /*annotation path*/) { return undefined; } const oMetaModelInterface = FieldHelp._getMetamodelInterface(oBinding.getModel()); if (!oMetaModelInterface) { return undefined; } let sDocumentationRefValue; try { const sFieldHelpPath = await this._requestIDPropertyPath(oMetaModelInterface, sResolvedPath); sResolvedPath = sFieldHelpPath; // for message logged in case of errors below sDocumentationRefValue = oMetaModelInterface.getDocumentationRef(sFieldHelpPath); } catch (oError) { Log.error(`Failed to request '${sDocumentationRef}' annotation for path '${sResolvedPath}'`, oError, sClassName); } return sDocumentationRefValue; } /** * Iterates over the internal data structure for all controls which have field help information and * checks whether the field help for that control has to be displayed at another control, e.g. an * input field in a table has to show the field help information at the table column header. It checks the * <code>fieldHelpDisplay</code> association or the <code>sap.ui.base.BindingInfo.OriginalParent</code> * symbol at the control and in the control's parent hierarchy. If the internal data structure contains * outdated references, they are cleaned up. * * @returns {Object<string, string>} * Maps a control ID having a field help information to the control ID at which it shall be displayed */ _getFieldHelpDisplayMapping() { const mControlIDToDisplayControlID = {}; for (const sControlID in this.mDocuRefControlToFieldHelp) { const oControl = Element.getElementById(sControlID); if (!oControl) { // control has been destroyed, cleanup internal data structure delete this.mDocuRefControlToFieldHelp[sControlID]; continue; } let sFieldHelpDisplayControlId; let oTempControl = oControl; do { sFieldHelpDisplayControlId = oTempControl.getAssociation("fieldHelpDisplay") || oTempControl[BindingInfo.OriginalParent]?.getId(); oTempControl = oTempControl.getParent(); } while (!sFieldHelpDisplayControlId && oTempControl); if (sFieldHelpDisplayControlId) { mControlIDToDisplayControlID[sControlID] = sFieldHelpDisplayControlId; } } return mControlIDToDisplayControlID; } /** * Returns the map of control ID to the corresponding set of documentation reference URNs. * * The method considers the following precedence for <em>explicit</em> field help set using * <code>FieldHelpUtil.setDocumentationRef</code> or <code>FieldHelpCustomData</code> and <em>implicit</em> * field help derived from OData bindings. * 1. Explicit field help on the control itself * 2. Explicit field help defined for controls pointing to the control as field help display control * 3. Union of implicit field helps defined on the control itself and controls pointing to the control as field * help display control * * @returns {Map<string, Set<string>>} * The map of control ID to the corresponding set of documentation reference URNs */ _getDisplayControlIDToURNs() { const mControlIDToDisplayControlID = this._getFieldHelpDisplayMapping(); const mExplicitDisplay = new Map(); const mExplicitSelf = new Map(); const mImplicit = new Map(); for (const sControlID in this.mDocuRefControlToFieldHelp) { const sDisplayControlID = mControlIDToDisplayControlID[sControlID] || sControlID; const aDocuRefsForControl = this.mDocuRefControlToFieldHelp[sControlID][undefined]; if (aDocuRefsForControl) { // explicit field help const oURNSet = new Set(aDocuRefsForControl); if (mControlIDToDisplayControlID[sControlID]) { // on display control const oExistingURNSet = mExplicitDisplay.get(sDisplayControlID); if (oExistingURNSet && !isSetIdentical(oURNSet, oExistingURNSet)) { Log.error("Cannot display field help for control '" + sControlID + "': different field help already set on hotspot '" + sDisplayControlID + "'", undefined, sClassName); continue; } mExplicitDisplay.set(sDisplayControlID, oURNSet); } else { // on control itself mExplicitSelf.set(sDisplayControlID, oURNSet); } } else { const aDocuRefs = Object.values(this.mDocuRefControlToFieldHelp[sControlID]).flat(); // add to a Set to filter duplicates const oURNSet = mImplicit.get(sDisplayControlID) ?? new Set(); aDocuRefs.forEach(oURNSet.add.bind(oURNSet)); mImplicit.set(sDisplayControlID, oURNSet); } } return new Map([...mImplicit, ...mExplicitDisplay, ...mExplicitSelf]); } /** * Gets an array of field help hotspots as required by the SAP Companion. * * @returns {module:sap/ui/core/fieldhelp/FieldHelpInfo[]} The array of field help hotspots */ _getFieldHelpHotspots() { const aFieldHelpHotspots = []; for (const [sDisplayControlID, oURNSet] of this._getDisplayControlIDToURNs()) { const oControl = Element.getElementById(sDisplayControlID); const sLabel = LabelEnablement._getLabelTexts(oControl)[0]; if (!sLabel) { Log.error(`Cannot find a label for control '${sDisplayControlID}'; ignoring field help`, Array.from(oURNSet).join(), sClassName); continue; } for (const sURN of oURNSet) { const oParameters = new URLSearchParams(sURN.slice(sURNPrefix.length)); const sOrigin = oParameters.get("origin"); aFieldHelpHotspots.push({ backendHelpKey: { id: oParameters.get("id"), type: oParameters.get("type"), ...(sOrigin && {origin: sOrigin}) }, hotspotId: sDisplayControlID, labelText: sLabel }); } } return aFieldHelpHotspots; } /** * Sets the field help information, given as documentation reference URNs, for the given control and the given * control property and calls asynchronously the <code>fnUpdateHotspotsCallback</code> as given in * {@link #activate}. * * @param {sap.ui.core.Element} oElement * The control * @param {string} [sControlProperty] * The name of the control property * @param {Array<string>} aDocumentationRefs * An array of documentation reference annotation URNs, e.g. * <code>["urn:sap-com:documentation:key?=~key&type=~type&id=~id&origin=~origin"]</code> */ _setFieldHelpDocumentationRefs(oElement, sControlProperty, aDocumentationRefs) { const sControlID = oElement.getId(); this.mDocuRefControlToFieldHelp[sControlID] ||= {}; if (aDocumentationRefs.length > 0) { this.mDocuRefControlToFieldHelp[sControlID][sControlProperty] = aDocumentationRefs; } else { delete this.mDocuRefControlToFieldHelp[sControlID][sControlProperty]; if (Object.keys(this.mDocuRefControlToFieldHelp[sControlID]).length === 0) { delete this.mDocuRefControlToFieldHelp[sControlID]; } } this._updateHotspots(); } /** * Updates the field help information for the given element. * * @param {sap.ui.core.Element} oElement * The element for which to set the field help information * @param {string[]} [aDocumentationRefs] * The string values of <code>com.sap.vocabularies.Common.v1.DocumentationRef</code> annotations; if not given * the custom data <code>sap-ui-DocumentationRef</code> of the given element is used */ _updateElement(oElement, aDocumentationRefs) { if (oElement.isDestroyed() || oElement.isDestroyStarted()) { aDocumentationRefs = []; } else { aDocumentationRefs ||= oElement.data("sap-ui-DocumentationRef") || []; } this._setFieldHelpDocumentationRefs(oElement, undefined, aDocumentationRefs); } /** * Calls the <code>fnUpdateHotspotsCallback</code> as given in {@link #activate} asynchronously with the latest * field help hotspots. */ _updateHotspots() { if (this.#bUpdateHotspotsPending) { return; } this.#bUpdateHotspotsPending = true; // gather and send field help info in task so that e.g. field help can be displayed at a column header setTimeout(() => { if (this.isActive()) { this.#fnUpdateHotspotsCallback(this._getFieldHelpHotspots()); this.#mMetaModel2TextMappingPromise.clear(); } this.#bUpdateHotspotsPending = false; }, 0); } /** * Updates the field help information for the given property of the given control if the control property * belongs to the {@link sap.ui.base.ManagedObject.MetadataOptions.Property "Data" group} and is bound to an * OData model. * * @param {sap.ui.core.Element} oElement The control * @param {string} sControlProperty The name of the control property * * @private */ _updateProperty(oElement, sControlProperty) { if (oElement.getMetadata().getProperty(sControlProperty)?.group !== "Data") { return; } const oBinding = oElement.getBinding(sControlProperty); if (!oBinding) { return; } let aBindings; if (oBinding.isA("sap.ui.model.CompositeBinding")) { const aPartsToIgnore = oBinding.getType()?.getPartsIgnoringMessages() || []; aBindings = oBinding.getBindings().filter((oPart, i) => !aPartsToIgnore.includes(i)); } else { aBindings = [oBinding]; } Promise.all( aBindings.map((oBinding) => this._requestDocumentationRef(oBinding)) ).then((aDocumentationRefs) => { aDocumentationRefs = aDocumentationRefs.filter((sDocumentationRef) => sDocumentationRef); this._setFieldHelpDocumentationRefs(oElement, sControlProperty, aDocumentationRefs); }); } /** * Gets the singleton instance of <code>FieldHelp</code>. * * @returns {module:sap/ui/core/fieldhelp/FieldHelp} The singleton instance * * @private * @ui5-restricted sap.ui.core * @since 1.125.0 */ static getInstance() { oFieldHelp ||= new FieldHelp(); return oFieldHelp; } /** * Activates the field help support. Determines the field help for all controls and calls the given update * callback with the currently available field help. * * @param {function(module:sap/ui/core/fieldhelp/FieldHelpInfo[])} fnUpdateHotspotsCallback * The callback function that is called with the currently available field help information if the hotspots * for the field help have changed * @throws {Error} * If the field help is already active and the update hotspots callback is different * * @private * @ui5-restricted sap.ui.core * @since 1.125.0 */ activate(fnUpdateHotspotsCallback) { if (this.#bActive) { if (this.#fnUpdateHotspotsCallback !== fnUpdateHotspotsCallback) { throw new Error("The field help is active for a different update hotspots callback handler"); } return; } this.#bActive = true; this.#fnUpdateHotspotsCallback = fnUpdateHotspotsCallback; ElementRegistry.forEach((oElement) => { const aDocumentationRefs = oElement.data("sap-ui-DocumentationRef"); if (aDocumentationRefs) { this._updateElement(oElement, aDocumentationRefs); } else { Object.keys(oElement.getMetadata().getAllProperties()).forEach((sPropertyName) => { this._updateProperty(oElement, sPropertyName); }); } }); ManagedObject.prototype.updateFieldHelp = updateFieldHelp; } /** * Deactivates the field help support and cleans up the internal data structures. * * @private * @ui5-restricted sap.ui.core * @since 1.125.0 */ deactivate() { this.#bActive = false; this.mDocuRefControlToFieldHelp = {}; this.#fnUpdateHotspotsCallback = null; this.#mMetaModel2TextMappingPromise.clear(); this.#mMetamodel2TextPropertyInfo.clear(); ManagedObject.prototype.updateFieldHelp = undefined; // restore the default } /** * Whether the field help is currently active. * * @returns {boolean} Whether the field help is active * * @private * @ui5-restricted sap.ui.core * @since 1.125.0 */ isActive() { return this.#bActive; } } return FieldHelp; });