You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
592 lines
19 KiB
592 lines
19 KiB
/**
|
|
* 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.
|
|
* @property {Map<string, SStoreDefinition>} _style
|
|
*/
|
|
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 = {}) {
|
|
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 = [];
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
addStyleClass(styleClass, styling = null, extStore = null) {
|
|
if (!extStore) {
|
|
extStore = this._styleClassesExtStore;
|
|
} else if (extStore.isMissing()) {
|
|
extStore = extStore.fillBy(this._styleClassesExtStore);
|
|
}
|
|
|
|
if (styling) {
|
|
if (styling instanceof Modifier) {
|
|
styling = styling._modifications;
|
|
}
|
|
|
|
Page.registerStyling('.' + styleClass, styling);
|
|
}
|
|
|
|
this._element.classList.add(styleClass);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @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}
|
|
*/
|
|
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 {boolean} untilFound
|
|
* @returns {Component}
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {Component}
|
|
*/
|
|
isHigherComponent() {
|
|
this.subscribeOnGenerate(CommonCompelGroups.HIGHER_COMPEL);
|
|
this.#isCompel = true;
|
|
return this.setAttribute("data-compel-isHCompel", "true")
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {Component}
|
|
*/
|
|
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();
|
|
}
|
|
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);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @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 {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}
|
|
*/
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* An echo of Scope-Functions from kotlin for convenience
|
|
*
|
|
* Executes a given function injects this component into the function.
|
|
* @param {Function} func
|
|
* @returns {Component}
|
|
*/
|
|
apply(func) {
|
|
func(this);
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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}
|
|
*/
|
|
applyToEl(func) {
|
|
func(this._element)
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Defines how a child Component is to be appended.
|
|
* @param {Component} component the child component to add it.
|
|
* @returns {HTMLElement}
|
|
*/
|
|
_appendChildComponent(component) {
|
|
let child = new WebTrinity();
|
|
if (component instanceof Component) {
|
|
child = component.generate();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
_processStyles(extStore = null) {
|
|
if (!extStore) {
|
|
extStore = this._stylesExtStore.setupForGeneralStyling();
|
|
} else {
|
|
extStore.setupForGeneralStyling();
|
|
}
|
|
|
|
/**
|
|
* @todo very likely code dupplication - but kept for the time being
|
|
* for error tracking.
|
|
*/
|
|
if (extStore === ExtStoreType.INTERNALIZED_WITHIN) {
|
|
let sizings = Object.keys(this._modifier._modifications)
|
|
.filter(e => e.includes("width") || e.includes("height"))
|
|
.filter(e => this._modifier._modifications[e].includes("calc"))
|
|
.reduce((a, c) => a.add(
|
|
c,
|
|
this._modifier
|
|
._modifications[c]
|
|
.split('(')[1]
|
|
.split(' - ')[0]
|
|
), new ObjectAccessObject());
|
|
|
|
fillAttrsInContainerByCb(
|
|
this._modifier._modifications,
|
|
this._element,
|
|
(key, val, el) => { el.style[key] = val; }
|
|
);
|
|
|
|
let hasElSizing = sizings.keys.some(k => Object.keys(this._element.style).includes(k));
|
|
if (sizings.keys.length > 0 && !hasElSizing) {
|
|
console.log("Fixing sizing - because not supported 'calc'", sizings);
|
|
|
|
fillAttrsInContainerByCb(
|
|
sizings,
|
|
this._element,
|
|
(key, val, el) => { el.style[key] = val; }
|
|
);
|
|
}
|
|
|
|
} else {
|
|
/* ADDS ELEMENT MODIFIER TO this._styles list for styles processing */
|
|
let modifierSSD = new SStoreDefinition();
|
|
modifierSSD._identifier = "." + this._compName;
|
|
modifierSSD._definition = this._modifier._modifications;
|
|
modifierSSD._extStore = extStore;
|
|
this._styles.unshift(modifierSSD);
|
|
}
|
|
|
|
let forCollection = [];
|
|
|
|
let counter = 0;
|
|
for (let i = 0; i < this._styles.length; i++) {
|
|
const ssd = this._styles[i];
|
|
/* 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 */
|
|
let refESType = (
|
|
ssd._extStore && ssd._extStore._type
|
|
? ssd._extStore.setupForGeneralStyling()._type
|
|
: extStore._type
|
|
);
|
|
|
|
switch (refESType) {
|
|
|
|
case ExtStoreType.INTERNALIZED_WITHIN:
|
|
fillAttrsInContainerByCb(
|
|
ssd._definition,
|
|
this._element,
|
|
(key, val, el) => { el.style[key] = val; }
|
|
)
|
|
break;
|
|
|
|
case ExtStoreType.INDIVIDUALLY_DOC_HEAD:
|
|
let container = generateAndFillStyleTag([ssd]);
|
|
container.setAttribute("data-compel-individually-nr", counter++);
|
|
Page.addElementToPage(container, refESType);
|
|
break;
|
|
|
|
case ExtStoreType.COLLECTED_DOC_HEAD:
|
|
forCollection.push(ssd);
|
|
break;
|
|
|
|
case ExtStoreType.CENTRALIZED_DOC_HEAD:
|
|
Page.registerStyling(ssd._identifier, ssd._definition);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return forCollection;
|
|
}
|
|
|
|
|
|
_processFunctions(extStore = null) {
|
|
if (!extStore) {
|
|
extStore = this._functionsExtStore.setupForFunctions();
|
|
} else {
|
|
extStore.setupForFunctions();
|
|
}
|
|
|
|
const forCollection = new Map();
|
|
const collectForBefore = [];
|
|
|
|
let counter = 0;
|
|
for (let i = 0; i < this._functions.length; i++) {
|
|
const ssd = this._functions[i];
|
|
/* Make sure that the type is unified for later processing */
|
|
|
|
let refESType = (
|
|
ssd._extStore && ssd._extStore._type
|
|
? ssd._extStore.setupForFunctions()._type
|
|
: extStore._type
|
|
);
|
|
|
|
switch (refESType) {
|
|
case ExtStoreType.CENTRALIZED_DOC_HEAD:
|
|
case ExtStoreType.CENTRALIZED_SEGMENT_BEGIN:
|
|
case ExtStoreType.CENTRALIZED_DOC_FOOTER:
|
|
Page.registerPageFunction(ssd._identifier, ssd._definition);
|
|
break;
|
|
|
|
case ExtStoreType.INDIVIDUALLY_WITHIN:
|
|
case ExtStoreType.INDIVIDUALLY_BEFORE:
|
|
case ExtStoreType.INDIVIDUALLY_SEGMENT_BEGIN:
|
|
case ExtStoreType.INDIVIDUALLY_DOC_FOOTER:
|
|
case ExtStoreType.INDIVIDUALLY_DOC_HEAD:
|
|
|
|
let container = document.createElement("script");
|
|
container.setAttribute("data-compel-individually-nr", counter++);
|
|
container.innerText += getScriptTagInjectionText(
|
|
clearFunctionDeclarationText(ssd._definition),
|
|
ssd._identifier
|
|
);
|
|
Page.addElementToPage(container, refESType, this._element);
|
|
|
|
break;
|
|
|
|
case ExtStoreType.COLLECTED_BEFORE:
|
|
collectForBefore.push(ssd);
|
|
break;
|
|
|
|
case ExtStoreType.COLLECTED_SEGMENT_BEGIN:
|
|
case ExtStoreType.COLLECTED_DOC_FOOTER:
|
|
case ExtStoreType.COLLECTED_DOC_HEAD:
|
|
if (!forCollection.has(refESType)) {
|
|
forCollection.set(refESType, []);
|
|
}
|
|
forCollection.get(refESType).push(ssd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return forCollection;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
generate(styleStore = null, functionStore = null) {
|
|
this._wenity = new WebTrinity();
|
|
|
|
/* DEAL WITH COMPONENT MODIFICATION FIRST */
|
|
this._styles.push(new SStoreDefinition(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);
|
|
|
|
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)
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
this._wenity.html = this._element;
|
|
|
|
for (let i = 0; i < this._toRegister.length; i++) {
|
|
const group = this._toRegister[i];
|
|
Page.subscribeComponentToGroup(group, this._compName)
|
|
}
|
|
|
|
return this._wenity;
|
|
}
|
|
|
|
|
|
}
|
|
|