UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,325 lines (1,199 loc) 74.2 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ //Provides class sap.ui.core.Lib sap.ui.define([ 'sap/base/assert', 'sap/base/config', 'sap/base/i18n/Localization', 'sap/base/i18n/ResourceBundle', 'sap/base/future', 'sap/base/Log', 'sap/base/util/deepExtend', "sap/base/util/isEmptyObject", "sap/base/util/isPlainObject", 'sap/base/util/LoaderExtensions', 'sap/base/util/fetch', 'sap/base/util/mixedFetch', "sap/base/util/ObjectPath", 'sap/base/util/Version', 'sap/base/util/array/uniqueSort', 'sap/ui/base/OwnStatics', 'sap/ui/Global', /* sap.ui.lazyRequire */ 'sap/ui/VersionInfo', 'sap/ui/base/DataType', 'sap/ui/base/EventProvider', 'sap/ui/base/Object', 'sap/ui/base/SyncPromise', 'sap/ui/core/_UrlResolver', "sap/ui/core/Supportability", "sap/ui/core/Theming" ], function ( assert, BaseConfig, Localization, ResourceBundle, future, Log, deepExtend, isEmptyObject, isPlainObject, LoaderExtensions, fetch, mixedFetch, ObjectPath, Version, uniqueSort, OwnStatics, Global, VersionInfo, DataType, EventProvider, BaseObject, SyncPromise, _UrlResolver, Supportability, Theming ) { "use strict"; const { includeLibraryTheme } = OwnStatics.get(Theming); /** * Save the library instances by their keys */ var mLibraries = {}; /** * Bookkeeping for the guessing of library names. * * Set of bundleUrls from which a library name has been derived, see #_getByBundleUrl * * Example: * mGuessedLibraries = { * "my/simple/library/i18n/i18n.properties": "my.simple.library" * } */ var mGuessedLibraries = {}; /** * Negative result bookkeeping for the guessing of library names. * * Set of bundleUrls from which a library name could not be derived, see #_getByBundleUrl * * Note: This cache is maintained separately from the positive cache to ease clearing it * when a new library instance is created (see #_get). This prevents that a negative result * is cached for a library that has been created/loaded in the meantime. * * Example: * mGuessedLibrariesNegative = { * "no/library/i18n/i18n.properties": undefined * } */ var mGuessedLibrariesNegative = {}; /** * Set of libraries that provide a bundle info file (library-preload-lazy.js). * * The file will be loaded, when a lazy dependency to the library is encountered. * @private */ var oLibraryWithBundleInfo = new Set([ "sap.suite.ui.generic.template", "sap.ui.comp", "sap.ui.layout", "sap.ui.unified" ]); /** * Retrieves the module path. * @param {string} sModuleName module name. * @param {string} sSuffix is used untouched (dots are not replaced with slashes). * @returns {string} module path. */ function getModulePath(sModuleName, sSuffix){ return sap.ui.require.toUrl(sModuleName.replace(/\./g, "/") + sSuffix); } /** * Register the given namespace prefix to the given URL * @param {string} sModuleNamePrefix The namespace prefix * @param {string} sUrlPrefix The URL prefix that will be registered for the given namespace */ function registerModulePath(sModuleNamePrefix, sUrlPrefix) { LoaderExtensions.registerResourcePath(sModuleNamePrefix.replace(/\./g, "/"), sUrlPrefix); } /** * Adds all resources from a preload bundle to the preload cache. * * When a resource exists already in the cache, the new content is ignored. * * @param {object} oData Preload bundle * @param {string} [oData.url] URL from which the bundle has been loaded * @param {string} [oData.name] Unique name of the bundle * @param {string} [oData.version='1.0'] Format version of the preload bundle * @param {object} oData.modules Map of resources keyed by their resource name; each resource must be a string or a function * @param {string} sURL URL from which the bundle has been loaded * * @private */ function registerPreloadedModules(oData, sURL) { var modules = oData.modules, fnUI5ToRJS = function(sName) { return /^jquery\.sap\./.test(sName) ? sName : sName.replace(/\./g, "/"); }; if ( Version(oData.version || "1.0").compareTo("2.0") < 0 ) { modules = {}; for ( var sName in oData.modules ) { modules[fnUI5ToRJS(sName) + ".js"] = oData.modules[sName]; } } sap.ui.require.preload(modules, oData.name, sURL); } /** * Configured type of preload file per library. * The empty name represents the default for all libraries not explicitly listed. * * A type can be one of * - 'none' (do not preload), * - 'js' (preload JS file), * - 'json' (preload a json file) * or 'both (try js first, then 'json') * * @private */ var mLibraryPreloadFileTypes = {}; // evaluate configuration for library preload file types BaseConfig.get({ name: "sapUiXxLibraryPreloadFiles", type: BaseConfig.Type.StringArray, external: true }).forEach(function(v){ var fields = String(v).trim().split(/\s*:\s*/), name = fields[0], fileType = fields[1]; if ( fields.length === 1 ) { fileType = name; name = ''; } if ( /^(?:none|js|json|both)$/.test(fileType) ) { mLibraryPreloadFileTypes[name] = fileType; } }); /** * This is an identifier to restrict the usage of constructor within this module */ var oConstructorKey = Symbol("sap.ui.core.Lib"); var oPropDescriptor = { configurable: true, enumerable: true, writable: false }; function createPropDescriptorWithValue(vValue) { oPropDescriptor.value = vValue; return oPropDescriptor; } /** * Freezes the object and nested objects to avoid later manipulation * * @param {object} oObject the object to deep freeze */ function deepFreeze(oObject) { if (oObject && typeof oObject === 'object' && !Object.isFrozen(oObject)) { Object.freeze(oObject); for (var sKey in oObject) { if (Object.hasOwn(oObject, sKey)) { deepFreeze(oObject[sKey]); } } } } /** * filter/normalize given dependencies * @param {Array<{name:string, lazy:boolean}>} aDependencies * @returns {Array<{name:string, lazy:boolean}>} * @private */ function filterDependencies(aDependencies) { const aResults = []; if (aDependencies) { aDependencies.forEach(function(oDependency) { if (!oDependency.lazy) { aResults.push({ name: oDependency.name }); } else if (oLibraryWithBundleInfo.has(oDependency.name)) { aResults.push({ name: oDependency.name, lazy: true }); } }); } return aResults; } /** * @classdesc * Constructor must not be used: To load a library, please use the static method {@link #.load}. * * This class also provides other static methods which are related to a library, such as {@link * #.getResourceBundleFor} to retrieve the resource bundle of a library, {@link #.init} to provide information for a * library and so on. * * @param {object} mSettings Info object for the library * @param {string} mSettings.name Name of the library; when given it must match the name by which the library has been loaded * @class * @alias sap.ui.core.Lib * @extends sap.ui.base.Object * @since 1.118 * @hideconstructor * @public */ var Library = BaseObject.extend("sap.ui.core.Lib", /** @lends sap.ui.core.Lib.prototype */ { constructor: function(mSettings) { BaseObject.call(this); assert(typeof mSettings === "object", "A settings object must be given to the constructor of sap/ui/core/Lib"); assert(typeof mSettings.name === "string" && mSettings.name, "The settings object that is given to the constructor of sap/ui/core/Lib must contain a 'name' property which is a non-empty string"); if (mSettings._key !== oConstructorKey) { throw new Error("The constructor of sap/ui/core/Lib is restricted to the internal usage. To get an instance of Library with name '" + mSettings.name + "', use the static method 'get' from sap/ui/core/Lib instead."); } this.name = mSettings.name; this.namespace = mSettings.name.replace(/\./g, '/'); var aPropsWithDefaults = ["dependencies", "types", "interfaces", "controls", "elements"]; // provide default values aPropsWithDefaults.forEach(function(sPropName) { Object.defineProperty(this, sPropName, createPropDescriptorWithValue([])); }.bind(this)); /** * Resource bundles that are cached by their locales as key */ Object.defineProperty(this, "_resourceBundles", { value: {}, writable: true }); /** * The '_loadingStatus' property may contain the following attributes * * {boolean} pending * * {boolean} async * * {Promise} promise */ Object.defineProperty(this, "_loadingStatus", { value: null, writable: true }); Object.defineProperty(this, "_settingsEnhanced", { value: false, writable: true }); Object.defineProperty(this, "_manifestFailed", { value: false, writable: true }); }, /** * Override the function to avoid creating facade for this instance to expose the settings properties that are * given through {@link #enhanceSettings}. * * @return {this} The Library instance itself * @override */ getInterface: function() { return this; }, /** * Indicates whether the {@link sap.ui.core.Lib#enhanceSettings} is called * * @returns {boolean} Whether a library's setting is enhanced with additional metadata * @private */ isSettingsEnhanced: function() { return this._settingsEnhanced; }, /** * Enhances a library's setting information. * * When the <code>mSettings</code> has been processed, a normalized version of it will be kept and set on the * library instance. * * @param {object} mSettings Info object for the library * @param {string} mSettings.version Version of the library * @param {string[]} [mSettings.dependencies=[]] List of libraries that this library depends on; names are in * dot notation (e.g. "sap.ui.core") * @param {string[]} [mSettings.types=[]] List of names of types that this library provides; names are in dot * notation (e.g. "sap.ui.core.CSSSize") * @param {string[]} [mSettings.interfaces=[]] List of names of interface types that this library provides; * names are in dot notation (e.g. "sap.ui.core.PopupInterface") * @param {string[]} [mSettings.controls=[]] Names of control types that this library provides; names are in dot * notation (e.g. "sap.ui.core.ComponentContainer") * @param {string[]} [mSettings.elements=[]] Names of element types that this library provides (excluding * controls); names are in dot notation (e.g. "sap.ui.core.Item") * @param {boolean} [mSettings.noLibraryCSS=false] Indicates whether the library doesn't provide/use theming. * When set to true, no library.css will be loaded for this library * @param {Object<string,any>} [mSettings.extensions] A map of potential extensions of the library metadata; structure not defined by * the UI5 core framework. Keys should be qualified names derived from the namespace of the code that introduces the feature, e.g. * <code>""sap.ui.support"</code> is used for the support rule metadata of a library. * @returns {sap.ui.core.Lib} The library instance * @private */ enhanceSettings: function(mSettings) { if (this._settingsEnhanced) { return this; } this._settingsEnhanced = true; var sKey, vValue, vValueToSet; for (sKey in mSettings) { vValue = mSettings[sKey]; vValueToSet = undefined; // don't copy undefined values if ( vValue !== undefined ) { if ( Array.isArray(this[sKey]) ) { // concat array typed values if (this[sKey].length === 0) { vValueToSet = vValue; } else { vValueToSet = uniqueSort(this[sKey].concat(vValue)); } } else if ( this[sKey] === undefined ) { // only set values for properties that are still undefined vValueToSet = vValue; } else if ( sKey != "name" ) { // ignore other values (silently ignore "name") future.warningThrows("library info setting ignored: " + sKey + "=" + vValue); } if (vValueToSet !== undefined) { // freeze settings value Object.defineProperty(this, sKey, createPropDescriptorWithValue(vValueToSet)); } } } return this; }, /** * Returns the file type (either js, json, none, or both) that should be used for preloading this library * instance. * * When <code>bJSON</code> is set to <code>true</code>, type "json" is returned directly. When * <code>bJSON</code> is set to <code>false</code>, type "js" is returned. Otherwise it takes the configured * file type into consideration. In case of conflict between the given <code>bJSON</code> and the configured * file type, type "none" is returned. * * @param {boolean} [bJSON] Whether the "json" file type is set * @returns {string} The determined file type. It can be "js", "json", "none", or "both". * @private * @ui5-transform-hint replace-param bJSON false */ _getFileType: function (bJSON) { var sFileType; var sConfiguredFileType = mLibraryPreloadFileTypes[this.name] || mLibraryPreloadFileTypes[''] || 'both'; if ( bJSON === true ) { sFileType = 'json'; } else if ( bJSON === false ) { sFileType = 'js'; } else { // take the configured preload file type as default sFileType = sConfiguredFileType; } if (sConfiguredFileType !== 'both' && sFileType !== 'both' && sConfiguredFileType !== sFileType ) { // if the configured and the supported file type are not equal and the library doesn't support 'both', // then there is no compromise -> 'none' sFileType = 'none'; } return sFileType; }, /** * Loads the library-preload bundle and the resource bundle for a library and apply the same for its * dependencies. * * When the optional parameter <code>mOptions.url</code> is given, its value will be registered for the * namespace of the library and all resources will be loaded from that location. * * When the library has been loaded already, or its entry module (library.js) is already loaded or preloaded, no * further action will be taken, especially, a given <code>mOptions.url</code> will not be registered. A promise * will be returned which resolves immediately. * * @param {object} [mOptions] The options object that contains the following properties * @param {string} [mOptions.url] URL to load the library from * @param {boolean} [mOptions.lazy] Whether the library-preload-lazy bundle should be loaded instead of the * library-preload bundle * @returns {Promise<sap.ui.core.Lib>} A promise that resolves with the library instance * @private */ preload: function(mOptions) { if (mOptions && (mOptions.hasOwnProperty("async") || mOptions.hasOwnProperty("sync"))) { future.errorThrows("The 'preload' function of class sap/ui/core/Lib only supports preloading a library asynchronously.", { suffix: "The given 'async' or 'sync' setting is ignored."}); } if (mOptions && mOptions.hasOwnProperty("json")) { future.errorThrows("The 'preload' function of class sap/ui/core/Lib only supports preloading in JS Format.", { suffix: "The given 'json' setting is ignored."}); } return this._preload(["url", "lazy"].reduce(function(acc, sProperty) { if (mOptions && mOptions.hasOwnProperty(sProperty)) { acc[sProperty] = mOptions[sProperty]; } return acc; }, {})); }, /** * Internal function for preloading a library which still supports the legacy parameters: * * <ul> * <li><code>mOptions.sync</code>: load the preload file in sync mode</li> * <li><code>mOptions.json</code>: load the preload file in "json" format</li> * </ul> * * @param [mOptions] The options object that contains the following properties * @param [mOptions.url] URL to load the library from * @param [mOptions.lazy] Whether the library-preload-lazy bundle should be loaded instead of the * library-preload bundle * @param [mOptions.sync] @deprecated Whether to load the preload bundle in sync mode * @param [mOptions.json] @deprecated Whether to load the preload in JSON format * @returns {Promise<Lib>|Lib} A promise that resolves with the library instance in async mode and the library * instance itself in sync mode * @private * @ui5-transform-hint replace-param mOptions.sync false * @ui5-transform-hint replace-param mOptions.json false */ _preload: function(mOptions) { mOptions = mOptions || {}; var sFileType = this._getFileType(mOptions.json), bEntryModuleExists = !!sap.ui.loader._.getModuleState(this.namespace + '/library.js'), bHttp2 = Library.isDepCacheEnabled(); if (sFileType === 'none') { return mOptions.sync ? this : Promise.resolve(this); } if (this._loadingStatus == null && mOptions.url) { registerModulePath(this.name, mOptions.url); } this._loadingStatus = this._loadingStatus || {}; if (this._loadingStatus.pending) { if (mOptions.sync) { if (mOptions.lazy) { // ignore a lazy request when an eager request is already pending return this; } else if (this._loadingStatus.async) { Log.warning("request to load " + this.name + " synchronously while async loading is pending; this causes a duplicate request and should be avoided by caller"); // fall through and preload synchronously } else { // sync cycle -> ignore nested call (would nevertheless be a dependency cycle) Log.warning("request to load " + this.name + " synchronously while sync loading is pending (cycle, ignored)"); return this; } } else if (this._loadingStatus.preloadFinished) { // async // When it's already in progress for loading a library and loading its own preload file (either JS, // JSON or doesn't need to load the preload at all) is finished, a dependency cycle between // libraries is detected. A resolved promise is returned instead of this._loadingStatus.promise to // avoid the deadlock between the libraries which have dependency of each other return Promise.resolve(this); } } if ((mOptions.sync && this._loadingStatus.pending === false) || (!mOptions.sync && this._loadingStatus.promise)) { // in the sync case, we can do a immediate return only when the library is fully loaded. return mOptions.sync ? this : this._loadingStatus.promise; } if (mOptions.lazy) { // For selected lazy dependencies, we load a library-preload-lazy module. // Errors are ignored and the library is not marked as pending in the bookkeeping // (but the loader avoids double loading). Log.debug("Lazy dependency to '" + this.name + "' encountered, loading library-preload-lazy.js"); /** @deprecated */ if (mOptions.sync) { try { sap.ui.requireSync(this.namespace + '/library-preload-lazy'); // legacy-relevant: Sync path } catch (e) { Log.error("failed to load '" + this.namespace + "/library-preload-lazy.js" + "' synchronously (" + (e && e.message || e) + ")"); } return this; } return sap.ui.loader._.loadJSResourceAsync( this.namespace + '/library-preload-lazy.js', /* ignoreErrors = */ true); } // otherwise mark as pending this._loadingStatus.pending = true; this._loadingStatus.async = !mOptions.sync; var pPreload; if (bEntryModuleExists) { pPreload = (mOptions.sync ? SyncPromise : Promise).resolve(); } else { // first preload code, resolves with list of dependencies (or undefined) pPreload = sFileType !== 'json' ? /* 'js' or 'both', not forced to JSON */ this._preloadJSFormat({ fallbackToJSON: sFileType !== "js", http2: bHttp2, sync: mOptions.sync }) : this._preloadJSONFormat({sync: mOptions.sync}); } // load dependencies, if there are any this._loadingStatus.promise = pPreload.then(function(aDependencies) { // resolve dependencies via manifest "this._getDependencies()" except for libary-preload.json const oManifest = this.getManifest(); var mDependencies = oManifest?.["sap.ui5"]?.dependencies?.libs; if (!aDependencies && mDependencies) { aDependencies = Object.keys(mDependencies).map((sDependency) => { return { name: sDependency, lazy: mDependencies[sDependency].lazy || false }; }); } this._loadingStatus.preloadFinished = true; let aPromises; if (aDependencies && aDependencies.length) { if (!mOptions.sync) { aDependencies = VersionInfo._getTransitiveDependencyForLibraries(aDependencies); } aDependencies = filterDependencies(aDependencies); aPromises = aDependencies.map(function(oDependency) { var oLibrary = Library._get(oDependency.name, true/* bCreate */); return oLibrary._preload({ /** @deprecated since 1.120 */ sync: mOptions.sync, lazy: oDependency.lazy }); }); } else { aPromises = []; } if (!mOptions.sync && oManifest && Version(oManifest._version).compareTo("1.9.0") >= 0) { aPromises.push(this.loadResourceBundle()); } const pFinish = mOptions.sync ? SyncPromise.all(aPromises) : Promise.all(aPromises); return pFinish.then(function() { this._loadingStatus.pending = false; return this; }.bind(this)); }.bind(this)); return mOptions.sync ? this._loadingStatus.promise.unwrap() : this._loadingStatus.promise; }, /** * Loads the library's preload bundle in JS format. In case the resource "library-preload.js" doesn't exist and * <code>mOptions.fallbackToJSON</code> is set to <code>true</code>, the library's preload in JSON format will * be loaded. * * @param {object} [mOptions] The options object that contains the following properties * @param {boolean} [mOptions.fallbackToJSON] Whether to load the preload in JSON format when loading the JS * format fails * @param {boolean} [mOptions.http2] Whether to load the "library-h2-preload" bundle instead of the * "library-preload" bundle * @param {boolean} [mOptions.sync] Whether to load the preload in sync mode * @returns {Promise|object} A promise that resolves with the dependency information of the library in async * mode or the dependency information directly in sync mode * @private * @ui5-transform-hint replace-param mOptions.sync false */ _preloadJSFormat: function(mOptions) { mOptions = mOptions || {}; var that = this; var sPreloadModule = this.namespace + (mOptions.http2 ? '/library-h2-preload' : '/library-preload') + (mOptions.sync ? '' : '.js'); var pResult; if (mOptions.sync) { // necessary to call sap.ui.requireSync in the "then" function to result in a rejected promise once the // loading of JS preload fails pResult = SyncPromise.resolve().then(function() { sap.ui.requireSync(sPreloadModule); // legacy-relevant: Synchronous preloading }); } else { pResult = sap.ui.loader._.loadJSResourceAsync(sPreloadModule); } return pResult.catch(function(e) { if (mOptions.fallbackToJSON) { var bFallback; if (mOptions.sync) { var oRootCause = e; while (oRootCause && oRootCause.cause) { oRootCause = oRootCause.cause; } // fall back to JSON, but only if the root cause was an XHRLoadError // ignore other errors (preload shouldn't fail) bFallback = oRootCause && oRootCause.name === "XHRLoadError"; } else { // loading library-preload.js failed, might be an old style lib with a library-preload.json only. // with mOptions.fallbackToJSON === false, this fallback can be suppressed bFallback = true; } if (bFallback) { Log.error("failed to load '" + sPreloadModule + "' (" + (e && e.message || e) + "), falling back to library-preload.json"); return that._preloadJSONFormat({sync: mOptions.sync}); } // ignore other errors } }); }, /** * Loads the library's preload bundle in JSON format. * * @param {object} [mOptions] The options object that contains the following properties * @param {boolean} [mOptions.sync] Whether to load the preload in sync mode * @returns {Promise|object} A promise that resolves with the dependency information of the library in async * mode or the dependency information directly in sync mode * @private * @deprecated */ _preloadJSONFormat: function(mOptions) { mOptions = mOptions || {}; var sURL = getModulePath(this.name, "/library-preload.json"); /** * @deprecated As of Version 1.120 */ fetch = mixedFetch ? mixedFetch : fetch; return fetch(sURL, { headers: { Accept: fetch.ContentTypes.JSON } }, mOptions.sync).then(function(response) { if (response.ok) { return response.json().then(function(data) { if (data) { registerPreloadedModules(data, sURL); if (Array.isArray(data.dependencies)) { // remove .library-preload suffix from dependencies return data.dependencies.map(function (sDepLibraryName) { return { name: sDepLibraryName.replace(/\.library-preload$/, '') }; }); } else { return data.dependencies; } } }); } else { throw Error(response.statusText || response.status); } }).catch(function(oError) { Log.error("failed to load '" + sURL + "': " + oError.message); }); }, /** * Returns the library's manifest when it's available. * * Only when the library's manifest is preloaded with the library's preload bundle, the manifest will be * returned from this function. This function never triggers a separate request to load the library's manifest. * * @param {boolean} [bSync=false] whether to use sync request to load the library manifest when it doesn't exist * in preload cache * @returns {object|undefined} The manifest of the library * @private */ getManifest: function(bSync) { if (!this.oManifest) { var manifestModule = this.namespace + '/manifest.json'; if (sap.ui.loader._.getModuleState(manifestModule) || (bSync && !this._manifestFailed)) { try { this.oManifest = LoaderExtensions.loadResource(manifestModule, { dataType: 'json', async: false, failOnError: !this.isSettingsEnhanced() }); if (this._oManifest) { deepFreeze(this.oManifest); } else { this._manifestFailed = true; } } catch (e) { this._manifestFailed = true; } } } return this.oManifest; }, /** * Returns the i18n information of the library which is read from the library's manifest. * * @private * @returns {object|undefined} The i18n information of the library */ _getI18nSettings: function() { var oManifest = this.getManifest(), vI18n; if ( oManifest && Version(oManifest._version).compareTo("1.9.0") >= 0 ) { vI18n = oManifest["sap.ui5"] && oManifest["sap.ui5"].library && oManifest["sap.ui5"].library.i18n; } // else vI18n = undefined vI18n = this._normalizeI18nSettings(vI18n); return vI18n; }, /** * Provides the default values for the library's i18n information * * @param {boolean|string|object} vI18n bundle information. Can be: * <ul> * <li>false - library has no resource bundle</li> * <li>true|null|undefined - use default settings: bundle is 'messageBundle.properties', * fallback and supported locales are not defined (defaulted by ResourceBundle)</li> * <li>typeof string - string is the url of the bundle, * fallback and supported locales are not defined (defaulted by ResourceBundle)</li> * <li>typeof object - object can contain bundleUrl, supportedLocales, fallbackLocale</li> * </ul> * * @private * @returns {object} normalized i18N information */ _normalizeI18nSettings: function(vI18n) { if ( vI18n == null || vI18n === true ) { vI18n = { bundleUrl: "messagebundle.properties" }; } else if ( typeof vI18n === "string" ) { vI18n = { bundleUrl: vI18n }; } else if (typeof vI18n === "object") { vI18n = deepExtend({}, vI18n); } return vI18n; }, /** * Includes the library theme into the current page (if a variant is specified it will include the variant * library theme) * * @param {string} [sVariant] the variant to include (optional) * @param {string} [sQuery] to be used only by the Core * @private * @deprecated */ _includeTheme: function(sVariant) { // sQuery is no longer applied, as the only relevant query parameter is the version, // which is now handled directly by the framework itself. includeLibraryTheme({ libName: this.name, variant: sVariant}); }, /** * Returns a resource bundle for the given locale. * * The locale's default value is read from {@link module:sap/base/i18n/Localization.getLanguage session locale}. * * This method returns the resource bundle directly. When the resource bundle for the given locale isn't loaded * yet, synchronous request will be used to load the resource bundle. If it should be loaded asynchronously, use * {@link #loadResourceBundle}. * * The {@link #preload} method will evaluate the same descriptor entry as described above. If it is not * <code>false</code>, loading the main resource bundle of the library will become a subtask of the * asynchronous preloading. * * Due to this preload of the main bundle and the caching behavior of this method, controls in such a library * still can use this method in their API, behavior and rendering code without causing a synchronous request to * be sent. Only when the bundle is needed at module execution time (by top level code in a control module), * then the asynchronous loading of resource bundle with {@link #loadResourceBundle} should be preferred. * * @param {string} [sLocale] Locale to retrieve the resource bundle for * @returns {module:sap/base/i18n/ResourceBundle} The best matching * resource bundle for the given locale or <code>undefined</code> when resource bundle isn't available * @private */ getResourceBundle: function(sLocale) { return this._loadResourceBundle(sLocale, true /* bSync */); }, /** * Retrieves a resource bundle for the given locale. * * The locale's default value is read from {@link module:sap/base/i18n/Localization.getLanguage session locale}. * * <h3>Configuration via App Descriptor</h3> * When the App Descriptor for the library is available without further request (manifest.json * has been preloaded) and when the App Descriptor is at least of version 1.9.0 or higher, then * this method will evaluate the App Descriptor entry <code>"sap.ui5" / "library" / "i18n"</code>. * <ul> * <li>When the entry is <code>true</code>, a bundle with the default name "messagebundle.properties" * will be loaded</li> * <li>If it is a string, then that string will be used as name of the bundle</li> * <li>If it is <code>false</code>, no bundle will be loaded and the result will be * <code>undefined</code></li> * </ul> * * <h3>Caching</h3> * Once a resource bundle for a library has been loaded, it will be cached. * Further calls for the same library and locale won't create new requests, but return the already * loaded bundle. There's therefore no need for control code to cache the returned bundle for a longer * period of time. Not further caching the result also prevents stale texts after a locale change. * * @param {string} [sLocale] Locale to retrieve the resource bundle for * @returns {Promise<module:sap/base/i18n/ResourceBundle>} Promise that resolves with the best matching * resource bundle for the given locale * @private */ loadResourceBundle: function(sLocale) { return this._loadResourceBundle(sLocale); }, /** * Internal method that either returns the resource bundle directly when <code>bSync</code> is set to * <code>true</code> or a Promise that resolves with the resource bundle in the asynchronous case. * * @param {string} [sLocale] Locale to retrieve the resource bundle for * @param {string} [bSync=false] Whether to load the resource bundle synchronously * @returns {module:sap/base/i18n/ResourceBundle|Promise<module:sap/base/i18n/ResourceBundle>} The resource * bundle in synchronous case, otherwise a promise that resolves with the resource bundle * @private */ _loadResourceBundle: function(sLocale, bSync) { var that = this, oManifest = this.getManifest(bSync), // A library ResourceBundle can be requested before its owning library is preloaded. // In this case we do not have the library's manifest yet and the default bundle (messagebundle.properties) is requested. // We still cache this default bundle for as long as the library remains "not-preloaded". // When the library is preloaded later on, a new ResourceBundle needs to be requested, since we need to take the // "sap.ui5/library/i18n" section of the library's manifest into account. bLibraryManifestIsAvailable = !!oManifest, vResult, vI18n, sNotLoadedCacheKey, sKey; assert(sLocale === undefined || typeof sLocale === "string", "sLocale must be a string or omitted"); sLocale = sLocale || Localization.getLanguage(); sNotLoadedCacheKey = sLocale + "/manifest-not-available"; // If the library was loaded in the meantime (or the first time around), we can delete the old ResourceBundle if (bLibraryManifestIsAvailable) { sKey = sLocale; delete this._resourceBundles[sNotLoadedCacheKey]; } else { // otherwise we use the temporary cache-key sKey = sNotLoadedCacheKey; } vResult = this._resourceBundles[sKey]; if (!vResult || (bSync && vResult instanceof Promise)) { vI18n = this._getI18nSettings(); if (vI18n) { var sBundleUrl = getModulePath(this.name + "/", vI18n.bundleUrl); // add known library name to cache to avoid later guessing mGuessedLibraries[sBundleUrl] = this; vResult = ResourceBundle.create({ bundleUrl: sBundleUrl, supportedLocales: vI18n.supportedLocales, fallbackLocale: vI18n.fallbackLocale, locale: sLocale, async: !bSync, activeTerminologies: Localization.getActiveTerminologies() }); if (vResult instanceof Promise) { vResult = vResult.then(function(oBundle) { that._resourceBundles[sKey] = oBundle; return oBundle; }); } // Save the result directly under the map // the real bundle will replace the promise after it's loaded in async case this._resourceBundles[sKey] = vResult; } } // if the bundle is loaded, return a promise which resolved with the bundle return bSync ? vResult : Promise.resolve(vResult); } }); /** * Checks whether the library for the given <code>sName</code> has been loaded or not. * * @param {string} sName The name of the library * @returns {boolean} Returns <code>true</code> if the library is loaded. Otherwise <code>false</code>. * @public */ Library.isLoaded = function(sName) { return mLibraries[sName] ? true : false; }; /** * Internal method for fetching library instance from the library cache by using the given <code>sName</code>. * * When the <code>bCreate</code> is set to <code>true</code>, a new instance for the library is created in case * there was no such library instance before. Otherwise, the library instance from the cache or * <code>undefined</code> is returned. * * @param {string} sName The name of the library * @param {boolean} bCreate Whether to create an instance for the library when there's no instance saved in the * cache under the given <code>sName</code> * @returns {Promise<sap.ui.core.Lib>|undefined} Either an instance of the library or <code>undefined</code> * @private */ Library._get = function(sName, bCreate) { var oLibrary = mLibraries[sName]; if (!oLibrary && bCreate) { mLibraries[sName] = oLibrary = new Library({ name: sName, _key: oConstructorKey }); mGuessedLibrariesNegative = {}; // Reset negative cache to enforce re-evaluation } return oLibrary; }; /** * Tries to derive a library from a bundle URL by guessing the resource name first, * then trying to match with the (known) loaded libraries. * * @param {string} sBundleUrl The bundleURL from which the library name needs to be derived. * @returns {sap.ui.core.Lib|undefined} Returns the corresponding library if found or 'undefined'. * @private */ Library._getByBundleUrl = function(sBundleUrl) { if (sBundleUrl) { if (mGuessedLibraries[sBundleUrl]) { return mGuessedLibraries[sBundleUrl]; } if (sBundleUrl in mGuessedLibrariesNegative) { return undefined; } // [1] Guess ResourceName var sBundleName = sap.ui.loader._.guessResourceName(sBundleUrl); if (sBundleName) { // [2] Guess library name for (var sLibrary in mLibraries) { var oLib = mLibraries[sLibrary]; if (!oLib.isSettingsEnhanced()) { // ignore libraries that haven't been initialized continue; } if (oLib.namespace !== "" && sBundleName.startsWith(oLib.namespace + "/")) { var sBundlePath = sBundleName.replace(oLib.namespace + "/", ""); // [3] Retrieve i18n from manifest for looking up the base bundle // (can be undefined if the lib defines "sap.ui5/library/i18n" with <false>) var vI18n = oLib._getI18nSettings(); if (vI18n) { // Resolve bundle paths relative to library before comparing var sManifestBaseBundlePath = getModulePath(oLib.namespace, "/" + vI18n.bundleUrl); sBundlePath = getModulePath(oLib.namespace, "/" + sBundlePath); // the input bundle-path and the derived library bundle-path must match, // otherwise we would enhance the wrong bundle with terminologies etc. if (sBundlePath === sManifestBaseBundlePath) { // [4.1] Cache matching result mGuessedLibraries[sBundleUrl] = oLib; return oLib; } // [4.2] Cache none-matching result mGuessedLibrariesNegative[sBundleUrl] = undefined; } } } } } }; /** * Returns a map that contains the libraries that are already initialized (by calling {@link #.init}). Each library * instance is saved in the map under its name as key. * * @returns {object} A map that contains the initialized libraries. Each library is saved in the map under its name * as key. * @private * @ui5-restricted sap.ui.core, sap.ui.support, sap.ui.fl, sap.ui.dt */ Library.all = function() { // return only libraries that are initialized (settings enhanced) return Library._all(false /* bIgnoreSettingsEnhanced */); }; /** * Returns a map that contains the libraries that are already initialized (by calling {@link #.init}). Each library * instance is saved in the map under its name as key. * * @param {boolean} [bIgnoreSettingsEnhanced=false] All libraries are returned when it's set to true. Otherwise only * the libraries with their settings enhanced are returned. * @returns {object} A map of libraries. Each library is saved in the map under its name as key. * @private * @ui5-restricted sap.ui.core */ Library._all = function(bIgnoreSettingsEnhanced) { var mInitLibraries = {}; Object.keys(mLibraries).forEach(function(sKey) { if (bIgnoreSettingsEnhanced || mLibraries[sKey].isSettingsEnhanced()) { mInitLibraries[sKey] = mLibraries[sKey]; } }); return mInitLibraries; }; /** * A symbol used to mark a Proxy as such * Proxys are indistinguishable from the outside, but we need a way * to prevent duplicate Proxy wrapping for library namespaces. * @deprecated */ const symIsProxy = Symbol("isProxy"); /** * Creates a Proxy handler object for the a library namespace. * Additionally creates a WeakMap for storing sub-namespace segments. * @param {string} sLibName the library name in dot-notation * @param {object} oLibNamespace the top-level library namespace object * @returns {object} an object containing the proxy-handler and the sub-namespace map * @deprecated */ function createProxyForLibraryNamespace(sLibName, oLibNamespace) { // weakmap to track sub-namespaces for a library // key: the sub-namespace objects, value: the accumulated namespace segments as string[] // initial entry (the first 'target') is the library namespace object itself const mSubNamespaces = new WeakMap(); mSubNamespaces.set(oLibNamespace, `${sLibName}.`); // Proxy facade for library namespace/info-object // will be filled successively by the library after Library.init() const oLibProxyHandler = { set(target, prop, value) { // only analyze plain-objects: literals and (Constructor) functions, etc. must not have a proxy // note: we explicitly must exclude Proxies here, since they are recognized as plain and empty if ( isPlainObject(value) && !value[symIsProxy]) { //Check Objects if they only contain static values // assumption: a non-empty plain-object with only static content is an enum const valueIsEmpty = isEmptyObject(value); let registerProxy = valueIsEmpty; if (!valueIsEmpty) { if (DataType._isEnumCandidate(value)) { // general namespace assignment target[prop] = value; // join library sub-paths when registering an enum type // note: namespace already contains a trailing dot '.' const sNamespacePrefix = mSubNamespaces.get(target); DataType.registerEnum(`${sNamespacePrefix}${prop}`, value); Log.debug(`[Library API-Version 2] If you intend to use API-Version 2 in your library, make sure to call 'sap/ui/base/DataType.registerEnum' for ${sNamespacePrefix}${prop}.`); } else { const firstChar = prop.charAt(0); if (firstChar === firstChar.toLowerCase() && firstChar !== firstChar.toUpperCase()) { registerProxy = true; } else { // general namespace assignment target[prop] = value; } } } if (registerProxy) { target[prop] = new Proxy(value, oLibProxyHandler); // append currently written property to the namespace (mind the '.' at the end for the next level) const sNamespacePrefix = `${mSubNamespaces.get(target)}${prop}.`; // track nested namespace paths segments per proxy object mSubNamespaces.set(value, sNamespacePrefix); } } else { // no plain-object values, e.g. strings, classes target[prop] = value; } return true; }, get(target, prop) { // check if an object is a proxy if (prop === symIsProxy) { return true; } return target[prop]; } }; return oLibProxyHandler; } /** * Provides information about a library. * * This method is intended to be called exactly once while the main module of a library (its <code>library.js</code> * module) is executing, typically at its begin. The single parameter <code>mSettings</code> is an info object that * describes the content of the library. * * When the <code>mSettings</code> has been processed, a normalized version will be set on the library instance * Finally, this function fires {@link #event:LibraryChanged} event with operation 'add' for the newly loaded * library. * * <h3>Side Effects</h3> * * While analyzing the <code>mSettings</code>, the framework takes some additional actions: * * <ul> * <li>If the object contains a list of <code>interfaces</code>, they will be registered with the {@link * sap.ui.base.DataType} class to make them available as aggregation types in managed objects.</li> * * <li>If the object contains a list of <code>controls</code> or <code>elements</code>, {@link sap.ui.lazyRequire * lazy stubs} will be created for their constructor as well as for their static <code>extend</code> and * <code>getMetadata</code> methods. * * <b>Note:</b> Future versions of UI5 will abandon the concept of lazy stubs as it requires synchronous * XMLHttpRequests which have been deprecated (see {@link http://xhr.spec.whatwg.org}). To be on the safe side, * productive applications should always require any modules that they directly depend on.</li> * * <li>With the <code>noLibraryCSS</code> property, the library can be marked as 'theming-free'. Otherwise, the * framework will add a &lt;link&gt; tag to the page's head, pointing to the library's theme-specific stylesheet. * The creation of such a &lt;link&gt; tag can be suppressed with the {@link topic:91f2d03b6f4d1014b6dd926db0e91070 global * configuration option} <code>preloadLibCss</code>. It can contain a list of library names for which no stylesheet * should be included. This is e.g. useful when an application merges the CSS for multiple libraries and already * loaded the resulting stylesheet.</li> * * <li>If a list of library <code>dependencies</code> is specified in the info object, those libraries will be * loaded synchronously if they haven't been loaded yet.</li> * </ul> * * <b>Note:</b> Dependencies between libraries have to be modeled consistently in several places: * <ul> * <li>Both eager and lazy dependencies have to be modelled in the <code>.library</code> file.</li> * <li>By default, UI5 CLI generates a <code>manifest.json</code> file from the content of the <code>.library</code> * file. However, if the <code>manifest.json</code> file for the library is not generated but * maintained manually, it must be kept consistent with the <code>.library</code> file, especially regarding * its listed library dependencies.</li> * <li>All eager library dependencies must be declared as AMD dependencies of the <code>library.js</code> module * by referring to the corresponding <code>"some/lib/namespace/library"</code> module of each library * dependency.</code></li> * <li>All eager dependencies must be listed in the <code>dependencies</code> property of the info object.</li> * <li>All lazy dependencies <b>must not</b> be listed as AMD dependencies or in the <code>dependencies</code> * property of the info object.</li> * </ul> * * Last but not least, higher layer frameworks might want to include their own metadata for libraries. * The property <code>extensions</code> might contain such additional metadata. Its structure is not defined * by the framework, but it is strongly suggested that each extension only occupies a single property * in the <code>extensions</code> object and that the name of that property contains some namespace * information (e.g. library name that introduces the feature) to avoid conflicts with other extensions. * The framework won't touch the content of <code>extensions</code> but will make it available * in the library info objects provided by {@link #.load}. * * * <h3>Relationship to Descriptor for Libraries (manifest.json)</h3> * * The information contained in <code>mSettings</code> is partially redundant to the content of the descriptor * for the same library (its <code>manifest.json</code> file). Future versions of UI5 will ignore the information * provided in <code>mSettings</code> and will evaluate the descriptor file instead. Library developers therefore * must keep the information in both files in sync if the <code>manifest.json</code> file is maintained manually. * * * <h3>Library API-Version 2</h3> * * The Library API Version 2 has been introduced to avoid access to the global namespace when retrieving enum types. * With Library API Version 2 a library must declare its enum types via {@link sap.ui.base.DataType.registerEnum} as described in the "Defining Enums" section below. * * Library API version 2 is defined as a number (int) in the library's <code>init()</code> call: * <pre> * var thisLib = Library.init({ * apiVersion: 2, * name: "my.library", * ... * }); * </pre> * * <b>Important:</b> The object returned by <code>Library.init()</code> should be used as the return value * of the <code>library.js</code> module. * * <b>Defining Enums</b> * * Enums that are exposed through a library (not as separate modules) should be defined as properties on the * object returned by <code>Library.init()</code>. Each enum must be registered via {@link sap.ui.base.DataType.registerEnum} * to make it available to the framework. * * Example for a simple enum definition: * <pre> * // The return value "thisLib" will be used to expose enums * var thisLib = Library.init({ * apiVersion: 2, * name: "my.library", * ... * }); * * // Note that enum keys and values must match * thisLib.MyEnumType = { * Small: "Small", * Medium: "Medium", * Large: "Large" * }; * * // make sure to register the enum and make it know to the framework for later type checks * DataType.registerEnum("my.library.MyEnumType", thisLib.MyEnumType); * </pre> * * <b>Special case: enums in nested namespaces</b> * * Ensure to create the namespace first and then define the enum: * * <pre> * thisLib.cards = thisLib.cards || {}; * * thisLib.cards.HeaderPosition = { * Top: "Top", * Bottom: "Bottom" * }; * * DataType.registerEnum("my.library.cards.HeaderPosition", thisLib.cards.HeaderPosition); * </pre> * * @param {object} mSettings Info object for the library * @param {string} mSettings.name Name of the library; It must match the name by which the library has been loaded * @param {string} [mSettings.version] Version of the library * @param {int} [mSettings.apiVersion=1] The library's API version; s