@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,289 lines (1,157 loc) • 62.8 kB
JavaScript
/*!
* 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 <link> 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 <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 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