UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,289 lines (1,157 loc) 62.8 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 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/i18n/ResourceBundle', 'sap/base/Log', 'sap/base/util/deepExtend', 'sap/base/util/LoaderExtensions', 'sap/base/util/mixedFetch', "sap/base/util/ObjectPath", 'sap/base/util/Version', 'sap/base/util/array/uniqueSort', '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/Configuration', 'sap/ui/core/_UrlResolver' ], function ( assert, ResourceBundle, Log, deepExtend, LoaderExtensions, mixedFetch, ObjectPath, Version, uniqueSort, Global, VersionInfo, DataType, EventProvider, BaseObject, SyncPromise, Configuration, _UrlResolver ) { "use strict"; /** * 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 or not, see #getLibraryNameForBundle * If no library name can be derived, the result will also be tracked with 'false' as value. * * Example: * mGuessedLibraries = { * "my/simple/library/i18n/i18n.properties": "my.simple.library", * "no/library/i18n/i18n.properties": false * } */ var mGuessedLibraries = {}; /** * 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 Configuration.getValue("xx-libraryPreloadFiles").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; } }); /** * Set of libraries which require CSS. */ var aAllLibrariesRequiringCss = []; var pThemeManager; /** * Get the sap/ui/core/theming/ThemeManager on demand * * @param {boolean} [bClear=false] Whether to reset the ThemeManager * @returns {Promise} The promise that resolves with the sap/ui/core/theming/ThemeManager class */ function _getThemeManager(bClear) { var ThemeManager = sap.ui.require("sap/ui/core/theming/ThemeManager"); if (!pThemeManager) { if (!ThemeManager) { pThemeManager = new Promise(function (resolve, reject) { sap.ui.require(["sap/ui/core/theming/ThemeManager"], function (ThemeManager) { resolve(ThemeManager); }, reject); }); } else { pThemeManager = Promise.resolve(ThemeManager); } } // This is only used within initLibrary to reset flag themeLoaded synchronously in case // a theme for a new library will be loaded if (ThemeManager && bClear) { ThemeManager.reset(); } return pThemeManager; } /** * 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 (oObject.hasOwnProperty(sKey)) { deepFreeze(oObject[sKey]); } } } } /* * Create an instance that represents a library with the given name. * * <h3>Note</h3> * This constructor is designed for internal usage only. To create an instance, use {@link #.get}. * * @classdesc * <h3>Library Loading</h3> * To load a library, {@link #.load} can be used directly without creating a library instance in advance. * * <h3>What a library does</h3> * <ul> * <li>preload: {@link #preload} loads the library-preload bundle and its resource bundle and apply the * same for its dependencies</li> * <li>theming: {@link #_includeTheme} creates a &lt;link&gt; in the page referring to the corresponding * <code>library.css</code></li> * <li>resource bundle: {@link #getResourceBundle} returns the resource bundle directly when it's already loaded or * triggers a synchronous request to load it. {@link #loadResourceBundle} loads a library's resource bundle file * asynchronously. The resource bundle file is also loaded when the <code>preload</code> function is called</li> * </ul> * * @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.BaseObject * @since 1.110 * @private * @hideconstructor * @ui5-restricted sap.ui.core */ 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/base/Library"); assert(typeof mSettings.name === "string" && mSettings.name, "The settings object that is given to the constructor of sap/ui/base/Library 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; 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 }); }, /** * 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} [oLibInfo.extensions] Potential extensions of the library metadata; structure not defined by * the UI5 core framework. * @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") Log.warning("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 */ _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"))) { Log.error("The 'preload' function of class sap/ui/core/Lib only support preloading a library asynchronously. The given 'async' or 'sync' setting is ignored."); } if (mOptions && mOptions.hasOwnProperty("json")) { Log.error("The 'preload' function of class sap/ui/core/Lib only support preloading in JS Format. 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 @deprecated [mOptions.sync] Whether to load the preload bundle in sync mode * @param @deprecated [mOptions.json] 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 */ _preload: function(mOptions) { mOptions = mOptions || {}; var sFileType = this._getFileType(mOptions.json), sLibPackage = this.name.replace(/\./g, '/'), bLibLoaded = !!sap.ui.loader._.getModuleState(sLibPackage + '/library.js'), bHttp2 = Configuration.getDepCache(); if (sFileType === 'none' || bLibLoaded) { // When a library's entry module is already available (either loaded or preloaded), a resolved promise // is returned instead of this._loadingStatus.promise to avoid the deadlock between 2 libraries which // have dependency of each other return mOptions.sync ? this : Promise.resolve(this); } if (this._loadingStatus == null && mOptions.url) { registerModulePath(this.name, mOptions.url); } this._loadingStatus = this._loadingStatus || {}; 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 (this._loadingStatus.pending && 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; } } 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"); if (mOptions.sync) { try { sap.ui.requireSync(sLibPackage + '/library-preload-lazy'); } catch (e) { Log.error("failed to load '" + sLibPackage + "/library-preload-lazy.js" + "' synchronously (" + (e && e.message || e) + ")"); } return this; } else { return sap.ui.loader._.loadJSResourceAsync( sLibPackage + '/library-preload-lazy.js', /* ignoreErrors = */ true); } } // otherwise mark as pending this._loadingStatus.pending = true; this._loadingStatus.async = !mOptions.sync; // first preload code, resolves with list of dependencies (or undefined) var 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) { var oManifest = this.getManifest(), aPromises; if (aDependencies && aDependencies.length) { if (!mOptions.sync) { var aEagerDependencies = [], aLazyDependencies = []; aDependencies.forEach(function(oDependency) { if (oDependency.lazy) { aLazyDependencies.push(oDependency); } else { aEagerDependencies.push(oDependency.name); } }); // aEagerDependencies contains string elements before executing the next line aEagerDependencies = VersionInfo._getTransitiveDependencyForLibraries(aEagerDependencies) .map(function(sDependencyName) { return { name: sDependencyName }; }); // aEagerDependencies contains object elements after executing the above line // combine transitive closure of eager dependencies and direct lazy dependencies, // the latter might be redundant aDependencies = aEagerDependencies.concat(aLazyDependencies); } aPromises = aDependencies.map(function(oDependency) { var oLibrary = Library._get(oDependency.name, true/* bCreate */); return oLibrary._preload({ sync: mOptions.sync, lazy: oDependency.lazy }); }); } else { aPromises = []; } if (!mOptions.sync && oManifest && Version(oManifest._version).compareTo("1.9.0") >= 0) { aPromises.push(this.loadResourceBundle()); } var 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 */ _preloadJSFormat: function(mOptions) { mOptions = mOptions || {}; var that = this; var sPreloadModule = this.name.replace(/\./g, '/') + (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.then(function() { return that._getDependencies(); }, 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 */ _preloadJSONFormat: function(mOptions) { mOptions = mOptions || {}; var sURL = getModulePath(this.name, "/library-preload.json"); return mixedFetch(sURL, { headers: { Accept: mixedFetch.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. * * @returns {object|undefined} The manifest of the library */ getManifest: function() { if (!this.oManifest) { var manifestModule = this.name.replace(/\./g, '/') + '/manifest.json'; if ( sap.ui.loader._.getModuleState(manifestModule) ) { this.oManifest = LoaderExtensions.loadResource(manifestModule, { dataType: 'json', async: false, // always sync as we are sure to load from preload cache failOnError: false }); deepFreeze(this.oManifest); } } return this.oManifest; }, /** * Returns the dependency information of the library which is read from the library's manifest. * * The returned array contains elements which have a property "name" and an optional "lazy" property. * * @private * @returns {Array<{name:string, lazy:boolean}>} The dependency information of the library */ _getDependencies: function() { var oManifest = this.getManifest(); var aDependencies = []; var mDependencies = oManifest && oManifest["sap.ui5"] && oManifest["sap.ui5"].dependencies && oManifest["sap.ui5"].dependencies.libs; if (mDependencies) { // convert manifest map to array, inject object which contains "name" and optional "lazy" properties return Object.keys(mDependencies).reduce(function(aResult, sDependencyName) { if (!mDependencies[sDependencyName].lazy) { aResult.push({ name: sDependencyName }); } else if (oLibraryWithBundleInfo.has(sDependencyName)) { aResult.push({ name: sDependencyName, lazy: true }); } return aResult; }, aDependencies); } else { return aDependencies; } }, /** * Returns the i18n information of the library which is read from the library's manifest. * * @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> * @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 */ _includeTheme: function(sVariant, sQuery) { var sName = this.name; aAllLibrariesRequiringCss.push({ name: sName, version: this.version, variant: sVariant }); _getThemeManager().then(function(ThemeManager) { ThemeManager.includeLibraryTheme(sName, sVariant, sQuery); }); }, /** * Returns a resource bundle for the given locale. * * The locale's default value is read from {@link sap.ui.core.Configuration#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. * * <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 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 {module:sap/base/i18n/ResourceBundle} The best matching * resource bundle for the given locale or <code>undefined</code> when resource bundle isn't available */ 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 sap.ui.core.Configuration#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 */ 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 * @prviate */ _loadResourceBundle: function(sLocale, bSync) { var that = this, oManifest = this.getManifest(), // 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 || Configuration.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: Configuration.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); } }); /** * Returns an array containing all libraries which require loading of CSS * * @returns {Array} Array containing all libraries which require loading of CSS * @private * @ui5-restricted sap.ui.core.theming.Parameters */ Library.getAllInstancesRequiringCss = function() { return aAllLibrariesRequiringCss.slice(); }; /** * Returns an instance of a Library whose "name" is the same as the given <code>sName</code>. Created library * instances are cached by its name. For one library name, there's maximum one instance created and cached. * * If no library under the given <code>sName</code> is created yet, <code>undefined</code> is returned. To load a * library, {@link #.load} can be used directly without calling this method in advance. * * @param {string} sName The name of the library * @returns {Promise<module:sap/ui/core/Lib>|undefined} Either an instance of the library or <code>undefined</code> * @public */ Library.get = function(sName) { return Library._get(sName); }; /** * 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<module: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 }); } 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]; } // [1] Guess ResourceName var sBundleName = sap.ui.loader._.guessResourceName(sBundleUrl); if (sBundleName) { // [2] Guess library name for (var sLibrary in mLibraries) { if (!mLibraries[sLibrary].isSettingsEnhanced()) { // ignore libraries that haven't been initialized continue; } var sLibraryName = sLibrary.replace(/\./g, "/"); var oLib = mLibraries[sLibrary]; if (sLibraryName !== "" && sBundleName.startsWith(sLibraryName + "/")) { var sBundlePath = sBundleName.replace(sLibraryName + "/", ""); // [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(sLibraryName, "/" + vI18n.bundleUrl); sBundlePath = getModulePath(sLibraryName, "/" + 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 mGuessedLibraries[sBundleUrl] = false; } } } } } }; /** * 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. * @public */ Library.all = function() { var mInitLibraries = {}; Object.keys(mLibraries).forEach(function(sKey) { if (mLibraries[sKey].isSettingsEnhanced()) { mInitLibraries[sKey] = mLibraries[sKey]; } }); return mInitLibraries; }; /** * 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>oLibInfo</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.<br> <b>Note:</b> Future versions might 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 sap.ui.core.Configuration 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.<br> <b>Note:</b> Dependencies between libraries don't have to be modeled as AMD * dependencies. Only when enums or types from an additional library are used in the coding of the * <code>library.js</code> module, the library should be additionally listed in the AMD dependencies.</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 returned by {@link #.getInitializedLibraries}. * * * <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 might ignore the information * provided in <code>oLibInfo</code> and might evaluate the descriptor file instead. Library developers therefore * should keep the information in both files in sync. * * When the <code>manifest.json</code> is generated from the <code>.library</code> file (which is the default * for UI5 libraries built with Maven), then the content of the <code>.library</code> and <code>library.js</code> * files must be kept in sync. * * @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 * @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} [oLibInfo.extensions] Potential extensions of the library metadata; structure not defined by the * UI5 core framework. * @returns {object|undefined} As of version 1.101; returns the library namespace, based on the given library name. * Returns 'undefined' if no library name is provided. * @public */ Library.init = function(mSettings) { assert(typeof mSettings === "object" , "mSettings given to 'sap/ui/core/Lib.create' must be an object"); assert(typeof mSettings.name === "string" && mSettings.name, "mSettings given to 'sap/ui/core/Lib.create' must have the 'name' property set"); var METHOD = "sap/ui/core/Lib.init"; Log.debug("Analyzing Library " + mSettings.name, null, METHOD); var oLib = Library._get(mSettings.name, true /* bCreate */); oLib.enhanceSettings(mSettings); // ensure namespace var oLibNamespace = ObjectPath.create(mSettings.name), i; // resolve dependencies for (i = 0; i < oLib.dependencies.length; i++) { var sDepLib = oLib.dependencies[i]; var oDepLib = Library._get(sDepLib, true /* bCreate */); Log.debug("resolve Dependencies to " + sDepLib, null, METHOD); if (!oDepLib.isSettingsEnhanced()) { Log.warning("Dependency from " + mSettings.name + " to " + sDepLib + " has not been resolved by library itself", null, METHOD); Library._load({name: sDepLib}, {sync: true}); // legacy-relevant: Sync fallback for missing manifest/AMD dependencies } } // register interface types DataType.registerInterfaceTypes(oLib.interfaces); // Declare a module for each (non-builtin) simple type // Only needed for backward compatibility: some code 'requires' such types although they never have been modules on their own for (i = 0; i < oLib.types.length; i++) { if ( !/^(any|boolean|float|int|string|object|void)$/.test(oLib.types[i]) ) { sap.ui.loader._.declareModule(oLib.types[i].replace(/\./g, "/") + ".js"); // ensure parent namespace of the type var sNamespacePrefix = oLib.types[i].substring(0, oLib.types[i].lastIndexOf(".")); if (ObjectPath.get(sNamespacePrefix) === undefined) { // parent type namespace does not exists, so we create its ObjectPath.create(sNamespacePrefix); } } } // create lazy loading stubs for all controls and elements var aElements = oLib.controls.concat(oLib.elements); for (i = 0; i < aElements.length; i++) { sap.ui.lazyRequire(aElements[i], "new extend getMetadata"); // TODO don't create an 'extend' stub for final classes } // include the library theme, but only if it has not been suppressed in library metadata or by configuration if (!oLib.noLibraryCSS) { var oLibThemingInfo = { name: oLib.name, version: oLib.version }; // Don't reset ThemeManager in case CSS for current library is already preloaded var bResetThemeManager = Configuration.getValue('preloadLibCss').indexOf(oLib.name) === -1; aAllLibrariesRequiringCss.push(oLibThemingInfo); _getThemeManager(bResetThemeManager).then(function(ThemeManager) { ThemeManager._includeLibraryThemeAndEnsureThemeRoot(oLibThemingInfo); }); } // expose some legacy names oLib.sName = oLib.name; oLib.aControls = oLib.controls; Library.fireLibraryChanged({ name: mSettings.name, stereotype: "library", operation: "add", metadata: oLib }); return oLibNamespace; }; function getLibraryModuleNames(aLibs) { return aLibs.map(function(oLib) { return oLib.name.replace(/\./g, "/") + "/library"; }); } function requireLibrariesAsync(aLibs) { var aLibraryModuleNames = getLibraryModuleNames(aLibs); return new Promise(function(resolve, reject) { sap.ui.require( aLibraryModuleNames, function () { // Wrapper function is needed to omit parameters for resolve() // which is always one library (first from the list), not an array of libraries. resolve(aLibs); }, reject ); }); } /** * Loads the given library and its dependencies and makes its content available to the application. * * * <h3>What it does</h3> * * When library preloads are