diff options
Diffstat (limited to 'public/js/icinga/utils.js')
-rw-r--r-- | public/js/icinga/utils.js | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/public/js/icinga/utils.js b/public/js/icinga/utils.js new file mode 100644 index 0000000..280e4f6 --- /dev/null +++ b/public/js/icinga/utils.js @@ -0,0 +1,582 @@ +/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +/** + * Icinga utility functions + */ +(function(Icinga, $) { + + 'use strict'; + + Icinga.Utils = function (icinga) { + + /** + * Utility functions may need access to their Icinga instance + */ + this.icinga = icinga; + + /** + * We will use this to create an URL helper only once + */ + this.urlHelper = null; + }; + + Icinga.Utils.prototype = { + + timeWithMs: function (now) { + + if (typeof now === 'undefined') { + now = new Date(); + } + + var ms = now.getMilliseconds() + ''; + while (ms.length < 3) { + ms = '0' + ms; + } + + return now.toLocaleTimeString() + '.' + ms; + }, + + timeShort: function (now) { + + if (typeof now === 'undefined') { + now = new Date(); + } + + return now.toLocaleTimeString().replace(/:\d{2}$/, ''); + }, + + formatHHiiss: function (date) { + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + if (hours < 10) hours = '0' + hours; + if (minutes < 10) minutes = '0' + minutes; + if (seconds < 10) seconds = '0' + seconds; + return hours + ':' + minutes + ':' + seconds; + }, + + /** + * Format the given byte-value into a human-readable string + * + * @param {number} The amount of bytes to format + * @returns {string} The formatted string + */ + formatBytes: function (bytes) { + var log2 = Math.log(bytes) / Math.LN2; + var pot = Math.floor(log2 / 10); + var unit = (['b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'])[pot]; + return ((bytes / Math.pow(1024, pot)).toFixed(2)) + ' ' + unit; + }, + + /** + * Return whether the given element is visible in the users view + * + * Borrowed from: http://stackoverflow.com/q/487073 + * + * @param {selector} element The element to check + * @returns {Boolean} + */ + isVisible: function(element) { + var $element = $(element); + if (!$element.length) { + return false; + } + + var docViewTop = $(window).scrollTop(); + var docViewBottom = docViewTop + $(window).height(); + var elemTop = $element.offset().top; + var elemBottom = elemTop + $element.height(); + + return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) && + (elemBottom <= docViewBottom) && (elemTop >= docViewTop)); + }, + + getUrlHelper: function () { + if (this.urlHelper === null) { + this.urlHelper = document.createElement('a'); + } + + return this.urlHelper; + }, + + /** + * Parse a given Url and return an object + */ + parseUrl: function (url) { + + var a = this.getUrlHelper(); + a.href = url; + + var result = { + source : url, + protocol: a.protocol.replace(':', ''), + host : a.hostname, + port : a.port, + query : a.search, + file : (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1], + hash : a.hash.replace('#',''), + path : a.pathname.replace(/^([^\/])/,'/$1'), + relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1], + segments: a.pathname.replace(/^\//,'').split('/'), + params : this.parseParams(a) + }; + a = null; + + return result; + }, + + // Local URLs only + addUrlParams: function (url, params) { + var parts = this.parseUrl(url), + result = parts.path, + newparams = parts.params; + + // We overwrite existing params + $.each(params, function (key, value) { + key = encodeURIComponent(key); + value = typeof value !== 'string' || !! value ? encodeURIComponent(value) : null; + + var found = false; + for (var i = 0; i < newparams.length; i++) { + if (newparams[i].key === key) { + newparams[i].value = value; + found = true; + break; + } + } + + if (! found) { + newparams.push({ key: key, value: value }); + } + }); + + if (newparams.length) { + result += '?' + this.buildQuery(newparams); + } + + if (parts.hash.length) { + result += '#' + parts.hash; + } + + return result; + }, + + // Local URLs only + removeUrlParams: function (url, params) { + var parts = this.parseUrl(url), + result = parts.path, + newparams = parts.params; + + $.each(params, function (_, key) { + key = encodeURIComponent(key); + + for (var i = 0; i < newparams.length; i++) { + if (newparams[i].key === key) { + newparams.splice(i, 1); + return; + } + } + }); + + if (newparams.length) { + result += '?' + this.buildQuery(newparams); + } + + if (parts.hash.length) { + result += '#' + parts.hash; + } + + return result; + }, + + /** + * Return a query string for the given params + * + * @param {Array} params + * @return {string} + */ + buildQuery: function (params) { + var query = ''; + + for (var i = 0; i < params.length; i++) { + if (!! query) { + query += '&'; + } + + query += params[i].key; + switch (params[i].value) { + case true: + break; + case false: + query += '=0'; + break; + case null: + query += '='; + break; + default: + query += '=' + params[i].value; + } + } + + return query; + }, + + /** + * Parse url params + */ + parseParams: function (a) { + var params = [], + segment = a.search.replace(/^\?/,'').split('&'), + len = segment.length, + i = 0, + key, + value, + equalPos; + + for (; i < len; i++) { + if (! segment[i]) { + continue; + } + + equalPos = segment[i].indexOf('='); + if (equalPos !== -1) { + key = segment[i].slice(0, equalPos); + value = segment[i].slice(equalPos + 1); + } else { + key = segment[i]; + value = true; + } + + params.push({ key: key, value: value }); + } + + return params; + }, + + /** + * Add the specified flag to the given URL + * + * @param {string} url + * @param {string} flag + * + * @returns {string} + */ + addUrlFlag: function (url, flag) { + var pos = url.search(/#(?!!)/); + + if (url.indexOf('?') !== -1) { + flag = '&' + flag; + } else { + flag = '?' + flag; + } + + if (pos === -1) { + return url + flag; + } + + return url.slice(0, pos) + flag + url.slice(pos); + }, + + /** + * Check whether two HTMLElements overlap + * + * @param a {HTMLElement} + * @param b {HTMLElement} + * + * @returns {Boolean} whether elements overlap, will return false when one + * element is not in the DOM + */ + elementsOverlap: function(a, b) + { + // a bounds + var aoff = $(a).offset(); + if (!aoff) { + return false; + } + var at = aoff.top; + var ah = a.offsetHeight || (a.getBBox && a.getBBox().height); + var al = aoff.left; + var aw = a.offsetWidth || (a.getBBox && a.getBBox().width); + + // b bounds + var boff = $(b).offset(); + if (!boff) { + return false; + } + var bt = boff.top; + var bh = b.offsetHeight || (b.getBBox && b.getBBox().height); + var bl = boff.left; + var bw = b.offsetWidth || (b.getBBox && b.getBBox().width); + + return !(at > (bt + bh) || bt > (at + ah)) && !(bl > (al + aw) || al > (bl + bw)); + }, + + /** + * Create a selector that can be used to fetch the element the same position in the DOM-Tree + * + * Create the path to the given element in the DOM-Tree, comparable to an X-Path. Climb the + * DOM tree upwards until an element with an unique ID is found, this id is used as the anchor, + * all other elements will be addressed by their position in the parent. + * + * @param {HTMLElement} el The element to extract the path for. + * + * @returns {Array} The path of the element, that can be passed to getElementByPath + */ + getDomPath: function (el) { + if (! el) { + return []; + } + if (el.id !== '') { + return ['#' + el.id]; + } + if (el === document.body) { + return ['body']; + } + + var siblings = el.parentNode.childNodes; + var index = 0; + for (var i = 0; i < siblings.length; i ++) { + if (siblings[i].nodeType === 1) { + index ++; + } + + if (siblings[i] === el) { + var p = this.getDomPath(el.parentNode); + p.push(':nth-child(' + (index) + ')'); + return p; + } + } + }, + + /** + * Get the CSS selector to the given node + * + * @param {HTMLElement} element + * + * @returns {string} + */ + getCSSPath: function(element) { + if (typeof element === 'undefined') { + throw 'Requires a element'; + } + + if (typeof element.jquery !== 'undefined') { + if (! element.length) { + throw 'Requires a element'; + } + + element = element[0]; + } + + var path = []; + + while (true) { + let id = element.id; + if (typeof id !== 'undefined' && typeof id !== 'string') { + // Sometimes there may be a form element with the name "id" + id = element.getAttribute("id"); + } + + if (!! id) { + // Only use ids if they're truly unique + let results = document.querySelectorAll('* #' + this.escapeCSSSelector(id)); + if (results.length === 1) { + path.push('#' + id); + break; + } + } + + var tagName = element.tagName; + var parent = element.parentElement; + + if (! parent) { + path.push(tagName.toLowerCase()); + break; + } + + if (parent.children.length) { + var index = 0; + do { + if (element.tagName === tagName) { + index++; + } + } while ((element = element.previousElementSibling)); + + path.push(tagName.toLowerCase() + ':nth-of-type(' + index + ')'); + } else { + path.push(tagName.toLowerCase()); + } + + element = parent; + } + + return path.reverse().join(' > '); + }, + + /** + * Escape the given string to be used in a CSS selector + * + * @param {string} selector + * @returns {string} + */ + escapeCSSSelector: function (selector) { + if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') { + return CSS.escape(selector); + } + + return selector.replaceAll(/^(\d)/, '\\\\3$1 '); + }, + + /** + * Climbs up the given dom path and returns the element + * + * This is the counterpart + * + * @param path {Array} The selector + * @returns {HTMLElement} The corresponding element + */ + getElementByDomPath: function (path) { + var $element; + $.each(path, function (i, selector) { + if (! $element) { + $element = $(selector); + } else { + $element = $element.children(selector).first(); + if (! $element[0]) { + return false; + } + } + }); + return $element[0]; + }, + + objectKeys: Object.keys || function (obj) { + var keys = []; + $.each(obj, function (key) { + keys.push(key); + }); + return keys; + }, + + objectsEqual: function equals(obj1, obj2) { + var obj1Keys = Object.keys(obj1); + var obj2Keys = Object.keys(obj2); + if (obj1Keys.length !== obj2Keys.length) { + return false; + } + + return obj1Keys.concat(obj2Keys) + .every(function (key) { + return obj1[key] === obj2[key]; + }); + }, + + arraysEqual: function (array1, array2) { + if (array1.length !== array2.length) { + return false; + } + + var value1, value2; + for (var i = 0; i < array1.length; i++) { + value1 = array1[i]; + value2 = array2[i]; + + if (typeof value1 === 'object') { + if (typeof value2 !== 'object' || ! this.objectsEqual(value1, value2)) { + return false; + } + } else if (value1 !== value2) { + return false; + } + } + + return true; + }, + + /** + * Cleanup + */ + destroy: function () { + this.urlHelper = null; + this.icinga = null; + }, + + /** + * Encode the parenthesis too + * + * @param str {String} A component of a URI + * + * @returns {String} Encoded component + */ + fixedEncodeURIComponent: function (str) { + return encodeURIComponent(str).replace(/[()]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); + }, + + escape: function (str) { + return String(str).replace( + /[&<>"']/gm, + function (c) { + return { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }[c]; + } + ); + }, + + /** + * Pad a string with another one + * + * @param {String} str the string to pad + * @param {String} padding the string to use for padding + * @param {Number} minLength the minimum length of the result + * + * @returns {String} the padded string + */ + padString: function(str, padding, minLength) { + str = String(str); + padding = String(padding); + while (str.length < minLength) { + str = padding + str; + } + return str; + }, + + /** + * Shuffle a string + * + * @param {String} str The string to shuffle + * + * @returns {String} The shuffled string + */ + shuffleString: function(str) { + var a = str.split(""), + n = a.length; + + for(var i = n - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + return a.join(""); + }, + + /** + * Generate an id + * + * @param {Number} len The desired length of the id + * + * @returns {String} The id + */ + generateId: function(len) { + return this.shuffleString('abcefghijklmnopqrstuvwxyz').substr(0, len); + } + }; + +}(Icinga, jQuery)); |