diff --git a/www/admin/smarty_test.php b/www/admin/smarty_test.php index 147d7313..320ef451 100755 --- a/www/admin/smarty_test.php +++ b/www/admin/smarty_test.php @@ -12,7 +12,17 @@ require 'config.php'; require BASE.INCLUDES.'admin_header.inc'; $MASTER_TEMPLATE_NAME = 'main_body.tpl'; $TEMPLATE_NAME = 'smarty_test.tpl'; -$PAGE_WIDTH = 750; +$CSS_NAME = 'smart_test.css'; +$USE_PROTOTYPE = false; +$USE_JQUERY = true; +if ($USE_PROTOTYPE) { + $EDIT_JAVASCRIPT = 'edit.pt.js'; + $JS_NAME = 'prototype.test.js'; +} else if ($USE_JQUERY) { + $EDIT_JAVASCRIPT = 'edit.jq.js'; + $JS_NAME = 'jquery.test.js'; +} +$PAGE_WIDTH = "100%"; require BASE.INCLUDES.'admin_set_paths.inc'; // smarty test diff --git a/www/includes/admin_set_paths.inc b/www/includes/admin_set_paths.inc index 14529cc1..57abbe6f 100644 --- a/www/includes/admin_set_paths.inc +++ b/www/includes/admin_set_paths.inc @@ -122,8 +122,8 @@ if (false === strstr(LAYOUT.DEFAULT_TEMPLATE.LANG, $cms->lang_dir) || // javascrip translate data as template for auto translate if (!$TEMPLATE_TRANSLATE) { - $cms->debug('LANG', 'Load lang: '.$lang.', for page file jsTranslate_'.$lang.'.tpl'); $TEMPLATE_TRANSLATE = 'jsTranslate_'.$lang.'.tpl'; + $cms->debug('LANG', 'Load lang: '.$lang.', for page file '.$TEMPLATE_TRANSLATE); } else { // we assume we have some fixed set // we must add _<$lang> diff --git a/www/includes/admin_smarty.inc b/www/includes/admin_smarty.inc index 8aadea69..84051000 100644 --- a/www/includes/admin_smarty.inc +++ b/www/includes/admin_smarty.inc @@ -8,13 +8,13 @@ *********************************************************************/ // trigger flags -$cms->HEADER['USE_PROTOTYPE'] = USE_PROTOTYPE; +$cms->HEADER['USE_PROTOTYPE'] = isset($USE_PROTOTYPE) ? $USE_PROTOTYPE : USE_PROTOTYPE; // scriptacolous, can only be used with prototype -if (USE_PROTOTYPE) { - $cms->HEADER['USE_SCRIPTACULOUS'] = USE_SCRIPTACULOUS; +if ($cms->HEADER['USE_PROTOTYPE']) { + $cms->HEADER['USE_SCRIPTACULOUS'] = isset($USE_SCRIPTACULOUS) ? $USE_SCRIPTACULOUS : USE_SCRIPTACULOUS; } // jquery and prototype should not be used together -$cms->HEADER['USE_JQUERY'] = USE_JQUERY; // don't use either of those two toger +$cms->HEADER['USE_JQUERY'] = isset($USE_JQUERY) ? $USE_JQUERY : USE_JQUERY; // don't use either of those two toger // set basic template path (tmp) // paths are set in the 'set_paths.inc' file diff --git a/www/layout/admin/default/css/smarty_test.css b/www/layout/admin/default/css/smarty_test.css new file mode 100755 index 00000000..01ee4311 --- /dev/null +++ b/www/layout/admin/default/css/smarty_test.css @@ -0,0 +1,83 @@ +/* smart test CSS */ + +.jp-test { + width: 100%; + margin: 20px; + box-sizing: border-box; +} + +.test-div { + margin: 20px; +} + +/* CORE overlay, progress elements */ +.actionBoxElement { + background-color: white; + border-radius: 10px; + border: 2px solid black; + box-shadow: #333333 10px 10px 25px; + color: black; + /*font-size: 20px;*/ + left: 45px; + min-height: 200px; + position: fixed; + text-align: center; + top: 45px; + width: 70%; + z-index: 99; +} + +.actionBoxTitle { + background-color: #c2c5cf; + border-radius: 7px 7px 0 0; + margin-bottom: 5px; + padding: 5px; +} + +.actionBoxText { + margin: 0 20px 0 20px; +} + +.actionBoxButtons { + bottom: 0; + font-size: 20px; + padding: 20px 20px 10px 20px; + position: absolute; + width: 95%; +} + +/* the overlay background black cover */ +.overlayBoxElement { + background-color: rgba(0, 0, 0, 0.3); + height: 100%; + left: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 98; +} + +/* the progress guruguru */ +/* NEW VERSION with CSS key frame animation */ +.progress { + width: 100px; + height: 100px; + background: rgba(255, 255, 255, 0.6); + border: 20px solid rgba(255, 255, 255, 0.25); + border-left-color: rgba(3, 155, 229 ,1); + border-top-color: rgba(3, 155, 229 ,1); + border-radius: 50%; + display: inline-block; + animation: rotate 600ms infinite linear; + /* align */ + left: 0; + top: 0; + position: absolute; + z-index: 120; +} +/* Animation for above progress */ +@keyframes rotate { + to { + transform: rotate(1turn) + } +} diff --git a/www/layout/admin/default/javascript/debug.js b/www/layout/admin/default/javascript/debug.js index 18cfcb38..80e3bb53 100644 --- a/www/layout/admin/default/javascript/debug.js +++ b/www/layout/admin/default/javascript/debug.js @@ -8,7 +8,7 @@ // if debug is set to true, console log messages are printed if (!DEBUG) { - $($H(window.console)).each(function(w) { - window.console[w.key] = function() {}; - }); + for (var prop in window.console) { + window.console[prop] = function () {}; + } } diff --git a/www/layout/admin/default/javascript/edit.jq.js b/www/layout/admin/default/javascript/edit.jq.js new file mode 100644 index 00000000..508f8551 --- /dev/null +++ b/www/layout/admin/default/javascript/edit.jq.js @@ -0,0 +1,676 @@ +/* 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: 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 = {}; + dimensions.height = $('#' + id).height(); + dimensions.width = $('#' + id).width(); + var type = $('#' + id).css('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).css({ + 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).css({ + 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).length) + { + $('body,html').animate({ + scrollTop: $('#' + element).offset().top - offset + }, 500); + } + } 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() +{ + let date = new Date(); + return date.getTime(); +} + +// METHOD: isObject +// PARAMS: possible object +// RETURN: true/false if it is an object or not +function isObject(val) { + if (val === null) { + return false; + } + return ((typeof val === 'function') || (typeof val === 'object')); +} + +// METHOD: exists +// PARAMS: uid +// RETURN: true/false +// DESC : checks if a DOM element actually exists +const exists = (id) => $('#' + id) ? 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').is(':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').addClass('progress'); + setCenter('indicator', true, true); + $('#indicator').show(); + overlayBoxShow(); +} +function actionIndicatorHide(loc = '') +{ + console.log('Indicator: HIDE [%s]', loc); + $('#indicator').hide(); + $('#indicator').removeClass('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').is(':visible')) { + $('#overlayBox').css('zIndex', '100'); + } else { + $('#overlayBox').show(); + } +} +function overlayBoxHide() +{ + // if the overlay box z-index is 100, do no hide, but set to 98 + if ($('#overlayBox').css('zIndex') == 100) { + $('#overlayBox').css('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 (isObject(base.sub) && base.sub.length > 0) { + for (var i = 0; i < base.sub.length; i ++) { + // recursive call to sub element + ael(base.sub[i], 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) +{ + for (var i = 0; i < attach.length; i ++) { + base.sub.push(Object.assign({}, attach[i])); + } + return base; +} + +// METHOD: rel [reset element] +// PARAMS: cel created element +// RETURN: "none", is self change, but returns base.sub +// DESC : resets the sub elements of the base element given +const rel = (base) => base.sub = []; + +// METHOD: rcssel [remove a css from the element] +// PARAMS: element, style sheet to remove +// RETURN: "none", in place because of reference +// DESC : searches and removes style from css array +function rcssel(_element, css) +{ + let css_index = _element.css.indexOf(css); + if (css_index > -1) { + _element.css.splice(css_index, 1); + } +} + +// METHOD: acssel [add css element] +// PARAMS: element, style sheet to add +// RETURN: "none", in place add because of reference +// DESC : adds a new style sheet to the element given +function acssel(_element, css) +{ + let css_index = _element.css.indexOf(css); + if (css_index == -1) { + _element.css.push(css); + } +} + +// METHOD: scssel +// PARAMS: element, style to remove, style to add +// RETURN: "none", in place add because of reference +// DESC : removes one css and adds another +// is a wrapper around rcssel/acssel +function scssel(_element, rcss, acss) +{ + rcssel(_element, rcss); + acssel(_element, acss); +} + +// 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 + let content = []; + // main part line + let line = '<' + tree.tag; + let i; + // 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 (isObject(tree.css) && tree.css.length > 0) { + line += ' class="'; + for (i = 0; i < tree.css.length; i ++) { + line += tree.css[i] + ' '; + } + // strip last space + line = line.slice(0, -1); + line += '"'; + } + // options is anything key = "data" + if (isObject(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 (isObject(tree.sub) && tree.sub.length > 0) { + if (tree.content) { + content.push(tree.content); + } + for (i = 0; i < tree.sub.length; i ++) { + content.push(phfo(tree.sub[i])); + } + } 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 +// 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 = '') +{ + let content = []; + let element_select; + let element_option; + let data_list = []; // for sorted output + // set outside select, gets stripped on return if options only is true + element_select = cel('select', 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); + } + // 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) { + let value = data[key]; + console.log('create [%s] options: key: %s, value: %s', name, key, value); + // basic options init + let 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) { + for (var i = 0; i < element_select.sub.length; i ++) { + content.push(phfo(element_select.sub[i])); + } + 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 = '') +{ + let element_option; + let option_selected; + let data_list = []; // for sorted output + // skip if not exists + if (document.getElementById(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; + }); + document.getElementById(name).innerHTML = ''; + for (const key of data_list) { + let 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; + document.getElementById(name).appendChild(element_option); + } + } +} + +// METHOD: initDatepickr +// PARAMS: initial date ID (#) +// RETURN: true on ok, false on failure +// DESC : inits date pickr which translations for dates (week/month) +function initDatepickr(init_date) +{ + if (document.getElementById(init_date)) { + datepickr('#' + init_date); // we need to add this so we have it initialized before we can actually change the definitions + // dates in japanese + datepickr.prototype.l10n.months.shorthand = [__('Jan'), __('Feb'), __('Mar'), __('Apr'), __('May'), __('Jun'), __('Jul'), __('Aug'), __('Sep'), __('Oct'), __('Nov'), __('Dec')]; + datepickr.prototype.l10n.months.longhand = [__('January'), __('February'), __('March'), __('April'), __('May'), __('June'), __('July'), __('August'), __('September'), __('October'), __('November'), __('December')]; + datepickr.prototype.l10n.weekdays.shorthand = [__('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat'), __('Sun')]; + datepickr.prototype.l10n.weekdays.longhand = [__('Monday'), __('Tuesday'), __('Wednesday'), __('Thursday'), __('Friday'), __('Saturday'), __('Sunday')]; + return true; + } else { + return false; + } +} + +// *** 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 */ diff --git a/www/layout/admin/default/javascript/edit.js b/www/layout/admin/default/javascript/edit.js deleted file mode 100644 index d9729a42..00000000 --- a/www/layout/admin/default/javascript/edit.js +++ /dev/null @@ -1,770 +0,0 @@ -/* 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, $(id).getStyle('position'), 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() -{ - let date = new Date(); - return date.getTime(); -} - -// METHOD: isObject -// PARAMS: possible object -// RETURN: true/false if it is an object or not -function isObject(val) { - if (val === null) { - return false; - } - return ((typeof val === 'function') || (typeof val === 'object')); -} - -// METHOD: exists -// PARAMS: uid -// RETURN: true/false -// DESC : checks if a DOM element actually exists -const exists = (id) => $(id) ? 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: 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: 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: "none", is self change, but returns base.sub -// DESC : resets the sub elements of the base element given -const rel = (base) => base.sub = []; - -// METHOD: rcssel [remove a css from the element] -// PARAMS: element, style sheet to remove -// RETURN: "none", in place because of reference -// DESC : searches and removes style from css array -function rcssel(_element, css) -{ - let css_index = _element.css.indexOf(css); - if (css_index > -1) { - _element.css.splice(css_index, 1); - } -} - -// METHOD: acssel [add css element] -// PARAMS: element, style sheet to add -// RETURN: "none", in place add because of reference -// DESC : adds a new style sheet to the element given -function acssel(_element, css) -{ - let css_index = _element.css.indexOf(css); - if (css_index == -1) { - _element.css.push(css); - } -} - -// METHOD: scssel -// PARAMS: element, style to remove, style to add -// RETURN: "none", in place add because of reference -// DESC : removes one css and adds another -// is a wrapper around rcssel/acssel -function scssel(_element, rcss, acss) -{ - rcssel(_element, rcss); - acssel(_element, acss); -} - -// 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 - let content = []; - // main part line - let 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 -// 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 = '') -{ - let content = []; - let element_select; - let element_option; - let data_list = []; // for sorted output - // set outside select, gets stripped on return if options only is true - element_select = cel('select', 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); - } - // 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) { - let value = data[key]; - console.log('create [%s] options: key: %s, value: %s', name, key, value); - // basic options init - let 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 = '') -{ - let element_option; - let option_selected; - let data_list = []; // for sorted output - // 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) { - let 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); - } - } -} - -// METHOD: initDatepickr -// PARAMS: initial date ID (#) -// RETURN: true on ok, false on failure -// DESC : inits date pickr which translations for dates (week/month) -function initDatepickr(init_date) -{ - if ($(init_date)) { - datepickr('#' + init_date); // we need to add this so we have it initialized before we can actually change the definitions - // dates in japanese - datepickr.prototype.l10n.months.shorthand = [__('Jan'), __('Feb'), __('Mar'), __('Apr'), __('May'), __('Jun'), __('Jul'), __('Aug'), __('Sep'), __('Oct'), __('Nov'), __('Dec')]; - datepickr.prototype.l10n.months.longhand = [__('January'), __('February'), __('March'), __('April'), __('May'), __('June'), __('July'), __('August'), __('September'), __('October'), __('November'), __('December')]; - datepickr.prototype.l10n.weekdays.shorthand = [__('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat'), __('Sun')]; - datepickr.prototype.l10n.weekdays.longhand = [__('Monday'), __('Tuesday'), __('Wednesday'), __('Thursday'), __('Friday'), __('Saturday'), __('Sunday')]; - return true; - } else { - return false; - } -} - -// *** MASTER logout call -// METHOD: loginLogout -// PARAMS: none -// RETURN: none -// DESC : submits basic data for form logout -function loginLogout() -{ - $('login_logout').value = 'Logout'; - $(form_name).submit(); -} - -/* END */ diff --git a/www/layout/admin/default/javascript/edit.js b/www/layout/admin/default/javascript/edit.js new file mode 120000 index 00000000..9844cbbf --- /dev/null +++ b/www/layout/admin/default/javascript/edit.js @@ -0,0 +1 @@ +edit.pt.js \ No newline at end of file diff --git a/www/layout/admin/default/javascript/edit.pt.js b/www/layout/admin/default/javascript/edit.pt.js new file mode 100644 index 00000000..923c618d --- /dev/null +++ b/www/layout/admin/default/javascript/edit.pt.js @@ -0,0 +1,753 @@ +/* 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() +{ + let date = new Date(); + return date.getTime(); +} + +// METHOD: isObject +// PARAMS: possible object +// RETURN: true/false if it is an object or not +function isObject(val) { + if (val === null) { + return false; + } + return ((typeof val === 'function') || (typeof val === 'object')); +} + +// METHOD: exists +// PARAMS: uid +// RETURN: true/false +// DESC : checks if a DOM element actually exists +const exists = (id) => $(id) ? 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: "none", is self change, but returns base.sub +// DESC : resets the sub elements of the base element given +const rel = (base) => base.sub = []; + +// METHOD: rcssel [remove a css from the element] +// PARAMS: element, style sheet to remove +// RETURN: "none", in place because of reference +// DESC : searches and removes style from css array +function rcssel(_element, css) +{ + let css_index = _element.css.indexOf(css); + if (css_index > -1) { + _element.css.splice(css_index, 1); + } +} + +// METHOD: acssel [add css element] +// PARAMS: element, style sheet to add +// RETURN: "none", in place add because of reference +// DESC : adds a new style sheet to the element given +function acssel(_element, css) +{ + let css_index = _element.css.indexOf(css); + if (css_index == -1) { + _element.css.push(css); + } +} + +// METHOD: scssel +// PARAMS: element, style to remove, style to add +// RETURN: "none", in place add because of reference +// DESC : removes one css and adds another +// is a wrapper around rcssel/acssel +function scssel(_element, rcss, acss) +{ + rcssel(_element, rcss); + acssel(_element, acss); +} + +// 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 + let content = []; + // main part line + let 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 +// 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 = '') +{ + let content = []; + let element_select; + let element_option; + let data_list = []; // for sorted output + // set outside select, gets stripped on return if options only is true + element_select = cel('select', 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); + } + // 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) { + let value = data[key]; + console.log('create [%s] options: key: %s, value: %s', name, key, value); + // basic options init + let 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 = '') +{ + let element_option; + let option_selected; + let data_list = []; // for sorted output + // 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) { + let 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); + } + } +} + +// METHOD: initDatepickr +// PARAMS: initial date ID (#) +// RETURN: true on ok, false on failure +// DESC : inits date pickr which translations for dates (week/month) +function initDatepickr(init_date) +{ + if ($(init_date)) { + datepickr('#' + init_date); // we need to add this so we have it initialized before we can actually change the definitions + // dates in japanese + datepickr.prototype.l10n.months.shorthand = [__('Jan'), __('Feb'), __('Mar'), __('Apr'), __('May'), __('Jun'), __('Jul'), __('Aug'), __('Sep'), __('Oct'), __('Nov'), __('Dec')]; + datepickr.prototype.l10n.months.longhand = [__('January'), __('February'), __('March'), __('April'), __('May'), __('June'), __('July'), __('August'), __('September'), __('October'), __('November'), __('December')]; + datepickr.prototype.l10n.weekdays.shorthand = [__('Mon'), __('Tue'), __('Wed'), __('Thu'), __('Fri'), __('Sat'), __('Sun')]; + datepickr.prototype.l10n.weekdays.longhand = [__('Monday'), __('Tuesday'), __('Wednesday'), __('Thursday'), __('Friday'), __('Saturday'), __('Sunday')]; + return true; + } else { + return false; + } +} + +// *** 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 */ diff --git a/www/layout/admin/default/javascript/jquery-min.js b/www/layout/admin/default/javascript/jquery.min.js similarity index 100% rename from www/layout/admin/default/javascript/jquery-min.js rename to www/layout/admin/default/javascript/jquery.min.js diff --git a/www/layout/admin/default/javascript/jquery.test.js b/www/layout/admin/default/javascript/jquery.test.js new file mode 100755 index 00000000..0da77b00 --- /dev/null +++ b/www/layout/admin/default/javascript/jquery.test.js @@ -0,0 +1,16 @@ +/* jquery tests */ + +/* global setCenter ClearCall overlayBoxShow actionIndicatorShow actionIndicatorHide */ +/* eslint no-undef: "error" */ + +$(document).ready(function() { + setCenter('test-div', true, true); + ClearCall(); + overlayBoxShow(); + actionIndicatorShow('testSmarty'); + setTimeout(function() { + console.log('Waiting dummy ...'); + actionIndicatorHide('testSmarty'); + ClearCall(); + }, 2000); +}); diff --git a/www/layout/admin/default/templates/main_body.tpl b/www/layout/admin/default/templates/main_body.tpl index 3860dc98..fe343f3f 100644 --- a/www/layout/admin/default/templates/main_body.tpl +++ b/www/layout/admin/default/templates/main_body.tpl @@ -30,7 +30,7 @@ {if $USE_JQUERY} - + {/if} {if $USE_PROTOTYPE} @@ -96,10 +96,7 @@ + +{* progresss indicator *} +
+{* the action confirm box *} +
+{* The Overlay box *} +