UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,397 lines (1,264 loc) 170 kB
/* * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides base class sap.ui.core.Component for all components sap.ui.define([ './Manifest', './ComponentHooks', './ComponentMetadata', './ElementRegistry', 'sap/base/config', 'sap/base/future', 'sap/base/i18n/Localization', 'sap/base/util/extend', 'sap/base/util/deepExtend', 'sap/base/util/merge', 'sap/ui/base/ManagedObject', 'sap/ui/base/OwnStatics', 'sap/ui/core/Lib', 'sap/ui/core/ResizeHandler', 'sap/ui/performance/trace/Interaction', 'sap/ui/util/_enforceNoReturnValue', 'sap/ui/util/_URL', 'sap/base/assert', 'sap/base/Log', 'sap/base/util/Deferred', 'sap/base/util/ObjectPath', 'sap/base/util/isPlainObject', 'sap/base/util/LoaderExtensions', 'sap/base/strings/camelize', 'sap/ui/core/_UrlResolver', 'sap/ui/VersionInfo', 'sap/ui/core/ComponentRegistry', 'sap/ui/core/util/_LocalizationHelper' ], function( Manifest, ComponentHooks, ComponentMetadata, ElementRegistry, BaseConfig, future, Localization, extend, deepExtend, merge, ManagedObject, OwnStatics, Library, ResizeHandler, Interaction, _enforceNoReturnValue, _URL, assert, Log, Deferred, ObjectPath, isPlainObject, LoaderExtensions, camelize, _UrlResolver, VersionInfo, ComponentRegistry, _LocalizationHelper ) { "use strict"; const { runWithOwner, getCurrentOwnerId } = OwnStatics.get(ManagedObject); var ServiceStartupOptions = { lazy: "lazy", eager: "eager", waitFor: "waitFor" }; function getConfigParam(sName) { return {name: sName, type: BaseConfig.Type.String, external: true}; } /** * Utility function which adds SAP-specific parameters to a URL instance * * @param {URL} oUri Native URL instance * @private */ function addSapParams(oUri) { ['sap-client', 'sap-server'].forEach(function(sName) { if (!oUri.searchParams.has(sName)) { var sValue = BaseConfig.get(getConfigParam(camelize(sName))); if (sValue) { oUri.searchParams.set(sName, sValue); } } }); } /** * Utility function which merges a map of property definitions to track * from which "source" a property was defined. * * This function is used to find out which Component has defined * which "dataSource/model". * * @param {object} mDefinitions Map with definitions to check * @param {object} mDefinitionSource Object to extend with definition - source mapping * @param {object} mSourceData Actual map with definitions * @param {object} oSource Corresponding source object which should be assigned to the definitions-source map * @private */ function mergeDefinitionSource(mDefinitions, mDefinitionSource, mSourceData, oSource) { if (mSourceData) { for (var sName in mDefinitions) { if (!mDefinitionSource[sName] && mSourceData[sName] && mSourceData[sName].uri) { mDefinitionSource[sName] = oSource; } } } } /** * Returns the configuration of a manifest section or the value for a * specific path. If no section or key is specified, the return value is null. * * <b>Note:</b> * This function is a local variant of sap.ui.core.ComponentMetadata#_getManifestEntry. * This function allows to access manifest information on an instance-specific manifest * first, before then looking up the inheritance chain. * All Components using the default manifest will rely on the above default implementation. * * @param {sap.ui.core.ComponentMetadata} oMetadata the Component metadata * @param {sap.ui.core.Manifest} oManifest the manifest * @param {string} sKey Either the manifest section name (namespace) or a concrete path * @param {boolean} [bMerged] Indicates whether the manifest entry is merged with the manifest entries of the parent component. * @return {any|null} Value of the manifest section or the key (could be any kind of value) * @private * @see {@link sap.ui.core.Component#_getManifestEntry} */ function getManifestEntry(oMetadata, oManifest, sKey, bMerged) { var oData = oManifest.getEntry(sKey); // merge / extend should only be done for objects or when entry wasn't found if (oData !== undefined && !isPlainObject(oData)) { return oData; } // merge the configuration of the parent manifest with local manifest // the configuration of the static component metadata will be ignored var oParent, oParentData; if (bMerged && (oParent = oMetadata.getParent()) instanceof ComponentMetadata) { oParentData = oParent._getManifestEntry(sKey, bMerged); } // only extend / clone if there is data // otherwise "null" will be converted into an empty object if (oParentData || oData) { oData = deepExtend({}, oParentData, oData); } return oData; } /** * Utility function which creates a metadata proxy object for the given * metadata object * * @param {sap.ui.core.ComponentMetadata} oMetadata the Component metadata * @param {sap.ui.core.Manifest} oManifest the manifest * @return {sap.ui.core.ComponentMetadata} a metadata proxy object */ function createMetadataProxy(oMetadata, oManifest) { // create a proxy for the metadata object and simulate to be an // instance of the original metadata object of the Component // => retrieving the prototype from the original metadata to // support to proxy sub-classes of ComponentMetadata var oMetadataProxy = Object.create(Object.getPrototypeOf(oMetadata)); // provide internal access to the static metadata object oMetadataProxy._oMetadata = oMetadata; oMetadataProxy._oManifest = oManifest; // copy all functions from the metadata object except of the // manifest related functions which will be instance specific now // we proxy private core restricted manifest related API, as well as older public/deprecated API (for compatibility) for (var m in oMetadata) { if (!/^(getManifest|_getManifest|getManifestObject|getManifestEntry|_getManifestEntry|getMetadataVersion)$/.test(m) && typeof oMetadata[m] === "function") { oMetadataProxy[m] = oMetadata[m].bind(oMetadata); } } /** * Public on ComponentMetadata, kept for compatibility. * * @deprecated * @return {Object|null} manifest. */ oMetadataProxy.getManifest = function() { return this._getManifest(); }; /** * Public on ComponentMetadata, kept for compatibility. * Detailed documentation see ComponentMetadata#getManifestEntry * * @param {string} sKey Either the manifest section name (namespace) or a concrete path * @param {boolean} [bMerged=false] Indicates whether the custom configuration is merged with the parent custom configuration of the Component. * @deprecated * @return {any|null} Value of the manifest section or the key (could be any kind of value) */ oMetadataProxy.getManifestEntry = function(sKey, bMerged) { return this._getManifestEntry(sKey, bMerged); }; oMetadataProxy._getManifest = function() { // return the content of the manifest instead of the static metadata return oManifest && oManifest.getJson(); }; oMetadataProxy.getManifestObject = function() { return oManifest; }; oMetadataProxy._getManifestEntry = function(sKey, bMerged) { return getManifestEntry(oMetadata, oManifest, sKey, bMerged); }; oMetadataProxy.getMetadataVersion = function() { return 2; // instance specific manifest => metadata version 2! }; /* * Provide oMetadataProxy to collectRoutingClasses, to derive routing classes from correct manifest object */ oMetadataProxy.collectRoutingClasses = oMetadata.collectRoutingClasses; oMetadataProxy[Symbol("isProxy")] = true; return oMetadataProxy; } let pCommandPool; const _loadCommandPool = () => { pCommandPool ??= new Promise((resolve, reject) => { sap.ui.require(["sap/ui/core/_CommandPool"], resolve, reject); }); return pCommandPool; }; const _resolveCommandsInManifest = async (oManifest) => { if (oManifest?.getEntry("/sap.ui5/commands")) { const _CommandPool = await _loadCommandPool(); _CommandPool.resolve(oManifest); } }; /** @deprecated As of version 1.135 */ const _resolveCommandsInManifestSync = (oManifest) => { if (oManifest?.getEntry("/sap.ui5/commands")) { const _CommandPool = sap.ui.requireSync("sap/ui/core/_CommandPool"); _CommandPool.resolve(oManifest); } }; /** * As <code>Component</code> is an abstract base class for components, applications should not call the constructor. * For many use cases the static {@link #.create Component.create} factory can be used to instantiate a <code>Component</code>. * Depending on the requirements, the framework also provides other ways to instantiate a <code>Component</code>, documented under the * {@link topic:958ead51e2e94ab8bcdc90fb7e9d53d0 "Component"} chapter. * * The set of allowed entries in the <code>mSettings</code> object depends on * the concrete subclass and is described there. See {@link sap.ui.core.Component} * for a general description of this argument. * * @param {string} * [sId] Optional ID for the new control; generated automatically if * no non-empty ID is given. Note: this can be omitted, no matter * whether <code>mSettings</code> are given or not! * @param {object} * [mSettings] Optional object with initial settings for the * new Component instance * @public * * @class Base Class for Components. * Components are independent and reusable parts of UI5 applications. * They facilitate the encapsulation of closely related parts of an application, * thus enabling developers to structure and maintain their applications more easily. * * @extends sap.ui.base.ManagedObject * @abstract * @author SAP SE * @version 1.147.0 * @alias sap.ui.core.Component * @since 1.9.2 */ var Component = ManagedObject.extend("sap.ui.core.Component", /** @lends sap.ui.core.Component.prototype */ { constructor : function(sId, mSettings) { // create a copy of arguments for later handover to ManagedObject var args = Array.prototype.slice.call(arguments); // identify how the constructor has been used to extract the settings if (typeof sId !== "string") { mSettings = sId; sId = undefined; } /** * Checks whether a settings object was provided plus a proxy for * the metadata object. If <strong>true</strong> the metadata proxy * and the manifest will be stored at the instance of the Component. * * @param {string} [mSettings._metadataProxy] * The proxy object for the metadata */ if (mSettings && typeof mSettings._metadataProxy === "object") { // set the concrete metadata proxy and the manifest and // delete the metadata proxy setting to avoid assert issues this._oMetadataProxy = mSettings._metadataProxy; this._oManifest = mSettings._metadataProxy._oManifest; delete mSettings._metadataProxy; /** * Returns the metadata object which has been adopted to return * the <strong>instance specific</strong> manifest. * * @return {object} the proxy object of the component metadata */ this.getMetadata = function() { return this._oMetadataProxy; }; } // --- Special settings (internal only) below --- // cache tokens if (mSettings && typeof mSettings._cacheTokens === "object") { this._mCacheTokens = mSettings._cacheTokens; delete mSettings._cacheTokens; } // active terminologies if (mSettings && Array.isArray(mSettings._activeTerminologies)) { this._aActiveTerminologies = mSettings._activeTerminologies; delete mSettings._activeTerminologies; } // component factory config if (mSettings && typeof mSettings._componentConfig === "object") { this._componentConfig = mSettings._componentConfig; delete mSettings._componentConfig; } /** * whether the component was created synchronously (e.g. via legacy-factory or constructor call) * @deprecated since 1.120 */ (() => { // Note: why is <true> the default? // Instantiating a Component via constructor is a sync creation, meaning in // UI5 1.x we must load manifest models sync. during the constructor, see _initComponentModels() // In UI5 2.x this code is not needed anymore, since only the async factory remains. // Creation via constructor does not allow for sync class loading anymore, meaning // consumers must provision the model classes before calling the constructor. this._bSyncCreation = mSettings?._syncCreation ?? true; delete mSettings?._syncCreation; })(); // registry of preloaded models from manifest ('afterManifest' models) if (mSettings && typeof mSettings._manifestModels === "object") { // use already created models from sap.ui.component.load if available this._mManifestModels = mSettings._manifestModels; delete mSettings._manifestModels; } else { this._mManifestModels = {}; } // registry for services this._mServices = {}; this._oKeepAliveConfig = this.getManifestEntry("/sap.ui5/keepAlive"); if (this._oKeepAliveConfig) { this._oKeepAliveConfig.supported = !!this._oKeepAliveConfig.supported; } this._bIsActive = true; this._aDestroyables = []; ManagedObject.apply(this, args); }, metadata : { stereotype : "component", "abstract": true, specialSettings: { /* * Component data */ componentData: 'any' }, version : "0.0", /*enable/disable type validation by Messaging handleValidation: 'boolean'*/ includes : [], // css, javascript files that should be used in the component dependencies : { // external dependencies libs : [], components : [], ui5version : "" }, config: {}, // static configuration customizing: { // component/view customizing /* Example: "sap.ui.viewReplacements": { "sap.xx.org.Main": { viewName: "sap.xx.new.Main", type: "XML" } }, "sap.ui.viewExtensions": { "sap.xx.new.Main": { "extensionX": { name: "sap.xx.new.Fragment1", className: "sap.ui.core.Fragment" }, "extensionY": { ... } } }, "sap.ui.controllerExtensions": { "sap.xx.org.Main": { "controllerName": "sap.xx.new.Main", "controllerNames": ["sap.xx.new.Sub1", "sap.xx.new.Sub2"] } }, "sap.ui.viewModification": { "sap.xx.new.Main": { "myControlId": { text: "{i18n_custom>mytext}" } } } */ }, /* properties: { config : "any" }, */ library: "sap.ui.core", designtime: "sap/ui/core/designtime/Component.designtime" } }, /* Metadata constructor */ ComponentMetadata); ComponentRegistry.init(Component); /** * Creates a new subclass of class <code>sap.ui.core.Component</code> with name * <code>sClassName</code> and enriches it with the information contained in <code>oClassInfo</code>. * * <code>oClassInfo</code> might contain the same kind of information as described in * {@link sap.ui.base.ManagedObject.extend}, plus the <code>manifest</code> property in the 'metadata' * object literal, indicating that the component configuration should be read from a manifest.json file. * * @param {string} sClassName * Qualified name of the newly created class * @param {object} [oClassInfo] * Object literal with information about the class * @param {sap.ui.core.Component.MetadataOptions} [oClassInfo.metadata] * The metadata object describing the class. * See {@link sap.ui.core.Component.MetadataOptions MetadataOptions} for the values allowed in every extend. * @param {function} [FNMetaImpl=sap.ui.core.ComponentMetadata] * Constructor function for the metadata object. If not given, it defaults to an * internal subclass of <code>sap.ui.core.ComponentMetadata</code>. * @returns {function} The created class / constructor function * @name sap.ui.core.Component.extend * @function * @static * @public */ /** * @typedef {sap.ui.base.ManagedObject.MetadataOptions} sap.ui.core.Component.MetadataOptions * * The structure of the "metadata" object which is passed when inheriting from sap.ui.core.Component using its static "extend" method. * See {@link sap.ui.core.Component.extend} and {@link sap.ui.core.Component.create} for additional details on its usage. * * @property {undefined|false|object|"json"} [manifest=undefined] The manifest option determines how a component manifest should be evaluated. * Default is <code>undefined</code>. * * When set to <code>false</code> or <code>undefined</code>, no manifest.json is present for this Component, however the Component can * still be started with a manifest given as an argument of the factory function, see {@link sap.ui.core.Component.create}. * When set to an object, this object will be interpreted as a manifest and must adhere to the * {@link topic:be0cf40f61184b358b5faedaec98b2da descriptor schema for components}. * When set to the string literal <code>"json"</code>, this property indicates that the component configuration * should be read from a manifest.json file which is assumed to exist next to the Component.js file. * * @public */ /** * Executes the given callback function for each sap.ui.core.Element whose owner-component * has the given ID and which has no parent. * @param {function(sap.ui.core.Element, sap.ui.core.ID)} fn callback function * @param {sap.ui.core.ID} sComponentId the component ID used for the owner check */ function forEachChildElement(fn, sComponentId) { ElementRegistry.forEach(function(oElement, sId) { var sElementOwnerId = Component.getOwnerIdFor(oElement); if (sElementOwnerId === sComponentId && !oElement.getParent()) { fn(oElement, sId); } }); } /** * Helper function to retrieve owner (extension) component holding the customizing configuration. * @param {string|sap.ui.core.Component|sap.ui.base.ManagedObject} vObject Component Id, component instance or ManagedObject * @throws {Error} If 'getExtensionComponent' function is given, but does not return an instance. * @returns {sap.ui.core.Component|undefined} The owner component or <code>undefined</code> */ function getCustomizingComponent(vObject) { var oComponent, sComponentId; /** * deprecated as of Version 1.120 */ if (BaseConfig.get({name: "sapUiXxDisableCustomizing", type: BaseConfig.Type.Boolean})) { return oComponent; } if (typeof vObject === "string") { sComponentId = vObject; } else if (vObject && typeof vObject.isA === "function" && !vObject.isA("sap.ui.core.Component")) { sComponentId = Component.getOwnerIdFor(vObject); } else { oComponent = vObject; } if (sComponentId) { oComponent = Component.getComponentById(sComponentId); } if (oComponent) { if (oComponent.getExtensionComponent) { oComponent = oComponent.getExtensionComponent(); if (!oComponent) { throw new Error("getExtensionComponent() must return an instance."); } } } return oComponent; } /** * @param {string|sap.ui.base.ManagedObject|sap.ui.core.Component} vObject Either Component Id, ManagedObject or component instance * @param {object} mOptions Info object to retrieve the customizing config * @param {object} mOptions.type Either <code>sap.ui.viewExtension</code>, <code>sap.ui.controllerReplacement</code>, <code>sap.ui.viewReplacement</code>, <code>sap.ui.viewModification</code> or <code>sap.ui.controllerExtension</code> * @param {object} [mOptions.name] Name of the customizing configuration. If none given the complete extension object is returned. * @param {object} [mOptions.extensionName] If type <code>sap.ui.viewExtension</code>, the extension name must be provided * @throws {Error} If 'getExtensionComponent' function is given, but does not return an instance. * @returns {object|undefined} Object containing the customizing config or <code>undefined</code> * @static * @private * @ui5-restricted sap.ui.core */ Component.getCustomizing = function(vObject, mOptions) { var sType = mOptions.type, sExtensionSuffix = mOptions.name ? "/" + mOptions.name : "", sPath = "/sap.ui5/extends/extensions/" + sType + sExtensionSuffix; if (sType === "sap.ui.viewExtensions") { sPath += "/" + mOptions.extensionName; } var oComponent = getCustomizingComponent(vObject); return oComponent ? oComponent._getManifestEntry(sPath, true) : undefined; }; /** * Currently active preload mode for components or falsy value. * * @returns {string} component preload mode * @private * @ui5-restricted sap.ui.core, sap.ui.fl * @since 1.120.0 */ Component.getComponentPreloadMode = function() { return BaseConfig.get({ name: "sapUiXxComponentPreload", type: BaseConfig.Type.String, external: true }) || Library.getPreloadMode(); }; /** * Returns the metadata for the Component class. * * @return {sap.ui.core.ComponentMetadata} Metadata for the Component class. * @static * @public * @name sap.ui.core.Component.getMetadata * @function */ /** * Returns the metadata for the specific class of the current instance. * * @return {sap.ui.core.ComponentMetadata} Metadata for the specific class of the current instance. * @public * @name sap.ui.core.Component#getMetadata * @function */ /** * Returns the manifest defined in the metadata of the component. * If not specified, the return value is null. * * @return {object} manifest. * @public * @since 1.33.0 */ Component.prototype.getManifest = function() { if (!this._oManifest) { return this.getMetadata()._getManifest(); } else { return this._oManifest.getJson(); } }; /** * Returns the configuration of a manifest section or the value for a * specific path. If no section or key is specified, the return value is null. * * Example: * <code> * { * "sap.ui5": { * "dependencies": { * "libs": { * "sap.m": {} * }, * "components": { * "my.component.a": {} * } * } * }); * </code> * * The configuration above can be accessed in the following ways: * <ul> * <li><b>By section/namespace</b>: <code>oComponent.getManifestEntry("sap.ui5")</code></li> * <li><b>By path</b>: <code>oComponent.getManifestEntry("/sap.ui5/dependencies/libs")</code></li> * </ul> * * By section/namespace returns the configuration for the specified manifest * section and by path allows to specify a concrete path to a dedicated entry * inside the manifest. The path syntax always starts with a slash (/). * * @param {string} sKey Either the manifest section name (namespace) or a concrete path * @return {any|null} Value of the manifest section or the key (could be any kind of value) * @public * @since 1.33.0 */ Component.prototype.getManifestEntry = function(sKey) { return this._getManifestEntry(sKey); }; /** * Returns the configuration of a manifest section or the value for a * specific path. If no section or key is specified, the return value is null. * * @param {string} sKey Either the manifest section name (namespace) or a concrete path * @param {boolean} [bMerged] Indicates whether the manifest entry is merged with the manifest entries of the parent component. * @return {any|null} Value of the manifest section or the key (could be any kind of value) * @see {@link #getManifestEntry} * @private * @ui5-restricted sap.ushell * @since 1.34.2 */ Component.prototype._getManifestEntry = function(sKey, bMerged) { if (!this._oManifest) { // get entry via standard component metadata return this.getMetadata()._getManifestEntry(sKey, bMerged); } else { // get entry via instance-specific manifest // this.getMetadata() returns the instance-specific ComponentMetadata Proxy return getManifestEntry(this.getMetadata(), this._oManifest, sKey, bMerged); } }; /** * Returns the manifest object. * @return {sap.ui.core.Manifest} manifest. * @public * @since 1.33.0 */ Component.prototype.getManifestObject = function() { if (!this._oManifest) { return this.getMetadata().getManifestObject(); } else { return this._oManifest; } }; /** * Returns true, if the Component instance is a variant. * * A Component is a variant if the property sap.ui5/componentName * is present in the manifest and if this property and the sap.app/id * differs. * * @return {boolean} true, if the Component instance is a variant * @private * @since 1.45.0 */ Component.prototype._isVariant = function() { if (this._oManifest) { // read the "/sap.ui5/componentName" which should be present for variants var sComponentName = this.getManifestEntry("/sap.ui5/componentName"); // a variant differs in the "/sap.app/id" and "/sap.ui5/componentName" return sComponentName && sComponentName !== this.getManifestEntry("/sap.app/id"); } else { return false; } }; /** * Activates the Customizing configuration for the given Component. * @param {string} sComponentName the name of the component to activate * @private * @deprecated Since 1.21.0 as it is handled by component instantiation */ Component.activateCustomizing = function(sComponentName) { // noop since it will be handled by component instantiation }; /** * Deactivates the Customizing configuration for the given Component. * @param {string} sComponentName Name of the Component to activate * @private * @deprecated Since 1.21.0 as it is handled by component termination */ Component.deactivateCustomizing = function(sComponentName) { // noop since it will be handled by component termination }; // ---- Ownership functionality ------------------------------------------------------------ // // Implementation note: the whole ownership functionality is now part of Component // a) to ensure that only Components are used as owners // b) to keep component related code out of ManagedObject as far as possible // // Only exception is the _sOwnerId property and its assignment in the ManagedObject // constructor, but that doesn't require much knowledge about components /** * Returns the ID of the object in whose "context" the given ManagedObject has been created. * * For objects that are not ManagedObjects or for which the owner is unknown, * <code>undefined</code> will be returned as owner ID. * * <strong>Note</strong>: Ownership for objects is only checked by the framework at the time * when they are created. It is not checked or updated afterwards. And it can only be detected * while the {@link sap.ui.core.Component#runAsOwner Component.runAsOwner} function is executing. * Without further action, this is only the case while the content of a UIComponent is * {@link sap.ui.core.UIComponent#createContent constructed} or when a * {@link sap.ui.core.routing.Router Router} creates a new View and its content. * * <strong>Note</strong>: This method does not guarantee that the returned owner ID belongs * to a Component. Currently, it always does. But future versions of UI5 might introduce a * more fine grained ownership concept, e.g. taking Views into account. Callers that * want to deal only with components as owners, should use the following method: * {@link sap.ui.core.Component.getOwnerComponentFor Component.getOwnerComponentFor}. * It guarantees that the returned object (if any) will be a Component. * * <strong>Further note</strong> that only the ID of the owner is recorded. In rare cases, * when the lifecycle of a ManagedObject is not bound to the lifecycle of its owner, * (e.g. by the means of aggregations), then the owner might have been destroyed already * whereas the ManagedObject is still alive. So even the existence of an owner ID is * not a guarantee for the existence of the corresponding owner. * * @param {sap.ui.base.ManagedObject} oObject Object to retrieve the owner ID for * @return {string|undefined} ID of the owner or <code>undefined</code> * @static * @public * @since 1.15.1 */ Component.getOwnerIdFor = function(oObject) { assert(oObject instanceof ManagedObject, "oObject must be given and must be a ManagedObject"); var sOwnerId = ( oObject instanceof ManagedObject ) && oObject._sOwnerId; return sOwnerId || undefined; // no or empty id --> undefined }; /** * Returns the Component instance in whose "context" the given ManagedObject has been created * or <code>undefined</code>. * * This is a convenience wrapper around {@link sap.ui.core.Component.getOwnerIdFor Component.getOwnerIdFor}. * If the owner ID cannot be determined for reasons documented on <code>getOwnerForId</code> * or when the Component for the determined ID no longer exists, <code>undefined</code> * will be returned. * * @param {sap.ui.base.ManagedObject} oObject Object to retrieve the owner Component for * @return {sap.ui.core.Component|undefined} the owner Component or <code>undefined</code>. * @static * @public * @since 1.25.1 */ Component.getOwnerComponentFor = function(oObject) { return Component.getComponentById(Component.getOwnerIdFor(oObject)); }; /** * Calls the function <code>fn</code> once and marks all ManagedObjects * created during that call as "owned" by this Component. * * Nested calls of this method are supported (e.g. inside a newly created, * nested component). The currently active owner Component will be remembered * before executing <code>fn</code> and restored afterwards. * * @param {function} fn Function to execute * @return {any} result of function <code>fn</code> * @since 1.25.1 * @public */ Component.prototype.runAsOwner = function(fn) { if (!this.isActive()) { throw new Error("Execute 'runAsOwner' on an inactive owner component is not supported. Component: '" + this.getMetadata().getName() + "' with id '" + this.getId() + "'."); } return runWithOwner(fn, this.getId()); }; // ---- ---- /** * Components don't have a facade and therefore return themselves as their interface. * * @returns {this} <code>this</code> as there's no facade for components * @see sap.ui.base.Object#getInterface * @public */ Component.prototype.getInterface = function() { return this; }; /* * initialize the Component and keep the component data */ Component.prototype._initCompositeSupport = function(mSettings) { // make user specific data available during component instantiation this.oComponentData = mSettings && mSettings.componentData; // manifest initialization (loading dependencies, includes, ... / register customizing) // => either call init on the instance specific manifest or the static one on the ComponentMetadata if (this._oManifest) { this._oManifest.init(this); } else { this.getMetadata().init(); } if (this._isVariant()) { // in case of variants we ensure to register the module path for the variant // to allow module loading of code extensibility relative to the manifest var sAppId = this._oManifest.getEntry("/sap.app/id"); if (sAppId) { registerModulePath(sAppId, this._oManifest.resolveUri("./", "manifest")); } } // init the component models this.initComponentModels(); /** * @deprecated Since 1.119 */ (() => { // error handler (if exists) if (this.onWindowError) { this._fnWindowErrorHandler = function(oEvent) { var oError = oEvent.originalEvent; this.onWindowError(oError.message, oError.filename, oError.lineno); }.bind(this); window.addEventListener("error", this._fnWindowErrorHandler); } // before unload handler (if exists) if (this.onWindowBeforeUnload) { this._fnWindowBeforeUnloadHandler = function(oEvent) { var vReturnValue = this.onWindowBeforeUnload.apply(this, arguments); // set returnValue for Chrome if (typeof (vReturnValue) === 'string') { oEvent.returnValue = vReturnValue; oEvent.preventDefault(); return vReturnValue; } }.bind(this); window.addEventListener("beforeunload", this._fnWindowBeforeUnloadHandler); } // unload handler (if exists) if (this.onWindowUnload) { this._fnWindowUnloadHandler = this.onWindowUnload.bind(this); window.addEventListener("unload", this._fnWindowUnloadHandler); } })(); }; /** * Returns the list of Promises for which an automatic destroy is scheduled. * Logs an error in case the application Component is missing a mandatory * constructor super call. * For compatibility reason we must not fail in this obviously broken scenario! * * @private */ Component.prototype._getDestroyables = function() { if (!this._aDestroyables) { future.errorThrows(`${this.getManifestObject().getComponentName()}: A sub-class of sap.ui.core.Component which overrides the constructor must apply the super constructor as well.`, null, "sap.ui.support", function() { return { type: "missingSuperConstructor" }; }); this._aDestroyables = []; } return this._aDestroyables; }; /* * clean up the component and its dependent entities like models or event handlers */ Component.prototype.destroy = function() { var pAsyncDestroy, bSomeRejected = false; // destroy all services for (var sLocalServiceAlias in this._mServices) { if (this._mServices[sLocalServiceAlias].instance) { this._mServices[sLocalServiceAlias].instance.destroy(); } } delete this._mServices; // destroy all models created via manifest definition for (var sModelName in this._mManifestModels) { this._mManifestModels[sModelName].destroy(); } delete this._mManifestModels; /** * @deprecated Since 1.119 */ (() => { // remove the event handlers if (this._fnWindowErrorHandler) { window.removeEventListener("error", this._fnWindowErrorHandler); delete this._fnWindowErrorHandler; } if (this._fnWindowBeforeUnloadHandler) { window.removeEventListener("beforeunload", this._fnWindowBeforeUnloadHandler); delete this._fnWindowBeforeUnloadHandler; } if (this._fnWindowUnloadHandler) { window.removeEventListener("unload", this._fnWindowUnloadHandler); delete this._fnWindowUnloadHandler; } })(); // destroy event bus if (this._oEventBus) { this._oEventBus.destroy(); delete this._oEventBus; } function fnDestroy(oInstance) { if (oInstance && !oInstance._bIsBeingDestroyed) { oInstance.destroy(); } } function fnError(oError) { // We ignore errors if we are in destroy phase and try to cleanup dangling objects // via the Element registry and the owner Component // remember rejections so we can do a defensive destruction of dangling controls in this case bSomeRejected = true; } // trigger an async destroy for all registered commponent promises var aDestroyables = this._getDestroyables(); for (var i = 0; i < aDestroyables.length; i++ ) { aDestroyables[i] = aDestroyables[i].then(fnDestroy, fnError); } if (aDestroyables.length > 0) { pAsyncDestroy = Promise.all(aDestroyables).then(function() { // defensive destroy: Do it only if some collected Promises rejected if (bSomeRejected) { // destroy dangling Controls forEachChildElement(function(oElement) { // we assume that we can safely destroy a control that has no parent oElement.destroy(); }, this.getId()); } }.bind(this)); } // destroy the object ManagedObject.prototype.destroy.apply(this, arguments); // unregister for messaging (on Messaging) const Messaging = sap.ui.require("sap/ui/core/Messaging"); Messaging?.unregisterObject(this); // manifest exit (unload includes, ... / unregister customzing) // => either call exit on the instance specific manifest or the static one on the ComponentMetadata if (this._oManifest) { this._oManifest.exit(this); delete this._oManifest; } else { this.getMetadata().exit(); } return pAsyncDestroy; }; /** * Returns user specific data object * * @return {object} componentData * @public * @since 1.15.0 */ Component.prototype.getComponentData = function() { return this.oComponentData; }; /** * Returns the event bus of this component. * @return {sap.ui.core.EventBus} the event bus * @since 1.20.0 * @public */ Component.prototype.getEventBus = function() { if (!this._oEventBus) { var EventBus = sap.ui.require("sap/ui/core/EventBus"); if (!EventBus) { var sClassName = this.getMetadata().getName(); future.warningThrows("The module 'sap/ui/core/EventBus' needs to be required before calling #getEventBus() on Component '" + sClassName + "'."); /** * @deprecated */ (() => { Log.warning("Synchronous loading of EventBus, due to #getEventBus() call on Component '" + sClassName + "'.", "SyncXHR", null, function() { return { type: "SyncXHR", name: sClassName }; }); // We don't expect the application to use this API anymore (see Dev-Guide) // For the application it is recommended to declare the EventBus via sap.ui.require or sap.ui.define EventBus = sap.ui.requireSync("sap/ui/core/EventBus"); // legacy-relevant })(); } this._oEventBus = new EventBus(); if (!this.isActive()) { this._oEventBus.suspend(); } } return this._oEventBus; }; /** * Determines if the component is active * * @returns {boolean} If the component is active <code>true</code>, otherwise <code>false</code> * @since 1.88 * @private * @ui5-restricted sap.ui.core */ Component.prototype.isActive = function() { return this._bIsActive; }; /** * Initializes the component models and services with the configuration * as defined in the manifest.json. * * @private */ Component.prototype.initComponentModels = function() { // in case of having no parent metadata we simply skip that function // since this would mean to init the models on the Component base class var oMetadata = this.getMetadata(); if (oMetadata.isBaseClass()) { return; } // retrieve the merged sap.app and sap.ui5 sections of the manifest // to create the models for the component + inherited ones var oManifestDataSources = this._getManifestEntry("/sap.app/dataSources", true) || {}; var oManifestModels = this._getManifestEntry("/sap.ui5/models", true) || {}; // pass the models and data sources to the internal helper this._initComponentModels(oManifestModels, oManifestDataSources, this._mCacheTokens); }; /** * Initializes the component models and services which are passed as * parameters to this function. * * @param {object} mModels models configuration from manifest.json * @param {object} mDataSources data sources configuration from manifest.json * @param {object} mCacheTokens cache tokens for OData models * * @private */ Component.prototype._initComponentModels = function(mModels, mDataSources, mCacheTokens) { var sComponentName = this.getManifestObject().getComponentName(); var mAllModelConfigs = _findManifestModelClasses({ models: mModels, dataSources: mDataSources, componentName: sComponentName }); /** * Sync provisioning of model classes. * @deprecated since 1.120 */ if (this._bSyncCreation) { _loadManifestModelClasses(mAllModelConfigs, sComponentName, this._bSyncCreation); } var mAllModelConfigurations = _createManifestModelConfigurations({ models: mAllModelConfigs, dataSources: mDataSources, component: this, mergeParent: true, cacheTokens: mCacheTokens, activeTerminologies: this.getActiveTerminologies() }), mModelConfigurations = {}, sModelName; if (!mAllModelConfigurations) { return; } // filter out models which are already created for (sModelName in mAllModelConfigurations) { if (!this._mManifestModels[sModelName]) { mModelConfigurations[sModelName] = mAllModelConfigurations[sModelName]; } } // create all models which are not created, yet. var mCreatedModels = _createManifestModels(mModelConfigurations, this._componentConfig, this.getManifestObject(), Component.getOwnerIdFor(this)); for (sModelName in mCreatedModels) { // keep the model instance to be able to destroy the created models on component destroy this._mManifestModels[sModelName] = mCreatedModels[sModelName]; } // set all the models to the component for (sModelName in this._mManifestModels) { var oModel = this._mManifestModels[sModelName]; // apply the model to the component with provided name ("" as key means unnamed model) this.setModel(oModel, sModelName || undefined); } }; /** * Returns a service interface for the {@link sap.ui.core.service.Service Service} * declared in the descriptor for components (manifest.json). The declaration needs * to be done in the <code>sap.ui5/services</code> section as follows: * <pre> * { * [...] * "sap.ui5": { * "services": { * "myLocalServiceAlias": { * "factoryName": "my.ServiceFactory", * ["optional": true] * } * } * } * [...] * } * </pre> * The service declaration is used to define a mapping between the local * alias for the service that can be used in the Component and the name of * the service factory which will be used to create a service instance. * * The <code>getService</code> function will look up the service factory and will * create a new instance by using the service factory function * {@link sap.ui.core.service.ServiceFactory#createInstance createInstance} * The optional property defines that the service is not mandatory and the * usage will not depend on the availability of this service. When requesting * an optional service the <code>getService</code> function will reject but * there will be no error logged in the console. * * When creating a new instance of the service the Component context will be * passed as <code>oServiceContext</code> as follows: * <pre> * { * "scopeObject": this, // the Component instance * "scopeType": "component" // the stereotype of the scopeObject * } * </pre> * * The service will be created only once per Component and reused in future * calls to the <code>getService</code> function. * <p> * This function will return a <code>Promise</code> which provides the service * interface when resolved. If the <code>factoryName</code> could not * be found in the {@link sap.ui.core.service.ServiceFactoryRegistry Service Factory Registry} * or the service declaration in the descriptor for components (manifest.json) * is missing the Promise will reject. * * This is an example of how the <code>getService</code> function can be used: * <pre> * oComponent.getService("myLocalServiceAlias").then(function(oService) { * oService.doSomething(); * }).catch(function(oError) { * Log.error(oError); * }); * </pre> * * @param {string} sLocalServiceAlias Local service alias as defined in the manifest.json * @return {Promise<sap.ui.core.service.Service>} Promise which will be resolved with the Service interface * @public * @since 1.37.0 */ Component.prototype.getService = function(sLocalServiceAlias) { // check whether the Service has already been created or not if (!this._mServices[sLocalServiceAlias]) { this._mServices[sLocalServiceAlias] = {}; // cache the promise to avoid redundant creation this._mServices[sLocalServiceAlias].promise = new Promise(function(fnResolve, fnReject) { sap.ui.require(["sap/ui/core/service/ServiceFactoryRegistry"], function(ServiceFactoryRegistry){ var oServiceManifestEntry = this._getManifestEntry("/sap.ui5/services/" + sLocalServiceAlias, true); // lookup the factoryName in the manifest var sServiceFactoryName = oServiceManifestEntry && oServiceManifestEntry.factoryName; if (!sServiceFactoryName) { fnReject(new Error("Service " + sLocalServiceAlias + " not declared!")); return; } // lookup the factory in the registry var oServiceFactory = ServiceFactoryRegistry.get(sServiceFactoryName); if (oServiceFactory) { // create a new Service instance with the current Component as context oServiceFactory.createInstance({ scopeObject: this, scopeType: "component", settings: oServiceManifestEntry.settings || {} }).then(function(oServiceInstance) { if (!this.bIsDestroyed) { // store the created Service instance and interface this._mServices[sLocalServiceAlias].instance = oServiceInstance; this._mServices[sLocalServiceAlias].interface = oServiceInstance.getInterface(); // return the Service interface fnResolve(this._mServices[sLocalServiceAlias].interface); } else { fnReject(new Error("Service " + sLocalServiceAlias + " could not be loaded as its Component was destroyed.")); } }.bind(this)).catch(fnReject); } else { // the Service Factory could not be found in the registry var sErrorMessage = "The ServiceFactory " + sServiceFactoryName + " for Service " + sLocalServiceAlias + " not found in ServiceFactoryRegistry!"; var bOptional = this._getManifestEntry("/sap.ui5/services/" + sLocalServiceAlias + "/optional", true); if (!bOptional) { // mandatory services will log an error into the console Log.error(sErrorMessage); } fnReject(new Error(sErrorMessage)); } }.bind(this), fnReject); }.bind(this)); } return this._mServices[sLocalServiceAlias].promise; }; /** * Internal activation function for non lazy services which should be started immediately * * @param {sap.ui.core.Component} oComponent The Component instance * @param {boolean} bAsyncMode Whether or not the component is loaded in async mode * @returns {Promise[]|null} An array of promises from then loaded services * @private * @ui5-transform-hint replace-param bAsyncMode true */ function activateServices(oComponent, bAsyncMode) { var oServices = oComponent._getManifestEntry("/sap.ui5/services", true); var aOutPromises = bAsyncMode ? [] : null; if (!oServices) { return aOutPromises; } var aServiceKeys = Object.keys(oServices); if (!bAsyncMode && aServiceKeys.some(function (sService) { return oServices[sService].startup === ServiceStartupOptions.waitFor; })) { throw new Error("The specified component \"" + oComponent.getMetadata().getName() + "\" cannot be loaded in sync mode since it has some services declared with \"startup\" set to \"waitFor\""); } return aServiceKeys.reduce(function (aPromises, sService) { if (oServices[sService].lazy === false || oServices[sService].startup === ServiceStartupOptions.waitFor || oServices[sService].startup === ServiceStartupOptions.eager) { var oServicePromise = oComponent.getService(sService); if (oServices[sService].startup === ServiceStartupOptions.waitFor) { aPromises.push(oServicePromise); } } return aPromises; }, aOutPromises); } /** * Creates a nested component that is declared in the <code>sap.ui5/componentUsages</code> section of * the descriptor (manifest.json). The following snippet shows the declaration: * <pre> * { * [...] * "sap.ui5": { * "componentUsages": { * "myUsage": { * "name": "my.useful.Component" * } * } * } * [...] * } * </pre> * The syntax of the configuration object of the component usage matches the * configuration object of the {#link sap.ui.component} factory function. * * This is an example of how the <code>createComponent</code> function can * be used for asynchronous scenarios: * <pre> * oComponent.createComponent("myUsage").then(function(oComponent) { * oComponent.doSomething(); * }).catch(function(oError) { * Log.error(oError); * }); * </pre> * * The following example shows how <code>createComponent</code> can be used to create a nested * component by providing specific properties like <code>id</code>, <code>async</code>, * <code>settings</code>, or <code>componentData</code>: * <pre> * var oComponent = oComponent.createComponent({ * usage: "myUsage", * id: "myId", * settings: { ... }, * componentData: { ... } * }); * </pre> * The allowed list of properties are defined in the parameter documentation * of this function. * * The properties can also be defined in the descriptor. These properties can * be overwritten by the local properties of that function. * * Synchronous Component creation is deprecated as of 1.135.0. * * @param {string|object} vUsage ID of the component usage or the configuration object that creates the component * @param {string} vUsage.usage ID of component usage * @param {string} [vUsage.id] ID of the nested component that is prefixed with <code>autoPrefixId</code> * @param {boolean} [vUsage.async=true] Indicates whether the component creation is done asynchronously (You should use synchronous creation only if really necessary, because this has a negative impact on performance.) * @param {object} [vUsage.settings] Settings for the nested component like for {#link sap.ui.component} or the component constructor * @param {object} [vUsage.componentData] Initial data of the component, see {@link sap.ui.core.Component#getComponentData} * @return {sap.ui.core.Component|Promise<sap.ui.core.Component>} Component instance or Promise which will be resolved with the component instance (defaults to Pro