From 4a0bd9079917e16edb2a267c0cb7638f2756f2b0 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 24 Dec 2024 12:55:05 +0100 Subject: [PATCH] MAJOR,REFA,IMPRO,CHANGE: Introducing extensive usage of Inheritance Code and functionallity of the Component and Context classes/files were split into several super-classes. --- join_js_files.sh | 29 +- src/component.js | 213 +--------- src/componentAncestry/addStyleAndFunctions.js | 374 ++++++++++++++++++ src/componentAncestry/modifiableComponent.js | 89 +++++ src/componentAncestry/wrapperComponent.js | 203 ++++++++++ src/context.js | 196 ++------- src/context/extStore.js | 157 ++++++++ src/context/scriptAndStyleContext.js | 266 +++++++++++++ src/context/webTrinity.js | 22 ++ 9 files changed, 1168 insertions(+), 381 deletions(-) create mode 100644 src/componentAncestry/addStyleAndFunctions.js create mode 100644 src/componentAncestry/modifiableComponent.js create mode 100644 src/componentAncestry/wrapperComponent.js create mode 100644 src/context/extStore.js create mode 100644 src/context/scriptAndStyleContext.js create mode 100644 src/context/webTrinity.js diff --git a/join_js_files.sh b/join_js_files.sh index 8927ba3..ff9b9c3 100644 --- a/join_js_files.sh +++ b/join_js_files.sh @@ -7,26 +7,47 @@ SRC="src" # Third "HIGHER_LIST" come several of the commons, the context as well as component # and thoose that use component. -SUB_LIST="siding.js shapes.js border.js dimensions.js" +SIZE_SIDE="siding.js shapes.js border.js dimensions.js" +CONTEXT="webTrinity.js extStore.js scriptAndStyleContext.js" +PRE_ANCESTRY="commonEvents.js context.js" MODIFIERS_LIST="alignment.js arrangement.js modifier.js" -HIGHER_LIST="commonEvents.js context.js component.js baseComponents.js builder.js" +COMPONENT_ANCESTRY="wrapperComponent.js modifiableComponent.js addStyleAndFunctions.js" +HIGHER_LIST="component.js baseComponents.js builder.js" echo "" > $TARGET echo "/* ## color.js ## */" >> $TARGET cat $SRC/color.js >> $TARGET -echo "/* # SUB_LIST # */" >> $TARGET -for i in $SUB_LIST; do +echo "/* # SIZE_SIDE # */" >> $TARGET +for i in $SIZE_SIDE; do echo "/* ## $i ## */" >> $TARGET cat $SRC/sizeSide/$i >> $TARGET done +echo "/* # CONTEXT # */" >> $TARGET +for i in $CONTEXT; do + echo "/* ## $i ## */" >> $TARGET + cat $SRC/context/$i >> $TARGET +done + +echo "/* # PRE_ANCESTRY # */" >> $TARGET +for i in $PRE_ANCESTRY; do + echo "/* ## $i ## */" >> $TARGET + cat $SRC/$i >> $TARGET +done + echo "/* # MODIFIERS_LIST # */" >> $TARGET for i in $MODIFIERS_LIST; do echo "/* ## $i ## */" >> $TARGET cat $SRC/$i >> $TARGET done +echo "/* # COMPONENT_ANCESTRY # */" >> $TARGET +for i in $COMPONENT_ANCESTRY; do + echo "/* ## $i ## */" >> $TARGET + cat $SRC/componentAncestry/$i >> $TARGET +done + echo "/* # HIGHER_LIST # */" >> $TARGET for i in $HIGHER_LIST; do echo "/* ## $i ## */" >> $TARGET diff --git a/src/component.js b/src/component.js index bbb8d85..7f9f693 100644 --- a/src/component.js +++ b/src/component.js @@ -7,12 +7,7 @@ /** * A chainable HTMLElement builder. */ -class Component { - _element; - _modifier - _alignment; - _arrangement; - _toRegister; +class Component extends ScriptStoringComponent { constructor(element, attr = {}) { this._modifier = new Modifier().margin(new Sides().all(0)); @@ -24,184 +19,6 @@ class Component { this._toRegister = []; } - /** - * Sets the alignment (modifications) for this element or more specific for its children. - * @param {Alignment} alignment - * @returns {Component} this component object - */ - alignment(alignment) { - this._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._modifier._modifications["justify-content"] = alignment; - - return this; - } - - /** - * Sets the arrangement (modifications) for this element or more specific for its children. - * @param {Arrangement} arrangement - * @returns {Component} this component object - */ - arrangement(arrangement) { - this._arrangement = arrangement; - switch (arrangement) { - case Arrangement.START: - this._modifier._modifications["justify-content"] = "start"; - break; - case Arrangement.END: - this._modifier._modifications["justify-content"] = "end"; - break; - case Arrangement.CENTER: - this._modifier._modifications["justify-content"] = "center"; - break; - case Arrangement.SPACE_AROUND: - this._modifier._modifications["justify-content"] = "space-around"; - break; - case Arrangement.SPACE_BETWEEN: - this._modifier._modifications["justify-content"] = "space-between"; - break; - case Arrangement.SPACE_EVENLY: - this._modifier._modifications["justify-content"] = "space-evenly"; - break; - } - return this; - } - - /** - * - * @param {Modifier} modifier - * @returns {Component} this component object - */ - modifier(modifier) { - this._modifier = this._modifier.join(modifier.ensureModifier()) - return this; - } - - /** - * Sets the innerText of the element - * @param {string} text - * @returns {Component} this component object - */ - text(text) { - this._element.innerText = text; - return this; - } - - title(text) { - this._element.title = text; - return this; - } - /** - * - * @param {string} styleClass - * @param {Modifier} modifier - * @returns {Component} this component object - */ - addStyleClass(styleClass, modifier = null) { - if (modifier) { - Page.registerStyling(styleClass, modifier._modifications); - } - this._element.classList.add(styleClass); - return this; - } - - registerStyleClass(styleClass, styleRuleMap) { - Page.registerStyling('.' + styleClass, styleRuleMap); - return this.addStyleClass(styleClass); - } - - /** - * - * @param {string} key - * @param {string} value - * @returns {Component} this component object - */ - setAttribute(key, value) { - this._element.setAttribute(key, value); - return this; - } - - - /** - * Ends chain. - * Applies all modifications on the element. - * @returns {HTMLElemment} the html element - */ - generate() { - /* apply styling to element */ - var mkeys = Object.keys(this._modifier._modifications); - for (let i = 0; i < mkeys.length; i++) { - this._element.style[mkeys[i]] = this._modifier._modifications[mkeys[i]]; - } - /* subscribe/register to lists */ - for (let i = 0; i < this._toRegister.length; i++) { - this._toRegister[i].push(this._element); - } - return this._element; - } - - - /** - * Opens a context to create element children. - * Either as one component or a list/array of components. - * @param {Component|Array} 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._element.append( - (component instanceof Component - ? component - : component.toComponent() - ) - .generate() - ); - } - return this; - } - - /** - * @deprecated - * @param {Array} innerComponent - * @returns {Component} this component object - */ - componentChildren(innerComponent) { - for (let i = 0; i < innerComponent.length; i++) { - this.childContext(innerComponent[i]); - } - return this; - } - - /** - * - * @param {CommonEvent} commonEvent - * @param {string} functionName - * @returns {Component} this component object - */ - setEvent(commonEvent, functionName) { - return this.setAttribute(commonEvent, `${functionName}(this)`); - } - - /** - * - * @returns {ChainableModifier} - */ - chainModifier() { - return new ChainableModifier(this); - } - /** * Collects the given List in the _toRegister attribute. * When generate() is called, @@ -213,32 +30,4 @@ class Component { this._toRegister.push(listName); return this; } - - /* - clickable(eventName) { - let cssClass = "button-like" - this.addStyleClass(cssClass) - this._modifier._modifications["box-shadow"] = "4px 6px #999"; - - let bckk = "background-color"; - if (this._modifier._modifications.hasOwnProperty(bckk)) { - let bckc = this._modifier._modifications[bckk]; - delete this._modifier._modifications[bckk]; - Page.registerStyling(`.${cssClass}`, { - [bckk]: bckc - }) - } - - Page.registerStyling(`.${cssClass}:hover`, { - "background-color": Colors.greenyellow.cssRGBString(), - }) - Page.registerStyling(`.${cssClass}:active`, { - "background-color": Colors.greenyellow.cssRGBString(), - "box-shadow": "1px 2px #666", - "transform": "translateY(4px)" - }) - - return this.setEvent(CommonEvents.ONCLICK, eventName); - } - */ } diff --git a/src/componentAncestry/addStyleAndFunctions.js b/src/componentAncestry/addStyleAndFunctions.js new file mode 100644 index 0000000..f1049a8 --- /dev/null +++ b/src/componentAncestry/addStyleAndFunctions.js @@ -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>} 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} 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; + } +} diff --git a/src/componentAncestry/modifiableComponent.js b/src/componentAncestry/modifiableComponent.js new file mode 100644 index 0000000..5bf9bf3 --- /dev/null +++ b/src/componentAncestry/modifiableComponent.js @@ -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} attrs + * @param {Object} intoContainer + * @param {Function} 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; + } +} diff --git a/src/componentAncestry/wrapperComponent.js b/src/componentAncestry/wrapperComponent.js new file mode 100644 index 0000000..7d3d5a0 --- /dev/null +++ b/src/componentAncestry/wrapperComponent.js @@ -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} 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 + * @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; + } +} diff --git a/src/context.js b/src/context.js index 529515e..67a3ae2 100644 --- a/src/context.js +++ b/src/context.js @@ -4,180 +4,39 @@ * @copyright by its creator Christian Martin */ + /** * The class provides overreaching options for building the website. - */ -/** - * The class provides overreaching options for building the website. - * @property {Array} #functionNames - * + * @extends ScriptAndStyleContext */ -class PageBuilder { - #cssClasses; - #functions; - #delayedFunctions; - #repeatingFunctions; - #functionNames; - #cssElementIdentifiers; +class PageBuilder extends ScriptAndStyleContext { + #registeredComponents; constructor() { - this.#cssClasses = document.createElement("style"); - this.#functions = document.createElement("script"); - this.#functionNames = []; - this.#cssElementIdentifiers = []; - } - - /** - * 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. - /** - * 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. - * @deprecated 'registerFunction' will bew removed. - * 'registerNamedFunction' will be removed. - * Use 'registerPageFunction' it fully supports registration/adding of functions. - * All variations of named, unnamed and reassigned, arrow or brakets notation. - * @param {string} name - * @param {function} fun - */ - registerFunction(name, fun) { - /** - * Is supposed to shrink all empty strings to length 1 - * @param {string} text - * @returns {string} - */ - function shrinkEmptyStrings(text) { - for (let i = 1; i < 10; i++) { - text = text.replaceAll(" ".slice(i), ' '); - } - return text; - } - if (!this.#functionNames.includes(name)) { - let clearedFuncText = shrinkEmptyStrings( - fun.toString() - .replaceAll('\n', ' ') - .replaceAll('\r\n', ' ') - .replaceAll('\n\r', ' ') - ); - let isFuncWritten = clearedFuncText.startsWith('function'); - let funcHasName = fun.name && fun.name.trim() !== ''; - if (isFuncWritten) { - let isNameInFuncText = clearedFuncText.startsWith(`function ${name}`); - this.#functions.innerText += (funcHasName && isNameInFuncText - ? clearedFuncText - : clearedFuncText.replace('function ', 'function ' + name) - ) + '; '; - } else { - this.#functions.innerText += `const ${name} = ${clearedFuncText}; ` - } - this.#functionNames.push(name); - } - return this; + this.#registeredComponents = []; } - /** - * @deprecated 'registerFunction' will bew removed. - * 'registerNamedFunction' will be removed. - * Use 'registerPageFunction' it fully supports registration of functions. - * All variations of named, unnamed and reassigned, arrow or brakets notation. - * @param {Function} namedFunction - * @returns - */ - registerNamedFunction(namedFunction) { - return this.registerFunction(namedFunction.name, namedFunction) + registerComponent() { + let compName = 'comp-el-' + this.#registeredComponents.length; + this.#registeredComponents.push(compName); + return compName; } /** - * @experimental Attention is adviced, registration mechanism doesn't work yet - * @param {string} name The name the interval will be tied to - * @param {Function} fun the function that is supposed to be executed repeatedly - * @param {number} interval the time in ms between executions - */ - registerRepeatingFunction(name, fun, interval) { - if (!Object.keys(this.#repeatingFunctions).includes(name)) { - this.#repeatingFunctions[name] = { - name: name, - fun: fun, - interval: interval - }; - } - } - - /** - * 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} elementDefinition The element identifier - * @param {Map} styleRuleMap The Styling rules/values - */ - registerStyling(elementDefinition, styleRuleMap) { - if (!this.#cssElementIdentifiers.includes(elementDefinition)) { - this.#cssClasses.innerText += `${elementDefinition - } {${Object.keys(styleRuleMap) - .map(e => e + ": " + styleRuleMap[e] + "; ") - .join(" ") - }} ` - this.#cssElementIdentifiers.push(elementDefinition) - } - } - - /** - * Adds into the (head) document. - * - script tag - * - function tag - * - sets and registers repeatedly executed functions - * - sets (timeout and) functions that are supposed to be executed after load - */ - generate() { - let head = document.querySelector("head"); - head.appendChild(this.#functions) - head.appendChild(this.#cssClasses) - - /* set repeating functions */ - if (this.#repeatingFunctions) { - let repeatedFun = Object.values(this.#repeatingFunctions) - .reduce((a, c, i, arr) => Object.assign(a, { - [c.name]: setInterval(c.fun, c.interval) - }), {}); - } - - /* set timeouts for funcitons executed after load */ - if (this.#delayedFunctions) { - for (let i = 0; i < this.#delayedFunctions.length; i++) { - let func = this.#delayedFunctions[i]; - if (func.repeat) { - setTimeout(setInterval(func.func, func.interval), func.dl, func.args); - } else { - setTimeout(func.func, func.dl, func.args); - } - } - } - console.log(this.#functionNames); - } - - /** - * 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} 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 + * Determines that the jpc-like-websites libs shouldn't be part of the resulting page. + * Therefore the generate() methods will package/generate finalized js, css and html elements + * into the final html page in the end. + * Especially tricky are reusable elements and functions that use such. + * + * @todo This method/feature will have to have a logic implemented for state altering components + * that then can be "packaged" into a single page. + * @todo This method/feature will work only, if an automatic reuse logic for elements/components is implemented within the jpc lib. + * @ATTENTION DO NOT USE */ - executeAfterLoad(func, delay = 1000, name = '', args = [], repeat = false, interval = 5000) { - if (name !== '') { - this.registerFunction(name, func); - } - if (!this.#delayedFunctions) { - this.#delayedFunctions = []; - } - this.#delayedFunctions.push({ dl: delay, func: func, args: args, repeat: repeat, interval: interval }); + packageWithoutFramework() { + return this; } - /** - * - /** * Little helper function. * If a single page application is in development. @@ -190,12 +49,19 @@ class PageBuilder { meta.setAttribute("http-equiv", "refresh"); meta.setAttribute("content", `${relaunchSeconds}`); + let devScript = document.createElement('script'); + devScript.setAttribute("data-label", "devScript"); + devScript.innerText = ` + let ts = new Date(); + console.log("Page is in Dev-Mode (through 'inDev()' call of PageBuilder."); + console.log("Refreshed at: ", ts.getHours()+':'+ts.getMinutes()+':'+ts.getSeconds(), "Intervall ${relaunchSeconds}s"); + `; + head.appendChild(devScript); head.insertAdjacentElement("beforeend", meta); - this.#functions.innerText = ` - let ts = new Date(); - console.log("Refreshed at: ", ts.getHours()+':'+ts.getMinutes()+':'+ts.getSeconds(), "Intervall ${relaunchSeconds}s"); - `; } + + + } const Page = new PageBuilder(); diff --git a/src/context/extStore.js b/src/context/extStore.js new file mode 100644 index 0000000..8352459 --- /dev/null +++ b/src/context/extStore.js @@ -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; +} diff --git a/src/context/scriptAndStyleContext.js b/src/context/scriptAndStyleContext.js new file mode 100644 index 0000000..3a17937 --- /dev/null +++ b/src/context/scriptAndStyleContext.js @@ -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} 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>} functions + */ + #css; + /** + * @property {map} 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} 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} 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); + } + } +} \ No newline at end of file diff --git a/src/context/webTrinity.js b/src/context/webTrinity.js new file mode 100644 index 0000000..0cee54a --- /dev/null +++ b/src/context/webTrinity.js @@ -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} js + * @param {HTMLStyleElement|map>} css + */ + constructor(html = null, js = null, css = null) { + this.html = html; + this.js = js; + this.css = css; + } +}