/*! * 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: * * @alias sap.ui.Device.media.RANGESETS#SAP_STANDARD * @public */ var RANGESETS = { "SAP_3STEPS": "3Step", "SAP_4STEPS": "4Step", "SAP_6STEPS": "6Step", "SAP_STANDARD": "Std" }; device.media.RANGESETS = RANGESETS; device.media._predefinedRangeSets = {}; device.media._predefinedRangeSets[RANGESETS.SAP_3STEPS] = {points: [520, 960], unit: "px", name: RANGESETS.SAP_3STEPS, names: ["S", "M", "L"]}; device.media._predefinedRangeSets[RANGESETS.SAP_4STEPS] = {points: [520, 760, 960], unit: "px", name: RANGESETS.SAP_4STEPS, names: ["S", "M", "L", "XL"]}; device.media._predefinedRangeSets[RANGESETS.SAP_6STEPS] = {points: [241, 400, 541, 768, 960], unit: "px", name: RANGESETS.SAP_6STEPS, names: ["XS", "S", "M", "L", "XL", "XXL"]}; device.media._predefinedRangeSets[RANGESETS.SAP_STANDARD] = {points: [600, 1024], unit: "px", name: RANGESETS.SAP_STANDARD, names: ["Phone", "Tablet", "Desktop"]}; var _defaultRangeSet = RANGESETS.SAP_STANDARD; var media_timeout = device.support.matchmedialistener ? 0 : 100; var _querysets = {}; var media_currentwidth = null; function getQuery(from, to, unit){ unit = unit || "px"; var q = "screen"; if (from > 0) { q = q + " and (min-width:" + from + unit + ")"; } if (to > 0) { q = q + " and (max-width:" + to + unit + ")"; } return q; } function handleChange(name){ if (!device.support.matchmedialistener && media_currentwidth == windowSize()[0]) { return; //Skip unnecessary resize events } if (_querysets[name].timer) { clearTimeout(_querysets[name].timer); _querysets[name].timer = null; } _querysets[name].timer = setTimeout(function() { var mParams = checkQueries(name, false); if (mParams) { fireEvent("media_" + name, mParams); } }, media_timeout); } function getRangeInfo(sSetName, iRangeIdx){ var q = _querysets[sSetName].queries[iRangeIdx]; var info = {from: q.from, unit: _querysets[sSetName].unit}; if (q.to >= 0) { info.to = q.to; } if (_querysets[sSetName].names) { info.name = _querysets[sSetName].names[iRangeIdx]; } return info; } function checkQueries(name, infoOnly){ if (_querysets[name]) { var aQueries = _querysets[name].queries; var info = null; for (var i = 0, len = aQueries.length; i < len; i++) { var q = aQueries[i]; if ((q != _querysets[name].currentquery || infoOnly) && device.media.matches(q.from, q.to, _querysets[name].unit)) { if (!infoOnly) { _querysets[name].currentquery = q; } if (!_querysets[name].noClasses && _querysets[name].names && !infoOnly) { refreshCSSClasses(name, _querysets[name].names[i]); } info = getRangeInfo(name, i); } } return info; } logger.log(WARNING, "No queryset with name " + name + " found", 'DEVICE.MEDIA'); return null; } function refreshCSSClasses(sSetName, sRangeName, bRemove){ var sClassPrefix = "sapUiMedia-" + sSetName + "-"; changeRootCSSClass(sClassPrefix + sRangeName, bRemove, sClassPrefix); } function changeRootCSSClass(sClassName, bRemove, sPrefix){ var oRoot = document.documentElement; if (oRoot.className.length == 0) { if (!bRemove) { oRoot.className = sClassName; } } else { var aCurrentClasses = oRoot.className.split(" "); var sNewClasses = ""; for (var i = 0; i < aCurrentClasses.length; i++) { if ((sPrefix && aCurrentClasses[i].indexOf(sPrefix) != 0) || (!sPrefix && aCurrentClasses[i] != sClassName)) { sNewClasses = sNewClasses + aCurrentClasses[i] + " "; } } if (!bRemove) { sNewClasses = sNewClasses + sClassName; } oRoot.className = sNewClasses; } } function windowSize(){ return [document.documentElement.clientWidth, document.documentElement.clientHeight]; } function convertToPx(val, unit){ if (unit === "em" || unit === "rem") { var s = window.getComputedStyle || function(e) { return e.currentStyle; }; var x = s(document.documentElement).fontSize; var f = (x && x.indexOf("px") >= 0) ? parseFloat(x, 10) : 16; return val * f; } return val; } function match_legacy(from, to, unit){ from = convertToPx(from, unit); to = convertToPx(to, unit); var width = windowSize()[0]; var a = from < 0 || from <= width; var b = to < 0 || width <= to; return a && b; } function match(from, to, unit){ var q = getQuery(from, to, unit); var mm = window.matchMedia(q); //FF returns null when running within an iframe with display:none return mm && mm.matches; } device.media.matches = device.support.matchmedia ? match : match_legacy; /** * Registers the given handler to the range change event, which is fired when a new range of the set is entered. * * The handler has one map parameter mParams: * * * @param {Function} fnFunction The function to call, when the range change event occurs. * @param {Object} [oListener] The 'this' context of the handler function. * @param {String} sName The name of the range set to listen to. * @name sap.ui.Device.media#attachHandler * @function * @public */ device.media.attachHandler = function(fnFunction, oListener, sName){ var name = sName || _defaultRangeSet; attachEvent("media_" + name, fnFunction, oListener); }; /** * Deregisters a previously registered handler from the range change event. * * @param {Function} fnFunction The function to call, when the range change event occurs. * @param {Object} [oListener] The 'this' context of the handler function. * @param {String} sName The name of the range set to listen to. * @name sap.ui.Device.media#detachHandler * @function * @public */ device.media.detachHandler = function(fnFunction, oListener, sName){ var name = sName || _defaultRangeSet; detachEvent("media_" + name, fnFunction, oListener); }; /** * Initializes a Screen width media query range set. * * This function can either be called only with the name parameter to initialize a predefined range set, * e.g. 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: * * * The range names are optional. If they are specified also a CSS class (e.g. sapUiMedia-MyRangeSet-Small) is added to the document root * depending on the current active range. This can be suppressed via parameter 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: * * * @param {Function} fnFunction The function to call, when the orientation change event occurs. * @param {Object} [oListener] The 'this' context of the handler function. * @name sap.ui.Device.orientation#attachHandler * @function * @public */ device.orientation.attachHandler = function(fnFunction, oListener){ attachEvent("orientation", fnFunction, oListener); }; /** * Registers the given handler to the resize event. * * The handler has one map parameter mParams: * * * @param {Function} fnFunction The function to call, when the resize event occurs. * @param {Object} [oListener] The 'this' context of the handler function. * @name sap.ui.Device.resize#attachHandler * @function * @public */ device.resize.attachHandler = function(fnFunction, oListener){ attachEvent("resize", fnFunction, oListener); }; /** * Deregisters a previously registered handler from the orientation change event. * @param {Function} fnFunction The function to call, when the orientation change event occurs. * @param {Object} [oListener] The 'this' context of the handler function. * @name sap.ui.Device.orientation#detachHandler * @function * @public */ device.orientation.detachHandler = function(fnFunction, oListener){ detachEvent("orientation", fnFunction, oListener); }; /** * Deregisters a previously registered handler from the resize event. * @param {Function} fnFunction The function to call, when the resize event occurs. * @param {Object} [oListener] The 'this' context of the handler function. * @name sap.ui.Device.resize#detachHandler * @function * @public */ device.resize.detachHandler = function(fnFunction, oListener){ detachEvent("resize", fnFunction, oListener); }; function setOrientationInfo(oInfo){ oInfo.landscape = isLandscape(true); oInfo.portrait = !oInfo.landscape; } function handleOrientationChange(){ setOrientationInfo(device.orientation); fireEvent("orientation", {landscape: device.orientation.landscape}); } function handleResizeChange(){ setResizeInfo(device.resize); fireEvent("resize", {height: device.resize.height, width: device.resize.width}); } function setResizeInfo(oInfo){ oInfo.width = windowSize()[0]; oInfo.height = windowSize()[1]; } function handleOrientationResizeChange(){ var wasL = device.orientation.landscape; var isL = isLandscape(); if (wasL != isL) { handleOrientationChange(); } //throttle resize events because most browsers throw one or more resize events per pixel //for every resize event inside the period from 150ms (starting from the first resize event), //we only fire one resize event after this period if (!iResizeTimeout) { iResizeTimeout = window.setTimeout(handleResizeTimeout, 150); } } function handleResizeTimeout() { handleResizeChange(); iResizeTimeout = null; } var bOrientationchange = false; var bResize = false; var iOrientationTimeout; var iResizeTimeout; var iClearFlagTimeout; var iWindowHeightOld = windowSize()[1]; var iWindowWidthOld = windowSize()[0]; var bKeyboardOpen = false; var iLastResizeTime; var rInputTagRegex = /INPUT|TEXTAREA|SELECT/; // On iPhone with iOS version 7.0.x and on iPad with iOS version 7.x (tested with all versions below 7.1.1), there's a invalide resize event fired // when changing the orientation while keyboard is shown. var bSkipFirstResize = device.os.ios && device.browser.name === "sf" && ((device.system.phone && device.os.version >= 7 && device.os.version < 7.1) || (device.system.tablet && device.os.version >= 7)); function isLandscape(bFromOrientationChange){ if (device.support.touch && device.support.orientation) { //if on screen keyboard is open and the call of this method is from orientation change listener, reverse the last value. //this is because when keyboard opens on android device, the height can be less than the width even in portrait mode. if (bKeyboardOpen && bFromOrientationChange) { return !device.orientation.landscape; } //when keyboard opens, the last orientation change value will be retured. if (bKeyboardOpen) { return device.orientation.landscape; } //otherwise compare the width and height of window } else { //most desktop browsers and windows phone/tablet which not support orientationchange if (device.support.matchmedia && device.support.orientation) { return !!window.matchMedia("(orientation: landscape)").matches; } } var size = windowSize(); return size[0] > size[1]; } function handleMobileOrientationResizeChange(evt) { if (evt.type == "resize") { // supress the first invalid resize event fired before orientationchange event while keyboard is open on iPhone 7.0.x // because this event has wrong size infos if (bSkipFirstResize && rInputTagRegex.test(document.activeElement.tagName) && !bOrientationchange) { return; } var iWindowHeightNew = windowSize()[1]; var iWindowWidthNew = windowSize()[0]; var iTime = new Date().getTime(); //skip multiple resize events by only one orientationchange if (iWindowHeightNew === iWindowHeightOld && iWindowWidthNew === iWindowWidthOld) { return; } bResize = true; //on mobile devices opening the keyboard on some devices leads to a resize event //in this case only the height changes, not the width if ((iWindowHeightOld != iWindowHeightNew) && (iWindowWidthOld == iWindowWidthNew)) { //Asus Transformer tablet fires two resize events when orientation changes while keyboard is open. //Between these two events, only the height changes. The check of if keyboard is open has to be skipped because //it may be judged as keyboard closed but the keyboard is still open which will affect the orientation detection if (!iLastResizeTime || (iTime - iLastResizeTime > 300)) { bKeyboardOpen = (iWindowHeightNew < iWindowHeightOld); } handleResizeChange(); } else { iWindowWidthOld = iWindowWidthNew; } iLastResizeTime = iTime; iWindowHeightOld = iWindowHeightNew; if (iClearFlagTimeout) { window.clearTimeout(iClearFlagTimeout); iClearFlagTimeout = null; } //Some Android build-in browser fires a resize event after the viewport is applied. //This resize event has to be dismissed otherwise when the next orientationchange event happens, //a UI5 resize event will be fired with the wrong window size. iClearFlagTimeout = window.setTimeout(clearFlags, 1200); } else if (evt.type == "orientationchange") { bOrientationchange = true; } if (iOrientationTimeout) { clearTimeout(iOrientationTimeout); iOrientationTimeout = null; } iOrientationTimeout = window.setTimeout(handleMobileTimeout, 50); } function handleMobileTimeout() { if (bOrientationchange && bResize) { handleOrientationChange(); handleResizeChange(); bOrientationchange = false; bResize = false; if (iClearFlagTimeout) { window.clearTimeout(iClearFlagTimeout); iClearFlagTimeout = null; } } iOrientationTimeout = null; } function clearFlags(){ bOrientationchange = false; bResize = false; iClearFlagTimeout = null; } //******** Update browser settings for test purposes ******** device._update = function(_simMobileOnDesktop) { ua = navigator.userAgent; logger.log(WARNING, "Device API values manipulated: NOT PRODUCTIVE FEATURE!!! This should be only used for test purposes. Only use if you know what you are doing."); setBrowser(); setOS(); setSystem(_simMobileOnDesktop); }; //******************************************************** setResizeInfo(device.resize); setOrientationInfo(device.orientation); //Add API to global namespace window.sap.ui.Device = device; // Add handler for orientationchange and resize after initialization of Device API (IE8 fires onresize synchronously) if (device.support.touch && device.support.orientation) { //logic for mobile devices which support orientationchange (like ios, android, blackberry) window.addEventListener("resize", handleMobileOrientationResizeChange, false); window.addEventListener("orientationchange", handleMobileOrientationResizeChange, false); } else { if (window.addEventListener) { //most desktop browsers and windows phone/tablet which not support orientationchange window.addEventListener("resize", handleOrientationResizeChange, false); } else { //IE8 window.attachEvent("onresize", handleOrientationResizeChange); } } //Always initialize the default media range set device.media.initRangeSet(); // define module if API is available if (sap.ui.define) { sap.ui.define("sap/ui/Device", [], function() { return device; }); } }()); /*! * URI.js - Mutating URLs * * Version: 1.11.2 * * Author: Rodney Rehm * Web: http://medialize.github.com/URI.js/ * * Licensed under * MIT License http://www.opensource.org/licenses/mit-license * GPL v3 http://opensource.org/licenses/GPL-3.0 * */ (function (root, factory) { // https://github.com/umdjs/umd/blob/master/returnExports.js if (typeof exports === 'object') { // Node module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains')); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. // ##### BEGIN: MODIFIED BY SAP // define(['./punycode', './IPv6', './SecondLevelDomains'], factory); // we can't support loading URI.js via AMD define. URI.js is packaged with SAPUI5 code // and define() doesn't execute synchronously. So the UI5 code executed after URI.js // fails as it is missing the URI.js code. // Instead we use the standard init code and only expose the result via define() // The (optional) dependencies are lost or must be loaded in advance root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); define([], function() { return root.URI; }); // ##### END: MODIFIED BY SAP } else { // Browser globals (root is window) root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); } }(this, function (punycode, IPv6, SLD, root) { "use strict"; // save current URI variable, if any var _URI = root && root.URI; function URI(url, base) { // Allow instantiation without the 'new' keyword if (!(this instanceof URI)) { return new URI(url, base); } if (url === undefined) { if (typeof location !== 'undefined') { url = location.href + ""; } else { url = ""; } } this.href(url); // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor if (base !== undefined) { return this.absoluteTo(base); } return this; }; var p = URI.prototype; var hasOwn = Object.prototype.hasOwnProperty; function escapeRegEx(string) { // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); } function getType(value) { // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value if (value === undefined) { return 'Undefined'; } return String(Object.prototype.toString.call(value)).slice(8, -1); } function isArray(obj) { return getType(obj) === "Array"; } function filterArrayValues(data, value) { var lookup = {}; var i, length; if (isArray(value)) { for (i = 0, length = value.length; i < length; i++) { lookup[value[i]] = true; } } else { lookup[value] = true; } for (i = 0, length = data.length; i < length; i++) { if (lookup[data[i]] !== undefined) { data.splice(i, 1); length--; i--; } } return data; } function arrayContains(list, value) { var i, length; // value may be string, number, array, regexp if (isArray(value)) { // Note: this can be optimized to O(n) (instead of current O(m * n)) for (i = 0, length = value.length; i < length; i++) { if (!arrayContains(list, value[i])) { return false; } } return true; } var _type = getType(value); for (i = 0, length = list.length; i < length; i++) { if (_type === 'RegExp') { if (typeof list[i] === 'string' && list[i].match(value)) { return true; } } else if (list[i] === value) { return true; } } return false; } function arraysEqual(one, two) { if (!isArray(one) || !isArray(two)) { return false; } // arrays can't be equal if they have different amount of content if (one.length !== two.length) { return false; } one.sort(); two.sort(); for (var i = 0, l = one.length; i < l; i++) { if (one[i] !== two[i]) { return false; } } return true; } URI._parts = function() { return { protocol: null, username: null, password: null, hostname: null, urn: null, port: null, path: null, query: null, fragment: null, // state duplicateQueryParameters: URI.duplicateQueryParameters, escapeQuerySpace: URI.escapeQuerySpace }; }; // state: allow duplicate query parameters (a=1&a=1) URI.duplicateQueryParameters = false; // state: replaces + with %20 (space in query strings) URI.escapeQuerySpace = true; // static properties URI.protocol_expression = /^[a-z][a-z0-9-+-]*$/i; URI.idn_expression = /[^a-z0-9\.-]/i; URI.punycode_expression = /(xn--)/i; // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; // credits to Rich Brown // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 // specification: http://www.ietf.org/rfc/rfc4291.txt URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; // gruber revised expression - http://rodneyrehm.de/t/url-regex.html URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig; // http://www.iana.org/assignments/uri-schemes.html // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports URI.defaultPorts = { http: "80", https: "443", ftp: "21", gopher: "70", ws: "80", wss: "443" }; // allowed hostname characters according to RFC 3986 // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/; // map DOM Elements to their URI attribute URI.domAttributes = { 'a': 'href', 'blockquote': 'cite', 'link': 'href', 'base': 'href', 'script': 'src', 'form': 'action', 'img': 'src', 'area': 'href', 'iframe': 'src', 'embed': 'src', 'source': 'src', 'track': 'src', 'input': 'src' // but only if type="image" }; URI.getDomAttribute = function(node) { if (!node || !node.nodeName) { return undefined; } var nodeName = node.nodeName.toLowerCase(); // should only expose src for type="image" if (nodeName === 'input' && node.type !== 'image') { return undefined; } return URI.domAttributes[nodeName]; }; function escapeForDumbFirefox36(value) { // https://github.com/medialize/URI.js/issues/91 return escape(value); } // encoding / decoding according to RFC3986 function strictEncodeURIComponent(string) { // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent return encodeURIComponent(string) .replace(/[!'()*]/g, escapeForDumbFirefox36) .replace(/\*/g, "%2A"); } URI.encode = strictEncodeURIComponent; URI.decode = decodeURIComponent; URI.iso8859 = function() { URI.encode = escape; URI.decode = unescape; }; URI.unicode = function() { URI.encode = strictEncodeURIComponent; URI.decode = decodeURIComponent; }; URI.characters = { pathname: { encode: { // RFC3986 2.1: For consistency, URI producers and normalizers should // use uppercase hexadecimal digits for all percent-encodings. expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig, map: { // -._~!'()* "%24": "$", "%26": "&", "%2B": "+", "%2C": ",", "%3B": ";", "%3D": "=", "%3A": ":", "%40": "@" } }, decode: { expression: /[\/\?#]/g, map: { "/": "%2F", "?": "%3F", "#": "%23" } } }, reserved: { encode: { // RFC3986 2.1: For consistency, URI producers and normalizers should // use uppercase hexadecimal digits for all percent-encodings. expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig, map: { // gen-delims "%3A": ":", "%2F": "/", "%3F": "?", "%23": "#", "%5B": "[", "%5D": "]", "%40": "@", // sub-delims "%21": "!", "%24": "$", "%26": "&", "%27": "'", "%28": "(", "%29": ")", "%2A": "*", "%2B": "+", "%2C": ",", "%3B": ";", "%3D": "=" } } } }; URI.encodeQuery = function(string, escapeQuerySpace) { var escaped = URI.encode(string + ""); return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped; }; URI.decodeQuery = function(string, escapeQuerySpace) { string += ""; try { return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string); } catch(e) { // we're not going to mess with weird encodings, // give up and return the undecoded original string // see https://github.com/medialize/URI.js/issues/87 // see https://github.com/medialize/URI.js/issues/92 return string; } }; URI.recodePath = function(string) { var segments = (string + "").split('/'); for (var i = 0, length = segments.length; i < length; i++) { segments[i] = URI.encodePathSegment(URI.decode(segments[i])); } return segments.join('/'); }; URI.decodePath = function(string) { var segments = (string + "").split('/'); for (var i = 0, length = segments.length; i < length; i++) { segments[i] = URI.decodePathSegment(segments[i]); } return segments.join('/'); }; // generate encode/decode path functions var _parts = {'encode':'encode', 'decode':'decode'}; var _part; var generateAccessor = function(_group, _part) { return function(string) { return URI[_part](string + "").replace(URI.characters[_group][_part].expression, function(c) { return URI.characters[_group][_part].map[c]; }); }; }; for (_part in _parts) { URI[_part + "PathSegment"] = generateAccessor("pathname", _parts[_part]); } URI.encodeReserved = generateAccessor("reserved", "encode"); URI.parse = function(string, parts) { var pos; if (!parts) { parts = {}; } // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] // extract fragment pos = string.indexOf('#'); if (pos > -1) { // escaping? parts.fragment = string.substring(pos + 1) || null; string = string.substring(0, pos); } // extract query pos = string.indexOf('?'); if (pos > -1) { // escaping? parts.query = string.substring(pos + 1) || null; string = string.substring(0, pos); } // extract protocol if (string.substring(0, 2) === '//') { // relative-scheme parts.protocol = null; string = string.substring(2); // extract "user:pass@host:port" string = URI.parseAuthority(string, parts); } else { pos = string.indexOf(':'); if (pos > -1) { parts.protocol = string.substring(0, pos) || null; if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) { // : may be within the path parts.protocol = undefined; } else if (parts.protocol === 'file') { // the file scheme: does not contain an authority string = string.substring(pos + 3); } else if (string.substring(pos + 1, pos + 3) === '//') { string = string.substring(pos + 3); // extract "user:pass@host:port" string = URI.parseAuthority(string, parts); } else { string = string.substring(pos + 1); parts.urn = true; } } } // what's left must be the path parts.path = string; // and we're done return parts; }; URI.parseHost = function(string, parts) { // extract host:port var pos = string.indexOf('/'); var bracketPos; var t; if (pos === -1) { pos = string.length; } if (string.charAt(0) === "[") { // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts // IPv6+port in the format [2001:db8::1]:80 (for the time being) bracketPos = string.indexOf(']'); parts.hostname = string.substring(1, bracketPos) || null; parts.port = string.substring(bracketPos+2, pos) || null; } else if (string.indexOf(':') !== string.lastIndexOf(':')) { // IPv6 host contains multiple colons - but no port // this notation is actually not allowed by RFC 3986, but we're a liberal parser parts.hostname = string.substring(0, pos) || null; parts.port = null; } else { t = string.substring(0, pos).split(':'); parts.hostname = t[0] || null; parts.port = t[1] || null; } if (parts.hostname && string.substring(pos).charAt(0) !== '/') { pos++; string = "/" + string; } return string.substring(pos) || '/'; }; URI.parseAuthority = function(string, parts) { string = URI.parseUserinfo(string, parts); return URI.parseHost(string, parts); }; URI.parseUserinfo = function(string, parts) { // extract username:password var firstSlash = string.indexOf('/'); var pos = firstSlash > -1 ? string.lastIndexOf('@', firstSlash) : string.indexOf('@'); var t; // authority@ must come before /path if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { t = string.substring(0, pos).split(':'); parts.username = t[0] ? URI.decode(t[0]) : null; t.shift(); parts.password = t[0] ? URI.decode(t.join(':')) : null; string = string.substring(pos + 1); } else { parts.username = null; parts.password = null; } return string; }; URI.parseQuery = function(string, escapeQuerySpace) { if (!string) { return {}; } // throw out the funky business - "?"[name"="value"&"]+ string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); if (!string) { return {}; } var items = {}; var splits = string.split('&'); var length = splits.length; var v, name, value; for (var i = 0; i < length; i++) { v = splits[i].split('='); name = URI.decodeQuery(v.shift(), escapeQuerySpace); // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null; if (items[name]) { if (typeof items[name] === "string") { items[name] = [items[name]]; } items[name].push(value); } else { items[name] = value; } } return items; }; URI.build = function(parts) { var t = ""; if (parts.protocol) { t += parts.protocol + ":"; } if (!parts.urn && (t || parts.hostname)) { t += '//'; } t += (URI.buildAuthority(parts) || ''); if (typeof parts.path === "string") { if (parts.path.charAt(0) !== '/' && typeof parts.hostname === "string") { t += '/'; } t += parts.path; } if (typeof parts.query === "string" && parts.query) { t += '?' + parts.query; } if (typeof parts.fragment === "string" && parts.fragment) { t += '#' + parts.fragment; } return t; }; URI.buildHost = function(parts) { var t = ""; if (!parts.hostname) { return ""; } else if (URI.ip6_expression.test(parts.hostname)) { if (parts.port) { t += "[" + parts.hostname + "]:" + parts.port; } else { // don't know if we should always wrap IPv6 in [] // the RFC explicitly says SHOULD, not MUST. t += parts.hostname; } } else { t += parts.hostname; if (parts.port) { t += ':' + parts.port; } } return t; }; URI.buildAuthority = function(parts) { return URI.buildUserinfo(parts) + URI.buildHost(parts); }; URI.buildUserinfo = function(parts) { var t = ""; if (parts.username) { t += URI.encode(parts.username); if (parts.password) { t += ':' + URI.encode(parts.password); } t += "@"; } return t; }; URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) { // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax! // URI.js treats the query string as being application/x-www-form-urlencoded // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type var t = ""; var unique, key, i, length; for (key in data) { if (hasOwn.call(data, key) && key) { if (isArray(data[key])) { unique = {}; for (i = 0, length = data[key].length; i < length; i++) { if (data[key][i] !== undefined && unique[data[key][i] + ""] === undefined) { t += "&" + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace); if (duplicateQueryParameters !== true) { unique[data[key][i] + ""] = true; } } } } else if (data[key] !== undefined) { t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace); } } } return t.substring(1); }; URI.buildQueryParameter = function(name, value, escapeQuerySpace) { // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? "=" + URI.encodeQuery(value, escapeQuerySpace) : ""); }; URI.addQuery = function(data, name, value) { if (typeof name === "object") { for (var key in name) { if (hasOwn.call(name, key)) { URI.addQuery(data, key, name[key]); } } } else if (typeof name === "string") { if (data[name] === undefined) { data[name] = value; return; } else if (typeof data[name] === "string") { data[name] = [data[name]]; } if (!isArray(value)) { value = [value]; } data[name] = data[name].concat(value); } else { throw new TypeError("URI.addQuery() accepts an object, string as the name parameter"); } }; URI.removeQuery = function(data, name, value) { var i, length, key; if (isArray(name)) { for (i = 0, length = name.length; i < length; i++) { data[name[i]] = undefined; } } else if (typeof name === "object") { for (key in name) { if (hasOwn.call(name, key)) { URI.removeQuery(data, key, name[key]); } } } else if (typeof name === "string") { if (value !== undefined) { if (data[name] === value) { data[name] = undefined; } else if (isArray(data[name])) { data[name] = filterArrayValues(data[name], value); } } else { data[name] = undefined; } } else { throw new TypeError("URI.addQuery() accepts an object, string as the first parameter"); } }; URI.hasQuery = function(data, name, value, withinArray) { if (typeof name === "object") { for (var key in name) { if (hasOwn.call(name, key)) { if (!URI.hasQuery(data, key, name[key])) { return false; } } } return true; } else if (typeof name !== "string") { throw new TypeError("URI.hasQuery() accepts an object, string as the name parameter"); } switch (getType(value)) { case 'Undefined': // true if exists (but may be empty) return name in data; // data[name] !== undefined; case 'Boolean': // true if exists and non-empty var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]); return value === _booly; case 'Function': // allow complex comparison return !!value(data[name], name, data); case 'Array': if (!isArray(data[name])) { return false; } var op = withinArray ? arrayContains : arraysEqual; return op(data[name], value); case 'RegExp': if (!isArray(data[name])) { return Boolean(data[name] && data[name].match(value)); } if (!withinArray) { return false; } return arrayContains(data[name], value); case 'Number': value = String(value); // omit break; case 'String': if (!isArray(data[name])) { return data[name] === value; } if (!withinArray) { return false; } return arrayContains(data[name], value); default: throw new TypeError("URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter"); } }; URI.commonPath = function(one, two) { var length = Math.min(one.length, two.length); var pos; // find first non-matching character for (pos = 0; pos < length; pos++) { if (one.charAt(pos) !== two.charAt(pos)) { pos--; break; } } if (pos < 1) { return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : ''; } // revert to last / if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') { pos = one.substring(0, pos).lastIndexOf('/'); } return one.substring(0, pos + 1); }; URI.withinString = function(string, callback) { // expression used is "gruber revised" (@gruber v2) determined to be the best solution in // a regex sprint we did a couple of ages ago at // * http://mathiasbynens.be/demo/url-regex // * http://rodneyrehm.de/t/url-regex.html return string.replace(URI.find_uri_expression, callback); }; URI.ensureValidHostname = function(v) { // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) // they are not part of DNS and therefore ignored by URI.js if (v.match(URI.invalid_hostname_characters)) { // test punycode if (!punycode) { throw new TypeError("Hostname '" + v + "' contains characters other than [A-Z0-9.-] and Punycode.js is not available"); } if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { throw new TypeError("Hostname '" + v + "' contains characters other than [A-Z0-9.-]"); } } }; // noConflict URI.noConflict = function(removeAll) { if (removeAll) { var unconflicted = { URI: this.noConflict() }; if (URITemplate && typeof URITemplate.noConflict == "function") { unconflicted.URITemplate = URITemplate.noConflict(); } if (IPv6 && typeof IPv6.noConflict == "function") { unconflicted.IPv6 = IPv6.noConflict(); } if (SecondLevelDomains && typeof SecondLevelDomains.noConflict == "function") { unconflicted.SecondLevelDomains = SecondLevelDomains.noConflict(); } return unconflicted; } else if (root.URI === this) { root.URI = _URI; } return this; }; p.build = function(deferBuild) { if (deferBuild === true) { this._deferred_build = true; } else if (deferBuild === undefined || this._deferred_build) { this._string = URI.build(this._parts); this._deferred_build = false; } return this; }; p.clone = function() { return new URI(this); }; p.valueOf = p.toString = function() { return this.build(false)._string; }; // generate simple accessors _parts = {protocol: 'protocol', username: 'username', password: 'password', hostname: 'hostname', port: 'port'}; generateAccessor = function(_part){ return function(v, build) { if (v === undefined) { return this._parts[_part] || ""; } else { this._parts[_part] = v || null; this.build(!build); return this; } }; }; for (_part in _parts) { p[_part] = generateAccessor(_parts[_part]); } // generate accessors with optionally prefixed input _parts = {query: '?', fragment: '#'}; generateAccessor = function(_part, _key){ return function(v, build) { if (v === undefined) { return this._parts[_part] || ""; } else { if (v !== null) { v = v + ""; if (v.charAt(0) === _key) { v = v.substring(1); } } this._parts[_part] = v; this.build(!build); return this; } }; }; for (_part in _parts) { p[_part] = generateAccessor(_part, _parts[_part]); } // generate accessors with prefixed output _parts = {search: ['?', 'query'], hash: ['#', 'fragment']}; generateAccessor = function(_part, _key){ return function(v, build) { var t = this[_part](v, build); return typeof t === "string" && t.length ? (_key + t) : t; }; }; for (_part in _parts) { p[_part] = generateAccessor(_parts[_part][1], _parts[_part][0]); } p.pathname = function(v, build) { if (v === undefined || v === true) { var res = this._parts.path || (this._parts.hostname ? '/' : ''); return v ? URI.decodePath(res) : res; } else { this._parts.path = v ? URI.recodePath(v) : "/"; this.build(!build); return this; } }; p.path = p.pathname; p.href = function(href, build) { var key; if (href === undefined) { return this.toString(); } this._string = ""; this._parts = URI._parts(); var _URI = href instanceof URI; var _object = typeof href === "object" && (href.hostname || href.path || href.pathname); if (href.nodeName) { var attribute = URI.getDomAttribute(href); href = href[attribute] || ""; _object = false; } // window.location is reported to be an object, but it's not the sort // of object we're looking for: // * location.protocol ends with a colon // * location.query != object.search // * location.hash != object.fragment // simply serializing the unknown object should do the trick // (for location, not for everything...) if (!_URI && _object && href.pathname !== undefined) { href = href.toString(); } if (typeof href === "string") { this._parts = URI.parse(href, this._parts); } else if (_URI || _object) { var src = _URI ? href._parts : href; for (key in src) { if (hasOwn.call(this._parts, key)) { this._parts[key] = src[key]; } } } else { throw new TypeError("invalid input"); } this.build(!build); return this; }; // identification accessors p.is = function(what) { var ip = false; var ip4 = false; var ip6 = false; var name = false; var sld = false; var idn = false; var punycode = false; var relative = !this._parts.urn; if (this._parts.hostname) { relative = false; ip4 = URI.ip4_expression.test(this._parts.hostname); ip6 = URI.ip6_expression.test(this._parts.hostname); ip = ip4 || ip6; name = !ip; sld = name && SLD && SLD.has(this._parts.hostname); idn = name && URI.idn_expression.test(this._parts.hostname); punycode = name && URI.punycode_expression.test(this._parts.hostname); } switch (what.toLowerCase()) { case 'relative': return relative; case 'absolute': return !relative; // hostname identification case 'domain': case 'name': return name; case 'sld': return sld; case 'ip': return ip; case 'ip4': case 'ipv4': case 'inet4': return ip4; case 'ip6': case 'ipv6': case 'inet6': return ip6; case 'idn': return idn; case 'url': return !this._parts.urn; case 'urn': return !!this._parts.urn; case 'punycode': return punycode; } return null; }; // component specific input validation var _protocol = p.protocol; var _port = p.port; var _hostname = p.hostname; p.protocol = function(v, build) { if (v !== undefined) { if (v) { // accept trailing :// v = v.replace(/:(\/\/)?$/, ''); if (v.match(/[^a-zA-z0-9\.+-]/)) { throw new TypeError("Protocol '" + v + "' contains characters other than [A-Z0-9.+-]"); } } } return _protocol.call(this, v, build); }; p.scheme = p.protocol; p.port = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v !== undefined) { if (v === 0) { v = null; } if (v) { v += ""; if (v.charAt(0) === ":") { v = v.substring(1); } if (v.match(/[^0-9]/)) { throw new TypeError("Port '" + v + "' contains characters other than [0-9]"); } } } return _port.call(this, v, build); }; p.hostname = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v !== undefined) { var x = {}; URI.parseHost(v, x); v = x.hostname; } return _hostname.call(this, v, build); }; // compound accessors p.host = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { return this._parts.hostname ? URI.buildHost(this._parts) : ""; } else { URI.parseHost(v, this._parts); this.build(!build); return this; } }; p.authority = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { return this._parts.hostname ? URI.buildAuthority(this._parts) : ""; } else { URI.parseAuthority(v, this._parts); this.build(!build); return this; } }; p.userinfo = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined) { if (!this._parts.username) { return ""; } var t = URI.buildUserinfo(this._parts); return t.substring(0, t.length -1); } else { if (v[v.length-1] !== '@') { v += '@'; } URI.parseUserinfo(v, this._parts); this.build(!build); return this; } }; p.resource = function(v, build) { var parts; if (v === undefined) { return this.path() + this.search() + this.hash(); } parts = URI.parse(v); this._parts.path = parts.path; this._parts.query = parts.query; this._parts.fragment = parts.fragment; this.build(!build); return this; }; // fraction accessors p.subdomain = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } // convenience, return "www" from "www.example.org" if (v === undefined) { if (!this._parts.hostname || this.is('IP')) { return ""; } // grab domain and add another segment var end = this._parts.hostname.length - this.domain().length - 1; return this._parts.hostname.substring(0, end) || ""; } else { var e = this._parts.hostname.length - this.domain().length; var sub = this._parts.hostname.substring(0, e); var replace = new RegExp('^' + escapeRegEx(sub)); if (v && v.charAt(v.length - 1) !== '.') { v += "."; } if (v) { URI.ensureValidHostname(v); } this._parts.hostname = this._parts.hostname.replace(replace, v); this.build(!build); return this; } }; p.domain = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (typeof v === 'boolean') { build = v; v = undefined; } // convenience, return "example.org" from "www.example.org" if (v === undefined) { if (!this._parts.hostname || this.is('IP')) { return ""; } // if hostname consists of 1 or 2 segments, it must be the domain var t = this._parts.hostname.match(/\./g); if (t && t.length < 2) { return this._parts.hostname; } // grab tld and add another segment var end = this._parts.hostname.length - this.tld(build).length - 1; end = this._parts.hostname.lastIndexOf('.', end -1) + 1; return this._parts.hostname.substring(end) || ""; } else { if (!v) { throw new TypeError("cannot set domain empty"); } URI.ensureValidHostname(v); if (!this._parts.hostname || this.is('IP')) { this._parts.hostname = v; } else { var replace = new RegExp(escapeRegEx(this.domain()) + "$"); this._parts.hostname = this._parts.hostname.replace(replace, v); } this.build(!build); return this; } }; p.tld = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (typeof v === 'boolean') { build = v; v = undefined; } // return "org" from "www.example.org" if (v === undefined) { if (!this._parts.hostname || this.is('IP')) { return ""; } var pos = this._parts.hostname.lastIndexOf('.'); var tld = this._parts.hostname.substring(pos + 1); if (build !== true && SLD && SLD.list[tld.toLowerCase()]) { return SLD.get(this._parts.hostname) || tld; } return tld; } else { var replace; if (!v) { throw new TypeError("cannot set TLD empty"); } else if (v.match(/[^a-zA-Z0-9-]/)) { if (SLD && SLD.is(v)) { replace = new RegExp(escapeRegEx(this.tld()) + "$"); this._parts.hostname = this._parts.hostname.replace(replace, v); } else { throw new TypeError("TLD '" + v + "' contains characters other than [A-Z0-9]"); } } else if (!this._parts.hostname || this.is('IP')) { throw new ReferenceError("cannot set TLD on non-domain host"); } else { replace = new RegExp(escapeRegEx(this.tld()) + "$"); this._parts.hostname = this._parts.hostname.replace(replace, v); } this.build(!build); return this; } }; p.directory = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined || v === true) { if (!this._parts.path && !this._parts.hostname) { return ''; } if (this._parts.path === '/') { return '/'; } var end = this._parts.path.length - this.filename().length - 1; var res = this._parts.path.substring(0, end) || (this._parts.hostname ? "/" : ""); return v ? URI.decodePath(res) : res; } else { var e = this._parts.path.length - this.filename().length; var directory = this._parts.path.substring(0, e); var replace = new RegExp('^' + escapeRegEx(directory)); // fully qualifier directories begin with a slash if (!this.is('relative')) { if (!v) { v = '/'; } if (v.charAt(0) !== '/') { v = "/" + v; } } // directories always end with a slash if (v && v.charAt(v.length - 1) !== '/') { v += '/'; } v = URI.recodePath(v); this._parts.path = this._parts.path.replace(replace, v); this.build(!build); return this; } }; p.filename = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined || v === true) { if (!this._parts.path || this._parts.path === '/') { return ""; } var pos = this._parts.path.lastIndexOf('/'); var res = this._parts.path.substring(pos+1); return v ? URI.decodePathSegment(res) : res; } else { var mutatedDirectory = false; if (v.charAt(0) === '/') { v = v.substring(1); } if (v.match(/\.?\//)) { mutatedDirectory = true; } var replace = new RegExp(escapeRegEx(this.filename()) + "$"); v = URI.recodePath(v); this._parts.path = this._parts.path.replace(replace, v); if (mutatedDirectory) { this.normalizePath(build); } else { this.build(!build); } return this; } }; p.suffix = function(v, build) { if (this._parts.urn) { return v === undefined ? '' : this; } if (v === undefined || v === true) { if (!this._parts.path || this._parts.path === '/') { return ""; } var filename = this.filename(); var pos = filename.lastIndexOf('.'); var s, res; if (pos === -1) { return ""; } // suffix may only contain alnum characters (yup, I made this up.) s = filename.substring(pos+1); res = (/^[a-z0-9%]+$/i).test(s) ? s : ""; return v ? URI.decodePathSegment(res) : res; } else { if (v.charAt(0) === '.') { v = v.substring(1); } var suffix = this.suffix(); var replace; if (!suffix) { if (!v) { return this; } this._parts.path += '.' + URI.recodePath(v); } else if (!v) { replace = new RegExp(escapeRegEx("." + suffix) + "$"); } else { replace = new RegExp(escapeRegEx(suffix) + "$"); } if (replace) { v = URI.recodePath(v); this._parts.path = this._parts.path.replace(replace, v); } this.build(!build); return this; } }; p.segment = function(segment, v, build) { var separator = this._parts.urn ? ':' : '/'; var path = this.path(); var absolute = path.substring(0, 1) === '/'; var segments = path.split(separator); if (segment !== undefined && typeof segment !== 'number') { build = v; v = segment; segment = undefined; } if (segment !== undefined && typeof segment !== 'number') { throw new Error("Bad segment '" + segment + "', must be 0-based integer"); } if (absolute) { segments.shift(); } if (segment < 0) { // allow negative indexes to address from the end segment = Math.max(segments.length + segment, 0); } if (v === undefined) { return segment === undefined ? segments : segments[segment]; } else if (segment === null || segments[segment] === undefined) { if (isArray(v)) { segments = []; // collapse empty elements within array for (var i=0, l=v.length; i < l; i++) { if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) { continue; } if (segments.length && !segments[segments.length -1].length) { segments.pop(); } segments.push(v[i]); } } else if (v || (typeof v === "string")) { if (segments[segments.length -1] === "") { // empty trailing elements have to be overwritten // to prevent results such as /foo//bar segments[segments.length -1] = v; } else { segments.push(v); } } } else { if (v || (typeof v === "string" && v.length)) { segments[segment] = v; } else { segments.splice(segment, 1); } } if (absolute) { segments.unshift(""); } return this.path(segments.join(separator), build); }; p.segmentCoded = function(segment, v, build) { var segments, i, l; if (typeof segment !== 'number') { build = v; v = segment; segment = undefined; } if (v === undefined) { segments = this.segment(segment, v, build); if (!isArray(segments)) { segments = segments !== undefined ? URI.decode(segments) : undefined; } else { for (i = 0, l = segments.length; i < l; i++) { segments[i] = URI.decode(segments[i]); } } return segments; } if (!isArray(v)) { v = typeof v === 'string' ? URI.encode(v) : v; } else { for (i = 0, l = v.length; i < l; i++) { v[i] = URI.decode(v[i]); } } return this.segment(segment, v, build); }; // mutating query string var q = p.query; p.query = function(v, build) { if (v === true) { return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); } else if (typeof v === "function") { var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); var result = v.call(this, data); this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); this.build(!build); return this; } else if (v !== undefined && typeof v !== "string") { this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); this.build(!build); return this; } else { return q.call(this, v, build); } }; p.setQuery = function(name, value, build) { var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); if (typeof name === "object") { for (var key in name) { if (hasOwn.call(name, key)) { data[key] = name[key]; } } } else if (typeof name === "string") { data[name] = value !== undefined ? value : null; } else { throw new TypeError("URI.addQuery() accepts an object, string as the name parameter"); } this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); if (typeof name !== "string") { build = value; } this.build(!build); return this; }; p.addQuery = function(name, value, build) { var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); URI.addQuery(data, name, value === undefined ? null : value); this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); if (typeof name !== "string") { build = value; } this.build(!build); return this; }; p.removeQuery = function(name, value, build) { var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); URI.removeQuery(data, name, value); this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); if (typeof name !== "string") { build = value; } this.build(!build); return this; }; p.hasQuery = function(name, value, withinArray) { var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); return URI.hasQuery(data, name, value, withinArray); }; p.setSearch = p.setQuery; p.addSearch = p.addQuery; p.removeSearch = p.removeQuery; p.hasSearch = p.hasQuery; // sanitizing URLs p.normalize = function() { if (this._parts.urn) { return this .normalizeProtocol(false) .normalizeQuery(false) .normalizeFragment(false) .build(); } return this .normalizeProtocol(false) .normalizeHostname(false) .normalizePort(false) .normalizePath(false) .normalizeQuery(false) .normalizeFragment(false) .build(); }; p.normalizeProtocol = function(build) { if (typeof this._parts.protocol === "string") { this._parts.protocol = this._parts.protocol.toLowerCase(); this.build(!build); } return this; }; p.normalizeHostname = function(build) { if (this._parts.hostname) { if (this.is('IDN') && punycode) { this._parts.hostname = punycode.toASCII(this._parts.hostname); } else if (this.is('IPv6') && IPv6) { this._parts.hostname = IPv6.best(this._parts.hostname); } this._parts.hostname = this._parts.hostname.toLowerCase(); this.build(!build); } return this; }; p.normalizePort = function(build) { // remove port of it's the protocol's default if (typeof this._parts.protocol === "string" && this._parts.port === URI.defaultPorts[this._parts.protocol]) { this._parts.port = null; this.build(!build); } return this; }; p.normalizePath = function(build) { if (this._parts.urn) { return this; } if (!this._parts.path || this._parts.path === '/') { return this; } var _was_relative; var _path = this._parts.path; var _parent, _pos; // handle relative paths if (_path.charAt(0) !== '/') { _was_relative = true; _path = '/' + _path; } // resolve simples _path = _path .replace(/(\/(\.\/)+)|(\/\.$)/g, '/') .replace(/\/{2,}/g, '/'); // resolve parents while (true) { _parent = _path.indexOf('/../'); if (_parent === -1) { // no more ../ to resolve break; } else if (_parent === 0) { // top level cannot be relative... _path = _path.substring(3); break; } _pos = _path.substring(0, _parent).lastIndexOf('/'); if (_pos === -1) { _pos = _parent; } _path = _path.substring(0, _pos) + _path.substring(_parent + 3); } // revert to relative if (_was_relative && this.is('relative')) { _path = _path.substring(1); } _path = URI.recodePath(_path); this._parts.path = _path; this.build(!build); return this; }; p.normalizePathname = p.normalizePath; p.normalizeQuery = function(build) { if (typeof this._parts.query === "string") { if (!this._parts.query.length) { this._parts.query = null; } else { this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace)); } this.build(!build); } return this; }; p.normalizeFragment = function(build) { if (!this._parts.fragment) { this._parts.fragment = null; this.build(!build); } return this; }; p.normalizeSearch = p.normalizeQuery; p.normalizeHash = p.normalizeFragment; p.iso8859 = function() { // expect unicode input, iso8859 output var e = URI.encode; var d = URI.decode; URI.encode = escape; URI.decode = decodeURIComponent; this.normalize(); URI.encode = e; URI.decode = d; return this; }; p.unicode = function() { // expect iso8859 input, unicode output var e = URI.encode; var d = URI.decode; URI.encode = strictEncodeURIComponent; URI.decode = unescape; this.normalize(); URI.encode = e; URI.decode = d; return this; }; p.readable = function() { var uri = this.clone(); // removing username, password, because they shouldn't be displayed according to RFC 3986 uri.username("").password("").normalize(); var t = ''; if (uri._parts.protocol) { t += uri._parts.protocol + '://'; } if (uri._parts.hostname) { if (uri.is('punycode') && punycode) { t += punycode.toUnicode(uri._parts.hostname); if (uri._parts.port) { t += ":" + uri._parts.port; } } else { t += uri.host(); } } if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') { t += '/'; } t += uri.path(true); if (uri._parts.query) { var q = ''; for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) { var kv = (qp[i] || "").split('='); q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace) .replace(/&/g, '%26'); if (kv[1] !== undefined) { q += "=" + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace) .replace(/&/g, '%26'); } } t += '?' + q.substring(1); } t += URI.decodeQuery(uri.hash(), true); return t; }; // resolving relative and absolute URLs p.absoluteTo = function(base) { var resolved = this.clone(); var properties = ['protocol', 'username', 'password', 'hostname', 'port']; var basedir, i, p; if (this._parts.urn) { throw new Error('URNs do not have any generally defined hierarchical components'); } if (!(base instanceof URI)) { base = new URI(base); } if (!resolved._parts.protocol) { resolved._parts.protocol = base._parts.protocol; } if (this._parts.hostname) { return resolved; } for (i = 0; p = properties[i]; i++) { resolved._parts[p] = base._parts[p]; } properties = ['query', 'path']; for (i = 0; p = properties[i]; i++) { if (!resolved._parts[p] && base._parts[p]) { resolved._parts[p] = base._parts[p]; } } if (resolved.path().charAt(0) !== '/') { basedir = base.directory(); resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path; resolved.normalizePath(); } resolved.build(); return resolved; }; p.relativeTo = function(base) { var relative = this.clone().normalize(); var relativeParts, baseParts, common, relativePath, basePath; if (relative._parts.urn) { throw new Error('URNs do not have any generally defined hierarchical components'); } base = new URI(base).normalize(); relativeParts = relative._parts; baseParts = base._parts; relativePath = relative.path(); basePath = base.path(); if (relativePath.charAt(0) !== '/') { throw new Error('URI is already relative'); } if (basePath.charAt(0) !== '/') { throw new Error('Cannot calculate a URI relative to another relative URI'); } if (relativeParts.protocol === baseParts.protocol) { relativeParts.protocol = null; } if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) { return relative.build(); } if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) { return relative.build(); } if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) { relativeParts.hostname = null; relativeParts.port = null; } else { return relative.build(); } if (relativePath === basePath) { relativeParts.path = ''; return relative.build(); } // determine common sub path common = URI.commonPath(relative.path(), base.path()); // If the paths have nothing in common, return a relative URL with the absolute path. if (!common) { return relative.build(); } var parents = baseParts.path .substring(common.length) .replace(/[^\/]*$/, '') .replace(/.*?\//g, '../'); relativeParts.path = parents + relativeParts.path.substring(common.length); return relative.build(); }; // comparing URIs p.equals = function(uri) { var one = this.clone(); var two = new URI(uri); var one_map = {}; var two_map = {}; var checked = {}; var one_query, two_query, key; one.normalize(); two.normalize(); // exact match if (one.toString() === two.toString()) { return true; } // extract query string one_query = one.query(); two_query = two.query(); one.query(""); two.query(""); // definitely not equal if not even non-query parts match if (one.toString() !== two.toString()) { return false; } // query parameters have the same length, even if they're permuted if (one_query.length !== two_query.length) { return false; } one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace); two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace); for (key in one_map) { if (hasOwn.call(one_map, key)) { if (!isArray(one_map[key])) { if (one_map[key] !== two_map[key]) { return false; } } else if (!arraysEqual(one_map[key], two_map[key])) { return false; } checked[key] = true; } } for (key in two_map) { if (hasOwn.call(two_map, key)) { if (!checked[key]) { // two contains a parameter not present in one return false; } } } return true; }; // state p.duplicateQueryParameters = function(v) { this._parts.duplicateQueryParameters = !!v; return this; }; p.escapeQuerySpace = function(v) { this._parts.escapeQuerySpace = !!v; return this; }; return URI; })); /*! * 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 ECMA Script 6 Polyfill (function(jQuery) { "use strict"; /* * No Documentation by intention. * This class represents a polyfill for ECMA Script 6 Promises * see http://www.html5rocks.com/en/tutorials/es6/promises/ */ var Promise = function(fAction) { if (typeof (fAction) != "function") { throw new TypeError("Argument is not a function"); } this._deferred = new jQuery.Deferred(); try { var that = this; fAction(function(oVal){ _finalize(that, oVal, true); //force async resolve }, function(oVal){ _finalize(that, oVal, false); //force async reject }); } catch (e) { //Error in action rejects the promise _finalize(this, e, false); } }; // *** Instance Promise functions *** Promise.prototype.then = function(fOnFulfilled, fOnRejected){ var oFollowUpPromise = new Promise(_dummy); this._deferred.then(_doWrap(fOnFulfilled, oFollowUpPromise, true), _doWrap(fOnRejected, oFollowUpPromise, false)); return oFollowUpPromise; }; Promise.prototype["catch"] = function(fOnRejected){ return this.then(undefined, fOnRejected); }; // *** Static Promise functions *** Promise.all = function(aPromises){ return new Promise(function(fResolve, fReject){ if (!jQuery.isArray(aPromises)) { fReject(new TypeError("invalid argument")); return; } if (aPromises.length == 0) { fResolve([]); return; } var bFailed = false, aValues = new Array(aPromises.length), iCount = 0; function _check(iIdx){ Promise.resolve(aPromises[iIdx]).then(function(oObj){ if (!bFailed) { iCount++; aValues[iIdx] = oObj; if (iCount == aPromises.length) { fResolve(aValues); } } }, function(oObj){ if (!bFailed) { bFailed = true; fReject(oObj); } }); } for (var i = 0; i < aPromises.length; i++) { _check(i); } }); }; Promise.race = function(aPromises){ return new Promise(function(fResolve, fReject){ if (!jQuery.isArray(aPromises)) { fReject(new TypeError("invalid argument")); } var bFinal = false; for (var i = 0; i < aPromises.length; i++) { /*eslint-disable no-loop-func */ Promise.resolve(aPromises[i]).then(function(oObj){ if (!bFinal) { bFinal = true; fResolve(oObj); } }, function(oObj){ if (!bFinal) { bFinal = true; fReject(oObj); } }); /*eslint-enable no-loop-func */ } }); }; Promise.resolve = function(oObj){ return oObj instanceof Promise ? oObj : _resolve(new Promise(_dummy), oObj); }; Promise.reject = function(oObj){ return _finalize(new Promise(_dummy), oObj, false); }; // *** Helper functions *** function _dummy(){} function _isThenable(oObj){ return oObj && oObj.then && typeof (oObj.then) == "function"; } function _finalize(oPromise, oObj, bResolve){ setTimeout(function(){ if (_isThenable(oObj) && bResolve) { //Assimilation _resolve(oPromise, oObj); } else { oPromise._deferred[bResolve ? "resolve" : "reject"](oObj); } }, 0); return oPromise; } function _resolve(oPromise, oObj){ if (_isThenable(oObj)) { var bFinal = false; try { oObj.then(function(oVal){ _finalize(oPromise, oVal, true); bFinal = true; }, function(oVal){ _finalize(oPromise, oVal, false); bFinal = true; }); } catch (e) { if (!bFinal) { _finalize(oPromise, e, false); } else { jQuery.sap.log.debug("Promise: Error in then: " + e); //Error is ignored } } } else { _finalize(oPromise, oObj, true); } return oPromise; } function _doWrap(fAction, oPromise, bResolve){ return function(oObj){ if (!fAction) { _finalize(oPromise, oObj, bResolve); } else { try { _resolve(oPromise, fAction(oObj)); } catch (e) { //catch error in fAction _finalize(oPromise, e, false); } } }; } // *** Polyfill *** if (!window.Promise) { window.Promise = Promise; } if (window.sap && window.sap.__ui5PublishPromisePolyfill) { //For testing purposes window._UI5Promise = Promise; } })(jQuery); /*! * 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. */ /*global URI, Promise, alert, console, XMLHttpRequest */ /** * @class Provides base functionality of the SAP jQuery plugin as extension of the jQuery framework.
* See also jQuery for details.
* Although these functions appear as static ones, they are meant to be used on jQuery instances.
* If not stated differently, the functions follow the fluent interface paradigm and return the jQuery instance for chaining of statements. * * Example for usage of an instance method: *
 *   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: * * * To keep the code size small, this implementation mainly validates the single string variant. * All other variants are only validated to some degree. It is the responsibility of the caller to * provide proper parts. * * @param {int|string|any[]|jQuery.sap.Version} vMajor the major part of the version (int) or any of the single parameter variants explained above. * @param {int} iMinor the minor part of the version number * @param {int} iPatch the patch part of the version number * @param {string} sSuffix the suffix part of the version number * @return {jQuery.sap.Version} the version object as determined from the parameters * * @class Represents a version consisting of major, minor, patch version and suffix, e.g. '1.2.7-SNAPSHOT'. * * @author SAP SE * @version 1.28.5 * @constructor * @public * @since 1.15.0 * @name jQuery.sap.Version */ function Version(vMajor, iMinor, iPatch, sSuffix) { if ( vMajor instanceof Version ) { // note: even a constructor may return a value different from 'this' return vMajor; } if ( !(this instanceof Version) ) { // act as a cast operator when called as function (not as a constructor) return new Version(vMajor, iMinor, iPatch, sSuffix); } var m; if (typeof vMajor === "string") { m = rVersion.exec(vMajor); } else if (jQuery.isArray(vMajor)) { m = vMajor; } else { m = arguments; } m = m || []; function norm(v) { v = parseInt(v,10); return isNaN(v) ? 0 : v; } vMajor = norm(m[0]); iMinor = norm(m[1]); iPatch = norm(m[2]); sSuffix = String(m[3] || ""); /** * Returns a string representation of this version. * * @return {string} a string representation of this version. * @name jQuery.sap.Version#toString * @public * @since 1.15.0 * @function */ this.toString = function() { return vMajor + "." + iMinor + "." + iPatch + sSuffix; }; /** * Returns the major version part of this version. * * @return {int} the major version part of this version * @name jQuery.sap.Version#getMajor * @public * @since 1.15.0 * @function */ this.getMajor = function() { return vMajor; }; /** * Returns the minor version part of this version. * * @return {int} the minor version part of this version * @name jQuery.sap.Version#getMinor * @public * @since 1.15.0 * @function */ this.getMinor = function() { return iMinor; }; /** * Returns the patch (or micro) version part of this version. * * @return {int} the patch version part of this version * @name jQuery.sap.Version#getPatch * @public * @since 1.15.0 * @function */ this.getPatch = function() { return iPatch; }; /** * Returns the version suffix of this version. * * @return {string} the version suffix of this version * @name jQuery.sap.Version#getSuffix * @public * @since 1.15.0 * @function */ this.getSuffix = function() { return sSuffix; }; /** * Compares this version with a given one. * * The version with which this version should be compared can be given as * 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 = ""); 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) *
    *
  1. global configuration object window["sap-ui-config"] (could be either a string/url or a configuration object)
  2. *
  3. data-sap-ui-config attribute of the bootstrap script tag
  4. *
  5. other data-sap-ui-xyz attributes of the bootstrap tag
  6. *
*/ var oCfgData = _window["sap-ui-config"] = (function() { function normalize(o) { jQuery.each(o, function(i, v) { var il = i.toLowerCase(); if ( !o.hasOwnProperty(il) ) { o[il] = v; delete o[i]; } }); return o; } var oScriptTag = _oBootstrap.tag, oCfg = _window["sap-ui-config"], sCfgFile = "sap-ui-config.json"; // load the configuration from an external JSON file if (typeof oCfg === "string") { _earlyLog("warning", "Loading external bootstrap configuration from \"" + oCfg + "\". This is a design time feature and not for productive usage!"); if (oCfg !== sCfgFile) { _earlyLog("warning", "The external bootstrap configuration file should be named \"" + sCfgFile + "\"!"); } jQuery.ajax({ url : oCfg, dataType : 'json', async : false, success : function(oData, sTextStatus, jqXHR) { oCfg = oData; }, error : function(jqXHR, sTextStatus, oError) { _earlyLog("error", "Loading externalized bootstrap configuration from \"" + oCfg + "\" failed! Reason: " + oError + "!"); oCfg = undefined; } }); } oCfg = normalize(oCfg || {}); oCfg.resourceroots = oCfg.resourceroots || {}; oCfg.themeroots = oCfg.themeroots || {}; oCfg.resourceroots[''] = oCfg.resourceroots[''] || _oBootstrap.resourceRoot; oCfg['xx-loadallmode'] = /(^|\/)(sap-?ui5|[^\/]+-all).js([?#]|$)/.test(_oBootstrap.url); // if a script tag has been identified, collect its configuration info if ( oScriptTag ) { // evaluate the config attribute first - if present var sConfig = oScriptTag.getAttribute("data-sap-ui-config"); if ( sConfig ) { try { /*eslint-disable no-new-func */ jQuery.extend(oCfg, normalize((new Function("return {" + sConfig + "};"))())); // TODO jQuery.parseJSON would be better but imposes unwanted restrictions on valid syntax /*eslint-enable no-new-func */ } catch (e) { // no log yet, how to report this error? _earlyLog("error", "failed to parse data-sap-ui-config attribute: " + (e.message || e)); } } // merge with any existing "data-sap-ui-" attributes jQuery.each(oScriptTag.attributes, function(i, attr) { var m = attr.name.match(/^data-sap-ui-(.*)$/); if ( m ) { // the following (deactivated) conversion would implement multi-word names like "resource-roots" m = m[1].toLowerCase(); // .replace(/\-([a-z])/g, function(s,w) { return w.toUpperCase(); }) if ( m === 'resourceroots' ) { // merge map entries instead of overwriting map jQuery.extend(oCfg[m], jQuery.parseJSON(attr.value)); } else if ( m === 'theme-roots' ) { // merge map entries, but rename to camelCase jQuery.extend(oCfg.themeroots, jQuery.parseJSON(attr.value)); } else if ( m !== 'config' ) { oCfg[m] = attr.value; } } }); } return oCfg; }()); // check whether noConflict must be used... if ( oCfgData.noconflict === true || oCfgData.noconflict === "true" || oCfgData.noconflict === "x" ) { jQuery.noConflict(); } /** * Root Namespace for the jQuery plug-in provided by SAP SE. * * @version 1.28.5 * @namespace * @public * @static */ jQuery.sap = {}; // -------------------------- VERSION ------------------------------------- jQuery.sap.Version = Version; // -------------------------- DEBUG LOCAL STORAGE ------------------------------------- jQuery.sap.debug = function(bEnable) { if (!window.localStorage) { return null; } function reloadHint(bUsesDbgSrc){ /*eslint-disable no-alert */ alert("Usage of debug sources is " + (bUsesDbgSrc ? "on" : "off") + " now.\nFor the change to take effect, you need to reload the page."); /*eslint-enable no-alert */ } if (bEnable === true) { window.localStorage.setItem("sap-ui-debug", "X"); reloadHint(true); } else if (bEnable === false) { window.localStorage.removeItem("sap-ui-debug"); reloadHint(false); } return window.localStorage.getItem("sap-ui-debug") == "X"; }; // -------------------------- STATISTICS LOCAL STORAGE ------------------------------------- jQuery.sap.statistics = function(bEnable) { if (!window.localStorage) { return null; } function reloadHint(bUsesDbgSrc){ /*eslint-disable no-alert */ alert("Usage of Gateway statistics " + (bUsesDbgSrc ? "on" : "off") + " now.\nFor the change to take effect, you need to reload the page."); /*eslint-enable no-alert */ } if (bEnable === true) { window.localStorage.setItem("sap-ui-statistics", "X"); reloadHint(true); } else if (bEnable === false) { window.localStorage.removeItem("sap-ui-statistics"); reloadHint(false); } return window.localStorage.getItem("sap-ui-statistics") == "X"; }; // -------------------------- Logging ------------------------------------- (function() { var FATAL = 0, ERROR = 1, WARNING = 2, INFO = 3, DEBUG = 4, TRACE = 5, /** * Unique prefix for this instance of the core in a multi-frame environment. */ sWindowName = (window.top == window) ? "" : "[" + window.location.pathname.split('/').slice(-1)[0] + "] ", // Note: comparison must use type coercion (==, not ===), otherwise test fails in IE /** * The array that holds the log entries that have been recorded so far */ aLog = [], /** * Maximum log level to be recorded (per component). */ mMaxLevel = { '' : ERROR }, /** * Registered listener to be informed about new log entries. */ oListener = null; function pad0(i,w) { return ("000" + String(i)).slice(-w); } function level(sComponent) { return (!sComponent || isNaN(mMaxLevel[sComponent])) ? mMaxLevel[''] : mMaxLevel[sComponent]; } function listener(){ if (!oListener) { oListener = { listeners: [], onLogEntry: function(oLogEntry){ for (var i = 0; i < oListener.listeners.length; i++) { if (oListener.listeners[i].onLogEntry) { oListener.listeners[i].onLogEntry(oLogEntry); } } }, attach: function(oLogger, oLstnr){ if (oLstnr) { oListener.listeners.push(oLstnr); if (oLstnr.onAttachToLog) { oLstnr.onAttachToLog(oLogger); } } }, detach: function(oLogger, oLstnr){ for (var i = 0; i < oListener.listeners.length; i++) { if (oListener.listeners[i] === oLstnr) { if (oLstnr.onDetachFromLog) { oLstnr.onDetachFromLog(oLogger); } oListener.listeners.splice(i,1); return; } } } }; } return oListener; } /** * Creates a new log entry depending on its level and component. * * If the given level is higher than the max level for the given component * (or higher than the global level, if no component is given), * then no entry is created. */ function log(iLevel, sMessage, sDetails, sComponent) { if (iLevel <= level(sComponent) ) { 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 : String(sMessage || ""), details : String(sDetails || ""), component: String(sComponent || "") }; aLog.push( oLogEntry ); if (oListener) { oListener.onLogEntry(oLogEntry); } /* * Console Log, also tries to log to the window.console, if available. * * Unfortunately, the support for window.console is quite different between the UI5 browsers. The most important differences are: * - in IE (checked until IE9), the console object does not exist in a window, until the developer tools are opened for that window. * After opening the dev tools, the console remains available even when the tools are closed again. Only using a new window (or tab) * restores the old state without console. * When the console is available, it provides most standard methods, but not debug and trace * - in FF3.6 the console is not available, until FireBug is opened. It disappears again, when fire bug is closed. * But when the settings for a web site are stored (convenience), the console remains open * When the console is available, it supports all relevant methods * - in FF9.0, the console is always available, but method assert is only available when firebug is open * - in Webkit browsers, the console object is always available and has all required methods * - Exception: in the iOS Simulator, console.info() does not exist */ /*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 + " " + sWindowName + oLogEntry.message + " - " + oLogEntry.details + " " + 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; } } /** * Creates a new Logger instance which will use the given component string * for all logged messages without a specific component. * * @param {string} sDefaultComponent * * @class A Logger class * @name jQuery.sap.log.Logger * @since 1.1.2 * @public */ function Logger(sDefaultComponent) { /** * Creates a new fatal-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @return {jQuery.sap.log.Logger} The log instance for method chaining * @name jQuery.sap.log.Logger#fatal * @function * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ this.fatal = function (sMessage, sDetails, sComponent) { log(FATAL, sMessage, sDetails, sComponent || sDefaultComponent); return this; }; /** * Creates a new error-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @return {jQuery.sap.log.Logger} The log instance * @name jQuery.sap.log.Logger#error * @function * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ this.error = function error(sMessage, sDetails, sComponent) { log(ERROR, sMessage, sDetails, sComponent || sDefaultComponent); return this; }; /** * Creates a new warning-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @return {jQuery.sap.log.Logger} The log instance * @name jQuery.sap.log.Logger#warning * @function * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ this.warning = function warning(sMessage, sDetails, sComponent) { log(WARNING, sMessage, sDetails, sComponent || sDefaultComponent); return this; }; /** * Creates a new info-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @return {jQuery.sap.log.Logger} The log instance * @name jQuery.sap.log.Logger#info * @function * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ this.info = function info(sMessage, sDetails, sComponent) { log(INFO, sMessage, sDetails, sComponent || sDefaultComponent); return this; }; /** * Creates a new debug-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @return {jQuery.sap.log.Logger} The log instance * @name jQuery.sap.log.Logger#debug * @function * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ this.debug = function debug(sMessage, sDetails, sComponent) { log(DEBUG, sMessage, sDetails, sComponent || sDefaultComponent); return this; }; /** * Creates a new trace-level entry in the log with the given message, details and calling component. * * @param {string} sMessage Message text to display * @param {string} [sDetails=''] Details about the message, might be omitted * @param {string} [sComponent=''] Name of the component that produced the log entry * @return {jQuery.sap.log.Logger} The log-instance * @name jQuery.sap.log.Logger#trace * @function * @public * @SecSink {0 1 2|SECRET} Could expose secret data in logs */ this.trace = function trace(sMessage, sDetails, sComponent) { log(TRACE, sMessage, sDetails, sComponent || sDefaultComponent); return this; }; /** * Defines the maximum jQuery.sap.log.Level of log entries that will be recorded. * Log entries with a higher (less important) log level will be omitted from the log. * When a component name is given, the log level will be configured for that component * only, otherwise the log level for the default component of this logger is set. * For the global logger, the global default level is set. * * Note: Setting a global default log level has no impact on already defined * component log levels. They always override the global default log level. * * @param {jQuery.sap.log.Level} iLogLevel * @param {string} [sComponent] The log component to set the log level for. * @return {jQuery.sap.log} The global logger to allow method chaining * @name jQuery.sap.log.Logger#setLevel * @function * @public */ this.setLevel = function setLevel(iLogLevel, sComponent) { sComponent = sComponent || sDefaultComponent || ''; mMaxLevel[sComponent] = iLogLevel; var mBackMapping = []; jQuery.each(jQuery.sap.log.LogLevel, function(idx, v){ mBackMapping[v] = idx; }); log(INFO, "Changing log level " + (sComponent ? "for '" + sComponent + "' " : "") + "to " + mBackMapping[iLogLevel], "", "jQuery.sap.log"); return this; }; /** * Returns the log level currently effective for the given component. * If no component is given or when no level has been configured for a * given component, the log level for the default component of this logger is returned. * * @param {string} [sComponent] Name of the component to retrieve the log level for * @return {int} The log level for the given component or the default log level * @name jQuery.sap.log.Logger#getLevel * @function * @public * @since 1.1.2 */ this.getLevel = function getLevel(sComponent) { return level(sComponent || sDefaultComponent); }; /** * Checks whether logging is enabled for the given log level, * depending on the currently effective log level for the given component. * * If no component is given, the default component of this logger will be taken into account. * * @param {int} [iLevel=Level.DEBUG] the log level in question * @param {string} [sComponent] Name of the component to check the log level for * @return {boolean} Whether logging is enabled or not * @name jQuery.sap.log.Logger#isLoggable * @function * @public * @since 1.13.2 */ this.isLoggable = function (iLevel, sComponent) { return (iLevel == null ? DEBUG : iLevel) <= level(sComponent || sDefaultComponent); }; } /** * A Logging API for JavaScript. * * Provides methods to manage a client-side log and to create entries in it. Each of the logging methods * {@link jQuery.sap.log.#debug}, {@link jQuery.sap.log.#info}, {@link jQuery.sap.log.#warning}, * {@link jQuery.sap.log.#error} and {@link jQuery.sap.log.#fatal} creates and records a log entry, * containing a timestamp, a log level, a message with details and a component info. * The log level will be one of {@link jQuery.sap.log.Level} and equals the name of the concrete logging method. * * By using the {@link jQuery.sap.log#setLevel} method, consumers can determine the least important * log level which should be recorded. Less important entries will be filtered out. (Note that higher numeric * values represent less important levels). The initially set level depends on the mode that UI5 is running in. * When the optimized sources are executed, the default level will be {@link jQuery.sap.log.Level.ERROR}. * For normal (debug sources), the default level is {@link jQuery.sap.log.Level.DEBUG}. * * All logging methods allow to specify a component. These components are simple strings and * don't have a special meaning to the UI5 framework. However they can be used to semantically group * log entries that belong to the same software component (or feature). There are two APIs that help * to manage logging for such a component. With {@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 * * * @return {object[]} an array containing the recorded log entries * @public * @static * @since 1.1.2 */ getLogEntries : function () { return aLog.slice(); }, /** * Allows to add a new LogListener that will be notified for new log entries. * The given object must provide method 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): * * * Example: *
	 *   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 * * @private */ mModules = { // predefine already loaded modules to avoid redundant loading // "sap/ui/thirdparty/jquery/jquery-1.7.1.js" : { state : READY, url : _sBootstrapUrl, content : jQuery }, "sap/ui/thirdparty/URI.js" : { state : READY, url : _sBootstrapUrl, content : URI }, "sap/ui/Device.js" : { state : READY, url : _sBootstrapUrl, content : sap.ui.Device }, "jquery.sap.global.js" : { state : READY, url : _sBootstrapUrl, content : jQuery } }, mPreloadModules = {}, /** * Information about third party modules that react on AMD loaders and need a workaround * to be able to work with jQuery.sap.require no matter whether an AMD loader is present or not. * * Note: this is a map for future extension * Note: should be maintained together with raw-module info in .library files * @private */ mAMDShim = { 'sap/ui/thirdparty/blanket.js': true, 'sap/ui/thirdparty/crossroads.js': true, 'sap/ui/thirdparty/d3.js': true, 'sap/ui/thirdparty/datajs.js': true, 'sap/ui/thirdparty/handlebars.js': true, 'sap/ui/thirdparty/hasher.js': true, 'sap/ui/thirdparty/IPv6.js': true, 'sap/ui/thirdparty/jquery/jquery-1.11.1.js': true, 'sap/ui/thirdparty/jquery/jquery-1.10.2.js': true, 'sap/ui/thirdparty/jquery/jquery-1.10.1.js': true, 'sap/ui/thirdparty/jquery/jquery.1.7.1.js': true, 'sap/ui/thirdparty/jquery/jquery.1.8.1.js': true, 'sap/ui/thirdparty/jquery-mobile-custom.js': true, 'sap/ui/thirdparty/jszip.js': true, 'sap/ui/thirdparty/less.js': true, 'sap/ui/thirdparty/punycode.js': true, 'sap/ui/thirdparty/require.js': true, 'sap/ui/thirdparty/SecondLevelDomains.js': true, 'sap/ui/thirdparty/signals.js': true, 'sap/ui/thirdparty/URI.js' : true, 'sap/ui/thirdparty/URITemplate.js' : true, 'sap/ui/demokit/js/esprima.js' : true }, /** * Stack of modules that are currently executed. * * Allows to identify the containing module in case of multi module files (e.g. sap-ui-core) * @private */ _execStack = [ ], /** * A prefix that will be added to module loading log statements and which reflects the nesting of module executions. * @private */ sLogPrefix = "", // max size a script should have when executing it with execScript (IE). Otherwise fallback to eval MAX_EXEC_SCRIPT_LENGTH = 512 * 1024, sDocumentLocation = document.location.href.replace(/\?.*|#.*/g, ""), FRAGMENT = "fragment", VIEW = "view", mKnownSubtypes = { js : [VIEW, FRAGMENT, "controller", "designtime"], xml: [VIEW, FRAGMENT], json: [VIEW, FRAGMENT], html: [VIEW, FRAGMENT] }, rJSSubtypes = new RegExp("(\\.(?:" + mKnownSubtypes.js.join("|") + "))?\\.js$"), rTypes, rSubTypes; (function() { var s = "", sSub = ""; jQuery.each(mKnownSubtypes, function(sType, aSubtypes) { s = (s ? s + "|" : "") + sType; sSub = (sSub ? sSub + "|" : "") + "(?:(?:" + aSubtypes.join("\\.|") + "\\.)?" + sType + ")"; }); s = "\\.(" + s + ")$"; sSub = "\\.(?:" + sSub + "|[^./]+)$"; log.debug("constructed regexp for file types :" + s); log.debug("constructed regexp for file sub-types :" + sSub); rTypes = new RegExp(s); rSubTypes = new RegExp(sSub); }()); /** * Name conversion function that converts a name in UI5 module name syntax to a name in requireJS module name syntax. * @private */ function ui5ToRJS(sName) { 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; } return sName.replace(/\./g, "/"); } /** * Name conversion function that converts a name in unified resource name syntax to a name in UI5 module name syntax. * If the name cannot be converted (e.g. doesn't end with '.js'), then 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: * * * UI5 APIs that only deal with Javascript resources, use a slight variation of this scheme, * where the extension '.js' is always omitted (see {@link sap.ui.define}, {@link sap.ui.require}). * * * Relationship to old Module Name Syntax * * Older UI5 APIs that deal with resources (like {@link jQuery.sap.registerModulePath}, * {@link jQuery.sap.require} and {@link jQuery.sap.declare}) used a dot-separated naming scheme * (called 'module names') which was motivated by object names in the global namespace in * Javascript. * * The new URN scheme better matches the names of the corresponding resources (files) as stored * in a server and the dot ('.') is no longer a forbidden character in a resource name. This finally * allows to handle resources with different types (extensions) with the same API, not only JS files. * * Last but not least does the URN scheme better match the naming conventions used by AMD loaders * (like 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:
* The following example defines a module "SomeClass", but doesn't hard code the module name. * If stored in a file 'sap/mylib/SomeClass.js', it can be requested as 'sap/mylib/SomeClass'. *
		 *   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 Modules
* If a dependencies array is given, each entry represents the name of another module that * the currently defined module depends on. All dependency modules are loaded before the value * of the currently defined module is determined. The module value of each dependency module * will be provided as a parameter to a factory function, the order of the parameters will match * the order of the modules in the dependencies array. * * Note: the order in which the dependency modules are executed is not * defined by the order in the dependencies array! The execution order is affected by dependencies * between the dependency modules as well as by their current state (whether a module * already has been loaded or not). Neither module implementations nor dependants that require * a module set must make any assumption about the execution order (other than expressed by * their dependencies). There is, however, one exception with regard to third party libraries, * see the list of limitations further down below. * * Note:a static module value (a literal provided to sap.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 Contract
* sap.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
* To be in line with AMD best practices, modules defined with 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 * *
    *
  1. before the factory function is called, the existence of the global parent namespace for * the current module is ensured
  2. *
  3. the module value will be automatically exported under a global name which is derived from * the name of the module
  4. *
* * The parameter lets the framework know whether any of those two operations is needed or not. * In future versions of UI5, a central configuration option is planned to suppress those 'exports'. * * * Third Party Modules
* Although third party modules don't use UI5 APIs, they still can be listed as dependencies in * a 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
* The current implementation of sap.ui.define differs from requireJS * or other AMD loaders in several aspects: * * * * Limitations, Design Considerations
* * @param {string} [sModuleName] name of the module in simplified resource name syntax. * When omitted, the loader determines the name from the request. * @param {string[]} [aDependencies] list of dependencies of the module * @param {function|any} vFactory the module value or a function that calculates the value * @param {boolean} [bExport] whether an export to global names is required - should be used by SAP-owned code only * @since 1.27.0 * @public * @experimental Since 1.27.0 - not all aspects of sap.ui.define are settled yet. If the documented * constraints and limitations are obeyed, SAP-owned code might use it. If the fourth parameter * is not used and if the asynchronous contract is respected, even Non-SAP code might use it. */ sap.ui.define = function(sModuleName, aDependencies, vFactory, bExport) { var sResourceName, i; // optional id if ( typeof sModuleName === 'string' ) { sResourceName = sModuleName + '.js'; } else { // shift parameters bExport = vFactory; vFactory = aDependencies; aDependencies = sModuleName; sResourceName = _execStack[_execStack.length - 1]; } // convert module name to UI5 module name syntax (might fail!) sModuleName = urnToUI5(sResourceName); // optional array of dependencies if ( !jQuery.isArray(aDependencies) ) { // shift parameters bExport = vFactory; vFactory = aDependencies; aDependencies = []; } else { // resolve relative module names var sPackage = sResourceName.slice(0,1 + sResourceName.lastIndexOf('/')); for (i = 0; i < aDependencies.length; i++) { if ( /^\.\//.test(aDependencies[i]) ) { aDependencies[i] = sPackage + aDependencies[i].slice(2); // 2 == length of './' prefix } } } if ( log.isLoggable() ) { log.debug("define(" + sResourceName + ", " + "['" + aDependencies.join("','") + "']" + ")"); } var oModule = declareModule(sResourceName); // note: dependencies will be converted from RJS to URN inside requireAll requireAll(aDependencies, function(aModules) { // factory if ( log.isLoggable() ) { log.debug("define(" + sResourceName + "): calling factory " + typeof vFactory); } if ( bExport ) { // ensure parent namespace jQuery.sap.getObject(sModuleName, 1); } if ( typeof vFactory === 'function' ) { oModule.content = vFactory.apply(window, aModules); } else { oModule.content = vFactory; } // HACK: global export if ( bExport ) { if ( oModule.content == null ) { log.error("module '" + sResourceName + "' returned no content, but should be exported"); } else { if ( log.isLoggable() ) { log.debug("exporting content of '" + sResourceName + "': as global object"); } jQuery.sap.setObject(sModuleName, oModule.content); } } }); }; /** * Resolves one or more module dependencies. * * Synchronous Retrieval of a Single Module Value * * When called with a single string, that string is assumed to be the name of an already loaded * module and the value of that module is returned. If the module has not been loaded yet, * or if it is a Non-UI5 module (e.g. third party module), undefined 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. *
* In case of IE8 only the load callback will work ignoring in case of success and error. * * @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 script has been loaded * @param {function} * [fnErrorCallback] callback function to get notified once the script loading failed (not supported by IE8) * * @public * @static * @SecSink {0|PATH} Parameter is used for future HTTP requests */ jQuery.sap.includeScript = function includeScript(sUrl, sId, fnLoadCallback, fnErrorCallback){ var oScript = window.document.createElement("script"); oScript.src = sUrl; oScript.type = "text/javascript"; if (sId) { oScript.id = sId; } if (!!sap.ui.Device.browser.internet_explorer && sap.ui.Device.browser.version < 9) { // in case if IE8 the error callback is not supported! // we can only check the loading via the readystatechange event if (fnLoadCallback) { oScript.onreadystatechange = function() { if (oScript.readyState === "loaded" || oScript.readyState === "complete") { fnLoadCallback(); oScript.onreadystatechange = null; } }; } } else { if (fnLoadCallback) { jQuery(oScript).load(fnLoadCallback); } if (fnErrorCallback) { jQuery(oScript).error(fnErrorCallback); } } // jQuery("head").append(oScript) doesn't work because they filter for the script // and execute them directly instead adding the SCRIPT tag to the head var oOld; if ((sId && (oOld = jQuery.sap.domById(sId)) && oOld.tagName === "SCRIPT")) { jQuery(oOld).remove(); // replacing scripts will not trigger the load event } appendHead(oScript); }; var oIEStyleSheetNode; var mIEStyleSheets = jQuery.sap._mIEStyleSheets = {}; /** * Includes the specified stylesheet via a <link>-tag in the head of the current document. If there is call to * 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 //
value = parseInt( elem.css( "zIndex" ), 10 ); if ( !isNaN( value ) && value !== 0 ) { return value; } } elem = elem.parent(); } } return 0; }; } /** * Gets the next parent DOM element with a given attribute and attribute value starting above the first given element * * @param {string} sAttribute Name of the attribute * @param {string} sValue Value of the attribute (optional) * @return {Element} null or the DOM reference * @public * @name jQuery#parentByAttribute * @author SAP SE * @since 0.9.0 * @function */ jQuery.fn.parentByAttribute = function parentByAttribute(sAttribute, sValue) { if (this.length > 0) { if (sValue) { return this.first().parents("[" + sAttribute + "='" + sValue + "']").get(0); } else { return this.first().parents("[" + sAttribute + "]").get(0); } } }; /** * Returns the window reference for a DomRef * * @param {Element} oDomRef The DOM reference * @return {Window} Window reference * @public * @since 0.9.0 */ jQuery.sap.ownerWindow = function ownerWindow(oDomRef){ if (oDomRef.ownerDocument.parentWindow) { return oDomRef.ownerDocument.parentWindow; } return oDomRef.ownerDocument.defaultView; }; var _oScrollbarSize = {}; /** * Returns the size (width of the vertical / height of the horizontal) native browser scrollbars. * * This function must only be used when the DOM is ready. * * @param {string} [sClasses=null] the CSS class that should be added to the test element. * @param {boolean} [bForce=false] force recalculation of size (e.g. when CSS was changed). When no classes are passed all calculated sizes are reset. * @return {object} JSON object with properties 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 * /wiki/display/NWCUIAMSIM/XSS+Secure+Programming+Guide */ /** * Create hex and pad to length * @private */ function hex(iChar, iLength) { var sHex = iChar.toString(16); if (iLength) { while (iLength > sHex.length) { sHex = "0" + sHex; } } return sHex; } /** * RegExp and escape function for HTML escaping */ var rHtml = /[\x00-\x2b\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\xff\u2028\u2029]/g, rHtmlReplace = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/, mHtmlLookup = { "<": "<", ">": ">", "&": "&", "\"": """ }; var fHtml = function(sChar) { var sEncoded = mHtmlLookup[sChar]; if (!sEncoded) { if (rHtmlReplace.test(sChar)) { sEncoded = "�"; } else { sEncoded = "&#x" + hex(sChar.charCodeAt(0)) + ";"; } mHtmlLookup[sChar] = sEncoded; } return sEncoded; }; /** * Encode the string for inclusion into HTML content/attribute * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @SecValidate {0|return|XSS} validates the given string for HTML contexts */ jQuery.sap.encodeHTML = function(sString) { return sString.replace(rHtml, fHtml); }; /** * Encode the string for inclusion into XML content/attribute * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @SecValidate {0|return|XSS} validates the given string for XML contexts */ jQuery.sap.encodeXML = function(sString) { return sString.replace(rHtml, fHtml); }; /** * Encode the string for inclusion into HTML content/attribute. * Old name "escapeHTML" kept for backward compatibility * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @deprecated Has been renamed, use {@link jQuery.sap.encodeHTML} instead. */ jQuery.sap.escapeHTML = function(sString) { return sString.replace(rHtml, fHtml); }; /** * RegExp and escape function for JS escaping */ var rJS = /[\x00-\x2b\x2d\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\xff\u2028\u2029]/g, mJSLookup = {}; var fJS = function(sChar) { var sEncoded = mJSLookup[sChar]; if (!sEncoded) { var iChar = sChar.charCodeAt(0); if (iChar < 256) { sEncoded = "\\x" + hex(iChar, 2); } else { sEncoded = "\\u" + hex(iChar, 4); } mJSLookup[sChar] = sEncoded; } return sEncoded; }; /** * Encode the string for inclusion into a JS string literal * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @SecValidate {0|return|XSS} validates the given string for a JavaScript contexts */ jQuery.sap.encodeJS = function(sString) { return sString.replace(rJS, fJS); }; /** * Encode the string for inclusion into a JS string literal. * Old name "escapeJS" kept for backward compatibility * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @deprecated Since 1.3.0. Has been renamed, use {@link jQuery.sap.encodeJS} instead. */ jQuery.sap.escapeJS = function(sString) { return sString.replace(rJS, fJS); }; /** * RegExp and escape function for URL escaping */ var rURL = /[\x00-\x2c\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\uffff]/g, mURLLookup = {}; var fURL = function(sChar) { var sEncoded = mURLLookup[sChar]; if (!sEncoded) { var iChar = sChar.charCodeAt(0); if (iChar < 128) { sEncoded = "%" + hex(iChar, 2); } else if (iChar < 2048) { sEncoded = "%" + hex((iChar >> 6) | 192, 2) + "%" + hex((iChar & 63) | 128, 2); } else { sEncoded = "%" + hex((iChar >> 12) | 224, 2) + "%" + hex(((iChar >> 6) & 63) | 128, 2) + "%" + hex((iChar & 63) | 128, 2); } mURLLookup[sChar] = sEncoded; } return sEncoded; }; /** * Encode the string for inclusion into an URL parameter * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @SecValidate {0|return|XSS} validates the given string for a URL context */ jQuery.sap.encodeURL = function(sString) { return sString.replace(rURL, fURL); }; /** * Encode a map of parameters into a combined URL parameter string * * @param {object} mParams The map of parameters to encode * @return The URL encoded parameters * @type {string} * @public * @SecValidate {0|return|XSS} validates the given string for a CSS context */ jQuery.sap.encodeURLParameters = function(mParams) { if (!mParams) { return ""; } var aUrlParams = []; jQuery.each(mParams, function (sName, oValue) { if (jQuery.type(oValue) === "string") { oValue = jQuery.sap.encodeURL(oValue); } aUrlParams.push(jQuery.sap.encodeURL(sName) + "=" + oValue); }); return aUrlParams.join("&"); }; /** * RegExp and escape function for CSS escaping */ var rCSS = /[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff\u2028\u2029][0-9A-Fa-f]?/g; var fCSS = function(sChar) { var iChar = sChar.charCodeAt(0); if (sChar.length == 1) { return "\\" + hex(iChar); } else { return "\\" + hex(iChar) + " " + sChar.substr(1); } }; /** * Encode the string for inclusion into CSS string literals or identifiers * * @param {string} sString The string to be escaped * @return The escaped string * @type {string} * @public * @SecValidate {0|return|XSS} validates the given string for a CSS context */ jQuery.sap.encodeCSS = function(sString) { return sString.replace(rCSS, fCSS); }; /** * WhitelistEntry object * @param {string} protocol The protocol of the URL * @param {string} host The host of the URL * @param {string} port The port of the URL * @param {string} path the path of the URL * @public */ function WhitelistEntry(protocol, host, port, path){ if (protocol) { this.protocol = protocol.toUpperCase(); } if (host) { this.host = host.toUpperCase(); } this.port = port; this.path = path; } var aWhitelist = []; /** * clears the whitelist for URL valiadtion * * @public */ jQuery.sap.clearUrlWhitelist = function() { aWhitelist.splice(0,aWhitelist.length); }; /** * Adds a whitelist entry for URL valiadtion * * @param {string} protocol The protocol of the URL * @param {string} host The host of the URL * @param {string} port The port of the URL * @param {string} path the path of the URL * @public */ jQuery.sap.addUrlWhitelist = function(protocol, host, port, path) { var oEntry = new WhitelistEntry(protocol, host, port, path); var iIndex = aWhitelist.length; aWhitelist[iIndex] = oEntry; }; /** * Removes a whitelist entry for URL valiadtion * * @param {int} iIndex index of entry * @public */ jQuery.sap.removeUrlWhitelist = function(iIndex) { aWhitelist.splice(iIndex,1); }; /** * Gets the whitelist for URL valiadtion * * @return {string[]} whitelist * @public */ jQuery.sap.getUrlWhitelist = function() { return aWhitelist.slice(); }; /** * Validates an URL. Check if it's not a script or other security issue. * * @param {string} sUrl * @return true if valid, false if not valid * @public */ jQuery.sap.validateUrl = function(sUrl) { var result = /(?:([^:\/?#]+):)?(?:\/\/([^\/?#:]*)(?::([0-9]+))?)?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/.exec(sUrl); if (!result) { return result; } var sProtocol = result[1], sHost = result[2], sPort = result[3], sPath = result[4], sQuery = result[5], sHash = result[6]; var rCheck = /[\x00-\x24\x26-\x29\x2b\x2c\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7d\x7f-\uffff]/; var rCheckHash = /[\x00-\x24\x26-\x29\x2b\x2c\x3a-\x3e\x5b-\x5e\x60\x7b-\x7d\x7f-\uffff]/; var rCheckMail = /[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/; // protocol if (sProtocol) { sProtocol = sProtocol.toUpperCase(); if (aWhitelist.length <= 0) { // no whitelist -> check for default protocols if (!/^(https?|ftp)/i.test(sProtocol)) { return false; } } } // Host -> whitelist + character check (TBD) if (sHost) { sHost = sHost.toUpperCase(); } // Path -> split for "/" and check if forbidden characters exist if (sPath) { if (sProtocol === "MAILTO") { var bCheck = rCheckMail.test(sPath); if (!bCheck) { return false; } } else { var aComponents = sPath.split("/"); for ( var i = 0; i < aComponents.length; i++) { var bCheck = rCheck.test(aComponents[i]); if (bCheck) { // forbidden character found return false; } } } } // query -> Split on & and = and check if forbidden characters exist if (sQuery) { var aComponents = sQuery.split("&"); for ( var i = 0; i < aComponents.length; i++) { var iPos = aComponents[i].search("="); if (iPos != -1) { var sPart1 = aComponents[i].substring(0,iPos); var sPart2 = aComponents[i].substring(iPos + 1); var bCheck1 = rCheck.test(sPart1); var bCheck2 = rCheck.test(sPart2); if (bCheck1 || bCheck2) { // forbidden character found return false; } } } } // hash if (sHash) { if (rCheckHash.test(sHash)) { // forbidden character found return false; } } //filter whitelist if (aWhitelist.length > 0) { var bFound = false; for (var i = 0; i < aWhitelist.length; i++) { jQuery.sap.assert(aWhitelist[i] instanceof WhitelistEntry, "whitelist entry type wrong"); if (!sProtocol || !aWhitelist[i].protocol || sProtocol == aWhitelist[i].protocol) { // protocol OK var bOk = false; if (sHost && aWhitelist[i].host && /^\*/.test(aWhitelist[i].host)) { // check for wildcard search at begin var sHostEscaped = aWhitelist[i].host.slice(1).replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); var rFilter = RegExp(sHostEscaped + "$"); if (rFilter.test(sHost)) { bOk = true; } } else if (!sHost || !aWhitelist[i].host || sHost == aWhitelist[i].host) { bOk = true; } if (bOk) { // host OK if ((!sHost && !sPort) || !aWhitelist[i].port || sPort == aWhitelist[i].port) { // port OK if (aWhitelist[i].path && /\*$/.test(aWhitelist[i].path)) { // check for wildcard search at end var sPathEscaped = aWhitelist[i].path.slice(0, -1).replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); var rFilter = RegExp("^" + sPathEscaped); if (rFilter.test(sPath)) { bFound = true; } } else if (!aWhitelist[i].path || sPath == aWhitelist[i].path) { // path OK bFound = true; } } } } if (bFound) { break; } } if (!bFound) { return false; } } return true; }; /** * Strips unsafe tags and attributes from HTML. * * @param {string} sHTML the HTML to be sanitized. * @param {object} [mOptions={}] options for the sanitizer * @return {string} sanitized HTML * @private */ jQuery.sap._sanitizeHTML = function(sHTML, mOptions) { return fnSanitizer(sHTML, mOptions || { uriRewriter: function(sUrl) { // by default we use the URL whitelist to check the URL's if (jQuery.sap.validateUrl(sUrl)) { return sUrl; } } }); }; /** * Registers an application defined sanitizer to be used instead of the built-in one. * * The given sanitizer function must have the same signature as * {@link jQuery.sap._sanitizeHTML}: * *
	 *   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