Created
July 15, 2023 07:29
-
-
Save Danny-Engelman/267b965cfcaa1d529f80f81e1490f8c0 to your computer and use it in GitHub Desktop.
A Component Making Elements - BaseClass
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ********************************************************** ACME_BaseClass | |
class ACME_BaseClass extends HTMLElement { | |
// ======================================================== ACME_BaseClass.$query | |
$query( | |
// selector is a DOM selector string eg. "div:not[id='1']" | |
// if starts with * then return all elements as NodeList | |
// if starts with ** then return all elements as Array | |
selector, | |
// optional 2nd parameter is the root element to query from | |
root = this.shadowRoot || this | |
) { | |
if (selector.charAt(0) === "*" || selector.charAt(1) === "*") { | |
if (selector.charAt(1) === "*") { | |
return [...root.querySelectorAll(selector.slice(2))]; | |
} else { | |
return root.querySelectorAll(selector.slice(1)); | |
} | |
} else return root.querySelector(selector); | |
} | |
// ======================================================== ACME_BaseClass.$elementHTML | |
$elementHTML({ | |
tag = "div", // HTML tag name | |
html = "", | |
attrs = "", | |
}) { | |
return `<${tag} ${attrs}>${html}</${tag}>`; | |
} | |
// ======================================================== ACME_BaseClass.$element | |
$element({ | |
tag = "div", // HTML tag name "div", if start with * or ** call $query to get element | |
props = {}, // properties (and eventlisteners) attached to element | |
attrs = [], // attributes attached to element | |
classes = [], | |
events = {}, // standard eventlisteners attached to element | |
listeners = {}, // custom eventlisteners attached to element, when added to the DOM | |
customevents = {}, // custom eventlisteners attached to element | |
prepend = [], // element.prepend(...prepend) | |
html, // element.innerHTML | |
append = [], // element.append(...append) | |
// optional override new element with existing element | |
element = tag.charAt(0) === "*" | |
? this.$query(tag) // do not create a new tag, find existing element | |
: document.createElement(tag), // else create a new tag | |
styles = {}, | |
//stuff any other properties into moreprops variable | |
...moreprops | |
}) { | |
// assign props,events and moreprops to element | |
element = Object.assign(element, { ...props, ...events, ...moreprops }); | |
// filter out empty classes | |
classes = classes.filter((x) => x.length); | |
if (classes.length) element.classList.add(...classes); | |
element.prepend(...prepend.filter(Boolean)); | |
if (html) element.innerHTML = html; | |
element.append(...append.filter(Boolean)); | |
(Array.isArray(attrs) | |
? attrs // if attrs is an Array, do a setAttribute for each attribute | |
: Object.entries(attrs) | |
) // else proces as Object | |
.map(([key, value]) => element.setAttribute(key, value)); | |
// apply styles | |
Object.entries(styles).map(([key, value]) => { | |
element.style[key] = value; | |
}); | |
// apply customevents | |
Object.entries(customevents).map(([name, handler]) => { | |
console.log(name, handler, this); | |
this.$listen_signal({ | |
name, | |
handler: handler.bind(element), // bind element scope to handler | |
eventbus: document, | |
}); | |
}); | |
// add listener to remove all eventlisteners on element | |
element.addEventListener( | |
new CustomEvent("removeEventListeners"), | |
(evt) => { | |
element.removeEventListeners(evt); | |
} | |
); | |
return element; | |
} | |
// ======================================================== ACME_BaseClass.disconnectedCallback | |
disconnectedCallback() { | |
console.warn("" + this.tagName + " disconnected"); | |
} | |
// ======================================================== ACME_BaseClass.eventbus | |
get eventbus() { | |
return this.__eventbus__ || document; | |
} | |
// ======================================================== ACME_BaseClass.connectedCallback | |
connectedCallback(...args) { | |
let scope = this; | |
//! register all methods starting with event_ as $listener | |
function registerEventMethods({ scope }) { | |
Object.getOwnPropertyNames(Object.getPrototypeOf(scope)).map( | |
(method) => { | |
let eventbus; | |
let [event, ...name] = method.split("_"); //! name becomes an Array! | |
if (event == "event") { | |
//! determine name and eventbus where to listen | |
name = name.join("_"); // make sure second _ in event_nameX_nameY is possible | |
if (name[0] === "$") { | |
// $click is registered on scope/this element | |
name = name.slice(1); // remove $ | |
eventbus = scope; // listening at current element level | |
} else { | |
// event_nameX_nameY is registered on document | |
eventbus = scope.eventbus; // listening at document level | |
} | |
// if (name == "borderColor") | |
// log( | |
// scope.nodeName + ` %c ${name} %c ${eventbus.nodeName}`, | |
// "background:gold;", | |
// "background:skyblue", | |
// method, | |
// eventbus | |
// ); | |
let useCapture = name.includes("_capture"); | |
if (useCapture) name = name.replace("_capture", ""); | |
// register the listener | |
scope.$listen_signal({ | |
name, // eventName | |
eventbus, | |
handler: scope[method].bind(scope), | |
useCapture, | |
}); | |
} | |
} | |
); // getOwnPropertyNames | |
} // registerEventMethods | |
registerEventMethods({ scope }); | |
//if (scope["render_once"]) scope["render_once"].call(scope); | |
//!if (scope["render"]) scope["render"].apply(scope,...args); | |
} | |
// ======================================================== ACME_BaseClass.$dispatch | |
$dispatch_signal({ | |
name, // EventName | |
detail = {}, // event.detail | |
// override options PER option: | |
bubbles = true, // default, bubbles up the DOM | |
composed = true, // default, escape shadowRoots | |
cancelable = true, // default, cancelable event bubbling | |
// optional overwrite whole options settings, or use already specified options | |
options = { | |
bubbles, | |
composed, | |
cancelable, | |
}, | |
eventbus = this, // default dispatch from current this element or use something like eventbus:document | |
once = false, // default .dispatchEvent option to execute a Listener once | |
}) { | |
//console.warn("%c EventName:", "background:yellow", name, [detail]); | |
window.HTMLColorPicker_Events = | |
window.HTMLColorPicker_Events || new Set(); | |
window.HTMLColorPicker_Events.add(name); | |
eventbus.dispatchEvent( | |
new CustomEvent(name, { | |
...options, // | |
detail, | |
}), | |
once // default false | |
); | |
} | |
// ======================================================== ACME_BaseClass.$emit | |
// shorthand code for $dispatch({}) | |
$emit_signal(name, detail = {}, root = this) { | |
root.$dispatch_signal({ | |
name, // eventName | |
detail, // evt.detail | |
}); | |
} | |
// ======================================================== ACME_BaseClass.$listen_signal | |
$listen_signal({ | |
name = this.nodeName, // first element is String or configuration Object{} | |
handler = () => { }, // optional handler FUNCTION, default empty function | |
eventbus = this, // at what element in the DOM the listener should be attached | |
useCapture = false, // optional, default false | |
}) { | |
eventbus.addEventListener( | |
name, | |
(evt) => handler(evt), | |
useCapture // default false | |
); | |
// record all listeners on this element | |
this._listeners = this._listeners || []; | |
this._listeners.push(() => eventbus.$removeEventListener(name, handler)); | |
} | |
// ======================================================== ACME_BaseClass.removeEventListeners | |
$removeEventListeners() { | |
this._listeners.map((x) => x()); | |
} | |
// ======================================================== ACME_BaseClass | |
} | |
// ********************************************************** ACME_BaseClass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment