@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,325 lines (1,199 loc) • 74.2 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
//Provides 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 <link> tag to the page's head, pointing to the library's theme-specific stylesheet.
* The creation of such a <link> 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