@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
941 lines (817 loc) • 31.6 kB
JavaScript
/*
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides base class sap.ui.core.Component for all components
sap.ui.define([
'sap/ui/base/Object',
'sap/ui/thirdparty/URI',
'sap/base/util/Version',
'sap/base/Log',
'sap/ui/dom/includeStylesheet',
'sap/base/i18n/ResourceBundle',
'sap/base/util/uid',
'sap/base/util/merge',
'sap/base/util/isPlainObject',
'sap/base/util/LoaderExtensions',
"sap/base/util/isEmptyObject"
],
function(
BaseObject,
URI,
Version,
Log,
includeStylesheet,
ResourceBundle,
uid,
merge,
isPlainObject,
LoaderExtensions,
isEmptyObject
) {
"use strict";
/*global Promise */
/**
* Removes the version suffix
*
* @param {string} sVersion Version
* @return {string} Version without suffix
*/
function getVersionWithoutSuffix(sVersion) {
var oVersion = Version(sVersion);
return oVersion.getSuffix() ? Version(oVersion.getMajor() + "." + oVersion.getMinor() + "." + oVersion.getPatch()) : oVersion;
}
/**
* Utility function to access a child member by a given path
*
* @param {object} oObject Object
* @param {string} sPath Path starting with a slash (/)
* @return {any} value of a member specified by its path;
* if the path doesn't start with a slash it returns the value for the given path of the object
*/
function getObject(oObject, sPath) {
// if the incoming sPath is a path we do a nested lookup in the
// manifest object and return the concrete value, e.g. "/sap.ui5/extends"
if (oObject && sPath && typeof sPath === "string" && sPath[0] === "/") {
var aPaths = sPath.substring(1).split("/"),
sPathSegment;
for (var i = 0, l = aPaths.length; i < l; i++) {
sPathSegment = aPaths[i];
// Prevent access to native properties
oObject = oObject.hasOwnProperty(sPathSegment) ? oObject[sPathSegment] : undefined;
// Only continue with lookup if the value is an object.
// Accessing properties of other types is not allowed!
if (oObject === null || typeof oObject !== "object") {
// Clear the value in case this is not the last segment in the path.
// Otherwise e.g. "/foo/bar/baz" would return the value of "/foo/bar"
// in case it is not an object.
if (i + 1 < l && oObject !== undefined) {
oObject = undefined;
}
break;
}
}
return oObject;
}
// if no path starting with slash is specified we access and
// return the value directly from the manifest
return oObject && oObject[sPath];
}
/**
* Freezes the object and nested objects to avoid later manipulation
*
* @param 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]);
}
}
}
}
/**
* Creates and initializes a manifest wrapper which provides API access to
* the content of the manifest.
*
* @param {object}
* oManifest the manifest object
* @param {object}
* [mOptions] (optional) the configuration options
* @param {string}
* [mOptions.componentName] (optional) the name of the component
* @param {string}
* [mOptions.baseUrl] (optional) the base URL which is used to resolve relative URLs against
* @param {boolean}
* [mOptions.process=true] (optional) Flag whether the manifest object should be processed or not
* which means that the placeholders will be replaced with resource bundle values
* @param {string[]}
* [mOptions.activeTerminologies] (optional) A list of active terminologies. If the <code>mOptions.process</code>
* flag is set to <code>true</code>, the given terminologies will be respected when replacing placeholders with resource
* bundle values.
* To use active terminologies, the <code>sap.app.i18n</code> section in the manifest
* must be defined in object syntax as described here: {@link topic:eba8d25a31ef416ead876e091e67824e Text Verticalization}.
* The order of the given active terminologies is significant. The {@link sap.base.i18n.ResourceBundle ResourceBundle} API
* documentation describes the processing behavior in more detail.
*
*
* @public
*
* @class The Manifest class.
* @extends sap.ui.base.Object
* @author SAP SE
* @version 1.87.1
* @alias sap.ui.core.Manifest
* @since 1.33.0
*/
var Manifest = BaseObject.extend("sap.ui.core.Manifest", /** @lends sap.ui.core.Manifest.prototype */
{
constructor : function(oManifest, mOptions) {
BaseObject.apply(this, arguments);
// create a unique id per manifest
this._uid = uid();
// instance variables
this._iInstanceCount = 0;
this._bIncludesLoaded = false;
// apply the manifest related values
this._oRawManifest = oManifest;
this._bProcess = !(mOptions && mOptions.process === false);
this._bAsync = !(mOptions && mOptions.async === false);
this._activeTerminologies = mOptions && mOptions.activeTerminologies;
// component name is passed via options (overrides the one defined in manifest)
this._sComponentName = mOptions && mOptions.componentName;
// resolve the base URL of the component depending of given base
// URL or the module path of the component
var sComponentName = this.getComponentName(),
sBaseUrl = mOptions && mOptions.baseUrl || sComponentName && sap.ui.require.toUrl(sComponentName.replace(/\./g, "/")) + "/";
if (sBaseUrl) {
this._oBaseUri = new URI(sBaseUrl).absoluteTo(new URI(document.baseURI).search(""));
}
// determine the base URL of the manifest or use the component base
// as by default the manifest is next to the component controller
if (mOptions && typeof mOptions.url === "string") {
this._oManifestBaseUri = new URI(mOptions.url).absoluteTo(new URI(document.baseURI).search("")).search("");
} else {
this._oManifestBaseUri = this._oBaseUri;
}
// make sure to freeze the raw manifest (avoid manipulations)
deepFreeze(this._oRawManifest);
// store the raw manifest for the time being and process the
// i18n placeholders in the manifest later
// remark: clone the frozen raw manifest to enable changes
this._oManifest = merge({}, this._oRawManifest);
// resolve the i18n texts immediately when manifest should be processed
if (this._bProcess) {
this._processI18n();
}
},
/**
* Triggers the processing of the i18n texts to replace them
* with the values from "sap.app/i18n"
*
* @param {boolean} bAsync true, if the ResourceBundle will be loaded async
* @param {string[]} [aI18nProperties] The array of manifest temnplate strings to replace (if processed already processed from outside this function)
* @return {Promise|undefined} when using the API async it will return a Promise which resolves when the texts have been replaced
*/
_processI18n: function(bAsync, aI18nProperties) {
// if not given from outside (from async Component startup):
// find all i18n property paths based on the handlebars placeholder template
if (!aI18nProperties) {
aI18nProperties = [];
this._preprocess({
i18nProperties: aI18nProperties
});
}
if (aI18nProperties.length > 0) {
var fnReplaceI18n = function(oResourceBundle) {
var fnReplaceI18nText = function(sMatch, sI18nKey) {
return oResourceBundle.getText(sI18nKey);
};
for (var i = 0, l = aI18nProperties.length; i < l; i++) {
var oProperty = aI18nProperties[i];
oProperty.object[oProperty.key] = oProperty.object[oProperty.key].replace(Manifest._rManifestTemplate, fnReplaceI18nText);
}
};
if (bAsync) {
return this._loadI18n(bAsync).then(fnReplaceI18n);
} else {
fnReplaceI18n(this._loadI18n(bAsync));
}
} else {
return bAsync ? Promise.resolve() : undefined;
}
},
/**
* Loads the ResourceBundle which is defined in the manifest
* in "sap.app/i18n".
*
* @param {boolean} bAsync flag, whether to load the ResourceBundle async or not
* @return {Promise|ResourceBundle} Promise which resolves with the ResourceBundle (async) or the ResourceBundle itself (sync)
* @private
*/
_loadI18n: function(bAsync) {
// extract the i18n URI from the manifest
var oManifest = this._oRawManifest,
oI18nURI,
// a bundle url given in the "sap.app.i18n" section is by default always resolved relative to the manifest
// when using the object syntax for the "sap.app.i18n" section a "bundleRelativeTo" property can be given to change the default
sBaseBundleUrlRelativeTo = "manifest",
vI18n = (oManifest["sap.app"] && oManifest["sap.app"]["i18n"]) || "i18n/i18n.properties";
if (typeof vI18n === "string") {
oI18nURI = new URI(vI18n);
// load the ResourceBundle relative to the manifest
return ResourceBundle.create({
url: this.resolveUri(oI18nURI, sBaseBundleUrlRelativeTo),
async: bAsync
});
} else if (typeof vI18n === "object") {
// make a copy as manifest is frozen
vI18n = JSON.parse(JSON.stringify(vI18n));
sBaseBundleUrlRelativeTo = vI18n.bundleUrlRelativeTo || sBaseBundleUrlRelativeTo;
// resolve bundleUrls of terminologies
this._processResourceConfiguration(vI18n, sBaseBundleUrlRelativeTo);
// merge activeTerminologies and settings object into mParams
var mParams = Object.assign({
activeTerminologies: this._activeTerminologies,
async: bAsync
}, vI18n);
return ResourceBundle.create(mParams);
}
},
/**
* Returns the manifest defined in the metadata of the component.
* If not specified, the return value is null.
*
* @return {Object} manifest.
* @public
*/
getJson: function() {
return this._oManifest;
},
/**
* Returns the raw manifest defined in the metadata of the component.
* If not specified, the return value is null.
*
* @return {Object} manifest
* @public
*/
getRawJson: function() {
return this._oRawManifest;
},
/**
* Returns the configuration of a manifest section or the value for a
* specific path. If no key is specified, the return value is null.
*
* Example:
* <code>
* {
* "sap.ui5": {
* "dependencies": {
* "libs": {
* "sap.m": {}
* },
* "components": {
* "my.component.a": {}
* }
* }
* });
* </code>
*
* The configuration above can be accessed in the following ways:
* <ul>
* <li><b>By section/namespace</b>: <code>oManifest.getEntry("sap.ui5")</code></li>
* <li><b>By path</b>: <code>oManifest.getEntry("/sap.ui5/dependencies/libs")</code></li>
* </ul>
*
* By section/namespace returns the configuration for the specified manifest
* section and by path allows to specify a concrete path to a dedicated entry
* inside the manifest. The path syntax always starts with a slash (/).
*
* @param {string} sKey Either the manifest section name (namespace) or a concrete path
* @return {any|null} Value of the key (could be any kind of value)
* @public
*/
getEntry: function(sPath) {
if (!sPath || sPath.indexOf(".") <= 0) {
Log.warning("Manifest entries with keys without namespace prefix can not be read via getEntry. Key: " + sPath + ", Component: " + this.getComponentName());
return null;
}
var oManifest = this.getJson();
var oEntry = getObject(oManifest, sPath);
// top-level manifest section must be an object (e.g. sap.ui5)
if (sPath && sPath[0] !== "/" && !isPlainObject(oEntry)) {
Log.warning("Manifest entry with key '" + sPath + "' must be an object. Component: " + this.getComponentName());
return null;
}
return oEntry;
},
/**
* Validates the current UI5 version with the minimal version defined in the
* manifest. If the minimal version is greater than the current version an
* issue will be reported in the console if open.
*
* @private
*/
checkUI5Version: function() {
// version check => only if minVersion is available a warning
// will be logged and the debug mode is turned on
// TODO: enhance version check also for libraries and components
var sMinUI5Version = this.getEntry("/sap.ui5/dependencies/minUI5Version");
if (sMinUI5Version &&
Log.isLoggable(Log.Level.WARNING) &&
sap.ui.getCore().getConfiguration().getDebug()) {
sap.ui.getVersionInfo({async: true}).then(function(oVersionInfo) {
var oMinVersion = getVersionWithoutSuffix(sMinUI5Version);
var oVersion = getVersionWithoutSuffix(oVersionInfo && oVersionInfo.version);
if (oMinVersion.compareTo(oVersion) > 0) {
Log.warning("Component \"" + this.getComponentName() + "\" requires at least version \"" + oMinVersion.toString() + "\" but running on \"" + oVersion.toString() + "\"!");
}
}.bind(this), function(e) {
Log.warning("The validation of the version for Component \"" + this.getComponentName() + "\" failed! Reasion: " + e);
}.bind(this));
}
},
/**
* Loads the included CSS and JavaScript resources. The resources will be
* resolved relative to the component location.
*
* @private
*/
loadIncludes: function() {
// skip loading includes once already loaded
if (this._bIncludesLoaded) {
return;
}
var mResources = this.getEntry("/sap.ui5/resources");
if (!mResources) {
return;
}
var sComponentName = this.getComponentName();
// load JS files
var aJSResources = mResources["js"];
if (aJSResources) {
for (var i = 0; i < aJSResources.length; i++) {
var oJSResource = aJSResources[i];
var sFile = oJSResource.uri;
if (sFile) {
// load javascript file
var m = sFile.match(/\.js$/i);
if (m) {
//var sJsUrl = this.resolveUri(sFile.slice(0, m.index));
var sJsUrl = sComponentName.replace(/\./g, '/') + (sFile.slice(0, 1) === '/' ? '' : '/') + sFile.slice(0, m.index);
Log.info("Component \"" + sComponentName + "\" is loading JS: \"" + sJsUrl + "\"");
// call internal sap.ui.require variant that accepts a requireJS path and loads the module synchronously
sap.ui.requireSync(sJsUrl);
}
}
}
}
// include CSS files
var aCSSResources = mResources["css"];
if (aCSSResources) {
for (var j = 0; j < aCSSResources.length; j++) {
var oCSSResource = aCSSResources[j];
if (oCSSResource.uri) {
var sCssUrl = this.resolveUri(oCSSResource.uri);
Log.info("Component \"" + sComponentName + "\" is loading CSS: \"" + sCssUrl + "\"");
includeStylesheet(sCssUrl, {
id: oCSSResource.id,
"data-sap-ui-manifest-uid": this._uid
});
}
}
}
this._bIncludesLoaded = true;
},
/**
* Removes the included CSS resources.
*
* @private
*/
removeIncludes: function() {
// skip removing includes when not loaded yet
if (!this._bIncludesLoaded) {
return;
}
var mResources = this.getEntry("/sap.ui5/resources");
if (!mResources) {
return;
}
var sComponentName = this.getComponentName();
// remove CSS files
var aCSSResources = mResources["css"];
if (aCSSResources) {
// As all <link> tags have been marked with the manifest's unique id (via data-sap-ui-manifest-uid)
// it is not needed to check for all individual CSS files defined in the manifest.
// Checking for all "href"s again might also cause issues when they have been adopted (e.g. to add cachebuster url params).
var aLinks = document.querySelectorAll("link[data-sap-ui-manifest-uid='" + this._uid + "']");
for (var i = 0; i < aLinks.length; i++) {
var oLink = aLinks[i];
Log.info("Component \"" + sComponentName + "\" is removing CSS: \"" + oLink.href + "\"");
oLink.parentNode.removeChild(oLink);
}
}
this._bIncludesLoaded = false;
},
/**
* Load external dependencies (like libraries and components)
*
* @private
*/
loadDependencies: function() {
// afterwards we load our dependencies!
var oDep = this.getEntry("/sap.ui5/dependencies"),
sComponentName = this.getComponentName();
if (oDep) {
// load the libraries
var mLibraries = oDep["libs"];
if (mLibraries) {
for (var sLib in mLibraries) {
if (!mLibraries[sLib].lazy) {
Log.info("Component \"" + sComponentName + "\" is loading library: \"" + sLib + "\"");
sap.ui.getCore().loadLibrary(sLib);
}
}
}
// load the components
var mComponents = oDep["components"];
if (mComponents) {
for (var sName in mComponents) {
if (!mComponents[sName].lazy) {
// TODO: refactor component/library loading code within Manifest / Component
// Only load component if not (pre-)loaded already
// Usually the dependencies are already loaded beforehand within Component.create
var sControllerModule = sName.replace(/\./g, "/") + "/Component";
var iModuleState = sap.ui.loader._.getModuleState(sControllerModule + ".js");
if (iModuleState === -1 /* PRELOADED */) {
// Execute preloaded component controller module
sap.ui.requireSync(sControllerModule);
} else if (iModuleState === 0 /* INITIAL */) {
Log.info("Component \"" + sComponentName + "\" is loading component: \"" + sName + ".Component\"");
// This can't be migrated to "Component.load" as the contract is sync
sap.ui.requireSync("sap/ui/core/Component");
sap.ui.component.load({
name: sName
});
}
}
}
}
}
},
/**
* Define the resource roots configured in the manifest.
* <p>
* In case of usage of "Manifest First" for Component loading the
* registration of the resource roots will be already done before loading
* the Component controller and thus can be used for the dependencies being
* declared within the sap.ui.define.
*
* @private
*/
defineResourceRoots: function() {
var mResourceRoots = this.getEntry("/sap.ui5/resourceRoots");
if (mResourceRoots) {
for (var sResourceRoot in mResourceRoots) {
var sResourceRootPath = mResourceRoots[sResourceRoot];
var oResourceRootURI = new URI(sResourceRootPath);
if (oResourceRootURI.is("absolute") || (oResourceRootURI.path() && oResourceRootURI.path()[0] === "/")) {
Log.error("Resource root for \"" + sResourceRoot + "\" is absolute and therefore won't be registered! \"" + sResourceRootPath + "\"", this.getComponentName());
continue;
}
sResourceRootPath = this._resolveUri(oResourceRootURI).toString();
var mPaths = {};
mPaths[sResourceRoot.replace(/\./g, "/")] = sResourceRootPath;
sap.ui.loader.config({paths:mPaths});
}
}
},
/**
* Returns the Component name which is defined in the manifest as
* <code>sap.ui5/componentName</code> or <code>sap.app/id</code>
*
* @return {string} the component name
* @public
*/
getComponentName: function() {
var oRawJson = this.getRawJson();
return this._sComponentName || getObject(oRawJson, "/sap.ui5/componentName") || getObject(oRawJson, "/sap.app/id");
},
/**
* Resolves the given URI relative to the Component by default
* or optional relative to the manifest when passing 'manifest'
* as second parameter.
*
* @param {string} sUri URI to resolve as string
* @param {string} [sRelativeTo='component'] defines to which base URI the given URI will be resolved to; one of ‘component' (default) or 'manifest'
* @return {string} resolved URI as string
* @public
* @since 1.60.1
*/
resolveUri: function(sUri, sRelativeTo) {
var oUri = this._resolveUri(new URI(sUri), sRelativeTo);
return oUri && oUri.toString();
},
/**
* Resolves the given URI relative to the Component by default
* or optional relative to the manifest when passing 'manifest'
* as second parameter.
*
* @param {URI} oUri URI to resolve
* @param {string} [sRelativeTo] defines to which base URI the given URI will be resolved to; one of ‘component' (default) or 'manifest'
* @return {URI} resolved URI
* @private
*/
_resolveUri: function(oUri, sRelativeTo) {
return Manifest._resolveUriRelativeTo(oUri, sRelativeTo === "manifest" ? this._oManifestBaseUri : this._oBaseUri);
},
/**
* Generic preprocessing function.
* Current features:
* - resolve "ui5://..." urls.
* - collect "i18n placeholder properties"
*
* @param {object} args arguments map
* @param {boolean} [args.resolveUI5Urls] whether "ui5://..." URLs should be resolved
* @param {array} [args.i18nProperties] an array into which all i18n placeholders will be pushed
*
* @private
* @ui5-restricted sap.ui.core.Manifest, sap.ui.core.Component
*/
_preprocess: function(args) {
Manifest.processObject(this._oManifest, function(oObject, sKey, sValue) {
if (args.resolveUI5Urls && sValue.startsWith("ui5:")) {
oObject[sKey] = LoaderExtensions.resolveUI5Url(sValue);
} else if (args.i18nProperties && sValue.match(Manifest._rManifestTemplate)) {
args.i18nProperties.push({
object: oObject,
key: sKey
});
}
});
},
/**
* Initializes the manifest which executes checks, define the resource
* roots, load the dependencies and the includes.
* @private
*/
init: function(oInstance) {
if (this._iInstanceCount === 0) {
// version check => only if minVersion is available a warning
// will be logged and the debug mode is turned on
this.checkUI5Version();
// define the resource roots
// => if not loaded via manifest first approach the resource roots
// will be registered too late for the AMD modules of the Component
// controller. This is a constraint for the resource roots config
// in the manifest!
this.defineResourceRoots();
// resolve "ui5://..." URLs after the resource-rooots have been defined
// this way all ui5 URLs can rely on any resource root definition
this._preprocess({
resolveUI5Urls: true
});
// load the component dependencies (other UI5 libraries)
this.loadDependencies();
// load the custom scripts and CSS files
this.loadIncludes();
// activate the static customizing
this.activateCustomizing();
}
// activate the instance customizing
if (oInstance) {
this.activateCustomizing(oInstance);
}
this._iInstanceCount++;
},
/**
* Terminates the manifest and does some final clean-up.
* @private
*/
exit: function(oInstance) {
// ensure that the instance count is never negative
var iInstanceCount = Math.max(this._iInstanceCount - 1, 0);
// deactivate the instance customizing
if (oInstance) {
this.deactivateCustomizing(oInstance);
}
if (iInstanceCount === 0) {
// deactivcate the customizing
this.deactivateCustomizing();
// remove the custom scripts and CSS files
this.removeIncludes();
}
this._iInstanceCount = iInstanceCount;
},
/**
* Activates the customizing for the component or a dedicated component
* instance when providing the component instance as parameter.
* @param {sap.ui.core.Component} [oInstance] Reference to the Component instance
* @private
*/
activateCustomizing: function(oInstance) {
// activate the customizing configuration
var oUI5Manifest = this.getEntry("sap.ui5", true),
mExtensions = oUI5Manifest && oUI5Manifest["extends"] && oUI5Manifest["extends"].extensions;
if (!isEmptyObject(mExtensions)) {
var CustomizingConfiguration = sap.ui.requireSync('sap/ui/core/CustomizingConfiguration');
if (!oInstance) {
CustomizingConfiguration.activateForComponent(this.getComponentName());
} else {
CustomizingConfiguration.activateForComponentInstance(oInstance);
}
}
},
/**
* Deactivates the customizing for the component or a dedicated component
* instance when providing the component instance as parameter.
* @param {sap.ui.core.Component} [oInstance] Reference to the Component instance
* @private
*/
deactivateCustomizing: function(oInstance) {
// deactivate the customizing configuration
var CustomizingConfiguration = sap.ui.require('sap/ui/core/CustomizingConfiguration');
if (CustomizingConfiguration) {
if (!oInstance) {
CustomizingConfiguration.deactivateForComponent(this.getComponentName());
} else {
CustomizingConfiguration.deactivateForComponentInstance(oInstance);
}
}
}
});
// Manifest Template RegExp: {{foo}}
Manifest._rManifestTemplate = /\{\{([^\}\}]+)\}\}/g;
/**
* Resolves the given URI relative to the given base URI.
*
* @param {URI} oUri URI to resolve
* @param {URI} oBase Base URI
* @return {URI} resolved URI
* @static
* @private
*/
Manifest._resolveUriRelativeTo = function(oUri, oBase) {
if (oUri.is("absolute") || (oUri.path() && oUri.path()[0] === "/")) {
return oUri;
}
var oPageBase = new URI(document.baseURI).search("");
oBase = oBase.absoluteTo(oPageBase);
return oUri.absoluteTo(oBase).relativeTo(oPageBase);
};
/**
* Function that loops through the model config and resolves the bundle urls
* of terminologies relative to the component or relative to the manifest
*
* @example
* {
* "oil": {
* "bundleUrl": "i18n/terminologies/oil.i18n.properties"
* },
* "retail": {
* "bundleName": "i18n.terminologies.retail.i18n.properties"
* }
* }
*
* @param mSettings Map with model config settings
* @param sBaseBundleUrlRelativeTo BundleUrlRelativeTo info from base config
* @param bAlreadyResolvedOnRoot Whether the bundleUrl was already resolved (usually by the sap.ui.core.Component)
*
* @private
* @ui5-restricted sap.ui.core.Component
*/
Manifest.prototype._processResourceConfiguration = function (mSettings, sBaseBundleUrlRelativeTo, bAlreadyResolvedOnRoot) {
var that = this;
Object.keys(mSettings).forEach(function(sKey) {
if (sKey === "bundleUrl" && !bAlreadyResolvedOnRoot) {
var sBundleUrl = mSettings[sKey];
mSettings[sKey] = that.resolveUri(sBundleUrl, mSettings["bundleUrlRelativeTo"] || sBaseBundleUrlRelativeTo);
}
if (sKey === "terminologies") {
var mTerminologies = mSettings[sKey];
for (var sTerminology in mSettings[sKey]) {
that._processResourceConfiguration(mTerminologies[sTerminology], sBaseBundleUrlRelativeTo);
}
}
if (sKey === "enhanceWith") {
var aEnhanceWith = mSettings[sKey];
for (var i = 0; i < aEnhanceWith.length; i++) {
that._processResourceConfiguration(aEnhanceWith[i], sBaseBundleUrlRelativeTo);
}
}
});
};
/**
* Function to load the manifest by URL
*
* @param {object} mOptions the configuration options
* @param {string} mOptions.manifestUrl URL of the manifest
* @param {string} [mOptions.componentName] name of the component
* @param {boolean} [mOptions.async=false] Flag whether to load the manifest async or not
* @param {boolean} [mOptions.failOnError=true] Flag whether to fail if an error occurs or not
* If set to <code>false</code>, errors during the loading of the manifest.json file (e.g. 404) will be ignored and
* the resulting manifest object will be <code>null</code>.
* For asynchronous calls the returned Promise will not reject but resolve with <code>null</code>.
* @param {function} [mOptions.processJson] Callback for asynchronous processing of the loaded manifest.
* The callback receives the parsed manifest object and must return a Promise which resolves with an object.
* It allows to early access and modify the manifest object.
* @param {string[]} [mOptions.activeTerminologies] A list of active terminologies.
* The order of the given active terminologies is significant. The {@link sap.base.i18n.ResourceBundle ResourceBundle} API
* documentation describes the processing behavior in more detail.
* Please have a look at this dev-guide chapter for general usage instructions: {@link topic:eba8d25a31ef416ead876e091e67824e Text Verticalization}.
* @return {sap.ui.core.Manifest|Promise} Manifest object or for asynchronous calls an ECMA Script 6 Promise object will be returned.
* @protected
*/
Manifest.load = function(mOptions) {
var sManifestUrl = mOptions && mOptions.manifestUrl,
sComponentName = mOptions && mOptions.componentName,
bAsync = mOptions && mOptions.async,
bFailOnError = mOptions && mOptions.failOnError,
fnProcessJson = mOptions && mOptions.processJson;
// When loading the manifest via URL the language and client should be
// added as query parameter as it may contain language dependent texts
// or needs to be loaded from a specific client.
// If the language or the client is already provided it won't be overridden
// as this is expected to be only done by intension.
var oManifestUrl = new URI(sManifestUrl);
["sap-language", "sap-client"].forEach(function(sName) {
if (!oManifestUrl.hasQuery(sName)) {
var sValue = sap.ui.getCore().getConfiguration().getSAPParam(sName);
if (sValue) {
oManifestUrl.addQuery(sName, sValue);
}
}
});
sManifestUrl = oManifestUrl.toString();
Log.info("Loading manifest via URL: " + sManifestUrl);
if (!bAsync) {
Log.warning("Synchronous loading of manifest, due to Manifest.load() call for '" + sManifestUrl + "'. Use parameter 'async' true to avoid this.", "SyncXHR", null, function() {
return {
type: "SyncXHR",
name: "Manifest"
};
});
}
var oManifestJSON = LoaderExtensions.loadResource({
url: sManifestUrl,
dataType: "json",
async: typeof bAsync !== "undefined" ? bAsync : false,
headers: {
"Accept-Language": sap.ui.getCore().getConfiguration().getLanguageTag()
},
failOnError: typeof bFailOnError !== "undefined" ? bFailOnError : true
});
var mSettings = {
componentName: sComponentName,
url: sManifestUrl,
process: false
};
if (mOptions.activeTerminologies) {
mSettings["activeTerminologies"] = mOptions.activeTerminologies;
}
if (bAsync) {
return oManifestJSON.then(function(oManifestJSON) {
// callback for preprocessing the json, e.g. via flex-hook in Component
if (fnProcessJson) {
return fnProcessJson(oManifestJSON);
} else {
return oManifestJSON;
}
}).then(function(oManifestJSON) {
return new Manifest(oManifestJSON, mSettings);
});
}
return new Manifest(oManifestJSON, mSettings);
};
/**
* Utility function to process strings in an object/array recursively
*
* @param {object/Array} oObject Object or array that will be processed
* @param {function} fnCallback function(oObject, sKey, sValue) to call for all strings. Use "oObject[sKey] = X" to change the value.
*/
Manifest.processObject = function (oObject, fnCallback) {
for (var sKey in oObject) {
if (!oObject.hasOwnProperty(sKey)) {
continue;
}
var vValue = oObject[sKey];
switch (typeof vValue) {
case "object":
// ignore null objects
if (vValue) {
Manifest.processObject(vValue, fnCallback);
}
break;
case "string":
fnCallback(oObject, sKey, vValue);
break;
default:
// do nothing in case of other types
}
}
};
return Manifest;
});