//******************************************************************************************************
// gsf.web.client.js - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 01/26/2016 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
// ReSharper disable NativeTypePrototypeExtending
// ReSharper disable UndeclaredGlobalVariableUsing
// Grid Solutions Framework Core Web Client Script Functions
"use strict";
if (typeof jQuery === "undefined") {
throw new Error("gsf.web.client script requires jQuery - make sure jquery.js is loaded first");
}
const isIE = detectIE();
var textMetrics;
$(function() {
// Create a canvas object that will be used for text metrics calculations
$("")
.appendTo("body");
// Get text metrics canvas context
textMetrics = document.getElementById("textMetricsCanvas").getContext("2d");
});
// Miscellaneous functions
function notNull(value, nonNullValue) {
return value || (nonNullValue || "");
}
function detectIE() {
const ua = window.navigator.userAgent;
const msie = ua.indexOf("MSIE ");
if (msie > 0) {
// IE 10 or older => return version number
return parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10);
}
const trident = ua.indexOf("Trident/");
if (trident > 0) {
// IE 11 => return version number
const rv = ua.indexOf("rv:");
return parseInt(ua.substring(rv + 3, ua.indexOf(".", rv)), 10);
}
const edge = ua.indexOf("Edge/");
if (edge > 0) {
// Edge (IE 12+) => return version number
return parseInt(ua.substring(edge + 5, ua.indexOf(".", edge)), 10);
}
// Other browser
return false;
}
function isNumber(val) {
return !isNaN(parseFloat(val)) && isFinite(val);
}
function toHex(val, leftPadding) {
if (leftPadding === undefined)
leftPadding = 4;
const isNegative = val < 0;
return (isNegative ? "-" : "") + "0x" + Math.abs(val).toString(16).padLeft(leftPadding, "0").toUpperCase();
}
function isBool(val) {
if (typeof value === "boolean")
return true;
const lval = val.toString().toLowerCase();
return lval === "true" || lval === "false";
}
function getBool(val) {
const num = +val;
return !isNaN(num) ? !!num : !!String(val).toLowerCase().replace(false, "");
}
function getParameterByName(name, url) {
if (!url)
url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)"), results = regex.exec(url);
if (!results)
return null;
if (!results[2])
return "";
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function clearCachedCredentials(securedUrl, successCallback) {
if (isIE) {
document.execCommand("ClearAuthenticationCache", "false");
if (successCallback)
successCallback(true);
} else {
const xhr = new XMLHttpRequest();
xhr.open("GET", securedUrl, true);
xhr.setRequestHeader("Content-type", "application/json");
// Send in an invalid set of credentials, i.e., base64 encoded _logout:_logout:
xhr.setRequestHeader("Authorization", "Basic X2xvZ291dDpfbG9nb3V0");
if (successCallback)
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE)
successCallback(this.status === 200, this.responseText);
};
xhr.send();
}
}
const persistentStorage = (function() {
function storageAvailable(type) {
var storage = [];
try {
storage = window[type];
const x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return e instanceof DOMException &&
(
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === "QuotaExceededError" ||
// Firefox
e.name === "NS_ERROR_DOM_QUOTA_REACHED"
) &&
// acknowledge QuotaExceededError only if there's something already stored
storage.length !== 0;
}
}
function fakeStorage() {
return {
getItem: function() {},
setItem: function() {},
removeItem: function() {}
};
}
if (storageAvailable("localStorage"))
return localStorage;
else
return fakeStorage();
})();
// Number functions
Number.prototype.truncate = function() {
if (typeof Math.trunc !== "function")
return parseInt(this.toString());
return Math.trunc(this);
};
Number.prototype.padLeft = function(totalWidth, paddingChar) {
return this.truncate().toString().padLeft(totalWidth, paddingChar || "0");
};
Number.prototype.padRight = function(totalWidth, paddingChar) {
return this.truncate().toString().padRight(totalWidth, paddingChar || "0");
};
// Array functions
// Combines a dictionary of key-value pairs into a string. Values will be escaped within startValueDelimiter and endValueDelimiter
// to contain nested key/value pair expressions like the following: "normalKVP=-1; nestedKVP={p1=true; p2=0.001}" when either the
// parameterDelimiter or the keyValueDelimiter are detected in the value of the key/value pair.
function joinKeyValuePairs(source, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) {
if (!parameterDelimiter)
parameterDelimiter = ";";
if (!keyValueDelimiter)
keyValueDelimiter = "=";
if (!startValueDelimiter)
startValueDelimiter = "{";
if (!endValueDelimiter)
endValueDelimiter = "}";
const values = [];
for (let key in source) {
if (source.hasOwnProperty(key)) {
let value = source[key];
if (isBool(value))
value = value.toString().toLowerCase();
else
value = value ? value.toString() : "";
if (value.indexOf(parameterDelimiter) >= 0 ||
value.indexOf(keyValueDelimiter) >= 0 ||
value.indexOf(startValueDelimiter) >= 0 ||
value.indexOf(endValueDelimiter) >= 0)
value = startValueDelimiter + value + endValueDelimiter;
values.push(key + keyValueDelimiter + value);
}
}
return values.join(parameterDelimiter + " ");
}
Array.prototype.joinKeyValuePairs =
function(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) {
return joinKeyValuePairs(this, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter);
};
Array.prototype.forEachWithDelay = function(iterationCallback, timeout, completedCallback, thisArg) {
var index = 0,
count = this.length,
self = this,
next = function() {
if (self[index])
iterationCallback.call(thisArg || self, self[index], index, self);
index++;
if (index < count)
setTimeout(next, timeout);
else
completedCallback.call(thisArg || self, self);
};
next();
};
if (!Array.prototype.any) {
if (Array.prototype.some) {
Array.prototype.any = Array.prototype.some;
} else {
Array.prototype.any = function(callback, thisArg) {
var args;
if (this === null)
throw new TypeError("this is null or not defined");
const array = Object(this);
const length = array.length >>> 0; // Convert array length to positive integer
if (typeof callback !== "function")
throw new TypeError();
if (arguments.length > 1)
args = thisArg;
else
args = undefined;
var index = 0;
while (index < length) {
if (index in array) {
const element = array[index];
if (callback.call(args, element, index, array))
return true;
}
index++;
}
return false;
};
}
}
if (!Array.prototype.count) {
Array.prototype.count = function (callback, thisArg) {
var args;
if (this === null)
throw new TypeError("this is null or not defined");
const array = Object(this);
const length = array.length >>> 0; // Convert array length to positive integer
if (typeof callback !== "function")
throw new TypeError();
if (arguments.length > 1)
args = thisArg;
else
args = undefined;
var index = 0;
var count = 0;
while (index < length) {
if (index in array) {
const element = array[index];
if (callback.call(args, element, index, array))
count++;
}
index++;
}
return count;
};
}
// Represents a dictionary style class with case-insensitive keys
function Dictionary(source) {
const self = this;
if (!source)
source = [];
self._keys = [];
self._values = [];
self.count = function() {
var size = 0;
for (let property in self._values) {
if (self._values.hasOwnProperty(property))
size++;
}
return size;
};
self.keys = function() {
const keys = [];
for (let property in self._keys) {
if (self._keys.hasOwnProperty(property))
keys.push(self._keys[property]);
}
return keys;
};
self.values = function() {
const values = [];
for (let property in self._values) {
if (self._keys.hasOwnProperty(property))
values.push(self._values[property]);
}
return values;
};
self.get = function(key) {
return self._values[String(key).toLowerCase()];
};
self.set = function(key, value) {
const lkey = String(key).toLowerCase();
if (!self._keys[lkey] || self._keys[lkey].toLowerCase() !== key)
self._keys[lkey] = key;
if (isBool(value))
self._values[lkey] = getBool(value);
else
self._values[lkey] = value;
};
self.remove = function(key) {
const lkey = String(key).toLowerCase();
delete self._keys[lkey];
delete self._values[lkey];
};
self.containsKey = function(key) {
const lkey = String(key).toLowerCase();
for (let property in self._values) {
if (self._values.hasOwnProperty(property) && property === lkey)
return true;
}
return false;
};
self.containsValue = function(value) {
for (let property in self._values) {
if (self._values.hasOwnProperty(property) && self._values[property] === value)
return true;
}
return false;
};
self.clear = function() {
self._keys = [];
self._values = [];
};
self.joinKeyValuePairs = function(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) {
const keyValuePairs = [];
for (let property in self._values) {
if (self._values.hasOwnProperty(property))
keyValuePairs[self._keys[property]] = self._values[property];
}
return keyValuePairs.joinKeyValuePairs(parameterDelimiter,
keyValueDelimiter,
startValueDelimiter,
endValueDelimiter);
};
self.pushAll = function(source) {
for (let property in source)
if (source.hasOwnProperty(property))
self.set(property, source[property]);
};
self.toObservableDictionary = function(useLowerKeys) {
// See ko.observableDictionary.js
const observableDictionary = new ko.observableDictionary();
for (let property in self._values) {
if (self._values.hasOwnProperty(property))
observableDictionary.push(useLowerKeys ? property : self._keys[property], self._values[property]);
}
return observableDictionary;
};
self.updateObservableDictionary = function(observableDictionary, useLowerKeys) {
for (let property in self._values) {
if (self._values.hasOwnProperty(property))
observableDictionary.set(useLowerKeys ? property : self._keys[property], self._values[property]);
}
};
// Construction
if (source instanceof Dictionary) {
for (let property in source._values)
if (source._values.hasOwnProperty(property))
self.set(source._keys[property], source._values[property]);
} else {
for (let property in source) {
if (source.hasOwnProperty(property))
self.set(property, source[property]);
}
}
}
Dictionary.fromObservableDictionary = function(observableDictionary) {
const dictionary = new Dictionary();
dictionary.pushAll(observableDictionary.toJSON());
return dictionary;
};
// String functions
function isEmpty(str) {
return !str || String(str).length === 0;
}
String.prototype.trimLeft = String.prototype.trimLeft ||
function() {
return this.replace(/^\s+/, "");
};
String.prototype.truncate = function(limit) {
const text = this.trim();
if (text.length > limit)
return text.substr(0, limit - 3) + "...";
return text;
};
String.prototype.replaceAll = function (findText, replaceWith, ignoreCase) {
let findVal = typeof findText === "string" ? findText : String(findText);
let replaceVal = typeof replaceWith === "string" ? replaceWith.replace(/\$/g, "$$$$") : replaceWith;
return this.replace(
new RegExp(findVal.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), ignoreCase ? "gi" : "g"), replaceVal);
};
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(searchString, position) {
const subjectString = this.toString();
if (typeof position !== "number" ||
!isFinite(position) ||
Math.floor(position) !== position ||
position > subjectString.length)
position = subjectString.length;
position -= searchString.length;
const lastIndex = subjectString.lastIndexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
};
}
if (!String.format) {
String.format = function(format) {
var text = format;
for (let i = 1; i < arguments.length; i++)
text = text.replaceAll("{" + (i - 1) + "}", arguments[i]);
return text;
};
}
String.prototype.padLeft = function(totalWidth, paddingChar) {
if (totalWidth > this.length)
return Array(totalWidth - this.length + 1).join(paddingChar || " ") + this;
return this;
};
String.prototype.padRight = function(totalWidth, paddingChar) {
if (totalWidth > this.length)
return this + Array(totalWidth - this.length + 1).join(paddingChar || " ");
return this;
};
String.prototype.countOccurrences = function(searchString) {
return this.split(searchString).length - 1;
};
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
}
String.prototype.regexEncode = function() {
return "\\u" + this.charCodeAt(0).toString(16).padLeft(4, "0");
};
// Returns a Dictionary of the parsed key/value pair expressions from a string. Parameter pairs are delimited by keyValueDelimiter
// and multiple pairs separated by parameterDelimiter. Supports encapsulated nested expressions.
String.prototype.parseKeyValuePairs = function(parameterDelimiter,
keyValueDelimiter,
startValueDelimiter,
endValueDelimiter,
ignoreDuplicateKeys) {
if (!parameterDelimiter)
parameterDelimiter = ";";
if (!keyValueDelimiter)
keyValueDelimiter = "=";
if (!startValueDelimiter)
startValueDelimiter = "{";
if (!endValueDelimiter)
endValueDelimiter = "}";
if (ignoreDuplicateKeys === undefined)
ignoreDuplicateKeys = true;
if (parameterDelimiter === keyValueDelimiter ||
parameterDelimiter === startValueDelimiter ||
parameterDelimiter === endValueDelimiter ||
keyValueDelimiter === startValueDelimiter ||
keyValueDelimiter === endValueDelimiter ||
startValueDelimiter === endValueDelimiter)
throw "All delimiters must be unique";
const escapedParameterDelimiter = parameterDelimiter.regexEncode();
const escapedKeyValueDelimiter = keyValueDelimiter.regexEncode();
const escapedStartValueDelimiter = startValueDelimiter.regexEncode();
const escapedEndValueDelimiter = endValueDelimiter.regexEncode();
const backslashDelimiter = "\\".regexEncode();
var keyValuePairs = new Dictionary();
var escapedValue = [];
var valueEscaped = false;
var delimiterDepth = 0;
// Escape any parameter or key/value delimiters within tagged value sequences
// For example, the following string:
// "normalKVP=-1; nestedKVP={p1=true; p2=false}")
// would be encoded as:
// "normalKVP=-1; nestedKVP=p1\\u003dtrue\\u003b p2\\u003dfalse")
for (let i = 0; i < this.length; i++) {
var character = this[i];
if (character === startValueDelimiter) {
if (!valueEscaped) {
valueEscaped = true;
continue; // Don't add tag start delimiter to final value
}
// Handle nested delimiters
delimiterDepth++;
}
if (character === endValueDelimiter) {
if (valueEscaped) {
if (delimiterDepth > 0) {
// Handle nested delimiters
delimiterDepth--;
} else {
valueEscaped = false;
continue; // Don't add tag stop delimiter to final value
}
} else {
throw "Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter \"" +
endValueDelimiter +
"\" before start value delimiter \"" +
startValueDelimiter +
"\".";
}
}
if (valueEscaped) {
// Escape any delimiter characters inside nested key/value pair
if (character === parameterDelimiter)
escapedValue.push(escapedParameterDelimiter);
else if (character === keyValueDelimiter)
escapedValue.push(escapedKeyValueDelimiter);
else if (character === startValueDelimiter)
escapedValue.push(escapedStartValueDelimiter);
else if (character === endValueDelimiter)
escapedValue.push(escapedEndValueDelimiter);
else if (character === "\\")
escapedValue.push(backslashDelimiter);
else
escapedValue.push(character);
} else {
if (character === "\\")
escapedValue.push(backslashDelimiter);
else
escapedValue.push(character);
}
}
if (delimiterDepth !== 0 || valueEscaped) {
// If value is still escaped, tagged expression was not terminated
if (valueEscaped)
delimiterDepth = 1;
throw "Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more " +
(delimiterDepth > 0
? "start value delimiters \"" + startValueDelimiter + "\""
: "end value delimiters \"" + endValueDelimiter + "\"") +
" than " +
(delimiterDepth < 0
? "start value delimiters \"" + startValueDelimiter + "\""
: "end value delimiters \"" + endValueDelimiter + "\"") +
".";
}
// Parse key/value pairs from escaped value
var pairs = escapedValue.join("").split(parameterDelimiter);
for (let i = 0; i < pairs.length; i++) {
// Separate key from value
var elements = pairs[i].split(keyValueDelimiter);
if (elements.length === 2) {
// Get key
var key = elements[0].trim();
// Get unescaped value
var unescapedValue = elements[1].trim().replaceAll(escapedParameterDelimiter, parameterDelimiter)
.replaceAll(escapedKeyValueDelimiter, keyValueDelimiter)
.replaceAll(escapedStartValueDelimiter, startValueDelimiter)
.replaceAll(escapedEndValueDelimiter, endValueDelimiter).replaceAll(backslashDelimiter, "\\");
// Add key/value pair to dictionary
if (ignoreDuplicateKeys) {
// Add or replace key elements with unescaped value
keyValuePairs.set(key, unescapedValue);
} else {
// Add key elements with unescaped value throwing an exception for encountered duplicate keys
if (keyValuePairs.containsKey(key))
throw "Failed to parse key/value pairs: duplicate key encountered. Key \"" +
key +
"\" is not unique within the string: \"" +
this +
"\"";
keyValuePairs.set(key, unescapedValue);
}
}
}
return keyValuePairs;
};
// Renders URLs and e-mail addresses as clickable links
function renderHotLinks(sourceText, target) {
if (target === undefined)
target = "_blank";
sourceText = sourceText.toString();
var replacedText;
// URLs starting with http://, https://, or ftp://
const replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = sourceText.replace(replacePattern1, "$1");
// URLs starting with "www." without // before it
const replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, "$1$2");
// Change e-mail addresses to mailto: links
const replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, "$1");
return replacedText;
}
// Date Functions
Date.prototype.addDays = function(days) {
return new Date(this.setDate(this.getDate() + days));
};
Date.prototype.toUTC = function() {
this.setMinutes(this.getMinutes() - this.getTimezoneOffset());
return this;
};
Date.prototype.daysBetween = function(startDate) {
const millisecondsPerDay = 24 * 60 * 60 * 1000;
return (this.toUTC() - startDate.toUTC()) / millisecondsPerDay;
};
String.prototype.toDate = function() {
return new Date(Date.parse(this));
};
String.prototype.formatDate = function(format, utc) {
return formatDate(this.toDate(), format, utc);
};
Date.prototype.formatDate = function(format, utc) {
return formatDate(this, format, utc);
};
function formatDate(date, format, utc) {
if (typeof date === "string")
return formatDate(date.toDate(), format, utc);
if (format === undefined && DateTimeFormat !== undefined)
format = DateTimeFormat;
if (utc === undefined)
utc = true;
function ii(i, len) {
var ss = i + "";
len = len || 2;
while (ss.length < len) ss = "0" + ss;
return ss;
}
if (!(format === null || format === undefined)) {
var MMMM = [
"\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October",
"November", "December"
];
var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var y = utc ? date.getUTCFullYear() : date.getFullYear();
format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y);
format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2));
format = format.replace(/(^|[^\\])y/g, "$1" + y);
var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]);
format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]);
format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M));
format = format.replace(/(^|[^\\])M/g, "$1" + M);
var d = utc ? date.getUTCDate() : date.getDate();
format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]);
format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]);
format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d));
format = format.replace(/(^|[^\\])d/g, "$1" + d);
var H = utc ? date.getUTCHours() : date.getHours();
format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H));
format = format.replace(/(^|[^\\])H/g, "$1" + H);
var h = H > 12 ? H - 12 : H === 0 ? 12 : H;
format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h));
format = format.replace(/(^|[^\\])h/g, "$1" + h);
var m = utc ? date.getUTCMinutes() : date.getMinutes();
format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m));
format = format.replace(/(^|[^\\])m/g, "$1" + m);
var s = utc ? date.getUTCSeconds() : date.getSeconds();
format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s));
format = format.replace(/(^|[^\\])s/g, "$1" + s);
var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds();
format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3));
f = Math.round(f / 10);
format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f));
f = Math.round(f / 10);
format = format.replace(/(^|[^\\])f/g, "$1" + f);
var T = H < 12 ? "AM" : "PM";
format = format.replace(/(^|[^\\])TT+/g, "$1" + T);
format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0));
var t = T.toLowerCase();
format = format.replace(/(^|[^\\])tt+/g, "$1" + t);
format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0));
var tz = -date.getTimezoneOffset();
var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-";
if (!utc) {
tz = Math.abs(tz);
var tzHrs = Math.floor(tz / 60);
var tzMin = tz % 60;
K += ii(tzHrs) + ":" + ii(tzMin);
}
format = format.replace(/(^|[^\\])K/g, "$1" + K);
var day = (utc ? date.getUTCDay() : date.getDay()) + 1;
format = format.replace(new RegExp(dddd[0], "g"), dddd[day]);
format = format.replace(new RegExp(ddd[0], "g"), ddd[day]);
format = format.replace(new RegExp(MMMM[0], "g"), MMMM[M]);
format = format.replace(new RegExp(MMM[0], "g"), MMM[M]);
format = format.replace(/\\(.)/g, "$1");
return format;
}
return null;
}
function delayedPromise(timeout) {
const $d = $.Deferred();
setTimeout(function () { $d.resolve(); }, timeout || 0);
return $d.promise();
}
// Takes a query string from a uri and returns a json object representation
function ParseQueryString(queryString) {
var obj = {}
decodeURI(queryString).replace(new RegExp("([^?=&]+)(=([^&]*))?", "g"), function ($0, $1, $2, $3) { return obj[$1] = $3 });
return obj;
}
// jQuery extensions
$.fn.enable = function() {
return this.each(function() {
this.disabled = false;
});
};
$.fn.disable = function() {
return this.each(function() {
this.disabled = true;
});
};
$.fn.visible = function() {
return this.each(function() {
$(this).css("visibility", "visible");
});
};
$.fn.invisible = function() {
return this.each(function() {
$(this).css("visibility", "hidden");
});
};
$.fn.readCookie = function(name) {
$(this).val(Cookies.get(name));
};
$.fn.writeCookie = function(name, expiration) {
if (expiration) // Date or number of days
Cookies.set(name, $(this).val(), { expires: expiration });
else
Cookies.set(name, $(this).val());
};
$.fn.removeCookie = function(name) {
Cookies.remove(name);
};
$.fn.paddingHeight = function() {
return this.outerHeight(true) - this.height();
};
$.fn.paddingWidth = function() {
return this.outerWidth(true) - this.width();
};
// Call function once at start-up to auto-size drop-down to selected option contents
$.fn.autoSizeSelect = function() {
return this.each(function() {
$(this).change(function() {
const arrowWidth = 30;
const $this = $(this);
// Create test element
const text = $this.find("option:selected").text();
const $test = $("").html(text);
// Add to body, get width, and get out
$test.appendTo("body");
const width = $test.width();
$test.remove();
// Set select width
$this.width(width + arrowWidth);
}).change();
});
};
// Cell truncations should only be used with .table-cell-hard-wrap style
$.fn.truncateToWidth = function(text, rows) {
if (isEmpty(text))
return "";
if (!rows)
rows = 1;
textMetrics.font = this.css("font");
var targetWidth = this.innerWidth();
if (rows > 1)
targetWidth *= (isIE ? 0.45 : 0.75) * rows;
if (targetWidth > 30) {
let textWidth = textMetrics.measureText(text).width;
let limit = Math.min(text.length, Math.ceil(targetWidth / (textWidth / text.length)));
while (textWidth > targetWidth && limit > 3) {
limit--;
text = text.truncate(limit);
textWidth = textMetrics.measureText(text).width;
}
} else {
text = text.charAt(0) + "...";
}
return text;
};
// The following target arrays of promises
$.fn.whenAny = function() {
var finish = $.Deferred();
if (this.length === 0)
finish.resolve();
else
$.each(this,
function(index, deferred) {
deferred.done(finish.resolve);
});
return finish.promise();
};
$.fn.whenAll = function() {
if (this.length > 0)
return $.when.apply($, this);
return $.Deferred().resolve().promise();
};
// Fix for IE11 not having this function
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
if (!Array.from) {
Array.from = (function() {
var toStr = Object.prototype.toString;
var isCallable = function(fn) {
return typeof fn === "function" || toStr.call(fn) === "[object Function]";
};
var toInteger = function(value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike === null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== "undefined") {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError("Array.from: when provided, the second argument must be a function");
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method
// of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === "undefined" ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
// Fix for IE11 not having this function
// https://tc39.github.io/ecma262/#sec-array.prototype.find
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Object.defineProperty(Array.prototype,
"find",
{
value: function(predicate) {
// 1. Let O be ? ToObject(this value).
if (this === null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (typeof predicate !== "function") {
throw new TypeError("predicate must be a function");
}
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
var thisArg = arguments[1];
// 5. Let k be 0.
var k = 0;
// 6. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(O, Pk).
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
// d. If testResult is true, return kValue.
var kValue = o[k];
if (predicate.call(thisArg, kValue, k, o)) {
return kValue;
}
// e. Increase k by 1.
k++;
}
// 7. Return undefined.
return undefined;
},
configurable: true,
writable: true
});
}
// Fix for IE11 not having this function
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
if (!Array.prototype.fill) {
Object.defineProperty(Array.prototype,
"fill",
{
value: function(value) {
// Steps 1-2.
if (this === null) {
throw new TypeError("this is null or not defined");
}
var O = Object(this);
// Steps 3-5.
var len = O.length >>> 0;
// Steps 6-7.
var start = arguments[1];
var relativeStart = start >> 0;
// Step 8.
var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
// Steps 9-10.
var end = arguments[2];
var relativeEnd = end === undefined ? len : end >> 0;
// Step 11.
var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
// Step 12.
while (k < final) {
O[k] = value;
k++;
}
// Step 13.
return O;
}
});
}
// Fix for IE11 not having this function
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat
if (Number.parseFloat === void 0)
Number.parseFloat = parseFloat;
// Fix for IE11 not having this function
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
Number.isInteger = Number.isInteger ||
function(value) {
return typeof value === "number" &&
isFinite(value) &&
Math.floor(value) === value;
};