UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

881 lines (776 loc) 30.7 kB
/* * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides base class sap.ui.core.Component for all components sap.ui.define([ 'sap/ui/base/Object', 'sap/ui/thirdparty/URI', 'sap/ui/VersionInfo', '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/ui/core/Configuration', 'sap/ui/core/Lib', './_UrlResolver' ], function( BaseObject, URI, VersionInfo, Version, Log, includeStylesheet, ResourceBundle, uid, merge, isPlainObject, LoaderExtensions, Configuration, Library, _UrlResolver ) { "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 {object} oObject the object to deep freeze * @private */ 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 module: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.111.5 * @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; // 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; // This should be only the case if manifestFirst is true but there was no manifest.json // As of 08.07.2021 we only set this parameter in Manifest.load in case of failing request this._bLoadManifestRequestFailed = mOptions && mOptions._bLoadManifestRequestFailed; // 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 including terminology bundles _UrlResolver._processResourceConfiguration(vI18n, { alreadyResolvedOnRoot: false, baseURI: this._oBaseUri, manifestBaseURI: this._oManifestBaseUri, relativeTo: 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} sPath 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) && Configuration.getDebug()) { VersionInfo.load().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! Reason: " + e); }.bind(this)); } }, /** * Loads the included CSS and JavaScript resources. The resources will be * resolved relative to the component location. * * @param {boolean} bAsync indicator whether the *.js resources should be loaded asynchronous * @return {Promise<void>|undefined} Promise for required *.js resources * * @private */ _loadIncludes: function(bAsync) { var mResources = this.getEntry("/sap.ui5/resources"), oPromise; if (!mResources) { return; } var sComponentName = this.getComponentName(); // [Deprecated since 1.94]: Load JS files. // Standard dependencies should be used instead. var aJSResources = mResources["js"]; if (aJSResources) { var requireAsync = function (sModule) { // Wrap promise within function because OPA waitFor (sap/ui/test/autowaiter/_promiseWaiter.js) // can't deal with a promise instance in the wrapped then handler return function() { return new Promise(function(resolve, reject) { sap.ui.require([sModule], resolve, reject); }); }; }; oPromise = Promise.resolve(); 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) { // call internal sap.ui.require variant that accepts a requireJS path and loads the module synchronously var sJsUrl = sComponentName.replace(/\./g, '/') + (sFile.slice(0, 1) === '/' ? '' : '/') + sFile.slice(0, m.index); Log.info("Component \"" + sComponentName + "\" is loading JS: \"" + sJsUrl + "\""); if (bAsync) { oPromise = oPromise.then(requireAsync(sJsUrl)); } else { sap.ui.requireSync(sJsUrl); // legacy-relevant: Sync path } } } } } // 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 }); } } } return oPromise; }, /** * Removes the included CSS resources. * * @private */ removeIncludes: function() { 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); } } }, /** * Load external dependencies (like libraries and components) * * @param {boolean} bAsync indicator whether the dependent libraries and components should be loaded asynchronous * @return {Promise<void>} Promise containing further promises of dependent libs and components requests * * @private */ _loadDependencies: function(bAsync) { var aPromises = []; // 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 + "\""); aPromises.push(Library._load(sLib, {sync: !bAsync})); } } } // collect all "non-lazy" components var mComponents = oDep["components"]; var aComponentDependencies = []; if (mComponents) { for (var sName in mComponents) { if (!mComponents[sName].lazy) { aComponentDependencies.push(sName); } } } if (bAsync) { // Async loading of Component, so that Component.load is available var pComponentLoad = new Promise(function(fnResolve, fnReject) { sap.ui.require(["sap/ui/core/Component"], function(Component) { fnResolve(Component); }, fnReject); }).then(function(Component) { // trigger Component.load for all "non-lazy" component dependencies (parallel) return Promise.all(aComponentDependencies.map(function(sComponentName) { // Component.load does not load the dependencies of a dependent component in case property manifest: false // because this could have a negative impact on performance and we do not know if there is a necessity // to load the dependencies // If needed we could make this configurable via manifest.json by adding a 'manifestFirst' option return Component.load({ name: sComponentName, manifest: false }); })); }); aPromises.push(pComponentLoad); } else { aComponentDependencies.forEach(function(sName) { // Check for and execute preloaded component controller module // Don't use sap.ui.component.load in order to avoid a warning log // See comments in commit 83f4b601f896dbfcab76fffd455cce841f15b2fb var sControllerModule = sName.replace(/\./g, "/") + "/Component"; var iModuleState = sap.ui.loader._.getModuleState(sControllerModule + ".js"); if (iModuleState === -1 /* PRELOADED */) { sap.ui.requireSync(sControllerModule); } else if (iModuleState === 0 /* INITIAL */) { Log.info("Component \"" + sComponentName + "\" is loading component: \"" + sName + ".Component\""); // requireSync needed because of cyclic dependency sap.ui.requireSync("sap/ui/core/Component"); sap.ui.component.load({ name: sName }); } }); } } return Promise.all(aPromises); }, /** * 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(sResourceRootPath); 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 oRelativeToBaseUri = sRelativeTo === "manifest" ? this._oManifestBaseUri : this._oBaseUri; var oResultUri = _UrlResolver._resolveUri(sUri, oRelativeToBaseUri); return oResultUri && oResultUri.toString(); }, /** * 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. * * @param {sap.ui.core.Component} [oInstance] Reference to the Component instance * @private */ init: function(oInstance) { if (this._iInstanceCount === 0) { this.loadDependenciesAndIncludes(); } this._iInstanceCount++; }, /** * Executes checks, define the resource roots, load the dependencies and the includes. * * @param {boolean} bAsync indicator whether the dependent dependencies and includes should be loaded asynchronous * @return {Promise<void>} Promise containing further promises of dependent libs and includes requests * * @private */ loadDependenciesAndIncludes: function (bAsync) { if (this._pDependenciesAndIncludes) { return this._pDependenciesAndIncludes; } // 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 }); this._pDependenciesAndIncludes = Promise.all([ this._loadDependencies(bAsync), // load the component dependencies (other UI5 libraries) this._loadIncludes(bAsync) // load the custom scripts and CSS files ]); return this._pDependenciesAndIncludes; }, /** * Terminates the manifest and does some final clean-up. * * @param {sap.ui.core.Component} [oInstance] Reference to the Component instance * @private */ exit: function(oInstance) { // ensure that the instance count is never negative var iInstanceCount = Math.max(this._iInstanceCount - 1, 0); if (iInstanceCount === 0) { // remove the custom scripts and CSS files this.removeIncludes(); delete this._pDependenciesAndIncludes; } this._iInstanceCount = iInstanceCount; } }); // Manifest Template RegExp: {{foo}} Manifest._rManifestTemplate = /\{\{([^\}\}]+)\}\}/g; /** * 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 module: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 = Configuration.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": Configuration.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 && oManifestJSON) { return fnProcessJson(oManifestJSON); } else { return oManifestJSON; } }).then(function(oManifestJSON) { if (!oManifestJSON) { // Loading manifest.json was not successful e.g. because there is no manifest.json // This should be only the case if manifestFirst is true but there was // no manifest.json mSettings._bLoadManifestRequestFailed = true; } 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; });