@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,397 lines (1,264 loc) • 170 kB
JavaScript
/*
* 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