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