Compare commits

...

42 Commits

Author SHA1 Message Date
chris 76fac29dd6 DOC: Introduced changelog and upcoming changes 1 month ago
chris 646afba4d3 FIX,REFA: extStore handling during generate and appendToPage 1 month ago
chris ea9261446d DOC: Added copyright, minor functional, README 1 month ago
chris e0a60593d2 CHORE: adjusted "build" script 1 month ago
chris 882303a5fa FEAT,REFA: extensions logic and management console 1 month ago
chris eae24e4bd5 DOC: Added or modified some doc 1 month ago
chris a0c6f68e23 REFA,FEAT: Contextmenu behaviour 1 month ago
chris 27d0635119 REFA,FEAT: Introduced DragAndDropImplementation as behaviour parameterr for components 1 month ago
chris 0ba13cca94 REFA,FEAT: Reintroduced and reintroduced ExtStorage and corresponding enums, functions and so on 1 month ago
chris e0046c6aa8 MINOR,HELPER: added default compel groups 1 month ago
chris 12bf468e22 FIX: Border Color value and component modifier setup during generate 1 month ago
chris 89207c163f HELPER,DOC,REFA: Added several helper methods 1 month ago
chris 170d00d852 REFA: Moved functions to their logical position 1 month ago
chris 4479c92bb4 MINOR,FEAT: Adjusted the component container logic 1 month ago
chris b7fff8604e REFA: Moved alignment and arrangement setting 1 month ago
chris a7fcb5a3ca REFA,RENAME,DOC: Renamed several functions for readability 1 month ago
chris 4b6f4006d9 MINOR,FEAT: Introduced default parameter "modifier" 1 month ago
chris 9447216e53 REFA,IMRO: Extended auto-subscriptions of components in compelgroups 2 months ago
chris f41e7f1384 REFA: provided an HTMLElement ToggleVisibility 2 months ago
chris c4b0fc9162 FEAT,MINOR: (Re- ?) Introduced component subscription 2 months ago
chris 5e782912f7 FIX,FEAT: Drag and Drop basic implementation 2 months ago
chris 1632528322 MINOR,FEAT,HELPERS,CONVINENCE: Couple functions/changes 2 months ago
chris b60b89cbea FEAT,MODIFY,FIX: ExtStore functionallity and parent-dependent-sizing 2 months ago
chris 0cc60ad07c MINOR,DOC: Added convenience functions to the Shape class and fixed/changed some doc 2 months ago
chris 771a05dba8 MINOR,FIX: In established functionallities 2 months ago
chris f9e7e188cb FIX: changed from !(Object) to ===null comparison 2 months ago
chris 02f72d4648 FEAT: DragAndDrop in a basic implementation 2 months ago
chris bb0ccb8904 FEAT: extended Siding added some constants 2 months ago
chris 6f78fa1cfb FEAT: ContextMenu 2 months ago
chris 3782344e1f FEAT: added hidden() and overflow() functions 2 months ago
chris 5b74731cf1 REFA,STEP,FEAT: added style remove functionallity and refactored inputTag-organisation in builder 2 months ago
chris 94ef6186cf REFA,STEP,DOC,FIX: some docs and obvious errors 2 months ago
chris 3b8efc9a6d REFA,STEP,UNSTABLE: reestablished generate 2 months ago
chris 0c51679e04 REFA,UNSTABLE,STEP: Removing old artifacts/fragments of earlier implementations 2 months ago
chris ec298757c2 STEP,UNSTABLE,REFA: Restructured organization of Component and its ancestry 2 months ago
chris 8121c9ec6a TYPO: added ; and (empty) dependency block 4 months ago
chris 9d073a8367 MINOR,FEAT: Added inputtags with predefined types to builder 4 months ago
chris cdc1188061 FEAT: Introduced first version of Scrollable and DragAndDrop 4 months ago
chris ea1786f379 FIX,IMPRO,REFA: Fixed framework after refactoring and -structuring 4 months ago
chris 4a0bd90799 MAJOR,REFA,IMPRO,CHANGE: Introducing extensive usage of Inheritance 4 months ago
chris 95d0649169 DOC,LICENCE,COPYRIGHT: Updated copyright comment in files-doc 4 months ago
chris fc2c6b15c0 MINOR,REFA,DOC,INDENT: Autoformatted code, added some doc 4 months ago
  1. 1
      .gitignore
  2. 0
      CHANGELOG.md
  3. 22
      README.md
  4. 170
      fileOrder.json
  5. 156
      generate_single_file.js
  6. 25
      join_js_files.sh
  7. 3
      package.json
  8. 3
      src/alignment.js
  9. 6
      src/arrangement.js
  10. 132
      src/baseComponents.js
  11. 261
      src/builder.js
  12. 5
      src/color.js
  13. 15
      src/commonEvents.js
  14. 594
      src/component.js
  15. 180
      src/componentAncestry/addStyleAndFunctions.js
  16. 60
      src/componentAncestry/modifiableComponent.js
  17. 200
      src/componentAncestry/wrapperComponent.js
  18. 277
      src/context.js
  19. 628
      src/context/extStore.js
  20. 66
      src/context/framework-controls.js
  21. 119
      src/context/generalHelpers.js
  22. 205
      src/context/scriptAndStyleContext.js
  23. 31
      src/context/webTrinity.js
  24. 53
      src/extensions/extension.js
  25. 69
      src/modifications/contextMenu.js
  26. 129
      src/modifications/dragAndDrop.js
  27. 140
      src/modifier.js
  28. 22
      src/sizeSide/border.js
  29. 44
      src/sizeSide/dimensions.js
  30. 69
      src/sizeSide/shapes.js
  31. 218
      src/sizeSide/siding.js
  32. 8
      upcoming.md

1
.gitignore

@ -1,3 +1,4 @@
node_modules/
samples/*/
jpc-like-websites.js
extensions/

0
CHANGELOG.md

22
README.md

@ -1,7 +1,7 @@
# jpc-like-websites
> Jetpack Compose like websites
The building method of Jetpack Compose (for android),
The building method of Jetpack Compose (for e.g. android),
feels really straight forward
and intuitive for me.
Furthermore I personally found it more readable
@ -13,20 +13,28 @@ to build those websites more or less fast (easy)
and reliable.
While they do look the way I intend them to.
After developing some minor apps with Jetpack Compose I liked a lot about the way
the components are build/structured.
After developing some minor apps with Jetpack Compose,
I liked a lot about the way the components are build/structured.
The reusability and so on...
Further I love to write code in a "method-chaning" kind of way:
Every line is a chain-link concerning one particular task,
the IDE offers an overview of possibilities.
Method-chaining is more or less native in Kotlin.
Logical support for git-line-wise change detection.
It makes even more sence concidering the git-line-wise change detection.
After some (very little) searching,
I choose to give it a couple of hours
and develop a small "lib" to enable myself to do exactly that.
> Develop jetpack compose like components in html/javascript.
> Develop html/javascript components in a "jetpack compose like" manner.
BUT it is not inteded to be or do more than that.
The lib/framework is supposed to help to build/define/construct components
or furhter single page applications.
It is not meant to be a framework like react, vue, angular ... the list goes on.
The closest comparison is actually typescript,
it is supposed to build html, js, and css.
If desired, it can generate the entire page (html-file)
without the framework itself.
Javascript is by far not my strongest field,
typescript even less.
@ -36,4 +44,4 @@ you have been warned.
```sh
sh join_js_files.sh
```
```

170
fileOrder.json

@ -0,0 +1,170 @@
{
"orderedGroups": {
"pure_stylings": [
"color.js",
"alignment.js",
"arrangement.js"
],
"size_sidings": [
"siding.js",
"shapes.js",
"border.js",
"dimensions.js"
],
"behaviour_modifications": [
"commonEvents.js",
"contextMenu.js",
"dragAndDrop.js"
],
"pre_context": [
"webTrinity.js",
"extStore.js",
"generalHelpers.js"
],
"modifier": [
"modifier.js"
],
"component": [
"wrapperComponent.js",
"modifiableComponent.js",
"addStyleAndFunctions.js",
"component.js"
],
"builder": [
"baseComponents.js",
"builder.js"
],
"extensions": [
"extension.js"
],
"app_context": [
"scriptAndStyleContext.js",
"framework-controls.js",
"context.js"
]
},
"keys": [
"color.js",
"alignment.js",
"arrangement.js",
"siding.js",
"shapes.js",
"border.js",
"dimensions.js",
"commonEvents.js",
"contextMenu.js",
"dragAndDrop.js",
"webTrinity.js",
"extStore.js",
"generalHelpers.js",
"modifier.js",
"wrapperComponent.js",
"modifiableComponent.js",
"addStyleAndFunctions.js",
"component.js",
"baseComponents.js",
"builder.js",
"extension.js",
"scriptAndStyleContext.js",
"framework-controls.js",
"context.js"
],
"objects": {
"color.js": {
"name": "color.js",
"folder": "src"
},
"alignment.js": {
"name": "alignment.js",
"folder": "src"
},
"arrangement.js": {
"name": "arrangement.js",
"folder": "src"
},
"siding.js": {
"name": "siding.js",
"folder": "src/sizeSide"
},
"shapes.js": {
"name": "shapes.js",
"folder": "src/sizeSide"
},
"border.js": {
"name": "border.js",
"folder": "src/sizeSide"
},
"dimensions.js": {
"name": "dimensions.js",
"folder": "src/sizeSide"
},
"commonEvents.js": {
"name": "commonEvents.js",
"folder": "src"
},
"contextMenu.js": {
"name": "contextMenu.js",
"folder": "src/modifications"
},
"dragAndDrop.js": {
"name": "dragAndDrop.js",
"folder": "src/modifications"
},
"webTrinity.js": {
"name": "webTrinity.js",
"folder": "src/context"
},
"extStore.js": {
"name": "extStore.js",
"folder": "src/context"
},
"generalHelpers.js": {
"name": "generalHelpers.js",
"folder": "src/context"
},
"modifier.js": {
"name": "modifier.js",
"folder": "src"
},
"wrapperComponent.js": {
"name": "wrapperComponent.js",
"folder": "src/componentAncestry"
},
"modifiableComponent.js": {
"name": "modifiableComponent.js",
"folder": "src/componentAncestry"
},
"addStyleAndFunctions.js": {
"name": "addStyleAndFunctions.js",
"folder": "src/componentAncestry"
},
"component.js": {
"name": "component.js",
"folder": "src"
},
"baseComponents.js": {
"name": "baseComponents.js",
"folder": "src"
},
"builder.js": {
"name": "builder.js",
"folder": "src"
},
"extension.js": {
"name": "extension.js",
"folder": "src/extensions"
},
"scriptAndStyleContext.js": {
"name": "scriptAndStyleContext.js",
"folder": "src/context"
},
"framework-controls.js": {
"name": "framework-controls.js",
"folder": "src/context"
},
"context.js": {
"name": "context.js",
"folder": "src"
}
}
}

156
generate_single_file.js

@ -0,0 +1,156 @@
const fs = require('fs/promises');
const path = require('path');
/**
* purely convienience
*/
class FileDependecy {
name;
folder;
path() {
return this.folder + '/' + this.name;
}
}
/**
* Object Access Object
* purely convienience
*/
class OAO {
constructor() {
/**
* @type {boolean|Map<string,FileDependecy>}
*/
this.orderedGroups
/**
* @type {Array<string>}
*/
this.keys = [];
/**
* @type {map<string, FileDependecy>}
*/
this.objects = {};
/**
* @type {boolean}
*/
this.isOrdered = false;
}
}
let fileOrder = Object.assign(new OAO(), require('./fileOrder.json'));
fileOrder.objects = fileOrder.keys
.reduce((a, fileName) => Object.assign(
a,
{ [fileName]: Object.assign(new FileDependecy(), fileOrder.objects[fileName]) }
), {});
/**
* Kept for future reference, might be that we will return to the dependency resolvement approach.
* @param {OAO} oao
* @returns {OAO}
*/
function resolveDependencyOrder(oao) {
let roots = oao.keys.filter(k => oao.objects[k].dependencies.length === 0);
let folderList = [...new Set(oao.keys.map(k => oao.objects[k].folder))];
console.log("Folders", folderList);
/**
*
* @param {OAO} oao
* @param {Array<Array<string>>} dealt
*/
function recursiveResolvement(oao, dealtLvls, dealt) {
const arrsEqualSize = (arr1, arr2) => arr1.length === arr2.length;
const arrHasAll = (arr1, arr2) => arr2.every(k => arr1.includes(k))
if (oao.keys.length === dealt.length) {
return [...dealt, ...current];
} else {
console.log(`Received ${dealt.length} dealt of ${oao.keys.length}`);
}
let remaining = oao.keys
.filter(k => !dealt.includes(k));
if (remaining.length < 2) {
return [...dealt, ...remaining];
}
let current = remaining
.filter(k => oao.objects[k].dependencies.every(sk => dealt.includes(sk)));
if (current.length === 0) {
console.log("Couldn't resolve", remaining);
return remaining;
}
dealtLvls.push(current);
if (arrsEqualSize(remaining, current)) {
return [...dealt, ...current];
}
return recursiveResolvement(oao, dealtLvls, [...dealt, ...current]);
}
let recursiveResolved = recursiveResolvement(oao, [roots], roots);
recursiveResolved
.forEach((k, i) => {
oao.objects[k].orderNr = i;
});
oao.isOrdered = recursiveResolved.slice(-1).length <= 2;
if (oao.isOrdered) {
oao.keys = recursiveResolved;
}
return oao;
}
function appendContent(srcFile, targetFile) {
return fs.readFile(srcFile, 'utf8')
.then(content => {
console.log(`Processing '${srcFile}'`);
console.log(` READ: successfully!`)
console.log(` Attepting to append`);
return fs.appendFile(targetFile, content);
})
.then(() => {
console.log(` Append/Write: successfully!`);
})
.catch(error => {
console.error(`Error reading/writing files: ${error.message}`);
})
}
let orderedJoinList = Object.keys(fileOrder.orderedGroups)
.flatMap(groupName => fileOrder.orderedGroups[groupName])
.map(fileName => fileOrder.objects[fileName].path());
/*
// Kept for future reference, might be that we will return to the dependency resolvement approach.
fileOrder = resolveDependencyOrder(fileOrder);
let orderedJoinList = fileOrder.keys
.map(fileName => fileOrder.objects[k].path());
*/
const targetFile = "./jpc-like-websites.js";
console.log("(Re-) Creating target file: '" + targetFile + "'");
/* EMPTY (create?) TARGET FILE */
fs.writeFile(targetFile, "", err => { })
orderedJoinList
.reduce((prevPromise, filePath) => prevPromise
.then(
()=>appendContent(filePath, targetFile)
), Promise.resolve())

25
join_js_files.sh

@ -7,28 +7,5 @@ SRC="src"
# Third "HIGHER_LIST" come several of the commons, the context as well as component
# and thoose that use component.
SUB_LIST="siding.js shapes.js border.js dimensions.js"
MODIFIERS_LIST="alignment.js arrangement.js modifier.js"
HIGHER_LIST="commonEvents.js context.js component.js baseComponents.js builder.js"
echo "" > $TARGET
echo "/* ## color.js ## */" >> $TARGET
cat $SRC/color.js >> $TARGET
echo "/* # SUB_LIST # */" >> $TARGET
for i in $SUB_LIST; do
echo "/* ## $i ## */" >> $TARGET
cat $SRC/sizeSide/$i >> $TARGET
done
echo "/* # MODIFIERS_LIST # */" >> $TARGET
for i in $MODIFIERS_LIST; do
echo "/* ## $i ## */" >> $TARGET
cat $SRC/$i >> $TARGET
done
echo "/* # HIGHER_LIST # */" >> $TARGET
for i in $HIGHER_LIST; do
echo "/* ## $i ## */" >> $TARGET
cat $SRC/$i >> $TARGET
done
node generate_single_file.js

3
package.json

@ -14,6 +14,9 @@
},
"author": "",
"license": "ISC",
"dependencies": {
},
"devDependencies": {
"typescript": "^5.6.2"
}

3
src/alignment.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**

6
src/arrangement.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
@ -15,5 +14,4 @@ const Arrangement = Object.freeze({
SPACE_BETWEEN: "space-between",
SPACE_EVENLY: "space-evenly",
SPACE_AROUND: "space-around",
})
});

132
src/baseComponents.js

@ -1,18 +1,61 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
* Represents a Component (of an HTMLElement) that is capable of receiving input.
* @extends Component
* @inheritdoc
*/
class InputComponent extends Component {
/**
*
* @param {string} element
* @param {Attr} attr
* @param {Modifier} modifier
*/
constructor(element, attr = {}, modifier = null) {
super(element, attr);
this.addStyleClass("el-input-comp");
if (modifier) {
this.modifier(modifier);
}
}
/**
* The parameter makes it optional to trigger the state by a variable
* @param {boolean} readonly
* @returns {Component}
*/
readonly(readonly = true) {
if (readonly) {
this._element.setAttribute("readonly", readonly);
}
return this;
}
}
/**
* Represents container Components.
* Some predefined modifications are applied on the child components.
* @extends Component
* @inheritdoc
*/
class FlexContainerComponent extends Component {
constructor(attr = {}) {
super(document.createElement("div"), attr)
.addStyleClass("flex-container-component")
/**
*
* @param {Attr} attr
* @param {Modifier} modifier
*/
constructor(attr = {}, modifier = null) {
super(document.createElement("div"), attr);
this.addStyleClass("flex-container-component");
if (modifier) {
this.modifier(modifier);
}
}
/**
@ -39,39 +82,79 @@ class FlexContainerComponent extends Component {
}
return super.childContext(innerComponent);
}
/**
*
* @returns {FlexContainerComponent}
*/
distibuteSpacingEvenly() {
console.log("Doing nothing Flexcontainer doesn't know how to distribute spacing evenly - ask the children (Row, Column)");
return this;
}
}
/**
* A FlexContainerComponent, which organizes the children in a column like manner.
* @extends FlexContainerComponent
* @inheritdoc
*/
class Column extends FlexContainerComponent {
constructor(attr = {}) {
super(attr)
.addStyleClass("column-component")
.modifier(
new Modifier()
.setStyleRule("flex-direction", "column")
);
/**
*
* @param {Attr} attr
* @param {Modifier} modifier
*/
constructor(attr = {}, modifier = null) {
super(attr, modifier);
this.addStyleClass("column-component");
this.modifier(
new Modifier()
.setStyleRule("flex-direction", "column")
);
}
/**
* @todo - adapt to extStore logic
* @override
* @returns {Column}
*/
distibuteSpacingEvenly() {
this._element.children.forEach(child => {
child.style["height"] = ((100 - this._element.childElementCount) / innerComponent.length);
})
return this;
}
}
/**
* A FlexContainerComponent, which organizes the children in a row like manner.
*/
*
* @extends FlexContainerComponent
* @inheritdoc
*/
class Row extends FlexContainerComponent {
constructor(attr = {}) {
super(attr)
.addStyleClass("row-component")
.modifier(
new Modifier()
.fillMaxWidth()
.setStyleRule("flex-direction", "row")
)
/**
*
* @param {Attr} attr
* @param {Modifier} modifier
*/
constructor(attr = {}, modifier = null) {
super(attr);
this.addStyleClass("row-component")
if (modifier) {
this.modifier(modifier);
}
this.modifier(
new Modifier()
.fillMaxWidth()
.setStyleRule("flex-direction", "row")
)
}
/**
*
* @param {*} innerComponent
* @param {Component|Array<Component>} innerComponent
* @returns {Row}
*/
childContext(innerComponent) {
@ -93,12 +176,13 @@ class Row extends FlexContainerComponent {
}
/**
*
* @todo - adapt to extStore logic
* @override
* @returns {Row}
*/
distibuteSpacingEvenly() {
this._element.children.forEach(child => {
child.style["width"] = (100 / innerComponent.length);
child.style["width"] = ((100 - this._element.childElementCount) / innerComponent.length);
})
return this;
}

261
src/builder.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
@ -17,210 +16,366 @@ const builder = {
openedChain: {}
},
/**
* @type {Object}
*/
extensions: {},
/**
*
* @param {string} htmlText
* @param {Modifier} modifier
* @returns {Component}
*/
componentFromHTML: function (htmlText, modifier = null) {
/**
* @type {Component}
*/
let compel = new Component(new DOMParser().parseFromString(htmlText, "text/html"));
if (modifier) {
return compel.modifier(modifier);
}
return compel;
},
/**
*
* @param {HTMLElement} element
* @param {Modifier} modifier
* @returns {Component}
*/
componentFromHTMLElement: function (element, modifier = null) {
let newCompel = new Component(element);
if (modifier) {
return newCompel.modifier(modifier);
}
return newCompel;
},
/**
*
* @param {string} tag
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
genTag: function (tag, attr = {}) { return new Component(document.createElement(tag), attr).addStyleClass(`el-${tag}`); },
genTag: function (tag, attr = {}, modifier = null) {
let compel = new Component(document.createElement(tag), attr).addStyleClass(`el-${tag}`);
if (modifier) {
return compel.modifier(modifier);
}
return compel;
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
anchor: function (attr = {}) { return builder.genTag("a", attr); },
anchor: function (attr = {}, modifier = null) { return builder.genTag("a", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
label: function (attr = {}) { return builder.genTag("label", attr); },
label: function (attr = {}, modifier = null) { return builder.genTag("label", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
button: function (attr = {}) { return builder.genTag("button", attr); },
button: function (attr = {}, modifier = null) { return builder.genTag("button", attr, modifier); },
/**
*
* @param {InputTypes|string} type
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
input: function (attr = {}) { return builder.genTag("input", attr); },
input: function (type, attr = {}, modifier = null) {
return new InputComponent(
document.createElement("input"),
Object.assign({ "type": type }, attr),
modifier
)
.addStyleClass(`el-input`);
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Function<Component>}
*/
inputTags: Object.freeze({
button: function (attr = {}, modifier = null) { return builder.input("button", attr, modifier) },
checkbox: function (attr = {}, modifier = null) { return builder.input("checkbox", attr, modifier) },
color: function (attr = {}, modifier = null) { return builder.input("color", attr, modifier) },
date: function (attr = {}, modifier = null) { return builder.input("date", attr, modifier) },
datetime: function (attr = {}, modifier = null) { return builder.input("datetime", attr, modifier) },
datetime_local: function (attr = {}, modifier = null) { return builder.input("datetime-local", attr, modifier) },
email: function (attr = {}, modifier = null) { return builder.input("email", attr, modifier) },
file: function (attr = {}, modifier = null) { return builder.input("file", attr, modifier) },
image: function (attr = {}, modifier = null) { return builder.input("image", attr, modifier) },
month: function (attr = {}, modifier = null) { return builder.input("month", attr, modifier) },
number: function (attr = {}, modifier = null) { return builder.input("number", attr, modifier) },
password: function (attr = {}, modifier = null) { return builder.input("password", attr, modifier) },
radio: function (attr = {}, modifier = null) { return builder.input("radio", attr, modifier) },
range: function (attr = {}, modifier = null) { return builder.input("range", attr, modifier) },
reset: function (attr = {}, modifier = null) { return builder.input("reset", attr, modifier) },
search: function (attr = {}, modifier = null) { return builder.input("search", attr, modifier) },
submit: function (attr = {}, modifier = null) { return builder.input("submit", attr, modifier) },
tel: function (attr = {}, modifier = null) { return builder.input("tel", attr, modifier) },
text: function (attr = {}, modifier = null) { return builder.input("text", attr, modifier) },
time: function (attr = {}, modifier = null) { return builder.input("time", attr, modifier) },
url: function (attr = {}, modifier = null) { return builder.input("url", attr, modifier) },
week: function (attr = {}, modifier = null) { return builder.input("week", attr, modifier) }
}),
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
div: function (attr = {}) { return builder.genTag("div", attr); },
div: function (attr = {}, modifier = null) { return builder.genTag("div", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
span: function (attr = {}) { return builder.genTag("span", attr); },
span: function (attr = {}, modifier = null) { return builder.genTag("span", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
paragraph: function (attr = {}) { return builder.genTag("p", attr); },
paragraph: function (attr = {}, modifier = null) { return builder.genTag("p", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
header: function (sizeGroup, attr = {}) { return builder.genTag(`h${sizeGroup}`, attr); },
header: function (sizeGroup, attr = {}, modifier = null) { return builder.genTag(`h${sizeGroup}`, attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
checkbox: function (attr = {}) { return builder.input({ "type": "checkbox" }) },
checkbox: function (attr = {}, modifier = null) { return builder.input({ "type": "checkbox" }, modifier) },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
select: function (attr = {}) { return builder.genTag("select", attr); },
select: function (attr = {}, modifier = null) { return builder.genTag("select", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
option: function (attr = {}) { return builder.genTag("option", attr); },
option: function (attr = {}, modifier = null) { return builder.genTag("option", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
select: function (attr = {}) { return builder.genTag("select", attr); },
radioBtn: function (attr = {}, modifier = null) { return builder.genTag("radioBtn", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
radioBtn: function (attr = {}) { return builder.genTag("radioBtn", attr); },
icon: function (attr = {}, modifier = null) { return builder.genTag("icon", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
icon: function (attr = {}) { return builder.genTag("icon", attr); },
img: function (attr = {}, modifier = null) { return builder.genTag("img", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
img: function (attr = {}) { return builder.genTag("img", attr); },
textarea: function (attr = {}, modifier = null) {
return new InputComponent(
document.createElement("textarea"),
attr,
modifier
)
.addStyleClass(`el-textarea`);
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
textarea: function (attr = {}) { return builder.genTag("textarea", attr); },
table: function (attr = {}, modifier = null) {
return builder.genTag("table", attr, modifier)
.chainModifier()
.removeStyleRule("display")
.toComponent();
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
table: function (attr = {}) { return builder.genTag("table", attr) },
tableRow: function (attr = {}, modifier = null) { return builder.genTag("tr", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableRow: function (attr = {}) { return builder.genTag("tr", attr) },
tableCell: function (attr = {}, modifier = null) { return builder.genTag("td", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableCell: function (attr = {}) { return builder.genTag("td", attr) },
tableCaption: function (attr = {}, modifier = null) { return builder.genTag("caption", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableCaption: function (attr = {}) { return builder.genTag("caption", attr) },
tableHeadCell: function (attr = {}, modifier = null) { return builder.genTag("th", attr, modifier); },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableHeadCell: function (attr = {}) { return builder.genTag("th", attr) },
tableBody: function (attr = {}, modifier = null) {
return builder.genTag("tbody", attr, modifier)
.chainModifier()
.removeStyleRule("display")
.toComponent();
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableBody: function (attr = {}) { return builder.genTag("tbody", attr) },
tableHead: function (attr = {}, modifier = null) {
return builder.genTag("thead", attr, modifier)
.chainModifier()
.removeStyleRule("display")
.toComponent();
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableHead: function (attr = {}) { return builder.genTag("thead", attr) },
tableFooter: function (attr = {}, modifier = null) {
return builder.genTag("tfoot", attr, modifier)
.chainModifier()
.removeStyleRule("display")
.toComponent();
},
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
tableFooter: function (attr = {}) { return builder.genTag("tfoot", attr) },
iframe: function (attr = {}, modifier = null) { return builder.genTag("iframe", attr, modifier) },
/**
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Component}
*/
form: function (attr = {}) {
return builder.genTag("form", attr)
.addStyleClass("flex-container-component")
.chainModifier()
.setStyleRule("flex-direction", "column")
.ensureModifier()
.toComponent()
form: function (attr = {}, modifier = null) {
return builder.genTag("form", attr, modifier)
.addStyleClass("flex-container-component")
.chainModifier()
.setStyleRule("flex-direction", "column")
.ensureModifier()
.toComponent()
},
/**
*
* @param {*} attr
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Row}
*/
row: function (attr = {}) { return new Row(attr) },
row: function (attr = {}, modifier = null) { return new Row(attr, modifier) },
/**
*
* @param {*} attr
*
* @param {Map<string,string>} attr
* @param {Modifier} modifier
* @returns {Column}
*/
column: function (attr = {}) { return new Column(attr) },
column: function (attr = {}, modifier = null) { return new Column(attr, modifier) },
/**
*
* @param {*} innerComponents
* @todo upwards bubbling of js or css is not dealt with yet.
*
*
* @param {Component} innerComponents
*/
page: function (innerComponents) {
let main = document.querySelector('main')
let main = document.querySelector('main');
main.parentElement.insertAdjacentElement(
"afterbegin",
builder.genTag("main")
.alignment(Alignment.CENTER)
.arrangement(Arrangement.CENTER)
.childContext(innerComponents)
.generate()
)
.isHigherComponent()
.alignment(Alignment.CENTER)
.arrangement(Arrangement.CENTER)
.childContext(innerComponents)
.generate()
.html
);
Page.generate();
main.remove();
}

5
src/color.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
@ -620,4 +619,4 @@ const MaterialFiveHundredlColors = Object.freeze({
GOLD: new Color(255, 235, 59),
YELLO_ORANGE: new Color(255, 152, 0),
ORANGE: new Color(255, 87, 34),
})
});

15
src/commonEvents.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
@ -10,6 +9,12 @@
*/
const CommonEvents = Object.freeze({
ONCLICK: "onclick",
ONCHANGE: "onchange"
})
ONCHANGE: "onchange",
SCROLL: "scroll",
DRAG_START: "dragstart",
DRAG_END: "dragend",
DRAG_ENTER: "dragenter",
DRAG_LEAVE: "dragleave",
DRAG_OVER: "dragover",
DROP: "drop",
});

594
src/component.js

@ -1,246 +1,526 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
* A chainable HTMLElement builder.
* Represents the most basic and simple form of a Component.
* It is mainly a collection of wrapper methods
* around the HTMLElement methods to make them chainable.
* It serves as base for further functionallity extensions.
* @extends StyleAndScriptStoringComponent
* @inheritdoc
*
*/
class Component {
_element;
_modifier
_alignment;
_arrangement;
class Component extends StyleAndScriptStoringComponent {
/**
* @type {boolean}
*/
#isCompel;
/**
* @type {WebTrinity}
*/
_wenity;
/**
* @type {Array<any>}
*/
_toRegister;
/**
* @type {boolean}
*/
_isContextMenu;
/**
* Initializes the component
* @param {HTMLElement} element the base element
* @param {Map<string,string>} attr Specific already known attributes
*/
constructor(element, attr = {}) {
this._modifier = new Modifier().margin(new Sides().all(0));
var akeys = Object.keys(attr);
for (let i = 0; i < akeys.length; i++) {
element.setAttribute(akeys[i], attr[akeys[i]]);
}
this._element = element;
super(element, attr);
this.#isCompel = false;
this._isContextMenu = false;
this._modifier = new Modifier()
.margin(new Sides().all(0));
this._modifier._modifications['display'] = "flex";
this._modifier._modifications["box-sizing"] = "border-box";
this._toRegister = [];
}
/**
* Sets the alignment (modifications) for this element or more specific for its children.
* @param {Alignment} alignment
* Adds a class to classList via HTMLElement.classList.add() method.
* Further collects rules in a property until generate is called.
*
* @CAUGHTION implementation is not safe to use, ignoring extStore is recommended;
*
* @todo difference between stylings and classes, extStore logic in combination with the Page.register... logic
*
* @override
*
* @param {string} styleClass (without the '.' in the front)
* @param {string|Modifier|map<string,string>} styling
* @param {ExtStorage|ExtStoreType|ExtStorePosition|OverwriteBehaviour|EXPosConfer|ESOverwriteConfer} extStore
* if a unique definition is desired, all constants or configurator objects are allowed - they will be processed accordingly
* @returns {Component} this component object
*/
alignment(alignment) {
this._alignment = alignment;
addStyleClass(styleClass, styling = null, extStore = null) {
if (!extStore) {
extStore = this._styleClassesExtStore;
} else if (extStore.isMissing()) {
extStore = extStore.fillBy(this._styleClassesExtStore);
}
this._modifier._modifications["display"] = "flex";
this._modifier._modifications["align-content"] = alignment;
this._modifier._modifications["align-items"] = alignment;
this._modifier._modifications["text-align"] = alignment;
//this._modifier._modifications["justify-content"] = alignment;
if (styling) {
if (styling instanceof Modifier) {
styling = styling._modifications;
}
Page.registerStyling('.' + styleClass, styling);
}
this._element.classList.add(styleClass);
return this;
}
/**
* Sets the arrangement (modifications) for this element or more specific for its children.
* @param {Arrangement} arrangement
* @returns {Component} this component object
*
* @param {boolean} vertical Defines if the Component should overflow vertically (default: true)
* @param {boolean} horizontal Defines if the Component should overflow horizontally (default: false)
* @returns {Component}
*/
arrangement(arrangement) {
this._arrangement = arrangement;
switch (arrangement) {
case Arrangement.START:
this._modifier._modifications["justify-content"] = "start";
break;
case Arrangement.END:
this._modifier._modifications["justify-content"] = "end";
break;
case Arrangement.CENTER:
this._modifier._modifications["justify-content"] = "center";
break;
case Arrangement.SPACE_AROUND:
this._modifier._modifications["justify-content"] = "space-around";
break;
case Arrangement.SPACE_BETWEEN:
this._modifier._modifications["justify-content"] = "space-between";
break;
case Arrangement.SPACE_EVENLY:
this._modifier._modifications["justify-content"] = "space-evenly";
break;
overflow(vertical = true, horizontal = false) {
if (vertical) {
this.subscribeOnGenerate(CommonCompelGroups.OVERFLOWING);
this._modifier._modifications["overflow-y"] = "auto";
}
if (horizontal) {
this.subscribeOnGenerate(CommonCompelGroups.OVERFLOWING);
this._modifier._modifications["overflow-x"] = "auto";
}
return this;
}
/**
*
* @param {Modifier} modifier
* @returns {Component} this component object
* @param {boolean} untilFound
* @returns {Component}
*/
modifier(modifier) {
this._modifier = this._modifier.join(modifier.ensureModifier())
hidden(untilFound = false) {
Page.registerStyling(".compel-mech-hidden", { "hidden": "hidden" });
this._modifier.removeStyleRule("display");
this.setAttribute(
"hidden",
(untilFound ? "until-found" : "hidden")
);
this.subscribeOnGenerate(CommonCompelGroups.HIDDEN_ON_START);
return this;
}
/**
* Sets the innerText of the element
* @param {string} text
* @returns {Component} this component object
*
* @returns {Component}
*/
text(text) {
this._element.innerText = text;
return this;
isHigherComponent() {
this.subscribeOnGenerate(CommonCompelGroups.HIGHER_COMPEL);
this.#isCompel = true;
return this.setAttribute("data-compel-isHCompel", "true")
}
title(text) {
this._element.title = text;
/**
* Collects the given List in the _toRegister attribute.
* When generate() is called,
* the created Element will be registered (added) in every list
* within the list.
* @param {*|string|Array<*>} listName
*/
subscribeOnGenerate(listName) {
this._toRegister.push(listName);
return this;
}
/**
*
* @param {string} styleClass
* @param {Modifier} modifier
* @returns {Component} this component object
* @returns {Component}
*/
addStyleClass(styleClass, modifier = null) {
if (modifier) {
Page.registerStyling(styleClass, modifier._modifications);
registerAsContextMenu() {
this.subscribeOnGenerate(CommonCompelGroups.IS_CONTEXT_MENU);
this._isContextMenu = true;
return this.addStyleClass('contextmenu')
.hidden();
}
/**
* @todo Positioning of the contextmenu element
* @todo extract into an extra function(allity) provider
*
* @param {Component} component
* @param {Sides|Function<HTMLElement|Event> => Sides} getRefPos
* @param {ExtStorage} [extStore=null]
* @returns {Component}
*/
contextMenu(component, getRefPos = null, extStore = null) {
if (!component._isContextMenu) {
component.registerAsContextMenu();
}
this._element.classList.add(styleClass);
return this;
if (!getRefPos) {
getRefPos = function (cmEvent) {
return new Sides()
.left(cmEvent.pageX)
.top(cmEvent.pageY);
}
}
this.subscribeOnGenerate(CommonCompelGroups.HAS_CONTEXT_MENU);
let identifier = component._compName;
this.addEventListener(
"contextmenu",
DefaultContextMenu.openContextMenuAction(identifier, getRefPos)
);
return this.childContext(component);
}
registerStyleClass(styleClass, styleRuleMap) {
Page.registerStyling('.' + styleClass, styleRuleMap);
return this.addStyleClass(styleClass);
/**
*
* @param {DragAndDropImplementation} dadImpl
* @returns {Component}
*/
draggable(dadImpl = new DragAndDropImplementation()) {
this.subscribeOnGenerate(CommonCompelGroups.DRAGGABLE);
this.subscribeOnGenerate(CommonCompelGroups.HAS_DRAG_EVENT);
let addedClass = "comp-el-mech-draggable";
let selector = this._element.getAttribute("data-autocompel");
selector = `.${addedClass}[data-autocompel="${selector}"]`;
return this.addStyleClass(addedClass)
.setAttribute("draggable", "true")
.setAttribute("dropEffect", "none")
.addEventListener(
CommonEvents.DRAG_START,
dadImpl.dragStartAction("text/plain", selector)
);
}
/**
*
* @param {string} key
* @param {string} value
* @returns {Component} this component object
* @param {EventDrag} dragEvent
* @param {Function} action
*/
onDrag(dragEvent, action = (e) => { e.preventDefault(); }) {
this.subscribeOnGenerate(CommonCompelGroups.HAS_DRAG_EVENT);
let selector = `comp-el-mech-drag${dragEvent}`;
return this.addEventListener(
'drag' + dragEvent,
action
);
}
/**
*
* @param {DragAndDropImplementation} dadImpl
* @returns {Component}
*/
setAttribute(key, value) {
this._element.setAttribute(key, value);
dropTarget(dadImpl = new DragAndDropImplementation()) {
this.subscribeOnGenerate(CommonCompelGroups.DROP_TARGET);
let specialClass = "comp-el-mech-droptarget";
this.addStyleClass(specialClass)
.onDrag(EventDrag.OVER);
let selector = `.${specialClass}[data-autocompel="${this._compName}"]`
this._element.addEventListener(
"drop",
dadImpl.dropEventAction("text/plain", selector)
);
return this;
}
/**
* Ends chain.
* Applies all modifications on the element.
* @returns {HTMLElemment} the html element
* An echo of Scope-Functions from kotlin for convenience
*
* Executes a given function injects this component into the function.
* @param {Function} func
* @returns {Component}
*/
generate() {
/* apply styling to element */
var mkeys = Object.keys(this._modifier._modifications);
for (let i = 0; i < mkeys.length; i++) {
this._element.style[mkeys[i]] = this._modifier._modifications[mkeys[i]];
}
/* subscribe/register to lists */
for (let i = 0; i < this._toRegister.length; i++) {
this._toRegister[i].push(this._element);
}
return this._element;
apply(func) {
func(this);
return this;
}
/**
* Opens a context to create element children.
* Either as one component or a list/array of components.
* @param {Component|Array<Component>} component
* @returns {Component} this component object
* An echo of Scope-Functions from kotlin for convenience
*
* Executes a given function injects the htmlelement of this component into the function.
* @param {Function} func
* @returns {Component}
*/
childContext(component) {
if (arguments.length > 1) {
for (let i = 0; i < arguments.length; i++) {
this.childContext(arguments[i]);
}
} else if (component instanceof Array) {
for (let i = 0; i < component.length; i++) {
this.childContext(component[i]);
}
} else {
this._element.append(
(component instanceof Component
? component
: component.toComponent()
)
.generate()
);
}
applyToEl(func) {
func(this._element)
return this;
}
/**
* @deprecated
* @param {Array<Component>} innerComponent
* @returns {Component} this component object
* Defines how a child Component is to be appended.
* @param {Component} component the child component to add it.
* @returns {HTMLElement}
*/
componentChildren(innerComponent) {
for (let i = 0; i < innerComponent.length; i++) {
this.childContext(innerComponent[i]);
_appendChildComponent(component) {
let child = new WebTrinity();
if (component instanceof Component) {
child = component.generate();
}
return this;
if (component instanceof WebTrinity) {
child = component;
}
if (component instanceof HTMLElement) {
console.log("No wenity set - htmlEl was given");
child.html = component;
}
this._element.append(child.html);
return child;
}
/**
*
* @param {CommonEvent} commonEvent
* @param {string} functionName
* @returns {Component} this component object
* @param {ExtStorage} extStore
* @returns {Array<SStoreDefinition>}
*/
setEvent(commonEvent, functionName) {
return this.setAttribute(commonEvent, `${functionName}(this)`);
_processStyles(extStore = null) {
extStore = (extStore
? extStore
: this._stylesExtStore
)
.setupForGeneralStyling();
let forCollection = [];
let counter = 0;
for (const ssd of this._styles) {
/* Make sure that the type is unified for later processing */
if (ssd._definition instanceof Modifier) {
ssd._definition = ssd._definition._modifications;
}
/* Check/Ensure proper ExtStorageType for following comparison */
/**
* @type {ExtStorage}
*/
let curExtStore = extStore;
if (Object.hasOwn(ssd, "_extStore") && ssd._extStore) {
curExtStore = ssd._extStore.setupForGeneralStyling();
}
if (curExtStore.getStylingDistribution()(ssd, this._element, counter)) {
forCollection.push(ssd);
}
}
return forCollection;
}
/**
*
* @returns {ChainableModifier}
* @param {ExtStorage} extStore
* @returns {Array<SStoreDefinition>}
*/
chainModifier() {
return new ChainableModifier(this);
_processFunctions(extStore = null) {
extStore = (extStore
? extStore
: this._functionsExtStore
)
.setupForFunctions();
const forCollection = new Map();
const collectForBefore = [];
let counter = 0;
for (const ssd of this._functions) {
/* Make sure that the type is unified for later processing */
let curExtStore = extStore;
if (Object.hasOwn(ssd, "_extStore") && ssd._extStore) {
curExtStore = ssd._extStore.setupForFunctions();
}
if (curExtStore.getFunctionDistribution()(ssd, counter)) {
if (curExtStore._position.BEFORE) {
collectForBefore.push(ssd);
} else {
if (!forCollection.has(curExtStore)) {
forCollection.set(curExtStore, []);
}
forCollection.get(curExtStore).push(ssd);
}
}
}
return forCollection;
}
/**
* Collects the given List in the _toRegister attribute.
* When generate() is called,
* the created Element will be registered (added) in every list
* within the list.
* @param {Array} listName
* Ends chain.
* Applies all modifications on the element.
* Processes alls stored additions.
* Returns the constructed HTMLElement of this Component.
*
*
*
* @param {ExtStorage}
* @returns {WebTrinity} the constructed HTMLElement of this Component.
*/
subscribeOnGenerate(listName) {
this._toRegister.push(listName);
return this;
}
generate(styleStore = null, functionStore = null) {
this._wenity = new WebTrinity();
if (!styleStore) {
styleStore = this._stylesExtStore;
}
/* DEAL WITH COMPONENT MODIFICATION FIRST */
// @todo pay attention to the "overwrite" behaviour - the local modifier styles are the "closest"
// it might be appropriate to use this._styles.unshift(...) instead.
this._styles.push(new SStoreDefinition(
(styleStore._aggregation !== ESAggregation.INTERNALIZED ? "." : "") + this._compName,
this._modifier,
this._stylesExtStore
));
/* DEAL WITH CHILDREN */
let collectedWenities = [];
for (let i = 0; i < this._children.length; i++) {
/**
* @type {Component}
*/
let child = this._children[i];
if (child instanceof ChainableModifier) {
child = child.toComponent();
}
if (Page._useCssCalc) {
child._modifier._updateDimensionsBy(this._modifier._paddingValues);
}
child = child.generate();
let wenity = this._appendChildComponent(child);
/*
clickable(eventName) {
let cssClass = "button-like"
this.addStyleClass(cssClass)
this._modifier._modifications["box-shadow"] = "4px 6px #999";
if (!wenity.isSSEmpty()) {
collectedWenities.push(wenity);
}
}
/* DEAL WITH STYLING AND PROCESSING */
/**
* @type {Array<SStoreDefinition>}
*/
let styleCollection = this._processStyles(styleStore);
/**
* @type {Map<ExtStoreType, Array<SStoreDefinition>>}
*/
const funcCollections = this._processFunctions(functionStore);
/**
*
* @param {Map<ExtStoreType, Array<SStoreDefinition>>} source
* @param {Map<ExtStoreType, Array<SStoreDefinition>>} target
* @param {ExtStoreType} extStoreType
* @returns
*/
function transferCollectedFunctions(source, target, extStoreType) {
if (source) {
if (source.has(extStoreType)) {
if (funcCollections.has(extStoreType)) {
target.get(extStoreType)
.push(source.get(extStoreType))
} else {
target.set(
extStoreType,
source.get(extStoreType)
);
}
}
}
return target;
}
function executeOnExtStoreTypeCollectedTriple(func) {
return new Map([
{ [ExtStoreType.COLLECTED_SEGMENT_BEGIN]: func(ExtStoreType.COLLECTED_SEGMENT_BEGIN) },
{ [ExtStoreType.COLLECTED_DOC_HEAD]: func(ExtStoreType.COLLECTED_DOC_HEAD) },
{ [ExtStoreType.COLLECTED_DOC_FOOTER]: func(ExtStoreType.COLLECTED_DOC_FOOTER) }
]);
}
for (let i = 0; i < collectedWenities.length; i++) {
const child = collectedWenities[i];
if (child.js) {
executeOnExtStoreTypeCollectedTriple(
(extstoretype) => transferCollectedFunctions(child.js, funcCollections, extstoretype)
);
}
}
let bckk = "background-color";
if (this._modifier._modifications.hasOwnProperty(bckk)) {
let bckc = this._modifier._modifications[bckk];
delete this._modifier._modifications[bckk];
Page.registerStyling(`.${cssClass}`, {
[bckk]: bckc
})
if (this.#isCompel) {
function dealCollected(map, extStoreType) {
if (map.has(extStoreType)) {
let collectionScriptTag = generateAndFillScriptTag(map.get(extStoreType));
if (extStoreType === ExtStoreType.COLLECTED_SEGMENT_BEGIN) {
this._element.insertAdjacentElement(
"afterbegin",
generateAndFillScriptTag(segment)
);
} else {
Page.addElementToPage(
collectionScriptTag,
extStoreType
);
}
}
}
executeOnExtStoreTypeCollectedTriple((est) => dealCollected(funcCollections, est));
} else {
this._wenity.js = funcCollections;
this._wenity.css = styleCollection;
}
Page.registerStyling(`.${cssClass}:hover`, {
"background-color": Colors.greenyellow.cssRGBString(),
})
Page.registerStyling(`.${cssClass}:active`, {
"background-color": Colors.greenyellow.cssRGBString(),
"box-shadow": "1px 2px #666",
"transform": "translateY(4px)"
})
this._wenity.html = this._element;
return this.setEvent(CommonEvents.ONCLICK, eventName);
for (let i = 0; i < this._toRegister.length; i++) {
const group = this._toRegister[i];
Page.subscribeComponentToGroup(group, this._compName)
}
return this._wenity;
}
*/
}

180
src/componentAncestry/addStyleAndFunctions.js

@ -0,0 +1,180 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* @inheritdoc
* @abstract
* @extends ModifiableComponent
*/
class StyleAndScriptStoringComponent extends ModifiableComponent {
/**
* @type {ExtStorage}
*/
_styleClassesExtStore
/**
* @type {ExtStorage}
*/
_stylesExtStore;
/**
* @type {Array<SStoreDefinition>}
*/
_styles;
/**
* @type {ExtStorage}
*/
_functionsExtStore;
/**
* @type {Array<SStoreDefinition>}
*/
_functions;
constructor(element, attr = {}) {
super(element, attr);
this._styleClassesExtStore = ExtStoreType.CENTRALIZED_DOC_HEAD
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE);
this._stylesExtStore = ExtStoreType.INTERNALIZED_WITHIN
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE);
this._styles = [];
this._functionsExtStore = ExtStoreType.CENTRALIZED_DOC_HEAD
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE);
this._functions = [];
}
/**
* @todo: Unify logic extract modifications into responsible construct
* @todo: Make it work as expected, fix docu
* @todo: Differentiate between directions (horizontal, vertiacl)
* @override
* @inheritdoc
* Sets the alignment (modifications) for this element or more specific for its children.
* @param {Alignment} alignment
* @returns {Component} this component object
*/
alignment(alignment) {
/*
this._modifier._modifications["display"] = "flex";
this._modifier._modifications["align-content"] = alignment;
this._modifier._modifications["align-items"] = alignment;
this._modifier._modifications["text-align"] = alignment;
*/
this._alignment = alignment;
this._modifier._modifications["align-content"] = this._alignment;
this._modifier._modifications["align-items"] = this._alignment;
this._modifier._modifications["text-align"] = this._alignment;
return this;
}
/**
* @todo: Unify logic extract modifications into responsible construct
* @todo: Differentiate between directions (horizontal, vertical)
* @todo: Make it work as expected, fix docu
* @override
* @inheritdoc
* Sets the arrangement (modifications) for this element or more specific for its children.
* @param {Arrangement} arrangement
* @returns {Component} this component object
*/
arrangement(arrangement) {
/*
this._modifier._modifications["justify-content"] = arrangement;
*/
this._arrangement = arrangement;
this._modifier._modifications["justify-content"] = this._arrangement;
return this;
}
/**
* Defines/Sets the general "storage-behaviour" for styling of this component.
* Further for potential css definitions.
* If a styling/modifier/class is passed via the corresponding methods/way,
* an alternative behaviour can be passed as a parameter - which will only be applied on/for that single one.
*
* @param {ExtStorage|ExtStoreType|OverwriteBehaviour} extStore
* @returns {Component}
*/
setStylingsStorage(extStore) {
if (extStore) {
if (extStore instanceof ExtStorage) {
this._stylesExtStore = extStore;
} else if (extStore instanceof ExtStoreType) {
this._stylesExtStore.setExtStoreType(extStore);
} else {
this._stylesExtStore.OverwriteBehaviour(extStore);
}
} else {
console.log("(Style)ExtStore was empty, did nothing");
}
return this;
}
/**
* Defines/Sets the general "storage-behaviour" for functions of this component.
* If a function is passed via "registerFunction",
* an alternative behaviour can be passed as a parameter - which will only be applied on/for that single one.
*
* @param {ExtStorage|ExtStoreType|OverwriteBehaviour} extStore
* @returns {Component}
*/
setFunctionsStorage(extStore) {
if (extStore) {
if (extStore instanceof ExtStorage) {
this._stylesExtStore = extStore;
} else if (extStore instanceof ExtStoreType) {
this._stylesExtStore.setExtStoreType(extStore);
} else {
this._stylesExtStore.OverwriteBehaviour(extStore);
}
} else {
console.log("(Function)ExtStore was empty, did nothing");
}
return this;
}
/**
*
* @param {Function} func
* @param {string} underTheName
* @param {ExtStorage|ExtStoreType|ExtStorePosition|OverwriteBehaviour|EXPosConfer|ESOverwriteConfer} extStore
* if a unique definition is desired, all constants or configurator objects are allowed - they will be processed accordingly
* @returns
*/
registerFunction(func, underTheName = "", extStore = null) {
let registrationName = [
underTheName.trim(),
func.name.trim(),
`func${this._compName}${Object.keys(this._functions).length}`
]
.find(e => e !== '');
/*
if (!extStore) {
extStore = this._functionsExtStore;
} else if (extStore instanceof ExtStoreConfer) {
extStore = extStore.ensureStore();
} else {
extStore = new ExtStorage().setSingleValueToClone(extStore, this._functionsExtStore);
}
*/
let entry = new SStoreDefinition();
entry._identifier = registrationName;
entry._definition = func;
entry._extStore = extStore;
this._functions.push(entry);
//identifyAndResolveOverwrite(this._functions, registrationName, entry, extStore._overwriteBehaviour);
return this;
}
}

60
src/componentAncestry/modifiableComponent.js

@ -0,0 +1,60 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* @inheritdoc
* @extends ChildbearerComponent
* @abstract
*/
class ModifiableComponent extends ChildbearerComponent {
/**
* @type {Modifier} modifier
*/
_modifier;
constructor(element, attr = {}) {
super(element, attr);
this._modifier = new Modifier();
}
/**
* Sets, updates or overwrites the Modifier-Object for this component
* @param {Modifier} modifier
* @param {boolean|ExtStorage|ExtStoreType|OverwriteBehaviour} [extStore=null]
* @returns {Component} this component object
*/
modifier(modifier, underTheName = "", extStore = false) {
if (underTheName === "") {
underTheName = `.${this._compName}-style-${this._styles.length}`;
}
if (!extStore) {
this._modifier = this._modifier
.join(modifier.ensureModifier());
} else {
this.addStyleClass(underTheName);
this._styles.push(
new SStoreDefinition(
underTheName,
modifier.ensureModifier()
)
);
}
return this;
}
/**
* Returns a Modifier to chain modifications
* instead of setting them within an sepperate context.
* @returns {ChainableModifier}
*/
chainModifier() {
return new ChainableModifier(this);
}
}

200
src/componentAncestry/wrapperComponent.js

@ -0,0 +1,200 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* Represents the most basic and simple form of a Component.
* It is mainly a collection of wrapper methods
* around the HTMLElement methods to make them chainable.
* It serves as base for further functionallity extensions.
* @abstract
*/
class ElementWrapper {
/**
* The basic HTMLElement the Component is wrapped around.
* It will be modified in several ways and in the end returned.
* @type {HTMLElement}
*/
_element;
/**
* The auto-generated name of the component.
* @type {string}
*/
_compName;
/**
* The auto-generated name of the component.
* @type {string}
*/
_givenName;
/**
* Initializes the component
* @param {HTMLElement} element the base element
* @param {map<string,string>} attr Specific already known attributes
*/
constructor(element, attr = {}) {
fillAttrsInContainerByCb(
attr,
element,
function cb(k, v, con) {
con.setAttribute(k, v);
}
);
this._compName = Page.autoRegisterComponent();
element.setAttribute('data-autocompel', this._compName);
this._element = element;
this.addStyleClass(this._compName);
}
setComponentName(name) {
this._givenName = name;
this.setAttribute('data-compel', name);
Page.registerComponent(name);
return this;
}
/**
* (Wrapper) Sets the innerText of the element
* @param {string} text
* @returns {Component} this component object
*/
text(text) {
if (this._element instanceof HTMLInputElement && this._element.type === "text") {
this._element.value = text;
} else {
this._element.innerText = text;
}
return this;
}
/**
* Wrapper to set the HTMLElement.title attribute
* @param {string} text
* @returns {Component}
*/
title(text) {
this._element.title = text;
return this;
}
/**
* Wrapper for HTMLElement.classList.add()
* @param {string} styleClass
* @returns {Component} this component object
*/
addStyleClass(styleClass) {
this._element.classList.add(styleClass);
return this;
}
/**
* Wrapper for the HTMLElement.setAttribute() method.
* @param {string} key
* @param {string} value
* @returns {Component} this component object
*/
setAttribute(key, value) {
this._element.setAttribute(key, value);
return this;
}
/**
* Wrapper for the HTMLElement.addEventListener()
* @param {keyof WindowEventMap|CommonEvents} theEvent
* @param {Function} theListener
* @param {boolean|AddEventListenerOptions} options
* @returns {Component} this component object
*/
addEventListener(theEvent, theListener, options = null) {
this._element.addEventListener(theEvent, theListener, options)
return this;
}
}
/**
* @inheritdoc
* @extends ElementWrapper
* @abstract
*/
class ChildbearerComponent extends ElementWrapper {
/**
* @type {Array<Component>} children
*/
_children;
/**
* @type {Alignment} alignment
*/
_alignment;
/**
* @type {Arrangement} arrangement
*/
_arrangement;
constructor(element, attr = {}) {
super(element, attr);
this._children = [];
}
/**
* @todo: Unify logic extract modifications into responsible construct
* @todo: Make it work as expected, fix docu
* @todo: Differentiate between directions (horizontal, vertiacl)
*
* Sets the alignment (modifications) for this element or more specific for its children.
* @param {Alignment} alignment
* @returns {Component} this component object
*/
alignment(alignment) {
/*
this._modifier._modifications["display"] = "flex";
this._modifier._modifications["align-content"] = alignment;
this._modifier._modifications["align-items"] = alignment;
this._modifier._modifications["text-align"] = alignment;
*/
this._alignment = alignment;
return this;
}
/**
* @todo: Unify logic extract modifications into responsible construct
* @todo: Differentiate between directions (horizontal, vertical)
* @todo: Make it work as expected, fix docu
*
* Sets the arrangement (modifications) for this element or more specific for its children.
* @param {Arrangement} arrangement
* @returns {Component} this component object
*/
arrangement(arrangement) {
/*
this._modifier._modifications["justify-content"] = arrangement;
*/
this._arrangement = arrangement;
return this;
}
/**
* Opens a context to create children elements.
* Either as one component or a list/array of components.
* @param {Component|Array<Component>} component
* @returns {Component} this component object
*/
childContext(component) {
if (arguments.length > 1) {
for (let i = 0; i < arguments.length; i++) {
this.childContext(arguments[i]);
}
} else if (component instanceof Array) {
for (let i = 0; i < component.length; i++) {
this.childContext(component[i]);
}
} else {
this._children.push(component);
}
return this;
}
}

277
src/context.js

@ -1,163 +1,123 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
* @todo Potentially class implementation could be removed entirely.
* Since it (at the time being) is only working as a singleton anyway.
* Further maybe rename to 'AppBuilder' instead of 'PageBuilder',
* page might later become part of a navigation setup.
*
*
* The class provides overreaching options for building the website.
*/
class PageBuilder {
#cssClasses;
#functions;
#delayedFunctions;
#repeatingFunctions;
#functionNames;
#cssElementIdentifiers;
* @extends ScriptAndStyleContext
*/
class PageBuilder extends ScriptAndStyleContext {
/**
* @type {Array<string>}
*/
#autoRegisteredComponents;
/**
* @type {Array<string>}
*/
#registeredComponents;
/**
* @type {boolean}
*/
#showFrameworkConsole;
/**
* @type {Array<CompelExtension>}
*/
_extensions;
constructor() {
this.#cssClasses = document.createElement("style");
this.#functions = document.createElement("script");
this.#functionNames = [];
this.#cssElementIdentifiers = [];
super();
this.#showFrameworkConsole = false;
this.#autoRegisteredComponents = [];
this.#registeredComponents = [];
this._extensions = [];
this._groups = new Map();
}
/**
* Registers a function to be added later in a script tag in the head of the document.
* @ATTENTION Be careful with intended empty strings (e.g. in variable values),
* empty strings within the function code will be shrunk.
* @param {string} name
* @param {function} fun
*
* @param {*|Array<*>} groups
* @param {Component} component
*/
registerFunction(name, fun) {
/**
* Is supposed to shrink all empty strings to length 1
* @param {string} text
* @returns {string}
*/
function shrinkEmptyStrings(text){
for (let i = 1; i < 10; i++) {
text = text.replaceAll(" ".slice(i), ' ');
subscribeComponentToGroup(groups, component) {
if (groups instanceof Array && !(groups instanceof String)) {
for (let i = 0; i < groups.length; i++) {
this.subscribeComponentToGroup(groups[i], component);
}
return text;
}
if (!this.#functionNames.includes(name)) {
let clearedFuncText = shrinkEmptyStrings(
fun.toString()
.replaceAll('\n', ' ')
.replaceAll('\r\n', ' ')
.replaceAll('\n\r', ' ')
);
let isFuncWritten = clearedFuncText.startsWith('function');
let funcHasName = fun.name && fun.name.trim() !== '';
if(isFuncWritten){
let isNameInFuncText = clearedFuncText.startsWith(`function ${name}`);
this.#functions.innerText += (funcHasName && isNameInFuncText
? clearedFuncText
: clearedFuncText.replace('function ', 'function '+name)
)+'; ';
}else{
this.#functions.innerText += `const ${name} = ${clearedFuncText}; `
} else {
if (!this._groups.has(groups)) {
this._groups.set(groups, []);
}
this.#functionNames.push(name);
this._groups.get(groups).push(component);
}
return this;
}
registerNamedFunction(namedFunction) {
return this.registerFunction(namedFunction.name, namedFunction)
autoRegisterComponent() {
let compName = 'comp-el-' + this.#autoRegisteredComponents.length;
this.#autoRegisteredComponents.push(compName);
return compName;
}
/**
* @experimental Attention is adviced, registration mechanism doesn't work yet
* @param {string} name The name the interval will be tied to
* @param {Function} fun the function that is supposed to be executed repeatedly
* @param {number} interval the time in ms between executions
*/
registerRepeatingFunction(name, fun, interval) {
if (!Object.keys(this.#repeatingFunctions).includes(name)) {
this.#repeatingFunctions[name] = {
name: name,
fun: fun,
interval: interval
};
}
registerComponent(compName) {
this.#registeredComponents.push(compName);
return compName;
}
/**
* Adds the styling rules to the element identifiers into the style tag.
* An elementDefinition can only be used once, repeated use will be ignored.
* @param {string} elementDefinition The element identifier
* @param {Map<string, string>} styleRuleMap The Styling rules/values
*/
registerStyling(elementDefinition, styleRuleMap) {
if (!this.#cssElementIdentifiers.includes(elementDefinition)) {
this.#cssClasses.innerText += `${elementDefinition
} {${Object.keys(styleRuleMap)
.map(e => e + ": " + styleRuleMap[e] + "; ")
.join(" ")
}} `
this.#cssElementIdentifiers.push(elementDefinition)
}
}
/**
* Adds into the (head) document.
* - script tag
* - function tag
* - sets and registers repeatedly executed functions
* - sets (timeout and) functions that are supposed to be executed after load
* Inserts the given element according to the extStore into the page/document.
* The refElement is a reference element for the case
* that extStore._position defines "before" or "segment_begin",
* which will then look for the refElement as the corresponding insert reference.
*
* @param {HTMLElement|Component} element
* @param {ExtStorage} extStore
* @param {HTMLElement|Component} refElement
*/
generate() {
let head = document.querySelector("head");
head.appendChild(this.#functions)
head.appendChild(this.#cssClasses)
/* set repeating functions */
if (this.#repeatingFunctions) {
let repeatedFun = Object.values(this.#repeatingFunctions)
.reduce((a, c, i, arr) => Object.assign(a, {
[c.name]: setInterval(c.fun, c.interval)
}), {});
}
addElementToPage(element, extStore = ExtStoreType.CENTRALIZED_DOC_HEAD) {
let { insertCallEl, relativePositioning } = {};
relativePositioning = extStore.getRelativePositioning();
insertCallEl = extStore.getRefElement(element);
insertCallEl.insertAdjacentElement(
relativePositioning,
element
);
/* set timeouts for funcitons executed after load */
if (this.#delayedFunctions) {
for (let i = 0; i < this.#delayedFunctions.length; i++) {
let func = this.#delayedFunctions[i];
if (func.repeat) {
setTimeout(setInterval(func.func, func.interval), func.dl, func.args);
} else {
setTimeout(func.func, func.dl, func.args);
}
}
}
console.log(this.#functionNames);
}
/**
* Registeres a function to be executed after page-load
* @param {Function} func the function that will be executed
* @param {number} delay the time in ms the execution is delayed after load
* @param {string} name if provided the function will be registered as well
* @param {Array<any>} args arguments for the function
* @param {boolean} repeat defines if the function is supposed to be repeated as well
* @param {number} interval if the function is supposed to repeat, this defines the interval of repetition
* Determines that the jpc-like-websites libs shouldn't be part of the resulting page.
* Therefore the generate() methods will package/generate finalized js, css and html elements
* into the final html page in the end.
* Especially tricky are reusable elements and functions that use such.
*
* @todo This method/feature will have to have a logic implemented for state altering components
* that then can be "packaged" into a single page.
* @todo This method/feature will work only, if an automatic reuse logic for elements/components is implemented within the jpc lib.
* @ATTENTION DO NOT USE
*/
executeAfterLoad(func, delay = 1000, name = '', args = [], repeat = false, interval = 5000) {
if (name !== '') {
this.registerFunction(name, func);
}
if (!this.#delayedFunctions) {
this.#delayedFunctions = [];
}
this.#delayedFunctions.push({ dl: delay, func: func, args: args, repeat: repeat, interval: interval });
enableFrameworkConsole() {
this.#showFrameworkConsole = true;
return this;
}
/**
*
* Little helper function.
* If a single page application is in development.
* This method sets an autoreload interval for the page.
* Default is 20 (sec).
* @param {number} relaunchSeconds timeinterval for page to reload (changes)
*/
inDev(relaunchSeconds = 20) {
@ -166,12 +126,67 @@ class PageBuilder {
meta.setAttribute("http-equiv", "refresh");
meta.setAttribute("content", `${relaunchSeconds}`);
let devScript = document.createElement('script');
devScript.setAttribute("data-label", "devScript");
devScript.innerText = `
let ts = new Date();
console.log("Page is in Dev-Mode (through 'inDev()' call of PageBuilder.");
console.log("Refreshed at: ", ts.getHours()+':'+ts.getMinutes()+':'+ts.getSeconds(), "Intervall ${relaunchSeconds}s");
`;
head.appendChild(devScript);
head.insertAdjacentElement("beforeend", meta);
this.#functions.innerText = `
let ts = new Date();
console.log("Refreshed at: ", ts.getHours()+':'+ts.getMinutes()+':'+ts.getSeconds(), "Intervall ${relaunchSeconds}s");
`;
}
/**
*
* @param {CompelExtension} extension
*/
addExtension(extension) {
if (extension instanceof CompelExtension) {
this._extensions.push(extension);
extension.install();
}
}
setPageTitle(title) {
this._apptitle = title;
document.querySelector("title")
.innerText = title;
}
generate() {
super.generate();
if (this.#showFrameworkConsole) {
let pageContextControlPanel = frameworkControlPanel(this._extensions);
pageContextControlPanel = pageContextControlPanel.generate();
document.querySelector('body')
.insertAdjacentElement(
"afterbegin",
pageContextControlPanel.html
);
}
compelgroups = this._groups;
}
}
const CommonCompelGroups = Object.freeze({
AUTO_REGISTRATED: "auto_registrated",
REUSABLE_COMPEL: "reusable",
HIGHER_COMPEL: "higher_compel",
OVERFLOWING: "overflowing",
HIDDEN_ON_START: "hidden_on_start",
IS_CONTEXT_MENU: "is_contextmenu",
HAS_CONTEXT_MENU: "has_contextmenu",
DRAGGABLE: "draggable",
HAS_DRAG_EVENT: "has_drag",
DROP_TARGET: "droptarget",
});
const Page = new PageBuilder();

628
src/context/extStore.js

@ -0,0 +1,628 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* ESAggregation := Extensions Storage Aggregation (method)
*/
const ESAggregation = Object.freeze({
INTERNALIZED: "intern",
INDIVIDUALLY: "individual",
COLLECTED: "collected",
CENTRALIZED: "centralized"
});
/**
* ExtStoragePos := Extensions Storage Position
*
* Determines where the extensions are positioned.
* Only relevant if ExtStorage is not 'internalized'.
* Determines where the tag (if individually) or the extensions are positioned.
*/
const ExtStorePosition = Object.freeze({
WITHIN: "WITHIN",
BEFORE: "BEFORE",
SEGMENT_BEGIN: "SEGMENT_BEGIN",
DOC_HEAD: "DOC_HEAD",
DOC_FOOTER: "DOC_FOOTER"
});
/**
* Defines how an identified dupplication should be "resolved"/dealt with.
* REPLACE:
* RENAME:
* RENAME_OLD:
* DROP_NEW:
* MOVE_ELEMENT_SPECIFIC: @ATTENTION implementation pending
*/
const OverwriteBehaviour = Object.freeze({
REPLACE: "REPLACE",
RENAME: "RENAME",
RENAME_OLD: "RENAME_OLD",
DROP_NEW: "DROP_NEW",
MOVE_ELEMENT_SPECIFIC: "MOVE_ELEMENT_SPECIFIC"
});
/**
* Is supposed to shrink all empty strings to length 1
* @param {Function} func
* @returns {string}
*/
function clearFunctionDeclarationText(func) {
function shrinkEmptyStrings(text) {
for (let i = 1; i < 10; i++) {
text = text.replaceAll(" ".slice(i), ' ');
}
return text;
}
return shrinkEmptyStrings(
func.toString()
.replaceAll('\n', ' ')
.replaceAll('\r\n', ' ')
.replaceAll('\n\r', ' ')
);
}
/**
*
* @param {Function} func
* @param {string} registrationName
* @returns {string}
*/
function getScriptTagInjectionText(func, registrationName) {
let funcHasName = func.name && func.name.trim() !== '';
if (func.startWith('function')) {
let label = `function ${registrationName}`;
let isNameInFuncText = func.startWith(label);
if (funcHasName && isNameInFuncText) {
return func;
} else {
return [label, '(', func.split('(', 1)[1]].join('')
}
} else {
return `const ${registrationName} = ${func}; `;
}
}
/**
* Stores a function until generate is called.
* Then the additional informations of the store wil be applied
* and the funcitons added to the page.
*/
class FunctionStoreBuffer {
/**
* Stores a function until generate is called.
* Then the additional informations of the store wil be applied
* and the funcitons added to the page.
* @param {Function} func the function that will be stored
* @param {Array<any>} args additional arguments that will be given to the function
* @param {boolean} repeats weither the funciton is supposed to execute repeatedly
* @param {number} interval the time in milliseconds between executions
* @param {boolean} execAfterStart weither the function is supposed to be executed after pageload
* @param {number} delay the time in milliseconds the execution will be delayed
*/
constructor(
func,
args = [],
repeats = false,
interval = -1,
execAfterStart = false,
delay = -1
) {
this.func = func;
this.args = args;
this.execAfterStart = execAfterStart;
this.delay = delay;
this.repeats = repeats;
this.interval = interval;
}
}
/**
* Extracted this super class to differentiate between
* internal and external store.
*/
class ExtStorage {
constructor(
aggregation = ESAggregation.INTERNALIZED,
position = ExtStorePosition.WITHIN,
behaviour = OverwriteBehaviour.DROP_NEW
) {
/**
* @type {ESAggregation}
*/
this._aggregation = aggregation;
/**
* @type {ExtStorePosition}
*/
this._position = position;
/**
* @type {OverwriteBehaviour}
*/
this._overwriteBehaviour = behaviour;
}
/**
*
* @param {ESAggregation} position
*/
setExtStoreAggregation(aggregation) {
this._aggregation = aggregation;
return this;
}
/**
*
* @param {ExtStoreType} position
*/
setExtStorePosition(position) {
this._position = position;
return this;
}
/**
*
* @param {OverwriteBehaviour} behave
* @returns {EXPosConfer}
*/
setOverwriteBehaviour(behave) {
this._overwriteBehaviour = behave;
return this;
}
/**
*
* @param {ExtStorage} extStore
* @returns {boolean}
*/
equals(extStore = null) {
if (!extStore) return false;
return extStore._type === this._type
&& extStore._overwriteBehaviour === this._overwriteBehaviour;
}
/**
*
* @returns {boolean}
*/
isMissing() {
return this._type === null || this._overwriteBehaviour === null;
}
/**
*
* @returns {boolean}
*/
isNotInternalOrIndividual() {
return !(
this._aggregation === ESAggregation.INTERNALIZED
|| this._aggregation === ESAggregation.INDIVIDUALLY
);
}
/**
*
* @param {ExtStorage} otherExtStore
* @returns {ExtStorage}
*/
fillBy(otherExtStore) {
if (this._type === null) {
this._type = otherExtStore._type;
}
if (this._overwriteBehaviour === null) {
this._overwriteBehaviour = otherExtStore._overwriteBehaviour;
}
return this;
}
/**
* @todo check if still implemented correctly
* Takes the singleValue and an ExtStore object to copy all values from.
* Then the singleValue will be compared to the three enums for the type of value.
* After the type is identified the corresponding (copied) value will be updated.
* @param {ExtStoreType|ExtStorePosition|OverwriteBehaviour} singleValue
* @param {ExtStorage} extStoreToClone
* @returns {ExtStorage}
*/
setSingleValueToClone(singleValue, extStoreToClone) {
this._type = extStoreToClone._type;
this._position = extStoreToClone._position;
this._overwriteBehaviour = extStoreToClone._overwriteBehaviour;
let target = [
...Object.values(ExtStoreType).map(text => Object({ "value": text, "ref": "type" })),
...Object.values(ExtStorePosition).map(text => Object({ "value": text, "ref": "pos" })),
...Object.values(OverwriteBehaviour).map(text => Object({ "value": text, "ref": "over" }))
]
.find(compareObj => compareObj["value"] === singleValue);
if (target) {
switch (target["ref"]) {
case "type":
this._type = singleValue;
break;
case "pos":
this._position = singleValue;
break;
case "over":
this._overwriteBehaviour = singleValue;
break;
}
}
return this;
}
/**
*
* @returns {ExtStorage} this extStore (updated if rules were used, that don't work for functions)
*/
setupForFunctions() {
if (this._type === ExtStoreType.INTERNALIZED_WITHIN) {
console.log("Updated Functions extstore from INTERNALIZED_WITHIN to INDIVIDUALLY_BEFORE")
this._type = ExtStoreType.INDIVIDUALLY_BEFORE;
}
return this;
}
/**
*
* @returns {ExtStorage}
*/
setupForGeneralStyling() {
if (this === ExtStoreType.INTERNALIZED_WITHIN) {
this._position = ExtStorePosition.WITHIN;
this._aggregation = ESAggregation.INTERNALIZED;
return this;
}
this._position = ExtStorePosition.DOC_HEAD;
switch (this) {
case ExtStoreType.INDIVIDUALLY_WITHIN:
case ExtStoreType.INDIVIDUALLY_BEFORE:
case ExtStoreType.INDIVIDUALLY_SEGMENT_BEGIN:
case ExtStoreType.INDIVIDUALLY_DOC_FOOTER:
case ExtStoreType.INDIVIDUALLY_DOC_HEAD:
this._aggregation = ESAggregation.INDIVIDUALLY;
break;
case ExtStoreType.COLLECTED_BEFORE:
case ExtStoreType.COLLECTED_SEGMENT_BEGIN:
case ExtStoreType.COLLECTED_DOC_FOOTER:
case ExtStoreType.COLLECTED_DOC_HEAD:
this._aggregation = ESAggregation.COLLECTED;
this._aggregation = ESAggregation.COLLECTED;
break;
case ExtStoreType.CENTRALIZED_DOC_HEAD:
case ExtStoreType.CENTRALIED_SEGMENT_BEGIN:
case ExtStoreType.CENTRALIZED_DOC_FOOTER:
default:
this._aggregation = ESAggregation.CENTRALIZED;
break
}
return this;
}
/**
* Currently it works the same as the "updateForFunctions()" since the same rules won't work.
* @returns {ExtStorage} this extStore (updated if rules were used, that won't work for StyleClasses)
*/
setupForStyleClass() {
/*
const positionedAfter = [
COLLECTED_DOC_FOOTER,
INDIVIDUALLY_DOC_FOOTER,
CENTRALIZED_DOC_FOOTER
];
if (positionedAfter.includes(this._type)) {
this._type = ExtStoreType.INTERNALIZED_WITHIN;
}
*/
return this.setupForGeneralStyling();
}
/**
*
* @returns {InsertPosition}
*/
getRelativePositioning() {
switch (this._position) {
case ExtStorePosition.BEFORE:
return "beforebegin"
case ExtStorePosition.SEGMENT_BEGIN:
return "afterbegin";
case ExtStorePosition.DOC_HEAD:
case ExtStorePosition.DOC_FOOTER:
return "beforeend"
case ExtStorePosition.WITHIN:
default:
return "afterbegin";
}
}
/**
* Expects a reference element for the positions before and segment_begin.
* Otherwise will return head, footer or element accordingly.
* @param {HTMLLIElement|Component} element
* @returns {HTMLElement}
*/
getRefElement(element = null) {
let ensuredElement = element;
if (!element) {
console.log("ExtStorePosition defines a relative position, but no reference Element is given - using head!")
return document.querySelector('head');
}
if (element instanceof Component) {
ensuredElement = element.generate().html;
}
switch (this._position) {
case ExtStorePosition.BEFORE:
case ExtStorePosition.SEGMENT_BEGIN:
return ensuredElement.closest('[data-compel-isHCompel="true"]');
case ExtStorePosition.DOC_HEAD:
return document.querySelector('head');
case ExtStorePosition.DOC_FOOTER:
return document.querySelector('footer');
case ExtStorePosition.WITHIN:
default:
return ensuredElement;
}
}
insertElementAccordingly(element) {
this.getRefElement(element)
.insertAdjacentElement(
this.getRelativePositioning(),
this.getRefElement(element)
)
}
/**
*
* @returns {function(SStoreDefinition,HTMLElement,number): boolean}
*/
getStylingDistribution() {
switch (this._aggregation) {
case ESAggregation.INDIVIDUALLY:
return function (ssd, orgElement, counter) {
let container = generateAndFillStyleTag([ssd]);
container.setAttribute("data-compel-individually-nr", counter++);
Page.addElementToPage(container, this);
return false;
}
case ESAggregation.COLLECTED:
return function (ssd, orgElement) {
return true;
}
case ESAggregation.CENTRALIZED:
return function (ssd, orgElement) {
Page.registerStyling(ssd._identifier, ssd._definition);
return false;
}
case ESAggregation.INTERNALIZED:
default:
return function (ssd, orgElement) {
fillAttrsInContainerByCb(
ssd._definition,
orgElement,
(key, val, el) => { el.style[key] = val; }
);
return false;
}
}
}
/**
*
* @returns {function(SStoreDefinition, Map<ExtStorage, Array<SStoreDefinition>, number): boolean}
*/
getFunctionDistribution() {
switch (this._aggregation) {
case ESAggregation.INTERNALIZED:
case ESAggregation.INDIVIDUALLY:
return function (ssd, counter) {
let container = document.createElement("script");
container.setAttribute("data-compel-individually-nr", counter++);
container.innerText += getScriptTagInjectionText(
clearFunctionDeclarationText(ssd._definition),
ssd._identifier
);
Page.addElementToPage(container, refESType);
return false;
}
case ESAggregation.COLLECTED:
return function () {
return true;
}
case ESAggregation.CENTRALIZED:
default:
return function (ssd) {
Page.registerPageFunction(ssd._identifier, ssd._definition);
return false;
}
}
}
}
/**
* ExtStorage := Extensions storage (type)
* Extensions in this context are stylings and scripts (currently only javascript).
* internalized: the extensions are part of the element code/attributes - works obviously only with styling
* individually: an individual tag is created/used
* collected: the extension can/will be collected with others in a higher position of the element hierarchy
* (but not document - root)
* centralized: the extensions are send to the Page to be joined in a centralized tag/position of the document
* (either head or footer tag)
*/
const ExtStoreType = Object.freeze({
INTERNALIZED_WITHIN: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.WITHIN),
INDIVIDUALLY_WITHIN: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.WITHIN),
INDIVIDUALLY_BEFORE: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.BEFORE),
INDIVIDUALLY_SEGMENT_BEGIN: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.SEGMENT_BEGIN),
INDIVIDUALLY_DOC_HEAD: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.DOC_HEAD),
INDIVIDUALLY_DOC_FOOTER: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.DOC_FOOTER),
COLLECTED_BEFORE: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.BEFORE),
COLLECTED_SEGMENT_BEGIN: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.SEGMENT_BEGIN),
COLLECTED_DOC_HEAD: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.DOC_HEAD),
COLLECTED_DOC_FOOTER: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.DOC_FOOTER),
CENTRALIZED_DOC_HEAD: new ExtStorage(ESAggregation.CENTRALIZED, ExtStorePosition.DOC_HEAD),
CENTRALIZED_SEGMENT_BEGIN: new ExtStorage(ESAggregation.CENTRALIZED, ExtStorePosition.SEGMENT_BEGIN),
CENTRALIZED_DOC_FOOTER: new ExtStorage(ESAggregation.CENTRALIZED, ExtStorePosition.DOC_FOOTER)
});
/**
* Style or Script Store Definition
* @property {string} _identifier;
* @property {any} _definition;
* @property {any} _additionaly;
* @property {ExtStorage} _extStore;
*/
class SStoreDefinition {
constructor(identifier, definition, extStore = null, additions = null) {
/**
* Usually the name or the selector
* @type {string} _identifier;
*/
this._identifier = identifier;
/**
* the values
* @type {any} _definition;
*/
this._definition = definition;
/**
* additional values, if needed. E.g. funciton args
* @type {any} _additionaly;
*/
this._additions = additions;
/**
* The corresponding extStore
* @type {ExtStorage} _extStore;
*/
this._extStore = extStore;
}
}
/**
* Resolves an overwrite case for a map/object.
* @param {string} key
* @param {Object} container
* @param {OverwriteBehaviour} overwriteBehaviour
* @returns {string} the key to be used
*/
function resolveOverwrite(key, container, overwriteBehaviour) {
let occurances = Object.keys(container)
.filter(e => e.includes(key))
.length;
switch (overwriteBehaviour) {
case OverwriteBehaviour.REPLACE:
break;
case OverwriteBehaviour.RENAME_OLD:
nameForOld = `${key}${occurances}`;
container[nameForOld] = container[key];
delete container[key];
break;
case OverwriteBehaviour.RENAME:
default:
key = `${key}${occurances}`;
break;
}
return key;
}
/**
* Will resolve the compareKey according to the overwriteBehaviour
* and add the newValue to the targetContainer with it.
* @param {Object} targetContainer
* @param {string} compareKey
* @param {Object} newValue
* @param {OverwriteBehaviour} overwriteBehaviour
* @returns {string} the "resolved" compareKey
*/
function identifyAndResolveOverwrite(targetContainer, compareKey, newValue, overwriteBehaviour) {
let keys = Object.keys(targetContainer);
if (keys.includes(compareKey)) {
if (overwriteBehaviour === OverwriteBehaviour.DROP_NEW) {
console.log("Not Adding, because overwrite is set to DROP_NEW");
return compareKey;
}
compareKey = resolveOverwrite(compareKey, targetContainer, overwriteBehaviour);
}
targetContainer[compareKey] = newValue;
return compareKey;
}
/**
*
* @param {Array<SStoreDefinition>} ssdArray
* @returns {HTMLScriptElement}
*/
function generateAndFillScriptTag(ssdArray) {
let tag = document.createElement("script");
tag.setAttribute("data-compel-gen", "true");
for (let i = 0; i < ssdArray.length; i++) {
const ssd = ssdArray[i];
tag.innerText += getScriptTagInjectionText(
clearFunctionDeclarationText(ssd._definition),
ssd._identifier
);
}
return tag;
}
/**
*
* @param {string} selector
* @param {Map<string,string>} stylingMap
* @returns {string}
*/
function getStylingInjectionText(selector, stylingMap) {
function keyValueToString(key) {
return `${key}: ${stylingMap[key]}; `;
}
return `${selector
} { ${Object.keys(stylingMap)
.map(keyValueToString)
.join(" ")
} }; `;
}
/**
*
* @param {Array<SStoreDefinition>} ssdArray
* @returns {HTMLStyleElement}
*/
function generateAndFillStyleTag(ssdArray) {
let tag = document.createElement("style");
tag.setAttribute("data-compel-gen", "true");
for (let i = 0; i < ssdArray.length; i++) {
const ssd = ssdArray[i];
tag.innerText += getStylingInjectionText(ssd._identifier, ssd._definition);
}
return tag;
}

66
src/context/framework-controls.js

@ -0,0 +1,66 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
function frameworkControlPanel(
extensions = []
) {
return builder.row()
.alignment(Alignment.CENTER)
.arrangement(Arrangement.CENTER)
.isHigherComponent()
.setStylingsStorage(ExtStoreType.INTERNALIZED_WITHIN)
.modifier(
new Modifier()
.fillMaxWidth()
.background(MaterialFiveHundredlColors.ORANGE)
.dimensions(
new Dimensions()
.height(200)
)
.border(
new Border(3)
.color(Colors.goldenrod_3)
)
.linkPadding(4)
)
.childContext([
builder.column()
.modifier(new Modifier().fillMaxHeight())
.childContext([
builder.label().text("Installed Extensions:")
,
builder.column()
.overflow()
.modifier(
new Modifier()
.linkPadding(4).ensureModifier()
.linkBorder(1)
)
.childContext(
extensions.map(
/**
*
* @param {CompelExtension} ext
* @returns {Component}
*/
ext => builder.span().text(ext.diplayTitle)
)
)
])
,
builder.div()
.alignment(Alignment.CENTER)
.arrangement(Arrangement.CENTER)
.childContext([
builder.label()
.text("to generate and download page displayed below click on 'generate'")
,
builder.button()
.text("generate")
])
]);
}

119
src/context/generalHelpers.js

@ -0,0 +1,119 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* Iterates over the keys of attrs,
* extracts the corresponding value
* and applies the callback (cb) on it in the order (key, value, targetContainer)
* @extends StyleAndScriptStoringComponent
* @param {map<string,any>} attrs
* @param {Object} intoContainer
* @param {Function<string, any, Object>} cb
* @returns {Object} the filled container
*/
function fillAttrsInContainerByCb(attrs, intoContainer, cb) {
let keys = Object.keys(attrs);
for (let i = 0; i < keys.length; i++) {
cb(keys[i], attrs[keys[i]], intoContainer);
}
return intoContainer;
}
/**
*
*/
class ObjectAccessObject {
/**
*
* @param {Object} object
*/
constructor(object = null) {
/**
* @type {Array<string>}
*/
this.keys = (object ? Object.keys(object) : []);
/**
* @type {Object}
*/
this.objects = (object ? object : {});
}
/**
*
* @param {Array<string>} keyArr
* @param {Object} refObject
* @returns
*/
fillByArrayReference(keyArr, refObject) {
this.keys = keyArr;
refObject = keyArr.reduce((a, c) => Object.assign(a, { [c]: refObject[c] }), {});
return this;
}
/**
*
* @param {string} key
* @param {*} value
* @returns {ObjectAccessObject}
*/
add(key, value) {
this.objects[key, value];
this.keys.push(key);
return this;
}
/**
*
* @param {string} key
* @returns {*}
*/
remove(key) {
let tmp = this.objects[key];
delete this.objects[key];
return tmp;
}
}
function toggleElementVisibility(element, ensureHidden = false) {
element.classList.toggle("compel-mech-hidden");
let isNowHidden = false;
if (element.hasAttribute("hidden")) {
element.removeAttribute("hidden");
element.style["display"] = "flex";
isNowHidden = false;
} else {
element.setAttribute("hidden", "hidden");
element.style.removeProperty("display");
isNowHidden = true;
}
if (ensureHidden && !isNowHidden) {
return toggleSelectorElementVisibility(selector)
} else {
return isNowHidden;
}
}
/**
*
* @param {string} selector
* @returns {boolean} for true element is now hidden and false it is not hidden.
*/
function toggleSelectorElementVisibility(selector, ensureHidden = false) {
/**
* @type {HTMLElement}
*/
let el = document.querySelector(selector);
let name = el.getAttribute("data-autocompel");
console.log("De-/hiding", name, selector);
return toggleElementVisibility(el, ensureHidden);
}

205
src/context/scriptAndStyleContext.js

@ -0,0 +1,205 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* @abstract
* Class adds function and style storing properties to the context (PageBuilder).
*/
class ScriptAndStyleContext {
/**
* @property {map<string, map<string,string>>} functions
*/
#css;
/**
* @property {map<string, FunctionStoreBuffer>} functions
*/
#functions;
constructor() {
this.#functions = {};
this.#css = {};
}
/**
*
* @param {string} nameAddition text that will be added to the end of the generated name
* @returns a name for a function (e.g. for storing)
*/
getFunctionName(nameAddition = "") {
return `func${this.#functions.length}${nameAddition}`;
}
/**
* Registers a function to be added later in a script tag in the head of the document.
* @ATTENTION Be careful with intended empty strings (e.g. in variable values),
* empty strings within the function code will be shrunk.
*
* @param {Function} fun The function that will be registered
* @param {string} underTheName (alternative) name for the registration of the function,
* if none the name of the function will be used, if that is missing a name will be generated.
* @param {OverwriteBehaviour} overwriteBehaviour defines what to do,
* if the registration name already exists (default: OverwriteBehaviour.RENAME - adds a nr to the name)
* @returns {string} the name under witch the function is registered (and therefore can be called from)
*/
registerPageFunction(fun, underTheName = '', overwriteBehaviour = OverwriteBehaviour.RENAME) {
/* Find name-root */
let registrationName = [
underTheName.trim(),
fun.name.trim(),
this.getFunctionName()
].find(e => e !== '');
/* deal with name already present */
let functionNames = Object.keys(this.#functions);
if (functionNames.includes(registrationName)) {
registrationName = resolveOverwrite(registrationName, this.#functions, overwriteBehaviour);
}
/* clear function text */
let clearedFuncText = clearFunctionDeclarationText(fun);
this.#functions[registrationName] = new FunctionStoreBuffer(clearedFuncText);
return registrationName;
}
/**
*
* @experimental Attention is adviced, registration mechanism doesn't work yet
* @param {Function} fun The function that is supposed to be executed repeatedly
* @param {number} interval the time in ms between executions
* @param {string} underTheName the name the interval will be tied to
* @param {OverwriteBehaviour} overwriteBehaviour defines what to do,
* if the registration name already exists (default: OverwriteBehaviour.RENAME - adds a nr to the name)
*/
registerRepeatingFunction(fun, interval, underTheName = '', overwriteBehaviour = OverwriteBehaviour.RENAME, args = []) {
let registrationName = this.registerPageFunction(fun, underTheName, overwriteBehaviour);
let fsb = this.#functions[registrationName];
fsb.repeats = true;
fsb.interval = interval;
fsb.args = args;
return registrationName;
}
/**
* Registeres a function to be executed after page-load
* @param {Function} func the function that will be executed
* @param {number} delay the time in ms the execution is delayed after load
* @param {string} name if provided the function will be registered as well
* @param {Array<any>} args arguments for the function
* @param {boolean} repeat defines if the function is supposed to be repeated as well
* @param {number} interval if the function is supposed to repeat, this defines the interval of repetition
*/
executeAfterLoad(fun, delay = 1000, underTheName = '', overwriteBehaviour = OverwriteBehaviour.RENAME, interval = -1, args = []) {
let registrationName = this.registerPageFunction(fun, underTheName, overwriteBehaviour);
let fsb = this.#functions[registrationName];
fsb.execAfterStart = true;
fsb.delay = delay;
fsb.args = args;
if (interval > 0) {
fsb.repeats = true;
fsb.interval = interval;
}
return registrationName;
}
/**
*
* @param {string} nameAddition text that will be added to the end of the generated name
* @returns a name for a styling (e.g. for storing)
*/
getStyleName(nameAddition = "") {
return `styling${this.#css.length}${nameAddition}`;
}
/**
* Adds the styling rules to the element identifiers into the style tag.
* An element definition can only be used once, repeated use will be ignored.
*
* @todo implement extStore logic
*
* @param {string} elementIdentifier The element identifier
* @param {map<string, string>|Modifier} styleRuleMap The Styling rules/values
*/
registerStyling(elementIdentifier, styleRuleMap) {
if(styleRuleMap instanceof Modifier){
styleRuleMap = styleRuleMap._modifications;
}
if (!Object.keys(this.#css).includes(elementIdentifier)) {
this.#css[elementIdentifier] = styleRuleMap
}
return elementIdentifier;
}
/**
* Adds into the (head) document.
* - script tag
* - function tag(s)
* - sets and registers repeatedly executed functions
* - sets (timeout and) functions that are supposed to be executed after load
* @class ScriptAndStyleContext
*/
generate() {
let head = document.querySelector('head');
/* generate style tag and fill it with stored stylings */
let styleTag = document.createElement('style');
Object.entries(this.#css)
.forEach((tuple) => {
styleTag.innerText += `${tuple[0]} {${Object.entries(tuple[1])
.map(style => style[0] + ": " + style[1] + "; ")
.join(" ")
}} `;
});
head.appendChild(styleTag);
/*
generate script tag(s) and fill it with stored functions
for now there will be 3 script tags, so interval, execAfterStart and normal functions are sepperated.
*/
let containersTag = document.createElement('script');
containersTag.setAttribute("data-compel-mech-script", "main");
containersTag.innerText = 'const delayed = {}; ';
containersTag.innerText += 'const repeated = {}; ';
head.appendChild(containersTag);
if (this.#functions.length > 0) {
let funcTag = document.createElement('script');
Object.entries(this.#functions)
.forEach(tuple => {
let regName = tuple[0];
let fsb = tuple[1];
funcTag.innerText += getScriptTagInjectionText(fsb.func, regName);
if (fsb.repeats && !fsb.execAfterStart) {
repeated[regName] = setInterval(regName, fsb.interval, fsb.args);
}
if (!fsb.repeats && fsb.execAfterStart) {
delayed[regName] = setTimeout(regName, fsb.interval, fsb.args);
}
if (fsb.repeats && fsb.execAfterStart) {
repeated[regName] = setInterval(regName, fsb.interval, fsb.args);
delayed[regName] = setTimeout(repeated[regName], fsb.delay, fsb.args);
}
});
head.appendChild(funcTag);
}
}
}

31
src/context/webTrinity.js

@ -0,0 +1,31 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* Wenity := Web Trinity
*/
class WebTrinity {
/**
*
* @param {HTMLElement|Component} html
* @param {HTMLStyleElement|Map<ExtStorageType, Array<SStoreDefinition>} js
* @param {HTMLScriptElement|Array<SStoreDefinition>} css
*/
constructor(html = null, js = null, css = null) {
this.html = html;
this.js = js;
this.css = css;
}
/**
*
* @returns {boolean}
*/
isSSEmpty() {
return (this.js === null || this.js.length === 0)
&& (this.css === null || this.css.size === 0);
}
}

53
src/extensions/extension.js

@ -0,0 +1,53 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* Defines an extension that can be added to the jpclw-framework
*/
class CompelExtension {
/**
* @type {string}
*/
name;
/**
* @type {string}
*/
diplayTitle;
/**
* @type {Array<SStoreDefinition>}
*/
stylings;
/**
* @type {Array<SStoreDefinition>}
*/
functions;
/**
* Predefined components. Usually of a higher (constructed) kind.
* @type {Object}
*/
components;
/**
* Extensions for/to the Page or the framework in general.
* @type {Object}
*/
frameworkControls;
/**
* Additional elements for the builder (likely referencing components)
* @type {Object}
*/
builderElements;
/**
* defines how jpc-like-websites is to be extended and executes that extension
*/
install() {
builder.extensions = Object.assign(builder.extensions, this.builderElements);
for (const key of Object.keys(this.stylings)) {
Page.registerStyling(key, this.stylings[key]);
}
}
}

69
src/modifications/contextMenu.js

@ -0,0 +1,69 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
/**
* Default implementation of a contextmenu-behaviour
*/
const DefaultContextMenu = {
openContextMenuAction: function (identifier, refPos) {
if (!refPos) {
refPos = function (event) {
return new Sides()
.left(event.pageX)
.top(event.pageY);
}
}
return function (event) {
event.preventDefault();
if (refPos instanceof Function) {
refPos = refPos(event);
}
let menu = document.querySelector(`[data-autocompel="${identifier}"`);
menu.style.left = `${refPos.getByIndex(SideDirections.LEFT)}px`;
menu.style.top = `${refPos.getByIndex(SideDirections.TOP)}px`;
toggleElementVisibility(menu, true);
toggleElementVisibility(menu);
document.addEventListener(
"click",
DefaultContextMenu.hideOnClickOutsideBoundsAction(identifier)
);
document.addEventListener(
"keyup",
DefaultContextMenu.hideOnEscapeAction(identifier)
);
}
}
,
hideOnEscapeAction: function (identifier) {
return function (event) {
if (event.key === "Escape") {
let menu = document.querySelector(`[data-autocompel="${identifier}"`);
toggleElementVisibility(menu, true);
document.removeEventListener("keyup");
}
}
}
,
hideOnClickOutsideBoundsAction: function (identifier) {
return function (event) {
let menu = document.querySelector(`[data-autocompel="${identifier}"`);
let area = getEnclosingBounds(menu);
if (!areXYInArea(area, event.clientX, event.clientY)) {
toggleElementVisibility(menu, true);
document.removeEventListener("click")
}
}
}
};

129
src/modifications/dragAndDrop.js

@ -0,0 +1,129 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* @copyright by its creator Christian Martin
*/
const EventDrag = Object.freeze({
OVER: "over",
START: "start",
END: "end",
ENTER: "enter",
LEAVE: "leave"
});
/**
* Defines default functions to setup a drag/drop behaviour
*/
class DragAndDropImplementation {
_isMngmtElNeeded;
ensureManagementElementExists() {
}
getManagementElement() {
}
/**
*
* @param {string} datakey
* @param {string} selector
* @returns {Function}
*/
dragStartAction(datakey, selector) {
return function (event) {
console.log("DragEvent", event, "on", selector);
event.dataTransfer
.setData(
datakey,
JSON.stringify({
["selector"]: selector,
["relDims"]: new TwoDimPoint(
event.clientX - event.target.getBoundingClientRect().left,
event.clientY - event.target.getBoundingClientRect().top
)
})
);
}
}
dragEventAction(selector) {
return function (event) {
}
}
/**
*
* @param {string} datakey
* @param {string} selector
* @returns {Function}
*/
dropEventAction(datakey, selector) {
return function (event) {
event.preventDefault();
let draggedData = JSON.parse(event.dataTransfer.getData(datakey));
let draggedElement = document.querySelector(draggedData.selector);
let target = event.target
.closest(selector);
console.log("Default drop event");
//if (![...target.childNodes].includes(draggedElement)) {
target.appendChild(draggedElement);
//}
}
}
};
const DADInPlace = new DragAndDropImplementation();
DADInPlace.dropEventAction = function (datakey, selector) {
/**
* @param {Event} event
*/
return function (event) {
event.preventDefault();
let draggedData = JSON.parse(event.dataTransfer.getData(datakey));
let draggedElement = document.querySelector(draggedData.selector);
let target = event.target
.closest(selector);
let dimsDraggedEl = new Dimensions()
.width(draggedElement.offsetWidth)
.height(draggedElement.offsetHeight);
let pagePoint = new TwoDimPoint(
event.clientX,
event.clientY
);
let parentPagePoint = new TwoDimPoint(
target.getBoundingClientRect().left,
target.getBoundingClientRect().top
);
draggedElement.style["width"] = dimsDraggedEl._fFirst + "px";
draggedElement.style["height"] = dimsDraggedEl._fSecond + "px";
// x page to relative is (parent-page), then -rel
let dropPosition = pagePoint
.minusTDP(parentPagePoint)
.minusTDP(draggedData.relDims);
draggedElement.style.position = "relative";
draggedElement.style.left = dropPosition.x + "px";
draggedElement.style.top = dropPosition.y + "px";
//if (![...target.childNodes].includes(draggedElement)) {
target.appendChild(draggedElement);
//}
}
}

140
src/modifier.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
@ -12,13 +11,25 @@
*/
class Modifier {
/**
* @property {Map<string,string>} _modifications
* @type {Map<string,string>} _modifications
*/
_modifications;
/**
* @type {Array<string>}
*/
_removeMods;
/**
* @type {Shape}
*/
_shape;
/**
* @type {Sides} paddingValues
*/
_paddingValues;
constructor() {
this._modifications = new Object();
this._removeMods = [];
}
/**
@ -37,6 +48,7 @@ class Modifier {
*/
fillMaxWidth(fraction = 1) {
this._modifications["width"] = (100 * fraction) + "%";
this._modifications["max-width"] = (100 * fraction) + "%";
return this;
}
@ -47,6 +59,52 @@ class Modifier {
*/
fillMaxHeight(fraction = 1) {
this._modifications["height"] = (100 * fraction) + "%";
this._modifications["max-height"] = (100 * fraction) + "%";
return this;
}
/**
*
* @param {string} keyWord weither 'height' or 'width' that will be adjusted and looked for
* @param {Sides} parentalPadding
*/
_updateDimensionsBy(parentalPadding) {
function updateDirection(keyWord, modifications, parentalAdjustment) {
let refKeys = Object.keys(modifications)
.filter(k => k.includes(keyWord));
if (refKeys.length > 0) {
for (let i = 0; i < refKeys.length; i++) {
let key = refKeys[i];
let value = modifications[key];
if (key.includes("calc")) {
console.log(
`Modifier._updateByParent... ${keyWord
} - unexpected value '${value
}' for '${key
}', skipping...`
);
} else {
let newValue = `calc(${value} - ${parentalAdjustment});`;
modifications[key] = newValue.trim();
}
}
}
return modifications;
}
if (parentalPadding) {
let pval = parentalPadding.getValues();
if (pval["horizontal"] > 0) {
this._modifications = updateDirection("width", this._modifications, pval["horizontal"] + parentalPadding._unit);
}
if (pval["vertical"] > 0) {
this._modifications = updateDirection("height", this._modifications, pval["vertical"] + parentalPadding._unit);
}
}
return this;
}
@ -71,9 +129,10 @@ class Modifier {
* @returns {Modifier} this modifier object
*/
padding(siding) {
this._paddingValues = siding;
let keyToAdd = "";
if (siding instanceof PaddingChain) {
/* TODO what is this supposed to do */
} else if (siding instanceof Sides) {
keyToAdd = "padding-"
}
@ -106,21 +165,33 @@ class Modifier {
/**
* Sets the background-color as a rgb color.
* If no color is given/specified the styling will be set to "inherit"
* and use the color setting from (one of) the parent.
* @param {Color} color
* @returns {Modifier} this modifier object
*/
background(color) {
this._modifications["background-color"] = color.cssRGBString();
this._modifications["background-color"] = (
color
? color.cssRGBString()
: "inherit"
);
return this;
}
/**
* Sets the color as a rgb color.
* Sets the color as a rgb color.
* If no color is given/specified the styling will be set to "inherit"
* and use the color setting from (one of) the parent.
* @param {Color} color
* @returns {Modifier} this modifier object
*/
color(color) {
this._modifications["color"] = color.cssRGBString();
this._modifications["color"] = (
color
? color.cssRGBString()
: "inherit"
);
return this;
}
@ -128,17 +199,30 @@ class Modifier {
* Adds the modifications of the given Modifier to current Modifier.
* This is especailly used in the cases of extending existing/pre defined
* Components.
*
* CAUTION matching existing modifications will be overwritten.
*
* @todo finish second parameter "modifications" - logic
*
* @param modifier The "new" Modifier
* @returns {Modifier} The "old/current" Modifier,
* extended with the modifications of the given Modifier.
*/
join(modifier, modifications = {}) {
var keys = Object.keys(modifier.ensureModifier()._modifications);
let keys = Object.keys(modifier.ensureModifier()._modifications);
for (let i = 0; i < keys.length; i++) {
/* if (!this._modifications.hasOwnProperty(keys[i])) */
this._modifications[keys[i]] = modifier.ensureModifier()._modifications[keys[i]];
}
let removeMods = modifier.ensureModifier()._removeMods;
if (removeMods.length > 0) {
for (let i = 0; i < removeMods.length; i++) {
delete this._modifications[removeMods[i]];
}
}
if (modifier._paddingValues) {
this._paddingValues = modifier._paddingValues;
}
return this;
}
@ -153,6 +237,31 @@ class Modifier {
return this;
}
/**
*
* @param {StylePropertyMap} rulemap
* @returns {Modifier}
*/
addStyleRuleMap(rulemap) {
for (const ruleKey of Object.keys(rulemap)) {
this._modifications[ruleKey] = rulemap[ruleKey];
}
return this;
}
/**
*
* @param {string} key
* @returns {Modifier} this modifier object
*/
removeStyleRule(key) {
this._removeMods.push(key);
if (Object.keys(this._modifications).includes(key)) {
delete this._modifications[key];
}
return this;
}
/**
* Sets a border line (with given linestyle) to all sides.
* If lineStyle is an array, the containing LineStyles,
@ -164,9 +273,9 @@ class Modifier {
* @returns {Modifier} this modifier object
*/
border(border) {
if (border._shape){
if (border._shape) {
this.clip(border._shape);
}else if(this._shape){
} else if (this._shape) {
border._shape = this._shape;
}
border.toModifications()
@ -247,9 +356,20 @@ class Modifier {
}
/**
* @extends Modifier
* @inheritdoc
*/
class ChainableModifier extends Modifier {
/**
* @type {Component}
*/
_component;
/**
*
* @param {Component} component
*/
constructor(component) {
super();
this._component = component;

22
src/sizeSide/border.js

@ -1,8 +1,7 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
@ -53,6 +52,10 @@ const Define = Object.freeze({
color: new BorderDefinition().color
})
/**
* @inheritdoc
* @extends Sides
*/
class Border extends Sides {
constructor(width = 0, color = Colors.black, style = LineStyles.solid, defaultUnit = SizeUnits.PIXEL, shape = Shapes.Rectangle) {
super(0, defaultUnit);
@ -76,6 +79,7 @@ class Border extends Sides {
*
* @param {string} key
* @param {*} value
* @returns {Border}
*/
setOnDirections(key, value) {
let orderedAttributes = this.getOrderedAttributes()
@ -111,6 +115,11 @@ class Border extends Sides {
return this;
}
/**
*
* @param {Shape} shape
* @returns {Border}
*/
shape(shape) {
this._shape = shape;
return this;
@ -148,7 +157,7 @@ class Border extends Sides {
setLineStyles(refSideStyleMap) {
let rkeys = Object.keys(refSideStyleMap);
for (let i = 0; i < array.length; i++) {
this._sidingStyles.setBySidingRef(rkeys[i]) = refSideStyleMap[rkeys[i]];
this._sidingStyles.setBySidingRef(rkeys[i], refSideStyleMap[rkeys[i]]);
}
return this;
}
@ -163,14 +172,17 @@ class Border extends Sides {
return [
{ key: `border-${names[i]}-width`, value: bdef._width + this._unit },
{ key: `border-${names[i]}-color`, value: bdef._color },
{ key: `border-${names[i]}-color`, value: bdef._color.cssRGBString() },
{ key: `border-${names[i]}-style`, value: bdef._style }
]
})
}
}
/**
* @inheritdoc
* @extends Border
*/
class BorderChain extends Border {
constructor(modifier){
super();

44
src/sizeSide/dimensions.js

@ -1,14 +1,21 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
* Simple Dimensions container for the height and width in pixels.
*
* @inheritdoc
* @extends DirectionUnitDependentAttribute
*/
class Dimensions extends DirectionUnitDependentAttribute {
/**
*
* @param {number} defaultValue
* @param {SizeUnits} defaultUnit
*/
constructor(defaultValue = 0, defaultUnit = SizeUnits.PIXEL) {
super();
this._unit = defaultUnit;
@ -36,6 +43,11 @@ class Dimensions extends DirectionUnitDependentAttribute {
return this;
}
/**
*
* @param {number} size
* @returns {Dimensions}
*/
all(size) {
return this.width(size).height(size);
}
@ -45,6 +57,10 @@ class Dimensions extends DirectionUnitDependentAttribute {
return this.getOrderedValues().slice(2)
}
/**
*
* @returns {Object}
*/
toModifications() {
let w = { key: "width", value: this._fFirst + this._unit }
let h = { key: "height", value: this._fSecond + this._unit }
@ -60,11 +76,33 @@ class Dimensions extends DirectionUnitDependentAttribute {
return []
}
}
}
/**
*
* @returns {TwoDimPoint}
*/
toTwoDimPoint() {
return new TwoDimPoint(
this._fFirst,
this._fSecond
);
}
}
/**
* @inheritdoc
* @extends Dimensions
*/
class DimensionsChain extends Dimensions {
/**
* @type {Modifier|ChainableModifier}
*/
_modifier;
/**
*
* @param {Modifier|ChainableModifier} modifier
*/
constructor(modifier) {
super();
this._modifier = modifier;

69
src/sizeSide/shapes.js

@ -1,12 +1,12 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
*
* @extends DirectionUnitDependentAttribute
* @inheritdoc
*/
class Shape extends DirectionUnitDependentAttribute {
constructor(defaultValue = 0, defaultUnit = SizeUnits.PIXEL) {
@ -14,7 +14,7 @@ class Shape extends DirectionUnitDependentAttribute {
}
/**
*
* @param {*} amount
* @param {number} amount
* @returns {Shape}
*/
topLeft(amount) {
@ -23,7 +23,7 @@ class Shape extends DirectionUnitDependentAttribute {
}
/**
*
* @param {*} amount
* @param {number} amount
* @returns {Shape}
*/
topRight(amount) {
@ -32,7 +32,7 @@ class Shape extends DirectionUnitDependentAttribute {
}
/**
*
* @param {*} amount
* @param {number} amount
* @returns {Shape}
*/
bottomLeft(amount) {
@ -41,37 +41,62 @@ class Shape extends DirectionUnitDependentAttribute {
}
/**
*
* @param {*} amount
* @param {number} amount
* @returns {Shape}
*/
bottomRight(amount) {
this.fThird = amount;
this._fThird = amount;
return this;
}
/**
*
* @param {*} amount
* Sets the BottomLeft and TopRight corners
* @param {number} amount
* @returns {Shape}
*/
diagonalPositive(amount) {
return this.bottomLeft(amount).topRight(amount);
}
/**
*
* @param {*} amount
* Sets the TopLeft and BottomRight corners
* @param {number} amount
* @returns {Shape}
*/
diagonalNegative(amount) {
return this.topLeft(amount).bottomRight(amount);
}
/**
* Sets both corners on the left side
* @param {number} amount
* @returns {Shape}
*/
left(amount) {
return this.topLeft(amount).bottomLeft(amount);
}
/**
* Sets both corners on the right side
* @param {number} amount
* @returns {Shape}
*/
right(amount) {
return this.topRight(amount).bottomRight(amount);
}
/**
* Sets both top corners
* @param {number} amount
* @returns {Shape}
*/
top(amount){
return this.topLeft(amount).topRight(amount);
}
/**
* Sets both bottom corners
* @param {number} amount
* @returns {Shape}
*/
bottom(amount){
return this.bottomLeft(amount).bottomRight(amount);
}
/**
*
@ -87,6 +112,10 @@ class Shape extends DirectionUnitDependentAttribute {
}
}
/**
* @inheritdoc
* @extends Shape
*/
class ShapeChain extends Shape {
_modifier;
constructor(modifier) {
@ -95,17 +124,17 @@ class ShapeChain extends Shape {
}
/**
*
* @returns {Modifier|ChainableModifier}
*/
*
* @returns {Modifier|ChainableModifier}
*/
toModifier() {
return this._modifier
.clip(this);
}
/**
*
* @returns {Modifier|ChainableModifier}
*/
*
* @returns {Modifier|ChainableModifier}
*/
ensureModifier() {
return this.toModifier()
}

218
src/sizeSide/siding.js

@ -1,19 +1,20 @@
/**
* This file is part of the jps-like-websites lib
* URL: https://git.labos.goip.de/chris/jpc-like-websites
* COPYRIGHT and LICENCE are owned by its creator Christian Martin
* Copy, altering or distribution without the allowance of the owner are prohibited
* @copyright by its creator Christian Martin
*/
/**
*
* Enum providing predefined set of Size-Units
*/
const SizeUnits = Object.freeze({
PIXEL: "px",
PERCENT: "%"
})
/**
* @abstract
*/
class DirectionUnitDependentAttribute {
_unit;
_fFirst;
@ -133,7 +134,35 @@ class DirectionUnitDependentAttribute {
}
const SideDirections = Object.freeze({
LEFT: 1,
TOP: 2,
RIGHT: 3,
BOTTOM: 4
});
const SideTransitionDirection = Object.freeze({
HORIZONTAL: 0,
VERTICAL: 1
});
const Corners = Object.freeze({
TOP_LEFT: 0,
TOP_RIGHT: 1,
BOTTOM_LEFT: 2,
BOTTOM_RIGHT: 3
});
const CornerTransitionDirection = Object.freeze({
TOP_LEFT_BOTTOM_RIGHT: 0,
TOP_RIGHT_BOTTOM_LEFT: 1
});
/**
* @inheritdoc
* @extends DirectionUnitDependentAttribute
*
*/
class Sides extends DirectionUnitDependentAttribute {
/**
*
@ -200,6 +229,21 @@ class Sides extends DirectionUnitDependentAttribute {
return this.top(amount).bottom(amount);
}
/**
*
* @returns {Object}
*/
getValues() {
return {
"left": this._fFirst,
"top": this._fSecond,
"right": this._fThird,
"bottom": this._fForth,
"horizontal": this._fFirst + this._fThird,
"vertical": this._fSecond + this._fForth
}
}
toModifications() {
return [
{ key: "left", value: this._fFirst + this._unit },
@ -210,7 +254,10 @@ class Sides extends DirectionUnitDependentAttribute {
}
}
/**
* @inheritdoc
* @extends Sides
*/
class PaddingChain extends Sides {
_modifier;
constructor(modifier) {
@ -268,3 +315,164 @@ class PaddingChain extends Sides {
}
}
/**
*
*/
class TwoDimPoint {
/**
*
* @param {number} x
* @param {number} y
*/
constructor(x, y) {
this.x = x;
this.y = y;
}
/**
*
* @param {number} delta
* @returns {TwoDimPoint}
*/
xMinus(delta) {
this.x = this.x - delta;
return this;
}
/**
*
* @param {number} delta
* @returns {TwoDimPoint}
*/
xPlus(delta) {
this.x = this.x + delta;
return this;
}
/**
*
* @param {number} delta
* @returns {TwoDimPoint}
*/
yMinus(delta) {
this.y = this.y - delta;
return this;
}
/**
*
* @param {number} delta
* @returns {TwoDimPoint}
*/
yPlus(delta) {
this.y = this.y + delta;
return this;
}
/**
*
* @param {TwoDimPoint} delta
* @returns {TwoDimPoint}
*/
minusTDP(delta) {
this.x = this.x - delta.x;
this.y = this.y - delta.y;
return this;
}
/**
*
* @param {TwoDimPoint} delta
* @returns {TwoDimPoint}
*/
plusTDP(delta) {
this.x = this.x + delta.x;
this.y = this.y + delta.y;
return this;
}
}
/**
*
* @param {number} start
* @param {number} end
* @param {number} value
* @param {number} tolerance
* @param {boolean} usePercentage
* @returns {boolean}
*/
function isValueInBounds(start, end, value, tolerance = 0, usePercentage = false) {
if (tolerance !== 0) {
if (usePercentage) {
start = start * (1 - tolerance / 100);
end = end * (1 + tolerance / 100);
} else {
start = start - tolerance;
end = end + tolerance;
}
}
return value >= start && value <= end;
}
/**
* @param {number} x
* @param {number} y
* @param {Map<SideDirections,number>} area
* @param {number} tolerance
* @param {boolean} usePercentage if tolerance is given and this is set to true,
* the tolerance will be calculated by percentage,
* otherwise it will be subtracted/added
* @returns {boolean}
*/
function areXYInArea(x, y, area, tolerance = 0, usePercentage = false) {
return isValueInBounds(
area.get(SideDirections.LEFT),
area.get(SideDirections.RIGHT),
x,
tolerance,
usePercentage
) && isValueInBounds(
area.get(SideDirections.TOP),
area.get(SideDirections.BOTTOM),
y,
tolerance,
usePercentage
);
}
/**
*
* @param {TwoDimPoint} point
* @param {Map<SideDirections,number>} area
* @param {number} tolerance
* @param {boolean} usePercentage if tolerance is given and this is set to true,
* the tolerance will be calculated by percentage,
* otherwise it will be subtracted/added
*/
function isPointInArea(point, area, tolerance = 0, usePercentage = false) {
return areXYInArea(
point.x,
point.y,
area,
tolerance,
usePercentage
);
}
/**
*
* @param {HTMLElement} element
* @returns {Object<SideDirections, number}
*/
function getEnclosingBounds(element) {
let area = element.getBoundingClientRect();
let parentArea = element.parentElement.getBoundingClientRect();
return {
[SideDirections.LEFT]: Math.min(area.left, parentArea.left),
[SideDirections.RIGHT]: Math.max(area.right, parentArea.right),
[SideDirections.TOP]: Math.min(area.top, parentArea.top),
[SideDirections.BOTTOM]: Math.max(area.bottom, parentArea.bottom)
}
}

8
upcoming.md

@ -0,0 +1,8 @@
# Upcoming Features/Changes
- [ ] externalize generator and corresponding functions
- [ ] make the generator modular
- [ ] include several generator-opttions into frameworkConsole
- [ ] (re-) enable "packageWithoutFramework"
- [ ] check naming and setup of PageBuilder-class
- [ ] move builder.page to PageBuilder
Loading…
Cancel
Save