Compare commits
42 Commits
Author | SHA1 | Date |
---|---|---|
|
76fac29dd6 | 1 month ago |
|
646afba4d3 | 1 month ago |
|
ea9261446d | 1 month ago |
|
e0a60593d2 | 1 month ago |
|
882303a5fa | 1 month ago |
|
eae24e4bd5 | 1 month ago |
|
a0c6f68e23 | 1 month ago |
|
27d0635119 | 1 month ago |
|
0ba13cca94 | 1 month ago |
|
e0046c6aa8 | 1 month ago |
|
12bf468e22 | 1 month ago |
|
89207c163f | 1 month ago |
|
170d00d852 | 1 month ago |
|
4479c92bb4 | 1 month ago |
|
b7fff8604e | 1 month ago |
|
a7fcb5a3ca | 1 month ago |
|
4b6f4006d9 | 1 month ago |
|
9447216e53 | 2 months ago |
|
f41e7f1384 | 2 months ago |
|
c4b0fc9162 | 2 months ago |
|
5e782912f7 | 2 months ago |
|
1632528322 | 2 months ago |
|
b60b89cbea | 2 months ago |
|
0cc60ad07c | 2 months ago |
|
771a05dba8 | 2 months ago |
|
f9e7e188cb | 2 months ago |
|
02f72d4648 | 2 months ago |
|
bb0ccb8904 | 2 months ago |
|
6f78fa1cfb | 2 months ago |
|
3782344e1f | 2 months ago |
|
5b74731cf1 | 2 months ago |
|
94ef6186cf | 2 months ago |
|
3b8efc9a6d | 2 months ago |
|
0c51679e04 | 2 months ago |
|
ec298757c2 | 2 months ago |
|
8121c9ec6a | 4 months ago |
|
9d073a8367 | 4 months ago |
|
cdc1188061 | 4 months ago |
|
ea1786f379 | 4 months ago |
|
4a0bd90799 | 4 months ago |
|
95d0649169 | 4 months ago |
|
fc2c6b15c0 | 4 months ago |
32 changed files with 3458 additions and 453 deletions
@ -1,3 +1,4 @@ |
|||
node_modules/ |
|||
samples/*/ |
|||
jpc-like-websites.js |
|||
extensions/ |
|||
|
@ -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" |
|||
} |
|||
} |
|||
} |
@ -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()) |
@ -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; |
|||
} |
|||
*/ |
|||
|
|||
|
|||
} |
|||
|
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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") |
|||
]) |
|||
]); |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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]); |
|||
} |
|||
} |
|||
} |
@ -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") |
|||
} |
|||
} |
|||
} |
|||
}; |
@ -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); |
|||
//}
|
|||
} |
|||
} |
@ -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…
Reference in new issue