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.
 
 

556 lines
16 KiB

/**
* 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 extends StyleAndScriptStoringComponent {
/**
* @type {boolean}
*/
_isCompel;
/**
* @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(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) {
let styleClass = "compel-mech-hidden";
let hid = "hidden";
Page.registerStyling("." + styleClass, { [hid]: hid });
this.addStyleClass(styleClass);
this._modifier.removeStyleRule("display");
this.setAttribute(
hid,
(untilFound ? "until-found" : hid)
);
this.subscribeOnGenerate(CommonCompelGroups.HIDDEN_ON_START);
return this;
}
/**
* Subscribes element under higher_compel group
* sets corr. variable true
* setAttribute("data-compel-isHCompel", "true")
*
* @returns {Component}
*/
isHigherComponent() {
this.subscribeOnGenerate(CommonCompelGroups.HIGHER_COMPEL);
this._isCompel = true;
this.addStyleClass("compel-higher");
return this.setAttribute("data-compel-isHCompel", "true")
}
/**
*
* @returns {string}
*/
getHigherCompelSelector() {
return this._element
.closest('[data-compel-isHCompel="true"].compel-higher')
.getAttribute("data-autocompel");
}
/**
* 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;
}
/**
*
* @param {ExtStorage} extStore
* @returns {Array<SStoreDefinition>}
*/
_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;
}
/**
*
* @param {ExtStorage} extStore
* @returns {Array<SStoreDefinition>}
*/
_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;
}
/**
* Ends chain.
* Applies all modifications on the element.
* Processes alls stored additions.
* Returns the constructed HTMLElement of this Component.
*
*
* @param {Modifier | undefined} [modifier=null]
* @param {ExtStorage | undefined} [styleStore=null]
* @param {ExtStorage | undefined} [functionStore=null]
* @param {ExtStorage}
* @returns {WebTrinity} the constructed HTMLElement of this Component.
*/
generate(modifier = null, styleStore = null, functionStore = null) {
if (this._parentComponent) {
let parent = this._parentComponent;
this._parentComponent = null;
return parent.childContext(this)
.generate(modifier, styleStore, functionStore);
}
if (modifier) {
this._modifier = modifier
.join(this._modifier);
}
let _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(modifier, styleStore, functionStore);
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) {
/**
*
* @param {Map<ExtStoreType, *>} map
* @param {ExtStorageType} extStoreType
*/
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 {
_wenity.js = funcCollections;
_wenity.css = styleCollection;
}
_wenity.html = this._element;
for (const group of this._toRegister) {
Page.subscribeComponentToGroup(group, this._compName)
}
return _wenity;
}
}