/* general edit javascript */ /* jshint esversion: 6 */ // debug set /*var FRONTEND_DEBUG = false; var DEBUG = true; if (!DEBUG) { $($H(window.console)).each(function(w) { window.console[w.key] = function() {}; }); }*/ // METHOD: pop // PARAMS: url, window name, features // RETURN: none // DESC : opens a popup window with winName and given features (string) function pop(theURL, winName, features) { winName = window.open(theURL, winName, features); winName.focus(); } // METHOD: expandTA // PARAMS: id // RETURN: none // DESC : automatically resize a text area based on the amount of lines in it function expandTA(ta_id) { var ta; // if a string comes, its a get by id, else use it as an element pass on if (!ta_id.length) { ta = ta_id; } else { ta = document.getElementById(ta_id); } var maxChars = ta.cols; var theRows = ta.value.split("\n"); var numNewRows = 0; for ( var i = 0; i < theRows.length; i++ ) { if ((theRows[i].length+2) > maxChars) { numNewRows += Math.ceil( (theRows[i].length+2) / maxChars ) ; } } ta.rows = numNewRows + theRows.length; } // METHOD: ShowHideMenu // PARAMS: status -> show or hide // id -> id to work on // RETURN: none // DESC: shows or hides the menu // this is used in some old menu templates function ShowHideMenu(status, id) { if (status == 'show') { document.getElementById(id).style.visibility = 'visible'; if (document.getElementById('search_results').innerHTML) { document.getElementById('search_results').style.visibility = 'visible'; } } else if (status == 'hide') { document.getElementById(id).style.visibility = 'hidden'; if (document.getElementById('search_results').style.visibility == 'visible') { document.getElementById('search_results').style.visibility = 'hidden'; } } } // used in old templates // move element action function mv(id, direction) { document.forms[form_name].action.value = 'move'; document.forms[form_name].action_flag.value = direction; document.forms[form_name].action_id.value = id; document.forms[form_name].submit(); } // load element action function le(id) { document.forms[form_name].action.value = 'load'; if (load_id) { document.forms[form_name].action_yes.value = confirm('Do you want to load this data?'); } else { document.forms[form_name].action_yes.value = 'true'; } document.forms[form_name].action_id.value = id; document.forms[form_name].action_menu.value = id; if (document.forms[form_name].action_yes.value == 'true') { document.forms[form_name].submit(); } } // METHOD: sh // PARAMS: id -> element to hide // showText -> text for the element if shown // hideText -> text for the element if hidden // RETURN: returns true if hidden, or false if not // DESC : hides an element, additional writes 1 (show) or 0 (hide) into Flag field // this needs scriptacolous installed for BlindUp/BlindDown function sh(id, showText, hideText) { flag = id + 'Flag'; btn = id + 'Btn'; // get status from element (hidden or visible) divStatus = $(id).visible(); //console.log('Set flag %s for element %s', divStatus, id); if (divStatus) { // hide the element Effect.BlindUp(id, {duration:0.3}); $(flag).value = 0; $(btn).innerHTML = showText; } else if (!divStatus) { // show the element Effect.BlindDown(id, {duration:0.3}); $(flag).value = 1; $(btn).innerHTML = hideText; } // return current button status return divStatus; } // METHOD: getWindowSize // PARAMS: none // RETURN: array with width/height // DESC : wrapper to get the real window size for the current browser window function getWindowSize() { var width, height; width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth); height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight); return { width: width, height: height }; } // METHOD: getScrollOffset // PARAMS: none // RETURN: array with x/y px // DESC : wrapper to get the correct scroll offset function getScrollOffset() { var left, top; left = window.pageXOffset || (window.document.documentElement.scrollLeft || window.document.body.scrollLeft); top = window.pageYOffset || (window.document.documentElement.scrollTop || window.document.body.scrollTop); return { left: left, top: top }; } // METHOD: setCenter // PARAMS: id to set center // RETURN: none // DESC : centers div to current window size middle function setCenter(id, left, top) { // get size of id var dimensions = $(id).getDimensions(); var type = $(id).getStyle('position'); var viewport = getWindowSize(); var offset = getScrollOffset(); console.log('Id %s, type: %s, dimensions %s x %s, viewport %s x %s', id, type, dimensions.width, dimensions.height, viewport.width, viewport.height); // console.log('Scrolloffset left: %s, top: %s', offset.left, offset.top); // console.log('Left: %s, Top: %s (%s)', parseInt((viewport.width / 2) - (dimensions.width / 2) + offset.left), parseInt((viewport.height / 2) - (dimensions.height / 2) + offset.top), parseInt((viewport.height / 2) - (dimensions.height / 2))); if (left) { $(id).setStyle ({ left: parseInt((viewport.width / 2) - (dimensions.width / 2) + offset.left) + 'px' }); } if (top) { // if we have fixed, we do not add the offset, else it moves out of the screen var top_pos = type == 'fixed' ? parseInt((viewport.height / 2) - (dimensions.height / 2)) : parseInt((viewport.height / 2) - (dimensions.height / 2) + offset.top); $(id).setStyle ({ top: top_pos + 'px' }); } } // METHOD: goToPos() // PARAMS: element, offset (default 0) // RETURN: none // DESC: goes to an element id position function goToPos(element, offset = 0) { try { if ($(element)) { // get the element pos var pos = $(element).cumulativeOffset(); // if not top element and no offset given, set auto offset for top element // also compensate by -40 for some offset calc issue and not have it too much to the header if (pos.top != 0 && offset == 0) { offset = ($(GL_main_content_div).style.paddingTop.replace('px', '') * -1) - 40; } //console.log('Scroll to: %s, Offset: %s [%s], PT: %s', element, offset, $('pbsMainContent').style.paddingTop.replace('px', ''), pos.top); window.scrollTo(pos.left, pos.top + offset); } } catch (err) { errorCatch(err); } } // METHOD: __ // PARAMS: text // RETURN: translated text (based on PHP selected language) // DESC : uses the i18n array created in the translation template, that is filled from gettext in PHP (Smarty) function __(string) { if (typeof i18n !== 'undefined' && isObject(i18n) && i18n[string]) { return i18n[string]; } else { return string; } } // METHOD: string.format // PARAMS: any, for string format // RETURN: formatted string // DESC : simple sprintf formater for replace // "{0} is cool, {1} is not".format("Alpha", "Beta"); // First, checks if it isn't implemented yet. if (!String.prototype.format) { String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != 'undefined' ? args[number] : match ; }); }; } // METHOD: numberWithCommas // PARAMS: number // RETURN: formatted with , in thousands // DESC : formats flat number 123456 to 123,456 const numberWithCommas = (x) => { var parts = x.toString().split("."); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); return parts.join("."); }; // METHOD: // PARAMS: string // RETURN: string with
// DESC : converts line breaks to br function convertLBtoBR(string) { return string.replace(/(?:\r\n|\r|\n)/g, '
'); } if (!String.prototype.escapeHTML) { String.prototype.escapeHTML = function() { return this.replace(/[&<>"'\/]/g, function (s) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }; return entityMap[s]; }); }; } if (!String.prototype.unescapeHTML) { String.prototype.unescapeHTML = function() { return this.replace(/&[#\w]+;/g, function (s) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', ''': "'", '/': "/" }; return entityMap[s]; }); }; } // METHOD: getTimestamp // PARAMS: none // RETURN: timestamp (in milliseconds) // DESC : returns current timestamp (unix timestamp) function getTimestamp() { var date = new Date(); return date.getTime(); } // METHOD: dec2hex // PARAMS: decimal string // RETURN: string // DESC : dec2hex :: Integer -> String // i.e. 0-255 -> '00'-'ff' function dec2hex(dec) { return ('0' + dec.toString(16)).substr(-2); } // METHOD: generateId // PARAMS: lenght in int // RETURN: random string // DESC : generateId :: Integer -> String // only works on mondern browsers function generateId(len) { var arr = new Uint8Array((len || 40) / 2); (window.crypto || window.msCrypto).getRandomValues(arr); return Array.from(arr, dec2hex).join(''); } // METHOD: randomIdF() // PARAMS: none // RETURN: not true random string // DESC : creates a pseudo random string of 10 characters // after many runs it will create duplicates function randomIdF() { return Math.random().toString(36).substring(2); } // METHOD: isObject // PARAMS: possible object // RETURN: true/false if it is an object or not // DESC : checks if a variable is an object function isObject(val) { if (val === null) { return false; } return ((typeof val === 'function') || (typeof val === 'object')); } // METHOD: keyInObject // PARAMS: key name, object // RETURN: true/false if key exists in object // DESC : checks if a key exists in a given object const keyInObject = (key, object) => (key in object) ? true : false; /*function keyInObject(key, object) { return (key in object) ? true : false; }*/ // METHOD: exists // PARAMS: uid // RETURN: true/false // DESC : checks if a DOM element actually exists const exists = (id) => $('#' + id).length > 0 ? true : false; /*function exists(id) { return $('#' + id).length > 0 ? true : false; }*/ // METHOD: formatBytes // PARAMS: bytes in int // RETURN: string in GB/MB/KB // DESC : converts a int number into bytes with prefix in two decimals precision // currently precision is fixed, if dynamic needs check for max/min precision function formatBytes(bytes) { var i = -1; do { bytes = bytes / 1024; i++; } while (bytes > 99); return parseFloat(Math.round(bytes * Math.pow(10, 2)) / Math.pow(10, 2)) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i]; } // METHOD: errorCatch // PARAMS: err (error from try/catch // RETURN: none // DESC : prints out error messages based on data available from the browser function errorCatch(err) { // for FF & Chrome if (err.stack) { // only FF if (err.lineNumber) { console.log('ERROR[%s:%s] %s', err.name, err.lineNumber, err.message); } else if (err.line) { // only Safari console.log('ERROR[%s:%s] %s', err.name, err.line, err.message); } else { console.log('ERROR[%s] %s', err.name, err.message); } // stack trace console.log('ERROR[stack] %s', err.stack); } else if (err.number) { // IE console.log('ERROR[%s:%s] %s', err.name, err.number, err.message); console.log('ERROR[description] %s', err.description); } else { // the rest console.log('ERROR[%s] %s', err.name, err.message); } } // METHOD: actionIndicator // PARAMS: none // RETURN: none // DESC : show or hide the "do" overlay function actionIndicator(loc = '') { if ($('overlayBox').visible()) { actionIndicatorHide(loc); } else { actionIndicatorShow(loc); } } // METHOD: actionIndicatorShow/actionIndicatorHide // PARAMS: loc for console log info // RETURN: none // DESC : explicit show/hide for action Indicator // instead of automatically show or hide, do // on command function actionIndicatorShow(loc = '') { console.log('Indicator: SHOW [%s]', loc); $('indicator').addClassName('progress'); setCenter('indicator', true, true); $('indicator').show(); overlayBoxShow(); } function actionIndicatorHide(loc = '') { console.log('Indicator: HIDE [%s]', loc); $('indicator').hide(); $('indicator').removeClassName('progress'); overlayBoxHide(); } // METHOD: overlayBoxView // PARAMS: none // RETURN: none // DESC : shows or hides the overlay box function overlayBoxShow() { // check if overlay box exists and if yes set the z-index to 100 if ($('overlayBox').visible()) { $('overlayBox').style.zIndex = "100"; } else { $('overlayBox').show(); } } function overlayBoxHide() { // if the overlay box z-index is 100, do no hide, but set to 98 if ($('overlayBox').style.zIndex == 100) { $('overlayBox').style.zIndex = "98"; } else { $('overlayBox').hide(); } } // METHOD: setOverlayBox // PARAMS: none // RETURN: none // DESC : position the overlay block box and shows it function setOverlayBox() { var viewport = document.viewport.getDimensions(); $('overlayBox').setStyle ({ width: '100%', height: '100%' }); $('overlayBox').show(); } // METHOD: ClearCall // PARAMS: none // RETURN: none // DESC : the abort call, clears the action box and hides it and the overlay box function ClearCall() { $('actionBox').innerHTML = ''; $('actionBox').hide(); $('overlayBox').hide(); } // *** DOM MANAGEMENT FUNCTIONS // METHOD: cel [create element] // PARAMS: tag: must set tag (div, span, etc) // id: optional set for id, if input, select will be used for name // content: text content inside, is skipped if sub elements exist // css: array for css tags // options: anything else (value, placeholder, OnClick, style) // RETURN: object // DESC : creates object for DOM element creation flow const cel = (tag, id = '', content = '', css = [], options = {}) => _element = { tag: tag, id: id, name: options.name, // override name if set [name gets ignored in tree build anyway] content: content, css: css, options: options, sub: [] }; // METHOD: ael [attach element] // PARAMS: base: object where to attach/search // attach: the object to be attached // id: optional id, if given search in base for this id and attach there // RETURN: "none", technically there is no return needed // DESC : attach a cel created object to another to create a basic DOM tree function ael(base, attach, id = '') { if (id) { // base id match already if (base.id == id) { base.sub.push(Object.assign({}, attach)); } else { // sub check if (base.sub.length > 0) { base.sub.each(function(t) { // recursive call to sub element ael(t, attach, id); }); } } } else { base.sub.push(Object.assign({}, attach)); } return base; } // METHOD: aelx [attach n elements] // PARAMS: base: object to where we attach the elements // attach 1..n: attach directly to the base element those attachments // RETURN: "none", technically there is no return needed // DESC : directly attach n elements to one master base element // this type does not support attach with optional id function aelx(base, ...attach) { attach.each(function(t) { base.sub.push(Object.assign({}, t)); }); return base; } // METHOD: rel [reset element] // PARAMS: cel created element // RETURN: returns reset base element // DESC : resets the sub elements of the base element given const rel = (base) => { base.sub = []; return base; }; // METHOD: rcssel [remove a css from the element] // PARAMS: element, style sheet to remove // RETURN: returns full element // DESC : searches and removes style from css array function rcssel(_element, css) { var css_index = _element.css.indexOf(css); if (css_index > -1) { _element.css.splice(css_index, 1); } return _element; } // METHOD: acssel [add css element] // PARAMS: element, style sheet to add // RETURN: returns full element // DESC : adds a new style sheet to the element given function acssel(_element, css) { var css_index = _element.css.indexOf(css); if (css_index == -1) { _element.css.push(css); } return _element; } // METHOD: scssel // PARAMS: element, style to remove, style to add // RETURN: returns full element // DESC : removes one css and adds another // is a wrapper around rcssel/acssel function scssel(_element, rcss, acss) { rcssel(_element, rcss); acssel(_element, acss); return _element; } // METHOD: phfo [produce html from object] // PARAMS: object tree with dom element declarations // RETURN: HTML string that can be used as innerHTML // DESC : parses the object tree created with cel/ael // and converts it into an HTML string that can // be inserted into the page function phfo(tree) { // holds the elements var content = []; // main part line var line = '<' + tree.tag; // first id, if set if (tree.id) { line += ' id="' + tree.id + '"'; // if anything input (input, textarea, select then add name too) if (['input', 'textarea', 'select'].includes(tree.tag)) { line += ' name="' + (tree.name ? tree.name : tree.id) + '"'; } } // second CSS if (tree.css.length > 0) { line += ' class="'; tree.css.each(function(t) { line += t + ' '; }); // strip last space line = line.slice(0, -1); line += '"'; } // options is anything key = "data" if (tree.options) { // ignores id, name, class as key for (const [key, item] of Object.entries(tree.options)) { if (!['id', 'name', 'class'].includes(key)) { line += ' ' + key + '="' + item + '"'; } } } // finish open tag line += '>'; // push finished line content.push(line); // dive into sub tree to attach sub nodes // NOTES: we can have content (text) AND sub nodes at the same level // CONTENT (TEXT) takes preference over SUB NODE in order if (tree.sub.length > 0) { if (tree.content) { content.push(tree.content); } tree.sub.each(function(t) { content.push(phfo(t)); }); } else if (tree.content) { content.push(tree.content); } // if not input close if (tree.tag != 'input') { content.push(''); } // combine to string return content.join(''); } // *** DOM MANAGEMENT FUNCTIONS // BLOCK: html wrappers for quickly creating html data blocks // NOTE : OLD FORMAT which misses multiple block set // METHOD: html_options // PARAMS: name/id, array for the options, selected item uid // options_only [def false] if this is true, it will not print the select part // return_string [def false]: return as string and not as element // sort [def '']: if empty as is, else allowed 'keys', 'values' all others are ignored // RETURN: html with build options block // DESC : creates an select/options drop down block. // the array needs to be key -> value format. key is for the option id and value is for the data output function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') { // wrapper to new call return html_options_block(name, data, selected, false, options_only, return_string, sort); } // NOTE : USE THIS CALL, the above one is deprecated // METHOD: html_options // PARAMS: name/id, array for the options, // selected item uid // multiple [def 0] if this is 1 or larger, the drop down will be turned into multiple select // the number sets the size value unless it is 1, then it is default // options_only [def false] if this is true, it will not print the select part // return_string [def false]: return as string and not as element // sort [def '']: if empty as is, else allowed 'keys', 'values' all others are ignored // RETURN: html with build options block // DESC : creates an select/options drop down block. // the array needs to be key -> value format. key is for the option id and value is for the data output function html_options_block(name, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '') { var content = []; var element_select; var select_options = {}; var element_option; var data_list = []; // for sorted output var value; var option; if (multiple > 0) { select_options.multiple = ''; if (multiple > 1) { select_options.size = multiple; } } // set outside select, gets stripped on return if options only is true element_select = cel('select', name, '', [], select_options); // console.log('Call for %s, options: %s', name, options_only); if (sort == 'keys') { data_list = Object.keys(data).sort(); } else if (sort == 'values') { data_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b])); } else { data_list = Object.keys(data); } // console.log('ORDER: %s', data_list); // use the previously sorted list // for (const [key, value] of Object.entries(data)) { for (const key of data_list) { value = data[key]; console.log('create [%s] options: key: %s, value: %s', name, key, value); // basic options init options = { 'label': value, 'value': key }; // add selected if matching if (selected == key) { options.selected = ''; } // create the element option element_option = cel('option', '', value, '', options); // attach it to the select element ael(element_select, element_option); } // if with select part, convert to text if (!options_only) { if (return_string) { content.push(phfo(element_select)); return content.join(''); } else { return element_select; } } else { // strip select part if (return_string) { element_select.sub.each(function(t) { content.push(phfo(t)); }); return content.join(''); } else { return element_select.sub; } } } // METHOD: html_options_refill // PARAMS: name/id, array of options, sort = '' // sort [def '']: if empty as is, else allowed 'keys', 'values' all others are ignored // RETURN: none // DESC : refills a select box with options and keeps the selected function html_options_refill(name, data, sort = '') { var element_option; var option_selected; var data_list = []; // for sorted output var value; // skip if not exists if ($(name)) { // console.log('Call for %s, options: %s', name, options_only); if (sort == 'keys') { data_list = Object.keys(data).sort(); } else if (sort == 'values') { data_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b])); } else { data_list = Object.keys(data); } // first read in existing ones from the options and get the selected one [].forEach.call(document.querySelectorAll('#' + name + ' :checked'), function(elm) { option_selected = elm.value; }); $(name).innerHTML = ''; for (const key of data_list) { value = data[key]; // console.log('add [%s] options: key: %s, value: %s', name, key, value); element_option = document.createElement('option'); element_option.label = value; element_option.value = key; element_option.innerHTML = value; $(name).appendChild(element_option); } } } // *** MASTER logout call // METHOD: loginLogout // PARAMS: none // RETURN: none // DESC : submits basic data for form logout function loginLogout() { const form = document.createElement('form'); form.method = 'post'; const hiddenField = document.createElement('input'); hiddenField.type = 'hidden'; hiddenField.name = 'login_logout'; hiddenField.value = 'Logout'; form.appendChild(hiddenField); document.body.appendChild(form); form.submit(); } /* END */