Move all "edit.js" functions into Modules
Create new repository to track these
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
39
ReadMe.md
Normal file
39
ReadMe.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# JavaScript Utils
|
||||
|
||||
Collection of Javascript functions. This will build into a single js file that was formaly known as "edit.js" (edit.jq.js).
|
||||
|
||||
## Build
|
||||
|
||||
Build with the following commands, the output will be stored in "build/js/utilsAll.js" and "build/js/utilsAll.min.js"
|
||||
|
||||
For full not minified version
|
||||
|
||||
```sh
|
||||
npm build utilsAll-build
|
||||
```
|
||||
|
||||
For minified build (but keeps the function names as is)
|
||||
|
||||
```sh
|
||||
npm build utilsAll-min-build
|
||||
```
|
||||
|
||||
Or build both at the same time
|
||||
|
||||
```sh
|
||||
npm build utilsAll-build-all
|
||||
```
|
||||
|
||||
## Develop
|
||||
|
||||
Install the core npm modules
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
Run watch
|
||||
|
||||
```sh
|
||||
npm run utilsAll-develop
|
||||
```
|
||||
2
build/js/general/jquery-3.6.0.min.js
vendored
Normal file
2
build/js/general/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
build/js/general/jquery.min.js
vendored
Symbolic link
1
build/js/general/jquery.min.js
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
jquery-3.6.0.min.js
|
||||
2
build/js/output/.gitignore
vendored
Normal file
2
build/js/output/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
33
build/test.html
Normal file
33
build/test.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>JavaScript Test</title>
|
||||
<script type="text/javascript" src="js/general/jquery.min.js"></script>
|
||||
<!-- <script type="text/javascript" src="js/general/translateTest-ja_JP.UTF-8.js"></script> -->
|
||||
<script type="text/javascript" src="js/output/utilsAll.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>JavaScript tests</h1>
|
||||
<div id="test-div">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script languagae="JavaScript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('MAIN PAGE LOADED');
|
||||
let el = document.getElementById('test-div');
|
||||
if (el === null) {
|
||||
throw new Error("element test-div not found");
|
||||
}
|
||||
let bytes = 1021152;
|
||||
//
|
||||
el.html = '';
|
||||
el.html += '<div>randomIdF: ' + randomIdF() + '</div>';
|
||||
el.html += '<div>getWindowSize: ' + getWindowSize() + '</div>';
|
||||
el.html += '<div>formatBytes: ' + formatBytes(bytes) + '</div>';
|
||||
el.html += '<div>formatBytesLong: ' + formatBytesLong(bytes) + '</div>';
|
||||
|
||||
// console.log('TR: %s', l10n.__('Original'));
|
||||
// console.log('TR: %s', l10n.__('Not exists'));
|
||||
});
|
||||
</script>
|
||||
59
eslint.config.mjs
Normal file
59
eslint.config.mjs
Normal file
@@ -0,0 +1,59 @@
|
||||
import globals from 'globals';
|
||||
import pluginJs from '@eslint/js';
|
||||
|
||||
/*
|
||||
module.exports = {
|
||||
// in globals block
|
||||
'extends': 'eslint:recommended',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 6
|
||||
},
|
||||
// rules copied
|
||||
};
|
||||
*/
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.jquery
|
||||
}
|
||||
}},
|
||||
pluginJs.configs.recommended,
|
||||
{
|
||||
'rules': {
|
||||
'indent': [
|
||||
'error',
|
||||
'tab',
|
||||
{
|
||||
'SwitchCase': 1
|
||||
}
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'unix'
|
||||
],
|
||||
// 'quotes': [
|
||||
// 'error',
|
||||
// 'single'
|
||||
// ],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'no-console': 'off',
|
||||
'no-unused-vars': [
|
||||
'error', {
|
||||
'vars': 'all',
|
||||
'args': 'after-used',
|
||||
'ignoreRestSiblings': false
|
||||
}
|
||||
],
|
||||
// Requires eslint >= v8.14.0
|
||||
'no-constant-binary-expression': 'error'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// __END__
|
||||
17
jsconfig.json
Normal file
17
jsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
// https://www.typescriptlang.org/tsconfig/#compilerOptions
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
"checkJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
||||
1554
package-lock.json
generated
Normal file
1554
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "javascript-utils",
|
||||
"version": "1.0.0",
|
||||
"main": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"utilsAll-min-build": "node_modules/esbuild/bin/esbuild utilsAll.min=src/utilsAll.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false --minify-whitespace --minify-syntax --sourcemap",
|
||||
"utilsAll-build": "node_modules/esbuild/bin/esbuild utilsAll=src/utilsAll.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false",
|
||||
"utilsAll-build-all": "npm run utilsAll-min-build && npm run utilsAll-build",
|
||||
"utilsAll-develop": "node_modules/esbuild/bin/esbuild utilsAll=src/utilsAll.mjs --outdir=build/js/output/ --format=esm --bundle --charset=utf8 --tree-shaking=false --watch"
|
||||
},
|
||||
"author": "Clemens Schwaighofer",
|
||||
"license": "",
|
||||
"description": "Javascript Utils Collection",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.20.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.20.1",
|
||||
"globals": "^15.15.0"
|
||||
}
|
||||
}
|
||||
438
src/utils/ActionBox.mjs
Normal file
438
src/utils/ActionBox.mjs
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
Description: Action Box handling
|
||||
Date: 2025/3/7
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { ActionBox };
|
||||
import { keyInObject, getObjectCount } from './JavaScriptHelpers.mjs';
|
||||
import { exists } from './DomHelpers.mjs';
|
||||
import { setCenter, getWindowSize } from './ResizingAndMove.mjs';
|
||||
|
||||
class ActionBox {
|
||||
|
||||
// open overlay boxes counter for z-index
|
||||
zIndex = {
|
||||
base: 100,
|
||||
max: 110,
|
||||
indicator: 0,
|
||||
boxes: {},
|
||||
active: [],
|
||||
top: ''
|
||||
};
|
||||
// general action box storage
|
||||
action_box_storage = {};
|
||||
// set to 10 min (*60 for seconds, *1000 for microseconds)
|
||||
action_box_cache_timeout = 10 * 60 * 1000;
|
||||
|
||||
hec;
|
||||
l10n;
|
||||
|
||||
/**
|
||||
* action box creator
|
||||
* @param {Object} hec HtmlElementCreator
|
||||
* @param {Object} l10n l10nTranslation
|
||||
*/
|
||||
constructor(hec, l10n)
|
||||
{
|
||||
this.hec = hec;
|
||||
this.l10n = l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an action box
|
||||
* @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new
|
||||
* @param {string} [content=''] content to add to the box
|
||||
* @param {array} [action_box_css=[]] additional css elements for the action box
|
||||
* @param {number} [override=0] override size adjust
|
||||
* @param {number} [content_override=0] override content size adjust
|
||||
*/
|
||||
showFillActionBox(target_id = 'actionBox', content = '', action_box_css = [], override = 0, content_override = 0)
|
||||
{
|
||||
// fill content
|
||||
this.fillActionBox(target_id, content, action_box_css);
|
||||
// show the box
|
||||
this.showActionBox(target_id, override, content_override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill action box with content, create it if it does not existgs
|
||||
* @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new
|
||||
* @param {string} [content=''] content to add to the box
|
||||
* @param {array} [action_box_css=[]] additional css elements for the action box
|
||||
*/
|
||||
fillActionBox(target_id = 'actionBox', content = '', action_box_css = [])
|
||||
{
|
||||
// show action box, calc height + center
|
||||
if (!exists(target_id)) {
|
||||
// add at the bottom
|
||||
$('#mainContainer').after(
|
||||
this.hec.phfo(this.hec.cel('div', target_id, '', ['actionBoxElement', 'hide'].concat(action_box_css)))
|
||||
);
|
||||
}
|
||||
// add the info box
|
||||
$('#' + target_id).html(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the size of the action box
|
||||
* @param {string} [target_id='actionBox'] which actionBox to work on
|
||||
* @param {number} [override=0] override size adjust
|
||||
* @param {number} [content_override=0] override content size adjust
|
||||
*/
|
||||
adjustActionBox(target_id = 'actionBox', override = 0, content_override = 0)
|
||||
{
|
||||
// adjust box size
|
||||
this.adjustActionBoxHeight(target_id, override, content_override);
|
||||
// center the alert box
|
||||
setCenter(target_id, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* hide any open action boxes and hide overlay
|
||||
*/
|
||||
hideAllActionBoxes()
|
||||
{
|
||||
// hide all action boxes that might exist
|
||||
$('#actionBox, div[id^="actionBox-"].actionBoxElement').hide();
|
||||
// hideOverlayBoxLayers();
|
||||
$('#overlayBox').hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* hide action box, but do not clear content
|
||||
* DEPRECATED
|
||||
* @param {string} [target_id='actionBox']
|
||||
*/
|
||||
hideActionBox(target_id = 'actionBox')
|
||||
{
|
||||
this.closeActionBoxFloat(target_id, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just show and adjust the box
|
||||
* DEPRECAED
|
||||
* @param {string} [target_id='actionBox'] which actionBox to work on
|
||||
* @param {number} [override=0] override size adjust
|
||||
* @param {number} [content_override=0] override content size adjust
|
||||
* @param {Boolean} [hide_all=false] if set to true, hide all other action boxes
|
||||
*/
|
||||
showActionBox(target_id = 'actionBox', override = 0, content_override = 0, hide_all = true)
|
||||
{
|
||||
this.showActionBoxFloat(target_id, override, content_override, hide_all);
|
||||
}
|
||||
|
||||
/**
|
||||
* close an action box with default clear content
|
||||
* for just hide use hideActionBox
|
||||
* DEPRECATED
|
||||
* @param {String} [target_id='actionBox'] which action box to close, default is set
|
||||
* @param {Boolean} [clean=true] if set to false will not remove html content, just hide
|
||||
*/
|
||||
closeActionBox(target_id = 'actionBox', clean = true)
|
||||
{
|
||||
// set the target/content ids
|
||||
this.closeActionBoxFloat(target_id, clean);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: better stacked action box: OPEN
|
||||
* @param {string} [target_id='actionBox'] which actionBox to work on
|
||||
* @param {number} [override=0] override size adjust
|
||||
* @param {number} [content_override=0] override content size adjust
|
||||
* @param {boolean} [hide_all=false] if set to true, hide all other action boxes
|
||||
*/
|
||||
showActionBoxFloat(target_id = 'actionBox', override = 0, content_override = 0, hide_all = false)
|
||||
{
|
||||
if (hide_all === true) {
|
||||
// hide all action boxes if they are currently open
|
||||
this.hideAllActionBoxes();
|
||||
}
|
||||
// if no box, created if
|
||||
if (!exists('overlayBox')) {
|
||||
$('body').prepend(this.hec.phfo(this.hec.cel('div', 'overlayBox', '', ['overlayBoxElement'])));
|
||||
$('#overlayBox').css('zIndex', this.zIndex.base);
|
||||
}
|
||||
// adjust zIndex so its above all other and set action box zindex +1
|
||||
$('#overlayBox').show();
|
||||
if (!keyInObject(target_id, this.zIndex.boxes)) {
|
||||
this.zIndex.boxes[target_id] = this.zIndex.max;
|
||||
// increase by ten
|
||||
this.zIndex.max += 10;
|
||||
} else if (this.zIndex.boxes[target_id] + 10 < this.zIndex.max) {
|
||||
// see if this is the highest level, if not move up and write no max zIndex
|
||||
// move it up to be the new top and move the others down
|
||||
// [loop, order by value]
|
||||
// current hack, increase max
|
||||
this.zIndex.boxes[target_id] = this.zIndex.max;
|
||||
this.zIndex.max += 10;
|
||||
}
|
||||
// make sure the overlayBox is one level below this
|
||||
// unless there is an active "indicator" index
|
||||
if (!this.zIndex.indicator) {
|
||||
$('#overlayBox').css('zIndex', this.zIndex.boxes[target_id] - 1);
|
||||
}
|
||||
$('#' + target_id).css('zIndex', this.zIndex.boxes[target_id]).show();
|
||||
// set target to this new level
|
||||
// @ts-ignore
|
||||
if (this.zIndex.active.indexOf(target_id) == -1) {
|
||||
// @ts-ignore
|
||||
this.zIndex.active.push(target_id);
|
||||
}
|
||||
this.zIndex.top = target_id;
|
||||
// adjust size
|
||||
this.adjustActionBox(target_id, override, content_override);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: better stacked action box: CLOSE
|
||||
* @param {String} [target_id='actionBox'] which action box to close, default is set
|
||||
* @param {Boolean} [clean=true] if set to false will not remove html content, just hide
|
||||
*/
|
||||
closeActionBoxFloat(target_id = 'actionBox', clean = true)
|
||||
{
|
||||
// do nothing if this does not exist
|
||||
if (!exists(target_id)) {
|
||||
return;
|
||||
}
|
||||
// clear storage object
|
||||
if (
|
||||
keyInObject(target_id, this.action_box_storage) && clean === true
|
||||
) {
|
||||
this.action_box_storage[target_id] = {};
|
||||
}
|
||||
if (clean === true) {
|
||||
$('#' + target_id).html('');
|
||||
}
|
||||
$('#' + target_id).hide();
|
||||
// remove from active list
|
||||
// @ts-ignore
|
||||
let idx = this.zIndex.active.indexOf(target_id);
|
||||
this.zIndex.active.splice(idx, 1);
|
||||
// do we have any visible action boxes.
|
||||
// find the highest zIndex and set overlayBox to this -1
|
||||
// @ts-ignore
|
||||
let visible_zIndexes = $('#actionBox:visible, div[id^="actionBox-"].actionBoxElement:visible').map((i, el) => ({
|
||||
id: el.id,
|
||||
zIndex: $('#' + el.id).css('zIndex')
|
||||
})).get();
|
||||
if (visible_zIndexes.length > 0) {
|
||||
let max_zIndex = 0;
|
||||
let max_el_id = '';
|
||||
for (let zIndex_el of visible_zIndexes) {
|
||||
if (parseInt(zIndex_el.zIndex) > max_zIndex) {
|
||||
max_zIndex = parseInt(zIndex_el.zIndex);
|
||||
max_el_id = zIndex_el.id;
|
||||
}
|
||||
}
|
||||
$('#overlayBox').css('zIndex', max_zIndex - 1);
|
||||
this.zIndex.top = max_el_id;
|
||||
} else {
|
||||
$('#overlayBox').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new action box and fill it with basic elements
|
||||
* @param {String} [target_id='actionBox']
|
||||
* @param {String} [title='']
|
||||
* @param {Object} [contents={}]
|
||||
* @param {Object} [headers={}]
|
||||
* @param {Boolean} [show_close=true]
|
||||
* @param {Object} [settings={}] Optional settings, eg style sheets
|
||||
*/
|
||||
createActionBox(
|
||||
target_id = 'actionBox',
|
||||
title = '',
|
||||
contents = {},
|
||||
headers = {},
|
||||
settings = {},
|
||||
show_close = true
|
||||
) {
|
||||
if (!keyInObject(target_id, this.action_box_storage)) {
|
||||
this.action_box_storage[target_id] = {};
|
||||
}
|
||||
// settings can have the following
|
||||
// : header_css:[]
|
||||
// : action_box_css:[]
|
||||
let header_css = [];
|
||||
if (keyInObject('header_css', settings)) {
|
||||
header_css = settings.header_css;
|
||||
}
|
||||
let action_box_css = [];
|
||||
if (keyInObject('action_box_css', settings)) {
|
||||
action_box_css = settings.action_box_css;
|
||||
}
|
||||
let elements = [];
|
||||
// add title + close button to actionBox
|
||||
elements.push(this.hec.phfo(
|
||||
this.hec.aelx(this.hec.cel('div', target_id + '_title', '', ['actionBoxTitle', 'flx-spbt'].concat(header_css)),
|
||||
...show_close === true ? [
|
||||
// title
|
||||
this.hec.cel('div', '', title, ['fs-b', 'w-80']),
|
||||
// close button
|
||||
this.hec.aelx(this.hec.cel('div', target_id + '_title_close_button', '', ['w-20', 'tar']),
|
||||
this.hec.cel('input', target_id + '_title_close', '', ['button-close', 'fs-s'],
|
||||
{
|
||||
type: 'button',
|
||||
value: this.l10n.__('Close'),
|
||||
OnClick: 'closeActionBox(\'' + target_id + '\', false);'
|
||||
}
|
||||
)
|
||||
)
|
||||
] : [
|
||||
this.hec.cel('div', '', title, ['fs-b', 'w-100'])
|
||||
]
|
||||
)
|
||||
));
|
||||
// if we have header content, add that here
|
||||
if (getObjectCount(headers) > 0) {
|
||||
// if the element has an entry called "raw_string" then this does not need to be converted
|
||||
if (keyInObject('raw_string', headers)) {
|
||||
elements.push(headers.raw_string);
|
||||
} else {
|
||||
elements.push(this.hec.phfo(headers));
|
||||
}
|
||||
}
|
||||
// main content part (this should NOT be empty), if empty, add empty _content block
|
||||
if (getObjectCount(contents) > 0) {
|
||||
// if the element has an entry called "raw_string" then this does not need to be converted
|
||||
if (keyInObject('raw_string', contents)) {
|
||||
elements.push(contents.raw_string);
|
||||
} else {
|
||||
elements.push(this.hec.phfo(contents));
|
||||
}
|
||||
} else {
|
||||
elements.push(this.hec.phfo(this.hec.cel('div', target_id + '_content', '', [])));
|
||||
}
|
||||
// footer clear call
|
||||
elements.push(this.hec.phfo(
|
||||
this.hec.aelx(this.hec.cel('div', target_id + '_footer', '', ['pd-5', 'flx-spbt']),
|
||||
...show_close === true ? [
|
||||
// dummy spacer
|
||||
this.hec.cel('div', '', '', ['fs-b', 'w-80']),
|
||||
// close button
|
||||
this.hec.aelx(this.hec.cel('div', target_id + '_footer_close_button', '', ['tar', 'w-20']),
|
||||
this.hec.cel('input', target_id + '_footer_close', '', ['button-close', 'fs-s'],
|
||||
{
|
||||
type: 'button',
|
||||
value: this.l10n.__('Close'),
|
||||
OnClick: 'closeActionBox(\'' + target_id + '\', false);'
|
||||
}
|
||||
)
|
||||
)
|
||||
] : [
|
||||
this.hec.cel('div', '', '', ['fs-b', 'w-100'])
|
||||
]
|
||||
)
|
||||
));
|
||||
elements.push(this.hec.phfo(this.hec.cel('input', target_id + '-cache_time', '', [], {
|
||||
type: 'hidden',
|
||||
value: Date.now()
|
||||
})));
|
||||
this.fillActionBox(target_id, elements.join(''), action_box_css);
|
||||
}
|
||||
|
||||
/**
|
||||
* adjusts the action box height based on content and window height of browser
|
||||
* TODO: border on outside/and other margin things need to be added in overall adjustment
|
||||
* @param {String} [target_id='actionBox'] target id, if not set, fall back to default
|
||||
* @param {Number} [override=0] override value to add to the actionBox height
|
||||
* @param {Number} [content_override=0] override the value from _content block if it exists
|
||||
*/
|
||||
adjustActionBoxHeight(target_id = 'actionBox', override = 0, content_override = 0)
|
||||
{
|
||||
var new_height = 0;
|
||||
var dim = {};
|
||||
var abc_dim = {};
|
||||
var content_id = '';
|
||||
// make sure it is a number
|
||||
if (isNaN(override)) {
|
||||
override = 0;
|
||||
}
|
||||
if (isNaN(content_override)) {
|
||||
content_override = 0;
|
||||
}
|
||||
// set the target/content ids
|
||||
switch (target_id) {
|
||||
case 'actionBox':
|
||||
content_id = 'action_box';
|
||||
break;
|
||||
case 'actionBoxSub':
|
||||
content_id ='action_box_sub';
|
||||
break;
|
||||
default:
|
||||
content_id = target_id;
|
||||
break;
|
||||
}
|
||||
// first remove any height, left, top style entris from target and content blocks
|
||||
// @ts-ignore
|
||||
$.each([target_id, content_id + '_content'], function(i, v) {
|
||||
$('#' + v).css({
|
||||
'height': '',
|
||||
'width': ''
|
||||
});
|
||||
});
|
||||
if (exists(content_id + '_title')) {
|
||||
dim.height = $('#' + content_id + '_title').outerHeight();
|
||||
console.log('Target: %s, Action box Title: %s', target_id, dim.height);
|
||||
new_height += dim.height ?? 0;
|
||||
}
|
||||
if (exists(content_id + '_header')) {
|
||||
dim.height = $('#' + content_id + '_header').outerHeight();
|
||||
console.log('Target: %s, Action box Header: %s', target_id, dim.height);
|
||||
new_height += dim.height ?? 0;
|
||||
}
|
||||
if (exists(content_id + '_content')) {
|
||||
if (content_override > 0) {
|
||||
console.log('Target: %s, Action box Content Override: %s', target_id, content_override);
|
||||
new_height += content_override;
|
||||
} else {
|
||||
abc_dim.height = $('#' + content_id + '_content').outerHeight();
|
||||
console.log('Target: %s, Action box Content: %s', target_id, abc_dim.height);
|
||||
new_height += abc_dim.height ?? 0;
|
||||
}
|
||||
}
|
||||
// always there sets
|
||||
if (exists(content_id + '_footer')) {
|
||||
dim.height = $('#' + content_id + '_footer').outerHeight();
|
||||
console.log('Target: %s, Action box Footer: %s', target_id, dim.height);
|
||||
new_height += dim.height ?? 0;
|
||||
}
|
||||
// get difference for the rest from outer box
|
||||
// console.log('Target: %s, Action box outer: %s, Content: %s, New: %s', target_id, $('#' + target_id).outerHeight(), $('#' + content_id + '_content').outerHeight(), new_height);
|
||||
// new_height += ($('#' + target_id).outerHeight() - new_height) + override;
|
||||
new_height += override;
|
||||
// get border width top-bottom from action Box, we need to remove this from the final height
|
||||
// console.log('Target: %s, Border top: %s', target_id, $('#' + target_id).css('border-top-width'));
|
||||
// get window size and check if content is bigger
|
||||
var viewport = getWindowSize();
|
||||
if (new_height >= viewport.height) {
|
||||
// resize the action box content and set overflow [of-s-y]
|
||||
if (exists(content_id + '_content')) {
|
||||
if (!$('#' + content_id + '_content').hasClass('of-s-y')) {
|
||||
$('#' + content_id + '_content').addClass('of-s-y');
|
||||
}
|
||||
}
|
||||
console.log('Target: %s, Viewport: %s, ActionBox (NH): %s, ABcontent: %s, ABouter: %s', target_id, viewport.height, new_height, abc_dim.height, $('#' + target_id).outerHeight());
|
||||
// the height off window - all - action box gives new action box height
|
||||
var m_height = viewport.height - (new_height - (abc_dim.height ?? 0));
|
||||
console.log('Target: %s, New ABcontent: %s', target_id, m_height);
|
||||
$('#' + content_id + '_content').css('height', m_height + 'px');
|
||||
new_height = new_height - (abc_dim.height ?? 0) + m_height;
|
||||
console.log('Target: %s, New Hight: %s', target_id, new_height);
|
||||
} else {
|
||||
// if size ok, check if overflow scoll is set, remove it
|
||||
if (exists(content_id + '_content')) {
|
||||
if ($('#' + content_id + '_content').hasClass('of-s-y')) {
|
||||
$('#' + content_id + '_content').removeClass('of-s-y');
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('Target: %s, Action Box new height: %s px (override %s px, content override %s px), window height: %s px, Visible Height: %s px', target_id, new_height, override, content_override, viewport.height, $('#' + content_id).outerHeight());
|
||||
// adjust height
|
||||
$('#' + target_id).css('height', new_height + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
// __EMD__
|
||||
305
src/utils/ActionIndicatorOverlayBox.mjs
Normal file
305
src/utils/ActionIndicatorOverlayBox.mjs
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
Description: Action Indicator and Overlay
|
||||
Date: 2025/2/7
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
import { setCenter } from './ResizingAndMove.mjs';
|
||||
export {
|
||||
ActionIndicatorOverlayBox,
|
||||
actionIndicator, actionIndicatorShow, actionIndicatorHide, overlayBoxShow,
|
||||
overlayBoxHide, setOverlayBox, hideOverlayBox, ClearCall
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* OLD action indicator and overlay boxes calls
|
||||
* DO NOT USE
|
||||
* actionIndicator -> showActionIndicator
|
||||
* actionIndicator -> hideActionIndicator
|
||||
* actionIndicatorShow -> showActionIndicator
|
||||
* actionIndicatorHide -> hideActionIndicator
|
||||
* overlayBoxShow -> showOverlayBoxLayers
|
||||
* overlayBoxHide -> hideOverlayBoxLayers
|
||||
* setOverlayBox -> showOverlayBoxLayers
|
||||
* hideOverlayBox -> hideOverlayBoxLayers
|
||||
* ClearCall -> clearCallActionBox
|
||||
* ***********************************************************/
|
||||
|
||||
/**
|
||||
* show or hide the "do" overlay
|
||||
* @param {String} loc location name for action indicator
|
||||
* default empty. for console.log
|
||||
* @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block
|
||||
* @deprecated showActionIndicator, hideActionIndicator
|
||||
*/
|
||||
function actionIndicator(loc, overlay = true)
|
||||
{
|
||||
if ($('#indicator').is(':visible')) {
|
||||
this.actionIndicatorHide(loc, overlay);
|
||||
} else {
|
||||
this.actionIndicatorShow(loc, overlay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* explicit show for action Indicator
|
||||
* instead of automatically show or hide, do on command show
|
||||
* @param {String} loc location name for action indicator
|
||||
* default empty. for console.log
|
||||
* @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block
|
||||
* @deprecated showActionIndicator, hideActionIndicator
|
||||
*/
|
||||
function actionIndicatorShow(loc, overlay = true)
|
||||
{
|
||||
// console.log('{Indicator}: SHOW [%s]', loc);
|
||||
if (!$('#indicator').is(':visible')) {
|
||||
if (!$('#indicator').hasClass('progress')) {
|
||||
$('#indicator').addClass('progress');
|
||||
}
|
||||
setCenter('indicator', true, true);
|
||||
$('#indicator').show();
|
||||
}
|
||||
if (overlay === true) {
|
||||
this.overlayBoxShow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* explicit hide for action Indicator
|
||||
* instead of automatically show or hide, do on command hide
|
||||
* @param {String} loc location name for action indicator
|
||||
* default empty. for console.log
|
||||
* @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block
|
||||
* @deprecated hideActionIndicator
|
||||
*/
|
||||
function actionIndicatorHide(loc, overlay = true)
|
||||
{
|
||||
// console.log('{Indicator}: HIDE [%s]', loc);
|
||||
$('#indicator').hide();
|
||||
if (overlay === true) {
|
||||
overlayBoxHide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shows the overlay box or if already visible, bumps the zIndex to 100
|
||||
* @deprecated showOverlayBoxLayers
|
||||
*/
|
||||
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();
|
||||
$('#overlayBox').css('zIndex', '98');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hides the overlay box or if zIndex is 100 bumps it down to previous level
|
||||
* @deprecated hideOverlayBoxLayers
|
||||
*/
|
||||
function overlayBoxHide()
|
||||
{
|
||||
// if the overlay box z-index is 100, do no hide, but set to 98
|
||||
if (parseInt($('#overlayBox').css('zIndex')) >= 100) {
|
||||
$('#overlayBox').css('zIndex', '98');
|
||||
} else {
|
||||
$('#overlayBox').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* position the overlay block box and shows it
|
||||
* @deprecated showOverlayBoxLayers
|
||||
*/
|
||||
function setOverlayBox()
|
||||
{
|
||||
if (!$('#overlayBox').is(':visible')) {
|
||||
$('#overlayBox').show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* opposite of set, always hides overlay box
|
||||
* @deprecated hideOverlayBoxLayers
|
||||
*/
|
||||
function hideOverlayBox()
|
||||
{
|
||||
if ($('#overlayBox').is(':visible')) {
|
||||
$('#overlayBox').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the abort call, clears the action box and hides it and the overlay box
|
||||
* @deprecated clearCallActionBox
|
||||
*/
|
||||
function ClearCall()
|
||||
{
|
||||
$('#actionBox').html('');
|
||||
$('#actionBox').hide();
|
||||
$('#overlayBox').hide();
|
||||
}
|
||||
|
||||
class ActionIndicatorOverlayBox {
|
||||
|
||||
// open overlay boxes counter for z-index
|
||||
#GL_OB_S = 100;
|
||||
#GL_OB_BASE = 100;
|
||||
|
||||
/**
|
||||
* show action indicator
|
||||
* - checks if not existing and add
|
||||
* - only shows if not visible (else ignore)
|
||||
* - overlaybox check is called and shown on a fixzed
|
||||
* zIndex of 1000
|
||||
* - indicator is page centered
|
||||
* @param {String} loc ID string, only used for console log
|
||||
*/
|
||||
showActionIndicator(loc) // eslint-disable-line no-unused-vars
|
||||
{
|
||||
// console.log('{Indicator}: SHOW [%s]', loc);
|
||||
// check if indicator element exists
|
||||
if ($('#indicator').length == 0) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'progress hide';
|
||||
el.id = 'indicator';
|
||||
$('body').append(el);
|
||||
} else if (!$('#indicator').hasClass('progress')) {
|
||||
// if I add a class it will not be hidden anymore
|
||||
// hide it
|
||||
$('#indicator').addClass('progress').hide();
|
||||
}
|
||||
// indicator not visible
|
||||
if (!$('#indicator').is(':visible')) {
|
||||
// check if overlay box element exits
|
||||
this.checkOverlayExists();
|
||||
// if not visible show
|
||||
if (!$('#overlayBox').is(':visible')) {
|
||||
$('#overlayBox').show();
|
||||
}
|
||||
// always set to 1000 zIndex to be top
|
||||
$('#overlayBox').css('zIndex', 1000);
|
||||
// show indicator
|
||||
$('#indicator').show();
|
||||
// center it
|
||||
setCenter('indicator', true, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hide action indicator, if it is visiable
|
||||
* If the global variable GL_OB_S is > GL_OB_BASE then
|
||||
* the overlayBox is not hidden but the zIndex
|
||||
* is set to this value
|
||||
* @param {String} loc ID string, only used for console log
|
||||
*/
|
||||
hideActionIndicator(loc) // eslint-disable-line no-unused-vars
|
||||
{
|
||||
// console.log('{Indicator}: HIDE [%s]', loc);
|
||||
// check if indicator is visible
|
||||
if ($('#indicator').is(':visible')) {
|
||||
// hide indicator
|
||||
$('#indicator').hide();
|
||||
// if global overlay box count is > 0
|
||||
// then set it to this level and keep
|
||||
if (this.#GL_OB_S > this.#GL_OB_BASE) {
|
||||
$('#overlayBox').css('zIndex', this.#GL_OB_S);
|
||||
} else {
|
||||
// else hide overlay box and set zIndex to 0
|
||||
$('#overlayBox').hide();
|
||||
$('#overlayBox').css('zIndex', this.#GL_OB_BASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if overlayBox exists, if not it is
|
||||
* added as hidden item at the body end
|
||||
*/
|
||||
checkOverlayExists()
|
||||
{
|
||||
// check if overlay box exists, if not create it
|
||||
if ($('#overlayBox').length == 0) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'overlayBoxElement hide';
|
||||
el.id = 'overlayBox';
|
||||
$('body').append(el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show overlay box
|
||||
* if not visible show and set zIndex to 10 (GL_OB_BASE)
|
||||
* if visible, add +1 to the GL_OB_S variable and
|
||||
* up zIndex by this value
|
||||
*/
|
||||
showOverlayBoxLayers(el_id)
|
||||
{
|
||||
// console.log('SHOW overlaybox: %s', GL_OB_S);
|
||||
// if overlay box is not visible show and set zIndex to 0
|
||||
if (!$('#overlayBox').is(':visible')) {
|
||||
$('#overlayBox').show();
|
||||
$('#overlayBox').css('zIndex', this.#GL_OB_BASE);
|
||||
// also set start variable to 0
|
||||
this.#GL_OB_S = this.#GL_OB_BASE;
|
||||
}
|
||||
// up the overlay box counter by 1
|
||||
this.#GL_OB_S ++;
|
||||
// set zIndex
|
||||
$('#overlayBox').css('zIndex', this.#GL_OB_S);
|
||||
// if element given raise zIndex and show
|
||||
if (el_id) {
|
||||
if ($('#' + el_id).length > 0) {
|
||||
$('#' + el_id).css('zIndex', this.#GL_OB_S + 1);
|
||||
$('#' + el_id).show();
|
||||
}
|
||||
}
|
||||
// console.log('SHOW overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));
|
||||
}
|
||||
|
||||
/**
|
||||
* hide overlay box
|
||||
* lower GL_OB_S value by -1
|
||||
* if we are 10 (GL_OB_BASE) or below hide the overlayIndex
|
||||
* and set zIndex and GL_OB_S to 0
|
||||
* else just set zIndex to the new GL_OB_S value
|
||||
* @param {String} el_id Target to hide layer
|
||||
*/
|
||||
hideOverlayBoxLayers(el_id='')
|
||||
{
|
||||
// console.log('HIDE overlaybox: %s', GL_OB_S);
|
||||
// remove on layer
|
||||
this.#GL_OB_S --;
|
||||
// if 0 or lower (overflow) hide it and
|
||||
// set zIndex to 0
|
||||
if (this.#GL_OB_S <= this.#GL_OB_BASE) {
|
||||
this.#GL_OB_S = this.#GL_OB_BASE;
|
||||
$('#overlayBox').hide();
|
||||
$('#overlayBox').css('zIndex', this.#GL_OB_BASE);
|
||||
} else {
|
||||
// if OB_S > 0 then set new zIndex
|
||||
$('#overlayBox').css('zIndex', this.#GL_OB_S);
|
||||
}
|
||||
if (el_id) {
|
||||
$('#' + el_id).hide();
|
||||
$('#' + el_id).css('zIndex', 0);
|
||||
}
|
||||
// console.log('HIDE overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));
|
||||
}
|
||||
|
||||
/**
|
||||
* only for single action box
|
||||
*/
|
||||
clearCallActionBox()
|
||||
{
|
||||
$('#actionBox').html('');
|
||||
$('#actionBox').hide();
|
||||
this.hideOverlayBoxLayers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// __END__
|
||||
19
src/utils/DateTimeHelpers.mjs
Normal file
19
src/utils/DateTimeHelpers.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Description: Date Time functions
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { getTimestamp };
|
||||
|
||||
/**
|
||||
* returns current timestamp (unix timestamp)
|
||||
* @return {Number} timestamp (in milliseconds)
|
||||
*/
|
||||
function getTimestamp()
|
||||
{
|
||||
var date = new Date();
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
// __END__
|
||||
75
src/utils/DomHelpers.mjs
Normal file
75
src/utils/DomHelpers.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Description: DOM Helpers
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { loadEl, pop, expandTA, exists };
|
||||
|
||||
/**
|
||||
* Gets html element or throws an error
|
||||
* @param {string} el_id Element ID to get
|
||||
* @returns {HTMLElement}
|
||||
* @throws Error
|
||||
*/
|
||||
function loadEl(el_id)
|
||||
{
|
||||
let el = document.getElementById(el_id);
|
||||
if (el === null) {
|
||||
throw new Error('Cannot find: ' + el_id);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* opens a popup window with winName and given features (string)
|
||||
* @param {String} theURL the url
|
||||
* @param {String} winName window name
|
||||
* @param {Object} features popup features
|
||||
*/
|
||||
function pop(theURL, winName, features)
|
||||
{
|
||||
let __winName = window.open(theURL, winName, features);
|
||||
if (__winName == null) {
|
||||
return;
|
||||
}
|
||||
__winName.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* automatically resize a text area based on the amount of lines in it
|
||||
* @param {string} ta_id element id
|
||||
*/
|
||||
function expandTA(ta_id)
|
||||
{
|
||||
let ta = this.loadEl(ta_id);
|
||||
if (ta instanceof HTMLElement && ta.getAttribute('type') !== "textarea") {
|
||||
throw new Error("Element is not a textarea: " + ta_id);
|
||||
}
|
||||
let maxChars = parseInt(ta.getAttribute('cols') ?? "0");
|
||||
let ta_value = ta.getAttribute('value');
|
||||
let theRows = [];
|
||||
if (ta_value != null) {
|
||||
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.setAttribute('row', (numNewRows + theRows.length).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if a DOM element actually exists
|
||||
* @param {String} id Element id to check for
|
||||
* @return {Boolean} true if element exists, false on failure
|
||||
*/
|
||||
function exists(id)
|
||||
{
|
||||
return $('#' + id).length > 0 ? true : false;
|
||||
}
|
||||
|
||||
// __END__
|
||||
80
src/utils/FormatBytes.mjs
Normal file
80
src/utils/FormatBytes.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Description: Byte string formatting
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { formatBytes, formatBytesLong, stringByteFormat };
|
||||
|
||||
/**
|
||||
* converts a int number into bytes with prefix in two decimals precision
|
||||
* currently precision is fixed, if dynamic needs check for max/min precision
|
||||
* @param {Number} bytes bytes in int
|
||||
* @return {String} string in GB/MB/KB
|
||||
*/
|
||||
function formatBytes(bytes)
|
||||
{
|
||||
var i = -1;
|
||||
do {
|
||||
bytes = bytes / 1024;
|
||||
i++;
|
||||
} while (bytes > 99);
|
||||
return (
|
||||
Math.round(bytes * Math.pow(10, 2)) / Math.pow(10, 2)
|
||||
) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
|
||||
}
|
||||
|
||||
/**
|
||||
* like formatBytes, but returns bytes for <1KB and not 0.n KB
|
||||
* @param {Number} bytes bytes in int
|
||||
* @return {String} string in GB/MB/KB
|
||||
*/
|
||||
function formatBytesLong(bytes)
|
||||
{
|
||||
if (isNaN(bytes)) {
|
||||
return bytes.toString();
|
||||
}
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
var sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return (
|
||||
(
|
||||
bytes /
|
||||
Math.pow(1024, i)
|
||||
).toFixed(2)
|
||||
// * 1 + ' ' + sizes[i]
|
||||
+ ' ' + sizes[i]
|
||||
).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string with B/K/M/etc into a byte number
|
||||
* @param {String|Number|Object} bytes Any string with B/K/M/etc
|
||||
* @return {String|Number} A byte number, or original string as is
|
||||
*/
|
||||
function stringByteFormat(bytes)
|
||||
{
|
||||
// if anything not string return
|
||||
if (!(typeof bytes === 'string' || bytes instanceof String)) {
|
||||
return bytes.toString();
|
||||
}
|
||||
// for pow exponent list
|
||||
let valid_units = 'bkmgtpezy';
|
||||
// valid string that can be converted
|
||||
let regex = /([\d.,]*)\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i;
|
||||
let matches = bytes.match(regex);
|
||||
// if nothing found, return original input
|
||||
if (matches !== null) {
|
||||
// remove all non valid entries outside numbers and .
|
||||
// convert to float number
|
||||
let m1 = parseFloat(matches[1].replace(/[^0-9.]/,''));
|
||||
// only get the FIRST letter from the size, convert it to lower case
|
||||
let m2 = matches[2].replace(/[^bkmgtpezy]/i, '').charAt(0).toLowerCase();
|
||||
if (m2) {
|
||||
// use the position in the valid unit list to do the math conversion
|
||||
bytes = m1 * Math.pow(1024, valid_units.indexOf(m2));
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// __END__
|
||||
276
src/utils/HtmlElementCreator.mjs
Normal file
276
src/utils/HtmlElementCreator.mjs
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
Description: DOM Management and HTML builder
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export {
|
||||
HtmlElementCreator,
|
||||
// deprecated name
|
||||
HtmlElementCreator as DomManagement
|
||||
};
|
||||
import { deepCopyFunction, isObject } from './JavaScriptHelpers.mjs';
|
||||
|
||||
class HtmlElementCreator {
|
||||
/**
|
||||
* reates object for DOM element creation flow
|
||||
* @param {String} tag must set tag (div, span, etc)
|
||||
* @param {String} [id=''] optional set for id, if input, select will be used for name
|
||||
* @param {String} [content=''] text content inside, is skipped if sub elements exist
|
||||
* @param {Array} [css=[]] array for css tags
|
||||
* @param {Object} [options={}] anything else (value, placeholder, OnClick, style)
|
||||
* @return {Object} created element as an object
|
||||
*/
|
||||
cel(tag, id = '', content = '', css = [], options = {})
|
||||
{
|
||||
return {
|
||||
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: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* attach a cel created object to another to create a basic DOM tree
|
||||
* @param {Object} base object where to attach/search
|
||||
* @param {Object} attach the object to be attached
|
||||
* @param {String} [id=''] optional id, if given search in base for this id and attach there
|
||||
* @return {Object} "none", technically there is no return needed as it is global attach
|
||||
*/
|
||||
ael(base, attach, id = '')
|
||||
{
|
||||
if (id) {
|
||||
// base id match already
|
||||
if (base.id == id) {
|
||||
// base.sub.push(Object.assign({}, attach));
|
||||
base.sub.push(deepCopyFunction(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
|
||||
this.ael(base.sub[i], attach, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// base.sub.push(Object.assign({}, attach));
|
||||
base.sub.push(deepCopyFunction(attach));
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* directly attach n elements to one master base element
|
||||
* this type does not support attach with optional id
|
||||
* @param {Object} base object to where we attach the elements
|
||||
* @param {...Object} attach attach 1..n: attach directly to the base element those attachments
|
||||
* @return {Object} "none", technically there is no return needed, global attach
|
||||
*/
|
||||
aelx(base, ...attach)
|
||||
{
|
||||
for (var i = 0; i < attach.length; i ++) {
|
||||
// base.sub.push(Object.assign({}, attach[i]));
|
||||
base.sub.push(deepCopyFunction(attach[i]));
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* same as aelx, but instead of using objects as parameters
|
||||
* get an array of objects to attach
|
||||
* @param {Object} base object to where we attach the elements
|
||||
* @param {Array} attach array of objects to attach
|
||||
* @return {Object} "none", technically there is no return needed, global attach
|
||||
*/
|
||||
aelxar(base, attach)
|
||||
{
|
||||
for (var i = 0; i < attach.length; i ++) {
|
||||
// base.sub.push(Object.assign({}, attach[i]));
|
||||
base.sub.push(deepCopyFunction(attach[i]));
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* resets the sub elements of the base element given
|
||||
* @param {Object} base cel created element
|
||||
* @return {Object} returns reset base element
|
||||
*/
|
||||
rel(base)
|
||||
{
|
||||
base.sub = [];
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* searches and removes style from css array
|
||||
* @param {Object} _element element to work one
|
||||
* @param {String} css style sheet to remove (name)
|
||||
* @return {Object} returns full element
|
||||
*/
|
||||
rcssel(_element, css)
|
||||
{
|
||||
var css_index = _element.css.indexOf(css);
|
||||
if (css_index > -1) {
|
||||
_element.css.splice(css_index, 1);
|
||||
}
|
||||
return _element;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new style sheet to the element given
|
||||
* @param {Object} _element element to work on
|
||||
* @param {String} css style sheet to add (name)
|
||||
* @return {Object} returns full element
|
||||
*/
|
||||
acssel(_element, css)
|
||||
{
|
||||
var css_index = _element.css.indexOf(css);
|
||||
if (css_index == -1) {
|
||||
_element.css.push(css);
|
||||
}
|
||||
return _element;
|
||||
}
|
||||
|
||||
/**
|
||||
* removes one css and adds another
|
||||
* is a wrapper around rcssel/acssel
|
||||
* @param {Object} _element element to work on
|
||||
* @param {String} rcss style to remove (name)
|
||||
* @param {String} acss style to add (name)
|
||||
* @return {Object} returns full element
|
||||
*/
|
||||
scssel(_element, rcss, acss)
|
||||
{
|
||||
this.rcssel(_element, rcss);
|
||||
this.acssel(_element, acss);
|
||||
}
|
||||
|
||||
/**
|
||||
* parses the object tree created with cel/ael and converts it into an HTML string
|
||||
* that can be inserted into the page
|
||||
* @param {Object} tree object tree with dom element declarations
|
||||
* @return {String} HTML string that can be used as innerHTML
|
||||
*/
|
||||
phfo(tree)
|
||||
{
|
||||
let name_elements = [
|
||||
'button',
|
||||
'fieldset',
|
||||
'form',
|
||||
'iframe',
|
||||
'input',
|
||||
'map',
|
||||
'meta',
|
||||
'object',
|
||||
'output',
|
||||
'param',
|
||||
'select',
|
||||
'textarea',
|
||||
];
|
||||
let skip_options = [
|
||||
'id',
|
||||
'name',
|
||||
'class',
|
||||
];
|
||||
let no_close = [
|
||||
'input',
|
||||
'br',
|
||||
'img',
|
||||
'hr',
|
||||
'area',
|
||||
'col',
|
||||
'keygen',
|
||||
'wbr',
|
||||
'track',
|
||||
'source',
|
||||
'param',
|
||||
'command',
|
||||
// only in header
|
||||
'base',
|
||||
'meta',
|
||||
'link',
|
||||
'embed',
|
||||
];
|
||||
// holds the elements
|
||||
var content = [];
|
||||
// main part line
|
||||
var line = '<' + tree.tag;
|
||||
var i;
|
||||
// first id, if set
|
||||
if (tree.id) {
|
||||
line += ' id="' + tree.id + '"';
|
||||
// if anything input (input, textarea, select then add name too)
|
||||
if (name_elements.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 (!skip_options.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(this.phfo(tree.sub[i]));
|
||||
}
|
||||
} else if (tree.content) {
|
||||
content.push(tree.content);
|
||||
}
|
||||
// if not input, image or br, then close
|
||||
if (
|
||||
!no_close.includes(tree.tag)
|
||||
) {
|
||||
content.push('</' + tree.tag + '>');
|
||||
}
|
||||
// combine to string
|
||||
return content.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTML elements from array list
|
||||
* as a flat element without master object file
|
||||
* Is like tree.sub call
|
||||
* @param {Array} list Array of cel created objects
|
||||
* @return {String} HTML String
|
||||
*/
|
||||
phfa(list)
|
||||
{
|
||||
var content = [];
|
||||
for (var i = 0; i < list.length; i ++) {
|
||||
content.push(this.phfo(list[i]));
|
||||
}
|
||||
return content.join('');
|
||||
}
|
||||
}
|
||||
|
||||
// __EMD__
|
||||
217
src/utils/HtmlHelpers.mjs
Normal file
217
src/utils/HtmlHelpers.mjs
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Description: HTML Helpers
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { escapeHtml, unescapeHtml, html_options, html_options_block, html_options_refill };
|
||||
import { loadEl} from './DomHelpers.mjs';
|
||||
import { DomManagement } from './HtmlElementCreator.mjs';
|
||||
let dom = new DomManagement();
|
||||
|
||||
/**
|
||||
* Escapes HTML in string
|
||||
* @param {String} string Text to escape HTML in
|
||||
* @returns {String}
|
||||
*/
|
||||
function escapeHtml(string)
|
||||
{
|
||||
return string.replace(/[&<>"'/]/g, function (s) {
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
'\'': ''',
|
||||
'/': '/'
|
||||
};
|
||||
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape a HTML encoded string
|
||||
* @param {String} string Text to unescape HTML in
|
||||
* @returns {String}
|
||||
*/
|
||||
function unescapeHtml(string)
|
||||
{
|
||||
return string.replace(/&[#\w]+;/g, function (s) {
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': '\'',
|
||||
'/': '/'
|
||||
};
|
||||
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
|
||||
// BLOCK: html wrappers for quickly creating html data blocks
|
||||
|
||||
/**
|
||||
* NOTE: OLD FORMAT which misses multiple block set
|
||||
* 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
|
||||
* @param {String} name name/id
|
||||
* @param {Object} data array for the options
|
||||
* @param {String} [selected=''] selected item uid
|
||||
* @param {Boolean} [options_only=false] if this is true, it will not print the select part
|
||||
* @param {Boolean} [return_string=false] return as string and not as element
|
||||
* @param {String} [sort=''] if empty as is, else allowed 'keys',
|
||||
* 'values' all others are ignored
|
||||
* @return {String} html with build options block
|
||||
* @deprecated html_options_block
|
||||
*/
|
||||
function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '')
|
||||
{
|
||||
// wrapper to new call
|
||||
return this.html_options_block(
|
||||
name, data, selected, 0, options_only, return_string, sort
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: USE THIS CALL, the above one is deprecated
|
||||
* 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
|
||||
* @param {String} name name/id
|
||||
* @param {Object} data array for the options
|
||||
* @param {String} [selected=''] selected item uid
|
||||
* @param {Number} [multiple=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
|
||||
* @param {Boolean} [options_only=false] if this is true, it will not print the select part
|
||||
* @param {Boolean} [return_string=false] return as string and not as element
|
||||
* @param {String} [sort=''] if empty as is, else allowed 'keys',
|
||||
* 'values' all others are ignored
|
||||
* @param {String} [onchange=''] onchange trigger call, default unset
|
||||
* @return {String} html with build options block
|
||||
*/
|
||||
function html_options_block(
|
||||
name, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = ''
|
||||
) {
|
||||
var content = [];
|
||||
var element_select;
|
||||
var select_options = {};
|
||||
var element_option;
|
||||
var data_list = []; // for sorted output
|
||||
var value;
|
||||
var options = {};
|
||||
// var option;
|
||||
if (multiple > 0) {
|
||||
select_options.multiple = '';
|
||||
if (multiple > 1) {
|
||||
select_options.size = multiple;
|
||||
}
|
||||
}
|
||||
if (onchange) {
|
||||
select_options.OnChange = onchange;
|
||||
}
|
||||
// set outside select, gets stripped on return if options only is true
|
||||
element_select = dom.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,
|
||||
'selected': ''
|
||||
};
|
||||
// add selected if matching
|
||||
if (multiple == 0 && !Array.isArray(selected) && selected == key) {
|
||||
options.selected = '';
|
||||
}
|
||||
// for multiple, we match selected as array
|
||||
if (multiple == 1 && Array.isArray(selected) && selected.indexOf(key) != -1) {
|
||||
options.selected = '';
|
||||
}
|
||||
// create the element option
|
||||
element_option = dom.cel('option', '', value, [], options);
|
||||
// attach it to the select element
|
||||
dom.ael(element_select, element_option);
|
||||
}
|
||||
// if with select part, convert to text
|
||||
if (!options_only) {
|
||||
if (return_string) {
|
||||
content.push(dom.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(dom.phfo(element_select.sub[i]));
|
||||
}
|
||||
return content.join('');
|
||||
} else {
|
||||
return element_select.sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* refills a select box with options and keeps the selected
|
||||
* @param {String} name name/id
|
||||
* @param {Object} data array of options
|
||||
* @param {String} [sort=''] if empty as is, else allowed 'keys', 'values'
|
||||
* all others are ignored
|
||||
*/
|
||||
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 (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;
|
||||
});
|
||||
loadEl(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;
|
||||
if (key == option_selected) {
|
||||
element_option.selected = true;
|
||||
}
|
||||
loadEl(name).appendChild(element_option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __EMD__
|
||||
159
src/utils/JavaScriptHelpers.mjs
Normal file
159
src/utils/JavaScriptHelpers.mjs
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Description: JavaScript Helpers
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export {
|
||||
errorCatch, isFunction, executeFunctionByName, isObject, getObjectCount,
|
||||
keyInObject, getKeyByValue,valueInObject, deepCopyFunction
|
||||
};
|
||||
|
||||
/**
|
||||
* prints out error messages based on data available from the browser
|
||||
* @param {Object} err error from try/catch block
|
||||
*/
|
||||
function errorCatch(err)
|
||||
{
|
||||
// for FF & Chrome
|
||||
if (err.stack) {
|
||||
// only FF
|
||||
if (err.lineNumber) {
|
||||
console.error('ERROR[%s:%s] ', err.name, err.lineNumber, err);
|
||||
} else if (err.line) {
|
||||
// only Safari
|
||||
console.error('ERROR[%s:%s] ', err.name, err.line, err);
|
||||
} else {
|
||||
console.error('ERROR[%s] ', err.name, err);
|
||||
}
|
||||
} else if (err.number) {
|
||||
// IE
|
||||
console.error('ERROR[%s:%s] %s', err.name, err.number, err.message);
|
||||
console.error('ERROR[description] %s', err.description);
|
||||
} else {
|
||||
// the rest
|
||||
console.error('ERROR[%s] %s', err.name, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if name is a function
|
||||
* @param {string} name Name of function to check if exists
|
||||
* @return {Boolean} true/false
|
||||
*/
|
||||
function isFunction(name)
|
||||
{
|
||||
if (typeof window[name] !== 'undefined' &&
|
||||
typeof window[name] === 'function') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* call a function by its string name
|
||||
* https://stackoverflow.com/a/359910
|
||||
* example: executeFunctionByName("My.Namespace.functionName", window, arguments);
|
||||
* @param {string} functionName The function name or namespace + function
|
||||
* @param {any} context context (window or first namespace)
|
||||
* hidden next are all the arguments
|
||||
* @return {any} Return values from functon
|
||||
*/
|
||||
function executeFunctionByName(functionName, context /*, args */)
|
||||
{
|
||||
var args = Array.prototype.slice.call(arguments, 2);
|
||||
var namespaces = functionName.split('.');
|
||||
var func = namespaces.pop();
|
||||
if (func == undefined) {
|
||||
throw new Error("Cannot get function from namespaces: " + functionName);
|
||||
}
|
||||
for (var i = 0; i < namespaces.length; i++) {
|
||||
context = context[namespaces[i]];
|
||||
}
|
||||
return context[func].apply(context, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if a variable is an object
|
||||
* @param {any} val possible object
|
||||
* @return {Boolean} true/false if it is an object or not
|
||||
*/
|
||||
function isObject(val)
|
||||
{
|
||||
if (val === null) {
|
||||
return false;
|
||||
}
|
||||
return ((typeof val === 'function') || (typeof val === 'object'));
|
||||
}
|
||||
|
||||
/**
|
||||
* get the length of an object (entries)
|
||||
* @param {Object} object object to check
|
||||
* @return {Number} number of entry
|
||||
*/
|
||||
function getObjectCount(object)
|
||||
{
|
||||
return Object.keys(object).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if a key exists in a given object
|
||||
* @param {String} key key name
|
||||
* @param {Object} object object to search key in
|
||||
* @return {Boolean} true/false if key exists in object
|
||||
*/
|
||||
function keyInObject(key, object)
|
||||
{
|
||||
return Object.prototype.hasOwnProperty.call(object, key) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns matching key of value
|
||||
* @param {Object} object object to search value in
|
||||
* @param {any} value any value (String, Number, etc)
|
||||
* @return {String} the key found for the first matching value
|
||||
*/
|
||||
function getKeyByValue(object, value)
|
||||
{
|
||||
return Object.keys(object).find(key => object[key] === value) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if value is found in object with a key
|
||||
* @param {Object} object object to search value in
|
||||
* @param {any} value any value (String, Number, etc)
|
||||
* @return {Boolean} true on value found, false on not found
|
||||
*/
|
||||
function valueInObject(object, value)
|
||||
{
|
||||
return Object.keys(object).find(key => object[key] === value) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* true deep copy for Javascript objects
|
||||
* if Object.assign({}, obj) is not working (shallow)
|
||||
* or if JSON.parse(JSON.stringify(obj)) is failing
|
||||
* @param {Object} inObject Object to copy
|
||||
* @return {Object} Copied Object
|
||||
*/
|
||||
function deepCopyFunction(inObject)
|
||||
{
|
||||
var outObject, value, key;
|
||||
if (typeof inObject !== 'object' || inObject === null) {
|
||||
// Return the value if inObject is not an object
|
||||
return inObject;
|
||||
}
|
||||
// Create an array or object to hold the values
|
||||
outObject = Array.isArray(inObject) ? [] : {};
|
||||
// loop over ech entry in object
|
||||
for (key in inObject) {
|
||||
value = inObject[key];
|
||||
// Recursively (deep) copy for nested objects, including arrays
|
||||
outObject[key] = deepCopyFunction(value);
|
||||
}
|
||||
|
||||
return outObject;
|
||||
}
|
||||
|
||||
// __END__
|
||||
111
src/utils/LoginMenu.mjs
Normal file
111
src/utils/LoginMenu.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Description: Login access and menu
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { loginLogout, createLoginRow, createNavMenu };
|
||||
import { isObject, getObjectCount } from './JavaScriptHelpers.mjs';
|
||||
import { exists } from './DomHelpers.mjs';
|
||||
import { HtmlElementCreator } from './HtmlElementCreator.mjs';
|
||||
import { l10nTranslation } from './l10nTranslation.mjs';
|
||||
let hec = new HtmlElementCreator();
|
||||
let l10n = new l10nTranslation(i18n ?? {});
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* create login string and logout button elements
|
||||
* @param {String} login_string the login string to show on the left
|
||||
* @param {String} [header_id='mainHeader'] the target for the main element block
|
||||
* if not set mainHeader is assumed
|
||||
* this is the target div for the "loginRow"
|
||||
*/
|
||||
function createLoginRow(login_string, header_id = 'mainHeader')
|
||||
{
|
||||
// if header does not exist, we do nothing
|
||||
if (exists(header_id)) {
|
||||
// that row must exist already, if not it must be the first in the "mainHeader"
|
||||
if (!exists('loginRow')) {
|
||||
$('#' + header_id).html(hec.phfo(hec.cel('div', 'loginRow', '', ['loginRow', 'flx-spbt'])));
|
||||
}
|
||||
// clear out just in case for first entry
|
||||
// fill with div name & login/logout button
|
||||
$('#loginRow').html(hec.phfo(hec.cel('div', 'loginRow-name', login_string)));
|
||||
$('#loginRow').append(hec.phfo(hec.cel('div', 'loginRow-info', '')));
|
||||
$('#loginRow').append(hec.phfo(
|
||||
hec.aelx(
|
||||
// outer div
|
||||
hec.cel('div', 'loginRow-logout'),
|
||||
// inner element
|
||||
hec.cel('input', 'logout', '', [], {
|
||||
value: l10n.__('Logout'),
|
||||
type: 'button',
|
||||
onClick: 'loginLogout()'
|
||||
})
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create the top nav menu that switches physical between pages
|
||||
* (edit access data based)
|
||||
* @param {Object} nav_menu the built nav menu with highlight info
|
||||
* @param {String} [header_id='mainHeader'] the target for the main element block
|
||||
* if not set mainHeader is assumed
|
||||
* this is the target div for the "menuRow"
|
||||
*/
|
||||
function createNavMenu(nav_menu, header_id = 'mainHeader')
|
||||
{
|
||||
// must be an object
|
||||
if (isObject(nav_menu) && getObjectCount(nav_menu) > 1) {
|
||||
// do we have more than one entry, if not, do not show (single page)
|
||||
if (!exists('menuRow')) {
|
||||
$('#' + header_id).html(hec.phfo(hec.cel('div', 'menuRow', '', ['menuRow', 'flx-s'])));
|
||||
}
|
||||
var content = [];
|
||||
$.each(nav_menu, function(key, item) {
|
||||
// key is number
|
||||
// item is object with entries
|
||||
if (key != 0) {
|
||||
content.push(hec.phfo(hec.cel('div', '', '·', ['pd-2'])));
|
||||
}
|
||||
// ignore item.popup for now
|
||||
if (item.enabled) {
|
||||
// set selected based on window.location.href as the php set will not work
|
||||
if (window.location.href.indexOf(item.url) != -1) {
|
||||
item.selected = 1;
|
||||
}
|
||||
// create the entry
|
||||
content.push(hec.phfo(
|
||||
hec.aelx(
|
||||
hec.cel('div'),
|
||||
hec.cel('a', '', item.name, ['pd-2'].concat(item.selected ? 'highlight': ''), {
|
||||
href: item.url
|
||||
})
|
||||
)
|
||||
));
|
||||
}
|
||||
});
|
||||
$('#menuRow').html(content.join(''));
|
||||
} else {
|
||||
$('#menuRow').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
50
src/utils/MathHelpers.mjs
Normal file
50
src/utils/MathHelpers.mjs
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Description: Math Helpers
|
||||
Date: 2025/3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { dec2hex, getRandomIntInclusive, roundPrecision };
|
||||
|
||||
/**
|
||||
* dec2hex :: Integer -> String
|
||||
* i.e. 0-255 -> '00'-'ff'
|
||||
* @param {Number} dec decimal string
|
||||
* @return {String} hex encdoded number
|
||||
*/
|
||||
function dec2hex(dec)
|
||||
{
|
||||
return ('0' + dec.toString(16)).substring(-2);
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a number between min/max
|
||||
* with min/max inclusive.
|
||||
* eg: 1,5 will create a number ranging from 1 o 5
|
||||
* @param {Number} min minimum int number inclusive
|
||||
* @param {Number} max maximumg int number inclusive
|
||||
* @return {Number} Random number
|
||||
*/
|
||||
function getRandomIntInclusive(min, max)
|
||||
{
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
// The maximum is inclusive and the minimum is inclusive
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a number to precision
|
||||
* @param {Number} number Number to round
|
||||
* @param {Number} precision Precision value
|
||||
* @returns {Number}
|
||||
*/
|
||||
function roundPrecision(number, precision)
|
||||
{
|
||||
if (!isNaN(number) || !isNaN(precision)) {
|
||||
return number;
|
||||
}
|
||||
return Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision);
|
||||
}
|
||||
|
||||
// __END__
|
||||
128
src/utils/ResizingAndMove.mjs
Normal file
128
src/utils/ResizingAndMove.mjs
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Description: Resize and Move Javascript
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
import { errorCatch} from './JavaScriptHelpers.mjs';
|
||||
import { loadEl } from './DomHelpers.mjs';
|
||||
export { getWindowSize, getScrollOffset, getScrollOffsetOpener, setCenter, goToPos, goTo };
|
||||
|
||||
/**
|
||||
* wrapper to get the real window size for the current browser window
|
||||
* @return {Object} object with width/height
|
||||
*/
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper to get the correct scroll offset
|
||||
* @return {Object} object with x/y px
|
||||
*/
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper to get the correct scroll offset for opener page (from popup)
|
||||
* @return {Object} object with x/y px
|
||||
*/
|
||||
function getScrollOffsetOpener()
|
||||
{
|
||||
var left, top;
|
||||
left = opener.window.pageXOffset || (opener.document.documentElement.scrollLeft || opener.document.body.scrollLeft);
|
||||
top = opener.window.pageYOffset || (opener.document.documentElement.scrollTop || opener.document.body.scrollTop);
|
||||
return {
|
||||
left: left,
|
||||
top: top
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* centers div to current window size middle
|
||||
* @param {String} id element to center
|
||||
* @param {Boolean} left if true centers to the middle from the left
|
||||
* @param {Boolean} top if true centers to the middle from the top
|
||||
*/
|
||||
function setCenter(id, left, top)
|
||||
{
|
||||
// get size of id
|
||||
var dimensions = {
|
||||
height: $('#' + id).height() ?? 0,
|
||||
width: $('#' + id).width() ?? 0
|
||||
};
|
||||
var type = $('#' + id).css('position');
|
||||
var viewport = this.getWindowSize();
|
||||
var offset = this.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: (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' ?
|
||||
(viewport.height / 2) - (dimensions.height / 2) :
|
||||
(viewport.height / 2) - (dimensions.height / 2) + offset.top;
|
||||
$('#' + id).css({
|
||||
top: top_pos + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* goes to an element id position
|
||||
* @param {String} element element id to move to
|
||||
* @param {Number} [offset=0] offset from top, default is 0 (px)
|
||||
* @param {Number} [duration=500] animation time, default 500ms
|
||||
* @param {String} [base='body,html'] base element for offset scroll
|
||||
*/
|
||||
function goToPos(element, offset = 0, duration = 500, base = 'body,html')
|
||||
{
|
||||
try {
|
||||
let element_offset = $('#' + element).offset();
|
||||
if (element_offset == undefined) {
|
||||
return;
|
||||
}
|
||||
if ($('#' + element).length) {
|
||||
$(base).animate({
|
||||
scrollTop: element_offset.top - offset
|
||||
}, duration);
|
||||
}
|
||||
} catch (err) {
|
||||
errorCatch(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* go to element, scroll
|
||||
* non jquery
|
||||
* @param {string} target
|
||||
*/
|
||||
function goTo(target)
|
||||
{
|
||||
loadEl(target).scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
// __END__
|
||||
49
src/utils/StringHelpers.mjs
Normal file
49
src/utils/StringHelpers.mjs
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Description: String Helpers
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { formatString, numberWithCommas, convertLBtoBR };
|
||||
|
||||
/**
|
||||
* simple sprintf formater for replace
|
||||
* usage: "{0} is cool, {1} is not".format("Alpha", "Beta");
|
||||
* First, checks if it isn't implemented yet.
|
||||
* @param {String} string String with {..} entries
|
||||
* @param {...any} args List of replacement
|
||||
* @returns {String} Escaped string
|
||||
*/
|
||||
function formatString(string, ...args)
|
||||
{
|
||||
return string.replace(/{(\d+)}/g, function(match, number)
|
||||
{
|
||||
return typeof args[number] != 'undefined' ?
|
||||
args[number] :
|
||||
match
|
||||
;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* formats flat number 123456 to 123,456
|
||||
* @param {Number} number number to be formated
|
||||
* @return {String} formatted with , in thousands
|
||||
*/
|
||||
function numberWithCommas(number)
|
||||
{
|
||||
var parts = number.toString().split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return parts.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* converts line breaks to br
|
||||
* @param {String} string any string
|
||||
* @return {String} string with <br>
|
||||
*/
|
||||
function convertLBtoBR(string)
|
||||
{
|
||||
return string.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||
}
|
||||
|
||||
// __END__
|
||||
36
src/utils/UniqIdGenerators.mjs
Normal file
36
src/utils/UniqIdGenerators.mjs
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Description: Generate unique ids
|
||||
Date: 2025/3/7
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { generateId, randomIdF };
|
||||
|
||||
|
||||
/**
|
||||
* generateId :: Integer -> String
|
||||
* only works on mondern browsers
|
||||
* @param {Number} len length of unique id string
|
||||
* @return {String} random string in length of len
|
||||
*/
|
||||
function generateId(len)
|
||||
{
|
||||
var arr = new Uint8Array((len || 40) / 2);
|
||||
(
|
||||
window.crypto ||
|
||||
// @ts-ignore
|
||||
window.msCrypto
|
||||
).getRandomValues(arr);
|
||||
return Array.from(arr, self.dec2hex).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a pseudo random string of 10 characters
|
||||
* works on all browsers
|
||||
* after many runs it will create duplicates
|
||||
* @return {String} not true random string
|
||||
*/
|
||||
function randomIdF()
|
||||
{
|
||||
return Math.random().toString(36).substring(2);
|
||||
}
|
||||
108
src/utils/UrlParser.mjs
Normal file
108
src/utils/UrlParser.mjs
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Description: HTML Helpers
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { parseQueryString, getQueryStringParam };
|
||||
import { keyInObject } from './JavaScriptHelpers.mjs';
|
||||
|
||||
/**
|
||||
* parses a query string from window.location.search.substring(1)
|
||||
* ALTERNATIVE CODE
|
||||
* var url = new URL(window.location.href);
|
||||
* param_uid = url.searchParams.get('uid');
|
||||
* @param {String} [query=''] the query string to parse
|
||||
* if not set will auto fill
|
||||
* @param {String} [return_key=''] if set only returns this key entry
|
||||
* or empty for none
|
||||
* @return {Object|String} parameter entry list
|
||||
*/
|
||||
function parseQueryString(query = '', return_key = '')
|
||||
{
|
||||
if (!query) {
|
||||
query = window.location.search.substring(1);
|
||||
}
|
||||
var vars = query.split('&');
|
||||
var query_string = {};
|
||||
for (var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split('=');
|
||||
var key = decodeURIComponent(pair[0]);
|
||||
var value = decodeURIComponent(pair[1]);
|
||||
// skip over run if there is nothing
|
||||
if (!key || value === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
// If first entry with this name
|
||||
if (typeof query_string[key] === 'undefined') {
|
||||
query_string[key] = decodeURIComponent(value);
|
||||
// If second entry with this name
|
||||
} else if (typeof query_string[key] === 'string') {
|
||||
var arr = [query_string[key], decodeURIComponent(value)];
|
||||
query_string[key] = arr;
|
||||
// If third or later entry with this name
|
||||
} else {
|
||||
query_string[key].push(decodeURIComponent(value));
|
||||
}
|
||||
}
|
||||
if (return_key) {
|
||||
if (keyInObject(return_key, query_string)) {
|
||||
return query_string[return_key];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
return query_string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* searches query parameters for entry and returns data either as string or array
|
||||
* if no search is given the whole parameters are returned as an object
|
||||
* if a parameter is set several times it will be returned as an array
|
||||
* if search parameter set and nothing found and empty string is returned
|
||||
* if no parametes exist and no serach is set and empty object is returned
|
||||
* @param {String} [search=''] if set searches for this entry, if empty
|
||||
* all parameters are returned
|
||||
* @param {String} [query=''] different query string to parse, if not
|
||||
* set (default) the current window href is used
|
||||
* @param {Boolean} [single=false] if set to true then only the first found
|
||||
* will be returned
|
||||
* @return {Object|Array|String} if search is empty, object, if search is set
|
||||
* and only one entry, then string, else array
|
||||
* unless single is true
|
||||
*/
|
||||
function getQueryStringParam(search = '', query = '', single = false)
|
||||
{
|
||||
if (!query) {
|
||||
query = window.location.href;
|
||||
}
|
||||
const url = new URL(query);
|
||||
let param = null;
|
||||
if (search) {
|
||||
let _params = url.searchParams.getAll(search);
|
||||
if (_params.length == 1 || single === true) {
|
||||
param = _params[0];
|
||||
} else if (_params.length > 1) {
|
||||
param = _params;
|
||||
}
|
||||
} else {
|
||||
// will be object, so declare it one
|
||||
param = {};
|
||||
// loop over paramenters
|
||||
for (const [key] of url.searchParams.entries()) {
|
||||
// check if not yet set
|
||||
if (typeof param[key] === 'undefined') {
|
||||
// get the parameters multiple
|
||||
let _params = url.searchParams.getAll(key);
|
||||
// if 1 set as string, else attach array as is
|
||||
param[key] = _params.length < 2 || single === true ?
|
||||
_params[0] :
|
||||
_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
// __EMD__
|
||||
34
src/utils/l10nTranslation.mjs
Normal file
34
src/utils/l10nTranslation.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Description: Translation call
|
||||
Date: 2025//3/6
|
||||
Creator: Clemens Schwaighofer
|
||||
*/
|
||||
|
||||
export { l10nTranslation };
|
||||
import { isObject } from './JavaScriptHelpers.mjs';
|
||||
|
||||
class l10nTranslation {
|
||||
|
||||
#i18n = {};
|
||||
|
||||
constructor(i18n) {
|
||||
this.#i18n = i18n;
|
||||
|
||||
}
|
||||
/**
|
||||
* uses the i18n object created in the translation template
|
||||
* that is filled from gettext in PHP
|
||||
* @param {String} string text to translate
|
||||
* @return {String} translated text (based on PHP selected language)
|
||||
*/
|
||||
__(string)
|
||||
{
|
||||
if (typeof this.#i18n !== 'undefined' && isObject(this.#i18n) && this.#i18n[string]) {
|
||||
return this.#i18n[string];
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
1238
src/utilsAll.mjs
Normal file
1238
src/utilsAll.mjs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user