Skip to content

Instantly share code, notes, and snippets.

@Danny-Engelman
Created July 15, 2023 07:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Danny-Engelman/267b965cfcaa1d529f80f81e1490f8c0 to your computer and use it in GitHub Desktop.
Save Danny-Engelman/267b965cfcaa1d529f80f81e1490f8c0 to your computer and use it in GitHub Desktop.
A Component Making Elements - BaseClass
// ********************************************************** 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