UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,401 lines (1,247 loc) 140 kB
/* * 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