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