@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,456 lines (1,268 loc) • 155 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 the real core class sap.ui.core.Core of SAPUI5
sap.ui.define([
'jquery.sap.global',
'sap/ui/Device',
'sap/ui/Global',
'sap/ui/base/BindingParser',
'sap/ui/base/DataType',
'sap/ui/base/EventProvider',
'sap/ui/base/Interface',
'sap/ui/base/Object',
'sap/ui/base/ManagedObject',
'sap/ui/performance/trace/Interaction',
'./Component',
'./Configuration',
'./Control',
'./Element',
'./ElementMetadata',
'./FocusHandler',
'./RenderManager',
'./ResizeHandler',
'./ThemeCheck',
'./UIArea',
'./message/MessageManager',
"sap/ui/util/ActivityDetection",
"sap/ui/dom/getScrollbarSize",
"sap/base/i18n/ResourceBundle",
"sap/base/Log",
"sap/ui/performance/Measurement",
"sap/ui/security/FrameOptions",
"sap/base/assert",
"sap/ui/dom/includeStylesheet",
"sap/base/util/ObjectPath",
"sap/base/util/Version",
"sap/base/util/array/uniqueSort",
"sap/base/util/uid",
'sap/ui/performance/trace/initTraces',
'sap/base/util/LoaderExtensions',
'sap/base/util/isEmptyObject',
'sap/base/util/each',
'sap/ui/VersionInfo',
'sap/ui/events/jquery/EventSimulation'
],
function(
jQuery,
Device,
Global,
BindingParser,
DataType,
EventProvider,
Interface,
BaseObject,
ManagedObject,
Interaction,
Component,
Configuration,
Control,
Element,
ElementMetadata,
FocusHandler,
RenderManager,
ResizeHandler,
ThemeCheck,
UIArea,
MessageManager,
ActivityDetection,
getScrollbarSize,
ResourceBundle,
Log,
Measurement,
FrameOptions,
assert,
includeStylesheet,
ObjectPath,
Version,
uniqueSort,
uid,
initTraces,
LoaderExtensions,
isEmptyObject,
each,
VersionInfo
/* ,EventSimulation */
) {
"use strict";
/*global Map, Promise */
// when the Core module has been executed before, don't execute it again
if (sap.ui.getCore && sap.ui.getCore()) {
return sap.ui.getCore();
}
// Initialize SAP Passport or FESR
initTraces();
// share the rendering log with the UIArea
var oRenderLog = UIArea._oRenderLog;
/**
* Set of libraries that have been loaded and initialized already.
* This is maintained separately from Core.mLibraries to protect it against
* modification from the outside (objects in mLibraries are currently exposed
* by getLoadedLibraries())
*/
var mLoadedLibraries = {};
/**
* Bookkeeping for the preloading of libraries.
*
* Might contain an object for each library (keyed by the library name).
* While the preload is pending for a library, the object has a property preload = true.
* In any case, the object contains a promise that fulfills / rejects when the preload
* fulfills / rejects.
* @private
*/
var mLibraryPreloadBundles = {};
/**
* EventProvider instance, EventProvider is no longer extended
* @private
*/
var _oEventProvider;
/*
* Internal class that can help to synchronize a set of asynchronous tasks.
* Each task must be registered in the sync point by calling startTask with
* an (purely informative) title. The returned value must be used in a later
* call to finishTask.
* When finishTask has been called for all tasks that have been started,
* the fnCallback will be fired.
* When a timeout is given and reached, the callback is called at that
* time, no matter whether all tasks have been finished or not.
*/
var SyncPoint = function (sName, fnCallback) {
var aTasks = [],
iOpenTasks = 0,
iFailures = 0;
this.startTask = function(sTitle) {
var iId = aTasks.length;
aTasks[iId] = { name : sTitle, finished : false };
iOpenTasks++;
return iId;
};
this.finishTask = function(iId, bSuccess) {
if ( !aTasks[iId] || aTasks[iId].finished ) {
throw new Error("trying to finish non existing or already finished task");
}
aTasks[iId].finished = true;
iOpenTasks--;
if ( bSuccess === false ) {
iFailures++;
}
if ( iOpenTasks === 0 ) {
Log.info("Sync point '" + sName + "' finished (tasks:" + aTasks.length + ", open:" + iOpenTasks + ", failures:" + iFailures + ")");
finish();
}
};
function finish() {
if ( fnCallback ) {
fnCallback(iOpenTasks, iFailures);
}
fnCallback = null;
}
Log.info("Sync point '" + sName + "' created");
};
/**
* @class Core Class of the SAP UI Library.
*
* This class boots the Core framework and makes it available for the application
* via method <code>sap.ui.getCore()</code>.
*
* Example:
* <pre>
*
* var oCore = sap.ui.getCore();
*
* </pre>
*
* With methods of the Core framework you can {@link #attachInit execute code} after the framework has been initialized.
* It provides access to the {@link #getConfiguration configuration} and exposes events that
* an application or a control can register to (e.g. {@link #event:localizationChanged localizationChanged},
* {@link #event:parseError parseError}, {@link #event:validationError validationError},
* {@link #event:formatError formatError}, {@link #event:validationSuccess validationSuccess}).
*
* Example:
* <pre>
*
* oCore.attachInit(function() {
* if ( oCore.getConfiguration().getRTL() ) {
* ...
* }
* });
*
* oCore.attachLocalizationChanged(function(oEvent) {
* ...
* });
*
* </pre>
*
* @extends sap.ui.base.Object
* @final
* @author SAP SE
* @version 1.87.1
* @alias sap.ui.core.Core
* @public
* @hideconstructor
*/
var Core = BaseObject.extend("sap.ui.core.Core", /** @lends sap.ui.core.Core.prototype */ {
constructor : function() {
var that = this,
METHOD = "sap.ui.core.Core";
// when a Core instance has been created before, don't create another one
if (sap.ui.getCore && sap.ui.getCore()) {
Log.error("Only the framework must create an instance of sap/ui/core/Core." +
" To get access to its functionality, use sap.ui.getCore().");
return sap.ui.getCore();
}
BaseObject.call(this);
_oEventProvider = new EventProvider();
// Generate all functions from EventProvider for backward compatibility
["attachEvent", "detachEvent", "getEventingParent"].forEach(function (sFuncName) {
Core.prototype[sFuncName] = _oEventProvider[sFuncName].bind(_oEventProvider);
});
/**
* Whether the core has been booted
* @private
*/
this.bBooted = false;
/**
* Whether the core has been initialized
* @private
*/
this.bInitialized = false;
/**
* Whether the DOM is ready (document.ready)
* @private
*/
this.bDomReady = false;
/**
* Available plugins in the order of registration.
* @private
*/
this.aPlugins = [];
/**
* Collection of loaded or adhoc created libraries, keyed by their name.
* @private
*/
this.mLibraries = {};
/**
* Already loaded resource bundles keyed by library and locale.
* @private
* @see sap.ui.core.Core.getLibraryResourceBundle
*/
this.mResourceBundles = {};
/**
* Currently created UIAreas keyed by their id.
* @private
* @todo FIXME how can a UI area ever be removed?
*/
this.mUIAreas = {};
/**
* Default model used for databinding
* @private
*/
this.oModels = {};
/**
* The event bus (initialized lazily)
* @private
*/
this.oEventBus = null;
Object.defineProperty(this, "mElements", {
get: function() {
Log.error("oCore.mElements was a private member and has been removed. Use one of the methods in sap.ui.core.Element.registry instead");
return Element.registry.all(); // this is a very costly snapshot!
},
configurable: false
});
/**
* Map of of created objects structured by their type which contains a map
* containing the created objects keyed by their type.
*
* Each object registers itself in its constructor and deregisters itself in its
* destroy method.
*
* @private
* @todo get rid of this collection as it represents a candidate for memory leaks
*/
this.mObjects = {
"template": {}
};
/**
* The instance of the root component (defined in the configuration {@link sap.ui.core.Configuration#getRootComponent})
* @private
*/
this.oRootComponent = null;
/**
* Ordered collection of initEvent listeners
* Moved here (before boot()) so that the libraries can be registered for lazy load!!
* @private
*/
this.aInitListeners = [];
/**
* Whether the legacy library has to be loaded.
* @private
*/
this.bInitLegacyLib = false;
/**
* The ID of a timer that will execute the next rendering.
*
* A non-falsy value indicates that a timer exists already, or at least that no
* new timer needs to be created as. During the boot phase, this member is set
* to the special value <code>this</code> which is non-falsy and which should never
* represent a valid timer ID (no chance of misinterpretation).
*/
this._sRerenderTimer = this;
/**
* Tasks that are called just before the rendering starts.
* @private
*/
this.aPrerenderingTasks = [];
Log.info("Creating Core",null,METHOD);
Measurement.start("coreComplete", "Core.js - complete");
Measurement.start("coreBoot", "Core.js - boot");
Measurement.start("coreInit", "Core.js - init");
/**
* Object holding the interpreted configuration
* Initialized from the global "sap-ui-config" object and from URL parameters
* @private
*/
this.oConfiguration = new Configuration(this);
// initialize frameOptions script (anti-clickjacking, etc.)
var oFrameOptionsConfig = this.oConfiguration["frameOptionsConfig"] || {};
oFrameOptionsConfig.mode = this.oConfiguration.getFrameOptions();
oFrameOptionsConfig.allowlistService = this.oConfiguration.getAllowlistService();
this.oFrameOptions = new FrameOptions(oFrameOptionsConfig);
// enable complex bindings if configured
if ( this.oConfiguration["bindingSyntax"] === "complex" ) {
ManagedObject.bindingParser = BindingParser.complexParser;
}
// switch bindingParser to designTime mode if configured
if (this.oConfiguration["xx-designMode"] == true ) {
BindingParser._keepBindingStrings = true;
}
// let Element and Component get friend access to the respective register/deregister methods
this._grantFriendAccess();
// handle modules
var aModules = this.oConfiguration.modules;
if ( this.oConfiguration.getDebug() ) {
// add debug module if configured
aModules.unshift("sap.ui.debug.DebugEnv");
}
// enforce the core library as the first loaded module
var i = aModules.indexOf("sap.ui.core.library");
if ( i != 0 ) {
if ( i > 0 ) {
aModules.splice(i,1);
}
aModules.unshift("sap.ui.core.library");
}
// enable LessSupport if specified in configuration
if (this.oConfiguration["xx-lesssupport"] && aModules.indexOf("sap.ui.core.plugin.LessSupport") == -1) {
Log.info("Including LessSupport into declared modules");
aModules.push("sap.ui.core.plugin.LessSupport");
}
// determine preload mode (e.g. resolve default or auto)
var sPreloadMode = this.oConfiguration.preload;
// if debug sources are requested, then the preload feature must be deactivated
if ( window["sap-ui-debug"] === true ) {
sPreloadMode = "";
}
// when the preload mode is 'auto', it will be set to 'async' or 'sync' for optimized sources
// depending on whether the ui5loader is configured async
if ( sPreloadMode === "auto" ) {
if (window["sap-ui-optimized"]) {
sPreloadMode = sap.ui.loader.config().async ? "async" : "sync";
} else {
sPreloadMode = "";
}
}
// write back the determined mode for later evaluation (e.g. loadLibrary)
this.oConfiguration.preload = sPreloadMode;
// This flag controls the core initialization flow.
// We can switch to async when an async preload is used or the ui5loader
// is in async mode. The latter might also happen for debug scenarios
// where no preload is used at all.
var bAsync = sPreloadMode === "async" || sap.ui.loader.config().async;
// evaluate configuration for library preload file types
this.oConfiguration['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;
}
});
Log.info("Declared modules: " + aModules, METHOD);
this._setupThemes();
this._setupContentDirection();
this._setupBrowser();
this._setupOS();
this._setupLang();
this._setupAnimation();
// create accessor to the Core API early so that initLibrary and others can use it
/**
* Retrieve the {@link sap.ui.core.Core SAPUI5 Core} instance for the current window.
* @returns {sap.ui.core.Core} the API of the current SAPUI5 Core instance.
* @public
* @function
* @ui5-global-only
*/
sap.ui.getCore = function() {
return that.getInterface();
};
// create the RenderManager so it can be used already
this.oRenderManager = new RenderManager();
// sync point 1 synchronizes document ready and rest of UI5 boot
var oSyncPoint1 = new SyncPoint("UI5 Document Ready", function(iOpenTasks, iFailures) {
that.bDomReady = true;
that.init();
});
var iDocumentReadyTask = oSyncPoint1.startTask("document.ready");
var iCoreBootTask = oSyncPoint1.startTask("preload and boot");
var fnContentLoadedCallback = function() {
Log.trace("document is ready");
oSyncPoint1.finishTask(iDocumentReadyTask);
document.removeEventListener("DOMContentLoaded", fnContentLoadedCallback);
};
// immediately execute callback if the ready state is already 'complete'
if (document.readyState !== "loading") {
fnContentLoadedCallback();
} else {
// task 1 is to wait for document.ready
document.addEventListener("DOMContentLoaded", fnContentLoadedCallback);
}
// sync point 2 synchronizes all library preloads and the end of the bootstrap script
var oSyncPoint2 = new SyncPoint("UI5 Core Preloads and Bootstrap Script", function(iOpenTasks, iFailures) {
Log.trace("Core loaded: open=" + iOpenTasks + ", failures=" + iFailures);
that._boot(bAsync, function() {
oSyncPoint1.finishTask(iCoreBootTask);
Measurement.end("coreBoot");
});
});
// a helper task to prevent the premature completion of oSyncPoint2
var iCreateTasksTask = oSyncPoint2.startTask("create sp2 tasks task");
// load the version info file in case of a custom theme to determine
// the distribution version which should be provided in library.css requests.
if (this.oConfiguration["versionedLibCss"]) {
var iVersionInfoTask = oSyncPoint2.startTask("load version info");
var fnCallback = function(oVersionInfo) {
if (oVersionInfo) {
Log.trace("Loaded \"sap-ui-version.json\".");
} else {
Log.error("Could not load \"sap-ui-version.json\".");
}
oSyncPoint2.finishTask(iVersionInfoTask);
};
// only use async mode if library preload is async
var vReturn = sap.ui.getVersionInfo({ async: bAsync, failOnError: false });
if (vReturn instanceof Promise) {
vReturn.then(fnCallback, function(oError) {
// this should only happen when there is a script error as "failOnError=false"
// prevents throwing a loading error (e.g. HTTP 404)
Log.error("Unexpected error when loading \"sap-ui-version.json\": " + oError);
oSyncPoint2.finishTask(iVersionInfoTask);
});
} else {
fnCallback(vReturn);
}
}
// when a boot task is configured, add it to syncpoint2
var fnCustomBootTask = this.oConfiguration["xx-bootTask"];
if ( fnCustomBootTask ) {
var iCustomBootTask = oSyncPoint2.startTask("custom boot task");
fnCustomBootTask( function(bSuccess) {
oSyncPoint2.finishTask(iCustomBootTask, typeof bSuccess === "undefined" || bSuccess === true );
});
}
this._polyfillFlexbox();
// when the bootstrap script has finished, it calls sap.ui.getCore().boot()
var iBootstrapScriptTask = oSyncPoint2.startTask("bootstrap script");
this.boot = function() {
if (this.bBooted) {
return;
}
this.bBooted = true;
oSyncPoint2.finishTask(iBootstrapScriptTask);
};
if ( sPreloadMode === "sync" || sPreloadMode === "async" ) {
// determine set of libraries
var aLibs = aModules.reduce(function(aResult, sModule) {
var iPos = sModule.search(/\.library$/);
if ( iPos >= 0 ) {
aResult.push(sModule.slice(0, iPos));
}
return aResult;
}, []);
var preloaded = this.loadLibraries(aLibs, {
async: bAsync,
preloadOnly: true
});
if ( bAsync ) {
var iPreloadLibrariesTask = oSyncPoint2.startTask("preload bootstrap libraries");
preloaded.then(function() {
oSyncPoint2.finishTask(iPreloadLibrariesTask);
}, function() {
oSyncPoint2.finishTask(iPreloadLibrariesTask, false);
});
}
}
// initializes the application cachebuster mechanism if configured
var aACBConfig = this.oConfiguration.getAppCacheBuster();
if (aACBConfig && aACBConfig.length > 0) {
var AppCacheBuster = sap.ui.requireSync('sap/ui/core/AppCacheBuster');
AppCacheBuster.boot(oSyncPoint2);
}
// Initialize support info stack
if (this.oConfiguration.getSupportMode() !== null) {
var iSupportInfoTask = oSyncPoint2.startTask("support info script");
var fnCallbackSupportBootstrapInfo = function(Support, Bootstrap) {
Support.initializeSupportMode(that.oConfiguration.getSupportMode(), bAsync);
Bootstrap.initSupportRules(that.oConfiguration.getSupportMode());
oSyncPoint2.finishTask(iSupportInfoTask);
};
if (bAsync) {
sap.ui.require(["sap/ui/core/support/Support", "sap/ui/support/Bootstrap"], fnCallbackSupportBootstrapInfo, function (oError) {
Log.error("Could not load support mode modules:", oError);
});
} else {
Log.warning("Synchronous loading of Support mode. Set preload configuration to 'async' or switch to asynchronous bootstrap to prevent these synchronous request.", "SyncXHR", null, function() {
return {
type: "SyncXHR",
name: "support-mode"
};
});
fnCallbackSupportBootstrapInfo(
sap.ui.requireSync("sap/ui/core/support/Support"),
sap.ui.requireSync("sap/ui/support/Bootstrap")
);
}
}
// Initialize test tools
if (this.oConfiguration.getTestRecorderMode() !== null) {
var iTestRecorderTask = oSyncPoint2.startTask("test recorder script");
var fnCallbackTestRecorder = function (Bootstrap) {
Bootstrap.init(that.oConfiguration.getTestRecorderMode());
oSyncPoint2.finishTask(iTestRecorderTask);
};
if (bAsync) {
sap.ui.require([
"sap/ui/testrecorder/Bootstrap"
], fnCallbackTestRecorder, function (oError) {
Log.error("Could not load test recorder:", oError);
});
} else {
Log.warning("Synchronous loading of Test recorder mode. Set preload configuration to 'async' or switch to asynchronous bootstrap to prevent these synchronous request.", "SyncXHR", null, function() {
return {
type: "SyncXHR",
name: "test-recorder-mode"
};
});
fnCallbackTestRecorder(
sap.ui.requireSync("sap/ui/testrecorder/Bootstrap")
);
}
}
oSyncPoint2.finishTask(iCreateTasksTask);
},
metadata : {
publicMethods: ["boot", "isInitialized","isThemeApplied","attachInitEvent","attachInit","getRenderManager","createRenderManager",
"getConfiguration", "setRoot", "createUIArea", "getUIArea", "getUIDirty", "getElementById",
"getCurrentFocusedControlId", "getControl", "getComponent", "getTemplate", "lock", "unlock","isLocked",
"attachEvent","detachEvent","applyChanges", "getEventBus",
"applyTheme","setThemeRoot","attachThemeChanged","detachThemeChanged","getStaticAreaRef",
"attachThemeScopingChanged","detachThemeScopingChanged","fireThemeScopingChanged",
"notifyContentDensityChanged",
"registerPlugin","unregisterPlugin","getLibraryResourceBundle", "byId",
"getLoadedLibraries", "loadLibrary", "loadLibraries", "initLibrary",
"includeLibraryTheme", "setModel", "getModel", "hasModel", "isMobile",
"attachControlEvent", "detachControlEvent", "attachIntervalTimer", "detachIntervalTimer",
"attachParseError", "detachParseError", "fireParseError",
"attachValidationError", "detachValidationError", "fireValidationError",
"attachFormatError", "detachFormatError", "fireFormatError",
"attachValidationSuccess", "detachValidationSuccess", "fireValidationSuccess",
"attachLocalizationChanged", "detachLocalizationChanged",
"attachLibraryChanged", "detachLibraryChanged",
"isStaticAreaRef", "createComponent", "getRootComponent", "getApplication",
"setMessageManager", "getMessageManager","byFieldGroupId",
"addPrerenderingTask"]
}
});
/**
* Map of event names and ids, that are provided by this class
* @private
*/
Core.M_EVENTS = {ControlEvent: "ControlEvent", UIUpdated: "UIUpdated", ThemeChanged: "ThemeChanged", ThemeScopingChanged: "themeScopingChanged", LocalizationChanged: "localizationChanged",
LibraryChanged : "libraryChanged",
ValidationError : "validationError", ParseError : "parseError", FormatError : "formatError", ValidationSuccess : "validationSuccess"};
// Id of the static UIArea
var STATIC_UIAREA_ID = "sap-ui-static";
// to protect against nested rendering we use an array of Steps instead of a single one
Core.aFnDone = [];
/**
* The core allows some friend components to register/deregister themselves
* @private
*/
Core.prototype._grantFriendAccess = function() {
var that = this;
// grant ElementMetadata "friend" access to Core for registration
ElementMetadata.prototype.register = function(oMetadata) {
that.registerElementClass(oMetadata);
};
// grant Element "friend" access to Core / FocusHandler to update the given elements focus info
Element._updateFocusInfo = function(oElement) {
if (that.oFocusHandler) {
that.oFocusHandler.updateControlFocusInfo(oElement);
}
};
};
/**
* Initializes the window "sap-ui-config" property, sets theme roots, initializes sTheme, sets theme CSS classes
* @private
*/
Core.prototype._setupThemes = function() {
var METHOD = "sap.ui.core.Core";
var oCfgData = window["sap-ui-config"];
// Configuration might have a themeRoot, if so integrate it in themeroots
if ( this.oConfiguration.themeRoot ) {
oCfgData = oCfgData || {};
oCfgData.themeroots = oCfgData.themeroots || {};
oCfgData.themeroots[this.oConfiguration.getTheme()] = this.oConfiguration.themeRoot;
}
if (oCfgData) {
// read themeRoots configuration
if (oCfgData.themeroots) {
for (var themeName in oCfgData.themeroots) {
var themeRoot = oCfgData.themeroots[themeName];
if (typeof themeRoot === "string") {
this.setThemeRoot(themeName, themeRoot);
} else {
for (var lib in themeRoot) {
if (lib.length > 0) {
this.setThemeRoot(themeName, [lib], themeRoot[lib]);
} else {
this.setThemeRoot(themeName, themeRoot[lib]);
}
}
}
}
}
}
// set CSS class for the theme name
this.sTheme = this.oConfiguration.getTheme();
document.documentElement.classList.add("sapUiTheme-" + this.sTheme);
Log.info("Declared theme " + this.sTheme,null,METHOD);
};
/**
* Set the document's dir property
* @private
*/
Core.prototype._setupContentDirection = function() {
var METHOD = "sap.ui.core.Core",
sDir = this.oConfiguration.getRTL() ? "rtl" : "ltr";
document.documentElement.setAttribute("dir", sDir); // webkit does not allow setting document.dir before the body exists
Log.info("Content direction set to '" + sDir + "'",null,METHOD);
};
/**
* Set the body's browser-related attributes.
* @private
*/
Core.prototype._setupBrowser = function() {
var METHOD = "sap.ui.core.Core";
//set the browser for CSS attribute selectors. do not move this to the onload function because sf and ie do not
//use the classes
var html = document.documentElement;
var b = Device.browser;
var id = b.name;
if (id) {
if (id === b.BROWSER.SAFARI && b.mobile) {
id = "m" + id;
}
id = id + (b.version === -1 ? "" : Math.floor(b.version));
html.dataset.sapUiBrowser = id;
Log.debug("Browser-Id: " + id, null, METHOD);
}
};
/**
* Set the body's OS-related attribute and CSS class
* @private
*/
Core.prototype._setupOS = function(html) {
var html = document.documentElement;
html.dataset.sapUiOs = Device.os.name + Device.os.versionStr;
var osCSS = null;
switch (Device.os.name) {
case Device.os.OS.IOS:
osCSS = "sap-ios";
break;
case Device.os.OS.ANDROID:
osCSS = "sap-android";
break;
case Device.os.OS.BLACKBERRY:
osCSS = "sap-bb";
break;
case Device.os.OS.WINDOWS_PHONE:
osCSS = "sap-winphone";
break;
}
if (osCSS) {
html.classList.add(osCSS);
}
};
/**
* Set the body's lang attribute and attach the localization change event
* @private
*/
Core.prototype._setupLang = function() {
var html = document.documentElement;
// append the lang info to the document (required for ARIA support)
var fnUpdateLangAttr = function() {
var oLocale = this.oConfiguration.getLocale();
oLocale ? html.setAttribute("lang", oLocale.toString()) : html.removeAttribute("lang");
};
fnUpdateLangAttr.call(this);
// listen to localization change event to update the lang info
this.attachLocalizationChanged(fnUpdateLangAttr, this);
};
/**
* Set the body's Animation-related attribute and configures jQuery animations accordingly.
* @private
*/
Core.prototype._setupAnimation = function() {
// We check for the existence of the configuration object, because the _setupAnimation function
// will first be called from the Configuration constructor within the Core constructor.
// During this first call, the configuration object is not yet set on the Core instance.
if (this.oConfiguration) {
var html = document.documentElement;
var bAnimation = this.oConfiguration.getAnimation();
html.dataset.sapUiAnimation = bAnimation ? "on" : "off";
if (typeof jQuery !== "undefined") {
jQuery.fx.off = !bAnimation;
}
var sAnimationMode = this.oConfiguration.getAnimationMode();
html.dataset.sapUiAnimationMode = sAnimationMode;
}
};
/**
* Initializes the jQuery.support.useFlexBoxPolyfill property
* @private
*/
Core.prototype._polyfillFlexbox = function() {
/**
* Whether the current browser needs a polyfill as a fallback for flex box support
* @type {boolean}
* @private
* @name jQuery.support.useFlexBoxPolyfill
* @since 1.12.0
* @deprecated since version 1.16.0
*
* For backwards compatibility we can't remove the deprecated flexbox polyfill.
* However, if the compatibility version is 1.16 or higher then the polyfill
* should not be used.
*/
jQuery.support.useFlexBoxPolyfill = false;
};
/**
* Boots the core and injects the necessary CSS and JavaScript files for the library.
* Applications shouldn't call this method. It is automatically called by the bootstrap scripts (e.g. sap-ui-core.js)
*
* @param {boolean} bAsync - Flag if modules should be loaded asynchronously
* @param {function} fnCallback - Callback after modules have been loaded
* @returns {undefined|Promise}
* @private
*/
Core.prototype._boot = function(bAsync, fnCallback) {
// if a list of preloaded library CSS is configured, request a merged CSS (if application did not already do it)
var aCSSLibs = this.oConfiguration['preloadLibCss'];
if (aCSSLibs && aCSSLibs.length > 0 && !aCSSLibs.appManaged) {
this.includeLibraryTheme("sap-ui-merged", undefined, "?l=" + aCSSLibs.join(","));
}
// load all modules now
if ( bAsync ) {
return this._requireModulesAsync().then(function() {
fnCallback();
});
}
Log.warning("Modules and libraries declared via bootstrap-configuration are loaded synchronously. Set preload configuration to" +
" 'async' or switch to asynchronous bootstrap to prevent these requests.", "SyncXHR", null, function() {
return {
type: "SyncXHR",
name: "legacy-module"
};
});
this.oConfiguration.modules.forEach( function(mod) {
var m = mod.match(/^(.*)\.library$/);
if ( m ) {
this.loadLibrary(m[1]);
} else {
// data-sap-ui-modules might contain legacy jquery.sap.* modules
sap.ui.requireSync( /^jquery\.sap\./.test(mod) ? mod : mod.replace(/\./g, "/"));
}
}.bind(this));
fnCallback();
};
Core.prototype._requireModulesAsync = function() {
var aLibs = [],
aModules = [];
this.oConfiguration.modules.forEach(function(sModule) {
var m = sModule.match(/^(.*)\.library$/);
if (m) {
aLibs.push(m[1]);
} else {
// data-sap-ui-modules might contain legacy jquery.sap.* modules
aModules.push(/^jquery\.sap\./.test(sModule) ? sModule : sModule.replace(/\./g, "/"));
}
});
// TODO: require libs and modules in parallel or define a sequence?
return Promise.all([
this.loadLibraries(aLibs),
new Promise(function(resolve) {
sap.ui.require(aModules, function() {
resolve(Array.prototype.slice.call(arguments));
});
})
]);
};
/**
* Applies the theme with the given name (by loading the respective style sheets, which does not disrupt the application).
*
* By default, the theme files are expected to be located at path relative to the respective control library ([libraryLocation]/themes/[themeName]).
* Different locations can be configured by using the method setThemePath() or by using the second parameter "sThemeBaseUrl" of applyTheme().
* Usage of this second parameter is a shorthand for setThemePath and internally calls setThemePath, so the theme location is then known.
*
* sThemeBaseUrl is a single URL to specify the default location of all theme files. This URL is the base folder below which the control library folders
* are located. E.g. if the CSS files are not located relative to the root location of UI5, but instead they are at locations like
* http://my.server/myapp/resources/sap/ui/core/themes/my_theme/library.css
* then the URL that needs to be given is:
* http://my.server/myapp/resources
* All theme resources are then loaded from below this folder - except if for a certain library a different location has been registered.
*
* If the theme resources are not all either below this base location or with their respective libraries, then setThemePath must be
* used to configure individual locations.
*
* @param {string} sThemeName the name of the theme to be loaded
* @param {string} [sThemeBaseUrl] the (optional) base location of the theme
* @public
*/
Core.prototype.applyTheme = function(sThemeName, sThemeBaseUrl) {
assert(typeof sThemeName === "string", "sThemeName must be a string");
assert(typeof sThemeBaseUrl === "string" || typeof sThemeBaseUrl === "undefined", "sThemeBaseUrl must be a string or undefined");
sThemeName = this.oConfiguration._normalizeTheme(sThemeName, sThemeBaseUrl);
if (sThemeBaseUrl) {
this.setThemeRoot(sThemeName, sThemeBaseUrl);
}
// only apply the theme if it is different from the active one
if (sThemeName && this.sTheme != sThemeName) {
var sCurrentTheme = this.sTheme;
var html = document.documentElement;
this._updateThemeUrls(sThemeName, /* bSuppressFOUC */ true);
this.sTheme = sThemeName;
this.oConfiguration._setTheme(sThemeName);
// modify the <html> tag's CSS class with the theme name
html.classList.remove("sapUiTheme-" + sCurrentTheme);
html.classList.add("sapUiTheme-" + sThemeName);
// notify the listeners
if ( this.oThemeCheck ) {
this.oThemeCheck.fireThemeChangedEvent(false);
}
}
};
// this function is also used by "sap.ui.core.ThemeCheck" to load a fallback theme for a single library
Core.prototype._updateThemeUrl = function(oLink, sThemeName, bSuppressFOUC) {
var sLibName,
iQueryIndex = oLink.href.search(/[?#]/),
sLibFileName,
sQuery,
sStandardLibFilePrefix = "library",
sRTL = this.oConfiguration.getRTL() ? "-RTL" : "",
sHref,
pos;
// derive lib name from id via regex
var mLinkId = /^sap-ui-theme(?:skeleton)?-(.*)$/i.exec(oLink.id);
if (Array.isArray(mLinkId)) {
sLibName = mLinkId[1];
} else {
// fallback to legacy logic
sLibName = oLink.id.slice(13); // length of "sap-ui-theme-"
}
if (iQueryIndex > -1) {
// Split href on query and/or fragment to check for the standard lib file prefix
sLibFileName = oLink.href.substring(0, iQueryIndex);
sQuery = oLink.href.substring(iQueryIndex);
} else {
sLibFileName = oLink.href;
sQuery = "";
}
// Get basename of stylesheet (e.g. "library.css")
sLibFileName = sLibFileName.substring(sLibFileName.lastIndexOf("/") + 1);
// handle 'variants'
if ((pos = sLibName.indexOf("-[")) > 0) { // assumes that "-[" does not occur as part of a library name
sStandardLibFilePrefix += sLibName.slice(pos + 2, -1); // 2=length of "-]"
sLibName = sLibName.slice(0, pos);
}
// try to distinguish "our" library css from custom css included with the ':' notation in includeLibraryTheme
if ( sLibFileName === (sStandardLibFilePrefix + ".css") || sLibFileName === (sStandardLibFilePrefix + "-RTL.css") ) {
sLibFileName = sStandardLibFilePrefix + sRTL + ".css";
}
sHref = this._getThemePath(sLibName, sThemeName) + sLibFileName + sQuery;
if ( sHref != oLink.href ) {
// sap/ui/dom/includeStylesheet has a special FOUC handling
// which is activated once the attribute data-sap-ui-foucmarker is
// present on the link to be replaced (usage of the Promise
// API is not sufficient as it will change the sync behavior)
if (bSuppressFOUC) {
oLink.dataset.sapUiFoucmarker = oLink.id;
}
// Replace the current <link> tag with a new one.
// Changing "oLink.href" would also trigger loading the new stylesheet but
// the load/error handlers would not get called which causes issues with the ThemeCheck
// as the "data-sap-ui-ready" attribute won't be set.
includeStylesheet(sHref, oLink.id);
}
};
// modify style sheet URLs to point to the given theme, using the current RTL mode
Core.prototype._updateThemeUrls = function(sThemeName, bSuppressFOUC) {
// select "our" stylesheets
var oQueryResult = document.querySelectorAll("link[id^=sap-ui-theme-],link[id^=sap-ui-themeskeleton-]");
Array.prototype.forEach.call(oQueryResult, function(oHTMLElement) {
this._updateThemeUrl(oHTMLElement, sThemeName, bSuppressFOUC);
}.bind(this));
};
/**
* Makes sure to register the correct module path for the given library and theme
* in case a themeRoot has been defined.
*
* @param {string} sLibName Library name (dot separated)
* @param {string} sThemeName Theme name
* @private
*/
Core.prototype._ensureThemeRoot = function(sLibName, sThemeName) {
if (this._mThemeRoots) {
var path = this._mThemeRoots[sThemeName + " " + sLibName] || this._mThemeRoots[sThemeName];
// check whether for this combination (theme+lib) a URL is registered or for this theme a default location is registered
if (path) {
path = path + sLibName.replace(/\./g, "/") + "/themes/" + sThemeName + "/";
registerModulePath(sLibName + ".themes." + sThemeName, path);
}
}
};
/**
* Returns the URL of the folder in which the CSS file for the given theme and the given library is located.
*
* @param {string} sLibName Library name (dot separated)
* @param {string} sThemeName Theme name
* @returns {string} module path URL (ends with a slash)
* @private
*/
Core.prototype._getThemePath = function(sLibName, sThemeName) {
// make sure to register correct theme module path in case themeRoots are defined
this._ensureThemeRoot(sLibName, sThemeName);
// use the library location as theme location
return getModulePath(sLibName + ".themes." + sThemeName, "/");
};
/**
* Defines the root directory from below which UI5 should load the theme with the given name.
* Optionally allows restricting the setting to parts of a theme covering specific control libraries.
*
* Example:
* <pre>
* sap.ui.getCore().setThemeRoot("my_theme", "https://mythemeserver.com/allThemes");
* sap.ui.getCore().applyTheme("my_theme");
* </pre>
*
* will cause the following file to be loaded (assuming that the bootstrap is configured to load
* libraries <code>sap.m</code> and <code>sap.ui.layout</code>):
* <pre>
* https://mythemeserver.com/allThemes/sap/ui/core/themes/my_theme/library.css
* https://mythemeserver.com/allThemes/sap/ui/layout/themes/my_theme/library.css
* https://mythemeserver.com/allThemes/sap/m/themes/my_theme/library.css
* </pre>
*
* If parts of the theme are at different locations (e.g. because you provide a standard theme
* like "sap_belize" for a custom control library and this self-made part of the standard theme is at a
* different location than the UI5 resources), you can also specify for which control libraries the setting
* should be used, by giving an array with the names of the respective control libraries as second parameter:
* <pre>
* sap.ui.getCore().setThemeRoot("sap_belize", ["my.own.library"], "https://mythemeserver.com/allThemes");
* </pre>
*
* This will cause the Belize theme to be loaded from the UI5 location for all standard libraries.
* Resources for styling the <code>my.own.library</code> controls will be loaded from the configured
* location:
* <pre>
* https://openui5.hana.ondemand.com/resources/sap/ui/core/themes/sap_belize/library.css
* https://openui5.hana.ondemand.com/resources/sap/ui/layout/themes/sap_belize/library.css
* https://openui5.hana.ondemand.com/resources/sap/m/themes/sap_belize/library.css
* https://mythemeserver.com/allThemes/my/own/library/themes/sap_belize/library.css
* </pre>
*
* If the custom theme should be loaded initially (via bootstrap attribute), the <code>themeRoots</code>
* property of the <code>window["sap-ui-config"]</code> object must be used instead of calling
* <code>sap.ui.getCore().setThemeRoot(...)</code> in order to configure the theme location early enough.
*
* @param {string} sThemeName Name of the theme for which to configure the location
* @param {string[]} [aLibraryNames] Optional library names to which the configuration should be restricted
* @param {string} sThemeBaseUrl Base URL below which the CSS file(s) will be loaded from
* @param {boolean} [bForceUpdate=false] Force updating URLs of currently loaded theme
* @return {this} the Core, to allow method chaining
* @since 1.10
* @public
*/
Core.prototype.setThemeRoot = function(sThemeName, aLibraryNames, sThemeBaseUrl, bForceUpdate) {
assert(typeof sThemeName === "string", "sThemeName must be a string");
assert((Array.isArray(aLibraryNames) && typeof sThemeBaseUrl === "string") || (typeof aLibraryNames === "string" && sThemeBaseUrl === undefined), "either the second parameter must be a string (and the third is undefined), or it must be an array and the third parameter is a string");
if (!this._mThemeRoots) {
this._mThemeRoots = {};
}
// normalize parameters
if (typeof aLibraryNames === "string") {
bForceUpdate = sThemeBaseUrl;
sThemeBaseUrl = aLibraryNames;
aLibraryNames = undefined;
}
sThemeBaseUrl = sThemeBaseUrl + (sThemeBaseUrl.slice( -1) == "/" ? "" : "/");
if (aLibraryNames) {
// registration of URL for several libraries
for (var i = 0; i < aLibraryNames.length; i++) {
var lib = aLibraryNames[i];
this._mThemeRoots[sThemeName + " " + lib] = sThemeBaseUrl;
}
} else {
// registration of theme default base URL
this._mThemeRoots[sThemeName] = sThemeBaseUrl;
}
// Update theme urls when theme roots of currently loaded theme have changed
if (bForceUpdate && sThemeName === this.sTheme) {
this._updateThemeUrls(this.sTheme);
}
return this;
};
/**
* Initializes the Core after the initial page was loaded
* @private
*/
Core.prototype.init = function() {
if (this.bInitialized) {
return;
}
var METHOD = "sap.ui.core.Core.init()";
// ensure that the core is booted now (e.g. loadAllMode)
this.boot();
Log.info("Initializing",null,METHOD);
this.oFocusHandler = new FocusHandler(document.body, this);
this.oRenderManager._setFocusHandler(this.oFocusHandler); //Let the RenderManager know the FocusHandler
this.oResizeHandler = new ResizeHandler(this);
this.oThemeCheck = new ThemeCheck(this);
Log.info("Initialized",null,METHOD);
Measurement.end("coreInit");
// start the plugins
Log.info("Starting Plugins",null,METHOD);
this.startPlugins();
Log.info("Plugins started",null,METHOD);
this._createUIAreas();
this._setBodyAccessibilityRole();
this.oThemeCheck.fireThemeChangedEvent(true);
var sWaitForTheme = this.oConfiguration['xx-waitForTheme'];
if ( this.isThemeApplied() || !sWaitForTheme ) {
Core.aFnDone.push(Interaction.notifyAsyncStep());
this._executeInitialization();
this.renderPendingUIUpdates("during Core init"); // directly render without setTimeout, so rendering is guaranteed to be finished when init() ends
Measurement.end("coreComplete");
} else if (sWaitForTheme === "rendering") {
Core.aFnDone.push(Interaction.notifyAsyncStep());
this._executeInitialization();
oRenderLog.debug("delay initial rendering until theme has been loaded");
_oEventProvider.attachEventOnce(Core.M_EVENTS.ThemeChanged, function() {
setTimeout(
this.renderPendingUIUpdates.bind(this, "after theme has been loaded"),
Device.browser.safari ? 50 : 0
);
}, this);
Measurement.end("coreComplete");
} else if (sWaitForTheme === "init") {
oRenderLog.debug("delay init event and initial rendering until theme has been loaded");
Core.aFnDone.push(Interaction.notifyAsyncStep());
_oEventProvider.attachEventOnce(Core.M_EVENTS.ThemeChanged, function() {
this._executeInitialization();
setTimeout(
this.renderPendingUIUpdates.bind(this, "after theme has been loaded"),
Device.browser.safari ? 50 : 0
);
Measurement.end("coreComplete");
}, this);
}
};
Core.prototype._createUIAreas = function() {
var oConfig = this.oConfiguration;
// create any pre-configured UIAreas
// if ( oConfig.areas && oConfig.areas.length > 0 ) {
if ( oConfig.areas ) {
// Log.warning("deprecated config option '(data-sap-ui-)areas' used.");
for (var i = 0, l = oConfig.areas.length; i < l; i++) {
this.createUIArea(oConfig.areas[i]);
}
oConfig.areas = undefined;
}
};
Core.prototype._executeOnInit = function() {
var vOnInit = this.oConfiguration.onInit;
// execute a configured init hook
if ( vOnInit ) {
if ( typeof vOnInit === "function" ) {
vOnInit();
} else if (typeof vOnInit === "string") {
// determine onInit being a module name prefixed via module or a global name
var aResult = /^module\:((?:[_$.\-a-zA-Z0-9]+\/)*[_$.\-a-zA-Z0-9]+)$/.exec(vOnInit);
if (aResult && aResult[1]) {
// ensure that the require is done async and the Core is finally booted!
setTimeout(sap.ui.require.bind(sap.ui, [aResult[1]]), 0);
} else {
// lookup the name specified in onInit and try to call the function directly
var fn = ObjectPath.get(vOnInit);
if (typeof fn === "function") {
fn();
} else {
Log.warning("[Deprecated] Do not use inline JavaScript code with the oninit attribute."
+ " Use the module:... syntax or the name of a global function");
/*
* In contrast to eval(), window.eval() executes the given string
* in the global context, without closure variables.
* See http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2
*/
window.eval(vOnInit); // csp-ignore-legacy-api
}
}
}
this.oConfiguration.onInit = undefined;
}
};
Core.prototype._setupRootComponent = function() {
var METHOD = "sap.ui.core.Core.init()",
oConfig = this.oConfiguration;
// load the root component
var sRootComponent = oConfig.getRootComponent();
if (sRootComponent) {
Log.info("Loading Root Component: " + sRootComponent,null,METHOD);
var oComponent = sap.ui.component({
name: sRootComponent
});
this.oRootComponent = oComponent;
var sRootNode = oConfig["xx-rootComponentNode"];
if (sRootNode && oComponent.isA('sap.ui.core.UIComponent')) {
var oRootNode = document.getElementById(sRootNode);
if (oRootNode) {
Log.info("Creating ComponentContainer for Root Component: " + sRootComponent,null,METHOD);
var ComponentContainer = sap.ui.requireSync('sap/ui/core/ComponentContainer'),
oContainer = new ComponentContainer({
component: oComponent,
propagateModel: true /* TODO: is this a configuration or do this by default? right now it behaves like the application */
});
oContainer.placeAt(oRootNode);
}
}
} else {
// DEPRECATED LEGACY CODE: load the application (TODO: remove when Application is removed!)
var sApplication = oConfig.getApplication();
if (sApplication) {
Log.warning("The configuration 'application' is deprecated. Please use the configuration 'component' instead! " +
"Please migrate from sap.ui.app.Application to sap.ui.core.Component.", "SyncXHR", null, function () {
return {
type: "Deprecation",
name: "sap.ui.core"
};
});
Log.info("Loading Application: " + sApplication,null,METHOD);
sap.ui.requireSync(sApplication.replace(/\./g, "/"));
var oClass = ObjectPath.get(sApplication);
assert(oClass !== undefined, "The specified application \"" + sApplication + "\" could not be found!");
var oApplication = new oClass();
assert(BaseObject.isA(oApplication, 'sap.ui.app.Application'), "The specified application \"" + sApplication + "\" must be an instance of sap.ui.app.Application!");
}
}
};
Core.prototype._setBodyAccessibilityRole = function() {
var oConfig = this.oConfiguration,
body = document.body;
//Add ARIA role 'application'
if (oConfig.getAccessibility() && oConfig.getAutoAriaBodyRole() && !body.getAttribute("role")) {
body.setAttribute("role", "application");
}
};
Core.prototype._executeInitListeners = function() {
var METHOD = "sap.ui.core.Core.init()";
// make sure that we have no concurrent modifications on the init listeners
var aCallbacks = this.aInitListeners;
// reset the init listener so that we are aware the listeners are already
// executed and the initialization phase is over / follow up registration
// would then immediately call the init event handler
this.aInitListeners = undefined;
// execute registered init event handlers
if (aCallbacks && aCallbacks.length > 0) {
// execute the callbacks
Log.info("Fire Loaded Event",null,METHOD);
aCallbacks.forEach(function(fn) {
fn();
});
}
};
Core.prototype._executeInitialization = function() {
if (this.bInitialized) {
return;
}
this.bInitialized = true;
this._executeOnInit();
this._setupRootComponent();
this._executeInitListeners();
};
/**
* Returns true if the Core has already been initialized. This means that instances
* of RenderManager etc. do already exist and the init event has already been fired
* (and will not be fired again).
*
* @return {boolean} whether the Core has already been initialized
* @public
*/
Core.prototype.isInitialized = function () {
return this.bInitialized;
};
/**
* Returns true, if the styles of the current theme are already applied, false otherwise.
*
* This function must not be used before the init event of the Core.
* If the styles are not yet applied a theme changed event will follow when the styles will be applied.
*
* @return {boolean} whether the styles of the current theme are already applied
* @public
*/
Core.prototype.isThemeApplied = function () {
return ThemeCheck.themeLoaded;
};
/**
* Registers a given function that is executed after the framework has been initialized.
*
* The method is executed only once and only if the framework has not been initialized already.
* This could be checked by calling {@link #isInitialized}, but in most cases it is more convenient to
* use {@link #attachInit} instead. This guarantees that the given function is executed exactly once,
* independent of the state of the framework.
*
* @param {function} fnFunction Function that is called after initialization of the framework
* @public
* @deprecated since 1.13.2 Register with the more convenient {@link #attachInit} function instead
*/
Core.prototype.attachInitEvent = function (fnFunction) {
assert(typeof fnFunction === "function", "fnFunction must be a function");
if (this.aInitListeners) {
this.aInitListeners.push(fnFunction);
}
};
/**
* Registers a given function that is executed after the framework has been initialized.
*
* The given function will either be called as soon as the framework has been initialized
* or, if it has been initialized already, it will be called immediately.
*
* More information about the initialization process and the steps it consists of can be found
* in the documentation topic "{@link topic:91f2c9076f4d1014b6dd926db0e91070 Initialization Process}".
*
* @param {functio