/*!
* SAP UI development toolkit for HTML5 (SAPUI5/OpenUI5)
* (c) Copyright 2009-2015 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/**
* Device and Feature Detection API of the SAP UI5 Library.
*
* @version 1.28.5
* @namespace
* @name sap.ui.Device
* @public
*/
/*global console */
//Declare Module if API is available
if (window.jQuery && window.jQuery.sap && window.jQuery.sap.declare) {
window.jQuery.sap.declare("sap.ui.Device", false);
}
//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() {
//Skip initialization if API is already available
if (typeof window.sap.ui.Device === "object" || typeof window.sap.ui.Device === "function" ) {
var apiVersion = "1.28.5";
window.sap.ui.Device._checkAPIVersion(apiVersion);
return;
}
var device = {};
////-------------------------- Logging -------------------------------------
/* since we cannot use the logging from jquery.sap.global.js, we need to come up with a seperate
* solution for the device API
*/
// helper function for date formatting
function pad0(i,w) {
return ("000" + String(i)).slice(-w);
}
var FATAL = 0, ERROR = 1, WARNING = 2, INFO = 3, DEBUG = 4, TRACE = 5;
var deviceLogger = function() {
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 IE and FF, console might not exist; in FF 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 ? console.debug(logText) : console.log(logText); break; // debug not available in IE, fallback to log
case TRACE: console.trace ? console.trace(logText) : console.log(logText); break; // trace not available in IE, fallback to log (no trace)
}
}
/*eslint-enable no-console */
return oLogEntry;
};
};
// instantiate new logger
var logger = new deviceLogger();
logger.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.28.5";
if (v != sVersion) {
logger.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], 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);
}
}
}
//******** 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 as string. Might be empty if no version can be determined.
*
* @name sap.ui.Device.os#versionStr
* @type String
* @public
*/
/**
* The version as float. Might be -1 if no version can be determined.
*
* @name sap.ui.Device.os#version
* @type float
* @public
*/
/**
* Flag indicating the Windows operating system.
*
* @name sap.ui.Device.os#windows
* @type boolean
* @public
*/
/**
* Flag indicating the Linux operating system.
*
* @name sap.ui.Device.os#linux
* @type boolean
* @public
*/
/**
* Flag indicating the MAC operating system.
*
* @name sap.ui.Device.os#macintosh
* @type boolean
* @public
*/
/**
* Flag indicating the iOS operating system.
*
* @name sap.ui.Device.os#ios
* @type boolean
* @public
*/
/**
* Flag indicating the Android operating system.
*
* @name sap.ui.Device.os#android
* @type boolean
* @public
*/
/**
* Flag indicating the Blackberry operating system.
*
* @name sap.ui.Device.os#blackberry
* @type boolean
* @public
*/
/**
* Flag indicating the Windows Phone operating system.
*
* @name sap.ui.Device.os#windows_phone
* @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
*/
/**
* Blackberry operating system name.
*
* @see sap.ui.Device.os#name
* @name sap.ui.Device.os.OS#BLACKBERRY
* @public
*/
/**
* Windows Phone operating system name.
*
* @see sap.ui.Device.os#name
* @alias sap.ui.Device.os.OS#WINDOWS_PHONE
* @public
*/
var OS = {
"WINDOWS": "win",
"MACINTOSH": "mac",
"LINUX": "linux",
"IOS": "iOS",
"ANDROID": "Android",
"BLACKBERRY": "bb",
"WINDOWS_PHONE": "winphone"
};
function getOS(userAgent){ // may return null!!
userAgent = userAgent || navigator.userAgent;
var platform, // regular expression for platform
result;
function getDesktopOS(){
var pf = navigator.platform;
if (pf.indexOf("Win") != -1 ) {
// userAgent in windows 7 contains: windows NT 6.1
// userAgent in windows 8 contains: windows NT 6.2 or higher
// TODO: update this after windows 9 is released
var rVersion = /windows NT 6.(\d)/i;
var uaResult = userAgent.match(rVersion);
var sVersionStr = "";
if (uaResult) {
if (uaResult[1] == 1) {
sVersionStr = "7";
} else if (uaResult[1] > 1) {
sVersionStr = "8";
}
}
return {"name": OS.WINDOWS, "versionStr": sVersionStr};
} else if (pf.indexOf("Mac") != -1) {
return {"name": OS.MACINTOSH, "versionStr": ""};
} else if (pf.indexOf("Linux") != -1) {
return {"name": OS.LINUX, "versionStr": ""};
}
logger.log(INFO, "OS detection returned no result");
return null;
}
// Windows Phone. User agent includes other platforms and therefore must be checked first:
platform = /Windows Phone (?:OS )?([\d.]*)/;
result = userAgent.match(platform);
if (result) {
return ({"name": OS.WINDOWS_PHONE, "versionStr": result[1]});
}
// BlackBerry 10:
if (userAgent.indexOf("(BB10;") > 0) {
platform = /\sVersion\/([\d.]+)\s/;
result = userAgent.match(platform);
if (result) {
return {"name": OS.BLACKBERRY, "versionStr": result[1]};
} else {
return {"name": OS.BLACKBERRY, "versionStr": '10'};
}
}
// iOS, Android, BlackBerry 6.0+:
platform = /\(([a-zA-Z ]+);\s(?:[U]?[;]?)([\D]+)((?:[\d._]*))(?:.*[\)][^\d]*)([\d.]*)\s/;
result = userAgent.match(platform);
if (result) {
var appleDevices = /iPhone|iPad|iPod/;
var bbDevices = /PlayBook|BlackBerry/;
if (result[0].match(appleDevices)) {
result[3] = result[3].replace(/_/g, ".");
//result[1] contains info of devices
return ({"name": OS.IOS, "versionStr": result[3]});
} else if (result[2].match(/Android/)) {
result[2] = result[2].replace(/\s/g, "");
return ({"name": OS.ANDROID, "versionStr": result[3]});
} else if (result[0].match(bbDevices)) {
return ({"name": OS.BLACKBERRY, "versionStr": result[4]});
}
}
// 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 b in OS) {
if (OS[b] === device.os.name) {
device.os[b.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 as string. Might be empty if no version can be determined.
*
* @name sap.ui.Device.browser#versionStr
* @type String
* @public
*/
/**
* The version as float. Might be -1 if no version can be determined.
*
* @name sap.ui.Device.browser#version
* @type float
* @public
*/
/**
* Flag indicating whether the mobile variant of the browser is used.
*
* @name sap.ui.Device.browser#mobile
* @type boolean
* @public
*/
/**
* Flag indicating the Internet Explorer browser.
*
* @name sap.ui.Device.browser#internet_explorer
* @type boolean
* @deprecated since 1.20: use sap.ui.Device.browser.msie
* @public
*/
/**
* Flag indicating the Internet Explorer browser.
*
* @name sap.ui.Device.browser#msie
* @type boolean
* @since 1.20.0
* @public
*/
/**
* Flag indicating the Firefox browser.
*
* @name sap.ui.Device.browser#firefox
* @type boolean
* @public
*/
/**
* Flag indicating the Chrome browser.
*
* @name sap.ui.Device.browser#chrome
* @type boolean
* @public
*/
/**
* Flag indicating the Safari browser.
*
* @name sap.ui.Device.browser#safari
* @type boolean
* @public
*/
/**
* Flag indicating a Webkit browser.
*
* @name sap.ui.Device.browser#webkit
* @type boolean
* @since 1.20.0
* @public
*/
/**
* Flag indicating a Mozilla browser.
*
* @name sap.ui.Device.browser#mozilla
* @type boolean
* @since 1.20.0
* @public
*/
/**
* Internet Explorer browser name.
*
* @see sap.ui.Device.browser#name
* @name sap.ui.Device.browser.BROWSER#INTERNET_EXPLORER
* @public
*/
/**
* Firefox browser name.
*
* @see sap.ui.Device.browser#name
* @name sap.ui.Device.browser.BROWSER#FIREFOX
* @public
*/
/**
* Chrome browser name.
*
* @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
* @alias sap.ui.Device.browser.BROWSER#ANDROID
* @public
*/
var BROWSER = {
"INTERNET_EXPLORER": "ie",
"FIREFOX": "ff",
"CHROME": "cr",
"SAFARI": "sf",
"ANDROID": "an"
};
var ua = navigator.userAgent;
/*!
* 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(customUa){
var _ua = (customUa || ua).toLowerCase(); // use custom user-agent if given
var rwebkit = /(webkit)[ \/]([\w.]+)/;
var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
var rmsie = /(msie) ([\w.]+)/;
//TODO this might needs to be adjusted in future IE version > 11
var rmsienew = /(trident)\/[\w.]+;.*rv:([\w.]+)/;
var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
// WinPhone IE11 userAgent contains "WebKit" and "Mozilla" and therefore must be checked first
var browserMatch = rmsienew.exec( _ua ) ||
rwebkit.exec( _ua ) ||
ropera.exec( _ua ) ||
rmsie.exec( _ua ) ||
_ua.indexOf("compatible") < 0 && rmozilla.exec( _ua ) ||
[];
var res = { browser: browserMatch[1] || "", version: browserMatch[2] || "0" };
res[res.browser] = true;
return res;
}
function getBrowser(customUa) {
var b = calcBrowser(customUa);
var _ua = customUa || ua;
// jQuery checks for user agent strings. We differentiate between browsers
var oExpMobile;
if ( b.mozilla ) {
oExpMobile = /Mobile/;
if ( _ua.match(/Firefox\/(\d+\.\d+)/) ) {
var version = parseFloat(RegExp.$1);
return {
name: BROWSER.FIREFOX,
versionStr: "" + version,
version: version,
mozilla: true,
mobile: oExpMobile.test(_ua)
};
} else {
// unknown mozilla browser
return {
mobile: oExpMobile.test(_ua),
mozilla: true
};
}
} else if ( b.webkit ) {
// webkit version is needed for calculation if the mobile android device is a tablet (calculation of other mobile devices work without)
var regExpWebkitVersion = _ua.toLowerCase().match(/webkit[\/]([\d.]+)/);
var webkitVersion;
if (regExpWebkitVersion) {
webkitVersion = regExpWebkitVersion[1];
}
oExpMobile = /Mobile/;
if ( _ua.match(/(Chrome|CriOS)\/(\d+\.\d+).\d+/)) {
var version = parseFloat(RegExp.$2);
return {
name: BROWSER.CHROME,
versionStr: "" + version,
version: version,
mobile: oExpMobile.test(_ua),
webkit: true,
webkitVersion: webkitVersion
};
} else if ( _ua.match(/Android .+ Version\/(\d+\.\d+)/) ) {
var version = parseFloat(RegExp.$1);
return {
name: BROWSER.ANDROID,
versionStr: "" + version,
version: version,
mobile: oExpMobile.test(_ua),
webkit: true,
webkitVersion: webkitVersion
};
} else { // Safari might have an issue with _ua.match(...); thus changing
var oExp = /(Version|PhantomJS)\/(\d+\.\d+).*Safari/;
if (oExp.test(_ua)) {
var aParts = oExp.exec(_ua);
var version = parseFloat(aParts[2]);
return {
name: BROWSER.SAFARI,
versionStr: "" + version,
version: version,
mobile: oExpMobile.test(_ua),
webkit: true,
webkitVersion: webkitVersion,
phantomJS: aParts[1] === "PhantomJS"
};
} else {
// unknown webkit browser
return {
mobile: oExpMobile.test(_ua),
webkit: true,
webkitVersion: webkitVersion
};
}
}
} else if ( b.msie || b.trident ) {
var version;
// recognize IE8 when running in compat mode (only then the documentMode property is there)
if (document.documentMode && !customUa) { // only use the actual documentMode when no custom user-agent was given
if (document.documentMode === 7) { // OK, obviously we are IE and seem to be 7... but as documentMode is there this cannot be IE7!
version = 8.0;
} else {
version = parseFloat(document.documentMode);
}
} else {
version = parseFloat(b.version);
}
return {
name: BROWSER.INTERNET_EXPLORER,
versionStr: "" + version,
version: version,
msie: true,
mobile: false // TODO: really?
};
}
return {
name: "",
versionStr: "",
version: -1,
mobile: false
};
}
device._testUserAgent = getBrowser; // expose the user-agent parsing (mainly for testing), but don't let it be overwritten
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
*/
/**
* Flag indicating whether touch events are supported.
*
* @name sap.ui.Device.support#touch
* @type boolean
* @public
*/
/**
* Flag indicating whether pointer events are supported.
*
* @name sap.ui.Device.support#pointer
* @type boolean
* @public
*/
/**
* Flag indicating whether media queries via JavaScript are supported.
*
* @name sap.ui.Device.support#matchmedia
* @type boolean
* @public
*/
/**
* Flag indicating whether events on JavaScript media queries are supported.
*
* @name sap.ui.Device.support#matchmedialistener
* @type boolean
* @public
*/
/**
* Flag indicating whether the native orientationchange event is supported.
*
* @name sap.ui.Device.support#orientation
* @type boolean
* @public
*/
/**
* Flag indicating whether the device has a Retina display.
*
* @name sap.ui.Device.support#retina
* @type boolean
* @public
*/
/**
* Flag indicating whether WebSockets are supported.
*
* @name sap.ui.Device.support#websocket
* @type boolean
* @public
*/
/**
* Flag indicating whether placeholder on input tags are supported.
*
* @name sap.ui.Device.support#input.placeholder
* @type boolean
* @public
*/
device.support = {};
//Maybe better to but this on device.browser because there are cases that a browser can touch but a device can't!
device.support.touch = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
// FIXME: PhantomJS doesn't support touch events but exposes itself as touch
// enabled browser. Therfore we manually override that in jQuery.support!
// This has been tested with PhantomJS 1.9.7 and 2.0.0!
if (device.browser.phantomJS) {
device.support.touch = false;
}
device.support.pointer = !!window.PointerEvent;
device.support.matchmedia = !!window.matchMedia;
var m = device.support.matchmedia ? window.matchMedia("screen and (max-width:0px)") : null; //IE10 doesn't like empty string as argument for matchMedia, FF returns null when running within an iframe with display:none
device.support.matchmedialistener = !!(m && m.addListener);
if (device.browser.safari && device.browser.version < 6) {
//Safari seems to have addListener but no events are fired ?!
device.support.matchmedialistener = false;
}
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 media queries.
*
* @namespace
* @name sap.ui.Device.media
* @public
*/
device.media = {};
/**
* Enumeration containing the names of predefined Screen width media query range sets.
*
* @namespace
* @name sap.ui.Device.media.RANGESETS
* @public
*/
/**
* A 3 step range set (S-L).
*
* @name sap.ui.Device.media.RANGESETS#SAP_3STEPS
* @public
*/
/**
* A 4 step range set (S-XL).
*
* @name sap.ui.Device.media.RANGESETS#SAP_4STEPS
* @public
*/
/**
* A 6 step range set (XS-XXL).
*
* @name sap.ui.Device.media.RANGESETS#SAP_6STEPS
* @public
*/
/**
* A 3 step range set (Phone, Tablet, Desktop).
*
* This range set is initialized always by default.
* Phone is < 600px
* Tablet is 600px >= Tablet < 1024
* Desktop is > 1024px
*
* There are 5 css classes to hide elements based on the width of the screen:
*
mParams
:
* sap.ui.Device.media.initRangeSet(sap.ui.Device.media.RANGESETS.SAP_3STEPS)
.
*
* Or it is possible to define a custom range set as in the following example:
* sap.ui.Device.media.initRangeSet("MyRangeSet", [200, 400], "px", ["Small", "Medium", "Large"])
defines 3 ranges:
* bSuppressClasses
.
*
* @param {String} sName The name of the range set. Either a predefined or custom one. The name must be a valid id (consist of letters and digits).
* @param {int[]} aRangeBorders The range borders
* @param {String} [sUnit] The unit which should be used. Allowed values are px (default), em or rem.
* @param {String[]} [aRangeNames] The names of the ranges. The names must be a valid id (consist of letters and digits).
* @param {boolean} [bSuppressClasses] Whether writing CSS classes to the document root should be suppressed
* @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)) {
logger.log(INFO, "Range set " + oConfig.name + " hase 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;
}
_querysets[oConfig.name] = oConfig;
if (device.support.matchmedialistener) { //FF, Safari, Chrome, IE10?
var queries = oConfig.queries;
for (var i = 0; i < queries.length; i++) {
var q = queries[i];
q.media = window.matchMedia(q.query);
q.media.addListener(oConfig.listener);
}
} else { //IE, Safari (<6?)
if (window.addEventListener) {
window.addEventListener("resize", oConfig.listener, false);
window.addEventListener("orientationchange", oConfig.listener, false);
} else { //IE8
window.attachEvent("onresize", oConfig.listener);
}
}
oConfig.listener();
};
/**
* Returns information about the current active range of the range set with the given name.
*
* @param {String} sName The name of the range set.
* @name sap.ui.Device.media#getCurrentRange
* @return {Map} the information about the current active range (same structure like the handler parameters (@see sap.ui.Device.media#attachHandler))
* @function
* @public
*/
device.media.getCurrentRange = function(sName){
if (!device.media.hasRangeSet(sName)) {
return null;
}
return checkQueries(sName, true);
};
/**
* Returns whether a range set with the given name is initialized.
*
* @param {String} sName The name of the range set.
* @name sap.ui.Device.media#hasRangeSet
* @return {boolean}
* @function
* @public
*/
device.media.hasRangeSet = function(sName){
return sName && !!_querysets[sName];
};
/**
* Removes a previously initialized range set and detaches all registered handlers.
*
* Initialized predefined range sets (@see sap.ui.Device.media#RANGESETS) cannot be removed.
*
* @param {String} sName The name of the range set.
* @name sap.ui.Device.media#removeRangeSet
* @function
* @protected
*/
device.media.removeRangeSet = function(sName){
if (!device.media.hasRangeSet(sName)) {
logger.log(INFO, "RangeSet " + sName + " not found, thus could not be removed.", 'DEVICE.MEDIA');
return;
}
for (var x in RANGESETS) {
if (sName === RANGESETS[x]) {
logger.log(WARNING, "Cannot remove default rangeset - no action taken.", 'DEVICE.MEDIA');
return;
}
}
var oConfig = _querysets[sName];
if (device.support.matchmedialistener) { //FF, Safari, Chrome, IE10?
var queries = oConfig.queries;
for (var i = 0; i < queries.length; i++) {
queries[i].media.removeListener(oConfig.listener);
}
} else { //IE, Safari (<6?)
if (window.removeEventListener) {
window.removeEventListener("resize", oConfig.listener, false);
window.removeEventListener("orientationchange", oConfig.listener, false);
} else { //IE8
window.detachEvent("onresize", oConfig.listener);
}
}
refreshCSSClasses(sName, "", true);
delete mEventRegistry["media_" + sName];
delete _querysets[sName];
};
//******** System Detection ********
/**
* Contains information about the system.
*
* @namespace
* @name sap.ui.Device.system
* @public
*/
/**
* Enumeration containing the names of known types of the devices.
*
* @namespace
* @name sap.ui.Device.system.SYSTEMTYPE
* @public
*/
/**
* Flag indicating if the device is a tablet.
*
* @name sap.ui.Device.system#tablet
* @type boolean
* @public
*/
/**
* Flag indicating if the device is a phone.
*
* @name sap.ui.Device.system#phone
* @type boolean
* @public
*/
/**
* Flag indicating if the device is a desktop.
*
* @name sap.ui.Device.system#desktop
* @type boolean
* @public
*/
/**
* Flag indicating if the device is a combination of desktop and tablet.
*
* This property is mainly targeting the windows 8 devices where the mouse and touch event may supported
* natively by the browser.
*
* This property is set to true only when both mouse and touch event are natively supported.
*
* @alias sap.ui.Device.system#combi
* @type boolean
* @public
*/
var SYSTEMTYPE = {
"TABLET" : "tablet",
"PHONE" : "phone",
"DESKTOP" : "desktop",
"COMBI" : "combi"
};
var isWin8 = !!device.os.windows && device.os.version === 8;
var isWin7 = !!device.os.windows && device.os.version === 7;
device.system = {};
function getSystem(_simMobileOnDesktop) {
var t = isTablet();
var s = {};
s.tablet = ((device.support.touch && !isWin7) || isWin8 || !!_simMobileOnDesktop) && t;
s.phone = device.os.windows_phone || ((device.support.touch && !isWin7) || !!_simMobileOnDesktop) && !t;
s.desktop = (!s.tablet && !s.phone) || isWin8 || isWin7;
s.combi = (s.desktop && s.tablet);
s.SYSTEMTYPE = SYSTEMTYPE;
for (var type in SYSTEMTYPE) {
changeRootCSSClass("sap-" + SYSTEMTYPE[type], !s[SYSTEMTYPE[type]]);
}
return s;
}
function isTablet() {
var android_phone = (/(?=android)(?=.*mobile)/i.test(navigator.userAgent));
// According to google documentation: https://developer.chrome.com/multidevice/webview/overview, the WebView shipped with Android 4.4 (KitKat) is based on the same code as Chrome for Android.
// If you're attempting to differentiate between the WebView and Chrome for Android, you should look for the presence of the Version/_X.X_ string in the WebView user-agent string
// The stock browser of Samsung device uses Chrome kernal from Android version 4.4. It behaves differently than the Chrome Webview, therefore it's excluded from this check by checking the 'SAMSUNG'
// string in the user agent.
var bChromeWebView = device.os.android && device.browser.chrome && (device.os.version >= 4.4) && /Version\/\d.\d/.test(navigator.userAgent) && !/SAMSUNG/.test(navigator.userAgent);
if (device.os.name === device.os.OS.IOS) {
return /ipad/i.test(navigator.userAgent);
} else {
if (device.support.touch) {
if (isWin8) {
return true;
}
//in real mobile device
var densityFactor = window.devicePixelRatio ? window.devicePixelRatio : 1; // may be undefined in Windows Phone devices
if (!bChromeWebView && (device.os.name === device.os.OS.ANDROID) && device.browser.webkit && (device.browser.webkitVersion > 537.10)) {
// On Android sometimes window.screen.width returns the logical CSS pixels, sometimes the physical device pixels;
// Tests on multiple devices suggest this depends on the Webkit version.
// The Webkit patch which changed the behavior was done here: https://bugs.webkit.org/show_bug.cgi?id=106460
// Chrome 27 with Webkit 537.36 returns the logical pixels,
// Chrome 18 with Webkit 535.19 returns the physical pixels.
// The BlackBerry 10 browser with Webkit 537.10+ returns the physical pixels.
// So it appears like somewhere above Webkit 537.10 we do not hve to divide by the devicePixelRatio anymore.
// update: Chrome WebView returns physical pixels therefore it's excluded from this special check
densityFactor = 1;
}
//this is how android distinguishes between tablet and phone
//http://android-developers.blogspot.de/2011/07/new-tools-for-managing-screen-sizes.html
var bTablet = (Math.min(window.screen.width / densityFactor, window.screen.height / densityFactor) >= 600);
// special workaround for Nexus 7 where the window.screen.width is 600px or 601px in portrait mode (=> tablet)
// but window.screen.height 552px in landscape mode (=> phone), because the browser UI takes some space on top.
// So the detected device type depends on the orientation :-(
// actually this is a Chrome bug, as "width"/"height" should return the entire screen's dimensions and
// "availWidth"/"availHeight" should return the size available after subtracting the browser UI
if (isLandscape()
&& (window.screen.height === 552 || window.screen.height === 553) // old/new Nexus 7
&& (/Nexus 7/i.test(navigator.userAgent))) {
bTablet = true;
}
return bTablet;
} else {
// in desktop browser, it's detected as tablet when
// 1. Windows 8 device with a touch screen where "Touch" is contained in the userAgent
// 2. Android emulation and it's not an Android phone
return (device.browser.msie && ua.indexOf("Touch") !== -1) || (device.os.name === device.os.OS.ANDROID && !android_phone);
}
}
}
function setSystem(_simMobileOnDesktop) {
device.system = getSystem(_simMobileOnDesktop);
if (device.system.tablet || device.system.phone) {
device.browser.mobile = true;
}
}
setSystem();
//******** Orientation Detection ********
/**
* Orientation Change Event API.
*
* @namespace
* @name sap.ui.Device.orientation
* @public
*/
device.orientation = {};
/**
* Resize Event API.
*
* @namespace
* @name sap.ui.Device.resize
* @public
*/
device.resize = {};
/**
* Registers the given handler to the orientation change event.
*
* The handler has one map parameter mParams
:
* mParams
:
* * var oRect = jQuery("#myDiv").rect(); * alert("Top Position: " + oRect.top); ** * @name jQuery * @static * @public */ (function() { if (!window.jQuery ) { throw new Error("SAPUI5 requires jQuery as a prerequisite (>= version 1.7)"); } // ensure not to initialize twice if (jQuery.sap) { return; } /** * Window that the sap plugin has been initialized for. * @private */ var _window = window; // early logging support var _earlyLogs = []; function _earlyLog(sLevel, sMessage) { _earlyLogs.push({ level: sLevel, message: sMessage }); } var _sBootstrapUrl; // -------------------------- VERSION ------------------------------------- var rVersion = /^[0-9]+(?:\.([0-9]+)(?:\.([0-9]+))?)?(.*)$/; /** * Returns a Version instance created from the given parameters. * * This function can either be called as a constructor (using
new
) or as a normal function.
* It always returns an immutable Version instance.
*
* The parts of the version number (major, minor, patch, suffix) can be provided in several ways:
* jQuery.sap.Version
instance, as a string (e.g. v.compareto("1.4.5")
)
* or major, minor, patch and suffix cab be given as separate parameters (e.g. v.compareTo(1, 4, 5)
)
* or in an array (e.g. v.compareTo([1, 4, 5])
).
*
* @return {int} 0, if the given version is equal to this version, a negative value if the given version is greater and a positive value otherwise
* @name jQuery.sap.Version#compareTo
* @public
* @since 1.15.0
* @function
*/
this.compareTo = function() {
var vOther = Version.apply(window, arguments);
/*eslint-disable no-nested-ternary */
return vMajor - vOther.getMajor() ||
iMinor - vOther.getMinor() ||
iPatch - vOther.getPatch() ||
((sSuffix < vOther.getSuffix()) ? -1 : (sSuffix === vOther.getSuffix()) ? 0 : 1);
/*eslint-enable no-nested-ternary */
};
}
/**
* Checks whether this version is in the range of the given versions (start included, end excluded).
*
* The boundaries against which this version should be checked can be given as
* jQuery.sap.Version
instances (e.g. v.inRange(v1, v2)
), as strings (e.g. v.inRange("1.4", "2.7")
)
* or as arrays (e.g. v.inRange([1,4], [2,7])
).
*
* @param {string|any[]|jQuery.sap.Version} vMin the start of the range (inclusive)
* @param {string|any[]|jQuery.sap.Version} vMax the end of the range (exclusive)
* @return {boolean} true
if this version is greater or equal to vMin
and smaller than vMax
, false
otherwise.
* @name jQuery.sap.Version#inRange
* @public
* @since 1.15.0
* @function
*/
Version.prototype.inRange = function(vMin, vMax) {
return this.compareTo(vMin) >= 0 && this.compareTo(vMax) < 0;
};
// -----------------------------------------------------------------------
var oJQVersion = Version(jQuery.fn.jquery);
if ( !oJQVersion.inRange("1.7.0", "2.0.0") ) {
_earlyLog("error", "SAPUI5 requires a jQuery version of 1.7 or higher, but lower than 2.0; current version is " + jQuery.fn.jquery);
}
// TODO move to a separate module? Only adds 385 bytes (compressed), but...
if ( !jQuery.browser ) {
// re-introduce the jQuery.browser support if missing (jQuery-1.9ff)
jQuery.browser = (function( ua ) {
var rwebkit = /(webkit)[ \/]([\w.]+)/,
ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
rmsie = /(msie) ([\w.]+)/,
rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
ua = ua.toLowerCase(),
match = rwebkit.exec( ua ) ||
ropera.exec( ua ) ||
rmsie.exec( ua ) ||
ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
[],
browser = {};
if ( match[1] ) {
browser[ match[1] ] = true;
browser.version = match[2] || "0";
if ( browser.webkit ) {
browser.safari = true;
}
}
return browser;
}(window.navigator.userAgent));
}
// Fixes the CORS issue (introduced by jQuery 1.7) when loading resources
// (e.g. SAPUI5 script) from other domains for IE browsers.
// The CORS check in jQuery filters out such browsers who do not have the
// property "withCredentials" which is the IE and Opera and prevents those
// browsers to request data from other domains with jQuery.ajax. The CORS
// requests are simply forbidden nevertheless if it works. In our case we
// simply load our script resources from another domain when using the CDN
// variant of SAPUI5. The following fix is also recommended by jQuery:
if (!!sap.ui.Device.browser.internet_explorer) {
jQuery.support = jQuery.support || {};
jQuery.support.cors = true;
}
/**
* Find the script URL where the SAPUI5 is loaded from and return an object which
* contains the identified script-tag and resource root
*/
var _oBootstrap = (function() {
var oTag, sUrl, sResourceRoot,
reConfigurator = /^(.*\/)?download\/configurator[\/\?]/,
reBootScripts = /^(.*\/)?(sap-ui-(core|custom|boot|merged)(-.*)?)\.js([?#]|$)/,
reResources = /^(.*\/)?resources\//;
// check all script tags that have a src attribute
jQuery("script[src]").each(function() {
var src = this.getAttribute("src"),
m;
if ( (m = src.match(reConfigurator)) !== null ) {
// guess 1: script tag src contains "/download/configurator[/?]" (for dynamically created bootstrap files)
oTag = this;
sUrl = src;
sResourceRoot = (m[1] || "") + "resources/";
return false;
} else if ( (m = src.match(reBootScripts)) !== null ) {
// guess 2: src contains one of the well known boot script names
oTag = this;
sUrl = src;
sResourceRoot = m[1] || "";
return false;
} else if ( this.id == 'sap-ui-bootstrap' && (m = src.match(reResources)) ) {
// guess 2: script tag has well known id and src contains "resources/"
oTag = this;
sUrl = src;
sResourceRoot = m[0];
return false;
}
});
return {
tag: oTag,
url: sUrl,
resourceRoot: sResourceRoot
};
})();
/**
* Determine whether sap-bootstrap-debug is set, run debugger statement and allow
* to restart the core from a new URL
*/
(function() {
if (/sap-bootstrap-debug=(true|x|X)/.test(location.search)) {
window["sap-ui-bRestart"] = false;
window["sap-ui-sRestartUrl"] = "http://localhost:8080/sapui5/resources/sap-ui-core.js";
// function to replace the bootstrap tag with a newly created script tag to enable
// restarting the core from a different server
var restartCore = function() {
var oScript = _oBootstrap.tag,
sScript = "";
oScript.parentNode.removeChild(oScript);
// clean up cachebuster stuff
jQuery("#sap-ui-bootstrap-cachebusted").remove();
window["sap-ui-config"] && window["sap-ui-config"].resourceRoots && (window["sap-ui-config"].resourceRoots[""] = undefined);
document.write(sScript);
var oRestart = new Error("Aborting UI5 bootstrap and restarting from: " + window["sap-ui-sRestartUrl"]);
oRestart.name = "Restart";
// clean up
delete window["sap-ui-bRestart"];
delete window["sap-ui-sRestartUrl"];
throw oRestart;
};
// debugger stops here. To restart UI5 from somewhere else (default: localhost), set:
// window["sap-ui-bRestart"] = true
// If you want to restart from a different server than localhost, you can adapt the URL, e.g.:
// window["sap-ui-sRestartUrl"] = "http://someserver:8080/sapui5/resources/sap-ui-core.js"
/*eslint-disable no-debugger */
debugger;
/*eslint-enable no-debugger */
if (window["sap-ui-bRestart"]) {
restartCore();
}
}
})();
/**
* Determine whether to use debug sources depending on URL parameter and local storage
* and load debug library if necessary
*/
(function() {
//Check URI param
var bDebugSources = /sap-ui-debug=(true|x|X)/.test(location.search),
bIsOptimized = window["sap-ui-optimized"];
//Check local storage
try { //Necessary for FF when Cookies are deactivated
bDebugSources = bDebugSources || (window.localStorage.getItem("sap-ui-debug") == "X");
} catch (e) {}
window["sap-ui-debug"] = bDebugSources;
// if bootstap URL already contains -dbg URL, just set sap-ui-loaddbg
if (/-dbg\.js([?#]|$)/.test(_oBootstrap.url)) {
window["sap-ui-loaddbg"] = true;
window["sap-ui-debug"] = true;
}
// if current sources are optimized and debug sources are wanted, restart with debug URL
if (bIsOptimized && bDebugSources) {
var sDebugUrl = _oBootstrap.url.replace(/\/(?:sap-ui-cachebuster\/)?([^\/]+)\.js/, "/$1-dbg.js");
window["sap-ui-optimized"] = false;
window["sap-ui-loaddbg"] = true;
document.write("");
var oRestart = new Error("Aborting UI5 bootstrap and restarting from: " + sDebugUrl);
oRestart.name = "Restart";
throw oRestart;
}
})();
/*
* Merged, raw (un-interpreted) configuration data from the following sources
* (last one wins)
* window["sap-ui-config"]
(could be either a string/url or a configuration object)data-sap-ui-config
attribute of the bootstrap script tagdata-sap-ui-xyz
attributes of the bootstrap tag{@link jQuery.sap.log.getLogger}(sComponent)
,
* one can retrieve a logger that automatically adds the given sComponent
as component
* parameter to each log entry, if no other component is specified. Typically, JavaScript code will
* retrieve such a logger once during startup and reuse it for the rest of its lifecycle.
* Second, the {@link jQuery.sap.log.Logger#setLevel}(iLevel, sComponent) method allows to set the log level
* for a specific component only. This allows a more fine granular control about the created logging entries.
* {@link jQuery.sap.log.Logger.getLevel} allows to retrieve the currently effective log level for a given
* component.
*
* {@link jQuery.sap.log#getLog} returns an array of the currently collected log entries.
*
* Furthermore, a listener can be registered to the log. It will be notified whenever a new entry
* is added to the log. The listener can be used for displaying log entries in a separate page area,
* or for sending it to some external target (server).
*
* @author SAP SE
* @since 0.9.0
* @namespace
* @public
* @borrows jQuery.sap.log.Logger#fatal as fatal
* @borrows jQuery.sap.log.Logger#error as error
* @borrows jQuery.sap.log.Logger#warning as warning
* @borrows jQuery.sap.log.Logger#info as info
* @borrows jQuery.sap.log.Logger#debug as debug
* @borrows jQuery.sap.log.Logger#trace as trace
* @borrows jQuery.sap.log.Logger#getLevel as getLevel
* @borrows jQuery.sap.log.Logger#setLevel as setLevel
* @borrows jQuery.sap.log.Logger#isLoggable as isLoggable
*/
jQuery.sap.log = jQuery.extend(new Logger(), /** @lends jQuery.sap.log */ {
/**
* Enumeration of the configurable log levels that a Logger should persist to the log.
*
* Only if the current LogLevel is higher than the level {@link jQuery.sap.log.Level} of the currently added log entry,
* then this very entry is permanently added to the log. Otherwise it is ignored.
* @see jQuery.sap.log.Logger#setLevel
* @namespace
* @public
*/
Level : {
/**
* Do not log anything
* @public
*/
NONE : FATAL - 1,
/**
* Fatal level. Use this for logging unrecoverable situations
* @public
*/
FATAL : FATAL,
/**
* Error level. Use this for logging of erroneous but still recoverable situations
* @public
*/
ERROR : ERROR,
/**
* Warning level. Use this for logging unwanted but foreseen situations
* @public
*/
WARNING : WARNING,
/**
* Info level. Use this for logging information of purely informative nature
* @public
*/
INFO : INFO,
/**
* Debug level. Use this for logging information necessary for debugging
* @public
*/
DEBUG : DEBUG,
/**
* Trace level. Use this for tracing the program flow.
* @public
*/
TRACE : TRACE, /* TODO Think about changing to 10 and thus to pull out of logging... -> Make tracing explicit */
/**
* Trace level to log everything.
*/
ALL : (TRACE + 1) /* TODO if TRACE is changed to make sure this is 6 again. There would then be some special TRACE handling. */
},
/**
* Returns a {@link jQuery.sap.log.Logger} for the given component.
*
* The method might or might not return the same logger object across multiple calls.
* While loggers are assumed to be light weight objects, consumers should try to
* avoid redundant calls and instead keep references to already retrieved loggers.
*
* The optional second parameter iDefaultLogLevel
allows to specify
* a default log level for the component. It is only applied when no log level has been
* defined so far for that component (ignoring inherited log levels). If this method is
* called multiple times for the same component but with different log levels,
* only the first call one might be taken into account.
*
* @param {string} sComponent Component to create the logger for
* @param {int} [iDefaultLogLevel] a default log level to be used for the component,
* if no log level has been defined for it so far.
* @return {jQuery.sap.log.Logger} A logger for the component.
* @public
* @static
* @since 1.1.2
*/
getLogger : function(sComponent, iDefaultLogLevel) {
if ( !isNaN(iDefaultLogLevel) && mMaxLevel[sComponent] == null ) {
mMaxLevel[sComponent] = iDefaultLogLevel;
}
return new Logger(sComponent);
},
/**
* Returns the logged entries recorded so far as an array.
*
* Log entries are plain JavaScript objects with the following properties
* onLogEntry
and can also be informed
* about onDetachFromLog
and onAttachToLog
* @param {object} oListener The new listener object that should be informed
* @return {jQuery.sap.log} The global logger
* @public
* @static
*/
addLogListener : function(oListener) {
listener().attach(this, oListener);
return this;
},
/**
* Allows to remove a registered LogListener.
* @param {object} oListener The new listener object that should be removed
* @return {jQuery.sap.log} The global logger
* @public
* @static
*/
removeLogListener : function(oListener) {
listener().detach(this, oListener);
return this;
}
});
/**
* Enumeration of levels that can be used in a call to {@link jQuery.sap.log.Logger#setLevel}(iLevel, sComponent).
*
* @deprecated Since 1.1.2. To streamline the Logging API a bit, the separation between Level and LogLevel has been given up.
* Use the (enriched) enumeration {@link jQuery.sap.log.Level} instead.
* @namespace
* @public
*/
jQuery.sap.log.LogLevel = jQuery.sap.log.Level;
/**
* Retrieves the currently recorded log entries.
* @deprecated Since 1.1.2. To avoid confusion with getLogger, this method has been renamed to {@link jQuery.sap.log.getLogEntries}.
* @function
* @public
* @static
*/
jQuery.sap.log.getLog = jQuery.sap.log.getLogEntries;
/**
* A simple assertion mechanism that logs a message when a given condition is not met.
*
* Note: Calls to this method might be removed when the JavaScript code
* is optimized during build. Therefore, callers should not rely on any side effects
* of this method.
*
* @param {boolean} bResult result of the checked assertion
* @param {string} sMessage message that will be raised when the result is false
*
* @public
* @static
* @SecSink {1|SECRET} Could expose secret data in logs
*/
jQuery.sap.assert = function(bResult, sMessage) {
if ( !bResult ) {
/*eslint-disable no-console */
if ( window.console && console.assert ) {
console.assert(bResult, sWindowName + sMessage);
} else {
// console is not always available (IE, FF) and IE doesn't support console.assert
jQuery.sap.log.debug("[Assertions] " + sMessage);
}
/*eslint-enable no-console */
}
};
// against all our rules: use side effect of assert to differentiate between optimized and productive code
jQuery.sap.assert( !!(mMaxLevel[''] = DEBUG), "will be removed in optimized version");
// evaluate configuration
oCfgData.loglevel = (function() {
var m = /(?:\?|&)sap-ui-log(?:L|-l)evel=([^&]*)/.exec(window.location.search);
return m && m[1];
}()) || oCfgData.loglevel;
if ( oCfgData.loglevel ) {
jQuery.sap.log.setLevel(jQuery.sap.log.Level[oCfgData.loglevel.toUpperCase()] || parseInt(oCfgData.loglevel,10));
}
jQuery.sap.log.info("SAP Logger started.");
// log early logs
jQuery.each(_earlyLogs, function(i,e) {
jQuery.sap.log[e.level](e.message);
});
_earlyLogs = null;
}());
// ---------------------------------------------------------------------------------------------------
/**
* Returns a new constructor function that creates objects with
* the given prototype.
*
* @param {object} oPrototype
* @return {function} the newly created constructor function
* @public
* @static
*/
jQuery.sap.factory = function factory(oPrototype) {
function Factory() {}
Factory.prototype = oPrototype;
return Factory;
};
/**
* Returns a new object which has the given oPrototype as its prototype.
*
* If several objects with the same prototype are to be created,
* {@link jQuery.sap.factory} should be used instead.
*
* @param {object} oPrototype
* @return {object} new object
* @public
* @static
*/
jQuery.sap.newObject = function newObject(oPrototype) {
return new (jQuery.sap.factory(oPrototype))();
};
/**
* Returns a new function that returns the given oValue
(using its closure).
*
* Avoids the need for a dedicated member for the value.
*
* As closures don't come for free, this function should only be used when polluting
* the enclosing object is an absolute "must-not" (as it is the case in public base classes).
*
* @param {object} oValue
*
* @public
* @static
*/
jQuery.sap.getter = function getter(oValue) {
return function() {
return oValue;
};
};
/**
* Returns a JavaScript object which is identified by a sequence of names.
*
* A call to getObject("a.b.C")
has essentially the same effect
* as accessing window.a.b.C
but with the difference that missing
* intermediate objects (a or b in the example above) don't lead to an exception.
*
* When the addressed object exists, it is simply returned. If it doesn't exists,
* the behavior depends on the value of the second, optional parameter
* iNoCreates
(assuming 'n' to be the number of names in the name sequence):
* getObject()
returns undefined
.
* iNoCreates
ones.
* * getObject() -- returns the context object (either param or window) * getObject("a.b.C") -- will only try to get a.b.C and return undefined if not found. * getObject("a.b.C", 0) -- will create a, b, and C in that order if they don't exists * getObject("a.b.c", 1) -- will create a and b, but not C. ** * When a
oContext
is given, the search starts in that object.
* Otherwise it starts in the window
object that this plugin
* has been created in.
*
* Note: Although this method internally uses object["key"]
to address object
* properties, it does not support all possible characters in a name.
* Especially the dot ('.') is not supported in the individual name segments,
* as it is always interpreted as a name separator.
*
* @param {string} sName a dot separated sequence of names that identify the required object
* @param {int} [iNoCreates=NaN] number of objects (from the right) that should not be created
* @param {object} [oContext=window] the context to execute the search in
*
* @public
* @static
*/
jQuery.sap.getObject = function getObject(sName, iNoCreates, oContext) {
var oObject = oContext || _window,
aNames = (sName || "").split("."),
l = aNames.length,
iEndCreate = isNaN(iNoCreates) ? 0 : l - iNoCreates,
i;
for (i = 0; oObject && i < l; i++) {
if (!oObject[aNames[i]] && i < iEndCreate ) {
oObject[aNames[i]] = {};
}
oObject = oObject[aNames[i]];
}
return oObject;
};
/**
* Sets an object property to a given value, where the property is
* identified by a sequence of names (path).
*
* When a oContext
is given, the path starts in that object.
* Otherwise it starts in the window
object that this plugin
* has been created for.
*
* Note: Although this method internally uses object["key"]
to address object
* properties, it does not support all possible characters in a name.
* Especially the dot ('.') is not supported in the individual name segments,
* as it is always interpreted as a name separator.
*
* @param {string} sName a dot separated sequence of names that identify the property
* @param {any} vValue value to be set, can have any type
* @param {object} [oContext=window] the context to execute the search in
* @public
* @static
*/
jQuery.sap.setObject = function (sName, vValue, oContext) {
var oObject = oContext || _window,
aNames = (sName || "").split("."),
l = aNames.length, i;
if ( l > 0 ) {
for (i = 0; oObject && i < l - 1; i++) {
if (!oObject[aNames[i]] ) {
oObject[aNames[i]] = {};
}
oObject = oObject[aNames[i]];
}
oObject[aNames[l - 1]] = vValue;
}
};
// ---------------------- sync point -------------------------------------------------------------
/*
* 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.
*/
function SyncPoint(sName, fnCallback, iTimeout) {
var aTasks = [],
iOpenTasks = 0,
iFailures = 0,
sTimer;
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 ) {
jQuery.sap.log.info("Sync point '" + sName + "' finished (tasks:" + aTasks.length + ", open:" + iOpenTasks + ", failures:" + iFailures + ")");
if ( sTimer ) {
clearTimeout(sTimer);
sTimer = null;
}
finish();
}
};
function finish() {
fnCallback && fnCallback(iOpenTasks, iFailures);
fnCallback = null;
}
if ( !isNaN(iTimeout) ) {
sTimer = setTimeout(function() {
jQuery.sap.log.info("Sync point '" + sName + "' timed out (tasks:" + aTasks.length + ", open:" + iOpenTasks + ", failures:" + iFailures + ")");
finish();
}, iTimeout);
}
jQuery.sap.log.info("Sync point '" + sName + "' created" + (iTimeout ? "(timeout after " + iTimeout + " ms)" : ""));
}
/**
* Internal function to create a sync point.
* @private
*/
jQuery.sap.syncPoint = function(sName, fnCallback, iTimeout) {
return new SyncPoint(sName, fnCallback, iTimeout);
};
// ---------------------- require/declare --------------------------------------------------------
var getModuleSystemInfo = (function() {
/**
* Local logger, by default only logging errors. Can be configured to DEBUG via config parameter.
* @private
*/
var log = jQuery.sap.log.getLogger("sap.ui.ModuleSystem",
(/sap-ui-xx-debug(M|-m)odule(L|-l)oading=(true|x|X)/.test(location.search) || oCfgData["xx-debugModuleLoading"]) ? jQuery.sap.log.Level.DEBUG : jQuery.sap.log.Level.INFO
),
/**
* A map of URL prefixes keyed by the corresponding module name prefix.
* URL prefix can either be given as string or as object with properties url and final.
* When final is set to true, module name prefix cannot be overwritten.
* @see jQuery.sap.registerModulePath
*
* Note that the empty prefix ('') will always match and thus serves as a fallback.
* @private
*/
mUrlPrefixes = { '' : { 'url' : 'resources/' } },
/**
* Module neither has been required nor preloaded not declared, but someone asked for it.
*/
INITIAL = 0,
/**
* Module has been preloaded, but not required or declared
*/
PRELOADED = -1,
/**
* Module has been declared.
*/
LOADING = 1,
/**
* Module has been loaded, but not yet executed.
*/
LOADED = 2,
/**
* Module is currently being executed
*/
EXECUTING = 3,
/**
* Module has been loaded and executed without errors.
*/
READY = 4,
/**
* Module either could not be loaded or execution threw an error
*/
FAILED = 5,
/**
* Set of modules that have been loaded (required) so far.
*
* Each module is an object that can have the following members
* FAILED
* undefined
is returned.
*
* @private
*/
function urnToUI5(sName) {
// UI5 module name syntax is only defined for JS resources
if ( !/\.js$/.test(sName) ) {
return;
}
sName = sName.slice(0, -3);
if ( /^sap\/ui\/thirdparty\/jquery\/jquery-/.test(sName) ) {
return "sap.ui.thirdparty.jquery.jquery-" + sName.slice("sap/ui/thirdparty/jquery/jquery-".length);
} else if ( /^jquery\.sap\./.test(sName) ) {
return sName; // do nothing
}
return sName.replace(/\//g, ".");
}
// find longest matching prefix for resource name
function getResourcePath(sResourceName, sSuffix) {
// split name into segments
var aSegments = sResourceName.split(/\//),
l, sNamePrefix, sResult, m;
// if no suffix was given and if the name is not empty, try to guess the suffix from the last segment
if ( arguments.length === 1 && aSegments.length > 0 ) {
// only known types (and their known subtypes) are accepted
m = rSubTypes.exec(aSegments[aSegments.length - 1]);
if ( m ) {
sSuffix = m[0];
aSegments[aSegments.length - 1] = aSegments[aSegments.length - 1].slice(0, m.index);
} else {
sSuffix = "";
}
}
// search for a defined name prefix, starting with the full name and successively removing one segment
for (l = aSegments.length; l >= 0; l--) {
sNamePrefix = aSegments.slice(0, l).join('/');
if ( mUrlPrefixes[sNamePrefix] ) {
sResult = mUrlPrefixes[sNamePrefix].url;
if ( l < aSegments.length ) {
sResult += aSegments.slice(l).join('/');
}
if ( sResult.slice(-1) === '/' ) {
sResult = sResult.slice(0, -1);
}
return sResult + (sSuffix || '');
}
}
jQuery.sap.assert(false, "should never happen");
}
function guessResourceName(sURL) {
var sNamePrefix,
sUrlPrefix,
sResourceName;
for (sNamePrefix in mUrlPrefixes) {
if ( mUrlPrefixes.hasOwnProperty(sNamePrefix) ) {
// Note: configured URL prefixes are guaranteed to end with a '/'
// But to support the legacy scenario promoted by the application tools ( "registerModulePath('Application','Application')" )
// the prefix check here has to be done without the slash
sUrlPrefix = mUrlPrefixes[sNamePrefix].url.slice(0, -1);
if ( sURL.indexOf(sUrlPrefix) === 0 ) {
// calc resource name
sResourceName = sNamePrefix + sURL.slice(sUrlPrefix.length);
// remove a leading '/' (occurs if name prefix is empty and if match was a full segment match
if ( sResourceName.charAt(0) === '/' ) {
sResourceName = sResourceName.slice(1);
}
if ( mModules[sResourceName] && mModules[sResourceName].data ) {
return sResourceName;
}
}
}
}
// return undefined;
}
function declareModule(sModuleName) {
var oModule;
// sModuleName must be a unified resource name of type .js
jQuery.sap.assert(/\.js$/.test(sModuleName), "must be a Javascript module");
oModule = mModules[sModuleName] || (mModules[sModuleName] = { state : INITIAL });
if ( oModule.state > INITIAL ) {
return oModule;
}
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "declare module '" + sModuleName + "'");
}
// avoid cycles
oModule.state = READY;
// the first call to declareModule is assumed to identify the bootstrap module
// Note: this is only a guess and fails e.g. when multiple modules are loaded via a script tag
// to make it safe, we could convert 'declare' calls to e.g. 'subdeclare' calls at build time.
if ( _execStack.length === 0 ) {
_execStack.push(sModuleName);
oModule.url = oModule.url || _sBootstrapUrl;
}
return oModule;
}
function requireModule(sModuleName) {
var m = rJSSubtypes.exec(sModuleName),
sBaseName, sType, oModule, aExtensions, i;
// only for robustness, should not be possible by design (all callers append '.js')
if ( !m ) {
log.error("can only require Javascript module, not " + sModuleName);
return;
}
// in case of having a type specified ignore the type for the module path creation and add it as file extension
sBaseName = sModuleName.slice(0, m.index);
sType = m[0]; // must be a normalized resource name of type .js sType can be empty or one of view|controller|fragment
oModule = mModules[sModuleName] || (mModules[sModuleName] = { state : INITIAL });
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "require '" + sModuleName + "' of type '" + sType + "'");
}
// check if module has been loaded already
if ( oModule.state !== INITIAL ) {
if ( oModule.state === PRELOADED ) {
oModule.state = LOADED;
execModule(sModuleName);
}
if ( oModule.state === READY ) {
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "module '" + sModuleName + "' has already been loaded (skipped).");
}
return this;
} else if ( oModule.state === FAILED ) {
throw new Error("found in negative cache: '" + sModuleName + "' from " + oModule.url + ": " + oModule.error);
} else {
// currently loading
return this;
}
}
// set marker for loading modules (to break cycles)
oModule.state = LOADING;
// if debug is enabled, try to load debug module first
aExtensions = window["sap-ui-loaddbg"] ? ["-dbg", ""] : [""];
for (i = 0; i < aExtensions.length && oModule.state !== LOADED; i++) {
// create module URL for the current extension
oModule.url = getResourcePath(sBaseName, aExtensions[i] + sType);
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "loading " + (aExtensions[i] ? aExtensions[i] + " version of " : "") + "'" + sModuleName + "' from '" + oModule.url + "'");
}
/*eslint-disable no-loop-func */
jQuery.ajax({
url : oModule.url,
dataType : 'text',
async : false,
success : function(response, textStatus, xhr) {
oModule.state = LOADED;
oModule.data = response;
},
error : function(xhr, textStatus, error) {
oModule.state = FAILED;
oModule.error = xhr ? xhr.status + " - " + xhr.statusText : textStatus;
}
});
/*eslint-enable no-loop-func */
}
// execute module __after__ loading it, this reduces the required stack space!
if ( oModule.state === LOADED ) {
execModule(sModuleName);
}
if ( oModule.state !== READY ) {
throw new Error("failed to load '" + sModuleName + "' from " + oModule.url + ": " + oModule.error);
}
}
// sModuleName must be a normalized resource name of type .js
function execModule(sModuleName) {
var oModule = mModules[sModuleName],
sOldPrefix, sScript, vAMD;
if ( oModule && oModule.state === LOADED && typeof oModule.data !== "undefined" ) {
// check whether the module is known to use an existing AMD loader, remember the AMD flag
vAMD = mAMDShim[sModuleName] && typeof window.define === "function" && window.define.amd;
try {
if ( vAMD ) {
// temp. remove the AMD Flag from the loader
delete window.define.amd;
}
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "executing '" + sModuleName + "'");
sOldPrefix = sLogPrefix;
sLogPrefix = sLogPrefix + ": ";
}
// execute the script in the window context
oModule.state = EXECUTING;
_execStack.push(sModuleName);
if ( typeof oModule.data === "function" ) {
oModule.data.call(window);
} else {
sScript = oModule.data;
// sourceURL: Firebug, Chrome, Safari and IE11 debugging help, appending the string seems to cost ZERO performance
// Note: IE11 supports sourceURL even when running in IE9 or IE10 mode
// Note: make URL absolute so Chrome displays the file tree correctly
// Note: do not append if there is already a sourceURL / sourceMappingURL
if (sScript && !sScript.match(/\/\/[#@] source(Mapping)?URL=.*$/)) {
sScript += "\n//# sourceURL=" + URI(oModule.url).absoluteTo(sDocumentLocation);
}
// framework internal hook to intercept the loaded script and modify
// it before executing the script - e.g. useful for client side coverage
if (typeof jQuery.sap.require._hook === "function") {
sScript = jQuery.sap.require._hook(sScript, sModuleName);
}
if (_window.execScript && (!oModule.data || oModule.data.length < MAX_EXEC_SCRIPT_LENGTH) ) {
try {
oModule.data && _window.execScript(sScript); // execScript fails if data is empty
} catch (e) {
_execStack.pop();
// eval again with different approach - should fail with a more informative exception
jQuery.sap.globalEval(oModule.data);
throw e; // rethrow err in case globalEval succeeded unexpectedly
}
} else {
_window.eval(sScript);
}
}
_execStack.pop();
oModule.state = READY;
oModule.data = undefined;
// best guess for legacy modules that don't use sap.ui.define
// TODO implement fallback for raw modules
oModule.content = oModule.content || jQuery.sap.getObject(urnToUI5(sModuleName));
if ( log.isLoggable() ) {
sLogPrefix = sOldPrefix;
log.debug(sLogPrefix + "finished executing '" + sModuleName + "'");
}
} catch (err) {
oModule.state = FAILED;
oModule.error = ((err.toString && err.toString()) || err.message) + (err.line ? "(line " + err.line + ")" : "" );
oModule.data = undefined;
if ( window["sap-ui-debug"] && (/sap-ui-xx-show(L|-l)oad(E|-e)rrors=(true|x|X)/.test(location.search) || oCfgData["xx-showloaderrors"]) ) {
log.error("error while evaluating " + sModuleName + ", embedding again via script tag to enforce a stack trace (see below)");
jQuery.sap.includeScript(oModule.url);
return;
}
} finally {
// restore AMD flag
if ( vAMD ) {
window.define.amd = vAMD;
}
}
}
}
function requireAll(aDependencies, fnCallback) {
var aModules = [],
i, sDepModName;
for (i = 0; i < aDependencies.length; i++) {
sDepModName = aDependencies[i];
log.debug(sLogPrefix + "require '" + sDepModName + "'");
requireModule(sDepModName + ".js");
// best guess for legacy modules that don't use sap.ui.define
// TODO implement fallback for raw modules
aModules[i] = mModules[sDepModName + ".js"].content || jQuery.sap.getObject(urnToUI5(sDepModName + ".js"));
log.debug(sLogPrefix + "require '" + sDepModName + "': done.");
}
fnCallback(aModules);
}
/**
* Constructs an URL to load the module with the given name and file type (suffix).
*
* Searches the longest prefix of the given module name for which a registration
* exists (see {@link jQuery.sap.registerModulePath}) and replaces that prefix
* by the registered URL prefix.
*
* The remainder of the module name is appended to the URL, replacing any dot with a slash.
*
* Finally, the given suffix (typically a file name extension) is added (unconverted).
*
* The returned name (without the suffix) doesn't end with a slash.
*
* @param {string} sModuleName module name to detemrine the path for
* @param {string} sSuffix suffix to be added to the resulting path
* @return {string} calculated path (URL) to the given module
*
* @public
* @static
*/
jQuery.sap.getModulePath = function(sModuleName, sSuffix) {
return getResourcePath(ui5ToRJS(sModuleName), sSuffix);
};
/**
* Determines the URL for a resource given its unified resource name.
*
* Searches the longest prefix of the given resource name for which a registration
* exists (see {@link jQuery.sap.registerResourcePath}) and replaces that prefix
* by the registered URL prefix.
*
* The remainder of the resource name is appended to the URL.
*
* Unified Resource Names
* Several UI5 APIs use Unified Resource Names (URNs) as naming scheme for resources that
* they deal with (e.h. Javascript, CSS, JSON, XML, ...). URNs are similar to the path
* component of an URL:
* requireJS
).
*
* @param {string} sResourceName unified resource name of the resource
* @returns {string} URL to load the resource from
* @public
* @experimental Since 1.27.0
* @function
*/
jQuery.sap.getResourcePath = getResourcePath;
/**
* Registers an URL prefix for a module name prefix.
*
* Before a module is loaded, the longest registered prefix of its module name
* is searched for and the associated URL prefix is used as a prefix for the request URL.
* The remainder of the module name is attached to the request URL by replacing
* dots ('.') with slashes ('/').
*
* The registration and search operates on full name segments only. So when a prefix
*
* 'sap.com' -> 'http://www.sap.com/ui5/resources/'
*
* is registered, then it will match the name
*
* 'sap.com.Button'
*
* but not
*
* 'sap.commons.Button'
*
* Note that the empty prefix ('') will always match and thus serves as a fallback for
* any search.
*
* The prefix can either be given as string or as object which contains the url and a 'final' property.
* If 'final' is set to true, overwriting a module prefix is not possible anymore.
*
* @param {string} sModuleName module name to register a path for
* @param {string | object} vUrlPrefix path prefix to register, either a string literal or an object (e.g. {url : 'url/to/res', 'final': true})
* @param {string} [vUrlPrefix.url] path prefix to register
* @param {boolean} [vUrlPrefix.final] flag to avoid overwriting the url path prefix for the given module name at a later point of time
*
* @public
* @static
* @SecSink {1|PATH} Parameter is used for future HTTP requests
*/
jQuery.sap.registerModulePath = function registerModulePath(sModuleName, vUrlPrefix) {
jQuery.sap.assert(!/\//.test(sModuleName), "module path must not contain a slash.");
sModuleName = sModuleName.replace(/\./g, "/");
// URL must not be empty
vUrlPrefix = vUrlPrefix || '.';
jQuery.sap.registerResourcePath(sModuleName, vUrlPrefix);
};
/**
* Registers an URL prefix for a resource name prefix.
*
* Before a resource is loaded, the longest registered prefix of its unified resource name
* is searched for and the associated URL prefix is used as a prefix for the request URL.
* The remainder of the resource name is attached to the request URL 1:1.
*
* The registration and search operates on full name segments only. So when a prefix
*
* 'sap/com' -> 'http://www.sap.com/ui5/resources/'
*
* is registered, then it will match the name
*
* 'sap/com/Button'
*
* but not
*
* 'sap/commons/Button'
*
* Note that the empty prefix ('') will always match and thus serves as a fallback for
* any search.
*
* The url prefix can either be given as string or as object which contains the url and a final flag.
* If final is set to true, overwriting a resource name prefix is not possible anymore.
*
* @param {string} sResourceNamePrefix in unified resource name syntax
* @param {string | object} vUrlPrefix prefix to use instead of the sResourceNamePrefix, either a string literal or an object (e.g. {url : 'url/to/res', 'final': true})
* @param {string} [vUrlPrefix.url] path prefix to register
* @param {boolean} [vUrlPrefix.final] flag to avoid overwriting the url path prefix for the given module name at a later point of time
*
* @public
* @static
* @SecSink {1|PATH} Parameter is used for future HTTP requests
*/
jQuery.sap.registerResourcePath = function registerResourcePath(sResourceNamePrefix, vUrlPrefix) {
sResourceNamePrefix = String(sResourceNamePrefix || "");
if (mUrlPrefixes[sResourceNamePrefix] && mUrlPrefixes[sResourceNamePrefix]["final"] == true) {
log.warning( "registerResourcePath with prefix " + sResourceNamePrefix + " already set as final to '" + mUrlPrefixes[sResourceNamePrefix].url + "'. This call is ignored." );
return;
}
if ( typeof vUrlPrefix === 'string' || vUrlPrefix instanceof String ) {
vUrlPrefix = { 'url' : vUrlPrefix };
}
if ( !vUrlPrefix || vUrlPrefix.url == null ) {
delete mUrlPrefixes[sResourceNamePrefix];
log.info("registerResourcePath ('" + sResourceNamePrefix + "') (registration removed)");
} else {
vUrlPrefix.url = String(vUrlPrefix.url);
// ensure that the prefix ends with a '/'
if ( vUrlPrefix.url.slice(-1) != '/' ) {
vUrlPrefix.url += '/';
}
mUrlPrefixes[sResourceNamePrefix] = vUrlPrefix;
log.info("registerResourcePath ('" + sResourceNamePrefix + "', '" + vUrlPrefix.url + "')" + ((vUrlPrefix['final']) ? " (final)" : ""));
}
};
/**
* Check whether a given module has been loaded / declared already.
*
* Returns true as soon as a module has been required the first time, even when
* loading/executing it has not finished yet. So the main assertion of a
* return value of true
is that the necessary actions have been taken
* to make the module available in the near future. It does not mean, that
* the content of the module is already available!
*
* This fuzzy behavior is necessary to avoid multiple requests for the same module.
* As a consequence of the assertion above, a preloaded module does not
* count as declared. For preloaded modules, an explicit call to
* jQuery.sap.require
is necessary to make them available.
*
* If a caller wants to know whether a module needs to be loaded from the server,
* it can set bIncludePreloaded
to true. Then, preloaded modules will
* be reported as 'declared' as well by this method.
*
* @param {string} sModuleName name of the module to be checked
* @param {boolean} [bIncludePreloaded=false] whether preloaded modules should be reported as declared.
* @return {boolean} whether the module has been declared already
* @public
* @static
*/
jQuery.sap.isDeclared = function isDeclared(sModuleName, bIncludePreloaded) {
sModuleName = ui5ToRJS(sModuleName) + ".js";
return mModules[sModuleName] && (bIncludePreloaded || mModules[sModuleName].state !== PRELOADED);
};
/**
* Returns the names of all declared modules.
* @return {string[]} the names of all declared modules
* @see jQuery.sap.isDeclared
* @public
* @static
*/
jQuery.sap.getAllDeclaredModules = function() {
var aModules = [];
jQuery.each(mModules, function(sURN, oModule) {
// filter out preloaded modules
if ( oModule && oModule.state !== PRELOADED ) {
var sModuleName = urnToUI5(sURN);
if ( sModuleName ) {
aModules.push(sModuleName);
}
}
});
return aModules;
};
// take resource roots from configuration
if ( oCfgData.resourceroots ) {
jQuery.each(oCfgData.resourceroots, jQuery.sap.registerModulePath);
}
// dump the URL prefixes
log.info("URL prefixes set to:");
for (var n in mUrlPrefixes) {
log.info(" " + (n ? "'" + n + "'" : "(default)") + " : " + mUrlPrefixes[n].url + ((mUrlPrefixes[n]['final']) ? " (final)" : "") );
}
/**
* Declares a module as existing.
*
* By default, this function assumes that the module will create a JavaScript object
* with the same name as the module. As a convenience it ensures that the parent
* namespace for that object exists (by calling jQuery.sap.getObject).
* If such an object creation is not desired, bCreateNamespace
must be set to false.
*
* @param {string | object} sModuleName name of the module to be declared
* or in case of an object {modName: "...", type: "..."}
* where modName is the name of the module and the type
* could be a specific dot separated extension e.g.
* {modName: "sap.ui.core.Dev", type: "view"}
* loads sap/ui/core/Dev.view.js
and
* registers as sap.ui.core.Dev.view
* @param {boolean} [bCreateNamespace=true] whether to create the parent namespace
*
* @public
* @static
*/
jQuery.sap.declare = function(sModuleName, bCreateNamespace) {
var sNamespaceObj = sModuleName;
// check for an object as parameter for sModuleName
// in case of this the object contains the module name and the type
// which could be {modName: "sap.ui.core.Dev", type: "view"}
if (typeof (sModuleName) === "object") {
sNamespaceObj = sModuleName.modName;
sModuleName = ui5ToRJS(sModuleName.modName) + (sModuleName.type ? "." + sModuleName.type : "") + ".js";
} else {
sModuleName = ui5ToRJS(sModuleName) + ".js";
}
declareModule(sModuleName);
// ensure parent namespace even if module was declared already
// (as declare might have been called by require)
if (bCreateNamespace !== false) {
// ensure parent namespace
jQuery.sap.getObject(sNamespaceObj, 1);
}
return this;
};
/**
* Ensures that the given module is loaded and executed before execution of the
* current script continues.
*
* By issuing a call to this method, the caller declares a dependency to the listed modules.
*
* Any required and not yet loaded script will be loaded and execute synchronously.
* Already loaded modules will be skipped.
*
* @param {...string | object} vModuleName one or more names of modules to be loaded
* or in case of an object {modName: "...", type: "..."}
* where modName is the name of the module and the type
* could be a specific dot separated extension e.g.
* {modName: "sap.ui.core.Dev", type: "view"}
* loads sap/ui/core/Dev.view.js
and
* registers as sap.ui.core.Dev.view
*
* @public
* @static
* @function
* @SecSink {0|PATH} Parameter is used for future HTTP requests
*/
jQuery.sap.require = function(vModuleName, fnCallback) {
if ( arguments.length > 1 ) {
// legacy mode with multiple arguments, each representing a dependency
for (var i = 0; i < arguments.length; i++) {
jQuery.sap.require(arguments[i]);
}
return this;
}
// check for an object as parameter for sModuleName
// in case of this the object contains the module name and the type
// which could be {modName: "sap.ui.core.Dev", type: "view"}
if (typeof (vModuleName) === "object") {
jQuery.sap.assert(!vModuleName.type || jQuery.inArray(vModuleName.type, mKnownSubtypes.js) >= 0, "type must be empty or one of " + mKnownSubtypes.js.join(", "));
vModuleName = ui5ToRJS(vModuleName.modName) + (vModuleName.type ? "." + vModuleName.type : "") + ".js";
} else {
vModuleName = ui5ToRJS(vModuleName) + ".js";
}
requireModule(vModuleName);
return this; // TODO
};
/**
* UI5 internal method that loads the given module, specified in requireJS notation (URL like, without extension).
*
* Applications MUST NOT USE THIS METHOD as it will be removed in one of the future versions.
* It is only intended for sap.ui.component.
*
* @param {string} sModuleName Module name in requireJS syntax
* @private
*/
jQuery.sap._requirePath = function(sModuleName) {
requireModule(sModuleName + ".js");
};
window.sap = window.sap || {};
sap.ui = sap.ui || {};
/**
* Defines a Javascript module with its name, its dependencies and a module value or factory.
*
* The typical and only suggested usage of this method is to have one single, top level call to
* sap.ui.define
in one Javascript resource (file). When a module is requested by its
* name for the first time, the corresponding resource is determined from the name and the current
* {@link jQuery.sap.registerResourcePath configuration}. The resource will be loaded and executed
* which in turn will execute the top level sap.ui.define
call.
*
* If the module name was omitted from that call, it will be substituted by the name that was used to
* request the module. As a preparation step, the dependencies as well as their transitive dependencies,
* will be loaded. Then, the module value will be determined: if a static value (object, literal) was
* given, that value will be the module value. If a function was given, that function will be called
* (providing the module values of the declared dependencies as parameters to the function) and its
* return value will be used as module value. The framework internally associates the resulting value
* with the module name and provides it to the original requestor of the module. Whenever the module
* is requested again, the same value will be returned (modules are executed only once).
*
* Example:* sap.ui.define(['./Helper', 'sap/m/Bar'], function(Helper,Bar) { * * // create a new class * var SomeClass = function(); * * // add methods to its prototype * SomeClass.prototype.foo = function() { * * // use a function from the dependency 'Helper' in the same package (e.g. 'sap/mylib/Helper' ) * var mSettings = Helper.foo(); * * // create and return a sap.m.Bar (using its local name 'Bar') * return new Bar(mSettings); * * } * * // return the class as module value * return SomeClass; * * }); ** * In another module or in an application HTML page, the {@link sap.ui.require} API can be used * to load the Something module and to work with it: * *
* sap.ui.require(['sap/mylib/Something'], function(Something) { * * // instantiate a Something and call foo() on it * new Something().foo(); * * }); ** * Module Name Syntax
sap.ui.define
uses a simplified variant of the {@link jQuery.sap.getResourcePath
* unified resource name} syntax for the module's own name as well as for its dependencies.
* The only difference to that syntax is, that for sap.ui.define
and
* sap.ui.require
, the extension (which always would be '.js') has to be omitted.
* Both methods always add this extension internally.
*
* As a convenience, the name of a dependency can start with the segment './' which will be
* replaced by the name of the package that contains the currently defined module (relative name).
*
* It is best practice to omit the name of the defined module (first parameter) and to use
* relative names for the dependencies whenever possible. This reduces the necessary configuration,
* simplifies renaming of packages and allows to map them to a different namespace.
*
*
* Dependency to Modulessap.ui.define
) cannot
* depend on the module values of the depency modules. Instead, modules can use a factory function,
* calculate the static value in that function, potentially based on the dependencies, and return
* the result as module value. The same approach must be taken when the module value is supposed
* to be a function.
*
*
* Asynchronous Contractsap.ui.define
is designed to support real Asynchronous Module Definitions (AMD)
* in future, although it internally still uses the the old synchronous module loading of UI5.
* Callers of sap.ui.define
therefore must not rely on any synchronous behavior
* that they might observe with the current implementation.
*
* For example, callers of sap.ui.define
must not use the module value immediately
* after invoking sap.ui.define
:
*
* * // COUNTER EXAMPLE HOW __NOT__ TO DO IT * * // define a class Something as AMD module * sap.ui.define('Something', [], function() { * var Something = function(); * return Something; * }); * * // DON'T DO THAT! * // accessing the class _synchronously_ after sap.ui.define was called * new Something(); ** * Applications that need to ensure synchronous module definition or synchronous loading of dependencies * MUST use the old {@link jQuery.sap.declare} and {@link jQuery.sap.require} APIs. * * * (No) Global References
sap.ui.define
* should not make any use of global variables if those variables are also available as module
* values. Instead, they should add dependencies to those modules and use the corresponding parameter
* of the factory function to access the module value.
*
* As the current programming model and the documentation of UI5 heavily rely on global names,
* there will be a transition phase where UI5 enables AMD modules and local references to module
* values in parallel to the old global names. The fourth parameter of sap.ui.define
* has been added to support that transition phase. When this parameter is set to true, the framework
* provides two additional functionalities
*
* sap.ui.define
call. They will be requested and executed like UI5 modules, but their
* module value will be undefined
.
*
* If the currently defined module needs to access the module value of such a third party module,
* it can access the value via its global name (if the module supports such a usage).
*
* Note that UI5 temporarily deactivates an existing AMD loader while it executes third party modules
* known to support AMD. This sounds contradictarily at a first glance as UI5 wants to support AMD,
* but for now it is necessary to fully support UI5 apps that rely on global names for such modules.
*
* Example:
* * // module 'Something' wants to use third party library 'URI.js' * // It is packaged by UI5 as non-UI5-module 'sap/ui/thirdparty/URI' * * sap.ui.define('Something', ['sap/ui/thirdparty/URI'], function(URIModuleValue) { * * new URIModuleValue(); // fails as module value is undefined * * //global URI // (optional) declare usage of global name so that static code checks don't complain * new URI(); // access to global name 'URI' works * * ... * }); ** * * Differences to requireJS
sap.ui.define
differs from requireJS
* or other AMD loaders in several aspects:
* sap.ui.define
is different from the plain define
.
* This has two reasons: first, it avoids the impression that sap.ui.define
is
* an exact implementation of an AMD loader. And second, it allows the coexistence of an AMD
* loader (requireJS) and sap.ui.define
in one application as long as UI5 or
* apps using UI5 are not fully prepared to run with an AMD loadersap.ui.define
currently loads modules with synchronous XHR calls. This is
* basically a tribute to the synchronous history of UI5.
* BUT: synchronous dependency loading and factory execution explicitly it not part of
* contract of sap.ui.define
. To the contrary, it is already clear and planned
* that asynchronous loading will be implemented, at least as an alternative if not as the only
* implementation. Also check section Asynchronous Contract above.sap.ui.define
does not support plugins to use other file types, formats or
* protocols. It is not planned to support this in futuresap.ui.define
does not support the 'sugar' of requireJS where CommonJS
* style dependency declarations using sap.ui.require("something")
are automagically
* converted into sap.ui.define
dependencies before executing the factory function.sap.ui.define
does not support the '../' prefix for module names. Only
* relative names in the same package or in subpackages thereof are supported.sap.ui.define
uses
* synchronous loading. It will be enhanced when asynchronous loading is implemented.jQuery.sap.require
call can load a sap.ui.define
'ed module.
* If the module definition would not execute synchronously, the synchronous contract of the
* require call would be broken (default behavior in existing UI5 apps)sap.ui.define
. Multiple calls
* currently are only supported in the so called 'preload' files that the UI5 merge tooling produces.
* The exact details of how this works might be changed in future implementations and are not
* yet part of the API contractundefined
is returned.
* This signature variant allows synchronous access to module values without initiating module loading.
*
* Sample:
* * var JSONModel = sap.ui.require("sap/ui/model/json/JSONModel"); ** * For modules that are known to be UI5 modules, this signature variant can be used to check whether * the module has been loaded. * * Asynchronous Loading of Multiple Modules * * If an array of strings is given and (optionally) a callback function, then the strings * are interpreted as module names and the corresponding modules (and their transitive * dependencies) are loaded. Then the callback function will be called asynchronously. * The module values of the specified modules will be provided as parameters to the callback * function in the same order in which they appeared in the dependencies array. * * The return value for the asynchronous use case is
undefined
.
*
* * sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/UIComponent'], function(JSONModel,UIComponent) { * * var MyComponent = UIComponent.extend('MyComponent', { * ... * }); * ... * * }); ** * This method uses the same variation of the {@link jQuery.sap.getResourcePath unified resource name} * syntax that {@link sap.ui.define} uses: module names are specified without the implicit extension '.js'. * Relative module names are not supported. * * @param {string|string[]} vDependencies dependency (dependencies) to resolve * @param {function} [fnCallback] callback function to execute after resolving an array of dependencies * @returns {any|undefined} a single module value or undefined * @public * @experimental Since 1.27.0 - not all aspects of sap.ui.require are settled yet. E.g. the return value * of the asynchronous use case might change (currently it is undefined). */ sap.ui.require = function(vDependencies, fnCallback) { jQuery.sap.assert(typeof vDependencies === 'string' || jQuery.isArray(vDependencies), "dependency param either must be a single string or an array of strings"); jQuery.sap.assert(fnCallback == null || typeof fnCallback === 'function', "callback must be a function or null/undefined"); if ( typeof vDependencies === 'string' ) { var sModuleName = vDependencies + '.js', oModule = mModules[sModuleName]; return oModule ? (oModule.content || jQuery.sap.getObject(urnToUI5(sModuleName))) : undefined; } requireAll(vDependencies, function(aModules) { if ( typeof fnCallback === 'function' ) { // enforce asynchronous execution of callback setTimeout(function() { fnCallback.apply(window, aModules); },0); } }); // return undefined; }; jQuery.sap.preloadModules = function(sPreloadModule, bAsync, oSyncPoint) { var sURL, iTask; jQuery.sap.assert(!bAsync || oSyncPoint, "if mode is async, a syncpoint object must be given"); if ( mPreloadModules[sPreloadModule] ) { return; } mPreloadModules[sPreloadModule] = true; sURL = jQuery.sap.getModulePath(sPreloadModule, ".json"); log.debug("preload file " + sPreloadModule); iTask = oSyncPoint && oSyncPoint.startTask("load " + sPreloadModule); jQuery.ajax({ dataType : "json", async : bAsync, url : sURL, success : function(data) { if ( data ) { data.url = sURL; } jQuery.sap.registerPreloadedModules(data, bAsync, oSyncPoint); oSyncPoint && oSyncPoint.finishTask(iTask); }, error : function(xhr, textStatus, error) { log.error("failed to preload '" + sPreloadModule + "': " + (error || textStatus)); oSyncPoint && oSyncPoint.finishTask(iTask, false); } }); }; jQuery.sap.registerPreloadedModules = function(oData, bAsync, oSyncPoint) { var bOldSyntax = Version(oData.version || "1.0").compareTo("2.0") < 0; if ( log.isLoggable() ) { log.debug(sLogPrefix + "adding preloaded modules from '" + oData.url + "'"); } if ( oData.name ) { mPreloadModules[oData.name] = true; } jQuery.each(oData.modules, function(sName,sContent) { sName = bOldSyntax ? ui5ToRJS(sName) + ".js" : sName; if ( !mModules[sName] ) { mModules[sName] = { state : PRELOADED, url : oData.url + "/" + sName, data : sContent, group: oData.name }; } // when a library file is preloaded, also mark its preload file as loaded // for normal library preload, this is redundant, but for non-default merged entities // like sap/fiori/core.js it avoids redundant loading of library preload files if ( sName.match(/\/library\.js$/) ) { mPreloadModules[urnToUI5(sName) + "-preload"] = true; } }); if ( oData.dependencies ) { jQuery.each(oData.dependencies, function(idx,sModuleName) { jQuery.sap.preloadModules(sModuleName, bAsync, oSyncPoint); }); } }; /** * Removes a set of resources from the resource cache. * * @param {string} sName unified resource name of a resource or the name of a preload group to be removed * @param {boolean} [bPreloadGroup=true] whether the name specifies a preload group, defaults to true * @param {boolean} [bUnloadAll] Whether all matching resources should be unloaded, even if they have been executed already. * @param {boolean} [bDeleteExports] Whether exportss (global variables) should be destroyed as well. Will be done for UI5 module names only. * @experimental Since 1.16.3 API might change completely, apps must not develop against it. * @private */ jQuery.sap.unloadResources = function(sName, bPreloadGroup, bUnloadAll, bDeleteExports) { var aModules = []; if ( bPreloadGroup == null ) { bPreloadGroup = true; } if ( bPreloadGroup ) { // collect modules that belong to the given group jQuery.each(mModules, function(sURN, oModule) { if ( oModule && oModule.group === sName ) { aModules.push(sURN); } }); // also remove a preload entry delete mPreloadModules[sName]; } else { // single module if ( mModules[sName] ) { aModules.push(sName); } } jQuery.each(aModules, function(i, sURN) { var oModule = mModules[sURN]; if ( oModule && bDeleteExports && sURN.match(/\.js$/) ) { jQuery.sap.setObject(urnToUI5(sURN), undefined); // TODO really delete property } if ( oModule && (bUnloadAll || oModule.state === PRELOADED) ) { delete mModules[sURN]; } }); }; /** * Converts a UI5 module name to a unified resource name. * * Used by View and Fragment APIs to convert a given module name into an URN. * * @experimental Since 1.16.0, not for public usage yet. * @private */ jQuery.sap.getResourceName = function(sModuleName, sSuffix) { return ui5ToRJS(sModuleName) + (sSuffix || ".js"); }; /** * Retrieves the resource with the given name, either from the preload cache or from * the server. The expected data type of the resource can either be specified in the * options (
dataType
) or it will be derived from the suffix of the sResourceName
.
* The only supported data types so far are xml, html, json and text. If the resource name extension
* doesn't match any of these extensions, the data type must be specified in the options.
*
* If the resource is found in the preload cache, it will be converted from text format
* to the requested dataType
using a converter from jQuery.ajaxSettings.converters
.
*
* If it is not found, the resource name will be converted to a resource URL (using {@link #getResourcePath})
* and the resulting URL will be requested from the server with a synchronous jQuery.ajax call.
*
* If the resource was found in the local preload cache and any necessary conversion succeeded
* or when the resource was retrieved from the backend successfully, the content of the resource will
* be returned. In any other case, an exception will be thrown, or if option failOnError is set to true,
* null
will be returned.
*
* Future implementations of this API might add more options. Generic implementations that accept an
* mOptions
object and propagate it to this function should limit the options to the currently
* defined set of options or they might fail for unknown options.
*
* For asynchronous calls the return value of this method is an ECMA Script 6 Promise object which callbacks are triggered
* when the resource is ready:
* If failOnError
is false
the catch callback of the promise is not called. The argument given to the fullfilled
* callback is null in error case.
* If failOnError
is true
the catch callback will be triggered. The argument is an Error object in this case.
*
* @param {string} [sResourceName] resourceName in unified resource name syntax
* @param {object} [mOptions] options
* @param {object} [mOptions.dataType] one of "xml", "html", "json" or "text". If not specified it will be derived from the resource name (extension)
* @param {string} [mOptions.name] unified resource name of the resource to load (alternative syntax)
* @param {string} [mOptions.url] url of a resource to load (alternative syntax, name will only be a guess)
* @param {string} [mOptions.headers] Http headers for an eventual XHR request
* @param {string} [mOptions.failOnError=true] whether to propagate load errors or not
* @param {string} [mOptions.async=false] whether the loading should be performed asynchronously.
* @return {string|Document|object|Promise} content of the resource. A string for text or html, an Object for JSON, a Document for XML. For asynchronous calls an ECMA Script 6 Promise object will be returned.
* @throws Error if loading the resource failed
* @private
* @experimental API is not yet fully mature and may change in future.
* @since 1.15.1
*/
jQuery.sap.loadResource = function(sResourceName, mOptions) {
var sType,
oData,
sUrl,
oError,
oDeferred;
if ( typeof sResourceName === "string" ) {
mOptions = mOptions || {};
} else {
mOptions = sResourceName || {};
sResourceName = mOptions.name;
if ( !sResourceName && mOptions.url) {
sResourceName = guessResourceName(mOptions.url);
}
}
// defaulting
mOptions = jQuery.extend({ failOnError: true, async: false }, mOptions);
sType = mOptions.dataType;
if ( sType == null && sResourceName ) {
sType = (sType = rTypes.exec(sResourceName)) && sType[1];
}
jQuery.sap.assert(/^(xml|html|json|text)$/.test(sType), "type must be one of xml, html, json or text");
oDeferred = mOptions.async ? new jQuery.Deferred() : null;
function handleData(d, e) {
if ( d == null && mOptions.failOnError ) {
e = e || new Error("no data returned for " + sResourceName);
if (mOptions.async) {
oDeferred.reject(e);
jQuery.sap.log.error(e);
return d;
}
throw e;
}
if (mOptions.async) {
oDeferred.resolve(d);
}
return d;
}
function convertData(d) {
var vConverter = jQuery.ajaxSettings.converters["text " + sType];
if ( typeof vConverter === "function" ) {
d = vConverter(d);
}
return handleData(d);
}
if ( sResourceName && mModules[sResourceName] ) {
oData = mModules[sResourceName].data;
mModules[sResourceName].state = LOADED;
}
if ( oData != null ) {
if (mOptions.async) {
//Use timeout to simulate async behavior for this sync case for easier usage
setTimeout(function(){
convertData(oData);
}, 0);
} else {
oData = convertData(oData);
}
} else {
jQuery.ajax({
url : sUrl = mOptions.url || getResourcePath(sResourceName),
async : mOptions.async,
dataType : sType,
headers: mOptions.headers,
success : function(data, textStatus, xhr) {
oData = handleData(data);
},
error : function(xhr, textStatus, error) {
oError = new Error("resource " + sResourceName + " could not be loaded from " + sUrl + ". Check for 'file not found' or parse errors. Reason: " + error);
oError.status = textStatus;
oError.error = error;
oError.statusCode = xhr.status;
oData = handleData(null, oError);
}
});
}
return mOptions.async ? window.Promise.resolve(oDeferred) : oData;
};
/*
* register a global event handler to detect script execution errors.
* Only works for browsers that support document.currentScript.
* /
window.addEventListener("error", function(e) {
if ( document.currentScript && document.currentScript.dataset.sapUiModule ) {
var error = {
message: e.message,
filename: e.filename,
lineno: e.lineno,
colno: e.colno
};
document.currentScript.dataset.sapUiModuleError = JSON.stringify(error);
}
});
*/
/**
* Loads the given Javascript resource (URN) asynchronously via as script tag.
* Returns a promise that will be resolved when the load event is fired or reject
* when the error event is fired.
*
* Note: execution errors of the script are not reported as 'error'.
*
* This method is not a full implementation of require. It is intended only for
* loading "preload" files that do not define an own module / module value.
*
* Functionality might be removed/renamed in future, so no code outside the
* sap.ui.core library must use it.
*
* @experimental
* @private
*/
jQuery.sap._loadJSResourceAsync = function(sResource, bIgnoreErrors) {
return new Promise(function(resolve,reject) {
var oModule = mModules[sResource] || (mModules[sResource] = { state : INITIAL });
var sUrl = oModule.url = getResourcePath(sResource);
oModule.state = LOADING;
var oScript = window.document.createElement('SCRIPT');
oScript.src = sUrl;
oScript.dataset.sapUiModule = sResource;
oScript.dataset.sapUiModuleError = '';
oScript.addEventListener('load', function(e) {
jQuery.sap.log.info("Javascript resource loaded: " + sResource);
// TODO either find a cross-browser solution to detect and assign execution errros or document behavior
// var error = e.target.dataset.sapUiModuleError;
// if ( error ) {
// oModule.state = FAILED;
// oModule.error = JSON.parse(error);
// jQuery.sap.log.error("failed to load Javascript resource: " + sResource + ":" + error);
// reject(oModule.error);
// }
oModule.state = READY;
// TODO oModule.data = ?
resolve();
});
oScript.addEventListener('error', function(e) {
jQuery.sap.log.error("failed to load Javascript resource: " + sResource);
oModule.state = FAILED;
// TODO oModule.error = xhr ? xhr.status + " - " + xhr.statusText : textStatus;
if ( bIgnoreErrors ) {
resolve();
} else {
reject();
}
});
appendHead(oScript);
});
};
return function() {
//remove final information in mUrlPrefixes
var mFlatUrlPrefixes = {};
jQuery.each(mUrlPrefixes, function(sKey,oUrlPrefix) {
mFlatUrlPrefixes[sKey] = oUrlPrefix.url;
});
return { modules : mModules, prefixes : mFlatUrlPrefixes };
};
}());
// --------------------- script and stylesheet handling --------------------------------------------------
// appends a link object to the head
function appendHead(oElement) {
var head = window.document.getElementsByTagName("head")[0];
if (head) {
head.appendChild(oElement);
}
}
/**
* Includes the script (via <script>-tag) into the head for the
* specified sUrl
and optional sId
.
* includeStylesheet
providing the sId of an already included stylesheet, the existing element will be
* replaced.
*
* @param {string}
* sUrl the URL of the script to load
* @param {string}
* [sId] id that should be used for the script include tag
* @param {function}
* [fnLoadCallback] callback function to get notified once the link has been loaded
* @param {function}
* [fnErrorCallback] callback function to get notified once the link loading failed.
* In case of usage in IE the error callback will also be executed if an empty stylesheet
* is loaded. This is the only option how to determine in IE if the load was successful
* or not since the native onerror callback for link elements doesn't work in IE. The IE
* always calls the onload callback of the link element.
*
* @public
* @static
* @SecSink {0|PATH} Parameter is used for future HTTP requests
*/
jQuery.sap.includeStyleSheet = function includeStyleSheet(sUrl, sId, fnLoadCallback, fnErrorCallback) {
var _createLink = function(sUrl, sId, fnLoadCallback, fnErrorCallback){
// create the new link element
var oLink = document.createElement("link");
oLink.type = "text/css";
oLink.rel = "stylesheet";
oLink.href = sUrl;
if (sId) {
oLink.id = sId;
}
var fnError = function() {
jQuery(oLink).attr("sap-ui-ready", "false");
if (fnErrorCallback) {
fnErrorCallback();
}
};
var fnLoad = function() {
jQuery(oLink).attr("sap-ui-ready", "true");
if (fnLoadCallback) {
fnLoadCallback();
}
};
// for IE we will check if the stylesheet contains any rule and then
// either trigger the load callback or the error callback
if (!!sap.ui.Device.browser.internet_explorer) {
var fnLoadOrg = fnLoad;
fnLoad = function(oEvent) {
var aRules;
try {
// in cross-origin scenarios the IE can still access the rules of the stylesheet
// if the stylesheet has been loaded properly
aRules = oEvent.target && oEvent.target.sheet && oEvent.target.sheet.rules;
// in cross-origin scenarios now the catch block will be executed because we
// cannot access the rules of the stylesheet but for non cross-origin stylesheets
// we will get an empty rules array and finally we cannot differ between
// empty stylesheet or loading issue correctly => documented in JSDoc!
} catch (ex) {
// exception happens when the stylesheet could not be loaded from the server
// we now ignore this and know that the stylesheet doesn't exists => trigger error
}
// no rules means error
if (aRules && aRules.length > 0) {
fnLoadOrg();
} else {
fnError();
}
};
}
jQuery(oLink).load(fnLoad);
jQuery(oLink).error(fnError);
return oLink;
};
var _appendStyle = function(sUrl, sId, fnLoadCallback, fnErrorCallback){
if (sap.ui.Device.browser.internet_explorer && sap.ui.Device.browser.version <= 9 && document.styleSheets.length >= 28) {
// in IE9 only 30 links are alowed, so use stylesheet object insted
var sRootUrl = URI.parse(document.URL).path;
var sAbsoluteUrl = new URI(sUrl).absoluteTo(sRootUrl).toString();
if (sId) {
var oIEStyleSheet = mIEStyleSheets[sId];
if (oIEStyleSheet && oIEStyleSheet.href === sAbsoluteUrl) {
// if stylesheet was already included and href is the same, do nothing
return;
}
}
jQuery.sap.log.warning("Stylesheet " + (sId ? sId + " " : "") + "not added as LINK because of IE limits", sUrl, "jQuery.sap.includeStyleSheet");
if (!oIEStyleSheetNode) {
// create a style sheet to add additional style sheet. But for this the Replace logic will not work any more
// the callback functions are not used in this case
// the sap-ui-ready attribute will not be set -> maybe problems with ThemeCheck
oIEStyleSheetNode = document.createStyleSheet();
}
// add up to 30 style sheets to every of this style sheets. (result is a tree of style sheets)
var bAdded = false;
for ( var i = 0; i < oIEStyleSheetNode.imports.length; i++) {
var oStyleSheet = oIEStyleSheetNode.imports[i];
if (oStyleSheet.imports.length < 30) {
oStyleSheet.addImport(sAbsoluteUrl);
bAdded = true;
break;
}
}
if (!bAdded) {
oIEStyleSheetNode.addImport(sAbsoluteUrl);
}
if (sId) {
// remember id and href URL in internal map as there is no link tag that can be checked
mIEStyleSheets[sId] = {
href: sAbsoluteUrl
};
}
// always make sure to re-append the customcss in the end if it exists
var oCustomCss = document.getElementById('sap-ui-core-customcss');
if (!jQuery.isEmptyObject(oCustomCss)) {
appendHead(oCustomCss);
}
} else {
var oLink = _createLink(sUrl, sId, fnLoadCallback, fnErrorCallback);
if (jQuery('#sap-ui-core-customcss').length > 0) {
jQuery('#sap-ui-core-customcss').first().before(jQuery(oLink));
} else {
appendHead(oLink);
}
}
};
// check for existence of the link
var oOld = jQuery.sap.domById(sId);
if (oOld && oOld.tagName === "LINK" && oOld.rel === "stylesheet") {
// link exists, so we replace it - but only if a callback has to be attached or if the href will change. Otherwise don't touch it
if (fnLoadCallback || fnErrorCallback || oOld.href !== URI(String(sUrl), URI().search("") /* returns current URL without search params */ ).toString()) {
jQuery(oOld).replaceWith(_createLink(sUrl, sId, fnLoadCallback, fnErrorCallback));
}
} else {
_appendStyle(sUrl, sId, fnLoadCallback, fnErrorCallback);
}
};
// TODO should be in core, but then the 'callback' could not be implemented
if ( !(oCfgData.productive === true || oCfgData.productive === "true" || oCfgData.productive === "x") ) {
jQuery(function() {
jQuery(document.body).keydown(function(e) {
if ( e.keyCode == 80 && e.shiftKey && e.altKey && e.ctrlKey ) {
try {
jQuery.sap.require("sap.ui.debug.TechnicalInfo");
} catch (err1) {
// alert("Sorry, failed to activate 'P'-mode!");
return;
}
sap.ui.debug.TechnicalInfo.open(function() {
var oInfo = getModuleSystemInfo();
return { modules : oInfo.modules, prefixes : oInfo.prefixes, config: oCfgData };
});
}
});
});
jQuery(function() {
jQuery(document.body).keydown(function(e) {
if ( e.keyCode == 83 /*S*/ && e.shiftKey && e.altKey && e.ctrlKey ) { //TODO: Is this ok?
try {
jQuery.sap.require("sap.ui.core.support.Support");
var oSupport = sap.ui.core.support.Support.getStub();
if (oSupport.getType() != sap.ui.core.support.Support.StubType.APPLICATION) {
return;
}
oSupport.openSupportTool();
} catch (err2) {
}
}
});
});
}
// *********** Include E2E-Trace Scripts *************
if (/sap-ui-xx-e2e-trace=(true|x|X)/.test(location.search)) {
jQuery.sap.require("sap.ui.core.support.trace.E2eTraceLib" + "" /* Make dynamic dependency */);
}
// *********** feature detection, enriching jQuery.support *************
// this might go into its own file once there is more stuff added
/**
* Holds information about the browser's capabilities and quirks.
* This object is provided and documented by jQuery.
* But it is extended by SAPUI5 with detection for features not covered by jQuery. This documentation ONLY covers the detection properties added by UI5.
* For the standard detection properties, please refer to the jQuery documentation.
*
* These properties added by UI5 are only available temporarily until jQuery adds feature detection on their own.
*
* @name jQuery.support
* @namespace
* @since 1.12
* @public
*/
if (!jQuery.support) {
jQuery.support = {};
}
jQuery.extend(jQuery.support, {touch: sap.ui.Device.support.touch}); // this is also defined by jquery-mobile-custom.js, but this information is needed earlier
var aPrefixes = ["Webkit", "ms", "Moz"];
var oStyle = document.documentElement.style;
var preserveOrTestCssPropWithPrefixes = function(detectionName, propName) {
if (jQuery.support[detectionName] === undefined) {
if (oStyle[propName] !== undefined) { // without vendor prefix
jQuery.support[detectionName] = true;
// If one of the flex layout properties is supported without the prefix, set the flexBoxPrefixed to false
if (propName === "boxFlex" || propName === "flexOrder" || propName === "flexGrow") {
// Exception for Chrome up to version 28
// because some versions implemented the non-prefixed properties without the functionality
if (!sap.ui.Device.browser.chrome || sap.ui.Device.browser.version > 28) {
jQuery.support.flexBoxPrefixed = false;
}
}
return;
} else { // try vendor prefixes
propName = propName.charAt(0).toUpperCase() + propName.slice(1);
for (var i in aPrefixes) {
if (oStyle[aPrefixes[i] + propName] !== undefined) {
jQuery.support[detectionName] = true;
return;
}
}
}
jQuery.support[detectionName] = false;
}
};
/**
* Whether the current browser supports (2D) CSS transforms
* @type {boolean}
* @public
* @name jQuery.support.cssTransforms
*/
preserveOrTestCssPropWithPrefixes("cssTransforms", "transform");
/**
* Whether the current browser supports 3D CSS transforms
* @type {boolean}
* @public
* @name jQuery.support.cssTransforms3d
*/
preserveOrTestCssPropWithPrefixes("cssTransforms3d", "perspective");
/**
* Whether the current browser supports CSS transitions
* @type {boolean}
* @public
* @name jQuery.support.cssTransitions
*/
preserveOrTestCssPropWithPrefixes("cssTransitions", "transition");
/**
* Whether the current browser supports (named) CSS animations
* @type {boolean}
* @public
* @name jQuery.support.cssAnimations
*/
preserveOrTestCssPropWithPrefixes("cssAnimations", "animationName");
/**
* Whether the current browser supports CSS gradients. Note that ANY support for CSS gradients leads to "true" here, no matter what the syntax is.
* @type {boolean}
* @public
* @name jQuery.support.cssGradients
*/
if (jQuery.support.cssGradients === undefined) {
var oElem = document.createElement('div'),
oStyle = oElem.style;
try {
oStyle.backgroundImage = "linear-gradient(left top, red, white)";
oStyle.backgroundImage = "-moz-linear-gradient(left top, red, white)";
oStyle.backgroundImage = "-webkit-linear-gradient(left top, red, white)";
oStyle.backgroundImage = "-ms-linear-gradient(left top, red, white)";
oStyle.backgroundImage = "-webkit-gradient(linear, left top, right bottom, from(red), to(white))";
} catch (e) {/* no support...*/}
jQuery.support.cssGradients = (oStyle.backgroundImage && oStyle.backgroundImage.indexOf("gradient") > -1);
oElem = null; // free for garbage collection
}
/**
* Whether the current browser supports only prefixed flexible layout properties
* @type {boolean}
* @public
* @name jQuery.support.flexBoxPrefixed
*/
jQuery.support.flexBoxPrefixed = true; // Default to prefixed properties
/**
* Whether the current browser supports the OLD CSS3 Flexible Box Layout directly or via vendor prefixes
* @type {boolean}
* @public
* @name jQuery.support.flexBoxLayout
*/
preserveOrTestCssPropWithPrefixes("flexBoxLayout", "boxFlex");
/**
* Whether the current browser supports the IE10 CSS3 Flexible Box Layout directly or via vendor prefixes
* @type {boolean}
* @public
* @name jQuery.support.ie10FlexBoxLayout
* @since 1.12.0
*/
// Just using one of the IE10 properties that's not in the new FlexBox spec
if (oStyle.msFlexOrder !== undefined) {
jQuery.support.ie10FlexBoxLayout = true;
}
/**
* Whether the current browser supports the NEW CSS3 Flexible Box Layout directly or via vendor prefixes
* @type {boolean}
* @public
* @name jQuery.support.newFlexBoxLayout
*/
preserveOrTestCssPropWithPrefixes("newFlexBoxLayout", "flexGrow"); // Use a new property that IE10 doesn't support
/**
* Whether the current browser supports any kind of Flexible Box Layout directly or via vendor prefixes
* @type {boolean}
* @public
* @name jQuery.support.hasFlexBoxSupport
*/
if (jQuery.support.flexBoxLayout || jQuery.support.newFlexBoxLayout || jQuery.support.ie10FlexBoxLayout) {
jQuery.support.hasFlexBoxSupport = true;
} else {
jQuery.support.hasFlexBoxSupport = false;
}
// *********** fixes for (pending) jQuery bugs **********
if (!jQuery.support.opacity) {
(function() {
// jQuery cssHook for setOpacity[IE8] doesn't properly cleanup the CSS filter property
var oldSet = jQuery.cssHooks.opacity.set;
jQuery.cssHooks.opacity.set = function( elem, value ) {
oldSet.apply(this, arguments);
if ( !jQuery.trim(elem.style.filter) ) {
elem.style.removeAttribute("filter");
}
};
}());
}
// *** Performance measure ***
function PerfMeasurement(){
function Measurement( sId, sInfo, iStart, iEnd ){
this.id = sId;
this.info = sInfo;
this.start = iStart;
this.end = iEnd;
this.pause = 0;
this.resume = 0;
this.duration = 0; // used time
this.time = 0; // time from start to end
}
var bActive = false;
var fnAjax = jQuery.ajax;
/**
* Gets the current state of the perfomance measurement functionality
*
* @return {boolean} current state of the perfomance measurement functionality
* @name jQuery.sap.measure#getActive
* @function
* @public
*/
this.getActive = function(){
return bActive;
};
/**
* Activates or deactivates the performance measure functionality
*
* @param {boolean} bOn state of the perfomance measurement functionality to set
* @return {boolean} current state of the perfomance measurement functionality
* @name jQuery.sap.measure#setActive
* @function
* @public
*/
this.setActive = function( bOn ){
if (bActive == bOn) {
return bActive;
}
bActive = bOn;
if (bActive) {
// redefine AJAX call
jQuery.ajax = function( url, options ){
jQuery.sap.measure.start(url.url, "Request for " + url.url);
fnAjax.apply(this,arguments);
jQuery.sap.measure.end(url.url);
};
} else if (fnAjax) {
jQuery.ajax = fnAjax;
}
return bActive;
};
this.setActive(/sap-ui-measure=(true|x|X)/.test(location.search));
this.mMeasurements = {};
/**
* Starts a performance measure
*
* @param {string} sId ID of the measurement
* @param {string} sInfo Info for the measurement
* @return {object} current measurement containing id, info and start-timestamp (false if error)
* @name jQuery.sap.measure#start
* @function
* @public
*/
this.start = function( sId, sInfo ){
if (!bActive) {
return;
}
var iTime = new Date().getTime();
var oMeasurement = new Measurement( sId, sInfo, iTime, 0);
// jQuery.sap.log.info("Performance measurement start: "+ sId + " on "+ iTime);
if (oMeasurement) {
this.mMeasurements[sId] = oMeasurement;
return ({id: oMeasurement.id, info: oMeasurement.info, start: oMeasurement.start });
} else {
return false;
}
};
/**
* Pauses a performance measure
*
* @param {string} sId ID of the measurement
* @return {object} current measurement containing id, info and start-timestamp, pause-timestamp (false if error)
* @name jQuery.sap.measure#pause
* @function
* @public
*/
this.pause = function( sId ){
if (!bActive) {
return;
}
var iTime = new Date().getTime();
var oMeasurement = this.mMeasurements[sId];
if (oMeasurement && oMeasurement.end > 0) {
// already ended -> no pause possible
return false;
}
if (oMeasurement && oMeasurement.pause == 0) {
// not already paused
oMeasurement.pause = iTime;
if (oMeasurement.pause >= oMeasurement.resume && oMeasurement.resume > 0) {
oMeasurement.duration = oMeasurement.duration + oMeasurement.pause - oMeasurement.resume;
oMeasurement.resume = 0;
} else if (oMeasurement.pause >= oMeasurement.start) {
oMeasurement.duration = oMeasurement.pause - oMeasurement.start;
}
}
// jQuery.sap.log.info("Performance measurement pause: "+ sId + " on "+ iTime + " duration: "+ oMeasurement.duration);
if (oMeasurement) {
return ({id: oMeasurement.id, info: oMeasurement.info, start: oMeasurement.start, pause: oMeasurement.pause });
} else {
return false;
}
};
/**
* Resumes a performance measure
*
* @param {string} sId ID of the measurement
* @return {object} current measurement containing id, info and start-timestamp, resume-timestamp (false if error)
* @name jQuery.sap.measure#resume
* @function
* @public
*/
this.resume = function( sId ){
if (!bActive) {
return;
}
var iTime = new Date().getTime();
var oMeasurement = this.mMeasurements[sId];
// jQuery.sap.log.info("Performance measurement resume: "+ sId + " on "+ iTime + " duration: "+ oMeasurement.duration);
if (oMeasurement && oMeasurement.pause > 0) {
// already paused
oMeasurement.pause = 0;
oMeasurement.resume = iTime;
}
if (oMeasurement) {
return ({id: oMeasurement.id, info: oMeasurement.info, start: oMeasurement.start, resume: oMeasurement.resume });
} else {
return false;
}
};
/**
* Ends a performance measure
*
* @param {string} sId ID of the measurement
* @return {object} current measurement containing id, info and start-timestamp, end-timestamp, time, duration (false if error)
* @name jQuery.sap.measure#end
* @function
* @public
*/
this.end = function( sId ){
if (!bActive) {
return;
}
var iTime = new Date().getTime();
var oMeasurement = this.mMeasurements[sId];
// jQuery.sap.log.info("Performance measurement end: "+ sId + " on "+ iTime);
if (oMeasurement && !oMeasurement.end) {
oMeasurement.end = iTime;
if (oMeasurement.end >= oMeasurement.resume && oMeasurement.resume > 0) {
oMeasurement.duration = oMeasurement.duration + oMeasurement.end - oMeasurement.resume;
oMeasurement.resume = 0;
} else if (oMeasurement.pause > 0) {
// duration already calculated
oMeasurement.pause = 0;
} else if (oMeasurement.end >= oMeasurement.start) {
oMeasurement.duration = oMeasurement.end - oMeasurement.start;
}
if (oMeasurement.end >= oMeasurement.start) {
oMeasurement.time = oMeasurement.end - oMeasurement.start;
}
}
if (oMeasurement) {
return ({id: oMeasurement.id,
info: oMeasurement.info,
start: oMeasurement.start,
end: oMeasurement.end,
time: oMeasurement.time,
duration: oMeasurement.duration});
} else {
return false;
}
};
/**
* Gets a performance measure
*
* @param {string} sId ID of the measurement
* @return {object} current measurement containing id, info and start-timestamp, end-timestamp, time, duration (false if error)
* @name jQuery.sap.measure#getMeasurement
* @function
* @public
*/
this.getMeasurement = function( sId ){
if (!bActive) {
return;
}
var oMeasurement = this.mMeasurements[sId];
if (oMeasurement) {
return ({id: oMeasurement.id,
info: oMeasurement.info,
start: oMeasurement.start,
end: oMeasurement.end,
time: oMeasurement.time,
duration: oMeasurement.duration});
} else {
return false;
}
};
/**
* Clears all performance measurements
*
* @name jQuery.sap.measure#clear
* @function
* @public
*/
this.clear = function( ){
if (!bActive) {
return;
}
this.mMeasurements = {};
};
/**
* Removes a performance measure
*
* @param {string} sId ID of the measurement
* @name jQuery.sap.measure#remove
* @function
* @public
*/
this.remove = function( sId ){
if (!bActive) {
return;
}
delete this.mMeasurements[sId];
};
/**
* Gets all performance measurements
*
* @return {object} [] current measurement containing id, info and start-timestamp, end-timestamp, time, duration (false if error)
* @name jQuery.sap.measure#getAllMeasurements
* @function
* @public
*/
this.getAllMeasurements = function( ){
if (!bActive) {
return;
}
var aMeasurements = [];
jQuery.each(this.mMeasurements, function(sId, oMeasurement){
aMeasurements.push({id: oMeasurement.id,
info: oMeasurement.info,
start: oMeasurement.start,
end: oMeasurement.end,
duration: oMeasurement.duration,
time: oMeasurement.time});
});
return aMeasurements;
};
/**
* Adds a performance measurement with all data
* This is usefull to add external measurements (e.g. from a backend) to the common measurement UI
*
* @param {string} sId ID of the measurement
* @param {string} sInfo Info for the measurement
* @param {int} iStart start timestamp
* @param {int} iEnd end timestamp
* @param {int} iTime time in milliseconds
* @param {int} iDuration effective time in milliseconds
* @return {object} [] current measurement containing id, info and start-timestamp, end-timestamp, time, duration (false if error)
* @name jQuery.sap.measure#add
* @function
* @public
*/
this.add = function( sId, sInfo, iStart, iEnd, iTime, iDuration ){
if (!bActive) {
return;
}
var oMeasurement = new Measurement( sId, sInfo, iStart, iEnd);
oMeasurement.time = iTime;
oMeasurement.duration = iDuration;
if (oMeasurement) {
this.mMeasurements[sId] = oMeasurement;
return ({id: oMeasurement.id,
info: oMeasurement.info,
start: oMeasurement.start,
end: oMeasurement.end,
time: oMeasurement.time,
duration: oMeasurement.duration});
} else {
return false;
}
};
}
/**
* Namespace for the jQuery performance measurement plug-in provided by SAP SE.
*
* @namespace
* @name jQuery.sap.measure
* @public
* @static
*/
jQuery.sap.measure = new PerfMeasurement();
/**
* FrameOptions class
*/
var FrameOptions = function(mSettings) {
/* mSettings: mode, callback, whitelist, whitelistService, timeout, blockEvents, showBlockLayer, allowSameOrigin */
this.mSettings = mSettings || {};
this.sMode = this.mSettings.mode || FrameOptions.Mode.ALLOW;
this.fnCallback = this.mSettings.callback;
this.iTimeout = this.mSettings.timeout || 10000;
this.bBlockEvents = this.mSettings.blockEvents !== false;
this.bShowBlockLayer = this.mSettings.showBlockLayer !== false;
this.bAllowSameOrigin = this.mSettings.allowSameOrigin !== false;
this.sParentOrigin = '';
this.bUnlocked = false;
this.bRunnable = false;
this.bParentUnlocked = false;
this.sStatus = "pending";
this.aFPChilds = [];
var that = this;
this.iTimer = setTimeout(function() {
that._callback(false);
}, this.iTimeout);
var fnHandlePostMessage = function() {
that._handlePostMessage.apply(that, arguments);
};
FrameOptions.__window.addEventListener('message', fnHandlePostMessage);
if (FrameOptions.__parent === FrameOptions.__self || FrameOptions.__parent == null || this.sMode === FrameOptions.Mode.ALLOW) {
// unframed page or "allow all" mode
this._applyState(true, true);
} else {
// framed page
this._lock();
// "deny" mode blocks embedding page from all origins
if (this.sMode === FrameOptions.Mode.DENY) {
this._callback(false);
return;
}
if (this.bAllowSameOrigin) {
try {
var oParentWindow = FrameOptions.__parent;
var bOk = false;
var bTrue = true;
do {
var test = oParentWindow.document.domain;
if (oParentWindow == FrameOptions.__top) {
if (test != undefined) {
bOk = true;
}
break;
}
oParentWindow = oParentWindow.parent;
} while (bTrue);
if (bOk) {
this._applyState(true, true);
}
} catch(e) {
// access to the top window is not possible
FrameOptions.__parent.postMessage('SAPFrameProtection*require-origin', '*');
}
} else {
// same origin not allowed
FrameOptions.__parent.postMessage('SAPFrameProtection*require-origin', '*');
}
}
};
FrameOptions.Mode = {
// only allow with same origin parent
TRUSTED: 'trusted',
// allow all kind of embedding (default)
ALLOW: 'allow',
// deny all kinds of embedding
DENY: 'deny'
};
// Allow globals to be mocked in unit test
FrameOptions.__window = window;
FrameOptions.__parent = parent;
FrameOptions.__self = self;
FrameOptions.__top = top;
// List of events to block while framing is unconfirmed
FrameOptions._events = [
"mousedown", "mouseup", "click", "dblclick", "mouseover", "mouseout",
"touchstart", "touchend", "touchmove", "touchcancel",
"keydown", "keypress", "keyup"
];
// check if string matches pattern
FrameOptions.prototype.match = function(sProbe, sPattern) {
if (!(/\*/i.test(sPattern))) {
return sProbe == sPattern;
} else {
sPattern = sPattern.replace(/\//gi, "\\/"); // replace / with \/
sPattern = sPattern.replace(/\./gi, "\\."); // replace . with \.
sPattern = sPattern.replace(/\*/gi, ".*"); // replace * with .*
sPattern = sPattern.replace(/:\.\*$/gi, ":\\d*"); // replace :.* with :\d* (only at the end)
if (sPattern.substr(sPattern.length - 1, 1) !== '$') {
sPattern = sPattern + '$'; // if not already there add $ at the end
}
if (sPattern.substr(0, 1) !== '^') {
sPattern = '^' + sPattern; // if not already there add ^ at the beginning
}
// sPattern looks like: ^.*:\/\/.*\.company\.corp:\d*$ or ^.*\.company\.corp$
var r = new RegExp(sPattern, 'i');
return r.test(sProbe);
}
};
FrameOptions._lockHandler = function(oEvent) {
oEvent.stopPropagation();
oEvent.preventDefault();
};
FrameOptions.prototype._createBlockLayer = function() {
if (document.readyState == "complete") {
var lockDiv = document.createElement("div");
lockDiv.style.position = "absolute";
lockDiv.style.top = "0px";
lockDiv.style.bottom = "0px";
lockDiv.style.left = "0px";
lockDiv.style.right = "0px";
lockDiv.style.opacity = "0";
lockDiv.style.backgroundColor = "white";
lockDiv.style.zIndex = 2147483647; // Max value of signed integer (32bit)
document.body.appendChild(lockDiv);
this._lockDiv = lockDiv;
}
};
FrameOptions.prototype._setCursor = function() {
if (this._lockDiv) {
this._lockDiv.style.cursor = this.sStatus == "denied" ? "not-allowed" : "wait";
}
};
FrameOptions.prototype._lock = function() {
var that = this;
if (this.bBlockEvents) {
for (var i = 0; i < FrameOptions._events.length; i++) {
document.addEventListener(FrameOptions._events[i], FrameOptions._lockHandler, true);
}
}
if (this.bShowBlockLayer) {
this._blockLayer = function() {
that._createBlockLayer();
that._setCursor();
};
if (document.readyState == "complete") {
this._blockLayer();
} else {
document.addEventListener("readystatechange", this._blockLayer);
}
}
};
FrameOptions.prototype._unlock = function() {
if (this.bBlockEvents) {
for (var i = 0; i < FrameOptions._events.length; i++) {
document.removeEventListener(FrameOptions._events[i], FrameOptions._lockHandler, true);
}
}
if (this.bShowBlockLayer) {
document.removeEventListener("readystatechange", this._blockLayer);
if (this._lockDiv) {
document.body.removeChild(this._lockDiv);
delete this._lockDiv;
}
}
};
FrameOptions.prototype._callback = function(bSuccess) {
this.sStatus = bSuccess ? "allowed" : "denied";
this._setCursor();
clearTimeout(this.iTimer);
if (typeof this.fnCallback === 'function') {
this.fnCallback.call(null, bSuccess);
}
};
FrameOptions.prototype._applyState = function(bIsRunnable, bIsParentUnlocked) {
if (bIsRunnable) {
this.bRunnable = true;
}
if (bIsParentUnlocked) {
this.bParentUnlocked = true;
}
if (!this.bRunnable || !this.bParentUnlocked) {
return;
}
this._unlock();
this._callback(true);
this._notifyChildFrames();
this.bUnlocked = true;
};
FrameOptions.prototype._applyTrusted = function(bTrusted) {
if (bTrusted) {
this._applyState(true, false);
} else {
this._callback(false);
}
};
FrameOptions.prototype._check = function() {
if (this.bRunnable) {
return;
}
var bTrusted = false;
if (this.bAllowSameOrigin && FrameOptions.__window.document.URL.indexOf(this.sParentOrigin) == 0) {
bTrusted = true;
} else if (this.mSettings.whitelist && this.mSettings.whitelist.length != 0) {
var sHostName = this.sParentOrigin.split('//')[1];
sHostName = sHostName.split(':')[0];
for (var i = 0; i < this.mSettings.whitelist.length; i++) {
var match = sHostName.indexOf(this.mSettings.whitelist[i]);
if (match != -1 && sHostName.substring(match) == this.mSettings.whitelist[i]) {
bTrusted = true;
break;
}
}
}
if (bTrusted) {
this._applyTrusted(bTrusted);
} else if (this.mSettings.whitelistService) {
var that = this;
var xmlhttp = new XMLHttpRequest();
var url = this.mSettings.whitelistService + '?parentOrigin=' + encodeURIComponent(this.sParentOrigin);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
that._handleXmlHttpResponse(xmlhttp);
}
};
xmlhttp.open('GET', url, true);
xmlhttp.setRequestHeader('Accept', 'application/json');
xmlhttp.send();
} else {
this._callback(false);
}
};
FrameOptions.prototype._handleXmlHttpResponse = function(xmlhttp) {
if (xmlhttp.status === 200) {
var bTrusted = false;
var sResponseText = xmlhttp.responseText;
var oRuleSet = JSON.parse(sResponseText);
if (oRuleSet.active == false) {
bTrusted = true;
} else if (this.match(this.sParentOrigin, oRuleSet.origin)) {
bTrusted = oRuleSet.framing;
}
this._applyTrusted(bTrusted);
} else {
this._callback(false);
}
};
FrameOptions.prototype._notifyChildFrames = function() {
for (var i = 0; i < this.aFPChilds.length; i++) {
this.aFPChilds[i].postMessage('SAPFrameProtection*parent-unlocked','*');
}
};
FrameOptions.prototype._handlePostMessage = function(oEvent) {
var oSource = oEvent.source,
sData = oEvent.data;
// For compatibility with previous version empty message from parent means parent-unlocked
// if (oSource === FrameOptions.__parent && sData == "") {
// sData = "SAPFrameProtection*parent-unlocked";
// }
if (oSource === FrameOptions.__self || oSource == null ||
typeof sData !== "string" || sData.indexOf("SAPFrameProtection*") === -1) {
return;
}
if (oSource === FrameOptions.__parent) {
if (!this.sParentOrigin) {
this.sParentOrigin = oEvent.origin;
this._check();
}
if (sData == "SAPFrameProtection*parent-unlocked") {
this._applyState(false, true);
}
} else if (oSource.parent === FrameOptions.__self && sData == "SAPFrameProtection*require-origin" && this.bUnlocked) {
oSource.postMessage("SAPFrameProtection*parent-unlocked", "*");
} else {
oSource.postMessage("SAPFrameProtection*parent-origin", "*");
this.aFPChilds.push(oSource);
}
};
jQuery.sap.FrameOptions = FrameOptions;
}());
/**
* Executes an 'eval' for its arguments in the global context (without closure variables).
*
* This is a synchronous replacement for jQuery.globalEval
which in some
* browsers (e.g. FireFox) behaves asynchronously.
*
* @type void
* @public
* @static
* @SecSink {0|XSS} Parameter is evaluated
*/
jQuery.sap.globalEval = function() {
/*eslint-disable no-eval */
eval(arguments[0]);
/*eslint-enable no-eval */
};
jQuery.sap.declare('sap-ui-core-nojQuery');
jQuery.sap.declare('sap.ui.Device', false);
jQuery.sap.declare('sap.ui.thirdparty.URI', false);
jQuery.sap.declare('jquery.sap.promise', false);
jQuery.sap.declare('jquery.sap.global', false);
jQuery.sap.registerPreloadedModules({
"name":"sap-ui-core-preload",
"version":"2.0",
"modules":{
"jquery.sap.act.js":function(){/*!
* SAP UI development toolkit for HTML5 (SAPUI5/OpenUI5)
* (c) Copyright 2009-2015 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides functionality for activity detection
sap.ui.define(['jquery.sap.global'],
function(jQuery) {
"use strict";
if (typeof window.jQuery.sap.act === "object" || typeof window.jQuery.sap.act === "function" ) {
return;
}
// Date.now = Date.now || function() {
// return new Date().getTime();
// };
/**
* @public
* @name jQuery.sap.act
* @namespace
* @static
*/
var _act = {},
_active = true,
_deactivatetimer = null,
_I_MAX_IDLE_TIME = 10000, //max. idle time in ms
_deactivateSupported = !!window.addEventListener, //Just skip IE8
_aActivateListeners = [],
_activityDetected = false,
_domChangeObserver = null;
function _onDeactivate(){
_deactivatetimer = null;
if (_activityDetected) {
_onActivate();
return;
}
_active = false;
//_triggerEvent(_aDeactivateListeners); //Maybe provide later
_domChangeObserver.observe(document.documentElement, {childList: true, attributes: true, subtree: true, characterData: true});
}
function _onActivate(){
// Never activate when document is not visible to the user
if (document.hidden === true) {
// In case of IE<10 document.visible is undefined, else it is either true or false
return;
}
if (!_active) {
_active = true;
_triggerEvent(_aActivateListeners);
_domChangeObserver.disconnect();
}
if (_deactivatetimer) {
_activityDetected = true;
} else {
_deactivatetimer = setTimeout(_onDeactivate, _I_MAX_IDLE_TIME);
_activityDetected = false;
}
}
function _triggerEvent(aListeners){
if (aListeners.length == 0) {
return;
}
var aEventListeners = aListeners.slice();
setTimeout(function(){
var oInfo;
for (var i = 0, iL = aEventListeners.length; i < iL; i++) {
oInfo = aEventListeners[i];
oInfo.fFunction.call(oInfo.oListener || window);
}
}, 0);
}
/**
* Registers the given handler to the activity event, which is fired when an activity was detected after a certain period of inactivity.
*
* The Event is not fired for Internet Explorer 8.
*
* @param {Function} fnFunction The function to call, when an activity event occurs.
* @param {Object} [oListener] The 'this' context of the handler function.
* @protected
*
* @function
* @name jQuery.sap.act#attachActivate
*/
_act.attachActivate = function(fnFunction, oListener){
_aActivateListeners.push({oListener: oListener, fFunction:fnFunction});
};
/**
* Deregisters a previously registered handler from the activity event.
*
* @param {Function} fnFunction The function to call, when an activity event occurs.
* @param {Object} [oListener] The 'this' context of the handler function.
* @protected
*
* @function
* @name jQuery.sap.act#detachActivate
*/
_act.detachActivate = function(fnFunction, oListener){
for (var i = 0, iL = _aActivateListeners.length; i < iL; i++) {
if (_aActivateListeners[i].fFunction === fnFunction && _aActivateListeners[i].oListener === oListener) {
_aActivateListeners.splice(i,1);
break;
}
}
};
/**
* Checks whether recently an activity was detected.
*
* Not supported for Internet Explorer 8.
*
* @return true if recently an activity was detected, false otherwise
* @protected
*
* @function
* @name jQuery.sap.act#isActive
*/
_act.isActive = !_deactivateSupported ? function(){ return true; } : function(){ return _active; };
/**
* Reports an activity.
*
* @public
*
* @function
* @name jQuery.sap.act#refresh
*/
_act.refresh = !_deactivateSupported ? function(){} : _onActivate;
// Setup and registering handlers
if (_deactivateSupported) {
var aEvents = ["resize", "orientationchange", "mousemove", "mousedown", "mouseup", //"mouseout", "mouseover",
"touchstart", "touchmove", "touchend", "touchcancel", "paste", "cut", "keydown", "keyup",
"DOMMouseScroll", "mousewheel"];
for (var i = 0; i < aEvents.length; i++) {
window.addEventListener(aEvents[i], _act.refresh, true);
}
if (window.MutationObserver) {
_domChangeObserver = new window.MutationObserver(_act.refresh);
} else if (window.WebKitMutationObserver) {
_domChangeObserver = new window.WebKitMutationObserver(_act.refresh);
} else {
_domChangeObserver = {
observe : function(){
document.documentElement.addEventListener("DOMSubtreeModified", _act.refresh);
},
disconnect : function(){
document.documentElement.removeEventListener("DOMSubtreeModified", _act.refresh);
}
};
}
if (typeof (document.hidden) === "boolean") {
document.addEventListener("visibilitychange", function() {
// Only trigger refresh if document has changed to visible
if (document.hidden !== true) {
_act.refresh();
}
}, false);
}
_onActivate();
}
jQuery.sap.act = _act;
return jQuery;
}, /* bExport= */ false);
},
"jquery.sap.dom.js":function(){/*!
* SAP UI development toolkit for HTML5 (SAPUI5/OpenUI5)
* (c) Copyright 2009-2015 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides functionality related to DOM analysis and manipulation which is not provided by jQuery itself.
sap.ui.define(['jquery.sap.global', 'sap/ui/Device'],
function(jQuery, Device) {
"use strict";
/**
* Shortcut for document.getElementById() with additionally an IE6/7 bug fixed.
* Used to replace the jQuery.sap.domById when running in IE < v8.
*
* @param {string} sId the id of the DOM element to return
* @param {Window} oWindow the window (optional)
* @return {Element} the DOMNode identified by the given sId
* @private
*/
var domByIdInternal = function(sId, oWindow) {
if (!oWindow) {
oWindow = window;
}
if (!sId || sId == "") {
return null;
}
var oDomRef = oWindow.document.getElementById(sId);
// IE also returns the element with the name or id whatever is first
// => the following line makes sure that this was the id
if (oDomRef && oDomRef.id == sId) {
return oDomRef;
}
// otherwise try to lookup the name
var oRefs = oWindow.document.getElementsByName(sId);
for (var i = 0;i < oRefs.length;i++) {
oDomRef = oRefs[i];
if (oDomRef && oDomRef.id == sId) {
return oDomRef;
}
}
return null;
};
/**
* Shortcut for document.getElementById(), including a bug fix for older IE versions.
*
* @param {string} sId The id of the DOM element to return
* @param {Window} [oWindow=window] The window (optional)
* @return {Element} The DOMNode identified by the given sId
* @public
* @function
* @since 0.9.0
*/
jQuery.sap.domById = !!Device.browser.internet_explorer && Device.browser.version < 8 ? domByIdInternal : function domById(sId, oWindow) {
return sId ? (oWindow || window).document.getElementById(sId) : null;
};
/**
* Shortcut for jQuery("#" + id) with additionally the id being escaped properly.
* I.e.: returns the jQuery object for the DOM element with the given id
*
* Use this method instead of jQuery(...) if you know the argument is exactly one id and
* the id is not known in advance because it is in a variable (as opposed to a string
* constant with known content).
*
* @param {string} sId The id to search for and construct the jQuery object
* @param {Element} oContext the context DOM Element
* @return {Object} The jQuery object for the DOM element identified by the given sId
* @public
* @since 0.9.1
*/
jQuery.sap.byId = function byId(sId, oContext) {
var escapedId = "";
if (sId) {
escapedId = "#" + sId.replace(/(:|\.)/g,'\\$1');
}
return jQuery(escapedId, oContext);
};
/**
* Calls focus() on the given DOM element, but catches and ignores any errors that occur when doing so.
* (i.e. IE8 throws an error when the DOM element is invisible or disabled)
*
* @param {Element} oDomRef The DOM element to focus (or null - in this case the method does nothing)
* @return {boolean} Whether the focus() command was executed without an error
* @public
* @since 1.1.2
*/
jQuery.sap.focus = function focus(oDomRef) {
if (!oDomRef) {
return;
}
try {
oDomRef.focus();
} catch (e) {
var id = (oDomRef && oDomRef.id) ? " (ID: '" + oDomRef.id + "')" : "";
jQuery.sap.log.warning("Error when trying to focus a DOM element" + id + ": " + e.message);
return false;
}
return true;
};
/**
* Sets or gets the position of the cursor in an element that supports cursor positioning
*
* @param {int} iPos The cursor position to set (or no parameter to retrieve the cursor position)
* @return {int | jQuery} The cursor position (or the jQuery collection if the position has been set)
* @public
* @name jQuery#cursorPos
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.cursorPos = function cursorPos(iPos) {
var len = arguments.length,
oTextRange,iLength,
sTagName,
sType;
sTagName = this.prop("tagName");
sType = this.prop("type");
if ( this.length === 1 && ((sTagName == "INPUT" && (sType == "text" || sType == "password" || sType == "search"))
|| sTagName == "TEXTAREA" )) {
var oDomRef = this.get(0);
if (len > 0) { // SET
if (typeof (oDomRef.selectionStart) == "number") { // FF and IE9+ method
oDomRef.focus();
oDomRef.selectionStart = iPos;
oDomRef.selectionEnd = iPos;
} else if (oDomRef.createTextRange) { // IE method
oTextRange = oDomRef.createTextRange();
var iMaxLength = oDomRef.value.length;
if (iPos < 0 || iPos > iMaxLength) {
iPos = iMaxLength;
}
if (oTextRange) {
oTextRange.collapse();
oTextRange.moveEnd("character",iPos);
oTextRange.moveStart("character",iPos);
oTextRange.select();
}
}
return this;
// end of SET
} else { // GET
if (typeof (oDomRef.selectionStart) == "number") { // Firefox etc.
return oDomRef.selectionStart;
} else if (oDomRef.createTextRange) { // IE 8
oTextRange = window.document.selection.createRange();
var oCopiedTextRange = oTextRange.duplicate();
// Logic in TEXTAREA and INPUT is different in IE -> check for element type
if (oDomRef.tagName == "TEXTAREA") {
oCopiedTextRange.moveToElementText(oDomRef);
var oCheckTextRange = oCopiedTextRange.duplicate();
iLength = oCopiedTextRange.text.length;
// first check if cursor on last position
oCheckTextRange.moveStart("character", iLength);
var iStart = 0;
if (oCheckTextRange.inRange(oTextRange)) {
iStart = iLength;
} else {
// find out cursor position using a bisection algorithm
var iCheckLength = iLength;
while (iLength > 1) {
iCheckLength = Math.round(iLength / 2);
iStart = iStart + iCheckLength;
oCheckTextRange = oCopiedTextRange.duplicate();
oCheckTextRange.moveStart("character", iStart);
if (oCheckTextRange.inRange(oTextRange)) {
//cursor is after or on iStart -> Length = not checked Length
iLength = iLength - iCheckLength;
} else {
//cursor is before iStart -> Length = checked Length
iStart = iStart - iCheckLength;
iLength = iCheckLength;
}
}
}
return iStart;
} else if (oCopiedTextRange.parentElement() === oDomRef) {
// ensure there is only the cursor and not the range (as this would create erroneous position)!
oCopiedTextRange.collapse();
// now, move the selection range to the beginning of the inputField and simply get the selected range's length
var iLength = oDomRef.value.length;
oCopiedTextRange.moveStart('character', -iLength);
return oCopiedTextRange.text.length;
}
}
return -1;
} // end of GET
} else {
// shouldn't really happen, but to be safe...
return this;
}
};
/**
* Sets the text selection in the first element of the collection.
* note: This feature is only supported for input element’s type of text, search, url, tel and password.
*
* @param {int} iStart Start position of the selection (inclusive)
* @param {int} iEnd End position of the selection (exclusive)
* @return {jQuery} The jQuery collection
* @public
* @name jQuery#selectText
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.selectText = function selectText(iStart, iEnd) {
var oDomRef = this.get(0);
try {
if (typeof (oDomRef.selectionStart) === "number") { // Firefox and IE9+
oDomRef.setSelectionRange(iStart, iEnd);
} else if (oDomRef.createTextRange) { // IE
var oTextEditRange = oDomRef.createTextRange();
oTextEditRange.collapse();
oTextEditRange.moveStart('character', iStart);
oTextEditRange.moveEnd('character', iEnd - iStart);
oTextEditRange.select();
}
} catch (e) {} // note: some browsers fail to read the "selectionStart" and "selectionEnd" properties from HTMLInputElement, e.g.: The input element's type "number" does not support selection.
return this;
};
/**
* Retrieve the selected text in the first element of the collection.
* note: This feature is only supported for input element’s type of text, search, url, tel and password.
*
* @return {string} The selected text.
* @public
* @name jQuery#getSelectedText
* @author SAP SE
* @since 1.26.0
* @function
*/
jQuery.fn.getSelectedText = function() {
var oDomRef = this.get(0);
try {
if (typeof oDomRef.selectionStart === "number") {
return oDomRef.value.substring(oDomRef.selectionStart, oDomRef.selectionEnd);
}
// older versions of Internet Explorer do not support the HTML5 "selectionStart" and "selectionEnd" properties
if (document.selection) {
return document.selection.createRange().text;
}
} catch (e) {} // note: some browsers fail to read the "selectionStart" and "selectionEnd" properties from HTMLInputElement, e.g.: The input element's type "number" does not support selection.
return "";
};
/**
* Returns the outer HTML of the given HTML element
*
* @return {string} outer HTML
* @public
* @name jQuery#outerHTML
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.outerHTML = function outerHTML() {
var oDomRef = this.get(0);
if (oDomRef && oDomRef.outerHTML) {
return jQuery.trim(oDomRef.outerHTML);
} else {
var doc = this[0] ? this[0].ownerDocument : document;
var oDummy = doc.createElement("div");
oDummy.appendChild(oDomRef.cloneNode(true));
return oDummy.innerHTML;
}
};
/**
* Returns whether oDomRefChild is oDomRefContainer or is contained in oDomRefContainer.
*
* This is a browser-independent version of the .contains method of Internet Explorer.
* For compatibility reasons it returns true if oDomRefContainer and oDomRefChild are equal.
*
* This method intentionally does not operate on the jQuery object, as the original jQuery.contains()
* method also does not do so.
*
* @param {Element} oDomRefContainer The container element
* @param {Element} oDomRefChild The child element (must not be a text node, must be an element)
* @return {boolean} 'true' if oDomRefChild is contained in oDomRefContainer or oDomRefChild is oDomRefContainer
* @public
* @author SAP SE
* @since 0.9.0
*/
jQuery.sap.containsOrEquals = function containsOrEquals(oDomRefContainer, oDomRefChild) {
if (oDomRefChild && oDomRefContainer && oDomRefChild != document && oDomRefChild != window) {
return (oDomRefContainer === oDomRefChild) || jQuery.contains(oDomRefContainer, oDomRefChild);
}
return false;
};
/**
* Returns a rectangle describing the current visual positioning of the first DOM object in the collection
* (or null if no element was given)
*
* @return {object} An object with left, top, width and height
* @public
* @name jQuery#rect
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.rect = function rect() {
var oDomRef = this.get(0);
if (oDomRef) {
// this should be available in all 'modern browsers'
if (oDomRef.getBoundingClientRect) {
var oClientRect = oDomRef.getBoundingClientRect();
var oRect = { top : oClientRect.top,
left : oClientRect.left,
width : oClientRect.right - oClientRect.left,
height : oClientRect.bottom - oClientRect.top };
var oWnd = jQuery.sap.ownerWindow(oDomRef);
oRect.left += jQuery(oWnd).scrollLeft();
oRect.top += jQuery(oWnd).scrollTop();
return oRect;
} else {
// IE6 and older; avoid crashing and give some hardcoded size
return { top : 10, left : 10, width : oDomRef.offsetWidth, height : oDomRef.offsetWidth };
}
}
return null;
};
/**
* Returns whether a point described by X and Y is inside this Rectangle's boundaries
*
* @param {int} iPosX
* @param {int} iPosY
* @return {boolean} Whether X and Y are inside this Rectangle's boundaries
* @public
* @name jQuery#rectContains
* @author SAP SE
* @since 0.18.0
* @function
*/
jQuery.fn.rectContains = function rectContains(iPosX, iPosY) {
jQuery.sap.assert(!isNaN(iPosX), "iPosX must be a number");
jQuery.sap.assert(!isNaN(iPosY), "iPosY must be a number");
var oRect = this.rect();
if (oRect) {
return iPosX >= oRect.left
&& iPosX <= oRect.left + oRect.width
&& iPosY >= oRect.top
&& iPosY <= oRect.top + oRect.height;
}
return false;
};
/**
* Returns true if the first element has a set tabindex
*
* @return {boolean} If the first element has a set tabindex
* @public
* @name jQuery#hasTabIndex
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.hasTabIndex = function hasTabIndex() {
var iTabIndex = this.prop("tabIndex");
if (this.attr("disabled") && !this.attr("tabindex")) {
// disabled field with not explicit set tabindex -> not in tab chain (bug of jQuery prop function)
iTabIndex = -1;
}
return !isNaN(iTabIndex) && iTabIndex >= 0;
};
/**
* Returns the first focusable domRef in a given container (the first element of the collection)
*
* @return {Element} The domRef
* @public
* @name jQuery#firstFocusableDomRef
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.firstFocusableDomRef = function firstFocusableDomRef() {
var oContainerDomRef = this.get(0);
var visibilityHiddenFilter = function (idx){
return jQuery(this).css("visibility") == "hidden";
};
if (!oContainerDomRef || jQuery(oContainerDomRef).is(':hidden') ||
jQuery(oContainerDomRef).filter(visibilityHiddenFilter).length == 1) {
return null;
}
var oCurrDomRef = oContainerDomRef.firstChild,
oDomRefFound = null;
while (oCurrDomRef) {
if (oCurrDomRef.nodeType == 1 && jQuery(oCurrDomRef).is(':visible')) {
if (jQuery(oCurrDomRef).hasTabIndex()) {
return oCurrDomRef;
}
if (oCurrDomRef.childNodes) {
oDomRefFound = jQuery(oCurrDomRef).firstFocusableDomRef();
if (oDomRefFound) {
return oDomRefFound;
}
}
}
oCurrDomRef = oCurrDomRef.nextSibling;
}
return null;
};
/**
* Returns the last focusable domRef in a given container
*
* @return {Element} The last domRef
* @public
* @name jQuery#lastFocusableDomRef
* @author SAP SE
* @since 0.9.0
* @function
*/
jQuery.fn.lastFocusableDomRef = function lastFocusableDomRef() {
var oContainerDomRef = this.get(0);
var visibilityHiddenFilter = function (idx){
return jQuery(this).css("visibility") == "hidden";
};
if (!oContainerDomRef || jQuery(oContainerDomRef).is(':hidden') ||
jQuery(oContainerDomRef).filter(visibilityHiddenFilter).length == 1) {
return null;
}
var oCurrDomRef = oContainerDomRef.lastChild,
oDomRefFound = null;
while (oCurrDomRef) {
if (oCurrDomRef.nodeType == 1 && jQuery(oCurrDomRef).is(':visible')) {
if (oCurrDomRef.childNodes) {
oDomRefFound = jQuery(oCurrDomRef).lastFocusableDomRef();
if (oDomRefFound) {
return oDomRefFound;
}
}
if (jQuery(oCurrDomRef).hasTabIndex()) {
return oCurrDomRef;
}
}
oCurrDomRef = oCurrDomRef.previousSibling;
}
return null;
};
/**
* Sets or returns the scrollLeft value of the first element in the given jQuery collection in right-to-left mode.
* Precondition: The element is rendered in RTL mode.
*
* Reason for this method is that the major browsers use three different values for the same scroll position when in RTL mode.
* This method hides those differences and returns/applies the same value that would be returned in LTR mode: The distance in px
* how far the given container is scrolled away from the leftmost scroll position.
*
* Returns "undefined" if no element and no iPos is given.
*
* @param {int} iPos
* @return {jQuery | int} The jQuery collection if iPos is given, otherwise the scroll position, counted from the leftmost position
* @public
* @name jQuery#scrollLeftRTL
* @author SAP SE
* @since 0.20.0
* @function
*/
jQuery.fn.scrollLeftRTL = function scrollLeftRTL(iPos) {
var oDomRef = this.get(0);
if (oDomRef) {
if (iPos === undefined) { // GETTER code
if (!!Device.browser.internet_explorer) {
return oDomRef.scrollWidth - oDomRef.scrollLeft - oDomRef.clientWidth;
} else if (!!Device.browser.webkit) {
return oDomRef.scrollLeft;
} else if (!!Device.browser.firefox) {
return oDomRef.scrollWidth + oDomRef.scrollLeft - oDomRef.clientWidth;
} else {
// unrecognized browser; it is hard to return a best guess, as browser strategies are very different, so return the actual value
return oDomRef.scrollLeft;
}
} else { // SETTER code
oDomRef.scrollLeft = jQuery.sap.denormalizeScrollLeftRTL(iPos, oDomRef);
return this;
}
}
};
/**
* Returns the MIRRORED scrollLeft value of the first element in the given jQuery collection in right-to-left mode.
* Precondition: The element is rendered in RTL mode.
*
* Reason for this method is that the major browsers return three different values for the same scroll position when in RTL mode.
* This method hides those differences and returns the value that would be returned in LTR mode if the UI would be mirrored horizontally:
* The distance in px how far the given container is scrolled away from the rightmost scroll position.
*
* Returns "undefined" if no element is given.
*
* @return {int} The scroll position, counted from the rightmost position
* @public
* @name jQuery#scrollRightRTL
* @author SAP SE
* @since 0.20.0
* @function
*/
jQuery.fn.scrollRightRTL = function scrollRightRTL() {
var oDomRef = this.get(0);
if (oDomRef) {
if (!!Device.browser.internet_explorer) {
return oDomRef.scrollLeft;
} else if (!!Device.browser.webkit) {
return oDomRef.scrollWidth - oDomRef.scrollLeft - oDomRef.clientWidth;
} else if (!!Device.browser.firefox) {
return (-oDomRef.scrollLeft);
} else {
// unrecognized browser; it is hard to return a best guess, as browser strategies are very different, so return the actual value
return oDomRef.scrollLeft;
}
}
};
/**
* For the given scrollLeft value this method returns the scrollLeft value as understood by the current browser in RTL mode.
* This value is specific to the given DOM element, as the computation may involve its dimensions.
*
* So when oDomRef should be scrolled 2px from the leftmost position, the number "2" must be given as iNormalizedScrollLeft
* and the result of this method (which may be a large or even negative number, depending on the browser) can then be set as
* oDomRef.scrollLeft to achieve the desired (cross-browser-consistent) scrolling position.
*
* This method does no scrolling on its own, it only calculates the value to set (so it can also be used for animations).
*
* @param {int} iNormalizedScrollLeft The distance from the leftmost position to which the element should be scrolled
* @param {Element} oDomRef The DOM Element to which scrollLeft will be applied
* @return {int} The scroll position that must be set for the DOM element
* @public
* @author SAP SE
* @since 0.20.0
*/
jQuery.sap.denormalizeScrollLeftRTL = function(iNormalizedScrollLeft, oDomRef) {
if (oDomRef) {
if (!!Device.browser.internet_explorer) {
return oDomRef.scrollWidth - oDomRef.clientWidth - iNormalizedScrollLeft;
} else if (!!Device.browser.webkit) {
return iNormalizedScrollLeft;
} else if (!!Device.browser.firefox) {
return oDomRef.clientWidth + iNormalizedScrollLeft - oDomRef.scrollWidth;
} else {
// unrecognized browser; it is hard to return a best guess, as browser strategies are very different, so return the actual value
return iNormalizedScrollLeft;
}
}
};
/**
* For the given scroll position measured from the "beginning" of a container (the right edge in RTL mode)
* this method returns the scrollLeft value as understood by the current browser in RTL mode.
* This value is specific to the given DOM element, as the computation may involve its dimensions.
*
* So when oDomRef should be scrolled 2px from the beginning, the number "2" must be given as iNormalizedScrollBegin
* and the result of this method (which may be a large or even negative number, depending on the browser) can then be set as
* oDomRef.scrollLeft to achieve the desired (cross-browser-consistent) scrolling position.
* Low values make the right part of the content visible, high values the left part.
*
* This method does no scrolling on its own, it only calculates the value to set (so it can also be used for animations).
*
* Only use this method in RTL mode, as the behavior in LTR mode is undefined and may change!
*
* @param {int} iNormalizedScrollBegin The distance from the rightmost position to which the element should be scrolled
* @param {Element} oDomRef The DOM Element to which scrollLeft will be applied
* @return {int} The scroll position that must be set for the DOM element
* @public
* @author SAP SE
* @since 1.26.1
*/
jQuery.sap.denormalizeScrollBeginRTL = function(iNormalizedScrollBegin, oDomRef) {
if (oDomRef) {
if (!!Device.browser.internet_explorer) {
return iNormalizedScrollBegin;
} else if (!!Device.browser.webkit) {
return oDomRef.scrollWidth - oDomRef.clientWidth - iNormalizedScrollBegin;
} else if (!!Device.browser.firefox) {
return -iNormalizedScrollBegin;
} else {
// unrecognized browser; it is hard to return a best guess, as browser strategies are very different, so return the actual value
return iNormalizedScrollBegin;
}
}
};
/*
* The following methods are taken from jQuery UI core but modified.
*
* jQuery UI Core
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/ui-core/
*/
jQuery.support.selectstart = "onselectstart" in document.createElement("div");
jQuery.fn.extend( /** @lends jQuery.prototype */ {
/**
* Disable HTML elements selection.
*
* @return {jQuery} this
to allow method chaining.
* @protected
* @since 1.24.0
*/
disableSelection: function() {
return this.on((jQuery.support.selectstart ? "selectstart" : "mousedown") + ".ui-disableSelection", function(oEvent) {
oEvent.preventDefault();
});
},
/**
* Enable HTML elements to get selected.
*
* @return {jQuery} this
to allow method chaining.
* @protected
* @since 1.24.0
*/
enableSelection: function() {
return this.off(".ui-disableSelection");
}
});
/*!
* The following functions are taken from jQuery UI 1.8.17 but modified
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*/
function visible( element ) {
// check if one of the parents (until it's position parent) is invisible
// prevent that elements in static area are always checked as invisible
// list all items until the offsetParent item (with jQuery >1.6 you can use parentsUntil)
var oOffsetParent = jQuery(element).offsetParent();
var bOffsetParentFound = false;
var $refs = jQuery(element).parents().filter(function() {
if (this === oOffsetParent) {
bOffsetParentFound = true;
}
return bOffsetParentFound;
});
// check for at least one item to be visible
return !jQuery(element).add($refs).filter(function() {
return jQuery.css( this, "visibility" ) === "hidden" || jQuery.expr.filters.hidden( this );
}).length;
}
function focusable( element, isTabIndexNotNaN ) {
var nodeName = element.nodeName.toLowerCase();
if ( nodeName === "area" ) {
var map = element.parentNode,
mapName = map.name,
img;
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
return false;
}
img = jQuery( "img[usemap=#" + mapName + "]" )[0];
return !!img && visible( img );
}
/*eslint-disable no-nested-ternary */
return ( /input|select|textarea|button|object/.test( nodeName )
? !element.disabled
: nodeName == "a"
? element.href || isTabIndexNotNaN
: isTabIndexNotNaN)
// the element and all of its ancestors must be visible
&& visible( element );
/*eslint-enable no-nested-ternary */
}
if (!jQuery.expr[":"].focusable) {
/*!
* The following function is taken from jQuery UI 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*
* But since visible is modified, focusable is different too the jQuery UI version too.
*/
jQuery.extend( jQuery.expr[ ":" ], {
/**
* This defines the jQuery ":focusable" selector; it is also defined in jQuery UI. If already present, nothing is
* done here, so we will not overwrite any previous implementation.
* If jQuery UI is loaded later on, this implementation here will be overwritten by that one, which is fine,
* as it is semantically the same thing and intended to do exactly the same.
*/
focusable: function( element ) {
return focusable( element, !isNaN( jQuery.attr( element, "tabindex" ) ) );
}
});
}
if (!jQuery.expr[":"].sapTabbable) {
/*!
* The following function is taken from
* jQuery UI Core 1.11.1
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/ui-core/
*/
jQuery.extend( jQuery.expr[ ":" ], {
/**
* This defines the jQuery ":tabbable" selector; it is also defined in jQuery UI. If already present, nothing is
* done here, so we will not overwrite any previous implementation.
* If jQuery UI is loaded later on, this implementation here will be overwritten by that one, which is fine,
* as it is semantically the same thing and intended to do exactly the same.
*/
sapTabbable: function( element ) {
var tabIndex = jQuery.attr( element, "tabindex" ),
isTabIndexNaN = isNaN( tabIndex );
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
}
});
}
if (!jQuery.expr[":"].sapFocusable) {
/*!
* Do not use jQuery UI focusable because this might be overwritten if jQuery UI is loaded
*/
jQuery.extend( jQuery.expr[ ":" ], {
/**
* This defines the jQuery ":sapFocusable" selector; If already present, nothing is
* done here, so we will not overwrite any previous implementation.
* If jQuery UI is loaded later on, this implementation here will NOT be overwritten by.
*/
sapFocusable: function( element ) {
return focusable( element, !isNaN( jQuery.attr( element, "tabindex" ) ) );
}
});
}
if (!jQuery.fn.zIndex) {
/*!
* The following function is taken from
* jQuery UI Core 1.11.1
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/ui-core/
*/
jQuery.fn.zIndex = function( zIndex ) {
if ( zIndex !== undefined ) {
return this.css( "zIndex", zIndex );
}
if ( this.length ) {
var elem = jQuery( this[ 0 ] ), position, value;
while ( elem.length && elem[ 0 ] !== document ) {
// Ignore z-index if position is set to a value where z-index is ignored by the browser
// This makes behavior of this function consistent across browsers
// WebKit always returns auto if the element is positioned
position = elem.css( "position" );
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
// IE returns 0 when zIndex is not specified
// other browsers return a string
// we ignore the case of nested elements with an explicit value of 0
// width
and height
(the values are of type number and are pixels).
* @public
* @since 1.4.0
*/
jQuery.sap.scrollbarSize = function(sClasses, bForce) {
if (typeof sClasses === "boolean") {
bForce = sClasses;
sClasses = null;
}
var sKey = sClasses || "#DEFAULT"; // # is an invalid character for CSS classes
if (bForce) {
if (sClasses) {
delete _oScrollbarSize[sClasses];
} else {
_oScrollbarSize = {};
}
}
if (_oScrollbarSize[sKey]) {
return _oScrollbarSize[sKey];
}
if (!document.body) {
return {width: 0, height: 0};
}
var $Area = jQuery("")
.css("visibility", "hidden")
.css("height", "0")
.css("width", "0")
.css("overflow", "hidden");
if (sClasses) {
$Area.addClass(sClasses);
}
$Area.prependTo(document.body);
var $Dummy = jQuery("");
$Area.append($Dummy);
var oDomRef = $Dummy.get(0);
var iWidth = oDomRef.offsetWidth - oDomRef.scrollWidth;
var iHeight = oDomRef.offsetHeight - oDomRef.scrollHeight;
$Area.remove();
// due to a bug in FireFox when hiding iframes via an outer DIV element
// the height and width calculation is not working properly - by not storing
// height and width when one value is 0 we make sure that once the iframe
// gets visible the height calculation will be redone (see snippix: #64049)
if (iWidth === 0 || iHeight === 0) {
return {width: iWidth, height: iHeight};
}
_oScrollbarSize[sKey] = {width: iWidth, height: iHeight};
return _oScrollbarSize[sKey];
};
/**
* Search ancestors of the given source DOM element for the specified CSS class name.
* If the class name is found, set it to the root DOM element of the target control.
* If the class name is not found, it is also removed from the target DOM element.
*
* @param {string} sStyleClass CSS class name
* @param {jQuery|Control|string} vSource jQuery object, control or an id of the source element.
* @param {jQuery|Control} vDestination target jQuery object or a control.
* @return {jQuery|Element} Target element
* @public
* @since 1.22
*/
jQuery.sap.syncStyleClass = function(sStyleClass, vSource, vDestination) {
if (!sStyleClass) {
return vDestination;
}
if (vSource instanceof sap.ui.core.Control) {
vSource = vSource.$();
} else if (typeof vSource === "string") {
vSource = jQuery.sap.byId(vSource);
} else if (!(vSource instanceof jQuery)) {
jQuery.sap.assert(false, 'jQuery.sap.syncStyleClass(): vSource must be a jQuery object or a Control or a string');
return vDestination;
}
var bClassFound = !!vSource.closest("." + sStyleClass).length;
if (vDestination instanceof jQuery) {
vDestination.toggleClass(sStyleClass, bClassFound);
} else if (vDestination instanceof sap.ui.core.Control) {
vDestination.toggleStyleClass(sStyleClass, bClassFound);
} else {
jQuery.sap.assert(false, 'jQuery.sap.syncStyleClass(): vDestination must be a jQuery object or a Control');
}
return vDestination;
};
return jQuery;
}, /* bExport= */ false);
},
"jquery.sap.encoder.js":function(){/*!
* SAP UI development toolkit for HTML5 (SAPUI5/OpenUI5)
* (c) Copyright 2009-2015 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides encoding functions for JavaScript.
sap.ui.define(['jquery.sap.global'],
function(jQuery) {
"use strict";
/*
* Encoding according to the Secure Programming Guide
* * function sanitizer(sHtml, mOptions); ** * The parameter
mOptions
will always be provided, but might be empty.
* The set of understood options is defined by the sanitizer. If no specific
* options are given, the sanitizer should run with the most secure settings.
* Sanitizers should ignore unknown settings. Known, but misconfigured settings should be
* reported as error.
*
* @param {function} fnSanitizer
* @private
*/
jQuery.sap._setHTMLSanitizer = function (fnSanitizer) {
jQuery.sap.assert(typeof fnSanitizer === "function", "Sanitizer must be a function");
fnSanitizer = fnSanitizer || defaultSanitizer;
};
function defaultSanitizer(sHTML, mOptions) {
if ( !window.html || !window.html.sanitize ) {
jQuery.sap.require("sap.ui.thirdparty.caja-html-sanitizer");
jQuery.sap.assert(window.html && window.html.sanitize, "Sanitizer should have been loaded");
}
var oTagPolicy = mOptions.tagPolicy || window.html.makeTagPolicy(mOptions.uriRewriter, mOptions.tokenPolicy);
return window.html.sanitizeWithPolicy(sHTML, oTagPolicy);
}
/**
* Globally configured sanitizer.
* @private
*/
var fnSanitizer = defaultSanitizer;
return jQuery;
}, /* bExport= */ false);
},
"jquery.sap.events.js":function(){/*!
* SAP UI development toolkit for HTML5 (SAPUI5/OpenUI5)
* (c) Copyright 2009-2015 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides functionality related to eventing.
sap.ui.define(['jquery.sap.global', 'jquery.sap.keycodes'],
function(jQuery/* , jQuerySap1 */) {
"use strict";
var onTouchStart,
onTouchMove,
onTouchEnd,
onTouchCancel,
onMouseEvent,
aMouseEvents,
bIsSimulatingTouchToMouseEvent = false;
if (sap.ui.Device.browser.webkit && /Mobile/.test(navigator.userAgent) && sap.ui.Device.support.touch) {
bIsSimulatingTouchToMouseEvent = true;
(function() {
var document = window.document,
bHandleEvent = false,
oTarget = null,
bIsMoved = false,
iStartX,
iStartY,
i = 0;
aMouseEvents = ["mousedown", "mouseover", "mouseup", "mouseout", "click"];
/**
* Fires a synthetic mouse event for a given type and native touch event.
* @param {String} sType the type of the synthetic event to fire, e.g. "mousedown"
* @param {jQuery.Event} oEvent the event object
* @private
*/
var fireMouseEvent = function(sType, oEvent) {
if (!bHandleEvent) {
return;
}
// we need mapping of the different event types to get the correct target
var oMappedEvent = oEvent.type == "touchend" ? oEvent.changedTouches[0] : oEvent.touches[0];
// create the synthetic event
var newEvent = document.createEvent('MouseEvent'); // trying to create an actual TouchEvent will create an error
newEvent.initMouseEvent(sType, true, true, window, oEvent.detail,
oMappedEvent.screenX, oMappedEvent.screenY, oMappedEvent.clientX, oMappedEvent.clientY,
oEvent.ctrlKey, oEvent.shiftKey, oEvent.altKey, oEvent.metaKey,
oEvent.button, oEvent.relatedTarget);
newEvent.isSynthetic = true;
// Timeout needed. Do not interrupt the native event handling.
window.setTimeout(function() {
oTarget.dispatchEvent(newEvent);
}, 0);
};
/**
* Checks if the target of the event is an input field.
* @param {jQuery.Event} oEvent the event object
* @return {Boolean} whether the target of the event is an input field.
*/
var isInputField = function(oEvent) {
return oEvent.target.tagName.match(/input|textarea|select/i);
};
/**
* Mouse event handler. Prevents propagation for native events.
* @param {jQuery.Event} oEvent the event object
* @private
*/
onMouseEvent = function(oEvent) {
if (!oEvent.isSynthetic && !isInputField(oEvent)) {
oEvent.stopPropagation();
oEvent.preventDefault();
}
};
/**
* Touch start event handler. Called whenever a finger is added to the surface. Fires mouse start event.
* @param {jQuery.Event} oEvent the event object
* @private
*/
onTouchStart = function(oEvent) {
var oTouches = oEvent.touches,
oTouch;
bHandleEvent = (oTouches.length == 1 && !isInputField(oEvent));
bIsMoved = false;
if (bHandleEvent) {
oTouch = oTouches[0];
// As we are only interested in the first touch target, we remember it
oTarget = oTouch.target;
if (oTarget.nodeType === 3) {
// no text node
oTarget = oTarget.parentNode;
}
// Remember the start position of the first touch to determine if a click was performed or not.
iStartX = oTouch.clientX;
iStartY = oTouch.clientY;
fireMouseEvent("mousedown", oEvent);
}
};
/**
* Touch move event handler. Fires mouse move event.
* @param {jQuery.Event} oEvent the event object
* @private
*/
onTouchMove = function(oEvent) {
var oTouch;
if (bHandleEvent) {
oTouch = oEvent.touches[0];
// Check if the finger is moved. When the finger was moved, no "click" event is fired.
if (Math.abs(oTouch.clientX - iStartX) > 10 || Math.abs(oTouch.clientY - iStartY) > 10) {
bIsMoved = true;
}
if (bIsMoved) {
// Fire "mousemove" event only when the finger was moved. This is to prevent unwanted movements.
fireMouseEvent("mousemove", oEvent);
}
}
};
/**
* Touch end event handler. Fires mouse up and click event.
* @param {jQuery.Event} oEvent the event object
* @private
*/
onTouchEnd = function(oEvent) {
fireMouseEvent("mouseup", oEvent);
if (!bIsMoved) {
fireMouseEvent("click", oEvent);
}
};
/**
* Touch cancel event handler. Fires mouse up event.
* @param {jQuery.Event} oEvent the event object
* @private
*/
onTouchCancel = function(oEvent) {
fireMouseEvent("mouseup", oEvent);
};
// Bind mouse events
for (; i < aMouseEvents.length; i++) {
// Add click on capturing phase to prevent propagation if necessary
document.addEventListener(aMouseEvents[i], onMouseEvent, true);
}
// Bind touch events
document.addEventListener('touchstart', onTouchStart, true);
document.addEventListener('touchmove', onTouchMove, true);
document.addEventListener('touchend', onTouchEnd, true);
document.addEventListener('touchcancel', onTouchCancel, true);
}());
}
/**
* Disable touch to mouse handling
*
* @public
*/
jQuery.sap.disableTouchToMouseHandling = function() {
var i = 0;
if (!bIsSimulatingTouchToMouseEvent) {
return;
}
// unbind touch events
document.removeEventListener('touchstart', onTouchStart, true);
document.removeEventListener('touchmove', onTouchMove, true);
document.removeEventListener('touchend', onTouchEnd, true);
document.removeEventListener('touchcancel', onTouchCancel, true);
// unbind mouse events
for (; i < aMouseEvents.length; i++) {
document.removeEventListener(aMouseEvents[i], onMouseEvent, true);
}
};
/**
* List of DOM events that a UIArea automatically takes care of.
*
* A control/element doesn't have to bind listeners for these events.
* It instead can implement an onevent(oEvent)
method
* for any of the following events that it wants to be notified about:
*
* click, dblclick, contextmenu, focusin, focusout, keydown, keypress, keyup, mousedown, mouseout, mouseover,
* mouseup, select, selectstart, dragstart, dragenter, dragover, dragleave, dragend, drop, paste, cut, input
*
* In case touch events are natively supported the following events are available in addition:
* touchstart, touchend, touchmove, touchcancel
*
* @public
*/
jQuery.sap.ControlEvents = [ // IMPORTANT: update the public documentation when extending this list
"click",
"dblclick",
"contextmenu",
"focusin",
"focusout",
"keydown",
"keypress",
"keyup",
"mousedown",
"mouseout",
"mouseover",
"mouseup",
"select",
"selectstart",
"dragstart",
"dragenter",
"dragover",
"dragleave",
"dragend",
"drop",
"paste",
"cut",
/* input event is fired synchronously on IE9+ when the value of an or