@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,401 lines (1,247 loc) • 140 kB
JavaScript
/*
* OpenUI5
* (c) Copyright 2009-2021 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',
'./ComponentMetadata',
'./Element',
'sap/base/util/extend',
'sap/base/util/deepExtend',
'sap/base/util/merge',
'sap/ui/base/ManagedObject',
'sap/ui/base/ManagedObjectRegistry',
'sap/ui/thirdparty/URI',
'sap/ui/performance/trace/Interaction',
'sap/base/assert',
'sap/base/Log',
'sap/base/util/ObjectPath',
'sap/base/util/UriParameters',
'sap/base/util/isPlainObject',
'sap/base/util/LoaderExtensions',
'sap/ui/VersionInfo'
], function(
Manifest,
ComponentMetadata,
Element,
extend,
deepExtend,
merge,
ManagedObject,
ManagedObjectRegistry,
URI,
Interaction,
assert,
Log,
ObjectPath,
UriParameters,
isPlainObject,
LoaderExtensions,
VersionInfo
) {
"use strict";
/*global Promise */
// TODO: dependency to sap/ui/core/library not possible due to cyclic dependency
var ViewType = {
JSON: "JSON",
XML: "XML",
HTML: "HTML",
JS: "JS",
Template: "Template"
};
var ServiceStartupOptions = {
lazy: "lazy",
eager: "eager",
waitFor: "waitFor"
};
/**
* Utility function which adds SAP-specific parameters to a URI instance
*
* @param {URI} oUri URI.js instance
* @private
*/
function addSapParams(oUri) {
['sap-client', 'sap-server'].forEach(function(sName) {
if (!oUri.hasSearch(sName)) {
var sValue = sap.ui.getCore().getConfiguration().getSAPParam(sName);
if (sValue) {
oUri.addSearch(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.
*
* @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
for (var m in oMetadata) {
if (!/^(getManifest|getManifestObject|getManifestEntry|getMetadataVersion)$/.test(m) && typeof oMetadata[m] === "function") {
oMetadataProxy[m] = oMetadata[m].bind(oMetadata);
}
}
// return the content of the manifest instead of the static metadata
oMetadataProxy.getManifest = function() {
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!
};
return oMetadataProxy;
}
/**
* Calls the function <code>fn</code> once and marks all ManagedObjects
* created during that call as "owned" by the given ID.
*
* @param {function} fn Function to execute
* @param {string} sOwnerId Id of the owner
* @param {Object} [oThisArg=undefined] Value to use as <code>this</code> when executing <code>fn</code>
* @return {any} result of function <code>fn</code>
*/
function runWithOwner(fn, sOwnerId, oThisArg) {
assert(typeof fn === "function", "fn must be a function");
var oldOwnerId = ManagedObject._sOwnerId;
try {
ManagedObject._sOwnerId = sOwnerId;
return fn.call(oThisArg);
} finally {
ManagedObject._sOwnerId = oldOwnerId;
}
}
/**
* Creates and initializes a new Component with the given <code>sId</code> and
* settings.
*
* 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.87.1
* @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;
};
}
if (mSettings && typeof mSettings._cacheTokens === "object") {
this._mCacheTokens = mSettings._cacheTokens;
delete mSettings._cacheTokens;
}
if (mSettings && Array.isArray(mSettings._activeTerminologies)) {
this._aActiveTerminologies = mSettings._activeTerminologies;
delete mSettings._activeTerminologies;
}
// registry of models from manifest
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 = {};
ManagedObject.apply(this, args);
},
metadata : {
stereotype : "component",
"abstract": true,
specialSettings: {
/*
* Component data
*/
componentData: 'any'
},
version : "0.0",
/*enable/disable type validation by MessageManager
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",
type: "sap.ui.core.XMLFragment"
},
"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"
}
}, /* Metadata constructor */ ComponentMetadata);
// apply the registry plugin
ManagedObjectRegistry.apply(Component, {
onDeregister: function(sComponentId) {
Element.registry.forEach(function(oElement) {
if ( oElement._sapui_candidateForDestroy && oElement._sOwnerId === sComponentId && !oElement.getParent() ) {
Log.debug("destroying dangling template " + oElement + " when destroying the owner component");
oElement.destroy();
}
});
}
});
/**
* 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
* @since 1.34.2
*/
Component.prototype._getManifestEntry = function(sKey, bMerged) {
if (!this._oManifest) {
return this.getMetadata().getManifestEntry(sKey, bMerged);
} else {
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} 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} the owner Component or <code>undefined</code>.
* @static
* @public
* @since 1.25.1
*/
Component.getOwnerComponentFor = function(oObject) {
return Component.get(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) {
return runWithOwner(fn, this.getId());
};
// ---- ----
/**
* Components don't have a facade and therefore return themselves as their interface.
*
* @returns {sap.ui.core.Component} <code>this</code> as there's no facade for components
* @see sap.ui.base.Object#getInterface
* @returns {this}
* @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;
// static initialization (loading dependencies, includes, ... / register customizing)
// => either init the static or the instance manifest
if (!this._isVariant()) {
this.getMetadata().init();
} else {
this._oManifest.init(this);
// 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();
// 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 = this.onWindowBeforeUnload.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);
}
};
/*
* clean up the component and its dependent entities like models or event handlers
*/
Component.prototype.destroy = function() {
// 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;
// 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;
}
// destroy the object
ManagedObject.prototype.destroy.apply(this, arguments);
// unregister for messaging (on MessageManager)
sap.ui.getCore().getMessageManager().unregisterObject(this);
// static initialization (unload includes, ... / unregister customzing)
// => either exit the static or the instance manifest
if (!this._isVariant()) {
this.getMetadata().exit();
} else {
this._oManifest.exit(this);
delete this._oManifest;
}
};
/**
* 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 sClassName = this.getMetadata().getName();
Log.warning("Synchronous loading of EventBus, due to #getEventBus() call on Component '" + sClassName + "'.", "SyncXHR", null, function() {
return {
type: "SyncXHR",
name: sClassName
};
});
var EventBus = sap.ui.requireSync("sap/ui/core/EventBus");
this._oEventBus = new EventBus();
}
return this._oEventBus;
};
/**
* 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 mAllModelConfigurations = Component._createManifestModelConfigurations({
models: mModels,
dataSources: mDataSources,
component: this,
mergeParent: true,
cacheTokens: mCacheTokens,
activeTerminologies: this.getActiveTerminologies()
});
if (!mAllModelConfigurations) {
return;
}
// filter out models which are already created
var mModelConfigurations = {};
for (var sModelName in mAllModelConfigurations) {
if (!this._mManifestModels[sModelName]) {
mModelConfigurations[sModelName] = mAllModelConfigurations[sModelName];
}
}
// create all models which are not created, yet.
var mCreatedModels = Component._createManifestModels(mModelConfigurations, this.toString());
for (var 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 (var 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} 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
*/
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.
*
* @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 sap.ui.core.Component#getComponentData)
* @return {sap.ui.core.Component|Promise} Component instance or Promise which will be resolved with the component instance (defaults to Promise / asynchronous behavior)
* @public
* @since 1.47.0
*/
Component.prototype.createComponent = function(vUsage) {
assert(
(typeof vUsage === 'string' && vUsage)
|| (typeof vUsage === 'object' && typeof vUsage.usage === 'string' && vUsage.usage),
"vUsage either must be a non-empty string or an object with a non-empty usage id"
);
// extract the config from the configuration object
var mConfig = {
async: true // async is by default true
};
if (vUsage) {
var sUsageId;
if (typeof vUsage === "object") {
sUsageId = vUsage.usage;
["id", "async", "settings", "componentData"].forEach(function(sName) {
if (vUsage[sName] !== undefined) {
mConfig[sName] = vUsage[sName];
}
});
} else if (typeof vUsage === "string") {
sUsageId = vUsage;
}
mConfig = this._enhanceWithUsageConfig(sUsageId, mConfig);
}
// create the component in the owner context of the current component
return Component._createComponent(mConfig, this);
};
/**
* Enhances the given config object with the manifest configuration of the given usage.
* The given object is not modified, but the final object will be returned.
*
* @param {*} sUsageId ID of the component usage
* @param {*} mConfig Configuration object for a component
* @return {object} Enhanced configuration object
*
* @private
* @ui5-restricted sap.ui.core.ComponentContainer
*/
Component.prototype._enhanceWithUsageConfig = function(sUsageId, mConfig) {
var mUsageConfig = this.getManifestEntry("/sap.ui5/componentUsages/" + sUsageId);
if (!mUsageConfig) {
throw new Error("Component usage \"" + sUsageId + "\" not declared in Component \"" + this.getManifestObject().getComponentName() + "\"!");
}
if (mUsageConfig.activeTerminologies) {
throw new Error("Terminologies vector can't be used in component usages");
}
// mix in the component configuration on top of the usage configuration
return deepExtend(mUsageConfig, mConfig);
};
/**
* Returns the list of active terminologies.
* See the {@link sap.ui.core.Component.create Component.create} factory API documentation for more detail.
*
* @return {string[]|undefined} List of active terminologies
*
* @public
* @since 1.76
*/
Component.prototype.getActiveTerminologies = function(){
return this._aActiveTerminologies ? this._aActiveTerminologies.slice() : undefined;
};
/**
* Initializes the Component instance after creation.
*
* Applications must not call this hook method directly, it is called by the
* framework while the constructor of a Component is executed.
*
* Subclasses of Component should override this hook to implement any necessary
* initialization.
*
* @function
* @name sap.ui.core.Component.prototype.init
* @protected
*/
//Component.prototype.init = function() {};
/**
* Cleans up the Component instance before destruction.
*
* Applications must not call this hook method directly, it is called by the
* framework when the element is {@link #destroy destroyed}.
*
* Subclasses of Component should override this hook to implement any necessary
* cleanup.
*
* @function
* @name sap.ui.core.Component.prototype.exit
* @protected
*/
//Component.prototype.exit = function() {};
/**
* The window before unload hook. Override this method in your Component class
* implementation, to handle cleanup before the real unload or to prompt a question
* to the user, if the component should be exited.
*
* @return {string} a string if a prompt should be displayed to the user
* confirming closing the Component (e.g. when the Component is not yet saved).
* @public
* @since 1.15.1
* @name sap.ui.core.Component.prototype.onWindowBeforeUnload
* @function
*/
//onWindowBeforeUnload : function() {},
/**
* The window unload hook. Override this method in your Component class
* implementation, to handle cleanup of the component once the window
* will be unloaded (e.g. closed).
*
* @public
* @since 1.15.1
* @name sap.ui.core.Component.prototype.onWindowUnload
* @function
*/
//onWindowUnload : function() {},
/**
* The window error hook. Override this method in your Component class implementation
* to listen to unhandled errors.
*
* @param {string} sMessage The error message.
* @param {string} sFile File where the error occurred
* @param {int} iLine Line number of the error
* @public
* @since 1.15.1
* @name sap.ui.core.Component.prototype.onWindowError
* @function
*/
//onWindowError : null, // function(sMessage, sFile, iLine) - function not added directly as it might result in bad stack traces in older browsers
/**
* The hook which gets called when the static configuration of the component
* has been changed by some configuration extension.
*
* @param {string} sConfigKey Error message.
* @public
* @since 1.15.1
* @name sap.ui.core.Component.prototype.onConfigChange
* @function
*/
//onConfigChange : null, // function(sConfigKey)
/**
* Internal API to create a component with Component.create (async) or sap.ui.component (sync).
* In case a <code>oOwnerComponent</code> is given, it will be created within the context
* of it.
*
* @param {object} mConfig Configuration object that creates the component
* @param {sap.ui.core.Component} [oOwnerComponent] Owner component
* @return {sap.ui.core.Component|Promise} Component instance or Promise which will be resolved with the component instance
*
* @private
* @ui5-restricted sap.ui.core.ComponentContainer
*/
Component._createComponent = function(mConfig, oOwnerComponent) {
function createComponent() {
if (mConfig.async === true) {
return Component.create(mConfig);
} else {
// use deprecated factory for sync use case only
return sap.ui.component(mConfig);
}
}
if (oOwnerComponent) {
// create the nested component in the context of this component
return oOwnerComponent.runAsOwner(createComponent);
} else {
return createComponent();
}
};
Component._applyCacheToken = function(oUri, oLogInfo, mMetadataUrlParams) {
var oConfig = sap.ui.getCore().getConfiguration();
var sSource = mMetadataUrlParams ? "Model" : "DataSource";
var sManifestPath = mMetadataUrlParams ? "[\"sap.ui5\"][\"models\"]" : "[\"sap.app\"][\"dataSources\"]";
var sLanguage = mMetadataUrlParams && mMetadataUrlParams["sap-language"] || oUri.search(true)["sap-language"];
var sClient = mMetadataUrlParams && mMetadataUrlParams["sap-client"] || oUri.search(true)["sap-client"];
// 1. "sap-language" must be part of the annotation URI
if (!sLanguage) {
Log.warning("Component Manifest: Ignoring provided \"sap-context-token=" + oLogInfo.cacheToken + "\" for " + sSource + " \"" + oLogInfo.dataSource + "\" (" + oUri.toString() + "). " +
"Missing \"sap-language\" URI parameter",
sManifestPath + "[\"" + oLogInfo.dataSource + "\"]", oLogInfo.componentName);
return;
}
// 2. "sap-client" must be set as URI param
if (!sClient) {
Log.warning("Component Manifest: Ignoring provided \"sap-context-token=" + oLogInfo.cacheToken + "\" for " + sSource + " \"" + oLogInfo.dataSource + "\" (" + oUri.toString() + "). " +
"Missing \"sap-client\" URI parameter",
sManifestPath + "[\"" + oLogInfo.dataSource + "\"]", oLogInfo.componentName);
return;
}
// 3. "sap-client" must equal to the value of "sap.ui.getCore().getConfiguration().getSAPParam("sap-client")"
if (sClient !== oConfig.getSAPParam("sap-client")) {
Log.warning("Component Manifest: Ignoring provided \"sap-context-token=" + oLogInfo.cacheToken + "\" for " + sSource + " \"" + oLogInfo.dataSource + "\" (" + oUri.toString() + "). " +
"URI parameter \"sap-client=" + sClient + "\" must be identical with configuration \"sap-client=" + oConfig.getSAPParam("sap-client") + "\"",
sManifestPath + "[\"" + oLogInfo.dataSource + "\"]", oLogInfo.componentName);
return;
}
// 4. uri has cache-token that does not match the given one - override it
if (oUri.hasQuery("sap-context-token") && !oUri.hasQuery("sap-context-token", oLogInfo.cacheToken) ||
mMetadataUrlParams && mMetadataUrlParams["sap-context-token"] && mMetadataUrlParams["sap-context-token"] !== oLogInfo.cacheToken) {
Log.warning("Component Manifest: Overriding existing \"sap-context-token=" + (oUri.query(true)["sap-context-token"] || mMetadataUrlParams["sap-context-token"]) + "\" with provided value \"" + oLogInfo.cacheToken + "\" for " + sSource + " \"" + oLogInfo.dataSource + "\" (" + oUri.toString() + ").",
sManifestPath + "[\"" + oLogInfo.dataSource + "\"]", oLogInfo.componentName);
}
if (mMetadataUrlParams) {
//if serviceUrl contains a valid cache token move it to metadataURLParams so it will be only added for the metadata request
if (oUri.hasQuery("sap-context-token")) {
Log.warning("Component Manifest: Move existing \"sap-context-token=" + oUri.query(true)["sap-context-token"] + "\" to metadataUrlParams for " + sSource + " \"" + oLogInfo.dataSource + "\" (" + oUri.toString() + ").",
sManifestPath + "[\"" + oLogInfo.dataSource + "\"]", oLogInfo.componentName);
}
oUri.removeQuery("sap-context-token");
mMetadataUrlParams["sap-context-token"] = oLogInfo.cacheToken;
} else {
oUri.setQuery("sap-context-token", oLogInfo.cacheToken);
}
};
/**
* Creates model configurations by processing "/sap.app/dataSources" and "/sap.ui5/models" manifest entries.
* Result can be handed over to {@link sap.ui.core.Component._createManifestModels} in order to create instances.
*
* @param {object} mOptions Configuration object (see below)
* @param {object} mOptions.models Manifest models section (/sap.ui5/models)
* @param {object} mOptions.dataSources Manifest dataSources section (/sap.app/dataSources)
* @param {sap.ui.core.Component} [mOptions.component] Corresponding component instance
* @param {sap.ui.core.Manifest} [mOptions.manifest] Component manifest instance (defaults to component's manifest if not set)
* @param {boolean} [mOptions.mergeParent=false] Whether the component's parent configuration should be taken into account (only relevant when component is set)
* @param {object} [mOptions.componentData] componentData object which should be used to create the configurations (only relevant when component is not set, defaults to componentData of provided component)
* @param {string[]} [mOptions.activeTerminologies] optional list of active terminologies.
* @return {object} key-value map with model name as key and model configuration as value
* @private
*/
Component._createManifestModelConfigurations = function(mOptions) {
var oComponent = mOptions.component;
var oManifest = mOptions.manifest || oComponent.getManifestObject();
var bMergeParent = mOptions.mergeParent;
var mCacheTokens = mOptions.cacheTokens || {};
var sLogComponentName = oComponent ? oComponent.toString() : oManifest.getComponentName();
var oConfig = sap.ui.getCore().getConfiguration();
var aActiveTerminologies = mOptions.activeTerminologies;
if (!mOptions.models) {
// skipping model creation because of missing sap.ui5 models manifest entry
return null;
}
var mConfig = {
// ui5 model definitions
models: mOptions.models,
// optional dataSources from "sap.app" manifest
dataSources: mOptions.dataSources || {},
// to identify where the dataSources/models have been originally defined
origin: {
dataSources: {},
models: {}
}
};
if (oComponent && bMergeParent) {
// identify the configuration in parent chain
var oMeta = oComponent.getMetadata();
while (oMeta instanceof ComponentMetadata) {
var oCurrentManifest = oMeta.getManifestObject();
var mCurrentDataSources = oMeta.getManifestEntry("/sap.app/dataSources");
mergeDefinitionSource(mConfig.dataSources, mConfig.origin.dataSources, mCurrentDataSources, oCurrentManifest);
var mCurrentModelConfigs = oMeta.getManifestEntry("/sap.ui5/models");
mergeDefinitionSource(mConfig.models, mConfig.origin.models, mCurrentModelConfigs, oCurrentManifest);
oMeta = oMeta.getParent();
}
}
var mModelConfigurations = {};
// create a model for each ["sap.ui5"]["models"] entry
for (var sModelName in mConfig.models) {
var oModelConfig = mConfig.models[sModelName];
var bIsDataSourceUri = false;
var mMetadataUrlParams = null;
// normalize dataSource shorthand, e.g.
// "myModel": "myDataSource" => "myModel": { dataSource: "myDataSource" }
if (typeof oModelConfig === 'string') {
oModelConfig = {
dataSource: oModelConfig
};
}
// check for referenced dataSource entry and read out settings/uri/type
// if not already provided in model config
if (oModelConfig.dataSource) {
var oDataSource = mConfig.dataSources && mConfig.dataSources[oModelConfig.dataSource];
if (typeof oDataSource === 'object') {
// default type is OData
if (oDataSource.type === undefined) {
oDataSource.type = 'OData';
}
var sODataVersion;
// read out type and translate to model class
// (only if no model type was set to allow overriding)
if (!oModelConfig.type) {
switch (oDataSource.type) {
case 'OData':
sODataVersion = oDataSource.settings && oDataSource.settings.odataVersion;
if (sODataVersion === "4.0") {
oModelConfig.type = 'sap.ui.model.odata.v4.ODataModel';
} else if (!sODataVersion || sODataVersion === "2.0") {
// 2.0 is the default in case no version is provided
oModelConfig.type = 'sap.ui.model.odata.v2.ODataModel';
} else {
Log.error('Component Manifest: Provided OData version "' + sODataVersion + '" in ' +
'dataSource "' + oModelConfig.dataSource + '" for model "' + sModelName + '" is unknown. ' +
'Falling back to default model type "sap.ui.model.odata.v2.ODataModel".',
'["sap.app"]["dataS