Browse Source
Code and functionallity of the Component and Context classes/files were split into several super-classes.master
9 changed files with 1168 additions and 381 deletions
@ -0,0 +1,374 @@ |
|||||
|
/** |
||||
|
* 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 |
||||
|
* @extends ModifiableComponent |
||||
|
*/ |
||||
|
class StyleStoringComponent extends ModifiableComponent { |
||||
|
/** |
||||
|
* @property {boolean} _styleStoreInternal |
||||
|
*/ |
||||
|
_styleStoreInternal; |
||||
|
/** |
||||
|
* The style-rule collection of this component |
||||
|
* @property {map<string,map<string,string>>} css |
||||
|
*/ |
||||
|
#css; |
||||
|
|
||||
|
/** |
||||
|
* @property {ExtStorageType} cssExtStore |
||||
|
*/ |
||||
|
_cssExtStore; |
||||
|
|
||||
|
/** |
||||
|
* @property {HTMLStyleElement} styleTag |
||||
|
*/ |
||||
|
#styleTag; |
||||
|
|
||||
|
constructor(element, attr = {}) { |
||||
|
super(element, attr); |
||||
|
this.#css = {}; |
||||
|
this._styleStoreInternal = true; |
||||
|
this._cssExtStore = ExtStorageType.CENTRALIZED; |
||||
|
this.#css = {}; |
||||
|
this.addStyleClass(this._compName); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Ads class to classList via HTMLElement.classList.add() method. |
||||
|
* Further collects rules in a property until generate is called. |
||||
|
* @override |
||||
|
* @param {string} styleClass |
||||
|
* @param {Modifier} modifier |
||||
|
* @param {OverwriteBehaviour} overwriteBehaviour |
||||
|
* @returns {Componant} this component object |
||||
|
*/ |
||||
|
addStyleClass(styleClass, modifier = null, overwriteBehaviour = OverwriteBehaviour.REPLACE) { |
||||
|
this._element.classList.add(styleClass); |
||||
|
|
||||
|
let selector = '.' + styleClass; |
||||
|
|
||||
|
if (Object.keys(this.#css).includes(selector)) { |
||||
|
selector = resolveOverwrite(selector, this.#css); |
||||
|
} |
||||
|
|
||||
|
this.#css[selector] = (modifier |
||||
|
? (modifier instanceof Modifier |
||||
|
? modifier._modifications |
||||
|
: modifier |
||||
|
) |
||||
|
: {} |
||||
|
); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param {boolean} storeInternal |
||||
|
* @returns {Component} |
||||
|
*/ |
||||
|
setStylingsStorage(storeInternal) { |
||||
|
if (storeInternal) { |
||||
|
this._styleStoreInternal = storeInternal; |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param {ExtStorageType} storageType |
||||
|
* @returns {Component} |
||||
|
*/ |
||||
|
setCssStorage(storageType) { |
||||
|
if (storageType) { |
||||
|
this._cssExtStore = storageType; |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Takes all "collected" styling rules associated to a selector (in #css) |
||||
|
* and inserts them as innerText to a created HTMLStyleElement. |
||||
|
* @returns {HTMLStyleElement} |
||||
|
*/ |
||||
|
_generateStyleTag() { |
||||
|
return Object.keys(this.#css) |
||||
|
.reduce((styleTag, selector) => { |
||||
|
let styleRules = this.#css[selector]; |
||||
|
let rulesText = Object.keys(styleRules) |
||||
|
.map(key => `${key}:${styleRules[key]}; `) |
||||
|
.join(' '); |
||||
|
|
||||
|
styleTag.innerText += `${selector} {${rulesText}} `; |
||||
|
return styleTag; |
||||
|
|
||||
|
}, document.createElement('style')); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @override |
||||
|
* @param {StyleStoringComponent|Component|ChainableModifier} component |
||||
|
*/ |
||||
|
#appendChildComponent(component) { |
||||
|
if (!(component instanceof Component)) { |
||||
|
component = component.toComponent(); |
||||
|
} |
||||
|
let wenity = component.generate(); |
||||
|
|
||||
|
if (Object.hasOwn(wenity, "css") & wenity.css) { |
||||
|
if (wenity.css instanceof HTMLStyleElement) { |
||||
|
this.#styleTag = wenity.css; |
||||
|
} else { |
||||
|
Object.entries(wenity.css) |
||||
|
.forEach(kv => { |
||||
|
this.#css[kv[0]] = kv[1]; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this._element.append(wenity.html); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @override |
||||
|
* @param {boolean|null} styleStoreInternal |
||||
|
* @param {ExtStorageType|null} cssStore |
||||
|
* @returns {WebTrinity} |
||||
|
*/ |
||||
|
generate(styleStoreInternal = null, cssStore = null) { |
||||
|
let wenity = new WebTrinity(); |
||||
|
|
||||
|
/* Sort Styling Storage Types */ |
||||
|
this.setStylingsStorage(styleStoreInternal); |
||||
|
this.setCssStorage(cssStore); |
||||
|
|
||||
|
/* invoke Storage Types/generate Element */ |
||||
|
if (this._styleStoreInternal) { |
||||
|
wenity.html = super.generate(); |
||||
|
} else { |
||||
|
this.addStyleClass(this._compName, this._modifier); |
||||
|
wenity.html = this._element; |
||||
|
} |
||||
|
|
||||
|
if (this._cssExtStore === ExtStorageType.CENTRALIZED) { |
||||
|
Object.entries(this.#css) |
||||
|
.forEach(kv => Page.registerStyling(kv[0], kv[1])); |
||||
|
} else { |
||||
|
switch (this._cssExtStore) { |
||||
|
case ExtStorageType.INDIVIDUALLY: |
||||
|
let tag = this._generateStyleTag(); |
||||
|
tag.setAttribute('data-compel-for', this._compName); |
||||
|
document.querySelector('head') |
||||
|
.insertAdjacentElement( |
||||
|
"beforeend", |
||||
|
tag |
||||
|
); |
||||
|
break; |
||||
|
case ExtStorageType.COLLECTED: |
||||
|
wenity.css = this.#css; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return wenity; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
*/ |
||||
|
class FunctionRegistration { |
||||
|
/** |
||||
|
* |
||||
|
* @param {Function} func |
||||
|
* @param {OverwriteBehaviour} overwriteBehaviour |
||||
|
*/ |
||||
|
constructor(func, overwriteBehaviour = OverwriteBehaviour.RENAME) { |
||||
|
this.fun = func; |
||||
|
this.overwrite = overwriteBehaviour; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
*/ |
||||
|
class ScriptStoringComponent extends StyleStoringComponent { |
||||
|
/** |
||||
|
* Collection of component associated/used functions |
||||
|
* @property {map<string,FunctionRegistration>} functions |
||||
|
*/ |
||||
|
#functions; |
||||
|
/** |
||||
|
* @property {ExtStore} |
||||
|
*/ |
||||
|
_funcStore; |
||||
|
/** |
||||
|
* @property {HTMLScriptElement} scriptTag |
||||
|
*/ |
||||
|
#scriptTag; |
||||
|
|
||||
|
constructor(element, attr = {}) { |
||||
|
super(element, attr); |
||||
|
this.#functions = {}; |
||||
|
this._funcStore = StoreExtAs.CENTRALIZED.positionedAt().BEGINNING(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @override |
||||
|
* @param {ScriptStoringComponent|Component|ChainableModifier} component |
||||
|
*/ |
||||
|
#appendChildComponent(component) { |
||||
|
if (!(component instanceof Component)) { |
||||
|
component = component.toComponent(); |
||||
|
} |
||||
|
let wenity = component.generate(); |
||||
|
|
||||
|
if (Object.hasOwn(wenity, "js") & wenity.js) { |
||||
|
if (wenity.js instanceof HTMLScriptElement) { |
||||
|
this.#scriptTag = wenity.js; |
||||
|
} else { |
||||
|
Object.entries(wenity.js) |
||||
|
.forEach(kv => { |
||||
|
this.#functions[kv[0]] = kv[1]; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this._element.append(wenity.html); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param {string} nameAddition |
||||
|
* @returns |
||||
|
*/ |
||||
|
_getFunctionName(nameAddition = "") { |
||||
|
return `func${this.#functions.length}${nameAddition}`; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @returns {HTMLScriptElement} |
||||
|
*/ |
||||
|
_generateScriptTag() { |
||||
|
return Object.keys(this.#functions) |
||||
|
.reduce((scriptTag, funName) => { |
||||
|
let funReg = this.#functions[funName]; |
||||
|
if (funReg.fun.startsWith('function')) { |
||||
|
let funcNameDeclaration = `function ${funName}`; |
||||
|
scriptTag.innerText += (funReg.fun.startsWith(funcNameDeclaration) |
||||
|
? funReg.fun |
||||
|
: funReg.fun.split('(') |
||||
|
.replace((a, c, i) => (i === 0 ? [funcNameDeclaration] : [...a, c]),) |
||||
|
.join('(') |
||||
|
) |
||||
|
} else { |
||||
|
scriptTag.innerText += `const ${funName} = ${funReg.fun}; `; |
||||
|
} |
||||
|
return scriptTag; |
||||
|
}, document.createElement('script')); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param {ExtStore} functionStore |
||||
|
* @returns {Component} |
||||
|
*/ |
||||
|
setFunctionStorage(functionStore) { |
||||
|
if (functionStore) { |
||||
|
this._funcStore = functionStore; |
||||
|
} |
||||
|
if (this._funcStore === ExtStorageType.INTERNALIZED) { |
||||
|
this._funcStore = ExtStorageType.INDIVIDUALLY; |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param {Function} func |
||||
|
* @param {string} underTheName |
||||
|
* @param {OverwriteBehaviour} overwriteBehaviour |
||||
|
* @returns |
||||
|
*/ |
||||
|
registerFunction(func, underTheName = "", overwriteBehaviour = OverwriteBehaviour.RENAME) { |
||||
|
let registrationName = [underTheName.trim(), func.name.trim(), this._getFunctionName()] |
||||
|
.find(e !== ''); |
||||
|
|
||||
|
/* deal with name already present */ |
||||
|
let functionNames = Object.keys(this.#functions); |
||||
|
if (functionNames.some(key => key.includes(registrationName))) { |
||||
|
registrationName = resolveOverwrite( |
||||
|
registrationName, |
||||
|
this.#functions |
||||
|
); |
||||
|
} |
||||
|
this.#functions[registrationName] = new FunctionRegistration(func, overwriteBehaviour); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param {boolean} styleStoreInternal |
||||
|
* @param {ExtStorageType} cssStore |
||||
|
* @param {ExtStore} funcStore |
||||
|
*/ |
||||
|
generate(styleStoreInternal = null, cssStore = null, funcStore = null) { |
||||
|
let wenity = super.generate(styleStoreInternal, cssStore); |
||||
|
|
||||
|
/* Sort Styling Storage Types */ |
||||
|
this.setFunctionStorage(funcStore); |
||||
|
|
||||
|
let tag = this._generateStyleTag(); |
||||
|
|
||||
|
if (this._funcStore.type === ExtStorageType.INDIVIDUALLY) { |
||||
|
switch (this._funcStore.position) { |
||||
|
case ExtStoragePos.WITHIN: |
||||
|
wenity.html.insertAdjacentElement( |
||||
|
"afterbegin", |
||||
|
tag |
||||
|
); |
||||
|
break; |
||||
|
|
||||
|
case ExtStoragePos.BEGINNING: |
||||
|
document.querySelector('head') |
||||
|
.insertAdjacentElement( |
||||
|
"beforeend", |
||||
|
tag |
||||
|
); |
||||
|
break; |
||||
|
case ExtStoragePos.END: |
||||
|
document.querySelector('body') |
||||
|
.insertAdjacentElement( |
||||
|
"beforeend", |
||||
|
tag |
||||
|
); |
||||
|
break; |
||||
|
case ExtStoragePos.BEFORE: |
||||
|
case ExtStoragePos.SEGMENT_BEGIN: |
||||
|
default: |
||||
|
wenity.js = tag; |
||||
|
break; |
||||
|
} |
||||
|
} else { |
||||
|
switch (this._funcStore.type) { |
||||
|
case ExtStorageType.COLLECTED: |
||||
|
case ExtStorageType.CENTRALIZED: |
||||
|
default: |
||||
|
wenity.js = tag; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return wenity; |
||||
|
} |
||||
|
} |
@ -0,0 +1,89 @@ |
|||||
|
/** |
||||
|
* 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 |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @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; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @extends ChildbearerComponent |
||||
|
* @abstract |
||||
|
*/ |
||||
|
class ModifiableComponent extends ChildbearerComponent { |
||||
|
_modifier; |
||||
|
|
||||
|
constructor(element, attr = {}) { |
||||
|
super(element, attr); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sets, updates or overwrites the Modifier-Object for this component |
||||
|
* @param {Modifier} modifier |
||||
|
* @returns {Componant} this component object |
||||
|
*/ |
||||
|
modifier(modifier) { |
||||
|
this._modifier = this._modifier |
||||
|
.join(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); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritdoc |
||||
|
* @override |
||||
|
* @param {Component|ChainableModifier|} component |
||||
|
*/ |
||||
|
#appendChildComponent(component) { |
||||
|
this._element.append( |
||||
|
(component instanceof Component |
||||
|
? component |
||||
|
: component.toComponent() |
||||
|
) |
||||
|
.generate() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritdoc |
||||
|
* @override |
||||
|
* @returns {HTMLElement} |
||||
|
*/ |
||||
|
generate() { |
||||
|
this._modifier._modifications["justify-content"] = this._arrangement; |
||||
|
this._modifier._modifications['display'] = "flex"; |
||||
|
this._modifier._modifications["align-content"] = this._alignment; |
||||
|
this._modifier._modifications["align-items"] = this._alignment; |
||||
|
this._modifier._modifications["text-align"] = this._alignment; |
||||
|
|
||||
|
fillAttrsInContainerByCb( |
||||
|
this._modifier._modifications, |
||||
|
this._element, |
||||
|
(key, val, el) => { el.style[key] = val; } |
||||
|
); |
||||
|
|
||||
|
return this._element; |
||||
|
} |
||||
|
} |
@ -0,0 +1,203 @@ |
|||||
|
/** |
||||
|
* 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; |
||||
|
|
||||
|
/** |
||||
|
* Initializes the component |
||||
|
* @param {HTMLElement} element the base element |
||||
|
* @param {map<string,string>} attr Specific already known attributes |
||||
|
*/ |
||||
|
constructor(element, attr = {}) { |
||||
|
let akeys = Object.keys(attr); |
||||
|
for (let i = 0; i < akeys.length; i++) { |
||||
|
element.setAttribute(akeys[i], attr[akeys[i]]); |
||||
|
} |
||||
|
this._element = element; |
||||
|
this._compName = Page.registerComponent(); |
||||
|
this.setAttribute('data-compel', this._compName); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* (Wrapper) Sets the innerText of the element |
||||
|
* @param {string} text |
||||
|
* @returns {Component} this component object |
||||
|
*/ |
||||
|
text(text) { |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Ends chain. |
||||
|
* Applies all modifications on the element. |
||||
|
* Returns the constructed HTMLElement of this Component. |
||||
|
* @returns {HTMLElement} |
||||
|
*/ |
||||
|
generate() { |
||||
|
return this._element; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @extends ElementWrapper |
||||
|
* @abstract |
||||
|
*/ |
||||
|
class ChildbearerComponent extends ElementWrapper { |
||||
|
/** |
||||
|
* @property {Alignment} alignment |
||||
|
*/ |
||||
|
_alignment; |
||||
|
|
||||
|
/** |
||||
|
* @property {Arrangement} arrangement |
||||
|
*/ |
||||
|
_arrangement; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @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; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Defines how a child Component is to be appended. |
||||
|
* @param {Component} component the child component to add it. |
||||
|
*/ |
||||
|
#appendChildComponent(component) { |
||||
|
this._element.append( |
||||
|
component.generate() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @override |
||||
|
* @inheritdoc |
||||
|
*/ |
||||
|
generate() { |
||||
|
this._element.style.justifyContent = this._arrangement; |
||||
|
this._element.style.display = "flex"; |
||||
|
this._element.style.alignContent = this._alignment; |
||||
|
this._element.style.alignItems = this._alignment; |
||||
|
this._element.style.textAlign = this._alignment; |
||||
|
return super.generate(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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.#appendChildComponent(component); |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
} |
@ -0,0 +1,157 @@ |
|||||
|
/** |
||||
|
* 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 |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* 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 - only 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 ExtStorageType = Object.freeze({ |
||||
|
INDIVIDUALLY: 1, |
||||
|
COLLECTED: 2, |
||||
|
CENTRALIZED: 3, |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 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 ExtStoragePos = Object.freeze({ |
||||
|
WITHIN: 0, |
||||
|
BEFORE: 1, |
||||
|
SEGMENT_BEGIN: 2, |
||||
|
BEGINNING: 3, |
||||
|
END: 4 |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* Extension class for setting the ExternalStorage definitions |
||||
|
* in a chained manner. |
||||
|
*/ |
||||
|
class ExtStoragePositioned { |
||||
|
#extStore; |
||||
|
/** |
||||
|
* |
||||
|
* @param {ExtStore} extStore |
||||
|
*/ |
||||
|
constructor(extStore) { |
||||
|
this.#extStore = extStore; |
||||
|
} |
||||
|
|
||||
|
WITHIN() { |
||||
|
this.#extStore.position = ExtStoragePos.WITHIN; |
||||
|
return this.#extStore; |
||||
|
} |
||||
|
BEFORE() { |
||||
|
this.#extStore.position = ExtStoragePos.BEFORE; |
||||
|
return this.#extStore; |
||||
|
} |
||||
|
SEGMENT_BEGIN() { |
||||
|
this.#extStore.position = ExtStoragePos.SEGMENT_BEGIN; |
||||
|
return this.#extStore; |
||||
|
} |
||||
|
BEGINNING() { |
||||
|
this.#extStore.position = ExtStoragePos.BEGINNING; |
||||
|
return this.#extStore; |
||||
|
} |
||||
|
END() { |
||||
|
this.#extStore.position = ExtStoragePos.END; |
||||
|
return this.#extStore; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Extracted this super class to differentiate between |
||||
|
* internal and external store. |
||||
|
*/ |
||||
|
class ExtStore { |
||||
|
type; |
||||
|
position; |
||||
|
/** |
||||
|
* |
||||
|
* @param {string} type |
||||
|
*/ |
||||
|
constructor(typeName) { |
||||
|
this.type = typeName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class InternalExtStore extends ExtStore{ |
||||
|
/** |
||||
|
* |
||||
|
* @param {string} type |
||||
|
*/ |
||||
|
constructor(typeName) { |
||||
|
super(typeName); |
||||
|
this.type = typeName; |
||||
|
this.position = ExtStoragePos.WITHIN; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class ExtExtStorage extends ExtStore{ |
||||
|
/** |
||||
|
* |
||||
|
* @param {string} type |
||||
|
*/ |
||||
|
constructor(typeName) { |
||||
|
this.type = typeName; |
||||
|
} |
||||
|
|
||||
|
positionedAt() { |
||||
|
return new ExtStoragePositioned(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const StoreExtAs = Object.freeze({ |
||||
|
INTERNALIZED: new InternalExtStore(ExtStorageType.INTERNALIZED), |
||||
|
INDIVIDUALLY: new ExtExtStorage(ExtStorageType.INDIVIDUALLY), |
||||
|
COLLECTED: new ExtExtStorage(ExtStorageType.COLLECTED), |
||||
|
CENTRALIZED: new ExtExtStorage(ExtStorageType.CENTRALIZED), |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
const OverwriteBehaviour = Object.freeze({ |
||||
|
REPLACE: 0, |
||||
|
RENAME: 1, |
||||
|
RENAME_OLD: 2 |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 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; |
||||
|
} |
@ -0,0 +1,266 @@ |
|||||
|
/** |
||||
|
* 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 |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 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', ' ') |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function getPageInjectionText(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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @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 !== ''); |
||||
|
|
||||
|
/* 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 elementDefinition can only be used once, repeated use will be ignored. |
||||
|
* @param {string} elementIdentifier The element identifier |
||||
|
* @param {map<string, string>} styleRuleMap The Styling rules/values |
||||
|
*/ |
||||
|
registerStyling(elementIdentifier, styleRuleMap) { |
||||
|
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 |
||||
|
*/ |
||||
|
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.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 += getPageInjectionText(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,22 @@ |
|||||
|
/** |
||||
|
* 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 {HTMLScriptElement|map<string,FunctionRegistration>} js |
||||
|
* @param {HTMLStyleElement|map<string, map<string,string>>} css |
||||
|
*/ |
||||
|
constructor(html = null, js = null, css = null) { |
||||
|
this.html = html; |
||||
|
this.js = js; |
||||
|
this.css = css; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue