@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,527 lines (1,411 loc) • 64.5 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/**
* Device and Feature Detection API: Provides information about the used browser / device and cross platform support for certain events
* like media queries, orientation change or resizing.
*
* This API is independent from any other part of the UI5 framework. This allows it to be loaded beforehand, if it is needed, to create the UI5 bootstrap
* dynamically depending on the capabilities of the browser or device.
*
* @version 1.111.5
* @namespace
* @name sap.ui.Device
* @public
*/
/*global console */
//Introduce namespace if it does not yet exist
if (typeof window.sap !== "object" && typeof window.sap !== "function") {
window.sap = {};
}
if (typeof window.sap.ui !== "object") {
window.sap.ui = {};
}
(function() {
"use strict";
//Skip initialization if API is already available
if (typeof window.sap.ui.Device === "object" || typeof window.sap.ui.Device === "function") {
var apiVersion = "1.111.5";
window.sap.ui.Device._checkAPIVersion(apiVersion);
return;
}
var Device = {};
////-------------------------- Logging -------------------------------------
/* since we cannot use the logging from sap/base/Log.js, we need to come up with a separate
* solution for the device API
*/
var FATAL = 0,
ERROR = 1,
WARNING = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5;
var DeviceLogger = function() {
// helper function for date formatting
function pad0(i, w) {
return ("000" + String(i)).slice(-w);
}
this.defaultComponent = 'DEVICE';
this.sWindowName = (window.top == window) ? "" : "[" + window.location.pathname.split('/').slice(-1)[0] + "] ";
// Creates a new log entry depending on its level and component.
this.log = function(iLevel, sMessage, sComponent) {
sComponent = sComponent || this.defaultComponent || '';
var oNow = new Date(),
oLogEntry = {
time: pad0(oNow.getHours(), 2) + ":" + pad0(oNow.getMinutes(), 2) + ":" + pad0(oNow.getSeconds(), 2),
date: pad0(oNow.getFullYear(), 4) + "-" + pad0(oNow.getMonth() + 1, 2) + "-" + pad0(oNow.getDate(), 2),
timestamp: oNow.getTime(),
level: iLevel,
message: sMessage || "",
component: sComponent || ""
};
/*eslint-disable no-console */
if (window.console) { // in FF, console might not exist; it might even disappear
var logText = oLogEntry.date + " " + oLogEntry.time + " " + this.sWindowName + oLogEntry.message + " - " + oLogEntry.component;
switch (iLevel) {
case FATAL:
case ERROR:
console.error(logText);
break;
case WARNING:
console.warn(logText);
break;
case INFO:
console.info ? console.info(logText) : console.log(logText);
break; // info not available in iOS simulator
case DEBUG:
console.debug(logText);
break;
case TRACE:
console.trace(logText);
break;
}
}
/*eslint-enable no-console */
return oLogEntry;
};
};
// instantiate new logger
var oLogger = new DeviceLogger();
oLogger.log(INFO, "Device API logging initialized");
//******** Version Check ********
//Only used internal to make clear when Device API is loaded in wrong version
Device._checkAPIVersion = function(sVersion) {
var v = "1.111.5";
if (v != sVersion) {
oLogger.log(WARNING, "Device API version differs: " + v + " <-> " + sVersion);
}
};
//******** Event Management ******** (see Event Provider)
var mEventRegistry = {};
function attachEvent(sEventId, fnFunction, oListener) {
if (!mEventRegistry[sEventId]) {
mEventRegistry[sEventId] = [];
}
mEventRegistry[sEventId].push({
oListener: oListener,
fFunction: fnFunction
});
}
function detachEvent(sEventId, fnFunction, oListener) {
var aEventListeners = mEventRegistry[sEventId];
if (!aEventListeners) {
return this;
}
for (var i = 0, iL = aEventListeners.length; i < iL; i++) {
if (aEventListeners[i].fFunction === fnFunction && aEventListeners[i].oListener === oListener) {
aEventListeners.splice(i, 1);
break;
}
}
if (aEventListeners.length == 0) {
delete mEventRegistry[sEventId];
}
}
function fireEvent(sEventId, mParameters) {
var aEventListeners = mEventRegistry[sEventId];
var oInfo;
if (aEventListeners) {
aEventListeners = aEventListeners.slice();
for (var i = 0, iL = aEventListeners.length; i < iL; i++) {
oInfo = aEventListeners[i];
oInfo.fFunction.call(oInfo.oListener || window, mParameters);
}
}
}
var oReducedNavigator;
var setDefaultNavigator = function () {
oReducedNavigator = {
userAgent: window.navigator.userAgent,
platform: window.navigator.platform
};
// Only add property standalone in case navigator has this property
if (window.navigator.hasOwnProperty("standalone")) {
oReducedNavigator.standalone = window.navigator.standalone;
}
};
setDefaultNavigator();
//******** OS Detection ********
/**
* Contains information about the operating system of the Device.
*
* @namespace
* @name sap.ui.Device.os
* @public
*/
/**
* Enumeration containing the names of known operating systems.
*
* @namespace
* @name sap.ui.Device.os.OS
* @public
*/
/**
* The name of the operating system.
*
* @see sap.ui.Device.os.OS
* @name sap.ui.Device.os.name
* @type string
* @public
*/
/**
* The version of the operating system as <code>string</code>.
*
* Might be empty if no version can reliably be determined.
*
* @name sap.ui.Device.os.versionStr
* @type string
* @public
*/
/**
* The version of the operating system as <code>float</code>.
*
* Might be <code>-1</code> if no version can reliably be determined.
*
* @name sap.ui.Device.os.version
* @type float
* @public
*/
/**
* If this flag is set to <code>true</code>, a Windows operating system is used.
*
* @name sap.ui.Device.os.windows
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, a Linux operating system is used.
*
* @name sap.ui.Device.os.linux
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, a Mac operating system is used.
*
* <b>Note:</b> An iPad using Safari browser, which is requesting desktop sites, is also recognized as Macintosh.
*
* @name sap.ui.Device.os.macintosh
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, an iOS operating system is used.
*
* @name sap.ui.Device.os.ios
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, an Android operating system is used.
*
* @name sap.ui.Device.os.android
* @type boolean
* @public
*/
/**
* Windows operating system name.
*
* @see sap.ui.Device.os.name
* @name sap.ui.Device.os.OS.WINDOWS
* @public
*/
/**
* MAC operating system name.
*
* @see sap.ui.Device.os.name
* @name sap.ui.Device.os.OS.MACINTOSH
* @public
*/
/**
* Linux operating system name.
*
* @see sap.ui.Device.os.name
* @name sap.ui.Device.os.OS.LINUX
* @public
*/
/**
* iOS operating system name.
*
* @see sap.ui.Device.os.name
* @name sap.ui.Device.os.OS.IOS
* @public
*/
/**
* Android operating system name.
*
* @see sap.ui.Device.os.name
* @name sap.ui.Device.os.OS.ANDROID
* @public
*/
var OS = {
"WINDOWS": "win",
"MACINTOSH": "mac",
"LINUX": "linux",
"IOS": "iOS",
"ANDROID": "Android"
};
function getOS() { // may return null!!
var userAgent = oReducedNavigator.userAgent;
var rPlatform, // regular expression for platform
aMatches;
function getDesktopOS() {
var sPlatform = oReducedNavigator.platform;
if (sPlatform.indexOf("Win") != -1) {
// userAgent in windows 7 contains: windows NT 6.1
// userAgent in windows 8 contains: windows NT 6.2 or higher
// userAgent since windows 10: Windows NT 10[...]
var rVersion = /Windows NT (\d+).(\d)/i;
var uaResult = userAgent.match(rVersion);
var sVersionStr = "";
// Using Lighthouse tool within chrome on windows does not provide a valid userAgent
// navigator.platform is 'Win' but navigator.userAgent indicates macOS
if (uaResult) {
if (uaResult[1] == "6") {
if (uaResult[2] == 1) {
sVersionStr = "7";
} else if (uaResult[2] > 1) {
sVersionStr = "8";
}
} else {
sVersionStr = uaResult[1];
}
}
return {
"name": OS.WINDOWS,
"versionStr": sVersionStr
};
} else if (sPlatform.indexOf("Mac") != -1) {
return {
"name": OS.MACINTOSH,
"versionStr": ""
};
} else if (sPlatform.indexOf("Linux") != -1) {
return {
"name": OS.LINUX,
"versionStr": ""
};
}
oLogger.log(INFO, "OS detection returned no result");
return null;
}
rPlatform = /\(([a-zA-Z ]+);\s(?:[U]?[;]?)([\D]+)((?:[\d._]*))(?:.*[\)][^\d]*)([\d.]*)\s/;
aMatches = userAgent.match(rPlatform);
if (aMatches) {
var rAppleDevices = /iPhone|iPad|iPod/;
if (aMatches[0].match(rAppleDevices)) {
aMatches[3] = aMatches[3].replace(/_/g, ".");
//result[1] contains info of devices
return ({
"name": OS.IOS,
"versionStr": aMatches[3]
});
} else if (aMatches[2].match(/Android/)) {
aMatches[2] = aMatches[2].replace(/\s/g, "");
return ({
"name": OS.ANDROID,
"versionStr": aMatches[3]
});
}
}
//Firefox on Android
rPlatform = /\((Android)[\s]?([\d][.\d]*)?;.*Firefox\/[\d][.\d]*/;
aMatches = userAgent.match(rPlatform);
if (aMatches) {
return ({
"name": OS.ANDROID,
"versionStr": aMatches.length == 3 ? aMatches[2] : ""
});
}
// Desktop
return getDesktopOS();
}
function setOS() {
Device.os = getOS() || {};
Device.os.OS = OS;
Device.os.version = Device.os.versionStr ? parseFloat(Device.os.versionStr) : -1;
if (Device.os.name) {
for (var name in OS) {
if (OS[name] === Device.os.name) {
Device.os[name.toLowerCase()] = true;
}
}
}
}
setOS();
//******** Browser Detection ********
/**
* Contains information about the used browser.
*
* @namespace
* @name sap.ui.Device.browser
* @public
*/
/**
* Enumeration containing the names of known browsers.
*
* @namespace
* @name sap.ui.Device.browser.BROWSER
* @public
*/
/**
* The name of the browser.
*
* @see sap.ui.Device.browser.BROWSER
* @name sap.ui.Device.browser.name
* @type string
* @public
*/
/**
* The version of the browser as <code>string</code>.
*
* Might be empty if no version can be determined.
*
* @name sap.ui.Device.browser.versionStr
* @type string
* @public
*/
/**
* The version of the browser as <code>float</code>.
*
* Might be <code>-1</code> if no version can be determined.
*
* @name sap.ui.Device.browser.version
* @type float
* @public
*/
/**
* If this flag is set to <code>true</code>, the mobile variant of the browser is used or
* a tablet or phone device is detected.
*
* <b>Note:</b> This information might not be available for all browsers.
* <b>Note:</b> The flag is also set to <code>true</code> for any touch device,
* including laptops with touchscreen monitor.
* For more information, see the documentation for {@link sap.ui.Device.system.combi} devices.
*
* @name sap.ui.Device.browser.mobile
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the Mozilla Firefox browser is used.
*
* @name sap.ui.Device.browser.firefox
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, a browser that is based on the Chromium browser
* project is used, such as the Google Chrome browser or the Microsoft Edge (Chromium) browser.
*
* @name sap.ui.Device.browser.chrome
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the Apple Safari browser is used.
*
* <b>Note:</b>
* This flag is also <code>true</code> when the standalone (fullscreen) mode or webview is used on iOS devices.
* Please also note the flags {@link sap.ui.Device.browser.fullscreen} and {@link sap.ui.Device.browser.webview}.
*
* @name sap.ui.Device.browser.safari
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, a browser featuring a Webkit engine is used.
*
* <b>Note:</b>
* This flag is also <code>true</code> when the used browser was based on the Webkit engine, but
* uses another rendering engine in the meantime. For example the Chrome browser started from version 28 and above
* uses the Blink rendering engine.
*
* @name sap.ui.Device.browser.webkit
* @type boolean
* @since 1.20.0
* @public
*/
/**
* If this flag is set to <code>true</code>, a browser featuring a Blink rendering engine is used.
*
* @name sap.ui.Device.browser.blink
* @type boolean
* @since 1.56.0
* @public
*/
/**
* If this flag is set to <code>true</code>, the Safari browser runs in standalone fullscreen mode on iOS.
*
* <b>Note:</b> This flag is only available if the Safari browser was detected. There might be slight
* differences in behavior and detection, e.g. regarding the availability of {@link sap.ui.Device.browser.version}.
*
* @name sap.ui.Device.browser.fullscreen
* @type boolean
* @since 1.31.0
* @public
*/
/**
* If this flag is set to <code>true</code>, the Safari browser runs in webview mode on iOS.
*
* <b>Note:</b> Since iOS 11 it is no longer reliably possible to detect whether an application runs in <code>webview</code>.
* The flag is <code>true</code> if the browser's user agent contains 'SAPFioriClient'. Applications
* using WKWebView have the possibility to customize the user agent, and to explicitly add this information.
*
* @name sap.ui.Device.browser.webview
* @deprecated as of version 1.98.
* @type boolean
* @since 1.31.0
* @public
*/
/**
* The version of the used Webkit engine, if available.
*
* @see sap.ui.Device.browser.webkit
* @name sap.ui.Device.browser.webkitVersion
* @type string
* @since 1.20.0
* @private
*/
/**
* If this flag is set to <code>true</code>, a browser featuring a Mozilla engine is used.
*
* @name sap.ui.Device.browser.mozilla
* @type boolean
* @since 1.20.0
* @public
*/
/**
* Firefox browser name.
*
* @see sap.ui.Device.browser.name
* @name sap.ui.Device.browser.BROWSER.FIREFOX
* @public
*/
/**
* Chrome browser name, used for Google Chrome browser and Microsoft Edge (Chromium) browser.
*
* @see sap.ui.Device.browser.name
* @name sap.ui.Device.browser.BROWSER.CHROME
* @public
*/
/**
* Safari browser name.
*
* @see sap.ui.Device.browser.name
* @name sap.ui.Device.browser.BROWSER.SAFARI
* @public
*/
/**
* Android stock browser name.
*
* @see sap.ui.Device.browser.name
* @name sap.ui.Device.browser.BROWSER.ANDROID
* @public
*/
var BROWSER = {
"FIREFOX": "ff",
"CHROME": "cr",
"SAFARI": "sf",
"ANDROID": "an"
};
function getBrowser() {
var sUserAgent = oReducedNavigator.userAgent,
sLowerCaseUserAgent = sUserAgent.toLowerCase();
/*!
* Taken from jQuery JavaScript Library v1.7.1
* http://jquery.com/
*
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Mon Nov 21 21:11:03 2011 -0500
*/
function calcBrowser() {
var rwebkit = /(webkit)[ \/]([\w.]+)/;
var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
var browserMatch = rwebkit.exec(sLowerCaseUserAgent) ||
sLowerCaseUserAgent.indexOf("compatible") < 0 && rmozilla.exec(sLowerCaseUserAgent) || [];
var oRes = {
browser: browserMatch[1] || "",
version: browserMatch[2] || "0"
};
oRes[oRes.browser] = true;
return oRes;
}
var oBrowser = calcBrowser();
// jQuery checks for user agent strings. We differentiate between browsers
var oExpMobile;
var oResult;
var fVersion;
if (oBrowser.mozilla) {
oExpMobile = /Mobile/;
if (sUserAgent.match(/Firefox\/(\d+\.\d+)/)) {
fVersion = parseFloat(RegExp.$1);
oResult = {
name: BROWSER.FIREFOX,
versionStr: "" + fVersion,
version: fVersion,
mozilla: true,
mobile: oExpMobile.test(sUserAgent)
};
} else {
// unknown mozilla browser
oResult = {
mobile: oExpMobile.test(sUserAgent),
mozilla: true,
version: -1
};
}
} else if (oBrowser.webkit) {
// webkit version is needed for calculation if the mobile android device is a tablet (calculation of other mobile devices work without)
var regExpWebkitVersion = sLowerCaseUserAgent.match(/webkit[\/]([\d.]+)/);
var webkitVersion;
if (regExpWebkitVersion) {
webkitVersion = regExpWebkitVersion[1];
}
oExpMobile = /Mobile/;
var aChromeMatch = sUserAgent.match(/(Chrome|CriOS)\/(\d+\.\d+).\d+/);
var aFirefoxMatch = sUserAgent.match(/FxiOS\/(\d+\.\d+)/);
var aAndroidMatch = sUserAgent.match(/Android .+ Version\/(\d+\.\d+)/);
if (aChromeMatch || aFirefoxMatch || aAndroidMatch) {
var sName, sVersion, bMobile;
if (aChromeMatch) {
sName = BROWSER.CHROME;
bMobile = oExpMobile.test(sUserAgent);
sVersion = parseFloat(aChromeMatch[2]);
} else if (aFirefoxMatch) {
sName = BROWSER.FIREFOX;
bMobile = true;
sVersion = parseFloat(aFirefoxMatch[1]);
} else if (aAndroidMatch) {
sName = BROWSER.ANDROID;
bMobile = oExpMobile.test(sUserAgent);
sVersion = parseFloat(aAndroidMatch[1]);
}
oResult = {
name: sName,
mobile: bMobile,
versionStr: "" + sVersion,
version: sVersion,
webkit: true,
webkitVersion: webkitVersion
};
} else { // Safari might have an issue with sUserAgent.match(...); thus changing
var oExp = /Version\/(\d+\.\d+).*Safari/;
if (oExp.test(sUserAgent) || /iPhone|iPad|iPod/.test(sUserAgent)) {
var bStandalone = oReducedNavigator.standalone;
oResult = {
name: BROWSER.SAFARI,
fullscreen: bStandalone === undefined ? false : bStandalone,
webview: /SAPFioriClient/.test(sUserAgent),
mobile: oExpMobile.test(sUserAgent),
webkit: true,
webkitVersion: webkitVersion
};
var aParts = oExp.exec(sUserAgent);
if (aParts) {
fVersion = parseFloat(aParts[1]);
oResult.versionStr = "" + fVersion;
oResult.version = fVersion;
} else {
oResult.version = -1;
}
} else { // other webkit based browser
oResult = {
mobile: oExpMobile.test(sUserAgent),
webkit: true,
webkitVersion: webkitVersion,
version: -1
};
}
}
} else {
oResult = {
name: "",
versionStr: "",
version: -1,
mobile: false
};
}
// Check for Blink rendering engine (https://stackoverflow.com/questions/20655470/how-to-detect-blink-in-chrome)
if ((oBrowser.chrome || window.Intl && window.Intl.v8BreakIterator) && 'CSS' in window) {
oResult.blink = true;
}
return oResult;
}
function setBrowser() {
Device.browser = getBrowser();
Device.browser.BROWSER = BROWSER;
if (Device.browser.name) {
for (var b in BROWSER) {
if (BROWSER[b] === Device.browser.name) {
Device.browser[b.toLowerCase()] = true;
}
}
}
}
setBrowser();
//******** Support Detection ********
/**
* Contains information about detected capabilities of the used browser or Device.
*
* @namespace
* @name sap.ui.Device.support
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser supports touch events.
*
* <b>Note:</b> This flag indicates whether the used browser supports touch events or not.
* This does not necessarily mean that the used device has a touchable screen.
* <b>Note:</b> This flag also affects other {@link sap.ui.Device} properties.
* For more information, see the documentation for {@link sap.ui.Device.browser.mobile} and
* {@link sap.ui.Device.system.combi} devices.
*
* @name sap.ui.Device.support.touch
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser supports pointer events.
*
* @name sap.ui.Device.support.pointer
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser natively supports media queries via JavaScript.
*
* <b>Note:</b> The {@link sap.ui.Device.media media queries API} of the device API can also be used when there is no native support.
*
* @name sap.ui.Device.support.matchmedia
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser natively supports events of media queries via JavaScript.
*
* <b>Note:</b> The {@link sap.ui.Device.media media queries API} of the device API can also be used when there is no native support.
*
* @name sap.ui.Device.support.matchmedialistener
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser natively supports the <code>orientationchange</code> event.
*
* <b>Note:</b> The {@link sap.ui.Device.orientation orientation event} of the device API can also be used when there is no native support.
*
* @name sap.ui.Device.support.orientation
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the device has a display with a high resolution.
*
* @name sap.ui.Device.support.retina
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser supports web sockets.
*
* @name sap.ui.Device.support.websocket
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the used browser supports the <code>placeholder</code> attribute on <code>input</code> elements.
*
* @name sap.ui.Device.support.input.placeholder
* @type boolean
* @public
*/
Device.support = {};
/**
* 1. Maybe better to but this on Device.browser because there are cases that a browser can touch but a device can't!
* 2. Chrome 70 removes the 'ontouchstart' from window for device with and without touch screen. Therefore we need to
* use maxTouchPoints to check whether the device support touch interaction
* 3. FF 52 fires touch events (touch start) when tapping, but the support is only detectable with "window.TouchEvent".
* This is also the recommended way of detecting touch feature support, according to the Chrome Developers
* (https://www.chromestatus.com/feature/4764225348042752).
*/
var detectTouch = function () {
return !!(('ontouchstart' in window)
|| (window.navigator.maxTouchPoints > 0)
|| (window.DocumentTouch && document instanceof window.DocumentTouch)
|| (window.TouchEvent && Device.browser.firefox));
};
Device.support.touch = detectTouch();
Device.support.pointer = !!window.PointerEvent;
Device.support.matchmedia = true;
Device.support.matchmedialistener = true;
Device.support.orientation = !!("orientation" in window && "onorientationchange" in window);
Device.support.retina = (window.retina || window.devicePixelRatio >= 2);
Device.support.websocket = ('WebSocket' in window);
Device.support.input = {};
Device.support.input.placeholder = ('placeholder' in document.createElement("input"));
//******** Match Media ********
/**
* Event API for screen width changes.
*
* This API is based on media queries but can also be used if media queries are not natively supported by the used browser.
* In this case, the behavior of media queries is simulated by this API.
*
* There are several predefined {@link sap.ui.Device.media.RANGESETS range sets} available. Each of them defines a
* set of intervals for the screen width (from small to large). Whenever the screen width changes and the current screen width is in
* a different interval to the one before the change, the registered event handlers for the range set are called.
*
* If needed, it is also possible to define a custom set of intervals.
*
* The following example shows a typical use case:
* <pre>
* function sizeChanged(mParams) {
* switch(mParams.name) {
* case "Phone":
* // Do what is needed for a little screen
* break;
* case "Tablet":
* // Do what is needed for a medium sized screen
* break;
* case "Desktop":
* // Do what is needed for a large screen
* }
* }
*
* // Register an event handler to changes of the screen size
* sap.ui.Device.media.attachHandler(sizeChanged, null, sap.ui.Device.media.RANGESETS.SAP_STANDARD);
* // Do some initialization work based on the current size
* sizeChanged(sap.ui.Device.media.getCurrentRange(sap.ui.Device.media.RANGESETS.SAP_STANDARD));
* </pre>
*
* @namespace
* @name sap.ui.Device.media
* @public
*/
Device.media = {};
/**
* Enumeration containing the names and settings of predefined screen width media query range sets.
*
* @namespace
* @name sap.ui.Device.media.RANGESETS
* @public
*/
/**
* A 3-step range set (S-L).
*
* The ranges of this set are:
* <ul>
* <li><code>"S"</code>: For screens smaller than 520 pixels.</li>
* <li><code>"M"</code>: For screens greater than or equal to 520 pixels and smaller than 960 pixels.</li>
* <li><code>"L"</code>: For screens greater than or equal to 960 pixels.</li>
* </ul>
*
* To use this range set, you must initialize it explicitly ({@link sap.ui.Device.media.initRangeSet}).
*
* If this range set is initialized, a CSS class is added to the page root (<code>html</code> tag) which indicates the current
* screen width range: <code>sapUiMedia-3Step-<i>NAME_OF_THE_INTERVAL</i></code>.
*
* @name sap.ui.Device.media.RANGESETS.SAP_3STEPS
* @public
*/
/**
* A 4-step range set (S-XL).
*
* The ranges of this set are:
* <ul>
* <li><code>"S"</code>: For screens smaller than 520 pixels.</li>
* <li><code>"M"</code>: For screens greater than or equal to 520 pixels and smaller than 760 pixels.</li>
* <li><code>"L"</code>: For screens greater than or equal to 760 pixels and smaller than 960 pixels.</li>
* <li><code>"XL"</code>: For screens greater than or equal to 960 pixels.</li>
* </ul>
*
* To use this range set, you must initialize it explicitly ({@link sap.ui.Device.media.initRangeSet}).
*
* If this range set is initialized, a CSS class is added to the page root (<code>html</code> tag) which indicates the current
* screen width range: <code>sapUiMedia-4Step-<i>NAME_OF_THE_INTERVAL</i></code>.
*
* @name sap.ui.Device.media.RANGESETS.SAP_4STEPS
* @public
*/
/**
* A 6-step range set (XS-XXL).
*
* The ranges of this set are:
* <ul>
* <li><code>"XS"</code>: For screens smaller than 241 pixels.</li>
* <li><code>"S"</code>: For screens greater than or equal to 241 pixels and smaller than 400 pixels.</li>
* <li><code>"M"</code>: For screens greater than or equal to 400 pixels and smaller than 541 pixels.</li>
* <li><code>"L"</code>: For screens greater than or equal to 541 pixels and smaller than 768 pixels.</li>
* <li><code>"XL"</code>: For screens greater than or equal to 768 pixels and smaller than 960 pixels.</li>
* <li><code>"XXL"</code>: For screens greater than or equal to 960 pixels.</li>
* </ul>
*
* To use this range set, you must initialize it explicitly ({@link sap.ui.Device.media.initRangeSet}).
*
* If this range set is initialized, a CSS class is added to the page root (<code>html</code> tag) which indicates the current
* screen width range: <code>sapUiMedia-6Step-<i>NAME_OF_THE_INTERVAL</i></code>.
*
* @name sap.ui.Device.media.RANGESETS.SAP_6STEPS
* @public
*/
/**
* A 3-step range set (Phone, Tablet, Desktop).
*
* The ranges of this set are:
* <ul>
* <li><code>"Phone"</code>: For screens smaller than 600 pixels.</li>
* <li><code>"Tablet"</code>: For screens greater than or equal to 600 pixels and smaller than 1024 pixels.</li>
* <li><code>"Desktop"</code>: For screens greater than or equal to 1024 pixels.</li>
* </ul>
*
* This range set is initialized by default. An initialization via {@link sap.ui.Device.media.initRangeSet} is not needed.
*
* A CSS class is added to the page root (<code>html</code> tag) which indicates the current
* screen width range: <code>sapUiMedia-Std-<i>NAME_OF_THE_INTERVAL</i></code>.
* Furthermore there are 5 additional CSS classes to hide elements based on the width of the screen:
* <ul>
* <li><code>sapUiHideOnPhone</code>: Will be hidden if the screen has 600px or less</li>
* <li><code>sapUiHideOnTablet</code>: Will be hidden if the screen has more than 600px and less than 1023px</li>
* <li><code>sapUiHideOnDesktop</code>: Will be hidden if the screen is larger than 1024px</li>
* <li><code>sapUiVisibleOnlyOnPhone</code>: Will be visible only if the screen has less than 600px</li>
* <li><code>sapUiVisibleOnlyOnTablet</code>: Will be visible only if the screen has 600px or more but less than 1024px</li>
* <li><code>sapUiVisibleOnlyOnDesktop</code>: Will be visible only if the screen has 1024px or more</li>
* </ul>
*
* @name sap.ui.Device.media.RANGESETS.SAP_STANDARD
* @public
*/
/**
* A 4-step range set (Phone, Tablet, Desktop, LargeDesktop).
*
* The ranges of this set are:
* <ul>
* <li><code>"Phone"</code>: For screens smaller than 600 pixels.</li>
* <li><code>"Tablet"</code>: For screens greater than or equal to 600 pixels and smaller than 1024 pixels.</li>
* <li><code>"Desktop"</code>: For screens greater than or equal to 1024 pixels and smaller than 1440 pixels.</li>
* <li><code>"LargeDesktop"</code>: For screens greater than or equal to 1440 pixels.</li>
* </ul>
*
* This range set is initialized by default. An initialization via {@link sap.ui.Device.media.initRangeSet} is not needed.
*
* A CSS class is added to the page root (<code>html</code> tag) which indicates the current
* screen width range: <code>sapUiMedia-StdExt-<i>NAME_OF_THE_INTERVAL</i></code>.
*
* @name sap.ui.Device.media.RANGESETS.SAP_STANDARD_EXTENDED
* @public
*/
var RANGESETS = {
"SAP_3STEPS": "3Step",
"SAP_4STEPS": "4Step",
"SAP_6STEPS": "6Step",
"SAP_STANDARD": "Std",
"SAP_STANDARD_EXTENDED": "StdExt"
};
Device.media.RANGESETS = RANGESETS;
Device.media._predefinedRangeSets = {};
Device.media._predefinedRangeSets[RANGESETS.SAP_3STEPS] = {
points: [520, 960],
unit: "px",
name: RANGESETS.SAP_3STEPS,
names: ["S", "M", "L"]
};
Device.media._predefinedRangeSets[RANGESETS.SAP_4STEPS] = {
points: [520, 760, 960],
unit: "px",
name: RANGESETS.SAP_4STEPS,
names: ["S", "M", "L", "XL"]
};
Device.media._predefinedRangeSets[RANGESETS.SAP_6STEPS] = {
points: [241, 400, 541, 768, 960],
unit: "px",
name: RANGESETS.SAP_6STEPS,
names: ["XS", "S", "M", "L", "XL", "XXL"]
};
Device.media._predefinedRangeSets[RANGESETS.SAP_STANDARD] = {
points: [600, 1024],
unit: "px",
name: RANGESETS.SAP_STANDARD,
names: ["Phone", "Tablet", "Desktop"]
};
Device.media._predefinedRangeSets[RANGESETS.SAP_STANDARD_EXTENDED] = {
points: [600, 1024, 1440],
unit: "px",
name: RANGESETS.SAP_STANDARD_EXTENDED,
names: ["Phone", "Tablet", "Desktop", "LargeDesktop"]
};
var _defaultRangeSet = RANGESETS.SAP_STANDARD;
var iMediaTimeout = Device.support.matchmedialistener ? 0 : 100;
var oQuerySets = {};
var iMediaCurrentWidth = null;
function getQuery(iFrom, iTo, iUnit) {
iUnit = iUnit || "px";
var sQuery = "all";
if (iFrom > 0) {
sQuery = sQuery + " and (min-width:" + iFrom + iUnit + ")";
}
if (iTo > 0) {
sQuery = sQuery + " and (max-width:" + iTo + iUnit + ")";
}
return sQuery;
}
function handleChange(sName) {
if (!Device.support.matchmedialistener && iMediaCurrentWidth == windowSize()[0]) {
return; //Skip unnecessary resize events
}
if (oQuerySets[sName].timer) {
clearTimeout(oQuerySets[sName].timer);
oQuerySets[sName].timer = null;
}
oQuerySets[sName].timer = setTimeout(function() {
var mParams = checkQueries(sName, false);
if (mParams) {
fireEvent("media_" + sName, mParams);
}
}, iMediaTimeout);
}
function checkQueries(sName, bInfoOnly, fnMatches) {
function getRangeInfo(sSetName, iRangeIdx) {
var q = oQuerySets[sSetName].queries[iRangeIdx];
var info = {
from: q.from,
unit: oQuerySets[sSetName].unit
};
if (q.to >= 0) {
info.to = q.to;
}
if (oQuerySets[sSetName].names) {
info.name = oQuerySets[sSetName].names[iRangeIdx];
}
return info;
}
fnMatches = fnMatches || Device.media.matches;
if (oQuerySets[sName]) {
var aQueries = oQuerySets[sName].queries;
var info = null;
for (var i = 0, len = aQueries.length; i < len; i++) {
var q = aQueries[i];
if ((q != oQuerySets[sName].currentquery || bInfoOnly) && fnMatches(q.from, q.to, oQuerySets[sName].unit)) {
if (!bInfoOnly) {
oQuerySets[sName].currentquery = q;
}
if (!oQuerySets[sName].noClasses && oQuerySets[sName].names && !bInfoOnly) {
refreshCSSClasses(sName, oQuerySets[sName].names[i]);
}
info = getRangeInfo(sName, i);
}
}
return info;
}
oLogger.log(WARNING, "No queryset with name " + sName + " found", 'DEVICE.MEDIA');
return null;
}
function refreshCSSClasses(sSetName, sRangeName, bRemove) {
var sClassPrefix = "sapUiMedia-" + sSetName + "-";
changeRootCSSClass(sClassPrefix + sRangeName, bRemove, sClassPrefix);
}
function changeRootCSSClass(sClassName, bRemove, sPrefix) {
var oRoot = document.documentElement;
if (oRoot.className.length == 0) {
if (!bRemove) {
oRoot.className = sClassName;
}
} else {
var aCurrentClasses = oRoot.className.split(" ");
var sNewClasses = "";
for (var i = 0; i < aCurrentClasses.length; i++) {
if ((sPrefix && aCurrentClasses[i].indexOf(sPrefix) != 0) || (!sPrefix && aCurrentClasses[i] != sClassName)) {
sNewClasses = sNewClasses + aCurrentClasses[i] + " ";
}
}
if (!bRemove) {
sNewClasses = sNewClasses + sClassName;
}
oRoot.className = sNewClasses;
}
}
function windowSize() {
return [window.innerWidth, window.innerHeight];
}
function matchLegacyBySize(iFrom, iTo, sUnit, iSize) {
function convertToPx(iValue, sUnit) {
if (sUnit === "em" || sUnit === "rem") {
var fnGetStyle = window.getComputedStyle || function(e) {
return e.currentStyle;
};
var iFontSize = fnGetStyle(document.documentElement).fontSize;
var iFactor = (iFontSize && iFontSize.indexOf("px") >= 0) ? parseFloat(iFontSize, 10) : 16;
return iValue * iFactor;
}
return iValue;
}
iFrom = convertToPx(iFrom, sUnit);
iTo = convertToPx(iTo, sUnit);
var width = iSize[0];
var a = iFrom < 0 || iFrom <= width;
var b = iTo < 0 || width <= iTo;
return a && b;
}
function matchLegacy(iFrom, iTo, sUnit) {
return matchLegacyBySize(iFrom, iTo, sUnit, windowSize());
}
function match(iFrom, iTo, sUnit) {
var oQuery = getQuery(iFrom, iTo, sUnit);
var mm = window.matchMedia(oQuery); //FF returns null when running within an iframe with display:none
return mm && mm.matches;
}
Device.media.matches = Device.support.matchmedia ? match : matchLegacy;
/**
* Registers the given event handler to change events of the screen width based on the range set with the specified name.
*
* The event is fired whenever the screen width changes and the current screen width is in
* a different interval of the given range set than before the width change.
*
* The event handler is called with a single argument: a map <code>mParams</code> which provides the following information
* about the entered interval:
* <ul>
* <li><code>mParams.from</code>: The start value (inclusive) of the entered interval as a number</li>
* <li><code>mParams.to</code>: The end value (exclusive) range of the entered interval as a number or undefined for the last interval (infinity)</li>
* <li><code>mParams.unit</code>: The unit used for the values above, e.g. <code>"px"</code></li>
* <li><code>mParams.name</code>: The name of the entered interval, if available</li>
* </ul>
*
* @param {function({from: number, to: number, unit: string, name: string | undefined})}
* fnFunction The handler function to call when the event occurs. This function will be called in the context of the
* <code>oListener</code> instance (if present) or on the <code>window</code> instance. A map with information
* about the entered range set is provided as a single argument to the handler (see details above).
* @param {object}
* [oListener] The object that wants to be notified when the event occurs (<code>this</code> context within the
* handler function). If it is not specified, the handler function is called in the context of the <code>window</code>.
* @param {string}
* [sName] The name of the range set to listen to. The range set must be initialized beforehand
* ({@link sap.ui.Device.media.initRangeSet}). If no name is provided, the
* {@link sap.ui.Device.media.RANGESETS.SAP_STANDARD default range set} is used.
*
* @name sap.ui.Device.media.attachHandler
* @function
* @public
*/
Device.media.attachHandler = function(fnFunction, oListener, sName) {
var name = sName || _defaultRangeSet;
attachEvent("media_" + name, fnFunction, oListener);
};
/**
* Removes a previously attached event handler from the change events of the screen width.
*
* The passed parameters must match those used for registration with {@link #.attachHandler} beforehand.
*
* @param {function}
* fnFunction The handler function to detach from the event
* @param {object}
* [oListener] The object that wanted to be notified when the event occurred
* @param {string}
* [sName] The name of the range set to listen to. If no name is provided, the
* {@link sap.ui.Device.media.RANGESETS.SAP_STANDARD default range set} is used.
*
* @name sap.ui.Device.media.detachHandler
* @function
* @public
*/
Device.media.detachHandler = function(fnFunction, oListener, sName) {
var name = sName || _defaultRangeSet;
detachEvent("media_" + name, fnFunction, oListener);
};
/**
* Initializes a screen width media query range set.
*
* This initialization step makes the range set ready to be used for one of the other functions in namespace <code>sap.ui.Device.media</code>.
* The most important {@link sap.ui.Device.media.RANGESETS predefined range sets} are initialized automatically.
*
* To make a not yet initialized {@link sap.ui.Device.media.RANGESETS predefined range set} ready to be used, call this function with the
* name of the range set to be initialized:
* <pre>
* sap.ui.Device.media.initRangeSet(sap.ui.Device.media.RANGESETS.SAP_3STEPS);
* </pre>
*
* Alternatively it is possible to define custom range sets as shown in the following example:
* <pre>
* sap.ui.Device.media.initRangeSet("MyRangeSet", [200, 400], "px", ["Small", "Medium", "Large"]);
* </pre>
* This example defines the following named ranges:
* <ul>
* <li><code>"Small"</code>: For screens smaller than 200 pixels.</li>
* <li><code>"Medium"</code>: For screens greater than or equal to 200 pixels and smaller than 400 pixels.</li>
* <li><code>"Large"</code>: For screens greater than or equal to 400 pixels.</li>
* </ul>
* The range names are optional. If they are specified a CSS class (e.g. <code>sapUiMedia-MyRangeSet-Small</code>) is also
* added to the document root depending on the current active range. This can be suppressed via parameter <code>bSuppressClasses</code>.
*
* @param {string}
* sName The name of the range set to be initialized - either a {@link sap.ui.Device.media.RANGESETS predefined} or custom one.
* The name must be a valid id and consist only of letters and numeric digits.
* @param {int[]}
* [aRangeBorders] The range borders
* @param {string}
* [sUnit] The unit which should be used for the values given in <code>aRangeBorders</code>.
* The allowed values are <code>"px"</code> (default), <code>"em"</code> or <code>"rem"</code>
* @param {string[]}
* [aRangeNames] The names of the ranges. The names must be a valid id and consist only of letters and digits. If names
* are specified, CSS classes are also added to the document root as described above. This behavior can be
* switched off explicitly by using <code>bSuppressClasses</code>. <b>Note:</b> <code>aRangeBorders</code> with <code>n</code> entries
* define <code>n+1</code> ranges. Therefore <code>n+1</code> names must be provided.
* @param {boolean}
* [bSuppressClasses] Whether or not writing of CSS classes to the document root should be suppressed when
* <code>aRangeNames</code> are provided
*
* @name sap.ui.Device.media.initRangeSet
* @function
* @public
*/
Device.media.initRangeSet = function(sName, aRangeBorders, sUnit, aRangeNames, bSuppressClasses) {
//TODO Do some Assertions and parameter checking
var oConfig;
if (!sName) {
oConfig = Device.media._predefinedRangeSets[_defaultRangeSet];
} else if (sName && Device.media._predefinedRangeSets[sName]) {
oConfig = Device.media._predefinedRangeSets[sName];
} else {
oConfig = {
name: sName,
unit: (sUnit || "px").toLowerCase(),
points: aRangeBorders || [],
names: aRangeNames,
noClasses: !!bSuppressClasses
};
}
if (Device.media.hasRangeSet(oConfig.name)) {
oLogger.log(INFO, "Range set " + oConfig.name + " has already been initialized", 'DEVICE.MEDIA');
return;
}
sName = oConfig.name;
oConfig.queries = [];
oConfig.timer = null;
oConfig.currentquery = null;
oConfig.listener = function() {
return handleChange(sName);
};
var from, to, query;
var aPoints = oConfig.points;
for (var i = 0, len = aPoints.length; i <= len; i++) {
from = (i == 0) ? 0 : aPoints[i - 1];
to = (i == aPoints.length) ? -1 : aPoints[i];
query = getQuery(from, to, oConfig.unit);
oConfig.queries.push({
query: query,
from: from,
to: to
});
}
if (oConfig.names && oConfig.names.length != oConfig.queries.length) {
oConfig.names = null;
}
oQuerySets[oConfig.name] = oConfig;
oConfig.queries.forEach(function(oQuery) {
oQuery.media = window.matchMedia(oQuery.query);
if (oQuery.media.addEventListener) {
oQuery.media.addEventListener("change", oConfig.listener);
} else { // Safari 13 and older only supports deprecated MediaQueryList.addListener
oQuery.media.addListener(oConfig.listener);
}
});
oConfig.listener();
};
/**
* Returns information about the current active range of the range set with the given name.
*
* If the optional parameter <code>iWidth</code> is given, the active range will be determined for that width,
* otherwise it is determined for the current window size.
*
* @param {string} sName The name of the range set. The range set must be initialized beforehand ({@link sap.ui.Device.media.initRangeSet})
* @param {int} [iWidth] An optional width, based on which the range should be determined;
* If <code>iWidth</code> is not a number, the window size will be used.
* @returns {{from: number, to: number, unit: string, name: string | undefined}} Information about the current active interval of the range set. The returned object has the same structure as the argument of the event handlers ({@link sap.ui.Device.media.attachHandler})
*
* @name sap.ui.Device.media.getCurrentRange
* @function
* @public
*/
Device.media.getCurrentRange = function(sName, iWidth) {
if (!Device.media.hasRangeSet(sName)) {
return null;
}
return checkQueries(sName, true, isNaN(iWidth) ? null : function(from, to, unit) {
return matchLegacyBySize(from, to, unit, [iWidth, 0]);
});
};
/**
* Returns <code>true</code> if a range set with the given name is already initialized.
*
* @param {string} sName The name of the range set.
*
* @name sap.ui.Device.media.hasRangeSet
* @return {boolean} Returns <code>true</code> if a range set with the given name is already initialized
* @function
* @public
*/
Device.media.hasRangeSet = function(sName) {
return sName && !!oQuerySets[sName];
};
/**
* Removes a previously initialized range set and detaches all registered handlers.
*
* Only custom range sets can be removed via this function. Initialized predefined range sets
* ({@link sap.ui.Device.media.RANGESETS}) cannot be removed.
*
* @param {string} sName The name of the range set which should be removed.
*
* @name sap.ui.Device.media.removeRangeSet
* @function
* @protected
*/
Device.media.removeRangeSet = function(sName) {
if (!Device.media.hasRangeSet(sName)) {
oLogger.log(INFO, "RangeSet " + sName + " not found, thus could not be removed.", 'DEVICE.MEDIA');
return;
}
for (var x in RANGESETS) {
if (sName === RANGESETS[x]) {
oLogger.log(WARNING, "Cannot remove default rangeset - no action taken.", 'DEVICE.MEDIA');
return;
}
}
var oConfig = oQuerySets[sName];
var queries = oConfig.queries;
for (var i = 0; i < queries.length; i++) {
if (queries[i].media.removeEventListener) {
queries[i].media.removeEventListener("change", oConfig.listener);
} else { // Safari 13 and older only supports deprecated MediaQueryList.removeListener
queries[i].media.removeListener(oConfig.listener);
}
}
refreshCSSClasses(sName, "", true);
delete mEventRegistry["media_" + sName];
delete oQuerySets[sName];
};
//******** System Detection ********
/**
* Provides a basic categorization of the used device based on various indicators.
*
* These indicators are, for example, the support of touch events, the used operating system, and the user agent of the browser.
*
* <b>Note:</b> There is no easy way to precisely determine the used device from the information provided by the browser. We therefore rely especially on the user agent.
* In combination with given device capabilities, it is therefore possible that multiple flags are set to <code>true</code>.
* This is mostly the case for desktop devices with touch capability, and for mobile devices requesting web pages as desktop pages.
*
* @namespace
* @name sap.ui.Device.system
* @public
*/
/**
* If this flag is set to <code>true</code>, the device is recognized as a tablet.
*
* Furthermore, a CSS class <code>sap-tablet</code> is added to the document root element.
*
* <b>Note:</b> This flag is also <code>true</code> for some browsers running on desktop devices. See the documentation for {@link sap.ui.Device.system.combi} devices.
* You can use the following logic to ensure that the current device is a tablet device:
*
* <pre>
* if(sap.ui.Device.system.tablet && !sap.ui.Device.system.desktop){
* ...tablet related commands...
* }
* </pre>
*
* @name sap.ui.Device.system.tablet
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the device is recognized as a phone.
*
* Furthermore, a CSS class <code>sap-phone</code> is added to the document root element.
*
* <b>Note:</b> In case a phone requests a web page as a "Desktop Page", it is possible
* that all properties except <code>Device.system.phone</code> are set to <code>true</code>.
* In this case it is not possible to differentiate between tablet and phone relying on the user agent.
*
* @name sap.ui.Device.system.phone
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the device is recognized as a desktop system.
*
* Furthermore, a CSS class <code>sap-desktop</code> is added to the document root element.
*
* <b>Note:</b> This flag is by default also true for Safari on iPads running on iOS 13 or higher.
* The end user can change this behavior by disabling "Request Desktop Website -> All websites" within the iOS settings.
* See also the documentation for {@link sap.ui.Device.system.combi} devices.
*
* @name sap.ui.Device.system.desktop
* @type boolean
* @public
*/
/**
* If this flag is set to <code>true</code>, the device is recognized as a combination of a desktop system and tablet.
*
* Furthermore, a CSS class <code>sap-combi</code> is added to the document root element.
*
* <b>Note:</b> This property is set to <code>true</code> only when both a desktop and a mobile device is detected.
*
* @name sap.ui.Device.system.combi
* @type boolean
* @public
*/
/**
* Enumeration containing the names of known types of the devices.
*
* @namespace
* @name sap.ui.Device.system.SYSTEMTYPE
* @private
*/
var SYSTEMTYPE = {
"TABLET": "tablet",
"PHONE": "phone",
"DESKTOP": "desktop",
"COMBI": "combi"
};
Device.system = {};
function getSystem(customUA) {
var bTabletDetected = !!isTablet(customUA);
var oSystem = {};
oSystem.tablet = bTabletDetected;
oSystem.phone = Device.support.touch && !bTabletDetected;
oSystem.desktop = !!((!oSystem.tablet && !oSystem.phone) || Device.os.windows || Device.os.linux || Device.os.macintosh);
oSystem.combi = oSystem.desktop && oSystem.tablet;
oSystem.SYSTEMTYPE = SYSTEMTYPE;
for (var type in SYSTEMTYPE) {
changeRootCSSClass("sap-" + SYSTEMTYPE[type], !oSystem[SYSTEMTYPE[type]]);
}
return oSystem;
}
function isTablet() {
var sUserAgent = oReduce