//****************************************************************************************************** // 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; };